refactor: fix HIGH+MEDIUM architectural violations from code review

HIGH: fix watch_medium data-loss bug, standardize error handling on
ApiError, fix dep direction (rss/template-askama no longer dep on
application), extract ImageFetcher port (remove reqwest from app layer),
move event construction from save_review to use case, extract
infra-wiring crate (DbPool/EventBusBackend dedup), deduplicate
presentation helpers (encode_error, export streaming, multipart parsing)

MEDIUM: split LocalApContentQuery god-trait 10→3 methods, dedup movie
resolution orchestration, add RemoteActorDto/PersonDto mappers, move
AppConfig to infra-wiring, fix SocialQueryPort Uuid→UserId, replace
stringly-typed api-types with domain enums, move count_reviews_in_year
to StatsRepository, dedup event publisher cfg blocks, extract
should_enrich, move group_by_month to application, dedup
count_local_posts, add FederationFlags Default, TUI input helper +
ShowError rename + typed auth errors, api-types cleanup
(UserSettingsDto/UserProfileBase/PreviewRowData)

102 files changed, -681 lines net
This commit is contained in:
2026-07-10 02:08:39 +02:00
parent 26152660bb
commit 12da356a40
110 changed files with 1399 additions and 1867 deletions

View File

@@ -2,5 +2,8 @@ pub mod diary;
pub mod import;
pub mod integrations;
pub mod movies;
pub mod search;
#[cfg(feature = "federation")]
pub mod social;
pub mod users;
pub mod watchlist;

View File

@@ -37,7 +37,7 @@ pub fn review_to_dto(review: &Review) -> ReviewDto {
rating: review.rating().value(),
comment: review.comment().map(|c| c.value().to_string()),
watched_at: domain::value_objects::format_watched_at(review.watched_at()),
watch_medium: review.watch_medium().map(|wm| wm.to_string()),
watch_medium: review.watch_medium().copied(),
}
}

View File

@@ -0,0 +1,22 @@
use api_types::search::PersonDto;
use domain::models::person::Person;
pub fn person_to_dto(p: &Person) -> PersonDto {
PersonDto {
id: p.id().value(),
external_id: p.external_id().value().to_string(),
name: p.name().to_string(),
known_for_department: p.known_for_department().map(str::to_string),
profile_path: p.profile_path().map(str::to_string),
biography: p.biography().map(str::to_string),
birthday: p.birthday().map(|d| d.to_string()),
deathday: p.deathday().map(|d| d.to_string()),
place_of_birth: p.place_of_birth().map(str::to_string),
also_known_as: p.also_known_as().to_vec(),
homepage: p.homepage().map(str::to_string),
imdb_url: p
.imdb_id()
.map(|id| format!("https://www.imdb.com/name/{id}")),
enriched: p.enriched_at().is_some(),
}
}

View File

@@ -0,0 +1,9 @@
use api_types::RemoteActorDto;
pub fn remote_actor_to_dto(a: activitypub::RemoteActor) -> RemoteActorDto {
RemoteActorDto {
handle: a.handle,
display_name: a.display_name,
url: a.url,
}
}

View File

@@ -1,7 +1,6 @@
use application::users::get_profile::PendingFollowerView;
use chrono::Datelike;
use domain::models::RemoteActorInfo;
use domain::models::{DiaryEntry, MonthActivity, UserSummary};
use domain::models::UserSummary;
use template_askama::{RemoteActorData, RemoteActorDisplay, UserSummaryView};
pub fn user_summary_view(u: &UserSummary) -> UserSummaryView {
@@ -44,49 +43,3 @@ pub fn pending_follower_data(p: &PendingFollowerView) -> RemoteActorData {
avatar_url: p.avatar_url.clone(),
}
}
pub fn group_by_month(entries: Vec<DiaryEntry>) -> Vec<MonthActivity> {
use std::collections::BTreeMap;
let mut map: BTreeMap<(i32, u32), Vec<DiaryEntry>> = BTreeMap::new();
for entry in entries {
let watched_at = entry.review().watched_at();
let year = watched_at.year();
let month = watched_at.month();
map.entry((year, month)).or_default().push(entry);
}
map.into_iter()
.rev()
.map(|((year, month), entries)| {
let year_month = format!("{:04}-{:02}", year, month);
MonthActivity {
month_label: format_year_month_long(&year_month),
count: entries.len() as i64,
entries,
year_month,
}
})
.collect()
}
fn format_year_month_long(ym: &str) -> String {
let parts: Vec<&str> = ym.splitn(2, '-').collect();
if parts.len() != 2 {
return ym.to_string();
}
let month = match parts[1] {
"01" => "January",
"02" => "February",
"03" => "March",
"04" => "April",
"05" => "May",
"06" => "June",
"07" => "July",
"08" => "August",
"09" => "September",
"10" => "October",
"11" => "November",
"12" => "December",
_ => parts[1],
};
format!("{} {}", month, parts[0])
}