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:
2026-07-10 03:50:43 +02:00
parent 12da356a40
commit dee013c7eb
99 changed files with 1262 additions and 896 deletions

View File

@@ -1,11 +1,11 @@
use std::sync::Arc;
use chrono::Duration;
use domain::{errors::DomainError, ports::WatchEventRepository};
use domain::{errors::DomainError, ports::WatchEventCommand};
pub async fn execute(watch_event: Arc<dyn WatchEventRepository>) -> Result<u64, DomainError> {
pub async fn execute(watch_event_command: Arc<dyn WatchEventCommand>) -> Result<u64, DomainError> {
let cutoff = chrono::Utc::now().naive_utc() - Duration::days(30);
watch_event.delete_non_pending_older_than(cutoff).await
watch_event_command.delete_non_pending_older_than(cutoff).await
}
#[cfg(test)]

View File

@@ -3,7 +3,7 @@ use std::sync::Arc;
use domain::{
errors::DomainError,
models::WatchEventStatus,
ports::WatchEventRepository,
ports::{WatchEventCommand, WatchEventQuery},
value_objects::{UserId, WatchEventId},
};
@@ -14,7 +14,8 @@ use crate::{
};
pub async fn execute(
watch_event: Arc<dyn WatchEventRepository>,
watch_event_command: Arc<dyn WatchEventCommand>,
watch_event_query: Arc<dyn WatchEventQuery>,
review_logger: Arc<dyn ReviewLogger>,
cmd: ConfirmWatchEventsCommand,
) -> Result<u32, DomainError> {
@@ -23,7 +24,7 @@ pub async fn execute(
for c in cmd.confirmations {
let event_id = WatchEventId::from_uuid(c.watch_event_id);
let event = watch_event
let event = watch_event_query
.get_by_id(&event_id)
.await?
.ok_or_else(|| DomainError::NotFound(format!("WatchEvent {}", c.watch_event_id)))?;
@@ -61,7 +62,7 @@ pub async fn execute(
review_logger.log_review(review_cmd).await?;
watch_event
watch_event_command
.update_status(&event_id, WatchEventStatus::Confirmed)
.await?;

View File

@@ -1,9 +1,10 @@
use std::sync::Arc;
use domain::ports::{EventPublisher, WatchEventRepository, WebhookTokenRepository};
use domain::ports::{EventPublisher, WatchEventCommand, WatchEventQuery, WebhookTokenRepository};
pub struct IngestWatchEventDeps {
pub webhook_token: Arc<dyn WebhookTokenRepository>,
pub watch_event: Arc<dyn WatchEventRepository>,
pub watch_event_command: Arc<dyn WatchEventCommand>,
pub watch_event_query: Arc<dyn WatchEventQuery>,
pub event_publisher: Arc<dyn EventPublisher>,
}

View File

@@ -3,14 +3,15 @@ use std::sync::Arc;
use domain::{
errors::DomainError,
models::WatchEventStatus,
ports::WatchEventRepository,
ports::{WatchEventCommand, WatchEventQuery},
value_objects::{UserId, WatchEventId},
};
use crate::integrations::commands::DismissWatchEventsCommand;
pub async fn execute(
watch_event: Arc<dyn WatchEventRepository>,
watch_event_command: Arc<dyn WatchEventCommand>,
watch_event_query: Arc<dyn WatchEventQuery>,
cmd: DismissWatchEventsCommand,
) -> Result<u32, DomainError> {
let user_id = UserId::from_uuid(cmd.user_id);
@@ -24,7 +25,7 @@ pub async fn execute(
.map(|id| WatchEventId::from_uuid(*id))
.collect();
let events = watch_event.get_by_ids(&ids).await?;
let events = watch_event_query.get_by_ids(&ids).await?;
if events.len() != ids.len() {
return Err(DomainError::NotFound(
@@ -37,7 +38,7 @@ pub async fn execute(
}
}
let count = watch_event
let count = watch_event_command
.update_status_batch(&ids, WatchEventStatus::Dismissed)
.await?;

View File

@@ -1,17 +1,17 @@
use std::sync::Arc;
use domain::{
errors::DomainError, models::WatchEvent, ports::WatchEventRepository, value_objects::UserId,
errors::DomainError, models::WatchEvent, ports::WatchEventQuery, value_objects::UserId,
};
use crate::integrations::queries::GetWatchQueueQuery;
pub async fn execute(
watch_event: Arc<dyn WatchEventRepository>,
watch_event_query: Arc<dyn WatchEventQuery>,
query: GetWatchQueueQuery,
) -> Result<Vec<WatchEvent>, DomainError> {
let user_id = UserId::from_uuid(query.user_id);
watch_event.list_pending(&user_id).await
watch_event_query.list_pending(&user_id).await
}
#[cfg(test)]

View File

@@ -30,7 +30,7 @@ pub async fn execute(
if let Some(ref ext_id) = external_metadata_id {
let one_hour_ago = chrono::Utc::now().naive_utc() - Duration::hours(1);
if deps
.watch_event
.watch_event_query
.find_duplicate(&user_id, ext_id, one_hour_ago)
.await?
{
@@ -49,7 +49,7 @@ pub async fn execute(
None,
);
deps.watch_event.save(&event).await?;
deps.watch_event_command.save(&event).await?;
let _ = deps
.event_publisher

View File

@@ -1,13 +1,10 @@
use std::sync::Arc;
use domain::ports::WatchEventRepository;
use domain::testing::InMemoryWatchEventRepository;
use crate::integrations::cleanup;
#[tokio::test]
async fn returns_zero_when_nothing_to_clean() {
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let count = cleanup::execute(watch_events).await.unwrap();

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use domain::models::{WatchEvent, WatchEventSource};
use domain::ports::{MovieRepository, WatchEventRepository};
use domain::ports::{MovieCommand, WatchEventCommand};
use domain::testing::{InMemoryWatchEventRepository, NoopEventPublisher};
use domain::value_objects::UserId;
use uuid::Uuid;
@@ -16,7 +16,7 @@ fn noop_logger() -> Arc<dyn crate::ports::ReviewLogger> {
#[tokio::test]
async fn confirms_watch_event_via_review_logger() {
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let uid = Uuid::new_v4();
let event = WatchEvent::new(
@@ -32,7 +32,8 @@ async fn confirms_watch_event_via_review_logger() {
watch_events.save(&event).await.unwrap();
let result = confirm::execute(
Arc::clone(&watch_events),
Arc::clone(&watch_events) as _,
Arc::clone(&watch_events) as _,
noop_logger(),
ConfirmWatchEventsCommand {
user_id: uid,
@@ -51,10 +52,11 @@ async fn confirms_watch_event_via_review_logger() {
#[tokio::test]
async fn empty_confirmations_returns_zero() {
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let result = confirm::execute(
Arc::clone(&watch_events),
Arc::clone(&watch_events) as _,
Arc::clone(&watch_events) as _,
noop_logger(),
ConfirmWatchEventsCommand {
user_id: Uuid::new_v4(),
@@ -69,7 +71,7 @@ async fn empty_confirmations_returns_zero() {
#[tokio::test]
async fn confirms_event_with_external_metadata_id_and_no_movie_id() {
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let uid = Uuid::new_v4();
let event = WatchEvent::new(
@@ -85,7 +87,8 @@ async fn confirms_event_with_external_metadata_id_and_no_movie_id() {
watch_events.save(&event).await.unwrap();
let result = confirm::execute(
Arc::clone(&watch_events),
Arc::clone(&watch_events) as _,
Arc::clone(&watch_events) as _,
noop_logger(),
ConfirmWatchEventsCommand {
user_id: uid,
@@ -104,7 +107,7 @@ async fn confirms_event_with_external_metadata_id_and_no_movie_id() {
#[tokio::test]
async fn rejects_other_users_event() {
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let owner = Uuid::new_v4();
let intruder = Uuid::new_v4();
@@ -121,7 +124,8 @@ async fn rejects_other_users_event() {
watch_events.save(&event).await.unwrap();
let result = confirm::execute(
Arc::clone(&watch_events),
Arc::clone(&watch_events) as _,
Arc::clone(&watch_events) as _,
noop_logger(),
ConfirmWatchEventsCommand {
user_id: intruder,
@@ -139,10 +143,11 @@ async fn rejects_other_users_event() {
#[tokio::test]
async fn fails_when_event_not_found() {
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let result = confirm::execute(
Arc::clone(&watch_events),
Arc::clone(&watch_events) as _,
Arc::clone(&watch_events) as _,
noop_logger(),
ConfirmWatchEventsCommand {
user_id: Uuid::new_v4(),
@@ -160,7 +165,7 @@ async fn fails_when_event_not_found() {
#[tokio::test]
async fn confirms_event_with_movie_id() {
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let events = NoopEventPublisher::new();
let uid = Uuid::new_v4();
let movie_uuid = Uuid::new_v4();
@@ -194,6 +199,7 @@ async fn confirms_event_with_movie_id() {
let watchlist = domain::testing::InMemoryWatchlistRepository::new();
let review_logger: Arc<dyn crate::ports::ReviewLogger> =
Arc::new(crate::diary::review_logger::DefaultReviewLogger::new(
Arc::clone(&movies) as _,
Arc::clone(&movies) as _,
Arc::clone(&reviews) as _,
Arc::clone(&watchlist) as _,
@@ -202,7 +208,8 @@ async fn confirms_event_with_movie_id() {
));
let result = confirm::execute(
Arc::clone(&watch_events),
Arc::clone(&watch_events) as _,
Arc::clone(&watch_events) as _,
review_logger,
ConfirmWatchEventsCommand {
user_id: uid,
@@ -221,7 +228,7 @@ async fn confirms_event_with_movie_id() {
#[tokio::test]
async fn confirms_event_without_movie_id_and_without_external_metadata_id() {
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let uid = Uuid::new_v4();
let event = WatchEvent::new(
@@ -237,7 +244,8 @@ async fn confirms_event_without_movie_id_and_without_external_metadata_id() {
watch_events.save(&event).await.unwrap();
let result = confirm::execute(
Arc::clone(&watch_events),
Arc::clone(&watch_events) as _,
Arc::clone(&watch_events) as _,
noop_logger(),
ConfirmWatchEventsCommand {
user_id: uid,
@@ -256,7 +264,7 @@ async fn confirms_event_without_movie_id_and_without_external_metadata_id() {
#[tokio::test]
async fn confirms_multiple_events() {
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let uid = Uuid::new_v4();
let event1 = WatchEvent::new(
@@ -285,7 +293,8 @@ async fn confirms_multiple_events() {
watch_events.save(&event2).await.unwrap();
let result = confirm::execute(
Arc::clone(&watch_events),
Arc::clone(&watch_events) as _,
Arc::clone(&watch_events) as _,
noop_logger(),
ConfirmWatchEventsCommand {
user_id: uid,
@@ -311,7 +320,7 @@ async fn confirms_multiple_events() {
#[tokio::test]
async fn confirms_event_without_year() {
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let uid = Uuid::new_v4();
let event = WatchEvent::new(
@@ -327,7 +336,8 @@ async fn confirms_event_without_year() {
watch_events.save(&event).await.unwrap();
let result = confirm::execute(
Arc::clone(&watch_events),
Arc::clone(&watch_events) as _,
Arc::clone(&watch_events) as _,
noop_logger(),
ConfirmWatchEventsCommand {
user_id: uid,

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use domain::models::{WatchEvent, WatchEventSource};
use domain::ports::WatchEventRepository;
use domain::ports::WatchEventCommand;
use domain::testing::InMemoryWatchEventRepository;
use domain::value_objects::UserId;
use uuid::Uuid;
@@ -10,10 +10,11 @@ use crate::integrations::{commands::DismissWatchEventsCommand, dismiss};
#[tokio::test]
async fn dismisses_empty_list_returns_zero() {
let events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let events = InMemoryWatchEventRepository::new();
let result = dismiss::execute(
Arc::clone(&events),
Arc::clone(&events) as _,
Arc::clone(&events) as _,
DismissWatchEventsCommand {
user_id: Uuid::new_v4(),
event_ids: vec![],
@@ -27,10 +28,11 @@ async fn dismisses_empty_list_returns_zero() {
#[tokio::test]
async fn fails_when_event_not_found() {
let events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let events = InMemoryWatchEventRepository::new();
let result = dismiss::execute(
Arc::clone(&events),
Arc::clone(&events) as _,
Arc::clone(&events) as _,
DismissWatchEventsCommand {
user_id: Uuid::new_v4(),
event_ids: vec![Uuid::new_v4()],
@@ -43,7 +45,7 @@ async fn fails_when_event_not_found() {
#[tokio::test]
async fn dismisses_existing_events() {
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let uid = Uuid::new_v4();
let user_id = UserId::from_uuid(uid);
@@ -71,7 +73,8 @@ async fn dismisses_existing_events() {
watch_events.save(&e2).await.unwrap();
let result = dismiss::execute(
Arc::clone(&watch_events),
Arc::clone(&watch_events) as _,
Arc::clone(&watch_events) as _,
DismissWatchEventsCommand {
user_id: uid,
event_ids: vec![id1, id2],

View File

@@ -2,7 +2,7 @@ use std::sync::Arc;
use chrono::Utc;
use domain::models::{WatchEvent, WatchEventSource};
use domain::ports::WatchEventRepository;
use domain::ports::WatchEventCommand;
use domain::testing::InMemoryWatchEventRepository;
use domain::value_objects::UserId;
use uuid::Uuid;
@@ -11,10 +11,10 @@ use crate::integrations::{get_queue, queries::GetWatchQueueQuery};
#[tokio::test]
async fn returns_empty_when_no_events() {
let events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let events = InMemoryWatchEventRepository::new();
let result = get_queue::execute(
Arc::clone(&events),
Arc::clone(&events) as _,
GetWatchQueueQuery {
user_id: Uuid::new_v4(),
},
@@ -27,7 +27,7 @@ async fn returns_empty_when_no_events() {
#[tokio::test]
async fn returns_pending_events() {
let events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let events = InMemoryWatchEventRepository::new();
let user_id = Uuid::new_v4();
let event = WatchEvent::new(
@@ -41,7 +41,7 @@ async fn returns_pending_events() {
);
events.save(&event).await.unwrap();
let result = get_queue::execute(Arc::clone(&events), GetWatchQueueQuery { user_id })
let result = get_queue::execute(Arc::clone(&events) as _, GetWatchQueueQuery { user_id })
.await
.unwrap();

View File

@@ -1,7 +1,7 @@
use std::sync::Arc;
use domain::models::WatchEventSource;
use domain::ports::{EventPublisher, WatchEventRepository, WebhookTokenRepository};
use domain::ports::{EventPublisher, WebhookTokenRepository};
use domain::testing::{
InMemoryWatchEventRepository, InMemoryWebhookTokenRepository, NoopEventPublisher,
};
@@ -30,7 +30,7 @@ impl domain::ports::MediaServerParser for FakeParser {
#[tokio::test]
async fn ingests_watch_event() {
let tokens: Arc<dyn WebhookTokenRepository> = InMemoryWebhookTokenRepository::new();
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let event_publisher: Arc<dyn EventPublisher> = NoopEventPublisher::new();
let user_id = Uuid::new_v4();
@@ -47,7 +47,8 @@ async fn ingests_watch_event() {
let deps = IngestWatchEventDeps {
webhook_token: Arc::clone(&tokens),
watch_event: Arc::clone(&watch_events),
watch_event_command: Arc::clone(&watch_events) as _,
watch_event_query: Arc::clone(&watch_events) as _,
event_publisher: Arc::clone(&event_publisher),
};
@@ -68,12 +69,13 @@ async fn ingests_watch_event() {
#[tokio::test]
async fn rejects_invalid_token() {
let tokens: Arc<dyn WebhookTokenRepository> = InMemoryWebhookTokenRepository::new();
let watch_events: Arc<dyn WatchEventRepository> = InMemoryWatchEventRepository::new();
let watch_events = InMemoryWatchEventRepository::new();
let event_publisher: Arc<dyn EventPublisher> = NoopEventPublisher::new();
let deps = IngestWatchEventDeps {
webhook_token: Arc::clone(&tokens),
watch_event: Arc::clone(&watch_events),
watch_event_command: Arc::clone(&watch_events) as _,
watch_event_query: Arc::clone(&watch_events) as _,
event_publisher: Arc::clone(&event_publisher),
};