- Added `OmdbProvider` to fetch movie metadata from the OMDB API. - Refactored `MetadataClient` to use `MetadataSearchCriteria` for fetching movie metadata. - Updated `MetadataClientImpl` to support fetching metadata using OMDB. - Modified `log_review` use case to utilize the new metadata fetching mechanism. - Updated tests and presentation layer to accommodate changes in metadata handling. - Added dependencies for `reqwest` and `async-trait` in relevant `Cargo.toml` files.
121 lines
3.6 KiB
Rust
121 lines
3.6 KiB
Rust
use domain::{
|
|
errors::DomainError,
|
|
events::DomainEvent,
|
|
models::{Movie, Review},
|
|
ports::MetadataSearchCriteria,
|
|
value_objects::{Comment, ExternalMetadataId, MovieTitle, Rating, ReleaseYear, UserId},
|
|
};
|
|
|
|
use crate::{commands::LogReviewCommand, context::AppContext};
|
|
|
|
pub async fn execute(ctx: &AppContext, cmd: LogReviewCommand) -> Result<(), DomainError> {
|
|
let rating = Rating::new(cmd.rating)?;
|
|
let user_id = UserId::from_uuid(cmd.user_id);
|
|
let comment = cmd.comment.clone().map(Comment::new).transpose()?;
|
|
|
|
let (movie, is_new_movie) = resolve_movie(ctx, &cmd).await?;
|
|
|
|
ctx.repository.upsert_movie(&movie).await?;
|
|
|
|
let review = Review::new(movie.id().clone(), user_id, rating, comment, cmd.watched_at)?;
|
|
let review_event = ctx.repository.save_review(&review).await?;
|
|
|
|
publish_events(ctx, &movie, is_new_movie, review_event).await?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
async fn resolve_movie(
|
|
ctx: &AppContext,
|
|
cmd: &LogReviewCommand,
|
|
) -> Result<(Movie, bool), DomainError> {
|
|
if let Some(ext_id_str) = &cmd.external_metadata_id {
|
|
if let Some(resolved) = resolve_external_movie(ctx, ext_id_str).await? {
|
|
return Ok(resolved);
|
|
}
|
|
}
|
|
|
|
resolve_manual_movie(ctx, cmd).await
|
|
}
|
|
|
|
async fn resolve_external_movie(
|
|
ctx: &AppContext,
|
|
ext_id_str: &str,
|
|
) -> Result<Option<(Movie, bool)>, DomainError> {
|
|
let tmdb_id = ExternalMetadataId::new(ext_id_str.to_string())?;
|
|
|
|
if let Some(m) = ctx.repository.get_movie_by_external_id(&tmdb_id).await? {
|
|
return Ok(Some((m, false)));
|
|
}
|
|
|
|
match ctx
|
|
.metadata_client
|
|
.fetch_movie_metadata(&MetadataSearchCriteria::ImdbId(tmdb_id))
|
|
.await
|
|
{
|
|
Ok(m) => Ok(Some((m, true))),
|
|
Err(e) => {
|
|
tracing::warn!(
|
|
"Failed to fetch from TMDB, falling back to manual entry: {:?}",
|
|
e
|
|
);
|
|
Ok(None)
|
|
}
|
|
}
|
|
}
|
|
|
|
async fn resolve_manual_movie(
|
|
ctx: &AppContext,
|
|
cmd: &LogReviewCommand,
|
|
) -> Result<(Movie, bool), DomainError> {
|
|
let title_str = cmd.manual_title.as_ref().ok_or_else(|| {
|
|
DomainError::ValidationError(
|
|
"Manual title required if TMDB fetch fails or is omitted".into(),
|
|
)
|
|
})?;
|
|
let year_val = cmd.manual_release_year.ok_or_else(|| {
|
|
DomainError::ValidationError(
|
|
"Manual release year required if TMDB fetch fails or is omitted".into(),
|
|
)
|
|
})?;
|
|
|
|
let title = MovieTitle::new(title_str.clone())?;
|
|
let release_year = ReleaseYear::new(year_val)?;
|
|
|
|
let candidates = ctx
|
|
.repository
|
|
.get_movies_by_title_and_year(&title, &release_year)
|
|
.await?;
|
|
|
|
let matched_movie = candidates
|
|
.into_iter()
|
|
.find(|m| m.is_manual_match(&title, &release_year, cmd.manual_director.as_deref()));
|
|
|
|
if let Some(existing_movie) = matched_movie {
|
|
Ok((existing_movie, false))
|
|
} else {
|
|
let new_movie = Movie::new(None, title, release_year, cmd.manual_director.clone(), None);
|
|
Ok((new_movie, true))
|
|
}
|
|
}
|
|
|
|
async fn publish_events(
|
|
ctx: &AppContext,
|
|
movie: &Movie,
|
|
is_new_movie: bool,
|
|
review_event: DomainEvent,
|
|
) -> Result<(), DomainError> {
|
|
if is_new_movie {
|
|
if let Some(ext_id) = movie.external_metadata_id() {
|
|
let discovery_event = DomainEvent::MovieDiscovered {
|
|
movie_id: movie.id().clone(),
|
|
external_metadata_id: ext_id.clone(),
|
|
};
|
|
ctx.event_publisher.publish(&discovery_event).await?;
|
|
}
|
|
}
|
|
|
|
ctx.event_publisher.publish(&review_event).await?;
|
|
Ok(())
|
|
}
|