fmt
Some checks failed
CI / Check / Test (push) Failing after 6m39s

This commit is contained in:
2026-06-02 23:50:20 +02:00
parent 9e13f04e9c
commit bf0350c87a
12 changed files with 97 additions and 132 deletions

36
Cargo.lock generated
View File

@@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2" checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2"
dependencies = [ dependencies = [
"ab_glyph_rasterizer", "ab_glyph_rasterizer",
"owned_ttf_parser 0.25.1", "owned_ttf_parser",
] ]
[[package]] [[package]]
@@ -1684,6 +1684,7 @@ name = "domain"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bytes",
"chrono", "chrono",
"email_address", "email_address",
"futures", "futures",
@@ -2787,7 +2788,9 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"async-trait", "async-trait",
"bytes",
"domain", "domain",
"futures",
"infer", "infer",
"object_store", "object_store",
"tokio", "tokio",
@@ -3777,22 +3780,13 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "owned_ttf_parser"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05e6affeb1632d6ff6a23d2cd40ffed138e82f1532571a26f527c8a284bb2fbb"
dependencies = [
"ttf-parser 0.15.2",
]
[[package]] [[package]]
name = "owned_ttf_parser" name = "owned_ttf_parser"
version = "0.25.1" version = "0.25.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b" checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
dependencies = [ dependencies = [
"ttf-parser 0.25.1", "ttf-parser",
] ]
[[package]] [[package]]
@@ -4240,10 +4234,12 @@ dependencies = [
"auth", "auth",
"axum", "axum",
"axum-governor", "axum-governor",
"bytes",
"chrono", "chrono",
"domain", "domain",
"dotenvy", "dotenvy",
"export", "export",
"futures",
"http-body-util", "http-body-util",
"image-storage", "image-storage",
"importer", "importer",
@@ -5101,16 +5097,6 @@ dependencies = [
"untrusted", "untrusted",
] ]
[[package]]
name = "rusttype"
version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3ff8374aa04134254b7995b63ad3dc41c7f7236f69528b28553da7d72efaa967"
dependencies = [
"ab_glyph_rasterizer",
"owned_ttf_parser 0.15.2",
]
[[package]] [[package]]
name = "rustversion" name = "rustversion"
version = "1.0.22" version = "1.0.22"
@@ -6484,12 +6470,6 @@ dependencies = [
"tokio", "tokio",
] ]
[[package]]
name = "ttf-parser"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
[[package]] [[package]]
name = "ttf-parser" name = "ttf-parser"
version = "0.25.1" version = "0.25.1"
@@ -7582,12 +7562,12 @@ dependencies = [
name = "wrapup-renderer" name = "wrapup-renderer"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"ab_glyph",
"async-trait", "async-trait",
"domain", "domain",
"image 0.25.10", "image 0.25.10",
"imageproc", "imageproc",
"plotters", "plotters",
"rusttype",
"tempfile", "tempfile",
"tokio", "tokio",
"tracing", "tracing",

View File

@@ -380,9 +380,7 @@ impl TryFrom<EventPayload> for DomainEvent {
end_date, end_date,
} => { } => {
let wid = parse_uuid(&wrapup_id, "wrapup_id")?; let wid = parse_uuid(&wrapup_id, "wrapup_id")?;
let uid = user_id let uid = user_id.map(|s| parse_uuid(&s, "user_id")).transpose()?;
.map(|s| parse_uuid(&s, "user_id"))
.transpose()?;
let sd = chrono::NaiveDate::parse_from_str(&start_date, "%Y-%m-%d") let sd = chrono::NaiveDate::parse_from_str(&start_date, "%Y-%m-%d")
.map_err(|e| DomainError::ValidationError(e.to_string()))?; .map_err(|e| DomainError::ValidationError(e.to_string()))?;
let ed = chrono::NaiveDate::parse_from_str(&end_date, "%Y-%m-%d") let ed = chrono::NaiveDate::parse_from_str(&end_date, "%Y-%m-%d")

View File

@@ -88,15 +88,13 @@ impl WrapUpRepository for PostgresWrapUpRepository {
let id_str = id.value().to_string(); let id_str = id.value().to_string();
let status_str = status_to_str(status); let status_str = status_to_str(status);
sqlx::query( sqlx::query("UPDATE wrap_up_records SET status = $1, error_message = $2 WHERE id = $3")
"UPDATE wrap_up_records SET status = $1, error_message = $2 WHERE id = $3", .bind(status_str)
) .bind(error)
.bind(status_str) .bind(&id_str)
.bind(error) .execute(&self.pool)
.bind(&id_str) .await
.execute(&self.pool) .map_err(map_err)?;
.await
.map_err(map_err)?;
Ok(()) Ok(())
} }
@@ -205,10 +203,7 @@ fn row_to_record(row: &sqlx::postgres::PgRow) -> Result<WrapUpRecord, DomainErro
let created_at_str: String = row.try_get("created_at").map_err(map_err)?; let created_at_str: String = row.try_get("created_at").map_err(map_err)?;
let completed_at_str: Option<String> = row.try_get("completed_at").map_err(map_err)?; let completed_at_str: Option<String> = row.try_get("completed_at").map_err(map_err)?;
let user_id = user_id_str let user_id = user_id_str.as_deref().map(parse_uuid).transpose()?;
.as_deref()
.map(parse_uuid)
.transpose()?;
Ok(WrapUpRecord { Ok(WrapUpRecord {
id: WrapUpId::from_uuid(parse_uuid(&id_str)?), id: WrapUpId::from_uuid(parse_uuid(&id_str)?),
@@ -219,7 +214,10 @@ fn row_to_record(row: &sqlx::postgres::PgRow) -> Result<WrapUpRecord, DomainErro
report_json, report_json,
error_message, error_message,
created_at: parse_datetime(&created_at_str)?, created_at: parse_datetime(&created_at_str)?,
completed_at: completed_at_str.as_deref().map(parse_datetime).transpose()?, completed_at: completed_at_str
.as_deref()
.map(parse_datetime)
.transpose()?,
}) })
} }
@@ -261,9 +259,7 @@ impl WrapUpStatsQuery for PostgresWrapUpStatsQuery {
ORDER BY r.watched_at ASC" ORDER BY r.watched_at ASC"
); );
let mut q = sqlx::query(&sql) let mut q = sqlx::query(&sql).bind(range.start).bind(range.end);
.bind(range.start)
.bind(range.end);
if let Some(ref uid) = scope_bind { if let Some(ref uid) = scope_bind {
q = q.bind(uid); q = q.bind(uid);
} }
@@ -307,18 +303,9 @@ impl WrapUpStatsQuery for PostgresWrapUpStatsQuery {
let original_language: Option<String> = let original_language: Option<String> =
row.try_get("original_language").map_err(map_err)?; row.try_get("original_language").map_err(map_err)?;
let genres = genres_map let genres = genres_map.get(&movie_id_str).cloned().unwrap_or_default();
.get(&movie_id_str) let keywords = keywords_map.get(&movie_id_str).cloned().unwrap_or_default();
.cloned() let cast = cast_map.get(&movie_id_str).cloned().unwrap_or_default();
.unwrap_or_default();
let keywords = keywords_map
.get(&movie_id_str)
.cloned()
.unwrap_or_default();
let cast = cast_map
.get(&movie_id_str)
.cloned()
.unwrap_or_default();
let cast_names: Vec<(String, u32)> = cast let cast_names: Vec<(String, u32)> = cast
.iter() .iter()

View File

@@ -213,10 +213,7 @@ fn row_to_record(row: &sqlx::sqlite::SqliteRow) -> Result<WrapUpRecord, DomainEr
let created_at_str: String = row.try_get("created_at").map_err(map_err)?; let created_at_str: String = row.try_get("created_at").map_err(map_err)?;
let completed_at_str: Option<String> = row.try_get("completed_at").map_err(map_err)?; let completed_at_str: Option<String> = row.try_get("completed_at").map_err(map_err)?;
let user_id = user_id_str let user_id = user_id_str.as_deref().map(parse_uuid).transpose()?;
.as_deref()
.map(parse_uuid)
.transpose()?;
Ok(WrapUpRecord { Ok(WrapUpRecord {
id: WrapUpId::from_uuid(parse_uuid(&id_str)?), id: WrapUpId::from_uuid(parse_uuid(&id_str)?),
@@ -227,7 +224,10 @@ fn row_to_record(row: &sqlx::sqlite::SqliteRow) -> Result<WrapUpRecord, DomainEr
report_json, report_json,
error_message, error_message,
created_at: parse_datetime(&created_at_str)?, created_at: parse_datetime(&created_at_str)?,
completed_at: completed_at_str.as_deref().map(parse_datetime).transpose()?, completed_at: completed_at_str
.as_deref()
.map(parse_datetime)
.transpose()?,
}) })
} }
@@ -270,9 +270,7 @@ impl WrapUpStatsQuery for SqliteWrapUpStatsQuery {
ORDER BY r.watched_at ASC" ORDER BY r.watched_at ASC"
); );
let mut q = sqlx::query(&sql) let mut q = sqlx::query(&sql).bind(&start_str).bind(&end_str);
.bind(&start_str)
.bind(&end_str);
if let Some(ref uid) = scope_bind { if let Some(ref uid) = scope_bind {
q = q.bind(uid); q = q.bind(uid);
} }
@@ -316,18 +314,9 @@ impl WrapUpStatsQuery for SqliteWrapUpStatsQuery {
let original_language: Option<String> = let original_language: Option<String> =
row.try_get("original_language").map_err(map_err)?; row.try_get("original_language").map_err(map_err)?;
let genres = genres_map let genres = genres_map.get(&movie_id_str).cloned().unwrap_or_default();
.get(&movie_id_str) let keywords = keywords_map.get(&movie_id_str).cloned().unwrap_or_default();
.cloned() let cast = cast_map.get(&movie_id_str).cloned().unwrap_or_default();
.unwrap_or_default();
let keywords = keywords_map
.get(&movie_id_str)
.cloned()
.unwrap_or_default();
let cast = cast_map
.get(&movie_id_str)
.cloned()
.unwrap_or_default();
let cast_names: Vec<(String, u32)> = cast let cast_names: Vec<(String, u32)> = cast
.iter() .iter()

View File

@@ -6,8 +6,8 @@ use domain::ports::{
MovieRepository, PasswordHasher, PersonCommand, PersonQuery, PosterFetcherClient, MovieRepository, PasswordHasher, PersonCommand, PersonQuery, PosterFetcherClient,
RemoteWatchlistRepository, ReviewRepository, SearchCommand, SearchPort, SocialQueryPort, RemoteWatchlistRepository, ReviewRepository, SearchCommand, SearchPort, SocialQueryPort,
StatsRepository, UserProfileFieldsRepository, UserRepository, WatchEventRepository, StatsRepository, UserProfileFieldsRepository, UserRepository, WatchEventRepository,
WatchlistRepository, WrapUpRepository, WrapUpStatsQuery, WrapUpVideoRenderer, WatchlistRepository, WebhookTokenRepository, WrapUpRepository, WrapUpStatsQuery,
WebhookTokenRepository, WrapUpVideoRenderer,
}; };
use crate::config::AppConfig; use crate::config::AppConfig;

