use async_trait::async_trait; use domain::{ errors::DomainError, models::{MetadataSearchCriteria, Movie}, ports::MetadataClient, value_objects::{ExternalMetadataId, MovieId, 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?; let movie_id = MovieId::from_external(&pm.imdb_id); Ok(Movie::from_persistence( movie_id, 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( config: &config::MetadataConfig, ) -> anyhow::Result> { use anyhow::Context; if let Some(key) = config.tmdb_api_key.clone() { Ok(std::sync::Arc::new(MetadataClientImpl::new_tmdb(key))) } else { let key = config .omdb_api_key .clone() .context("either TMDB_API_KEY or OMDB_API_KEY must be set")?; Ok(std::sync::Arc::new(MetadataClientImpl::new_omdb(key))) } }