Files
siftlode/backend/app/routes/plex/discover.py
T
peter 531eb2eef1 refactor(plex): playlists, playback, Discover and admin through the unit; the plex package is fenced
The last Plex HTTP files do no database work of their own, so ALLOWED is empty: no route builds
SQL, touches the session or a unit's table. Discover's rails move whole into plex/discover.py; a
user's playlists, watch-state writes (the race-safe finish, an early checkpoint, a status, a
season or show at once) and the watch-sync link go through plex/queries.py and plex/service.py.

The plex package joins FENCED: it reads no other unit's tables, says whose a row is through
userscope (the rollup and the watch sync's 8 inline filters moved to owned_by), and commits only in
its named jobs — the library sync, TMDB enrichment, the watch sync and its background pushes.
A playlist's has-item probe keeps the plain key lookup, so a NUL in it is still the 422 a test pins.

Pins 178 -> 0; basedpyright baseline 118 -> 106.
2026-10-19 22:42:46 +02:00

51 lines
2.1 KiB
Python

"""Plex Discover: the rail-surface landing and its Surprise-me draw (`plex.discover`)."""
from fastapi import APIRouter, Depends, Query
from sqlalchemy.orm import Session
from app.auth import current_user
from app.db import get_db
from app.models import User
from app.plex import discover as plex_discover
from app.plex.discover import SURPRISE_PER_KIND
from app.plex.query import LibraryFilters
from app.routes.plex._shared import _norm_lang
router = APIRouter(prefix="/api/plex", tags=["plex"])
@router.get("/discover")
def discover(
scope: str = "both",
lang: str = "en",
f: LibraryFilters = Depends(),
user: User = Depends(current_user),
db: Session = Depends(get_db),
) -> dict:
"""The Discover landing payload — every rail in one pass (serve-from-DB), narrowed by the active
sidebar filters. Empty rails come back as empty lists (the client hides them); `because_you_watched`
is null when there's no anchor."""
return plex_discover.discover_page(db, user, scope, _norm_lang(lang), f)
@router.get("/surprise")
def surprise(
scope: str = "both",
lang: str = "en",
limit: int = Query(default=2 * SURPRISE_PER_KIND, ge=1, le=40),
# The previous draw's rating keys: a reshuffle deals other titles first.
exclude: list[str] = Query(default=[], max_length=40),
# The draw on screen, read again rather than redrawn: its titles' watch state moves (a play, a
# toggle) while the draw itself stands until the ↻.
keep: list[str] = Query(default=[], max_length=40),
f: LibraryFilters = Depends(),
user: User = Depends(current_user),
db: Session = Depends(get_db),
) -> dict:
"""A fresh random sample for the reshuffling Surprise-me rail (a random draw each call), narrowed by
the active filters. Only titles the user has NOT seen — a watched movie / a fully-watched show is
excluded (the point is to surface something to watch). Capped at SURPRISE_PER_KIND movies +
SURPRISE_PER_KIND shows, drawn separately then interleaved so a lopsided library doesn't crowd out
one type."""
return plex_discover.surprise(db, user, scope, _norm_lang(lang), limit, exclude, keep, f)