View File

@@ -1,6 +1,9 @@
use std::sync::Arc; use std::sync::Arc;
use domain::testing::{InMemoryWrapUpRepository, InMemoryWrapUpStatsQuery, NoopRemoteWatchlistRepository, NoopSocialQueryPort}; use domain::testing::{
InMemoryWrapUpRepository, InMemoryWrapUpStatsQuery, NoopRemoteWatchlistRepository,
NoopSocialQueryPort,
};
use domain::{ use domain::{
ports::{ ports::{
AuthService, DiaryExporter, DiaryRepository, DocumentParser, EventPublisher, ImageStorage, AuthService, DiaryExporter, DiaryRepository, DocumentParser, EventPublisher, ImageStorage,

View File

@@ -205,26 +205,14 @@ fn compute_rating_extremes(rows: &[WrapUpMovieRow]) -> (Option<MovieRef>, Option
} }
fn compute_chronological_extremes(rows: &[WrapUpMovieRow]) -> (Option<MovieRef>, Option<MovieRef>) { fn compute_chronological_extremes(rows: &[WrapUpMovieRow]) -> (Option<MovieRef>, Option<MovieRef>) {
let first = rows let first = rows.iter().min_by_key(|r| r.watched_at).map(movie_ref);
.iter() let last = rows.iter().max_by_key(|r| r.watched_at).map(movie_ref);
.min_by_key(|r| r.watched_at)
.map(movie_ref);
let last = rows
.iter()
.max_by_key(|r| r.watched_at)
.map(movie_ref);
(first, last) (first, last)
} }
fn compute_year_extremes(rows: &[WrapUpMovieRow]) -> (Option<MovieRef>, Option<MovieRef>) { fn compute_year_extremes(rows: &[WrapUpMovieRow]) -> (Option<MovieRef>, Option<MovieRef>) {
let oldest = rows let oldest = rows.iter().min_by_key(|r| r.release_year).map(movie_ref);
.iter() let newest = rows.iter().max_by_key(|r| r.release_year).map(movie_ref);
.min_by_key(|r| r.release_year)
.map(movie_ref);
let newest = rows
.iter()
.max_by_key(|r| r.release_year)
.map(movie_ref);
(oldest, newest) (oldest, newest)
} }

View File

@@ -20,11 +20,11 @@ pub mod remote_watchlist;
pub use remote_watchlist::RemoteWatchlistEntry; pub use remote_watchlist::RemoteWatchlistEntry;
pub mod watch_event; pub mod watch_event;
pub mod wrapup; pub mod wrapup;
pub use wrapup::*;
pub use watch_event::{ pub use watch_event::{
ParsedPlaybackEvent, PersistedWatchEvent, WatchEvent, WatchEventSource, WatchEventStatus, ParsedPlaybackEvent, PersistedWatchEvent, WatchEvent, WatchEventSource, WatchEventStatus,
WebhookToken, WebhookToken,
}; };
pub use wrapup::*;
pub use import::{ pub use import::{
AnnotatedRow, DomainField, FieldMapping, FileFormat, ImportError, ImportRow, ParsedFile, AnnotatedRow, DomainField, FieldMapping, FileFormat, ImportError, ImportRow, ParsedFile,

View File

@@ -1131,11 +1131,13 @@ impl WrapUpRepository for InMemoryWrapUpRepository {
.collect()) .collect())
} }
async fn list_global( async fn list_global(&self) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
&self,
) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
let store = self.store.lock().unwrap(); let store = self.store.lock().unwrap();
Ok(store.iter().filter(|r| r.user_id.is_none()).cloned().collect()) Ok(store
.iter()
.filter(|r| r.user_id.is_none())
.cloned()
.collect())
} }
async fn find_existing( async fn find_existing(
@@ -1184,9 +1186,7 @@ impl WrapUpRepository for PanicWrapUpRepository {
) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> { ) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
panic!("PanicWrapUpRepository called") panic!("PanicWrapUpRepository called")
} }
async fn list_global( async fn list_global(&self) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
&self,
) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
panic!("PanicWrapUpRepository called") panic!("PanicWrapUpRepository called")
} }
async fn find_existing( async fn find_existing(

View File

@@ -135,12 +135,9 @@ pub async fn get_report(
) -> impl IntoResponse { ) -> impl IntoResponse {
match get_wrapup::execute(&state.app_ctx, WrapUpId::from_uuid(id)).await { match get_wrapup::execute(&state.app_ctx, WrapUpId::from_uuid(id)).await {
Ok(Some(record)) if record.status == WrapUpStatus::Ready => match record.report_json { Ok(Some(record)) if record.status == WrapUpStatus::Ready => match record.report_json {
Some(json) => ( Some(json) => {
StatusCode::OK, (StatusCode::OK, [("content-type", "application/json")], json).into_response()
[("content-type", "application/json")], }
json,
)
.into_response(),
None => StatusCode::NOT_FOUND.into_response(), None => StatusCode::NOT_FOUND.into_response(),
}, },
Ok(Some(_)) => StatusCode::ACCEPTED.into_response(), Ok(Some(_)) => StatusCode::ACCEPTED.into_response(),
@@ -158,10 +155,7 @@ pub async fn get_report(
), ),
security(("bearer_auth" = [])) security(("bearer_auth" = []))
)] )]
pub async fn get_video( pub async fn get_video(State(state): State<AppState>, Path(id): Path<Uuid>) -> impl IntoResponse {
State(state): State<AppState>,
Path(id): Path<Uuid>,
) -> impl IntoResponse {
let record = match state let record = match state
.app_ctx .app_ctx
.repos .repos
@@ -219,11 +213,21 @@ fn render_wrapup(
year: i32, year: i32,
ctx: &application::ports::HtmlPageContext, ctx: &application::ports::HtmlPageContext,
) -> axum::response::Response { ) -> axum::response::Response {
let rating_max = report.rating_distribution.iter().copied().max().unwrap_or(1).max(1); let rating_max = report
let rating_pcts: [f64; 5] = std::array::from_fn(|i| { .rating_distribution
report.rating_distribution[i] as f64 / rating_max as f64 * 100.0 .iter()
}); .copied()
let genre_max = report.top_genres.first().map(|g| g.count).unwrap_or(1).max(1); .max()
.unwrap_or(1)
.max(1);
let rating_pcts: [f64; 5] =
std::array::from_fn(|i| report.rating_distribution[i] as f64 / rating_max as f64 * 100.0);
let genre_max = report
.top_genres
.first()
.map(|g| g.count)
.unwrap_or(1)
.max(1);
let genre_pcts: Vec<f64> = report let genre_pcts: Vec<f64> = report
.top_genres .top_genres
.iter() .iter()

View File

@@ -599,13 +599,23 @@ impl domain::ports::WrapUpRepository for Panic {
) -> Result<(), DomainError> { ) -> Result<(), DomainError> {
panic!() panic!()
} }
async fn set_complete(&self, _: &domain::value_objects::WrapUpId, _: &str) -> Result<(), DomainError> { async fn set_complete(
&self,
_: &domain::value_objects::WrapUpId,
_: &str,
) -> Result<(), DomainError> {
panic!() panic!()
} }
async fn get_by_id(&self, _: &domain::value_objects::WrapUpId) -> Result<Option<domain::models::wrapup::WrapUpRecord>, DomainError> { async fn get_by_id(
&self,
_: &domain::value_objects::WrapUpId,
) -> Result<Option<domain::models::wrapup::WrapUpRecord>, DomainError> {
panic!() panic!()
} }
async fn list_for_user(&self, _: uuid::Uuid) -> Result<Vec<domain::models::wrapup::WrapUpRecord>, DomainError> { async fn list_for_user(
&self,
_: uuid::Uuid,
) -> Result<Vec<domain::models::wrapup::WrapUpRecord>, DomainError> {
panic!() panic!()
} }
async fn list_global(&self) -> Result<Vec<domain::models::wrapup::WrapUpRecord>, DomainError> { async fn list_global(&self) -> Result<Vec<domain::models::wrapup::WrapUpRecord>, DomainError> {

View File

@@ -196,10 +196,16 @@ async fn main() -> anyhow::Result<()> {
Arc::clone(&ctx.repos.movie), Arc::clone(&ctx.repos.movie),
Arc::clone(&ctx.repos.search_command), Arc::clone(&ctx.repos.search_command),
)) as Arc<dyn EventHandler>; )) as Arc<dyn EventHandler>;
let wrapup_handler = Arc::new(application::wrapup::event_handler::WrapUpEventHandler::new( let wrapup_handler = Arc::new(
ctx.clone(), application::wrapup::event_handler::WrapUpEventHandler::new(ctx.clone()),
)) as Arc<dyn EventHandler>; ) as Arc<dyn EventHandler>;
let mut h = vec![poster, cleanup, search_cleanup, discovery_indexer, wrapup_handler]; let mut h = vec![
poster,
cleanup,
search_cleanup,
discovery_indexer,
wrapup_handler,
];
if let Some(e) = enrichment_handler { if let Some(e) = enrichment_handler {
h.push(e); h.push(e);
} }
@@ -240,9 +246,9 @@ async fn main() -> anyhow::Result<()> {
Arc::clone(&ctx.repos.search_command), Arc::clone(&ctx.repos.search_command),
)) as Arc<dyn EventHandler>; )) as Arc<dyn EventHandler>;
tracing::info!("federation event handler registered"); tracing::info!("federation event handler registered");
let wrapup_handler = Arc::new(application::wrapup::event_handler::WrapUpEventHandler::new( let wrapup_handler = Arc::new(
ctx.clone(), application::wrapup::event_handler::WrapUpEventHandler::new(ctx.clone()),
)) as Arc<dyn EventHandler>; ) as Arc<dyn EventHandler>;
let mut h = vec![ let mut h = vec![
poster, poster,
cleanup, cleanup,