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:
@@ -3,11 +3,12 @@ use std::sync::Arc;
|
||||
use domain::ports::{
|
||||
AuthService, DiaryExporter, DiaryRepository, DocumentParser, EventPublisher,
|
||||
FederatedProfileQuery, GoalRepository, ImportProfileRepository, ImportSessionRepository,
|
||||
MetadataClient, MovieProfileRepository, MovieRepository, ObjectStorage, PasswordHasher,
|
||||
MetadataClient, MovieCommand, MovieProfileRepository, MovieQuery, ObjectStorage, PasswordHasher,
|
||||
PersonCommand, PersonEnrichmentClient, PersonQuery, PosterFetcherClient,
|
||||
RefreshSessionRepository, RemoteGoalRepository, RemoteWatchlistRepository, ReviewRepository,
|
||||
SearchCommand, SearchPort, SocialQueryPort, StatsRepository, UserProfileFieldsRepository,
|
||||
UserRepository, UserSettingsRepository, WatchEventRepository, WatchlistRepository,
|
||||
UserRepository, UserSettingsRepository, WatchEventCommand, WatchEventQuery,
|
||||
WatchlistRepository,
|
||||
WebhookTokenRepository, WrapUpRepository, WrapUpStatsQuery,
|
||||
};
|
||||
|
||||
@@ -16,7 +17,8 @@ use application::ports::ReviewLogger;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Repositories {
|
||||
pub movie: Arc<dyn MovieRepository>,
|
||||
pub movie_command: Arc<dyn MovieCommand>,
|
||||
pub movie_query: Arc<dyn MovieQuery>,
|
||||
pub review: Arc<dyn ReviewRepository>,
|
||||
pub diary: Arc<dyn DiaryRepository>,
|
||||
pub stats: Arc<dyn StatsRepository>,
|
||||
@@ -25,7 +27,8 @@ pub struct Repositories {
|
||||
pub import_profile: Arc<dyn ImportProfileRepository>,
|
||||
pub movie_profile: Arc<dyn MovieProfileRepository>,
|
||||
pub watchlist: Arc<dyn WatchlistRepository>,
|
||||
pub watch_event: Arc<dyn WatchEventRepository>,
|
||||
pub watch_event_command: Arc<dyn WatchEventCommand>,
|
||||
pub watch_event_query: Arc<dyn WatchEventQuery>,
|
||||
pub webhook_token: Arc<dyn WebhookTokenRepository>,
|
||||
pub person_command: Arc<dyn PersonCommand>,
|
||||
pub person_query: Arc<dyn PersonQuery>,
|
||||
|
||||
@@ -4,13 +4,14 @@ use anyhow::Context;
|
||||
use domain::ports::{
|
||||
AuthService, LocalApContentQuery, MetadataClient, ObjectStorage, PasswordHasher,
|
||||
PosterFetcherClient, RefreshSessionRepository, UserProfileFieldsRepository,
|
||||
WatchEventRepository, WebhookTokenRepository,
|
||||
WatchEventCommand, WatchEventQuery, WebhookTokenRepository,
|
||||
};
|
||||
|
||||
pub use infra_wiring::DbPool;
|
||||
|
||||
pub struct DatabaseOutput {
|
||||
pub movie: Arc<dyn domain::ports::MovieRepository>,
|
||||
pub movie_command: Arc<dyn domain::ports::MovieCommand>,
|
||||
pub movie_query: Arc<dyn domain::ports::MovieQuery>,
|
||||
pub review: Arc<dyn domain::ports::ReviewRepository>,
|
||||
pub diary: Arc<dyn domain::ports::DiaryRepository>,
|
||||
pub stats: Arc<dyn domain::ports::StatsRepository>,
|
||||
@@ -19,7 +20,8 @@ pub struct DatabaseOutput {
|
||||
pub import_profile: Arc<dyn domain::ports::ImportProfileRepository>,
|
||||
pub movie_profile: Arc<dyn domain::ports::MovieProfileRepository>,
|
||||
pub watchlist: Arc<dyn domain::ports::WatchlistRepository>,
|
||||
pub watch_event: Arc<dyn WatchEventRepository>,
|
||||
pub watch_event_command: Arc<dyn WatchEventCommand>,
|
||||
pub watch_event_query: Arc<dyn WatchEventQuery>,
|
||||
pub webhook_token: Arc<dyn WebhookTokenRepository>,
|
||||
pub person_command: Arc<dyn domain::ports::PersonCommand>,
|
||||
pub person_query: Arc<dyn domain::ports::PersonQuery>,
|
||||
@@ -47,13 +49,13 @@ pub async fn build_database_adapters(backend: &str, url: &str) -> anyhow::Result
|
||||
let (pc, pq) = postgres::create_person_adapter(w.pool.clone());
|
||||
let (sc, sp) = postgres_search::create_search_adapter(w.pool.clone());
|
||||
let pf = postgres::create_profile_fields_repo(w.pool.clone());
|
||||
let we: Arc<dyn WatchEventRepository> =
|
||||
Arc::new(postgres::PostgresWatchEventRepository::new(w.pool.clone()));
|
||||
let we = Arc::new(postgres::PostgresWatchEventRepository::new(w.pool.clone()));
|
||||
let wt: Arc<dyn WebhookTokenRepository> = Arc::new(
|
||||
postgres::PostgresWebhookTokenRepository::new(w.pool.clone()),
|
||||
);
|
||||
Ok(DatabaseOutput {
|
||||
movie: w.movie,
|
||||
movie_command: w.movie_command,
|
||||
movie_query: w.movie_query,
|
||||
review: w.review,
|
||||
diary: w.diary,
|
||||
stats: w.stats,
|
||||
@@ -62,7 +64,8 @@ pub async fn build_database_adapters(backend: &str, url: &str) -> anyhow::Result
|
||||
import_profile: w.import_profile,
|
||||
movie_profile: w.movie_profile,
|
||||
watchlist: w.watchlist,
|
||||
watch_event: we,
|
||||
watch_event_command: we.clone() as _,
|
||||
watch_event_query: we as _,
|
||||
webhook_token: wt,
|
||||
person_command: pc,
|
||||
person_query: pq,
|
||||
@@ -90,12 +93,12 @@ pub async fn build_database_adapters(backend: &str, url: &str) -> anyhow::Result
|
||||
let (pc, pq) = sqlite::create_person_adapter(w.pool.clone());
|
||||
let (sc, sp) = sqlite_search::create_search_adapter(w.pool.clone());
|
||||
let pf = sqlite::create_profile_fields_repo(w.pool.clone());
|
||||
let we: Arc<dyn WatchEventRepository> =
|
||||
Arc::new(sqlite::SqliteWatchEventRepository::new(w.pool.clone()));
|
||||
let we = Arc::new(sqlite::SqliteWatchEventRepository::new(w.pool.clone()));
|
||||
let wt: Arc<dyn WebhookTokenRepository> =
|
||||
Arc::new(sqlite::SqliteWebhookTokenRepository::new(w.pool.clone()));
|
||||
Ok(DatabaseOutput {
|
||||
movie: w.movie,
|
||||
movie_command: w.movie_command,
|
||||
movie_query: w.movie_query,
|
||||
review: w.review,
|
||||
diary: w.diary,
|
||||
stats: w.stats,
|
||||
@@ -104,7 +107,8 @@ pub async fn build_database_adapters(backend: &str, url: &str) -> anyhow::Result
|
||||
import_profile: w.import_profile,
|
||||
movie_profile: w.movie_profile,
|
||||
watchlist: w.watchlist,
|
||||
watch_event: we,
|
||||
watch_event_command: we.clone() as _,
|
||||
watch_event_query: we as _,
|
||||
webhook_token: wt,
|
||||
person_command: pc,
|
||||
person_query: pq,
|
||||
|
||||
@@ -106,7 +106,7 @@ pub async fn delete_review(
|
||||
let deps = DeleteReviewDeps {
|
||||
review: state.app_ctx.repos.review.clone(),
|
||||
diary: state.app_ctx.repos.diary.clone(),
|
||||
movie: state.app_ctx.repos.movie.clone(),
|
||||
movie_command: state.app_ctx.repos.movie_command.clone(),
|
||||
event_publisher: state.app_ctx.services.event_publisher.clone(),
|
||||
};
|
||||
delete_review::execute(&deps, cmd).await?;
|
||||
@@ -277,7 +277,7 @@ pub async fn post_delete_review_html(
|
||||
let deps = DeleteReviewDeps {
|
||||
review: state.app_ctx.repos.review.clone(),
|
||||
diary: state.app_ctx.repos.diary.clone(),
|
||||
movie: state.app_ctx.repos.movie.clone(),
|
||||
movie_command: state.app_ctx.repos.movie_command.clone(),
|
||||
event_publisher: state.app_ctx.services.event_publisher.clone(),
|
||||
};
|
||||
match delete_review::execute(&deps, cmd).await {
|
||||
|
||||
@@ -10,6 +10,7 @@ use api_types::{
|
||||
CreateGoalRequest, GoalDto, GoalsResponse, UpdateGoalRequest, UpdateUserSettingsRequest,
|
||||
UserSettingsDto,
|
||||
};
|
||||
use application::goals::deps::{GoalCommandDeps, GoalQueryDeps};
|
||||
|
||||
// ── Shared mapper ────────────────────────────────────────────────────────────
|
||||
|
||||
@@ -38,9 +39,12 @@ pub async fn list_goals(
|
||||
State(state): State<AppState>,
|
||||
user: AuthenticatedUser,
|
||||
) -> Result<Json<GoalsResponse>, ApiError> {
|
||||
let deps = GoalQueryDeps {
|
||||
goal: state.app_ctx.repos.goal.clone(),
|
||||
stats: state.app_ctx.repos.stats.clone(),
|
||||
};
|
||||
let goals = application::goals::list::execute(
|
||||
state.app_ctx.repos.goal.clone(),
|
||||
state.app_ctx.repos.stats.clone(),
|
||||
&deps,
|
||||
application::goals::queries::ListGoalsQuery {
|
||||
user_id: user.0.value(),
|
||||
},
|
||||
@@ -65,10 +69,13 @@ pub async fn create_goal(
|
||||
user: AuthenticatedUser,
|
||||
Json(req): Json<CreateGoalRequest>,
|
||||
) -> Result<Json<GoalDto>, ApiError> {
|
||||
let deps = GoalCommandDeps {
|
||||
goal: state.app_ctx.repos.goal.clone(),
|
||||
stats: state.app_ctx.repos.stats.clone(),
|
||||
event_publisher: state.app_ctx.services.event_publisher.clone(),
|
||||
};
|
||||
let g = application::goals::create::execute(
|
||||
state.app_ctx.repos.goal.clone(),
|
||||
state.app_ctx.repos.stats.clone(),
|
||||
state.app_ctx.services.event_publisher.clone(),
|
||||
&deps,
|
||||
application::goals::commands::CreateGoalCommand {
|
||||
user_id: user.0.value(),
|
||||
year: req.year,
|
||||
@@ -95,10 +102,13 @@ pub async fn update_goal(
|
||||
Path(year): Path<u16>,
|
||||
Json(req): Json<UpdateGoalRequest>,
|
||||
) -> Result<Json<GoalDto>, ApiError> {
|
||||
let deps = GoalCommandDeps {
|
||||
goal: state.app_ctx.repos.goal.clone(),
|
||||
stats: state.app_ctx.repos.stats.clone(),
|
||||
event_publisher: state.app_ctx.services.event_publisher.clone(),
|
||||
};
|
||||
let g = application::goals::update::execute(
|
||||
state.app_ctx.repos.goal.clone(),
|
||||
state.app_ctx.repos.stats.clone(),
|
||||
state.app_ctx.services.event_publisher.clone(),
|
||||
&deps,
|
||||
application::goals::commands::UpdateGoalCommand {
|
||||
user_id: user.0.value(),
|
||||
year,
|
||||
@@ -123,9 +133,13 @@ pub async fn delete_goal(
|
||||
user: AuthenticatedUser,
|
||||
Path(year): Path<u16>,
|
||||
) -> Result<StatusCode, ApiError> {
|
||||
let deps = GoalCommandDeps {
|
||||
goal: state.app_ctx.repos.goal.clone(),
|
||||
stats: state.app_ctx.repos.stats.clone(),
|
||||
event_publisher: state.app_ctx.services.event_publisher.clone(),
|
||||
};
|
||||
application::goals::delete::execute(
|
||||
state.app_ctx.repos.goal.clone(),
|
||||
state.app_ctx.services.event_publisher.clone(),
|
||||
&deps,
|
||||
application::goals::commands::DeleteGoalCommand {
|
||||
user_id: user.0.value(),
|
||||
year,
|
||||
@@ -148,9 +162,12 @@ pub async fn get_user_goals(
|
||||
AuthenticatedUser(_viewer): AuthenticatedUser,
|
||||
Path(user_id): Path<Uuid>,
|
||||
) -> Result<Json<GoalsResponse>, ApiError> {
|
||||
let deps = GoalQueryDeps {
|
||||
goal: state.app_ctx.repos.goal.clone(),
|
||||
stats: state.app_ctx.repos.stats.clone(),
|
||||
};
|
||||
let goals = application::goals::list::execute(
|
||||
state.app_ctx.repos.goal.clone(),
|
||||
state.app_ctx.repos.stats.clone(),
|
||||
&deps,
|
||||
application::goals::queries::ListGoalsQuery { user_id },
|
||||
)
|
||||
.await?;
|
||||
|
||||
@@ -19,6 +19,7 @@ use application::import::{
|
||||
DeleteImportProfileCommand, ExecuteImportCommand, SaveImportProfileCommand,
|
||||
},
|
||||
create_session as create_import_session, delete_profile as delete_import_profile,
|
||||
deps::{ApplyMappingDeps, ApplyProfileDeps, CreateSessionDeps, ExecuteImportDeps, SaveProfileDeps},
|
||||
execute as execute_import, list_profiles as list_import_profiles,
|
||||
save_profile as save_import_profile,
|
||||
};
|
||||
@@ -160,8 +161,10 @@ pub async fn post_upload(
|
||||
};
|
||||
|
||||
match create_import_session::execute(
|
||||
state.app_ctx.repos.import_session.clone(),
|
||||
state.app_ctx.services.document_parser.clone(),
|
||||
&CreateSessionDeps {
|
||||
import_session: state.app_ctx.repos.import_session.clone(),
|
||||
document_parser: state.app_ctx.services.document_parser.clone(),
|
||||
},
|
||||
CreateImportSessionCommand {
|
||||
user_id: user_id.value(),
|
||||
bytes,
|
||||
@@ -250,9 +253,11 @@ pub async fn post_mapping(
|
||||
.into_response();
|
||||
}
|
||||
match apply_import_mapping::execute(
|
||||
state.app_ctx.repos.import_session.clone(),
|
||||
state.app_ctx.services.document_parser.clone(),
|
||||
state.app_ctx.repos.movie.clone(),
|
||||
&ApplyMappingDeps {
|
||||
import_session: state.app_ctx.repos.import_session.clone(),
|
||||
document_parser: state.app_ctx.services.document_parser.clone(),
|
||||
movie_query: state.app_ctx.repos.movie_query.clone(),
|
||||
},
|
||||
ApplyImportMappingCommand {
|
||||
user_id: user_id.value(),
|
||||
session_id: session_id.value(),
|
||||
@@ -346,8 +351,10 @@ pub async fn post_confirm(
|
||||
.filter(|n| !n.trim().is_empty());
|
||||
if let Some(name) = profile_name {
|
||||
let _ = save_import_profile::execute(
|
||||
state.app_ctx.repos.import_session.clone(),
|
||||
state.app_ctx.repos.import_profile.clone(),
|
||||
&SaveProfileDeps {
|
||||
import_session: state.app_ctx.repos.import_session.clone(),
|
||||
import_profile: state.app_ctx.repos.import_profile.clone(),
|
||||
},
|
||||
SaveImportProfileCommand {
|
||||
user_id: user_id.value(),
|
||||
session_id: session_id.value(),
|
||||
@@ -365,8 +372,10 @@ pub async fn post_confirm(
|
||||
.collect();
|
||||
|
||||
match execute_import::execute(
|
||||
state.app_ctx.repos.import_session.clone(),
|
||||
state.app_ctx.services.review_logger.clone(),
|
||||
&ExecuteImportDeps {
|
||||
import_session: state.app_ctx.repos.import_session.clone(),
|
||||
review_logger: state.app_ctx.services.review_logger.clone(),
|
||||
},
|
||||
ExecuteImportCommand {
|
||||
user_id: user_id.value(),
|
||||
session_id: session_id.value(),
|
||||
@@ -487,8 +496,10 @@ pub async fn api_post_session(
|
||||
_ => FileFormat::Csv,
|
||||
};
|
||||
let r = create_import_session::execute(
|
||||
state.app_ctx.repos.import_session.clone(),
|
||||
state.app_ctx.services.document_parser.clone(),
|
||||
&CreateSessionDeps {
|
||||
import_session: state.app_ctx.repos.import_session.clone(),
|
||||
document_parser: state.app_ctx.services.document_parser.clone(),
|
||||
},
|
||||
CreateImportSessionCommand {
|
||||
user_id: user_id.value(),
|
||||
bytes,
|
||||
@@ -583,9 +594,11 @@ pub async fn api_put_mapping(
|
||||
.collect();
|
||||
|
||||
let rows = apply_import_mapping::execute(
|
||||
state.app_ctx.repos.import_session.clone(),
|
||||
state.app_ctx.services.document_parser.clone(),
|
||||
state.app_ctx.repos.movie.clone(),
|
||||
&ApplyMappingDeps {
|
||||
import_session: state.app_ctx.repos.import_session.clone(),
|
||||
document_parser: state.app_ctx.services.document_parser.clone(),
|
||||
movie_query: state.app_ctx.repos.movie_query.clone(),
|
||||
},
|
||||
ApplyImportMappingCommand {
|
||||
user_id: user_id.value(),
|
||||
session_id: session_id.value(),
|
||||
@@ -674,8 +687,10 @@ pub async fn api_post_confirm(
|
||||
.map(ImportSessionId::from_uuid)
|
||||
.map_err(|_| DomainError::ValidationError("invalid session id".into()))?;
|
||||
let s = execute_import::execute(
|
||||
state.app_ctx.repos.import_session.clone(),
|
||||
state.app_ctx.services.review_logger.clone(),
|
||||
&ExecuteImportDeps {
|
||||
import_session: state.app_ctx.repos.import_session.clone(),
|
||||
review_logger: state.app_ctx.services.review_logger.clone(),
|
||||
},
|
||||
ExecuteImportCommand {
|
||||
user_id: user_id.value(),
|
||||
session_id: session_id.value(),
|
||||
@@ -739,8 +754,10 @@ pub async fn api_post_profile(
|
||||
.map(ImportSessionId::from_uuid)
|
||||
.map_err(|_| DomainError::ValidationError("invalid session id".into()))?;
|
||||
let id = save_import_profile::execute(
|
||||
state.app_ctx.repos.import_session.clone(),
|
||||
state.app_ctx.repos.import_profile.clone(),
|
||||
&SaveProfileDeps {
|
||||
import_session: state.app_ctx.repos.import_session.clone(),
|
||||
import_profile: state.app_ctx.repos.import_profile.clone(),
|
||||
},
|
||||
SaveImportProfileCommand {
|
||||
user_id: user_id.value(),
|
||||
session_id: session_id.value(),
|
||||
@@ -810,8 +827,10 @@ pub async fn api_apply_profile(
|
||||
.map_err(|_| DomainError::ValidationError("invalid profile id".into()))?;
|
||||
|
||||
apply_import_profile::execute(
|
||||
state.app_ctx.repos.import_profile.clone(),
|
||||
state.app_ctx.repos.import_session.clone(),
|
||||
&ApplyProfileDeps {
|
||||
import_profile: state.app_ctx.repos.import_profile.clone(),
|
||||
import_session: state.app_ctx.repos.import_session.clone(),
|
||||
},
|
||||
ApplyImportProfileCommand {
|
||||
user_id: user_id.value(),
|
||||
session_id,
|
||||
@@ -832,9 +851,11 @@ pub async fn api_apply_profile(
|
||||
|
||||
let mappings = session.field_mappings.unwrap_or_default();
|
||||
let rows = apply_import_mapping::execute(
|
||||
state.app_ctx.repos.import_session.clone(),
|
||||
state.app_ctx.services.document_parser.clone(),
|
||||
state.app_ctx.repos.movie.clone(),
|
||||
&ApplyMappingDeps {
|
||||
import_session: state.app_ctx.repos.import_session.clone(),
|
||||
document_parser: state.app_ctx.services.document_parser.clone(),
|
||||
movie_query: state.app_ctx.repos.movie_query.clone(),
|
||||
},
|
||||
ApplyImportMappingCommand {
|
||||
user_id: user_id.value(),
|
||||
session_id,
|
||||
|
||||
@@ -135,7 +135,7 @@ pub async fn get_watch_queue_page(
|
||||
let query = GetWatchQueueQuery {
|
||||
user_id: user_id.value(),
|
||||
};
|
||||
let events = get_watch_queue::execute(state.app_ctx.repos.watch_event.clone(), query)
|
||||
let events = get_watch_queue::execute(state.app_ctx.repos.watch_event_query.clone(), query)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
|
||||
@@ -173,7 +173,8 @@ pub async fn post_confirm_single(
|
||||
};
|
||||
|
||||
match confirm_watch_events::execute(
|
||||
state.app_ctx.repos.watch_event.clone(),
|
||||
state.app_ctx.repos.watch_event_command.clone(),
|
||||
state.app_ctx.repos.watch_event_query.clone(),
|
||||
state.app_ctx.services.review_logger.clone(),
|
||||
cmd,
|
||||
)
|
||||
@@ -203,7 +204,7 @@ pub async fn post_dismiss_single(
|
||||
event_ids: vec![event_id],
|
||||
};
|
||||
|
||||
match dismiss_watch_events::execute(state.app_ctx.repos.watch_event.clone(), cmd).await {
|
||||
match dismiss_watch_events::execute(state.app_ctx.repos.watch_event_command.clone(), state.app_ctx.repos.watch_event_query.clone(), cmd).await {
|
||||
Ok(_) => Redirect::to("/watch-queue").into_response(),
|
||||
Err(e) => {
|
||||
let msg = encode_error(&e.to_string());
|
||||
|
||||
@@ -48,7 +48,7 @@ pub async fn list_movies(
|
||||
Query(params): Query<MoviesQueryParams>,
|
||||
) -> Result<Json<MoviesResponse>, ApiError> {
|
||||
let page = get_movies::execute(
|
||||
state.app_ctx.repos.movie.clone(),
|
||||
state.app_ctx.repos.movie_query.clone(),
|
||||
GetMoviesQuery {
|
||||
limit: params.limit,
|
||||
offset: params.offset,
|
||||
@@ -122,7 +122,8 @@ pub async fn sync_poster(
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
sync_poster::execute(
|
||||
&SyncPosterDeps {
|
||||
movie: state.app_ctx.repos.movie.clone(),
|
||||
movie_command: state.app_ctx.repos.movie_command.clone(),
|
||||
movie_query: state.app_ctx.repos.movie_query.clone(),
|
||||
movie_profile: state.app_ctx.repos.movie_profile.clone(),
|
||||
metadata: state.app_ctx.services.metadata.clone(),
|
||||
poster_fetcher: state.app_ctx.services.poster_fetcher.clone(),
|
||||
@@ -154,7 +155,7 @@ pub async fn get_movie_detail(
|
||||
|
||||
let result = get_movie_social_page::execute(
|
||||
&GetMovieSocialPageDeps {
|
||||
movie: state.app_ctx.repos.movie.clone(),
|
||||
movie_query: state.app_ctx.repos.movie_query.clone(),
|
||||
diary: state.app_ctx.repos.diary.clone(),
|
||||
movie_profile: state.app_ctx.repos.movie_profile.clone(),
|
||||
},
|
||||
@@ -288,7 +289,7 @@ pub async fn get_movie_detail_html(
|
||||
|
||||
match get_movie_social_page::execute(
|
||||
&GetMovieSocialPageDeps {
|
||||
movie: state.app_ctx.repos.movie.clone(),
|
||||
movie_query: state.app_ctx.repos.movie_query.clone(),
|
||||
diary: state.app_ctx.repos.diary.clone(),
|
||||
movie_profile: state.app_ctx.repos.movie_profile.clone(),
|
||||
},
|
||||
|
||||
@@ -269,54 +269,62 @@ pub async fn get_user_profile(
|
||||
Err(e) => return crate::errors::domain_error_response(e),
|
||||
};
|
||||
|
||||
let entries = profile.entries.map(|p| DiaryResponse {
|
||||
items: p
|
||||
.items
|
||||
.iter()
|
||||
.map(crate::mappers::movies::entry_to_dto)
|
||||
.collect(),
|
||||
total_count: p.total_count,
|
||||
limit: p.limit,
|
||||
offset: p.offset,
|
||||
});
|
||||
|
||||
let history = profile.history.map(|entries| {
|
||||
application::users::group_by_month(entries)
|
||||
.into_iter()
|
||||
.map(|m| MonthActivityDto {
|
||||
year_month: m.year_month,
|
||||
month_label: m.month_label,
|
||||
count: m.count,
|
||||
entries: m
|
||||
.entries
|
||||
let view_data = if let Some(p) = profile.entries {
|
||||
Some(api_types::ProfileViewData::Entries {
|
||||
entries: DiaryResponse {
|
||||
items: p
|
||||
.items
|
||||
.iter()
|
||||
.map(crate::mappers::movies::entry_to_dto)
|
||||
.collect(),
|
||||
})
|
||||
.collect()
|
||||
});
|
||||
|
||||
let trends = profile.trends.map(|t| UserTrendsDto {
|
||||
monthly_ratings: t
|
||||
.monthly_ratings
|
||||
.into_iter()
|
||||
.map(|r| MonthlyRatingDto {
|
||||
year_month: r.year_month,
|
||||
month_label: r.month_label,
|
||||
avg_rating: r.avg_rating,
|
||||
count: r.count,
|
||||
})
|
||||
.collect(),
|
||||
top_directors: t
|
||||
.top_directors
|
||||
.into_iter()
|
||||
.map(|d| DirectorStatDto {
|
||||
director: d.director,
|
||||
count: d.count,
|
||||
})
|
||||
.collect(),
|
||||
max_director_count: t.max_director_count,
|
||||
});
|
||||
total_count: p.total_count,
|
||||
limit: p.limit,
|
||||
offset: p.offset,
|
||||
},
|
||||
})
|
||||
} else if let Some(h) = profile.history {
|
||||
Some(api_types::ProfileViewData::History {
|
||||
history: application::users::group_by_month(h)
|
||||
.into_iter()
|
||||
.map(|m| MonthActivityDto {
|
||||
year_month: m.year_month,
|
||||
month_label: m.month_label,
|
||||
count: m.count,
|
||||
entries: m
|
||||
.entries
|
||||
.iter()
|
||||
.map(crate::mappers::movies::entry_to_dto)
|
||||
.collect(),
|
||||
})
|
||||
.collect(),
|
||||
})
|
||||
} else if let Some(t) = profile.trends {
|
||||
Some(api_types::ProfileViewData::Trends {
|
||||
trends: UserTrendsDto {
|
||||
monthly_ratings: t
|
||||
.monthly_ratings
|
||||
.into_iter()
|
||||
.map(|r| MonthlyRatingDto {
|
||||
year_month: r.year_month,
|
||||
month_label: r.month_label,
|
||||
avg_rating: r.avg_rating,
|
||||
count: r.count,
|
||||
})
|
||||
.collect(),
|
||||
top_directors: t
|
||||
.top_directors
|
||||
.into_iter()
|
||||
.map(|d| DirectorStatDto {
|
||||
director: d.director,
|
||||
count: d.count,
|
||||
})
|
||||
.collect(),
|
||||
max_director_count: t.max_director_count,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
Json(UserProfileResponse {
|
||||
user_id,
|
||||
@@ -339,13 +347,13 @@ pub async fn get_user_profile(
|
||||
},
|
||||
following_count: profile.following_count,
|
||||
followers_count: profile.followers_count,
|
||||
entries,
|
||||
history,
|
||||
trends,
|
||||
view_data,
|
||||
goals: {
|
||||
let goals_list = application::goals::list::execute(
|
||||
state.app_ctx.repos.goal.clone(),
|
||||
state.app_ctx.repos.stats.clone(),
|
||||
&application::goals::deps::GoalQueryDeps {
|
||||
goal: state.app_ctx.repos.goal.clone(),
|
||||
stats: state.app_ctx.repos.stats.clone(),
|
||||
},
|
||||
application::goals::queries::ListGoalsQuery { user_id },
|
||||
)
|
||||
.await
|
||||
@@ -394,38 +402,46 @@ async fn build_federated_profile_response(
|
||||
Err(e) => return crate::errors::domain_error_response(e),
|
||||
};
|
||||
|
||||
let entries = profile.entries.map(|p| DiaryResponse {
|
||||
items: p
|
||||
.items
|
||||
.iter()
|
||||
.map(crate::mappers::movies::entry_to_dto)
|
||||
.collect(),
|
||||
total_count: p.total_count,
|
||||
limit: p.limit,
|
||||
offset: p.offset,
|
||||
});
|
||||
|
||||
let trends = profile.trends.map(|t| UserTrendsDto {
|
||||
monthly_ratings: t
|
||||
.monthly_ratings
|
||||
.into_iter()
|
||||
.map(|r| MonthlyRatingDto {
|
||||
year_month: r.year_month,
|
||||
month_label: r.month_label,
|
||||
avg_rating: r.avg_rating,
|
||||
count: r.count,
|
||||
})
|
||||
.collect(),
|
||||
top_directors: t
|
||||
.top_directors
|
||||
.into_iter()
|
||||
.map(|d| DirectorStatDto {
|
||||
director: d.director,
|
||||
count: d.count,
|
||||
})
|
||||
.collect(),
|
||||
max_director_count: t.max_director_count,
|
||||
});
|
||||
let view_data = if let Some(p) = profile.entries {
|
||||
Some(api_types::ProfileViewData::Entries {
|
||||
entries: DiaryResponse {
|
||||
items: p
|
||||
.items
|
||||
.iter()
|
||||
.map(crate::mappers::movies::entry_to_dto)
|
||||
.collect(),
|
||||
total_count: p.total_count,
|
||||
limit: p.limit,
|
||||
offset: p.offset,
|
||||
},
|
||||
})
|
||||
} else if let Some(t) = profile.trends {
|
||||
Some(api_types::ProfileViewData::Trends {
|
||||
trends: UserTrendsDto {
|
||||
monthly_ratings: t
|
||||
.monthly_ratings
|
||||
.into_iter()
|
||||
.map(|r| MonthlyRatingDto {
|
||||
year_month: r.year_month,
|
||||
month_label: r.month_label,
|
||||
avg_rating: r.avg_rating,
|
||||
count: r.count,
|
||||
})
|
||||
.collect(),
|
||||
top_directors: t
|
||||
.top_directors
|
||||
.into_iter()
|
||||
.map(|d| DirectorStatDto {
|
||||
director: d.director,
|
||||
count: d.count,
|
||||
})
|
||||
.collect(),
|
||||
max_director_count: t.max_director_count,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let username = fed
|
||||
.display_name
|
||||
@@ -449,9 +465,7 @@ async fn build_federated_profile_response(
|
||||
},
|
||||
following_count: 0,
|
||||
followers_count: 0,
|
||||
entries,
|
||||
history: None,
|
||||
trends,
|
||||
view_data,
|
||||
goals: None,
|
||||
is_federated: true,
|
||||
handle: Some(fed.handle),
|
||||
@@ -735,8 +749,10 @@ pub async fn get_user_profile_html(
|
||||
search: params.search.clone(),
|
||||
goals: {
|
||||
let goals_list = application::goals::list::execute(
|
||||
state.app_ctx.repos.goal.clone(),
|
||||
state.app_ctx.repos.stats.clone(),
|
||||
&application::goals::deps::GoalQueryDeps {
|
||||
goal: state.app_ctx.repos.goal.clone(),
|
||||
stats: state.app_ctx.repos.stats.clone(),
|
||||
},
|
||||
application::goals::queries::ListGoalsQuery {
|
||||
user_id: profile_user_uuid,
|
||||
},
|
||||
|
||||
@@ -95,7 +95,8 @@ pub async fn post_watchlist_add(
|
||||
Json(req): Json<AddToWatchlistRequest>,
|
||||
) -> Result<impl IntoResponse, ApiError> {
|
||||
let deps = WatchlistAddDeps {
|
||||
movie: state.app_ctx.repos.movie.clone(),
|
||||
movie_command: state.app_ctx.repos.movie_command.clone(),
|
||||
movie_query: state.app_ctx.repos.movie_query.clone(),
|
||||
metadata: state.app_ctx.services.metadata.clone(),
|
||||
watchlist: state.app_ctx.repos.watchlist.clone(),
|
||||
event_publisher: state.app_ctx.services.event_publisher.clone(),
|
||||
@@ -280,7 +281,8 @@ pub async fn post_watchlist_add_html(
|
||||
};
|
||||
|
||||
let deps = WatchlistAddDeps {
|
||||
movie: state.app_ctx.repos.movie.clone(),
|
||||
movie_command: state.app_ctx.repos.movie_command.clone(),
|
||||
movie_query: state.app_ctx.repos.movie_query.clone(),
|
||||
metadata: state.app_ctx.services.metadata.clone(),
|
||||
watchlist: state.app_ctx.repos.watchlist.clone(),
|
||||
event_publisher: state.app_ctx.services.event_publisher.clone(),
|
||||
|
||||
@@ -129,7 +129,8 @@ async fn run_ingest(
|
||||
) -> StatusCode {
|
||||
let deps = IngestWatchEventDeps {
|
||||
webhook_token: state.app_ctx.repos.webhook_token.clone(),
|
||||
watch_event: state.app_ctx.repos.watch_event.clone(),
|
||||
watch_event_command: state.app_ctx.repos.watch_event_command.clone(),
|
||||
watch_event_query: state.app_ctx.repos.watch_event_query.clone(),
|
||||
event_publisher: state.app_ctx.services.event_publisher.clone(),
|
||||
};
|
||||
match ingest_watch_event::execute(&deps, cmd, parser).await {
|
||||
@@ -250,7 +251,7 @@ pub async fn get_watch_queue(
|
||||
let query = GetWatchQueueQuery {
|
||||
user_id: user.0.value(),
|
||||
};
|
||||
let events = get_watch_queue::execute(state.app_ctx.repos.watch_event.clone(), query).await?;
|
||||
let events = get_watch_queue::execute(state.app_ctx.repos.watch_event_query.clone(), query).await?;
|
||||
|
||||
let dtos = events
|
||||
.into_iter()
|
||||
@@ -296,7 +297,8 @@ pub async fn post_confirm_watch_events(
|
||||
};
|
||||
|
||||
let confirmed = confirm_watch_events::execute(
|
||||
state.app_ctx.repos.watch_event.clone(),
|
||||
state.app_ctx.repos.watch_event_command.clone(),
|
||||
state.app_ctx.repos.watch_event_query.clone(),
|
||||
state.app_ctx.services.review_logger.clone(),
|
||||
cmd,
|
||||
)
|
||||
@@ -325,6 +327,6 @@ pub async fn post_dismiss_watch_events(
|
||||
};
|
||||
|
||||
let dismissed =
|
||||
dismiss_watch_events::execute(state.app_ctx.repos.watch_event.clone(), cmd).await?;
|
||||
dismiss_watch_events::execute(state.app_ctx.repos.watch_event_command.clone(), state.app_ctx.repos.watch_event_query.clone(), cmd).await?;
|
||||
Ok(Json(DismissWatchResponse { dismissed }))
|
||||
}
|
||||
|
||||
@@ -97,7 +97,7 @@ async fn wire_dependencies() -> anyhow::Result<(AppState, axum::Router)> {
|
||||
remote_watchlist_repo: remote_watchlist_repo.clone(),
|
||||
remote_goal_repo: Arc::clone(&db.remote_goal),
|
||||
local_ap_content: Arc::clone(&ap_content_repo),
|
||||
movie_repo: Arc::clone(&db.movie),
|
||||
movie_repo: Arc::clone(&db.movie_query),
|
||||
review_repo: Arc::clone(&db.review),
|
||||
diary_repo: Arc::clone(&db.diary),
|
||||
goal_repo: Arc::clone(&db.goal),
|
||||
@@ -127,7 +127,8 @@ async fn wire_dependencies() -> anyhow::Result<(AppState, axum::Router)> {
|
||||
let ap_router = axum::Router::new();
|
||||
|
||||
let review_logger = Arc::new(application::diary::review_logger::DefaultReviewLogger::new(
|
||||
Arc::clone(&db.movie),
|
||||
Arc::clone(&db.movie_command),
|
||||
Arc::clone(&db.movie_query),
|
||||
Arc::clone(&db.review),
|
||||
Arc::clone(&db.watchlist),
|
||||
Arc::clone(&metadata_client),
|
||||
@@ -136,7 +137,8 @@ async fn wire_dependencies() -> anyhow::Result<(AppState, axum::Router)> {
|
||||
|
||||
let app_ctx = AppContext {
|
||||
repos: Repositories {
|
||||
movie: db.movie,
|
||||
movie_command: db.movie_command,
|
||||
movie_query: db.movie_query,
|
||||
review: db.review,
|
||||
diary: db.diary,
|
||||
stats: db.stats,
|
||||
@@ -145,7 +147,8 @@ async fn wire_dependencies() -> anyhow::Result<(AppState, axum::Router)> {
|
||||
import_profile: db.import_profile,
|
||||
movie_profile: db.movie_profile,
|
||||
watchlist: db.watchlist,
|
||||
watch_event: db.watch_event,
|
||||
watch_event_command: db.watch_event_command,
|
||||
watch_event_query: db.watch_event_query,
|
||||
webhook_token: db.webhook_token,
|
||||
person_command: db.person_command,
|
||||
person_query: db.person_query,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use api_types::{
|
||||
ProfileFieldDto, ProfileResponse, UpdateProfileFieldsRequest, UserProfileBase,
|
||||
UserProfileResponse, UserStatsDto, UserSummaryDto, UsersResponse,
|
||||
ProfileFieldDto, ProfileResponse, ProfileViewData, UpdateProfileFieldsRequest,
|
||||
UserProfileBase, UserProfileResponse, UserStatsDto, UserSummaryDto, UsersResponse,
|
||||
};
|
||||
use utoipa::OpenApi;
|
||||
|
||||
@@ -18,6 +18,7 @@ use utoipa::OpenApi;
|
||||
UserSummaryDto,
|
||||
UserProfileBase,
|
||||
UserProfileResponse,
|
||||
ProfileViewData,
|
||||
UserStatsDto,
|
||||
ProfileResponse,
|
||||
UpdateProfileFieldsRequest,
|
||||
|
||||
@@ -17,7 +17,7 @@ use domain::{
|
||||
collections::{PageParams, Paginated},
|
||||
},
|
||||
ports::{
|
||||
AuthService, DiaryRepository, EventPublisher, MetadataClient, MovieRepository,
|
||||
AuthService, DiaryRepository, EventPublisher, MetadataClient, MovieCommand, MovieQuery,
|
||||
ObjectStorage, PasswordHasher, PersonCommand, PersonQuery, PosterFetcherClient,
|
||||
ReviewRepository, SearchCommand, SearchPort, StatsRepository, UserRepository,
|
||||
WatchlistRepository,
|
||||
@@ -35,7 +35,16 @@ use tower::ServiceExt;
|
||||
pub struct Panic;
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl MovieRepository for Panic {
|
||||
impl MovieCommand for Panic {
|
||||
async fn upsert_movie(&self, _: &Movie) -> Result<(), DomainError> {
|
||||
panic!()
|
||||
}
|
||||
async fn delete_movie(&self, _: &MovieId) -> Result<(), DomainError> {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
#[async_trait::async_trait]
|
||||
impl MovieQuery for Panic {
|
||||
async fn get_movie_by_external_id(
|
||||
&self,
|
||||
_: &ExternalMetadataId,
|
||||
@@ -52,12 +61,6 @@ impl MovieRepository for Panic {
|
||||
) -> Result<Vec<Movie>, DomainError> {
|
||||
panic!()
|
||||
}
|
||||
async fn upsert_movie(&self, _: &Movie) -> Result<(), DomainError> {
|
||||
panic!()
|
||||
}
|
||||
async fn delete_movie(&self, _: &MovieId) -> Result<(), DomainError> {
|
||||
panic!()
|
||||
}
|
||||
async fn existing_external_ids(
|
||||
&self,
|
||||
_: &[ExternalMetadataId],
|
||||
@@ -530,7 +533,7 @@ impl domain::ports::RemoteWatchlistRepository for Panic {
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl domain::ports::WatchEventRepository for Panic {
|
||||
impl domain::ports::WatchEventCommand for Panic {
|
||||
async fn save(&self, _: &domain::models::WatchEvent) -> Result<(), DomainError> {
|
||||
panic!()
|
||||
}
|
||||
@@ -541,6 +544,22 @@ impl domain::ports::WatchEventRepository for Panic {
|
||||
) -> Result<(), DomainError> {
|
||||
panic!()
|
||||
}
|
||||
async fn update_status_batch(
|
||||
&self,
|
||||
_: &[domain::value_objects::WatchEventId],
|
||||
_: domain::models::WatchEventStatus,
|
||||
) -> Result<u64, DomainError> {
|
||||
panic!()
|
||||
}
|
||||
async fn delete_non_pending_older_than(
|
||||
&self,
|
||||
_: chrono::NaiveDateTime,
|
||||
) -> Result<u64, DomainError> {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
#[async_trait::async_trait]
|
||||
impl domain::ports::WatchEventQuery for Panic {
|
||||
async fn list_pending(
|
||||
&self,
|
||||
_: &domain::value_objects::UserId,
|
||||
@@ -559,13 +578,6 @@ impl domain::ports::WatchEventRepository for Panic {
|
||||
) -> Result<Vec<domain::models::WatchEvent>, DomainError> {
|
||||
panic!()
|
||||
}
|
||||
async fn update_status_batch(
|
||||
&self,
|
||||
_: &[domain::value_objects::WatchEventId],
|
||||
_: domain::models::WatchEventStatus,
|
||||
) -> Result<u64, DomainError> {
|
||||
panic!()
|
||||
}
|
||||
async fn find_duplicate(
|
||||
&self,
|
||||
_: &domain::value_objects::UserId,
|
||||
@@ -574,12 +586,6 @@ impl domain::ports::WatchEventRepository for Panic {
|
||||
) -> Result<bool, DomainError> {
|
||||
panic!()
|
||||
}
|
||||
async fn delete_non_pending_older_than(
|
||||
&self,
|
||||
_: chrono::NaiveDateTime,
|
||||
) -> Result<u64, DomainError> {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
#[async_trait::async_trait]
|
||||
impl domain::ports::WebhookTokenRepository for Panic {
|
||||
@@ -782,7 +788,8 @@ pub fn make_test_state(auth_service: Arc<dyn AuthService>) -> crate::state::AppS
|
||||
crate::state::AppState {
|
||||
app_ctx: AppContext {
|
||||
repos: Repositories {
|
||||
movie: Arc::clone(&repo) as _,
|
||||
movie_command: Arc::clone(&repo) as _,
|
||||
movie_query: Arc::clone(&repo) as _,
|
||||
review: Arc::clone(&repo) as _,
|
||||
diary: Arc::clone(&repo) as _,
|
||||
stats: Arc::clone(&repo) as _,
|
||||
@@ -791,7 +798,8 @@ pub fn make_test_state(auth_service: Arc<dyn AuthService>) -> crate::state::AppS
|
||||
import_profile: Arc::clone(&repo) as _,
|
||||
movie_profile: Arc::clone(&repo) as _,
|
||||
watchlist: Arc::clone(&repo) as _,
|
||||
watch_event: Arc::clone(&repo) as _,
|
||||
watch_event_command: Arc::clone(&repo) as _,
|
||||
watch_event_query: Arc::clone(&repo) as _,
|
||||
webhook_token: Arc::clone(&repo) as _,
|
||||
profile_fields: Arc::clone(&repo) as _,
|
||||
person_command: Arc::clone(&repo) as _,
|
||||
|
||||
@@ -445,7 +445,8 @@ async fn test_app() -> Router {
|
||||
let state = AppState {
|
||||
app_ctx: AppContext {
|
||||
repos: Repositories {
|
||||
movie: Arc::new(SqliteMovieRepository::new(pool.clone())) as _,
|
||||
movie_command: Arc::new(SqliteMovieRepository::new(pool.clone())) as _,
|
||||
movie_query: Arc::new(SqliteMovieRepository::new(pool.clone())) as _,
|
||||
review: Arc::new(SqliteReviewRepository::new(pool.clone())) as _,
|
||||
diary: Arc::new(SqliteDiaryRepository::new(pool.clone())) as _,
|
||||
stats: Arc::new(SqliteStatsRepository::new(pool.clone())) as _,
|
||||
@@ -454,7 +455,8 @@ async fn test_app() -> Router {
|
||||
import_profile: Arc::new(PanicImportProfile),
|
||||
movie_profile: Arc::new(PanicMovieProfile),
|
||||
watchlist: Arc::new(PanicWatchlist),
|
||||
watch_event: Arc::new(domain::testing::PanicWatchEventRepository),
|
||||
watch_event_command: Arc::new(domain::testing::PanicWatchEventCommand),
|
||||
watch_event_query: Arc::new(domain::testing::PanicWatchEventQuery),
|
||||
webhook_token: Arc::new(domain::testing::PanicWebhookTokenRepository),
|
||||
profile_fields: Arc::new(PanicProfileFields),
|
||||
person_command: Arc::new(PanicPersonCommand),
|
||||
|
||||
Reference in New Issue
Block a user