36
Cargo.lock
generated
36
Cargo.lock
generated
@@ -9,7 +9,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "01c0457472c38ea5bd1c3b5ada5e368271cb550be7a4ca4a0b4634e9913f6cc2"
|
||||
dependencies = [
|
||||
"ab_glyph_rasterizer",
|
||||
"owned_ttf_parser 0.25.1",
|
||||
"owned_ttf_parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1684,6 +1684,7 @@ name = "domain"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"email_address",
|
||||
"futures",
|
||||
@@ -2787,7 +2788,9 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"domain",
|
||||
"futures",
|
||||
"infer",
|
||||
"object_store",
|
||||
"tokio",
|
||||
@@ -3777,22 +3780,13 @@ dependencies = [
|
||||
"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]]
|
||||
name = "owned_ttf_parser"
|
||||
version = "0.25.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "36820e9051aca1014ddc75770aab4d68bc1e9e632f0f5627c4086bc216fb583b"
|
||||
dependencies = [
|
||||
"ttf-parser 0.25.1",
|
||||
"ttf-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -4240,10 +4234,12 @@ dependencies = [
|
||||
"auth",
|
||||
"axum",
|
||||
"axum-governor",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"domain",
|
||||
"dotenvy",
|
||||
"export",
|
||||
"futures",
|
||||
"http-body-util",
|
||||
"image-storage",
|
||||
"importer",
|
||||
@@ -5101,16 +5097,6 @@ dependencies = [
|
||||
"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]]
|
||||
name = "rustversion"
|
||||
version = "1.0.22"
|
||||
@@ -6484,12 +6470,6 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.15.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b3e06c9b9d80ed6b745c7159c40b311ad2916abb34a49e9be2653b90db0d8dd"
|
||||
|
||||
[[package]]
|
||||
name = "ttf-parser"
|
||||
version = "0.25.1"
|
||||
@@ -7582,12 +7562,12 @@ dependencies = [
|
||||
name = "wrapup-renderer"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ab_glyph",
|
||||
"async-trait",
|
||||
"domain",
|
||||
"image 0.25.10",
|
||||
"imageproc",
|
||||
"plotters",
|
||||
"rusttype",
|
||||
"tempfile",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
||||
@@ -380,9 +380,7 @@ impl TryFrom<EventPayload> for DomainEvent {
|
||||
end_date,
|
||||
} => {
|
||||
let wid = parse_uuid(&wrapup_id, "wrapup_id")?;
|
||||
let uid = user_id
|
||||
.map(|s| parse_uuid(&s, "user_id"))
|
||||
.transpose()?;
|
||||
let uid = user_id.map(|s| parse_uuid(&s, "user_id")).transpose()?;
|
||||
let sd = chrono::NaiveDate::parse_from_str(&start_date, "%Y-%m-%d")
|
||||
.map_err(|e| DomainError::ValidationError(e.to_string()))?;
|
||||
let ed = chrono::NaiveDate::parse_from_str(&end_date, "%Y-%m-%d")
|
||||
|
||||
@@ -88,15 +88,13 @@ impl WrapUpRepository for PostgresWrapUpRepository {
|
||||
let id_str = id.value().to_string();
|
||||
let status_str = status_to_str(status);
|
||||
|
||||
sqlx::query(
|
||||
"UPDATE wrap_up_records SET status = $1, error_message = $2 WHERE id = $3",
|
||||
)
|
||||
.bind(status_str)
|
||||
.bind(error)
|
||||
.bind(&id_str)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(map_err)?;
|
||||
sqlx::query("UPDATE wrap_up_records SET status = $1, error_message = $2 WHERE id = $3")
|
||||
.bind(status_str)
|
||||
.bind(error)
|
||||
.bind(&id_str)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map_err(map_err)?;
|
||||
|
||||
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 completed_at_str: Option<String> = row.try_get("completed_at").map_err(map_err)?;
|
||||
|
||||
let user_id = user_id_str
|
||||
.as_deref()
|
||||
.map(parse_uuid)
|
||||
.transpose()?;
|
||||
let user_id = user_id_str.as_deref().map(parse_uuid).transpose()?;
|
||||
|
||||
Ok(WrapUpRecord {
|
||||
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,
|
||||
error_message,
|
||||
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"
|
||||
);
|
||||
|
||||
let mut q = sqlx::query(&sql)
|
||||
.bind(range.start)
|
||||
.bind(range.end);
|
||||
let mut q = sqlx::query(&sql).bind(range.start).bind(range.end);
|
||||
if let Some(ref uid) = scope_bind {
|
||||
q = q.bind(uid);
|
||||
}
|
||||
@@ -307,18 +303,9 @@ impl WrapUpStatsQuery for PostgresWrapUpStatsQuery {
|
||||
let original_language: Option<String> =
|
||||
row.try_get("original_language").map_err(map_err)?;
|
||||
|
||||
let genres = genres_map
|
||||
.get(&movie_id_str)
|
||||
.cloned()
|
||||
.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 genres = genres_map.get(&movie_id_str).cloned().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
|
||||
.iter()
|
||||
|
||||
@@ -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 completed_at_str: Option<String> = row.try_get("completed_at").map_err(map_err)?;
|
||||
|
||||
let user_id = user_id_str
|
||||
.as_deref()
|
||||
.map(parse_uuid)
|
||||
.transpose()?;
|
||||
let user_id = user_id_str.as_deref().map(parse_uuid).transpose()?;
|
||||
|
||||
Ok(WrapUpRecord {
|
||||
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,
|
||||
error_message,
|
||||
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"
|
||||
);
|
||||
|
||||
let mut q = sqlx::query(&sql)
|
||||
.bind(&start_str)
|
||||
.bind(&end_str);
|
||||
let mut q = sqlx::query(&sql).bind(&start_str).bind(&end_str);
|
||||
if let Some(ref uid) = scope_bind {
|
||||
q = q.bind(uid);
|
||||
}
|
||||
@@ -316,18 +314,9 @@ impl WrapUpStatsQuery for SqliteWrapUpStatsQuery {
|
||||
let original_language: Option<String> =
|
||||
row.try_get("original_language").map_err(map_err)?;
|
||||
|
||||
let genres = genres_map
|
||||
.get(&movie_id_str)
|
||||
.cloned()
|
||||
.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 genres = genres_map.get(&movie_id_str).cloned().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
|
||||
.iter()
|
||||
|
||||
@@ -6,8 +6,8 @@ use domain::ports::{
|
||||
MovieRepository, PasswordHasher, PersonCommand, PersonQuery, PosterFetcherClient,
|
||||
RemoteWatchlistRepository, ReviewRepository, SearchCommand, SearchPort, SocialQueryPort,
|
||||
StatsRepository, UserProfileFieldsRepository, UserRepository, WatchEventRepository,
|
||||
WatchlistRepository, WrapUpRepository, WrapUpStatsQuery, WrapUpVideoRenderer,
|
||||
WebhookTokenRepository,
|
||||
WatchlistRepository, WebhookTokenRepository, WrapUpRepository, WrapUpStatsQuery,
|
||||
WrapUpVideoRenderer,
|
||||
};
|
||||
|
||||
use crate::config::AppConfig;
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use domain::testing::{InMemoryWrapUpRepository, InMemoryWrapUpStatsQuery, NoopRemoteWatchlistRepository, NoopSocialQueryPort};
|
||||
use domain::testing::{
|
||||
InMemoryWrapUpRepository, InMemoryWrapUpStatsQuery, NoopRemoteWatchlistRepository,
|
||||
NoopSocialQueryPort,
|
||||
};
|
||||
use domain::{
|
||||
ports::{
|
||||
AuthService, DiaryExporter, DiaryRepository, DocumentParser, EventPublisher, ImageStorage,
|
||||
|
||||
@@ -205,26 +205,14 @@ fn compute_rating_extremes(rows: &[WrapUpMovieRow]) -> (Option<MovieRef>, Option
|
||||
}
|
||||
|
||||
fn compute_chronological_extremes(rows: &[WrapUpMovieRow]) -> (Option<MovieRef>, Option<MovieRef>) {
|
||||
let first = rows
|
||||
.iter()
|
||||
.min_by_key(|r| r.watched_at)
|
||||
.map(movie_ref);
|
||||
let last = rows
|
||||
.iter()
|
||||
.max_by_key(|r| r.watched_at)
|
||||
.map(movie_ref);
|
||||
let first = rows.iter().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)
|
||||
}
|
||||
|
||||
fn compute_year_extremes(rows: &[WrapUpMovieRow]) -> (Option<MovieRef>, Option<MovieRef>) {
|
||||
let oldest = rows
|
||||
.iter()
|
||||
.min_by_key(|r| r.release_year)
|
||||
.map(movie_ref);
|
||||
let newest = rows
|
||||
.iter()
|
||||
.max_by_key(|r| r.release_year)
|
||||
.map(movie_ref);
|
||||
let oldest = rows.iter().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)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,11 +20,11 @@ pub mod remote_watchlist;
|
||||
pub use remote_watchlist::RemoteWatchlistEntry;
|
||||
pub mod watch_event;
|
||||
pub mod wrapup;
|
||||
pub use wrapup::*;
|
||||
pub use watch_event::{
|
||||
ParsedPlaybackEvent, PersistedWatchEvent, WatchEvent, WatchEventSource, WatchEventStatus,
|
||||
WebhookToken,
|
||||
};
|
||||
pub use wrapup::*;
|
||||
|
||||
pub use import::{
|
||||
AnnotatedRow, DomainField, FieldMapping, FileFormat, ImportError, ImportRow, ParsedFile,
|
||||
|
||||
@@ -1131,11 +1131,13 @@ impl WrapUpRepository for InMemoryWrapUpRepository {
|
||||
.collect())
|
||||
}
|
||||
|
||||
async fn list_global(
|
||||
&self,
|
||||
) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
async fn list_global(&self) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
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(
|
||||
@@ -1184,9 +1186,7 @@ impl WrapUpRepository for PanicWrapUpRepository {
|
||||
) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
panic!("PanicWrapUpRepository called")
|
||||
}
|
||||
async fn list_global(
|
||||
&self,
|
||||
) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
async fn list_global(&self) -> Result<Vec<crate::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
panic!("PanicWrapUpRepository called")
|
||||
}
|
||||
async fn find_existing(
|
||||
|
||||
@@ -135,12 +135,9 @@ pub async fn get_report(
|
||||
) -> impl IntoResponse {
|
||||
match get_wrapup::execute(&state.app_ctx, WrapUpId::from_uuid(id)).await {
|
||||
Ok(Some(record)) if record.status == WrapUpStatus::Ready => match record.report_json {
|
||||
Some(json) => (
|
||||
StatusCode::OK,
|
||||
[("content-type", "application/json")],
|
||||
json,
|
||||
)
|
||||
.into_response(),
|
||||
Some(json) => {
|
||||
(StatusCode::OK, [("content-type", "application/json")], json).into_response()
|
||||
}
|
||||
None => StatusCode::NOT_FOUND.into_response(),
|
||||
},
|
||||
Ok(Some(_)) => StatusCode::ACCEPTED.into_response(),
|
||||
@@ -158,10 +155,7 @@ pub async fn get_report(
|
||||
),
|
||||
security(("bearer_auth" = []))
|
||||
)]
|
||||
pub async fn get_video(
|
||||
State(state): State<AppState>,
|
||||
Path(id): Path<Uuid>,
|
||||
) -> impl IntoResponse {
|
||||
pub async fn get_video(State(state): State<AppState>, Path(id): Path<Uuid>) -> impl IntoResponse {
|
||||
let record = match state
|
||||
.app_ctx
|
||||
.repos
|
||||
@@ -219,11 +213,21 @@ fn render_wrapup(
|
||||
year: i32,
|
||||
ctx: &application::ports::HtmlPageContext,
|
||||
) -> axum::response::Response {
|
||||
let rating_max = report.rating_distribution.iter().copied().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 rating_max = report
|
||||
.rating_distribution
|
||||
.iter()
|
||||
.copied()
|
||||
.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
|
||||
.top_genres
|
||||
.iter()
|
||||
|
||||
@@ -599,13 +599,23 @@ impl domain::ports::WrapUpRepository for Panic {
|
||||
) -> Result<(), DomainError> {
|
||||
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!()
|
||||
}
|
||||
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!()
|
||||
}
|
||||
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!()
|
||||
}
|
||||
async fn list_global(&self) -> Result<Vec<domain::models::wrapup::WrapUpRecord>, DomainError> {
|
||||
|
||||
@@ -196,10 +196,16 @@ async fn main() -> anyhow::Result<()> {
|
||||
Arc::clone(&ctx.repos.movie),
|
||||
Arc::clone(&ctx.repos.search_command),
|
||||
)) as Arc<dyn EventHandler>;
|
||||
let wrapup_handler = Arc::new(application::wrapup::event_handler::WrapUpEventHandler::new(
|
||||
ctx.clone(),
|
||||
)) as Arc<dyn EventHandler>;
|
||||
let mut h = vec![poster, cleanup, search_cleanup, discovery_indexer, wrapup_handler];
|
||||
let wrapup_handler = Arc::new(
|
||||
application::wrapup::event_handler::WrapUpEventHandler::new(ctx.clone()),
|
||||
) as Arc<dyn EventHandler>;
|
||||
let mut h = vec![
|
||||
poster,
|
||||
cleanup,
|
||||
search_cleanup,
|
||||
discovery_indexer,
|
||||
wrapup_handler,
|
||||
];
|
||||
if let Some(e) = enrichment_handler {
|
||||
h.push(e);
|
||||
}
|
||||
@@ -240,9 +246,9 @@ async fn main() -> anyhow::Result<()> {
|
||||
Arc::clone(&ctx.repos.search_command),
|
||||
)) as Arc<dyn EventHandler>;
|
||||
tracing::info!("federation event handler registered");
|
||||
let wrapup_handler = Arc::new(application::wrapup::event_handler::WrapUpEventHandler::new(
|
||||
ctx.clone(),
|
||||
)) as Arc<dyn EventHandler>;
|
||||
let wrapup_handler = Arc::new(
|
||||
application::wrapup::event_handler::WrapUpEventHandler::new(ctx.clone()),
|
||||
) as Arc<dyn EventHandler>;
|
||||
let mut h = vec![
|
||||
poster,
|
||||
cleanup,
|
||||
|
||||
Reference in New Issue
Block a user