feat: atomic review writes with events

This commit is contained in:
2026-10-26 00:40:43 +01:00
parent 0236a488fa
commit c8a1e8bfe4
19 changed files with 440 additions and 117 deletions

View File

@@ -1,6 +1,7 @@
use async_trait::async_trait;
use domain::{
errors::DomainError,
events::DomainEvent,
models::{Review, ReviewSource},
ports::ReviewRepository,
value_objects::{ReviewId, UserId},
@@ -10,7 +11,7 @@ use sqlx::SqlitePool;
use rows::ReviewRow;
pub(crate) mod rows;
use crate::shared::datetime_to_str;
use crate::shared::{datetime_to_str, enqueue_events};
pub struct SqliteReviewRepository {
pool: SqlitePool,
@@ -24,7 +25,11 @@ impl SqliteReviewRepository {
#[async_trait]
impl ReviewRepository for SqliteReviewRepository {
async fn save_review(&self, review: &Review) -> Result<(), DomainError> {
async fn save_review(
&self,
review: &Review,
events: &[DomainEvent],
) -> Result<(), DomainError> {
let id = review.id().value().to_string();
let movie_id = review.movie_id().value().to_string();
let user_id = review.user_id().value().to_string();
@@ -37,6 +42,11 @@ impl ReviewRepository for SqliteReviewRepository {
ReviewSource::Remote { actor_url } => Some(actor_url.clone()),
};
let mut tx = self
.pool
.begin()
.await
.map_err(crate::shared::map_sqlx_error)?;
sqlx::query(
"INSERT INTO reviews (id, movie_id, user_id, rating, comment, watched_at, created_at, remote_actor_url, watch_medium, spoilers)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
@@ -51,11 +61,11 @@ impl ReviewRepository for SqliteReviewRepository {
.bind(&remote_actor_url)
.bind(review.watch_medium().map(|wm| wm.to_string()))
.bind(review.spoilers())
.execute(&self.pool)
.execute(&mut *tx)
.await
.map_err(crate::shared::map_sqlx_error)?;
Ok(())
enqueue_events(&mut tx, events).await?;
tx.commit().await.map_err(crate::shared::map_sqlx_error)
}
async fn get_review_by_id(&self, review_id: &ReviewId) -> Result<Option<Review>, DomainError> {
@@ -72,13 +82,22 @@ impl ReviewRepository for SqliteReviewRepository {
.transpose()
}
async fn update_review(&self, review: &Review) -> Result<(), DomainError> {
async fn update_review(
&self,
review: &Review,
events: &[DomainEvent],
) -> Result<(), DomainError> {
let id = review.id().value().to_string();
let rating = review.rating().value() as i64;
let comment = review.comment().map(|c| c.value().to_string());
let watched_at = datetime_to_str(review.watched_at());
let watch_medium = review.watch_medium().map(|wm| wm.to_string());
let mut tx = self
.pool
.begin()
.await
.map_err(crate::shared::map_sqlx_error)?;
sqlx::query(
"UPDATE reviews SET rating = ?, comment = ?, watched_at = ?, watch_medium = ?, spoilers = ? WHERE id = ?",
)
@@ -88,21 +107,31 @@ impl ReviewRepository for SqliteReviewRepository {
.bind(&watch_medium)
.bind(review.spoilers())
.bind(&id)
.execute(&self.pool)
.execute(&mut *tx)
.await
.map_err(crate::shared::map_sqlx_error)?;
Ok(())
enqueue_events(&mut tx, events).await?;
tx.commit().await.map_err(crate::shared::map_sqlx_error)
}
async fn delete_review(&self, review_id: &ReviewId) -> Result<(), DomainError> {
async fn delete_review(
&self,
review_id: &ReviewId,
events: &[DomainEvent],
) -> Result<(), DomainError> {
let id = review_id.value().to_string();
sqlx::query("DELETE FROM reviews WHERE id = ?")
.bind(&id)
.execute(&self.pool)
let mut tx = self
.pool
.begin()
.await
.map_err(crate::shared::map_sqlx_error)?;
Ok(())
sqlx::query("DELETE FROM reviews WHERE id = ?")
.bind(&id)
.execute(&mut *tx)
.await
.map_err(crate::shared::map_sqlx_error)?;
enqueue_events(&mut tx, events).await?;
tx.commit().await.map_err(crate::shared::map_sqlx_error)
}
async fn get_all_reviews_for_user(&self, user_id: &UserId) -> Result<Vec<Review>, DomainError> {