feat: atomic goal, watchlist, profile writes
This commit is contained in:
@@ -94,19 +94,17 @@ impl ReviewLogger for DefaultReviewLogger {
|
||||
events.push(review_event);
|
||||
self.review_repo.save_review(&review, &events).await?;
|
||||
|
||||
let was_on_watchlist = self
|
||||
.watchlist_repo
|
||||
.remove_if_present(review.user_id(), review.movie_id())
|
||||
.await?;
|
||||
if was_on_watchlist {
|
||||
let _ = self
|
||||
.event_publisher
|
||||
.publish(&DomainEvent::WatchlistEntryRemoved {
|
||||
// The removal event is recorded only if an entry was actually removed.
|
||||
self.watchlist_repo
|
||||
.remove_if_present(
|
||||
review.user_id(),
|
||||
review.movie_id(),
|
||||
&[DomainEvent::WatchlistEntryRemoved {
|
||||
user_id: review.user_id().clone(),
|
||||
movie_id: review.movie_id().clone(),
|
||||
})
|
||||
.await;
|
||||
}
|
||||
}],
|
||||
)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -29,19 +29,16 @@ pub async fn execute(
|
||||
cmd.target_count,
|
||||
GoalType::Movies,
|
||||
)?;
|
||||
deps.goal_command.save(&g).await?;
|
||||
let event = DomainEvent::GoalCreated {
|
||||
goal_id: g.id().clone(),
|
||||
user_id: user_id.clone(),
|
||||
year: cmd.year,
|
||||
target_count: cmd.target_count,
|
||||
};
|
||||
deps.goal_command.save(&g, &[event]).await?;
|
||||
|
||||
let current_count = deps.stats.count_reviews_in_year(&user_id, cmd.year).await?;
|
||||
|
||||
deps.event_publisher
|
||||
.publish(&DomainEvent::GoalCreated {
|
||||
goal_id: g.id().clone(),
|
||||
user_id,
|
||||
year: cmd.year,
|
||||
target_count: cmd.target_count,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(GoalWithProgress {
|
||||
goal: g,
|
||||
current_count,
|
||||
|
||||
@@ -11,15 +11,12 @@ pub async fn execute(deps: &GoalCommandDeps, cmd: DeleteGoalCommand) -> Result<(
|
||||
.await?
|
||||
.ok_or_else(|| DomainError::NotFound(format!("Goal for year {}", cmd.year)))?;
|
||||
|
||||
deps.goal_command.delete(g.id(), &user_id).await?;
|
||||
|
||||
deps.event_publisher
|
||||
.publish(&DomainEvent::GoalDeleted {
|
||||
goal_id: g.id().clone(),
|
||||
user_id,
|
||||
year: cmd.year,
|
||||
})
|
||||
.await?;
|
||||
let event = DomainEvent::GoalDeleted {
|
||||
goal_id: g.id().clone(),
|
||||
user_id: user_id.clone(),
|
||||
year: cmd.year,
|
||||
};
|
||||
deps.goal_command.delete(g.id(), &user_id, &[event]).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::ports::{EventPublisher, GoalCommand, GoalQuery, StatsRepository};
|
||||
use domain::ports::{GoalCommand, GoalQuery, StatsRepository};
|
||||
|
||||
pub struct GoalCommandDeps {
|
||||
pub goal_command: Arc<dyn GoalCommand>,
|
||||
pub goal_query: Arc<dyn GoalQuery>,
|
||||
pub stats: Arc<dyn StatsRepository>,
|
||||
pub event_publisher: Arc<dyn EventPublisher>,
|
||||
}
|
||||
|
||||
pub struct GoalQueryDeps {
|
||||
|
||||
@@ -17,19 +17,16 @@ pub async fn execute(
|
||||
.ok_or_else(|| DomainError::NotFound(format!("Goal for year {}", cmd.year)))?;
|
||||
|
||||
g.update_target(cmd.target_count)?;
|
||||
deps.goal_command.update(&g).await?;
|
||||
let event = DomainEvent::GoalUpdated {
|
||||
goal_id: g.id().clone(),
|
||||
user_id: user_id.clone(),
|
||||
year: cmd.year,
|
||||
target_count: cmd.target_count,
|
||||
};
|
||||
deps.goal_command.update(&g, &[event]).await?;
|
||||
|
||||
let current_count = deps.stats.count_reviews_in_year(&user_id, cmd.year).await?;
|
||||
|
||||
deps.event_publisher
|
||||
.publish(&DomainEvent::GoalUpdated {
|
||||
goal_id: g.id().clone(),
|
||||
user_id,
|
||||
year: cmd.year,
|
||||
target_count: cmd.target_count,
|
||||
})
|
||||
.await?;
|
||||
|
||||
Ok(GoalWithProgress {
|
||||
goal: g,
|
||||
current_count,
|
||||
|
||||
@@ -58,7 +58,6 @@ pub struct GetCurrentProfileDeps {
|
||||
|
||||
pub struct UpdateProfileFieldsDeps {
|
||||
pub profile_fields: Arc<dyn UserProfileFieldsRepository>,
|
||||
pub event_publisher: Arc<dyn EventPublisher>,
|
||||
}
|
||||
|
||||
pub struct GetSettingsDeps {
|
||||
|
||||
@@ -91,6 +91,16 @@ pub async fn execute(
|
||||
}
|
||||
});
|
||||
|
||||
let mut events = vec![DomainEvent::UserUpdated {
|
||||
user_id: user_id.clone(),
|
||||
}];
|
||||
if let Some(new_actor_url) = moved_to {
|
||||
events.push(DomainEvent::UserAccountMoved {
|
||||
user_id: user_id.clone(),
|
||||
new_actor_url,
|
||||
});
|
||||
}
|
||||
|
||||
deps.user
|
||||
.update_profile(
|
||||
&user_id,
|
||||
@@ -102,24 +112,9 @@ pub async fn execute(
|
||||
also_known_as: cmd.also_known_as,
|
||||
profile_fields: vec![],
|
||||
},
|
||||
&events,
|
||||
)
|
||||
.await?;
|
||||
|
||||
deps.event_publisher
|
||||
.publish(&DomainEvent::UserUpdated {
|
||||
user_id: user_id.clone(),
|
||||
})
|
||||
.await?;
|
||||
|
||||
if let Some(new_actor_url) = moved_to {
|
||||
let _ = deps
|
||||
.event_publisher
|
||||
.publish(&DomainEvent::UserAccountMoved {
|
||||
user_id,
|
||||
new_actor_url,
|
||||
})
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ pub async fn execute(
|
||||
) -> Result<(), DomainError> {
|
||||
UserProfile::validate_custom_fields(&cmd.fields)?;
|
||||
let user_id = UserId::from_uuid(cmd.user_id);
|
||||
deps.profile_fields.set_fields(&user_id, cmd.fields).await?;
|
||||
deps.event_publisher
|
||||
.publish(&DomainEvent::UserUpdated { user_id })
|
||||
.await?;
|
||||
Ok(())
|
||||
let event = DomainEvent::UserUpdated {
|
||||
user_id: user_id.clone(),
|
||||
};
|
||||
deps.profile_fields
|
||||
.set_fields(&user_id, cmd.fields, &[event])
|
||||
.await
|
||||
}
|
||||
|
||||
@@ -23,19 +23,15 @@ pub async fn execute(
|
||||
.await?;
|
||||
|
||||
let entry = WatchlistEntry::new(user_id.clone(), movie.id().clone());
|
||||
deps.watchlist.add(&entry).await?;
|
||||
|
||||
let _ = deps
|
||||
.event_publisher
|
||||
.publish(&DomainEvent::WatchlistEntryAdded {
|
||||
user_id,
|
||||
movie_id: movie.id().clone(),
|
||||
movie_title: movie.title().value().to_string(),
|
||||
release_year: movie.release_year().value(),
|
||||
external_metadata_id: movie.external_metadata_id().map(|e| e.value().to_string()),
|
||||
added_at: entry.added_at,
|
||||
})
|
||||
.await;
|
||||
let event = DomainEvent::WatchlistEntryAdded {
|
||||
user_id,
|
||||
movie_id: movie.id().clone(),
|
||||
movie_title: movie.title().value().to_string(),
|
||||
release_year: movie.release_year().value(),
|
||||
external_metadata_id: movie.external_metadata_id().map(|e| e.value().to_string()),
|
||||
added_at: entry.added_at,
|
||||
};
|
||||
deps.watchlist.add(&entry, &[event]).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -29,5 +29,4 @@ pub struct IsOnWatchlistDeps {
|
||||
|
||||
pub struct RemoveFromWatchlistDeps {
|
||||
pub watchlist: Arc<dyn WatchlistRepository>,
|
||||
pub event_publisher: Arc<dyn EventPublisher>,
|
||||
}
|
||||
|
||||
@@ -13,12 +13,11 @@ pub async fn execute(
|
||||
) -> Result<(), DomainError> {
|
||||
let user_id = UserId::from_uuid(cmd.user_id);
|
||||
let movie_id = MovieId::from_uuid(cmd.movie_id);
|
||||
deps.watchlist.remove(&user_id, &movie_id).await?;
|
||||
|
||||
let _ = deps
|
||||
.event_publisher
|
||||
.publish(&DomainEvent::WatchlistEntryRemoved { user_id, movie_id })
|
||||
.await;
|
||||
let event = DomainEvent::WatchlistEntryRemoved {
|
||||
user_id: user_id.clone(),
|
||||
movie_id: movie_id.clone(),
|
||||
};
|
||||
deps.watchlist.remove(&user_id, &movie_id, &[event]).await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user