feat: search reindex, worker improvements, person IDs, user display names

- add admin POST /api/v1/admin/reindex-search endpoint + event-driven handler
- backfill persons from movie_cast/movie_crew into persons table
- paginate person list_page/backfill_from_credits_batch to cap memory
- concurrent worker event dispatch with semaphore (max 8)
- graceful worker shutdown (drain in-flight tasks on SIGINT)
- always ack events, log handler errors as warnings (no infinite retry)
- NATS ack_wait 600s, AtomicBool guard against concurrent reindex
- add username/display_name to UserSummaryDto and users list
- add person_id to CastMemberDto/CrewMemberDto via get_movie_profile use case
- add movie_id to wrapup MovieRef, person_id to wrapup PersonStat
- thread tmdb_person_id through wrapup cast pipeline
- add is_federated to FeedEntryDto
- cap orphaned persons query with LIMIT 500
- add SPA link to classic site footer
This commit is contained in:
2026-06-04 14:43:28 +02:00
parent af8e58aeb8
commit bd7dc648c4
36 changed files with 693 additions and 118 deletions

View File

@@ -345,9 +345,9 @@ impl WrapUpStatsQuery for SqliteWrapUpStatsQuery {
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, i64)> = cast
.iter()
.map(|c| (c.name.clone(), c.billing_order))
.map(|c| (c.name.clone(), c.billing_order, c.tmdb_person_id))
.collect();
let cast_profile_paths: Vec<Option<String>> =
cast.iter().map(|c| c.profile_path.clone()).collect();
@@ -379,6 +379,7 @@ impl WrapUpStatsQuery for SqliteWrapUpStatsQuery {
struct CastEntry {
name: String,
billing_order: u32,
tmdb_person_id: i64,
profile_path: Option<String>,
}
@@ -453,7 +454,7 @@ async fn fetch_cast_sqlite(
return Ok(HashMap::new());
}
let sql = format!(
"SELECT movie_id, name, billing_order, profile_path \
"SELECT movie_id, name, billing_order, tmdb_person_id, profile_path \
FROM movie_cast \
WHERE movie_id IN ({}) AND billing_order <= 3 \
ORDER BY billing_order ASC",
@@ -470,10 +471,12 @@ async fn fetch_cast_sqlite(
let mid: String = row.try_get("movie_id").map_err(map_err)?;
let name: String = row.try_get("name").map_err(map_err)?;
let billing_order: i32 = row.try_get("billing_order").map_err(map_err)?;
let tmdb_person_id: i64 = row.try_get("tmdb_person_id").map_err(map_err)?;
let profile_path: Option<String> = row.try_get("profile_path").map_err(map_err)?;
map.entry(mid).or_default().push(CastEntry {
name,
billing_order: billing_order as u32,
tmdb_person_id,
profile_path,
});
}