From 57520c00f3066280ed8f8195b95f8adab376852d Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Thu, 11 Jun 2026 23:18:28 +0200 Subject: [PATCH] refactor: move AppContext to presentation crate, structurally enforce boundary --- Makefile | 11 +- .../tmdb-enrichment/src/movie_handler.rs | 5 +- crates/application/src/auth/tests/logout.rs | 12 +- crates/application/src/auth/tests/refresh.rs | 3 +- crates/application/src/diary/export_diary.rs | 4 +- .../src/diary/tests/delete_review.rs | 4 +- .../src/diary/tests/get_activity_feed.rs | 7 +- .../src/diary/tests/get_movie_social_page.rs | 3 +- crates/application/src/goals/create.rs | 8 +- crates/application/src/goals/get.rs | 12 +- crates/application/src/goals/list.rs | 8 +- crates/application/src/goals/update.rs | 4 +- .../application/src/import/create_session.rs | 4 +- .../application/src/import/list_profiles.rs | 5 +- .../src/import/tests/list_profiles.rs | 4 +- .../src/integrations/generate_token.rs | 4 +- .../application/src/integrations/get_queue.rs | 4 +- .../src/integrations/get_tokens.rs | 4 +- crates/application/src/integrations/ingest.rs | 5 +- .../src/integrations/tests/ingest.rs | 4 +- crates/application/src/jobs/import_cleanup.rs | 5 +- .../src/jobs/watch_event_cleanup.rs | 5 +- crates/application/src/jobs/wrapup.rs | 5 +- crates/application/src/lib.rs | 1 - crates/application/src/movies/sync_poster.rs | 11 +- .../src/movies/tests/sync_poster.rs | 5 +- crates/application/src/search/execute.rs | 2 +- crates/application/src/test_helpers.rs | 54 +---- crates/application/src/users/get_settings.rs | 4 +- crates/application/src/users/get_users.rs | 6 +- .../src/users/tests/get_settings.rs | 4 +- .../application/src/users/tests/get_users.rs | 4 +- .../src/users/tests/update_settings.rs | 3 +- .../application/src/users/update_profile.rs | 5 +- .../src/users/update_profile_fields.rs | 6 +- crates/application/src/watchlist/add.rs | 5 +- .../src/wrapup/handle_requested.rs | 4 +- .../src/context.rs | 4 +- crates/presentation/src/handlers/diary.rs | 19 +- crates/presentation/src/handlers/import.rs | 25 +- .../presentation/src/handlers/integrations.rs | 4 +- crates/presentation/src/handlers/movies.rs | 23 +- crates/presentation/src/handlers/webhook.rs | 16 +- crates/presentation/src/handlers/wrapup.rs | 22 +- crates/presentation/src/lib.rs | 1 + crates/presentation/src/main.rs | 6 +- crates/presentation/src/state.rs | 2 +- crates/presentation/src/tests/extractors.rs | 6 +- crates/presentation/tests/api_test.rs | 6 +- crates/worker/src/db.rs | 49 +--- crates/worker/src/main.rs | 218 +++++++----------- 51 files changed, 268 insertions(+), 377 deletions(-) rename crates/{application => presentation}/src/context.rs (97%) diff --git a/Makefile b/Makefile index e2f26a7..4b5fc2b 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,18 @@ .DEFAULT_GOAL := check # Run the full local check suite — same order as CI would. -check: fmt-check clippy test +check: fmt-check clippy test check-appcontext @echo "✅ All checks passed" +# Enforce that no application use case imports AppContext (god-object guard). +check-appcontext: + @if grep -rn "AppContext" crates/application/src --include="*.rs" | grep -q .; then \ + echo "❌ AppContext found in application crate:"; \ + grep -rn "AppContext" crates/application/src --include="*.rs"; \ + exit 1; \ + fi + @echo "✅ No AppContext in application crate" + # Apply rustfmt to all files. fmt: cargo fmt diff --git a/crates/adapters/tmdb-enrichment/src/movie_handler.rs b/crates/adapters/tmdb-enrichment/src/movie_handler.rs index 8484431..9fb2247 100644 --- a/crates/adapters/tmdb-enrichment/src/movie_handler.rs +++ b/crates/adapters/tmdb-enrichment/src/movie_handler.rs @@ -1,10 +1,7 @@ use std::sync::Arc; use application::movies::{ - commands::EnrichMovieCommand, - deps::EnrichMovieDeps, - enrich_movie, - request_enrichment, + commands::EnrichMovieCommand, deps::EnrichMovieDeps, enrich_movie, request_enrichment, }; use async_trait::async_trait; use domain::{ diff --git a/crates/application/src/auth/tests/logout.rs b/crates/application/src/auth/tests/logout.rs index 51fef4c..1fdaaa0 100644 --- a/crates/application/src/auth/tests/logout.rs +++ b/crates/application/src/auth/tests/logout.rs @@ -7,8 +7,9 @@ use crate::{ auth::{ commands::RegisterCommand, deps::{LoginDeps, RefreshDeps, RegisterDeps}, - login, logout, refresh, register, + login, logout, queries::LoginQuery, + refresh, register, }, test_helpers::TestContextBuilder, }; @@ -52,12 +53,9 @@ async fn logout_revokes_refresh_token() { .await .unwrap(); - logout::execute( - b.refresh_session_repo.clone(), - &login_result.refresh_token, - ) - .await - .unwrap(); + logout::execute(b.refresh_session_repo.clone(), &login_result.refresh_token) + .await + .unwrap(); let refresh_deps = RefreshDeps { refresh_session: b.refresh_session_repo.clone(), diff --git a/crates/application/src/auth/tests/refresh.rs b/crates/application/src/auth/tests/refresh.rs index 7a33b23..34f0796 100644 --- a/crates/application/src/auth/tests/refresh.rs +++ b/crates/application/src/auth/tests/refresh.rs @@ -7,8 +7,9 @@ use crate::{ auth::{ commands::RegisterCommand, deps::{LoginDeps, RefreshDeps, RegisterDeps}, - login, refresh, register, + login, queries::LoginQuery, + refresh, register, }, test_helpers::TestContextBuilder, }; diff --git a/crates/application/src/diary/export_diary.rs b/crates/application/src/diary/export_diary.rs index e904ecc..0eda689 100644 --- a/crates/application/src/diary/export_diary.rs +++ b/crates/application/src/diary/export_diary.rs @@ -16,5 +16,7 @@ pub async fn execute( let entries = diary .get_user_history(&UserId::from_uuid(query.user_id)) .await?; - diary_exporter.serialize_entries(&entries, query.format).await + diary_exporter + .serialize_entries(&entries, query.format) + .await } diff --git a/crates/application/src/diary/tests/delete_review.rs b/crates/application/src/diary/tests/delete_review.rs index 404a9ff..c7c40fc 100644 --- a/crates/application/src/diary/tests/delete_review.rs +++ b/crates/application/src/diary/tests/delete_review.rs @@ -12,9 +12,7 @@ use domain::{ }; use crate::{ - diary::commands::DeleteReviewCommand, - diary::delete_review, - diary::deps::DeleteReviewDeps, + diary::commands::DeleteReviewCommand, diary::delete_review, diary::deps::DeleteReviewDeps, }; fn make_movie() -> Movie { diff --git a/crates/application/src/diary/tests/get_activity_feed.rs b/crates/application/src/diary/tests/get_activity_feed.rs index e498611..c5bd601 100644 --- a/crates/application/src/diary/tests/get_activity_feed.rs +++ b/crates/application/src/diary/tests/get_activity_feed.rs @@ -5,11 +5,8 @@ use domain::errors::DomainError; use domain::testing::{FakeDiaryRepository, NoopSocialQueryPort}; use crate::{ - config::AppConfig, - diary::deps::GetActivityFeedDeps, - diary::get_activity_feed, - diary::queries::GetActivityFeedQuery, - test_helpers::TestContextBuilder, + config::AppConfig, diary::deps::GetActivityFeedDeps, diary::get_activity_feed, + diary::queries::GetActivityFeedQuery, test_helpers::TestContextBuilder, }; fn default_deps() -> GetActivityFeedDeps { diff --git a/crates/application/src/diary/tests/get_movie_social_page.rs b/crates/application/src/diary/tests/get_movie_social_page.rs index 849c57f..1252b2b 100644 --- a/crates/application/src/diary/tests/get_movie_social_page.rs +++ b/crates/application/src/diary/tests/get_movie_social_page.rs @@ -10,8 +10,7 @@ use domain::{ }; use crate::{ - diary::deps::GetMovieSocialPageDeps, - diary::get_movie_social_page, + diary::deps::GetMovieSocialPageDeps, diary::get_movie_social_page, diary::queries::GetMovieSocialPageQuery, }; diff --git a/crates/application/src/goals/create.rs b/crates/application/src/goals/create.rs index 1467d08..6c763e5 100644 --- a/crates/application/src/goals/create.rs +++ b/crates/application/src/goals/create.rs @@ -17,9 +17,7 @@ pub async fn execute( ) -> Result { let user_id = UserId::from_uuid(cmd.user_id); - let existing = goal - .find_by_user_and_year(&user_id, cmd.year) - .await?; + let existing = goal.find_by_user_and_year(&user_id, cmd.year).await?; if existing.is_some() { return Err(DomainError::ValidationError( "Goal already exists for this year".into(), @@ -34,9 +32,7 @@ pub async fn execute( )?; goal.save(&g).await?; - let current_count = goal - .count_reviews_in_year(&user_id, cmd.year) - .await?; + let current_count = goal.count_reviews_in_year(&user_id, cmd.year).await?; event_publisher .publish(&DomainEvent::GoalCreated { diff --git a/crates/application/src/goals/get.rs b/crates/application/src/goals/get.rs index 517e13a..a8651ad 100644 --- a/crates/application/src/goals/get.rs +++ b/crates/application/src/goals/get.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use domain::{errors::DomainError, models::GoalWithProgress, ports::GoalRepository, value_objects::UserId}; +use domain::{ + errors::DomainError, models::GoalWithProgress, ports::GoalRepository, value_objects::UserId, +}; use super::queries::GetGoalQuery; @@ -10,15 +12,11 @@ pub async fn execute( ) -> Result, DomainError> { let user_id = UserId::from_uuid(query.user_id); - let found = goal - .find_by_user_and_year(&user_id, query.year) - .await?; + let found = goal.find_by_user_and_year(&user_id, query.year).await?; let Some(g) = found else { return Ok(None) }; - let current_count = goal - .count_reviews_in_year(&user_id, query.year) - .await?; + let current_count = goal.count_reviews_in_year(&user_id, query.year).await?; Ok(Some(GoalWithProgress { goal: g, diff --git a/crates/application/src/goals/list.rs b/crates/application/src/goals/list.rs index adeef55..0f2c20b 100644 --- a/crates/application/src/goals/list.rs +++ b/crates/application/src/goals/list.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use domain::{errors::DomainError, models::GoalWithProgress, ports::GoalRepository, value_objects::UserId}; +use domain::{ + errors::DomainError, models::GoalWithProgress, ports::GoalRepository, value_objects::UserId, +}; use super::queries::ListGoalsQuery; @@ -13,9 +15,7 @@ pub async fn execute( let mut result = Vec::with_capacity(goals.len()); for g in goals { - let current_count = goal - .count_reviews_in_year(&user_id, g.year()) - .await?; + let current_count = goal.count_reviews_in_year(&user_id, g.year()).await?; result.push(GoalWithProgress { goal: g, current_count, diff --git a/crates/application/src/goals/update.rs b/crates/application/src/goals/update.rs index 5053270..f6fe43a 100644 --- a/crates/application/src/goals/update.rs +++ b/crates/application/src/goals/update.rs @@ -25,9 +25,7 @@ pub async fn execute( g.update_target(cmd.target_count)?; goal.update(&g).await?; - let current_count = goal - .count_reviews_in_year(&user_id, cmd.year) - .await?; + let current_count = goal.count_reviews_in_year(&user_id, cmd.year).await?; event_publisher .publish(&DomainEvent::GoalUpdated { diff --git a/crates/application/src/import/create_session.rs b/crates/application/src/import/create_session.rs index f54084b..da3268d 100644 --- a/crates/application/src/import/create_session.rs +++ b/crates/application/src/import/create_session.rs @@ -22,9 +22,7 @@ pub async fn execute( cmd: CreateImportSessionCommand, ) -> Result { let user_id = UserId::from_uuid(cmd.user_id); - import_session - .delete_expired_for_user(&user_id) - .await?; + import_session.delete_expired_for_user(&user_id).await?; let parsed = document_parser .parse(&cmd.bytes, cmd.format) diff --git a/crates/application/src/import/list_profiles.rs b/crates/application/src/import/list_profiles.rs index 7fa9d2d..019cee8 100644 --- a/crates/application/src/import/list_profiles.rs +++ b/crates/application/src/import/list_profiles.rs @@ -1,6 +1,9 @@ use std::sync::Arc; -use domain::{errors::DomainError, models::ImportProfile, ports::ImportProfileRepository, value_objects::UserId}; +use domain::{ + errors::DomainError, models::ImportProfile, ports::ImportProfileRepository, + value_objects::UserId, +}; pub async fn execute( import_profile: Arc, diff --git a/crates/application/src/import/tests/list_profiles.rs b/crates/application/src/import/tests/list_profiles.rs index f0ab34a..b5cc7a2 100644 --- a/crates/application/src/import/tests/list_profiles.rs +++ b/crates/application/src/import/tests/list_profiles.rs @@ -11,7 +11,9 @@ async fn returns_empty_when_no_profiles() { let profiles = InMemoryImportProfileRepository::new(); let user_id = UserId::from_uuid(Uuid::new_v4()); - let result = list_profiles::execute(Arc::clone(&profiles) as _, &user_id).await.unwrap(); + let result = list_profiles::execute(Arc::clone(&profiles) as _, &user_id) + .await + .unwrap(); assert!(result.is_empty()); } diff --git a/crates/application/src/integrations/generate_token.rs b/crates/application/src/integrations/generate_token.rs index c6102ea..56209e7 100644 --- a/crates/application/src/integrations/generate_token.rs +++ b/crates/application/src/integrations/generate_token.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use domain::{errors::DomainError, models::WebhookToken, ports::WebhookTokenRepository, value_objects::UserId}; +use domain::{ + errors::DomainError, models::WebhookToken, ports::WebhookTokenRepository, value_objects::UserId, +}; use sha2::{Digest, Sha256}; use crate::integrations::commands::GenerateWebhookTokenCommand; diff --git a/crates/application/src/integrations/get_queue.rs b/crates/application/src/integrations/get_queue.rs index ef2d943..14011a3 100644 --- a/crates/application/src/integrations/get_queue.rs +++ b/crates/application/src/integrations/get_queue.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use domain::{errors::DomainError, models::WatchEvent, ports::WatchEventRepository, value_objects::UserId}; +use domain::{ + errors::DomainError, models::WatchEvent, ports::WatchEventRepository, value_objects::UserId, +}; use crate::integrations::queries::GetWatchQueueQuery; diff --git a/crates/application/src/integrations/get_tokens.rs b/crates/application/src/integrations/get_tokens.rs index 2bac36c..192aef4 100644 --- a/crates/application/src/integrations/get_tokens.rs +++ b/crates/application/src/integrations/get_tokens.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use domain::{errors::DomainError, models::WebhookToken, ports::WebhookTokenRepository, value_objects::UserId}; +use domain::{ + errors::DomainError, models::WebhookToken, ports::WebhookTokenRepository, value_objects::UserId, +}; use crate::integrations::queries::GetWebhookTokensQuery; diff --git a/crates/application/src/integrations/ingest.rs b/crates/application/src/integrations/ingest.rs index 5f69fc0..80144ff 100644 --- a/crates/application/src/integrations/ingest.rs +++ b/crates/application/src/integrations/ingest.rs @@ -17,10 +17,7 @@ pub async fn execute( .await? .ok_or_else(|| DomainError::Unauthorized("invalid webhook token".into()))?; - let _ = deps - .webhook_token - .touch_last_used(webhook_token.id()) - .await; + let _ = deps.webhook_token.touch_last_used(webhook_token.id()).await; let parsed = match parser.parse_playback_event(&cmd.raw_payload)? { Some(event) => event, diff --git a/crates/application/src/integrations/tests/ingest.rs b/crates/application/src/integrations/tests/ingest.rs index 68d165a..594d466 100644 --- a/crates/application/src/integrations/tests/ingest.rs +++ b/crates/application/src/integrations/tests/ingest.rs @@ -2,7 +2,9 @@ use std::sync::Arc; use domain::models::WatchEventSource; use domain::ports::{EventPublisher, WatchEventRepository, WebhookTokenRepository}; -use domain::testing::{InMemoryWebhookTokenRepository, InMemoryWatchEventRepository, NoopEventPublisher}; +use domain::testing::{ + InMemoryWatchEventRepository, InMemoryWebhookTokenRepository, NoopEventPublisher, +}; use uuid::Uuid; use crate::integrations::commands::{GenerateWebhookTokenCommand, IngestWatchEventCommand}; diff --git a/crates/application/src/jobs/import_cleanup.rs b/crates/application/src/jobs/import_cleanup.rs index 511554e..bc4dc60 100644 --- a/crates/application/src/jobs/import_cleanup.rs +++ b/crates/application/src/jobs/import_cleanup.rs @@ -2,7 +2,10 @@ use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; -use domain::{errors::DomainError, ports::{ImportSessionRepository, PeriodicJob}}; +use domain::{ + errors::DomainError, + ports::{ImportSessionRepository, PeriodicJob}, +}; pub struct ImportSessionCleanupJob { import_session: Arc, diff --git a/crates/application/src/jobs/watch_event_cleanup.rs b/crates/application/src/jobs/watch_event_cleanup.rs index 635c321..4c5d71d 100644 --- a/crates/application/src/jobs/watch_event_cleanup.rs +++ b/crates/application/src/jobs/watch_event_cleanup.rs @@ -2,7 +2,10 @@ use std::sync::Arc; use std::time::Duration; use async_trait::async_trait; -use domain::{errors::DomainError, ports::{PeriodicJob, WatchEventRepository}}; +use domain::{ + errors::DomainError, + ports::{PeriodicJob, WatchEventRepository}, +}; pub struct WatchEventCleanupJob { watch_event: Arc, diff --git a/crates/application/src/jobs/wrapup.rs b/crates/application/src/jobs/wrapup.rs index c4adc50..4fddce5 100644 --- a/crates/application/src/jobs/wrapup.rs +++ b/crates/application/src/jobs/wrapup.rs @@ -114,10 +114,7 @@ impl PeriodicJob for WrapUpCleanupJob { async fn run(&self) -> Result<(), DomainError> { let cutoff = chrono::Utc::now().naive_utc() - chrono::Duration::days(7); - let n = self - .wrapup_repo - .delete_failed_older_than(cutoff) - .await?; + let n = self.wrapup_repo.delete_failed_older_than(cutoff).await?; if n > 0 { tracing::info!("wrapup cleanup: removed {n} failed records"); } diff --git a/crates/application/src/lib.rs b/crates/application/src/lib.rs index 9e0eb13..aff5419 100644 --- a/crates/application/src/lib.rs +++ b/crates/application/src/lib.rs @@ -1,5 +1,4 @@ pub mod config; -pub mod context; pub mod jobs; pub mod ports; pub mod worker; diff --git a/crates/application/src/movies/sync_poster.rs b/crates/application/src/movies/sync_poster.rs index d1104f1..b99f138 100644 --- a/crates/application/src/movies/sync_poster.rs +++ b/crates/application/src/movies/sync_poster.rs @@ -30,11 +30,7 @@ pub async fn execute(deps: &SyncPosterDeps, cmd: SyncPosterCommand) -> Result<() })? .clone(); - let poster_url = match deps - .metadata - .get_poster_url(&external_metadata_id) - .await - { + let poster_url = match deps.metadata.get_poster_url(&external_metadata_id).await { Ok(Some(url)) => url, Ok(None) => return Ok(()), Err(e) => { @@ -43,10 +39,7 @@ pub async fn execute(deps: &SyncPosterDeps, cmd: SyncPosterCommand) -> Result<() } }; - let image_bytes = deps - .poster_fetcher - .fetch_poster_bytes(&poster_url) - .await?; + let image_bytes = deps.poster_fetcher.fetch_poster_bytes(&poster_url).await?; let stored_path = deps .object_storage diff --git a/crates/application/src/movies/tests/sync_poster.rs b/crates/application/src/movies/tests/sync_poster.rs index a19ddd2..6442b80 100644 --- a/crates/application/src/movies/tests/sync_poster.rs +++ b/crates/application/src/movies/tests/sync_poster.rs @@ -6,7 +6,10 @@ use domain::{ errors::DomainError, models::Movie, ports::{MetadataClient, MovieRepository}, - testing::{InMemoryMovieProfileRepository, InMemoryMovieRepository, NoopEventPublisher, NoopObjectStorage, FakeSearchCommand}, + testing::{ + FakeSearchCommand, InMemoryMovieProfileRepository, InMemoryMovieRepository, + NoopEventPublisher, NoopObjectStorage, + }, value_objects::{ExternalMetadataId, MovieTitle, PosterUrl, ReleaseYear}, }; diff --git a/crates/application/src/search/execute.rs b/crates/application/src/search/execute.rs index de7c9e0..d4b24e5 100644 --- a/crates/application/src/search/execute.rs +++ b/crates/application/src/search/execute.rs @@ -1,9 +1,9 @@ -use std::sync::Arc; use domain::{ errors::DomainError, models::{SearchQuery, SearchResults}, ports::SearchPort, }; +use std::sync::Arc; pub async fn execute( search_port: Arc, diff --git a/crates/application/src/test_helpers.rs b/crates/application/src/test_helpers.rs index 557fb19..6c888f0 100644 --- a/crates/application/src/test_helpers.rs +++ b/crates/application/src/test_helpers.rs @@ -1,8 +1,7 @@ use std::sync::Arc; use domain::testing::{ - InMemoryGoalRepository, InMemoryWrapUpRepository, InMemoryWrapUpStatsQuery, - NoopRemoteWatchlistRepository, NoopSocialQueryPort, + InMemoryGoalRepository, InMemoryWrapUpRepository, InMemoryWrapUpStatsQuery, NoopSocialQueryPort, }; use domain::{ ports::{ @@ -29,12 +28,7 @@ use domain::{ use async_trait::async_trait; use domain::errors::DomainError; -use crate::{ - config::AppConfig, - context::{AppContext, Repositories, Services}, - diary::commands::LogReviewCommand, - ports::ReviewLogger, -}; +use crate::{config::AppConfig, diary::commands::LogReviewCommand, ports::ReviewLogger}; pub struct NoopReviewLogger; @@ -263,48 +257,4 @@ impl TestContextBuilder { self.config = config; self } - - pub fn build(self) -> AppContext { - AppContext { - repos: Repositories { - movie: self.movie_repo, - review: self.review_repo, - diary: self.diary_repo, - stats: self.stats_repo, - user: self.user_repo, - import_session: self.import_session_repo, - import_profile: self.import_profile_repo, - movie_profile: self.movie_profile_repo, - watchlist: self.watchlist_repo, - watch_event: self.watch_event_repo, - webhook_token: self.webhook_token_repo, - profile_fields: self.profile_fields_repo, - person_command: self.person_command, - person_query: self.person_query, - search_port: self.search_port, - search_command: self.search_command, - remote_watchlist: Arc::new(NoopRemoteWatchlistRepository), - social_query: self.social_query, - wrapup_stats: self.wrapup_stats, - wrapup_repo: self.wrapup_repo, - goal: self.goal_repo, - user_settings: self.user_settings_repo, - remote_goal: Arc::new(domain::testing::NoopRemoteGoalRepository), - refresh_session: self.refresh_session_repo, - }, - services: Services { - auth: self.auth_service, - password_hasher: self.password_hasher, - metadata: self.metadata_client, - poster_fetcher: self.poster_fetcher, - object_storage: self.object_storage, - event_publisher: self.event_publisher, - diary_exporter: self.diary_exporter, - document_parser: self.document_parser, - review_logger: self.review_logger, - person_enrichment: None, - }, - config: self.config, - } - } } diff --git a/crates/application/src/users/get_settings.rs b/crates/application/src/users/get_settings.rs index 95e4543..2320065 100644 --- a/crates/application/src/users/get_settings.rs +++ b/crates/application/src/users/get_settings.rs @@ -1,6 +1,8 @@ use std::sync::Arc; -use domain::{errors::DomainError, models::UserSettings, ports::UserSettingsRepository, value_objects::UserId}; +use domain::{ + errors::DomainError, models::UserSettings, ports::UserSettingsRepository, value_objects::UserId, +}; pub async fn execute( user_settings: Arc, diff --git a/crates/application/src/users/get_users.rs b/crates/application/src/users/get_users.rs index d61b3d2..c802d07 100644 --- a/crates/application/src/users/get_users.rs +++ b/crates/application/src/users/get_users.rs @@ -1,7 +1,11 @@ use std::sync::Arc; use crate::users::queries::GetUsersQuery; -use domain::{errors::DomainError, models::UserSummary, ports::{RemoteActorInfo, SocialQueryPort, UserRepository}}; +use domain::{ + errors::DomainError, + models::UserSummary, + ports::{RemoteActorInfo, SocialQueryPort, UserRepository}, +}; pub struct UsersListData { pub users: Vec, diff --git a/crates/application/src/users/tests/get_settings.rs b/crates/application/src/users/tests/get_settings.rs index 6774d4f..b11049d 100644 --- a/crates/application/src/users/tests/get_settings.rs +++ b/crates/application/src/users/tests/get_settings.rs @@ -7,7 +7,9 @@ async fn returns_default_settings() { let b = TestContextBuilder::new(); let user_settings = b.user_settings_repo.clone(); - let settings = get_settings::execute(user_settings, Uuid::nil()).await.unwrap(); + let settings = get_settings::execute(user_settings, Uuid::nil()) + .await + .unwrap(); assert!(!settings.federate_goals()); } diff --git a/crates/application/src/users/tests/get_users.rs b/crates/application/src/users/tests/get_users.rs index 8fcc74e..a6281db 100644 --- a/crates/application/src/users/tests/get_users.rs +++ b/crates/application/src/users/tests/get_users.rs @@ -8,7 +8,9 @@ async fn returns_empty_when_no_users() { let user = b.user_repo.clone(); let social_query = b.social_query.clone(); - let result = get_users::execute(user, social_query, GetUsersQuery).await.unwrap(); + let result = get_users::execute(user, social_query, GetUsersQuery) + .await + .unwrap(); assert!(result.users.is_empty()); assert!(result.remote_actors.is_empty()); diff --git a/crates/application/src/users/tests/update_settings.rs b/crates/application/src/users/tests/update_settings.rs index 6ca753e..02f084c 100644 --- a/crates/application/src/users/tests/update_settings.rs +++ b/crates/application/src/users/tests/update_settings.rs @@ -11,8 +11,7 @@ use crate::{ #[tokio::test] async fn updates_federate_goals() { let settings_repo = InMemoryUserSettingsRepository::new(); - let b = TestContextBuilder::new() - .with_user_settings(Arc::clone(&settings_repo) as _); + let b = TestContextBuilder::new().with_user_settings(Arc::clone(&settings_repo) as _); let user_settings = b.user_settings_repo.clone(); let uid = Uuid::nil(); diff --git a/crates/application/src/users/update_profile.rs b/crates/application/src/users/update_profile.rs index 8b2da82..930e3b6 100644 --- a/crates/application/src/users/update_profile.rs +++ b/crates/application/src/users/update_profile.rs @@ -2,7 +2,10 @@ use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId}; use crate::users::{commands::UpdateProfileCommand, deps::UpdateProfileDeps}; -pub async fn execute(deps: &UpdateProfileDeps, cmd: UpdateProfileCommand) -> Result<(), DomainError> { +pub async fn execute( + deps: &UpdateProfileDeps, + cmd: UpdateProfileCommand, +) -> Result<(), DomainError> { let user_id = UserId::from_uuid(cmd.user_id); let user = deps diff --git a/crates/application/src/users/update_profile_fields.rs b/crates/application/src/users/update_profile_fields.rs index 2139a24..e923c86 100644 --- a/crates/application/src/users/update_profile_fields.rs +++ b/crates/application/src/users/update_profile_fields.rs @@ -1,7 +1,11 @@ use std::sync::Arc; use domain::{ - errors::DomainError, events::DomainEvent, models::UserProfile, ports::{EventPublisher, UserProfileFieldsRepository}, value_objects::UserId, + errors::DomainError, + events::DomainEvent, + models::UserProfile, + ports::{EventPublisher, UserProfileFieldsRepository}, + value_objects::UserId, }; use crate::users::commands::UpdateProfileFieldsCommand; diff --git a/crates/application/src/watchlist/add.rs b/crates/application/src/watchlist/add.rs index a1ba851..d5581bb 100644 --- a/crates/application/src/watchlist/add.rs +++ b/crates/application/src/watchlist/add.rs @@ -10,7 +10,10 @@ use crate::{ watchlist::{commands::AddToWatchlistCommand, deps::WatchlistAddDeps}, }; -pub async fn execute(deps: &WatchlistAddDeps, cmd: AddToWatchlistCommand) -> Result<(), DomainError> { +pub async fn execute( + deps: &WatchlistAddDeps, + cmd: AddToWatchlistCommand, +) -> Result<(), DomainError> { let user_id = UserId::from_uuid(cmd.user_id); let movie = if let Some(id) = cmd.input.movie_id { diff --git a/crates/application/src/wrapup/handle_requested.rs b/crates/application/src/wrapup/handle_requested.rs index 25faf38..0b86e46 100644 --- a/crates/application/src/wrapup/handle_requested.rs +++ b/crates/application/src/wrapup/handle_requested.rs @@ -37,9 +37,7 @@ pub async fn execute( match compute::execute(deps.wrapup_stats.clone(), query).await { Ok(report) => { - deps.wrapup_repo - .set_complete(&wrapup_id, &report) - .await?; + deps.wrapup_repo.set_complete(&wrapup_id, &report).await?; deps.event_publisher .publish(&DomainEvent::WrapUpCompleted { wrapup_id }) diff --git a/crates/application/src/context.rs b/crates/presentation/src/context.rs similarity index 97% rename from crates/application/src/context.rs rename to crates/presentation/src/context.rs index ee71715..6d5cac0 100644 --- a/crates/application/src/context.rs +++ b/crates/presentation/src/context.rs @@ -11,8 +11,8 @@ use domain::ports::{ WrapUpStatsQuery, }; -use crate::config::AppConfig; -use crate::ports::ReviewLogger; +use application::config::AppConfig; +use application::ports::ReviewLogger; #[derive(Clone)] pub struct Repositories { diff --git a/crates/presentation/src/handlers/diary.rs b/crates/presentation/src/handlers/diary.rs index 35a3af5..c71cf29 100644 --- a/crates/presentation/src/handlers/diary.rs +++ b/crates/presentation/src/handlers/diary.rs @@ -8,10 +8,10 @@ use uuid::Uuid; use application::diary::{ commands::DeleteReviewCommand, - delete_review, export_diary as export_diary_uc, get_activity_feed as get_feed_uc, get_diary, - log_review, - queries::{ExportQuery, GetActivityFeedQuery}, + delete_review, deps::{DeleteReviewDeps, GetActivityFeedDeps}, + export_diary as export_diary_uc, get_activity_feed as get_feed_uc, get_diary, log_review, + queries::{ExportQuery, GetActivityFeedQuery}, }; use domain::models::ExportFormat; @@ -81,7 +81,11 @@ pub async fn post_review( Json(req): Json, ) -> Result { let data = LogReviewData::try_from(req).map_err(ApiError)?; - log_review::execute(&state.app_ctx.services.review_logger, data.into_command(user.0.value())).await?; + log_review::execute( + &state.app_ctx.services.review_logger, + data.into_command(user.0.value()), + ) + .await?; Ok(StatusCode::CREATED) } @@ -244,7 +248,12 @@ pub async fn post_review_html( } }; - match log_review::execute(&state.app_ctx.services.review_logger, data.into_command(user_id.value())).await { + match log_review::execute( + &state.app_ctx.services.review_logger, + data.into_command(user_id.value()), + ) + .await + { Ok(_) => Redirect::to("/").into_response(), Err(e) => { let msg = encode_error(&e.to_string()); diff --git a/crates/presentation/src/handlers/import.rs b/crates/presentation/src/handlers/import.rs index b7e00fc..f86c3fd 100644 --- a/crates/presentation/src/handlers/import.rs +++ b/crates/presentation/src/handlers/import.rs @@ -109,18 +109,16 @@ pub async fn get_import_page( Extension(csrf): Extension, ) -> impl IntoResponse { let ctx = super::helpers::build_page_context(&state, Some(user_id.clone()), csrf.0).await; - let profiles = list_import_profiles::execute( - state.app_ctx.repos.import_profile.clone(), - &user_id, - ) - .await - .unwrap_or_default() - .into_iter() - .map(|p| ImportProfileView { - id: p.id.value().to_string(), - name: p.name, - }) - .collect::>(); + let profiles = + list_import_profiles::execute(state.app_ctx.repos.import_profile.clone(), &user_id) + .await + .unwrap_or_default() + .into_iter() + .map(|p| ImportProfileView { + id: p.id.value().to_string(), + name: p.name, + }) + .collect::>(); render_page(ImportUploadTemplate { ctx: &ctx, profiles: &profiles, @@ -765,7 +763,8 @@ pub async fn api_get_profiles( State(state): State, AuthenticatedUser(user_id): AuthenticatedUser, ) -> impl IntoResponse { - match list_import_profiles::execute(state.app_ctx.repos.import_profile.clone(), &user_id).await { + match list_import_profiles::execute(state.app_ctx.repos.import_profile.clone(), &user_id).await + { Ok(profiles) => axum::Json( profiles .into_iter() diff --git a/crates/presentation/src/handlers/integrations.rs b/crates/presentation/src/handlers/integrations.rs index 167ac55..8cb8814 100644 --- a/crates/presentation/src/handlers/integrations.rs +++ b/crates/presentation/src/handlers/integrations.rs @@ -116,7 +116,9 @@ pub async fn post_revoke_token( user_id: user_id.value(), token_id, }; - if let Err(e) = revoke_webhook_token::execute(state.app_ctx.repos.webhook_token.clone(), cmd).await { + if let Err(e) = + revoke_webhook_token::execute(state.app_ctx.repos.webhook_token.clone(), cmd).await + { tracing::error!("revoke token failed: {:?}", e); } diff --git a/crates/presentation/src/handlers/movies.rs b/crates/presentation/src/handlers/movies.rs index 5443c76..8fa582b 100644 --- a/crates/presentation/src/handlers/movies.rs +++ b/crates/presentation/src/handlers/movies.rs @@ -9,16 +9,11 @@ use uuid::Uuid; use application::{ diary::{ commands::SyncPosterCommand, - deps::{GetMovieSocialPageDeps}, + deps::GetMovieSocialPageDeps, get_movie_social_page, get_review_history, queries::{GetMovieSocialPageQuery, GetReviewHistoryQuery}, }, - movies::{ - deps::SyncPosterDeps, - get_movies, - queries::GetMoviesQuery, - sync_poster, - }, + movies::{deps::SyncPosterDeps, get_movies, queries::GetMoviesQuery, sync_poster}, watchlist::{is_on as is_on_watchlist, queries::IsOnWatchlistQuery}, }; use domain::services::review_history::Trend; @@ -88,8 +83,11 @@ pub async fn get_review_history( State(state): State, Path(movie_id): Path, ) -> Result, ApiError> { - let (history, trend) = - get_review_history::execute(&state.app_ctx.repos.diary, GetReviewHistoryQuery { movie_id }).await?; + let (history, trend) = get_review_history::execute( + &state.app_ctx.repos.diary, + GetReviewHistoryQuery { movie_id }, + ) + .await?; Ok(Json(ReviewHistoryResponse { movie: crate::mappers::movies::movie_to_dto(history.movie()), @@ -210,12 +208,7 @@ pub async fn get_movie_profile( ) -> impl IntoResponse { use application::movies::get_movie_profile; let query = get_movie_profile::GetMovieProfileQuery { movie_id }; - match get_movie_profile::execute( - state.app_ctx.repos.movie_profile.clone(), - query, - ) - .await - { + match get_movie_profile::execute(state.app_ctx.repos.movie_profile.clone(), query).await { Ok(Some(result)) => { let p = result.profile; Json(MovieProfileResponse { diff --git a/crates/presentation/src/handlers/webhook.rs b/crates/presentation/src/handlers/webhook.rs index 2581545..584ec04 100644 --- a/crates/presentation/src/handlers/webhook.rs +++ b/crates/presentation/src/handlers/webhook.rs @@ -15,10 +15,11 @@ use application::integrations::{ ConfirmWatchEventsCommand, DismissWatchEventsCommand, GenerateWebhookTokenCommand, IngestWatchEventCommand, RevokeWebhookTokenCommand, WatchEventConfirmation, }, - confirm as confirm_watch_events, deps::IngestWatchEventDeps, + confirm as confirm_watch_events, + deps::IngestWatchEventDeps, dismiss as dismiss_watch_events, generate_token as generate_webhook_token, - get_queue as get_watch_queue, get_tokens as get_webhook_tokens, - ingest as ingest_watch_event, queries::{GetWatchQueueQuery, GetWebhookTokensQuery}, + get_queue as get_watch_queue, get_tokens as get_webhook_tokens, ingest as ingest_watch_event, + queries::{GetWatchQueueQuery, GetWebhookTokensQuery}, revoke_token as revoke_webhook_token, }; use domain::models::WatchEventSource; @@ -164,7 +165,8 @@ pub async fn post_generate_webhook_token( label: req.label, }; - let result = generate_webhook_token::execute(state.app_ctx.repos.webhook_token.clone(), cmd).await?; + let result = + generate_webhook_token::execute(state.app_ctx.repos.webhook_token.clone(), cmd).await?; let base_url = &state.app_ctx.config.base_url; let webhook_url = format!("{base_url}/api/v1/webhooks/{provider}"); @@ -191,7 +193,8 @@ pub async fn get_webhook_tokens( let query = GetWebhookTokensQuery { user_id: user.0.value(), }; - let tokens = get_webhook_tokens::execute(state.app_ctx.repos.webhook_token.clone(), query).await?; + let tokens = + get_webhook_tokens::execute(state.app_ctx.repos.webhook_token.clone(), query).await?; let dtos = tokens .into_iter() @@ -321,6 +324,7 @@ pub async fn post_dismiss_watch_events( event_ids: req.event_ids, }; - let dismissed = dismiss_watch_events::execute(state.app_ctx.repos.watch_event.clone(), cmd).await?; + let dismissed = + dismiss_watch_events::execute(state.app_ctx.repos.watch_event.clone(), cmd).await?; Ok(Json(DismissWatchResponse { dismissed })) } diff --git a/crates/presentation/src/handlers/wrapup.rs b/crates/presentation/src/handlers/wrapup.rs index 86b1328..b73a833 100644 --- a/crates/presentation/src/handlers/wrapup.rs +++ b/crates/presentation/src/handlers/wrapup.rs @@ -116,9 +116,12 @@ pub async fn get_status( _user: AuthenticatedUser, Path(id): Path, ) -> Result, ApiError> { - let record = get_wrapup::execute(state.app_ctx.repos.wrapup_repo.clone(), WrapUpId::from_uuid(id)) - .await? - .ok_or_else(|| DomainError::NotFound("wrap-up not found".into()))?; + let record = get_wrapup::execute( + state.app_ctx.repos.wrapup_repo.clone(), + WrapUpId::from_uuid(id), + ) + .await? + .ok_or_else(|| DomainError::NotFound("wrap-up not found".into()))?; Ok(Json(record_to_dto(&record))) } @@ -138,7 +141,12 @@ pub async fn get_report( _user: AuthenticatedUser, Path(id): Path, ) -> impl IntoResponse { - match get_wrapup::execute(state.app_ctx.repos.wrapup_repo.clone(), WrapUpId::from_uuid(id)).await { + match get_wrapup::execute( + state.app_ctx.repos.wrapup_repo.clone(), + WrapUpId::from_uuid(id), + ) + .await + { Ok(Some(record)) if record.status == WrapUpStatus::Ready => match record.report { Some(ref report) => { let json = serde_json::to_string(report).unwrap_or_default(); @@ -168,7 +176,11 @@ pub async fn delete_wrapup_handler( _admin: AdminApiUser, Path(id): Path, ) -> Result { - delete_wrapup::execute(state.app_ctx.repos.wrapup_repo.clone(), WrapUpId::from_uuid(id)).await?; + delete_wrapup::execute( + state.app_ctx.repos.wrapup_repo.clone(), + WrapUpId::from_uuid(id), + ) + .await?; Ok(StatusCode::NO_CONTENT) } diff --git a/crates/presentation/src/lib.rs b/crates/presentation/src/lib.rs index 7cadd60..99cfe72 100644 --- a/crates/presentation/src/lib.rs +++ b/crates/presentation/src/lib.rs @@ -1,3 +1,4 @@ +pub mod context; pub mod csrf; pub mod errors; pub mod extractors; diff --git a/crates/presentation/src/main.rs b/crates/presentation/src/main.rs index 33dff87..642787b 100644 --- a/crates/presentation/src/main.rs +++ b/crates/presentation/src/main.rs @@ -5,12 +5,10 @@ use anyhow::Context; use tokio::net::TcpListener; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; -use application::{ - config::AppConfig, - context::{AppContext, Repositories, Services}, -}; +use application::config::AppConfig; use export::ExportAdapter; use importer::ImporterDocumentParser; +use presentation::context::{AppContext, Repositories, Services}; use presentation::{factory, openapi, routes, state::AppState}; use rss::RssAdapter; diff --git a/crates/presentation/src/state.rs b/crates/presentation/src/state.rs index f31a9fa..a098fcd 100644 --- a/crates/presentation/src/state.rs +++ b/crates/presentation/src/state.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use application::context::AppContext; +use crate::context::AppContext; use crate::ports::RssFeedRenderer; diff --git a/crates/presentation/src/tests/extractors.rs b/crates/presentation/src/tests/extractors.rs index c016a66..e1bb397 100644 --- a/crates/presentation/src/tests/extractors.rs +++ b/crates/presentation/src/tests/extractors.rs @@ -1,8 +1,6 @@ use super::*; -use application::{ - config::AppConfig, - context::{AppContext, Repositories, Services}, -}; +use crate::context::{AppContext, Repositories, Services}; +use application::config::AppConfig; use axum::{ Router, body::Body, diff --git a/crates/presentation/tests/api_test.rs b/crates/presentation/tests/api_test.rs index 6947bdc..a51b4ce 100644 --- a/crates/presentation/tests/api_test.rs +++ b/crates/presentation/tests/api_test.rs @@ -1,9 +1,6 @@ use std::sync::Arc; -use application::{ - config::AppConfig, - context::{AppContext, Repositories, Services}, -}; +use application::config::AppConfig; use async_trait::async_trait; use axum::{ Router, @@ -25,6 +22,7 @@ use domain::{ value_objects::{Email, ExternalMetadataId, PasswordHash, PosterUrl, UserId}, }; use http_body_util::BodyExt; +use presentation::context::{AppContext, Repositories, Services}; use presentation::{routes, state::AppState}; use rss::RssAdapter; use sqlite::SqliteMovieRepository; diff --git a/crates/worker/src/db.rs b/crates/worker/src/db.rs index 207010f..b9a7b9b 100644 --- a/crates/worker/src/db.rs +++ b/crates/worker/src/db.rs @@ -2,11 +2,9 @@ use std::sync::Arc; use anyhow::Context; use domain::ports::{ - DiaryRepository, ImageRefCommand, ImageRefQuery, ImportProfileRepository, - ImportSessionRepository, LocalApContentQuery, MovieProfileRepository, MovieRepository, - PersonCommand, PersonQuery, ReviewRepository, SearchCommand, SearchPort, StatsRepository, - UserProfileFieldsRepository, UserRepository, WatchEventRepository, WatchlistRepository, - WebhookTokenRepository, + ImageRefCommand, ImageRefQuery, ImportSessionRepository, LocalApContentQuery, + MovieProfileRepository, MovieRepository, PersonCommand, PersonQuery, SearchCommand, + UserRepository, WatchEventRepository, }; pub enum DbPool { @@ -18,28 +16,18 @@ pub enum DbPool { pub struct WorkerDbOutput { pub movie: Arc, - pub review: Arc, - pub diary: Arc, - pub stats: Arc, pub user: Arc, pub import_session: Arc, - pub import_profile: Arc, pub movie_profile: Arc, - pub watchlist: Arc, pub watch_event: Arc, - pub webhook_token: Arc, pub person_command: Arc, pub person_query: Arc, pub search_command: Arc, - pub search_port: Arc, - pub profile_fields: Arc, pub ap_content: Arc, pub image_ref_command: Arc, pub image_ref_query: Arc, pub wrapup_stats: Arc, pub wrapup_repo: Arc, - pub goal: Arc, - pub user_settings: Arc, pub remote_goal: Arc, pub refresh_session: Arc, pub db_pool: DbPool, @@ -54,38 +42,24 @@ pub async fn connect(database_url: &str, backend: &str) -> anyhow::Result = Arc::new(postgres::PostgresWatchEventRepository::new(w.pool.clone())); - let wt: Arc = Arc::new( - postgres::PostgresWebhookTokenRepository::new(w.pool.clone()), - ); Ok(WorkerDbOutput { movie: w.movie, - review: w.review, - diary: w.diary, - stats: w.stats, user: w.user, import_session: w.import_session, - import_profile: w.import_profile, movie_profile: w.movie_profile, - watchlist: w.watchlist, watch_event: we, - webhook_token: wt, person_command, person_query, search_command, - search_port, - profile_fields: pf, ap_content: w.ap_content, image_ref_command, image_ref_query, wrapup_stats: w.wrapup_stats, wrapup_repo: w.wrapup_repo, - goal: w.goal, - user_settings: w.user_settings, remote_goal: w.remote_goal, refresh_session: Arc::new(postgres::PostgresRefreshSessionAdapter::new( w.pool.clone(), @@ -100,37 +74,24 @@ pub async fn connect(database_url: &str, backend: &str) -> anyhow::Result = Arc::new(sqlite::SqliteWatchEventRepository::new(w.pool.clone())); - let wt: Arc = - Arc::new(sqlite::SqliteWebhookTokenRepository::new(w.pool.clone())); Ok(WorkerDbOutput { movie: w.movie, - review: w.review, - diary: w.diary, - stats: w.stats, user: w.user, import_session: w.import_session, - import_profile: w.import_profile, movie_profile: w.movie_profile, - watchlist: w.watchlist, watch_event: we, - webhook_token: wt, person_command, person_query, search_command, - search_port, - profile_fields: pf, ap_content: w.ap_content, image_ref_command, image_ref_query, wrapup_stats: w.wrapup_stats, wrapup_repo: w.wrapup_repo, - goal: w.goal, - user_settings: w.user_settings, remote_goal: w.remote_goal, refresh_session: Arc::new(sqlite::SqliteRefreshSessionAdapter::new(w.pool.clone())) as _, diff --git a/crates/worker/src/main.rs b/crates/worker/src/main.rs index 45d309f..6303265 100644 --- a/crates/worker/src/main.rs +++ b/crates/worker/src/main.rs @@ -6,20 +6,12 @@ use std::sync::Arc; use anyhow::Context; use application::{ - MovieDiscoveryIndexer, SearchCleanupHandler, SearchReindexHandler, - config::AppConfig, - context::{AppContext, Repositories, Services}, - movies::deps::ReindexSearchDeps, - worker::WorkerService, + MovieDiscoveryIndexer, SearchCleanupHandler, SearchReindexHandler, config::AppConfig, + movies::deps::ReindexSearchDeps, worker::WorkerService, }; -use export::ExportAdapter; -use importer::ImporterDocumentParser; use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; -use domain::ports::{ - DiaryExporter, DocumentParser, EventHandler, MovieEnrichmentClient, PeriodicJob, - PersonEnrichmentClient, -}; +use domain::ports::{EventHandler, MovieEnrichmentClient, PeriodicJob, PersonEnrichmentClient}; #[cfg(not(any(feature = "sqlite", feature = "postgres")))] compile_error!( @@ -35,7 +27,6 @@ async fn main() -> anyhow::Result<()> { let backend = std::env::var("DATABASE_BACKEND").unwrap_or_else(|_| "sqlite".to_string()); let app_config = AppConfig::from_env(); - let (auth_service, password_hasher) = auth::create()?; let metadata_client = metadata::create()?; let poster_fetcher = poster_fetcher::create()?; let object_storage = object_storage::create()?; @@ -60,7 +51,7 @@ async fn main() -> anyhow::Result<()> { fed_follow_repo, fed_actor_repo, fed_blocklist_repo, - fed_social_query, + _fed_social_query, fed_review_store, fed_remote_watchlist_repo, ) = match &db.db_pool { @@ -70,61 +61,22 @@ async fn main() -> anyhow::Result<()> { db::DbPool::Postgres(pool) => postgres_federation::wire(pool.clone()), }; - let review_logger = Arc::new(application::diary::review_logger::DefaultReviewLogger::new( - Arc::clone(&db.movie), - Arc::clone(&db.review), - Arc::clone(&db.watchlist), - Arc::clone(&metadata_client), - Arc::clone(&event_publisher_arc), - )); + let movie = db.movie; + let user = db.user; + let import_session = db.import_session; + let movie_profile = db.movie_profile; + let watch_event = db.watch_event; + let person_command = db.person_command; + let person_query = db.person_query; + let search_command = db.search_command; + let wrapup_stats = db.wrapup_stats; + let wrapup_repo = db.wrapup_repo; + let remote_goal = db.remote_goal; + let refresh_session = db.refresh_session; - let mut ctx = AppContext { - repos: Repositories { - movie: db.movie, - review: db.review, - diary: db.diary, - stats: db.stats, - user: db.user, - import_session: db.import_session, - import_profile: db.import_profile, - movie_profile: db.movie_profile, - watchlist: db.watchlist, - watch_event: db.watch_event, - webhook_token: db.webhook_token, - profile_fields: db.profile_fields, - person_command: db.person_command, - person_query: db.person_query, - search_port: db.search_port, - search_command: db.search_command, - #[cfg(feature = "federation")] - remote_watchlist: fed_remote_watchlist_repo.clone(), - #[cfg(not(feature = "federation"))] - remote_watchlist: Arc::new(domain::testing::NoopRemoteWatchlistRepository), - #[cfg(feature = "federation")] - social_query: fed_social_query, - #[cfg(not(feature = "federation"))] - social_query: Arc::new(domain::testing::NoopSocialQueryPort), - wrapup_stats: db.wrapup_stats, - wrapup_repo: db.wrapup_repo, - goal: db.goal, - user_settings: db.user_settings, - remote_goal: db.remote_goal, - refresh_session: db.refresh_session, - }, - services: Services { - auth: auth_service, - password_hasher, - metadata: metadata_client, - poster_fetcher, - object_storage, - event_publisher: event_publisher_arc, - diary_exporter: Arc::new(ExportAdapter) as Arc, - document_parser: Arc::new(ImporterDocumentParser) as Arc, - review_logger, - person_enrichment: None, - }, - config: app_config, - }; + let event_publisher = event_publisher_arc; + let object_storage = object_storage; + let metadata = metadata_client; // ── Enrichment ──────────────────────────────────────────────────────────── // Both the event handler and the staleness job are gated on TMDB_API_KEY. @@ -142,23 +94,21 @@ async fn main() -> anyhow::Result<()> { let client = Arc::new(client); let handler = Arc::new(tmdb_enrichment::MovieEnrichmentHandler::new( Arc::clone(&client) as Arc, - Arc::clone(&ctx.repos.movie), - Arc::clone(&ctx.repos.movie_profile), - Arc::clone(&ctx.repos.person_command), - Arc::clone(&ctx.repos.search_command), - Arc::clone(&ctx.services.object_storage), + Arc::clone(&movie), + Arc::clone(&movie_profile), + Arc::clone(&person_command), + Arc::clone(&search_command), + Arc::clone(&object_storage), )) as Arc; - let person_enrichment_arc = - Arc::clone(&client) as Arc; - ctx.services.person_enrichment = Some(Arc::clone(&person_enrichment_arc)); + let person_enrichment_arc = Arc::clone(&client) as Arc; let person_handler = Arc::new(tmdb_enrichment::PersonEnrichmentHandler::new( - Arc::clone(&ctx.repos.person_query), + Arc::clone(&person_query), Some(person_enrichment_arc), - Arc::clone(&ctx.repos.person_command), + Arc::clone(&person_command), )) as Arc; let job = Arc::new(application::jobs::EnrichmentStalenessJob::new( - Arc::clone(&ctx.repos.movie_profile), - Arc::clone(&ctx.services.event_publisher), + Arc::clone(&movie_profile), + Arc::clone(&event_publisher), )) as Arc; (Some(handler), Some(person_handler), Some(job)) } @@ -171,27 +121,31 @@ async fn main() -> anyhow::Result<()> { // ── Image conversion ────────────────────────────────────────────────────── let conversion = image_converter::build( - Arc::clone(&ctx.services.object_storage), + Arc::clone(&object_storage), image_ref_command, image_ref_query, - Arc::clone(&ctx.services.event_publisher), + Arc::clone(&event_publisher), )?; // ── Periodic jobs ───────────────────────────────────────────────────────── let mut periodic_jobs: Vec> = vec![ - Arc::new(application::jobs::ImportSessionCleanupJob::new(ctx.repos.import_session.clone())), - Arc::new(application::jobs::WatchEventCleanupJob::new(ctx.repos.watch_event.clone())), + Arc::new(application::jobs::ImportSessionCleanupJob::new( + import_session.clone(), + )), + Arc::new(application::jobs::WatchEventCleanupJob::new( + watch_event.clone(), + )), Arc::new(application::jobs::WrapUpAutoGenerateJob::new( - Arc::clone(&ctx.repos.user), - Arc::clone(&ctx.repos.wrapup_repo), - Arc::clone(&ctx.services.event_publisher), - )), - Arc::new(application::jobs::WrapUpCleanupJob::new( - Arc::clone(&ctx.repos.wrapup_repo), + Arc::clone(&user), + Arc::clone(&wrapup_repo), + Arc::clone(&event_publisher), )), + Arc::new(application::jobs::WrapUpCleanupJob::new(Arc::clone( + &wrapup_repo, + ))), Arc::new(application::jobs::RefreshSessionCleanupJob::new( - Arc::clone(&ctx.repos.refresh_session), + Arc::clone(&refresh_session), )), ]; if let Some(job) = enrichment_job { @@ -217,42 +171,40 @@ async fn main() -> anyhow::Result<()> { let handlers: Vec> = { let poster = Arc::new(poster_sync::PosterSyncHandler::new( - Arc::clone(&ctx.repos.movie), - Arc::clone(&ctx.services.metadata), - Arc::clone(&ctx.services.poster_fetcher), - Arc::clone(&ctx.services.object_storage), - Arc::clone(&ctx.services.event_publisher), + Arc::clone(&movie), + Arc::clone(&metadata), + Arc::clone(&poster_fetcher), + Arc::clone(&object_storage), + Arc::clone(&event_publisher), 3, )) as Arc; let cleanup = Arc::new(object_storage::ImageCleanupHandler::new(Arc::clone( - &ctx.services.object_storage, + &object_storage, ))) as Arc; #[cfg(not(feature = "federation"))] { let search_cleanup = Arc::new(SearchCleanupHandler::new( - Arc::clone(&ctx.repos.search_command), - Arc::clone(&ctx.repos.person_query), + Arc::clone(&search_command), + Arc::clone(&person_query), )) as Arc; let discovery_indexer = Arc::new(MovieDiscoveryIndexer::new( - Arc::clone(&ctx.repos.movie), - Arc::clone(&ctx.repos.search_command), + Arc::clone(&movie), + Arc::clone(&search_command), )) as Arc; - let wrapup_handler = Arc::new( - application::wrapup::event_handler::WrapUpEventHandler::new( - Arc::clone(&ctx.repos.wrapup_repo), - Arc::clone(&ctx.services.event_publisher), - Arc::clone(&ctx.repos.wrapup_stats), - ), - ) as Arc; - let reindex_handler = - Arc::new(SearchReindexHandler::new(ReindexSearchDeps { - movie: Arc::clone(&ctx.repos.movie), - movie_profile: Arc::clone(&ctx.repos.movie_profile), - search_command: Arc::clone(&ctx.repos.search_command), - person_command: Arc::clone(&ctx.repos.person_command), - person_query: Arc::clone(&ctx.repos.person_query), + let wrapup_handler = + Arc::new(application::wrapup::event_handler::WrapUpEventHandler::new( + Arc::clone(&wrapup_repo), + Arc::clone(&event_publisher), + Arc::clone(&wrapup_stats), + )) as Arc; + let reindex_handler = Arc::new(SearchReindexHandler::new(ReindexSearchDeps { + movie: Arc::clone(&movie), + movie_profile: Arc::clone(&movie_profile), + search_command: Arc::clone(&search_command), + person_command: Arc::clone(&person_command), + person_query: Arc::clone(&person_query), })) as Arc; let mut h = vec![ poster, @@ -283,12 +235,12 @@ async fn main() -> anyhow::Result<()> { blocklist_repo: fed_blocklist_repo, review_store: fed_review_store, remote_watchlist_repo: fed_remote_watchlist_repo, - remote_goal_repo: Arc::clone(&ctx.repos.remote_goal), + remote_goal_repo: Arc::clone(&remote_goal), local_ap_content: fed_ap_content, user_repo: fed_user_repo, base_url, allow_registration, - event_publisher: Arc::clone(&ctx.services.event_publisher), + event_publisher: Arc::clone(&event_publisher), }) .await?; @@ -298,28 +250,26 @@ async fn main() -> anyhow::Result<()> { }) as Arc; let search_cleanup = Arc::new(SearchCleanupHandler::new( - Arc::clone(&ctx.repos.search_command), - Arc::clone(&ctx.repos.person_query), + Arc::clone(&search_command), + Arc::clone(&person_query), )) as Arc; let discovery_indexer = Arc::new(MovieDiscoveryIndexer::new( - Arc::clone(&ctx.repos.movie), - Arc::clone(&ctx.repos.search_command), + Arc::clone(&movie), + Arc::clone(&search_command), )) as Arc; tracing::info!("federation event handler registered"); - let wrapup_handler = Arc::new( - application::wrapup::event_handler::WrapUpEventHandler::new( - Arc::clone(&ctx.repos.wrapup_repo), - Arc::clone(&ctx.services.event_publisher), - Arc::clone(&ctx.repos.wrapup_stats), - ), - ) as Arc; - let reindex_handler = - Arc::new(SearchReindexHandler::new(ReindexSearchDeps { - movie: Arc::clone(&ctx.repos.movie), - movie_profile: Arc::clone(&ctx.repos.movie_profile), - search_command: Arc::clone(&ctx.repos.search_command), - person_command: Arc::clone(&ctx.repos.person_command), - person_query: Arc::clone(&ctx.repos.person_query), + let wrapup_handler = + Arc::new(application::wrapup::event_handler::WrapUpEventHandler::new( + Arc::clone(&wrapup_repo), + Arc::clone(&event_publisher), + Arc::clone(&wrapup_stats), + )) as Arc; + let reindex_handler = Arc::new(SearchReindexHandler::new(ReindexSearchDeps { + movie: Arc::clone(&movie), + movie_profile: Arc::clone(&movie_profile), + search_command: Arc::clone(&search_command), + person_command: Arc::clone(&person_command), + person_query: Arc::clone(&person_query), })) as Arc; let mut h = vec![ poster,