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.
51 lines
2.1 KiB
Python
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)
|