fix(plex,audit): E30 review round 1 — the people job keeps going, HU genres everywhere, results as data

- people_enrich: a failed TMDB lookup or a dead headshot is skipped, not the end of the run (a dead
  file is marked and left for a week; three failures in a row end it); portraits wait for the Plex
  module switch; a no-Plex original keeps its own cache key, so the resize still replaces it; the job
  entry lives in the plex domain (run_people_enrich)
- person-image takes only a headshot-shaped CDN url (no query variants of one file)
- every synced row gets Hungarian genres, and a HU genre filter matches the word as picked too;
  migration 0094 fills the rows no overlay reached
- a job run's audit row carries its result as data; audit_clear / demo_reset store their counts
- one credit-column list for the prune and the enrichment order; _overlay_pass typed
- tests: batch skip + stop, HU bio stored, dead headshots, module switch, the person-image route,
  the job body, genre filter + facet write + twin fallback, tagline/summary fallbacks, audit data,
  the reader's alphabet at every remaining collated() call site
This commit is contained in:
2026-09-30 03:26:57 +02:00
parent 59e6cc027d
commit ab963c81d2
15 changed files with 571 additions and 118 deletions
+5 -1
View File
@@ -834,7 +834,11 @@ def reset_demo(admin: User = Depends(admin_user), db: Session = Depends(get_db))
demo = get_or_create_demo_user(db)
seeded = reset_demo_state(db, demo)
audit.record(
db, admin.id, AuditAction.DEMO_RESET, summary=f"{seeded} sample playlists re-seeded"
db,
admin.id,
AuditAction.DEMO_RESET,
summary=f"{seeded} sample playlists re-seeded",
after={"playlists_seeded": seeded}, # the count as data: named in the reader's language
)
db.commit()
return {"ok": True, "reset": True, "playlists_seeded": seeded}
+7 -1
View File
@@ -66,6 +66,12 @@ def list_audit(
def clear_audit(admin: User = Depends(admin_user), db: Session = Depends(get_db)) -> dict:
"""Clear the audit log. Leaves ONE row recording who cleared it + how many (tamper-evidence)."""
n = db.execute(delete(AuditLog)).rowcount or 0
audit.record(db, admin.id, AuditAction.AUDIT_CLEAR, summary=f"Cleared {n} audit entries")
audit.record(
db,
admin.id,
AuditAction.AUDIT_CLEAR,
summary=f"Cleared {n} audit entries",
after={"cleared": n}, # the count as data: the log names it in the reader's language
)
db.commit()
return {"ok": True, "cleared": n}
+5 -4
View File
@@ -213,11 +213,12 @@ def _resume_push_superseded(user_id: int, item_id: int, intent: datetime) -> boo
@router.get("/person-image")
def person_image(u: str, request: Request) -> Response:
"""Proxy a cast/crew photo from Plex's public metadata CDN (host-whitelisted; no token needed),
resized through Plex and cached (plex.people.cache_portrait; the people_enrich job fills the same
cache ahead of time). Signed-session auth; like /image, the DB session is released before the fetch."""
"""Proxy a cast/crew photo from Plex's public metadata CDN (only a headshot-shaped URL on its host;
no token needed), resized through Plex and cached (plex.people.cache_portrait; the people_enrich job
fills the same cache ahead of time). Signed-session auth; like /image, the DB session is released
before the fetch."""
_require_session(request)
if not u.startswith(plex_people.PERSON_IMG_HOST):
if not plex_people.is_portrait_url(u):
raise refused(400, "image_host")
hit = plex_people.portrait_cached(u)
if hit: