Files
movies-diary/crates/application/src/deps.rs
Gabriel Kaszewski 020fbb891e
Some checks failed
CI / Check / Test (push) Failing after 9m16s
feat: always-on Insights — correlation analysis over viewing habits
Conservative, live insights (ADR-0010) derived from each user's diary:
rating lift by genre (first-time watches only), genre seasonality vs a
uniform 1/12 month baseline, medium weekend-lift, and watch-time rhythm.

- domain: WatchRow model, InsightsQuery port, pure insights_analyzer with
  all honesty thresholds (sample minimums, strength cutoffs, suppressions
  for import noise), unit-tested
- adapters: thin Sqlite/Postgres fetch twins, reusing WrapUp's genre fetch
- application: get_insights use case, InsightsGroup in Deps
- composition/server: wiring through Repositories/DatabaseOutput
- presentation: GET /api/v1/insights handler, typed serde-tagged DTOs,
  openapi doc
- spa: /insights route (shadcn cards), insights feature hook, profile
  entry link, en locale strings
- server test: end-to-end seed sqlite → HTTP → rating lift + rhythm

Also records ADR-0010, defines "Insight" in the glossary, and includes
pre-existing worktree comment-stripping cleanups.
2026-10-12 19:29:54 +02:00

201 lines
7.8 KiB
Rust

