feat: atomic wrapup, movie, integration writes

Ride writes: WrapUpRequested/Completed (wrapup create/set_complete),
MovieDiscovered (resolve upsert, new+ext id only), PosterSynced (poster
handler upsert), MovieDeleted (delete_movie), WatchEventIngested (save).
Deps drop event_publisher: wrapup, ingest, delete_review, watchlist add,
review logger.

Remaining .publish( sites:
announce-only: users/delete_account UserDeleted, person/get +
get_credits PersonEnrichmentRequested, jobs/enrichment_staleness,
http-axum search, movies/sync_poster + poster_sync_handler +
image-converter backfill + users/update_profile ImageStored (object
storage), activitypub federation_event_bridge (reacting to events).
rides write, deferred: social/execute (see report),
activitypub review_handler MovieEnrichmentRequested (after
save_remote_review).
This commit is contained in:
2026-10-26 01:02:34 +01:00
parent 2f133afb96
commit 6c295866a0
51 changed files with 1281 additions and 329 deletions

View File

@@ -33,18 +33,15 @@ pub async fn execute(deps: &DeleteReviewDeps, cmd: DeleteReviewCommand) -> Resul
let history = deps.diary.get_review_history(&movie_id).await?;
if history.viewings().is_empty() {
let poster_path = history.movie().poster_path().cloned();
deps.movie_command.delete_movie(&movie_id).await?;
// best-effort: movie is already deleted, so publish failure is non-fatal
if let Err(e) = deps
.event_publisher
.publish(&DomainEvent::MovieDeleted {
movie_id,
poster_path,
})
.await
{
tracing::warn!("failed to publish MovieDeleted event: {e}");
}
deps.movie_command
.delete_movie(
&movie_id,
&[DomainEvent::MovieDeleted {
movie_id: movie_id.clone(),
poster_path,
}],
)
.await?;
}
Ok(())

View File

@@ -1,8 +1,8 @@
use std::sync::Arc;
use domain::ports::{
DiaryExporter, DiaryQuery, EventPublisher, FollowGraphQuery, MovieCommand,
MovieProfileRepository, MovieQuery, ReviewRepository, UserRepository,
DiaryExporter, DiaryQuery, FollowGraphQuery, MovieCommand, MovieProfileRepository, MovieQuery,
ReviewRepository, UserRepository,
};
use crate::config::AppSettings;
@@ -11,7 +11,6 @@ pub struct DeleteReviewDeps {
pub review: Arc<dyn ReviewRepository>,
pub diary: Arc<dyn DiaryQuery>,
pub movie_command: Arc<dyn MovieCommand>,
pub event_publisher: Arc<dyn EventPublisher>,
}
pub struct EditReviewDeps {

View File

@@ -5,10 +5,7 @@ use domain::{
errors::DomainError,
events::DomainEvent,
models::Review,
ports::{
EventPublisher, MetadataClient, MovieCommand, MovieQuery, ReviewRepository,
WatchlistRepository,
},
ports::{MetadataClient, MovieCommand, MovieQuery, ReviewRepository, WatchlistRepository},
value_objects::{Comment, Rating, UserId},
};
@@ -22,7 +19,6 @@ pub struct DefaultReviewLogger {
review_repo: Arc<dyn ReviewRepository>,
watchlist_repo: Arc<dyn WatchlistRepository>,
metadata_client: Arc<dyn MetadataClient>,
event_publisher: Arc<dyn EventPublisher>,
}
impl DefaultReviewLogger {
@@ -32,7 +28,6 @@ impl DefaultReviewLogger {
review_repo: Arc<dyn ReviewRepository>,
watchlist_repo: Arc<dyn WatchlistRepository>,
metadata_client: Arc<dyn MetadataClient>,
event_publisher: Arc<dyn EventPublisher>,
) -> Self {
Self {
movie_command,
@@ -40,7 +35,6 @@ impl DefaultReviewLogger {
review_repo,
watchlist_repo,
metadata_client,
event_publisher,
}
}
}
@@ -57,13 +51,12 @@ impl ReviewLogger for DefaultReviewLogger {
self.movie_command.as_ref(),
self.movie_query.as_ref(),
self.metadata_client.as_ref(),
self.event_publisher.as_ref(),
)
.await?;
// Always upsert: even existing movies may have updated metadata
if !is_new_movie {
self.movie_command.upsert_movie(&movie).await?;
self.movie_command.upsert_movie(&movie, &[]).await?;
}
let review = Review::new(