feat(plex): 'last watched' browse sort + auto-apply in the in-progress view (S3)

Add a 'lastwatched' sort key to the unified Plex browse. Movies rank by
coalesce(progress_updated_at, watched_at); shows by max(coalesce(...)) across
their episodes (rollup.resumed_at, now broadened from progress-only so watched
episodes count too). Never-watched titles sink via nullslast.

Frontend auto-applies lastwatched:desc when entering the in-progress watch-state
view and restores the pre-entry sort on exit; a manual sort change while there
still wins. i18n HU+EN. Backend tests cover both union legs + mixed nullslast.
This commit is contained in:
2026-08-27 02:50:41 +02:00
parent 6c918d1dff
commit 8299ea72b7
7 changed files with 212 additions and 6 deletions
+7 -3
View File
@@ -43,8 +43,10 @@ def rollup(cards: list[dict]) -> dict:
def agg_subquery(db: Session, user_id: int, include_resumed: bool = False):
"""Per-show episode counts for a user: total, watched, in-progress. Reused to (a) filter the show
grid by aggregate watch-state, (b) compute each card's badge, and (c) drive the Discover
continue-watching rail. `include_resumed` adds `resumed_at` = max(progress_updated_at) across the
show's episodes, so the rail can order shows by how recently they were last progressed."""
continue-watching rail, and (d) the browse "last watched" sort. `include_resumed` adds `resumed_at`
= max(coalesce(progress_updated_at, watched_at)) across the show's episodes — i.e. how recently ANY
episode was last touched (resumed OR marked watched) — so callers can order shows by recency. A
fully-watched show (no resume progress) still gets a timestamp from its episodes' watched_at."""
ps = aliased(PlexState)
cols = [
PlexItem.show_id.label("show_id"),
@@ -63,7 +65,9 @@ def agg_subquery(db: Session, user_id: int, include_resumed: bool = False):
).label("inprog"),
]
if include_resumed:
cols.append(func.max(ps.progress_updated_at).label("resumed_at"))
cols.append(
func.max(func.coalesce(ps.progress_updated_at, ps.watched_at)).label("resumed_at")
)
return (
select(*cols)
.outerjoin(ps, and_(ps.item_id == PlexItem.id, ps.user_id == user_id))