use std::sync::Arc;
use domain::ports::{EventPublisher, MediaServerParser, ObjectStorage, PersonEnrichmentClient};
use crate::auth::deps::{LoginDeps, LogoutDeps, RefreshDeps, RegisterAndLoginDeps, RegisterDeps};
use crate::diary::deps::{
DeleteReviewDeps, EditReviewDeps, ExportDiaryDeps, GetActivityFeedDeps, GetDiaryDeps,
GetMovieSocialPageDeps, GetReviewHistoryDeps, GetUserFeedDeps,
};
use crate::goals::deps::{GoalCommandDeps, GoalQueryDeps};
use crate::import::deps::{
ApplyMappingDeps, ApplyProfileAndMapDeps, ApplyProfileDeps, CreateSessionDeps,
DeleteImportProfileDeps, ExecuteImportDeps, GetMappingStageDeps, GetPreviewStageDeps,
GetSessionStateDeps, ListImportProfilesDeps, SaveProfileDeps,
};
use crate::insights::deps::GetInsightsDeps;
use crate::integrations::deps::{
ConfirmWatchEventsDeps, DismissWatchEventsDeps, GenerateWebhookTokenDeps, GetWatchQueueDeps,
GetWebhookTokensDeps, IngestWatchEventDeps, RevokeWebhookTokenDeps,
};
use crate::movies::deps::{
EnrichMovieDeps, GetMovieProfileDeps, GetMoviesDeps, ReindexSearchDeps, SyncPosterDeps,
};
use crate::movies::merge_duplicates::MergeDuplicatesDeps;
use crate::person::deps::{EnrichPersonDeps, GetPersonDeps};
use crate::search::deps::SearchDeps;
use crate::social::deps::{SocialCommandDeps, SocialQueryDeps};
use crate::users::deps::{
AuthorizeAdminDeps, DeleteAccountDeps, GetCurrentProfileDeps, GetFederatedProfileDeps,
GetFederatedProfileStatsDeps, GetLocalProfileDeps, GetPageViewerDeps, GetProfileSettingsDeps,
GetSettingsDeps, GetUsersListDeps, ResolveUsernameDeps, UpdateProfileDeps,
UpdateProfileFieldsDeps, UpdateSettingsDeps,
};
use crate::watchlist::deps::{
GetWatchlistDeps, GetWatchlistForOwnerDeps, IsOnWatchlistDeps, RemoveFromWatchlistDeps,
WatchlistAddDeps,
};
use crate::wrapup::deps::{
DeleteWrapUpDeps, GenerateWrapUpDeps, GetReadyReportDeps, GetWrapUpDeps,
HandleWrapUpRequestedDeps, ListWrapUpsDeps,
};
pub struct AuthGroup {
pub login: LoginDeps,
pub register: RegisterDeps,
pub refresh: RefreshDeps,
pub register_and_login: RegisterAndLoginDeps,
pub logout: LogoutDeps,
}
pub struct DiaryGroup {
pub delete_review: DeleteReviewDeps,
pub edit_review: EditReviewDeps,
pub get_movie_social_page: GetMovieSocialPageDeps,
pub get_activity_feed: GetActivityFeedDeps,
pub get_user_feed: GetUserFeedDeps,
pub get_diary: GetDiaryDeps,
pub get_review_history: GetReviewHistoryDeps,
pub export_diary: ExportDiaryDeps,
}
pub struct GoalsGroup {
pub command: GoalCommandDeps,
pub query: GoalQueryDeps,
}
pub struct ImportGroup {
pub create_session: CreateSessionDeps,
pub apply_mapping: ApplyMappingDeps,
pub apply_profile: ApplyProfileDeps,
pub execute_import: ExecuteImportDeps,
pub save_profile: SaveProfileDeps,
pub get_mapping_stage: GetMappingStageDeps,
pub get_preview_stage: GetPreviewStageDeps,
pub get_session_state: GetSessionStateDeps,
pub apply_profile_and_map: ApplyProfileAndMapDeps,
pub delete_profile: DeleteImportProfileDeps,
pub list_profiles: ListImportProfilesDeps,
}
pub struct IntegrationsGroup {
pub ingest_watch_event: IngestWatchEventDeps,
pub confirm_watch_events: ConfirmWatchEventsDeps,
pub dismiss_watch_events: DismissWatchEventsDeps,
pub generate_webhook_token: GenerateWebhookTokenDeps,
pub get_watch_queue: GetWatchQueueDeps,
pub get_webhook_tokens: GetWebhookTokensDeps,
pub revoke_webhook_token: RevokeWebhookTokenDeps,
/// Webhook payload parsers. Held on the group rather than inside
/// `IngestWatchEventDeps` because `ingest::execute` takes the parser as an
/// argument — the caller picks which one per route.
pub jellyfin_parser: Arc<dyn MediaServerParser>,
pub plex_parser: Arc<dyn MediaServerParser>,
}
pub struct InsightsGroup {
pub get: GetInsightsDeps,
}
pub struct MoviesGroup {
pub sync_poster: SyncPosterDeps,
pub get_movie_profile: GetMovieProfileDeps,
pub get_movies: GetMoviesDeps,
}
pub struct PersonGroup {
pub get_person: GetPersonDeps,
}
pub struct SearchGroup {
pub execute: SearchDeps,
}
pub struct SocialGroup {
pub command: SocialCommandDeps,
pub query: SocialQueryDeps,
}
pub struct UsersGroup {
pub get_local_profile: GetLocalProfileDeps,
pub get_federated_profile_stats: GetFederatedProfileStatsDeps,
pub get_page_viewer: GetPageViewerDeps,
pub resolve_username: ResolveUsernameDeps,
pub get_profile_settings: GetProfileSettingsDeps,
pub get_users_list: GetUsersListDeps,
pub update_profile: UpdateProfileDeps,
/// Not reachable from the server binary; see the `Deps`-level note above. Pre-existing dead use case: `users::delete_account::execute` has zero callers anywhere in the workspace, worker included.
pub delete_account: DeleteAccountDeps,
pub get_current_profile: GetCurrentProfileDeps,
pub update_profile_fields: UpdateProfileFieldsDeps,
pub get_settings: GetSettingsDeps,
pub update_settings: UpdateSettingsDeps,
pub authorize_admin: AuthorizeAdminDeps,
pub get_federated_profile: GetFederatedProfileDeps,
}
pub struct WatchlistGroup {
pub add: WatchlistAddDeps,
pub get_watchlist_for_owner: GetWatchlistForOwnerDeps,
pub get_watchlist: GetWatchlistDeps,
pub is_on_watchlist: IsOnWatchlistDeps,
pub remove_from_watchlist: RemoveFromWatchlistDeps,
}
pub struct WrapupGroup {
pub get_ready_report: GetReadyReportDeps,
pub delete_wrapup: DeleteWrapUpDeps,
pub generate: GenerateWrapUpDeps,
pub get_wrapup: GetWrapUpDeps,
pub list_wrapups: ListWrapUpsDeps,
}
/// Every deps struct a handler can need, built once by the composition root.
/// Use cases still receive only their own narrow struct — nothing takes `&Deps`.
///
/// `composition::build_deps` is called only from `crates/presentation/src/main.rs`.
/// The worker-only groups this used to also carry now live in `WorkerDeps`, built by
/// `composition::build_worker_deps` and consumed by `crates/worker/src/main.rs` —
/// `crates/worker` no longer wires its own deps (its former `db.rs` is gone). Nothing
/// in `Deps` below is worker-only anymore, except one field with no consumer anywhere
/// in the workspace — see its comment.
pub struct Deps {
pub auth: AuthGroup,
pub diary: DiaryGroup,
pub goals: GoalsGroup,
pub import: ImportGroup,
pub insights: InsightsGroup,
pub integrations: IntegrationsGroup,
pub movies: MoviesGroup,
pub person: PersonGroup,
pub search: SearchGroup,
pub social: SocialGroup,
pub users: UsersGroup,
pub watchlist: WatchlistGroup,
pub wrapup: WrapupGroup,
}
/// Ports the worker binary can actually construct — a strict subset of
/// `Services`. The worker has no `auth`, `password_hasher`, `diary_exporter`,
/// `document_parser`, or `review_logger`; those ports have no worker-side use case,
/// so `WorkerServices` simply does not carry them (see ADR / task-2 brief for why
/// this is a separate struct rather than an `Option`-riddled `Services`).
pub struct WorkerServices {
pub object_storage: Arc<dyn ObjectStorage>,
pub event_publisher: Arc<dyn EventPublisher>,
/// `Option` here mirrors `Services::person_enrichment` — genuine optional
/// configuration, not a container-shape workaround.
pub person_enrichment: Option<Arc<dyn PersonEnrichmentClient>>,
}
/// The deps structs the worker binary needs, built by `composition::build_worker_deps`.
/// These are the five groups that moved out of `Deps` during worker unification —
/// they have no consumer the server binary can ever reach.
pub struct WorkerDeps {
pub enrich_movie: EnrichMovieDeps,
pub reindex_search: ReindexSearchDeps,
pub merge_duplicates: MergeDuplicatesDeps,
pub enrich_person: EnrichPersonDeps,
pub handle_requested: HandleWrapUpRequestedDeps,
}