refactor: remaining MEDIUM — CQRS splits, DI Deps, profile dedup, event Value, response enum
M1: MovieRepository→MovieCommand/MovieQuery, WatchEventRepository→ WatchEventCommand/WatchEventQuery M2: goals/ and import/ use Deps structs M7: extract upload_image helper in update_profile M8: FederationDeliveryRequested activity_json String→serde_json::Value M11: UserProfileResponse uses ProfileViewData enum
This commit is contained in:
@@ -75,7 +75,8 @@ pub async fn migrate(pool: &SqlitePool) -> Result<(), domain::errors::DomainErro
|
||||
|
||||
pub struct SqliteWireOutput {
|
||||
pub pool: SqlitePool,
|
||||
pub movie: std::sync::Arc<dyn domain::ports::MovieRepository>,
|
||||
pub movie_command: std::sync::Arc<dyn domain::ports::MovieCommand>,
|
||||
pub movie_query: std::sync::Arc<dyn domain::ports::MovieQuery>,
|
||||
pub review: std::sync::Arc<dyn domain::ports::ReviewRepository>,
|
||||
pub diary: std::sync::Arc<dyn domain::ports::DiaryRepository>,
|
||||
pub stats: std::sync::Arc<dyn domain::ports::StatsRepository>,
|
||||
@@ -119,9 +120,12 @@ pub async fn wire(database_url: &str) -> anyhow::Result<SqliteWireOutput> {
|
||||
pool.clone(),
|
||||
));
|
||||
|
||||
let movie_repo = std::sync::Arc::new(SqliteMovieRepository::new(pool.clone()));
|
||||
|
||||
Ok(SqliteWireOutput {
|
||||
pool: pool.clone(),
|
||||
movie: std::sync::Arc::new(SqliteMovieRepository::new(pool.clone())) as _,
|
||||
movie_command: movie_repo.clone() as _,
|
||||
movie_query: movie_repo as _,
|
||||
review: std::sync::Arc::new(SqliteReviewRepository::new(pool.clone())) as _,
|
||||
diary: std::sync::Arc::new(SqliteDiaryRepository::new(pool.clone())) as _,
|
||||
stats: std::sync::Arc::new(SqliteStatsRepository::new(pool.clone())) as _,
|
||||
|
||||
@@ -5,7 +5,7 @@ use domain::{
|
||||
Movie, MovieFilter, MovieSummary,
|
||||
collections::{PageParams, Paginated},
|
||||
},
|
||||
ports::MovieRepository,
|
||||
ports::{MovieCommand, MovieQuery},
|
||||
value_objects::{ExternalMetadataId, MovieId, MovieTitle, ReleaseYear},
|
||||
};
|
||||
use sqlx::SqlitePool;
|
||||
@@ -28,7 +28,51 @@ impl SqliteMovieRepository {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl MovieRepository for SqliteMovieRepository {
|
||||
impl MovieCommand for SqliteMovieRepository {
|
||||
async fn upsert_movie(&self, movie: &Movie) -> Result<(), DomainError> {
|
||||
let id = movie.id().value().to_string();
|
||||
let external_metadata_id = movie.external_metadata_id().map(|e| e.value().to_string());
|
||||
let title = movie.title().value();
|
||||
let release_year = movie.release_year().value() as i64;
|
||||
let director = movie.director();
|
||||
let poster_path = movie.poster_path().map(|p| p.value().to_string());
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO movies (id, external_metadata_id, title, release_year, director, poster_path)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
external_metadata_id = excluded.external_metadata_id,
|
||||
title = excluded.title,
|
||||
release_year = excluded.release_year,
|
||||
director = excluded.director,
|
||||
poster_path = excluded.poster_path",
|
||||
)
|
||||
.bind(&id)
|
||||
.bind(&external_metadata_id)
|
||||
.bind(title)
|
||||
.bind(release_year)
|
||||
.bind(director)
|
||||
.bind(&poster_path)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_movie(&self, movie_id: &MovieId) -> Result<(), DomainError> {
|
||||
let id = movie_id.value().to_string();
|
||||
sqlx::query("DELETE FROM movies WHERE id = ?")
|
||||
.bind(&id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl MovieQuery for SqliteMovieRepository {
|
||||
async fn get_movie_by_external_id(
|
||||
&self,
|
||||
external_metadata_id: &ExternalMetadataId,
|
||||
@@ -81,47 +125,6 @@ impl MovieRepository for SqliteMovieRepository {
|
||||
.collect()
|
||||
}
|
||||
|
||||
async fn upsert_movie(&self, movie: &Movie) -> Result<(), DomainError> {
|
||||
let id = movie.id().value().to_string();
|
||||
let external_metadata_id = movie.external_metadata_id().map(|e| e.value().to_string());
|
||||
let title = movie.title().value();
|
||||
let release_year = movie.release_year().value() as i64;
|
||||
let director = movie.director();
|
||||
let poster_path = movie.poster_path().map(|p| p.value().to_string());
|
||||
|
||||
sqlx::query(
|
||||
"INSERT INTO movies (id, external_metadata_id, title, release_year, director, poster_path)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
ON CONFLICT(id) DO UPDATE SET
|
||||
external_metadata_id = excluded.external_metadata_id,
|
||||
title = excluded.title,
|
||||
release_year = excluded.release_year,
|
||||
director = excluded.director,
|
||||
poster_path = excluded.poster_path",
|
||||
)
|
||||
.bind(&id)
|
||||
.bind(&external_metadata_id)
|
||||
.bind(title)
|
||||
.bind(release_year)
|
||||
.bind(director)
|
||||
.bind(&poster_path)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn delete_movie(&self, movie_id: &MovieId) -> Result<(), DomainError> {
|
||||
let id = movie_id.value().to_string();
|
||||
sqlx::query("DELETE FROM movies WHERE id = ?")
|
||||
.bind(&id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(Self::map_err)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn existing_external_ids(
|
||||
&self,
|
||||
ids: &[ExternalMetadataId],
|
||||
|
||||
@@ -2,7 +2,7 @@ use async_trait::async_trait;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
models::{PersistedWatchEvent, WatchEvent, WatchEventSource, WatchEventStatus, WebhookToken},
|
||||
ports::{WatchEventRepository, WebhookTokenRepository},
|
||||
ports::{WatchEventCommand, WatchEventQuery, WebhookTokenRepository},
|
||||
value_objects::{MovieId, UserId, WatchEventId, WebhookTokenId},
|
||||
};
|
||||
use sqlx::{Row, SqlitePool};
|
||||
@@ -38,7 +38,7 @@ impl SqliteWatchEventRepository {
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WatchEventRepository for SqliteWatchEventRepository {
|
||||
impl WatchEventCommand for SqliteWatchEventRepository {
|
||||
async fn save(&self, event: &WatchEvent) -> Result<(), DomainError> {
|
||||
let id = event.id().value().to_string();
|
||||
let user_id = event.user_id().value().to_string();
|
||||
@@ -88,6 +88,44 @@ impl WatchEventRepository for SqliteWatchEventRepository {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn update_status_batch(
|
||||
&self,
|
||||
ids: &[WatchEventId],
|
||||
status: WatchEventStatus,
|
||||
) -> Result<u64, DomainError> {
|
||||
if ids.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
let placeholders: Vec<&str> = ids.iter().map(|_| "?").collect();
|
||||
let sql = format!(
|
||||
"UPDATE watch_events SET status = ? WHERE id IN ({})",
|
||||
placeholders.join(",")
|
||||
);
|
||||
let mut q = sqlx::query(&sql).bind(status.to_string());
|
||||
for id in ids {
|
||||
q = q.bind(id.value().to_string());
|
||||
}
|
||||
let result = q.execute(&self.pool).await.map_err(map_err)?;
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
|
||||
async fn delete_non_pending_older_than(
|
||||
&self,
|
||||
before: chrono::NaiveDateTime,
|
||||
) -> Result<u64, DomainError> {
|
||||
let before_str = datetime_to_str(&before);
|
||||
let result =
|
||||
sqlx::query("DELETE FROM watch_events WHERE status != 'pending' AND created_at < ?")
|
||||
.bind(&before_str)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(map_err)?;
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl WatchEventQuery for SqliteWatchEventRepository {
|
||||
async fn list_pending(&self, user_id: &UserId) -> Result<Vec<WatchEvent>, DomainError> {
|
||||
let uid = user_id.value().to_string();
|
||||
|
||||
@@ -141,27 +179,6 @@ impl WatchEventRepository for SqliteWatchEventRepository {
|
||||
rows.iter().map(row_to_watch_event).collect()
|
||||
}
|
||||
|
||||
async fn update_status_batch(
|
||||
&self,
|
||||
ids: &[WatchEventId],
|
||||
status: WatchEventStatus,
|
||||
) -> Result<u64, DomainError> {
|
||||
if ids.is_empty() {
|
||||
return Ok(0);
|
||||
}
|
||||
let placeholders: Vec<&str> = ids.iter().map(|_| "?").collect();
|
||||
let sql = format!(
|
||||
"UPDATE watch_events SET status = ? WHERE id IN ({})",
|
||||
placeholders.join(",")
|
||||
);
|
||||
let mut q = sqlx::query(&sql).bind(status.to_string());
|
||||
for id in ids {
|
||||
q = q.bind(id.value().to_string());
|
||||
}
|
||||
let result = q.execute(&self.pool).await.map_err(map_err)?;
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
|
||||
async fn find_duplicate(
|
||||
&self,
|
||||
user_id: &UserId,
|
||||
@@ -186,20 +203,6 @@ impl WatchEventRepository for SqliteWatchEventRepository {
|
||||
|
||||
Ok(count > 0)
|
||||
}
|
||||
|
||||
async fn delete_non_pending_older_than(
|
||||
&self,
|
||||
before: chrono::NaiveDateTime,
|
||||
) -> Result<u64, DomainError> {
|
||||
let before_str = datetime_to_str(&before);
|
||||
let result =
|
||||
sqlx::query("DELETE FROM watch_events WHERE status != 'pending' AND created_at < ?")
|
||||
.bind(&before_str)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(map_err)?;
|
||||
Ok(result.rows_affected())
|
||||
}
|
||||
}
|
||||
|
||||
fn row_to_watch_event(row: &sqlx::sqlite::SqliteRow) -> Result<WatchEvent, DomainError> {
|
||||
|
||||
Reference in New Issue
Block a user