feat: implement TMDb enrichment for movie profiles
- Add SqliteMovieProfileRepository for managing movie profiles in SQLite. - Create TmdbEnrichmentClient to fetch movie details from TMDb API. - Implement enrichment event handling with EnrichmentHandler. - Introduce periodic jobs for cleaning up expired import sessions and checking for stale movie profiles. - Update application context to include movie profile repository. - Add API endpoint to retrieve movie profiles. - Extend domain models with new structures for movie enrichment (Genre, Keyword, CastMember, CrewMember, MovieProfile). - Modify event system to include MovieEnrichmentRequested event. - Enhance tests to cover new functionality and ensure stability.
This commit is contained in:
@@ -4,7 +4,7 @@ use domain::ports::{
|
||||
AuthService, DiaryExporter, DiaryRepository, DocumentParser, EventPublisher,
|
||||
ImageStorage,
|
||||
ImportProfileRepository, ImportSessionRepository,
|
||||
MetadataClient, MovieRepository, PasswordHasher, PosterFetcherClient,
|
||||
MetadataClient, MovieProfileRepository, MovieRepository, PasswordHasher, PosterFetcherClient,
|
||||
ReviewRepository, StatsRepository, UserRepository,
|
||||
};
|
||||
|
||||
@@ -27,5 +27,6 @@ pub struct AppContext {
|
||||
pub user_repository: Arc<dyn UserRepository>,
|
||||
pub import_session_repository: Arc<dyn ImportSessionRepository>,
|
||||
pub import_profile_repository: Arc<dyn ImportProfileRepository>,
|
||||
pub movie_profile_repository: Arc<dyn MovieProfileRepository>,
|
||||
pub config: AppConfig,
|
||||
}
|
||||
|
||||
60
crates/application/src/jobs.rs
Normal file
60
crates/application/src/jobs.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
use std::time::Duration;
|
||||
|
||||
use async_trait::async_trait;
|
||||
use domain::{errors::DomainError, events::DomainEvent, ports::PeriodicJob};
|
||||
|
||||
use crate::context::AppContext;
|
||||
|
||||
pub struct ImportSessionCleanupJob {
|
||||
ctx: AppContext,
|
||||
}
|
||||
|
||||
impl ImportSessionCleanupJob {
|
||||
pub fn new(ctx: AppContext) -> Self {
|
||||
Self { ctx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PeriodicJob for ImportSessionCleanupJob {
|
||||
fn interval(&self) -> Duration {
|
||||
Duration::from_secs(3600)
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<(), DomainError> {
|
||||
let n = crate::use_cases::cleanup_expired_import_sessions::execute(&self.ctx).await?;
|
||||
tracing::info!("import session cleanup: removed {} expired sessions", n);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EnrichmentStalenessJob {
|
||||
ctx: AppContext,
|
||||
}
|
||||
|
||||
impl EnrichmentStalenessJob {
|
||||
pub fn new(ctx: AppContext) -> Self {
|
||||
Self { ctx }
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl PeriodicJob for EnrichmentStalenessJob {
|
||||
fn interval(&self) -> Duration {
|
||||
Duration::from_secs(3600)
|
||||
}
|
||||
|
||||
async fn run(&self) -> Result<(), DomainError> {
|
||||
let stale = self.ctx.movie_profile_repository.list_stale().await?;
|
||||
if stale.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
tracing::info!("enrichment scan: {} stale movies", stale.len());
|
||||
for (movie_id, external_metadata_id) in stale {
|
||||
let event = DomainEvent::MovieEnrichmentRequested { movie_id, external_metadata_id };
|
||||
self.ctx.event_publisher.publish(&event).await?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
pub mod commands;
|
||||
pub mod jobs;
|
||||
pub mod worker;
|
||||
pub mod config;
|
||||
pub mod context;
|
||||
|
||||
@@ -50,6 +50,14 @@ async fn publish_events(
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(ext_id) = movie.external_metadata_id() {
|
||||
let enrichment_event = DomainEvent::MovieEnrichmentRequested {
|
||||
movie_id: movie.id().clone(),
|
||||
external_metadata_id: ext_id.value().to_string(),
|
||||
};
|
||||
ctx.event_publisher.publish(&enrichment_event).await?;
|
||||
}
|
||||
|
||||
ctx.event_publisher.publish(&review_event).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -96,6 +96,7 @@ mod tests {
|
||||
DomainEvent::ReviewDeleted { .. } => "review_deleted",
|
||||
DomainEvent::MovieDeleted { .. } => "movie_deleted",
|
||||
DomainEvent::UserUpdated { .. } => "user_updated",
|
||||
DomainEvent::MovieEnrichmentRequested { .. } => "movie_enrichment_requested",
|
||||
};
|
||||
self.calls.lock().unwrap().push(label);
|
||||
Ok(())
|
||||
|
||||
Reference in New Issue
Block a user