feat(plex): collections — sync, browse, filter, and info-page strips (Phase 1, read)

Backend (migration 0047_plex_collections): a plex_collections table mirrors every Plex
collection (card metadata + smart flag + an editable flag reserved for Phase 2); membership
is stored as GIN-indexed collection_keys on member movies (plex_items) and shows (plex_shows).
The background sync (plex_sync) now fetches each library's collections + their children and
rebuilds membership — so ALL reads are local (Plex's dual-language collection queries are slow;
this trades a ~4-min background sync for instant reads). New /api/plex/collections endpoint;
/browse gains a combinable  filter; item_detail returns the movie's collection
'strips' (sibling titles as playable cards, smallest/most-specific collection first) — all pure
local lookups.

Frontend: PlexSidebar gains a searchable Collection picker + an active-collection chip;
PlexInfo renders the collection strips (playable posters + 'Browse collection' → sets the
filter); the collection is part of PlexFilters (persisted). i18n en/hu/de.

Phase 2 (create/edit collections with write-back to Plex) is separate.
This commit is contained in:
npeter83 2026-07-06 02:25:48 +02:00
parent 0385b13a28
commit 418a874851
14 changed files with 386 additions and 15 deletions

View file

@ -33,7 +33,10 @@
"addedOpt": { "24h": "24 Std.", "1w": "1 Woche", "1m": "1 Monat", "6m": "6 Monate", "1y": "1 Jahr" },
"contentRating": "Altersfreigabe",
"match": { "any": "Beliebig", "all": "Alle" },
"dir": { "desc": "↓ Absteigend", "asc": "↑ Aufsteigend" }
"dir": { "desc": "↓ Absteigend", "asc": "↑ Aufsteigend" },
"collection": "Sammlung",
"collectionSearch": "Sammlungen suchen…",
"collectionEmpty": "Keine Sammlungen"
},
"count": "{{count}} Titel",
"searchCount": "{{count}} Treffer",
@ -103,7 +106,8 @@
"clearResume": "Fortsetzungsposition löschen",
"filterYear": "Titel aus {{year}}",
"filterRating": "Titel mit {{n}}+",
"filterActor": "Titel mit {{name}}"
"filterActor": "Titel mit {{name}}",
"browseCollection": "Sammlung durchsuchen →"
},
"playable": {
"direct": "Spielt direkt im Browser",