use async_trait::async_trait; use domain::{ errors::DomainError, models::Movie, ports::{MetadataClient, MetadataSearchCriteria}, value_objects::{ExternalMetadataId, MovieTitle, PosterUrl, ReleaseYear}, }; mod omdb; mod tmdb; pub(crate) struct ProviderMovie { pub imdb_id: ExternalMetadataId, pub title: MovieTitle, pub release_year: ReleaseYear, pub director: Option, pub poster_url: Option, } #[async_trait] pub(crate) trait MetadataProvider: Send + Sync { async fn fetch(&self, criteria: &MetadataSearchCriteria) -> Result; } pub struct MetadataClientImpl { provider: Box, } impl MetadataClientImpl { pub fn new_omdb(api_key: String) -> Self { Self { provider: Box::new(omdb::OmdbProvider::new(api_key)), } } pub fn new_tmdb(api_key: String) -> Self { Self { provider: Box::new(tmdb::TmdbProvider::new(api_key)), } } } #[async_trait] impl MetadataClient for MetadataClientImpl { async fn fetch_movie_metadata( &self, criteria: &MetadataSearchCriteria, ) -> Result { let pm = self.provider.fetch(criteria).await?; Ok(Movie::new( Some(pm.imdb_id), pm.title, pm.release_year, pm.director, None, )) } async fn get_poster_url( &self, external_metadata_id: &ExternalMetadataId, ) -> Result, DomainError> { let criteria = MetadataSearchCriteria::ImdbId(external_metadata_id.clone()); let pm = self.provider.fetch(&criteria).await?; Ok(pm.poster_url) } } pub fn create() -> anyhow::Result> { use anyhow::Context; if let Ok(key) = std::env::var("TMDB_API_KEY") { Ok(std::sync::Arc::new(MetadataClientImpl::new_tmdb(key))) } else { let key = std::env::var("OMDB_API_KEY") .context("either TMDB_API_KEY or OMDB_API_KEY must be set")?; Ok(std::sync::Arc::new(MetadataClientImpl::new_omdb(key))) } }