Some checks failed
CI / Check / Test (push) Failing after 9m16s
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.
131 lines
5.7 KiB
Rust
131 lines
5.7 KiB
Rust
use sqlx::SqlitePool;
|
|
|
|
mod diary;
|
|
mod goals;
|
|
mod image_ref;
|
|
mod import_profile;
|
|
mod import_session;
|
|
mod insights;
|
|
mod migrations;
|
|
mod models;
|
|
mod movie;
|
|
mod movie_dedup;
|
|
mod persons;
|
|
mod profile;
|
|
mod profile_fields;
|
|
mod refresh_sessions;
|
|
mod review;
|
|
mod stats;
|
|
mod user_settings;
|
|
mod users;
|
|
mod watch_event;
|
|
mod watchlist;
|
|
mod wrapup;
|
|
|
|
pub use diary::SqliteDiaryRepository;
|
|
pub use image_ref::{SqliteImageRefAdapter, create_image_ref};
|
|
pub use import_profile::SqliteImportProfileRepository;
|
|
pub use import_session::SqliteImportSessionRepository;
|
|
pub use insights::SqliteInsightsQuery;
|
|
pub use movie::SqliteMovieRepository;
|
|
pub use movie_dedup::SqliteMovieDeduplicator;
|
|
pub use persons::{SqlitePersonAdapter, create_person_adapter};
|
|
pub use profile::SqliteMovieProfileRepository;
|
|
pub use profile_fields::SqliteProfileFieldsRepository;
|
|
pub use refresh_sessions::SqliteRefreshSessionAdapter;
|
|
pub use review::SqliteReviewRepository;
|
|
pub use sqlite_social::SqliteApContentQuery;
|
|
pub use stats::SqliteStatsRepository;
|
|
pub use users::SqliteUserRepository;
|
|
pub use watch_event::{SqliteWatchEventRepository, SqliteWebhookTokenRepository};
|
|
pub use watchlist::SqliteWatchlistRepository;
|
|
pub use wrapup::{SqliteWrapUpRepository, SqliteWrapUpStatsQuery};
|
|
|
|
pub fn create_profile_fields_repo(
|
|
pool: sqlx::SqlitePool,
|
|
) -> std::sync::Arc<dyn domain::ports::UserProfileFieldsRepository> {
|
|
std::sync::Arc::new(SqliteProfileFieldsRepository::new(pool))
|
|
}
|
|
|
|
pub async fn migrate(pool: &SqlitePool) -> Result<(), domain::errors::DomainError> {
|
|
migrations::run(pool).await
|
|
}
|
|
|
|
pub struct SqliteWireOutput {
|
|
pub pool: SqlitePool,
|
|
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::DiaryQuery>,
|
|
pub stats: std::sync::Arc<dyn domain::ports::StatsRepository>,
|
|
pub user: std::sync::Arc<dyn domain::ports::UserRepository>,
|
|
pub import_session: std::sync::Arc<dyn domain::ports::ImportSessionRepository>,
|
|
pub import_profile: std::sync::Arc<dyn domain::ports::ImportProfileRepository>,
|
|
pub movie_profile: std::sync::Arc<dyn domain::ports::MovieProfileRepository>,
|
|
pub watchlist: std::sync::Arc<dyn domain::ports::WatchlistRepository>,
|
|
pub ap_content: std::sync::Arc<dyn domain::ports::LocalApContentQuery>,
|
|
pub wrapup_repo: std::sync::Arc<dyn domain::ports::WrapUpRepository>,
|
|
pub wrapup_stats: std::sync::Arc<dyn domain::ports::WrapUpStatsQuery>,
|
|
pub insights: std::sync::Arc<dyn domain::ports::InsightsQuery>,
|
|
pub goal_command: std::sync::Arc<dyn domain::ports::GoalCommand>,
|
|
pub goal_query: std::sync::Arc<dyn domain::ports::GoalQuery>,
|
|
pub user_settings: std::sync::Arc<dyn domain::ports::UserSettingsRepository>,
|
|
pub federation_settings: std::sync::Arc<dyn domain::ports::UserFederationSettingsQuery>,
|
|
pub remote_goal: std::sync::Arc<dyn domain::ports::RemoteGoalRepository>,
|
|
pub deduplicator: std::sync::Arc<dyn domain::ports::MovieDeduplicator>,
|
|
}
|
|
|
|
pub async fn wire(database_url: &str) -> anyhow::Result<SqliteWireOutput> {
|
|
use anyhow::Context;
|
|
use sqlx::sqlite::SqliteConnectOptions;
|
|
use std::str::FromStr;
|
|
|
|
let opts = SqliteConnectOptions::from_str(database_url)
|
|
.context("Invalid DATABASE_URL")?
|
|
.create_if_missing(true)
|
|
.journal_mode(sqlx::sqlite::SqliteJournalMode::Wal)
|
|
.busy_timeout(std::time::Duration::from_secs(5));
|
|
let pool = sqlx::pool::PoolOptions::new()
|
|
.max_connections(4)
|
|
.connect_with(opts)
|
|
.await
|
|
.context("Failed to connect to SQLite database")?;
|
|
|
|
migrate(&pool)
|
|
.await
|
|
.map_err(|e| anyhow::anyhow!("{e}"))
|
|
.context("Database migration failed")?;
|
|
|
|
let user_settings_repo = std::sync::Arc::new(user_settings::SqliteUserSettingsRepository::new(
|
|
pool.clone(),
|
|
));
|
|
|
|
let movie_repo = std::sync::Arc::new(SqliteMovieRepository::new(pool.clone()));
|
|
|
|
Ok(SqliteWireOutput {
|
|
pool: pool.clone(),
|
|
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 _,
|
|
user: std::sync::Arc::new(SqliteUserRepository::new(pool.clone())) as _,
|
|
import_session: std::sync::Arc::new(SqliteImportSessionRepository::new(pool.clone())) as _,
|
|
import_profile: std::sync::Arc::new(SqliteImportProfileRepository::new(pool.clone())) as _,
|
|
movie_profile: std::sync::Arc::new(SqliteMovieProfileRepository::new(pool.clone())) as _,
|
|
watchlist: std::sync::Arc::new(SqliteWatchlistRepository::new(pool.clone())) as _,
|
|
ap_content: std::sync::Arc::new(SqliteApContentQuery::new(pool.clone())) as _,
|
|
wrapup_repo: std::sync::Arc::new(SqliteWrapUpRepository::new(pool.clone())) as _,
|
|
wrapup_stats: std::sync::Arc::new(SqliteWrapUpStatsQuery::new(pool.clone())) as _,
|
|
insights: std::sync::Arc::new(SqliteInsightsQuery::new(pool.clone())) as _,
|
|
goal_command: std::sync::Arc::new(goals::SqliteGoalRepository::new(pool.clone())) as _,
|
|
goal_query: std::sync::Arc::new(goals::SqliteGoalRepository::new(pool.clone())) as _,
|
|
user_settings: std::sync::Arc::clone(&user_settings_repo) as _,
|
|
federation_settings: user_settings_repo as _,
|
|
remote_goal: std::sync::Arc::new(sqlite_social::SqliteRemoteGoalRepository::new(
|
|
pool.clone(),
|
|
)) as _,
|
|
deduplicator: std::sync::Arc::new(SqliteMovieDeduplicator::new(pool)) as _,
|
|
})
|
|
}
|