Applies the three findings from the /code-review high pass on this stack:
1. to_tsquery_str moved from app.feedquery to app.utils — it's a generic
tsquery sanitizer with no feed dependency; housing it in the feed
read-model coupled the Plex browse module to the feed domain. Both feed
and plex now import it from the neutral util module.
2. feedquery.decode_cursor no longer raises fastapi.HTTPException — it raises
a plain ValueError, and routes/feed.get_feed translates it to the HTTP 400.
The read-model no longer imports fastapi, matching its "no request state"
docstring.
3. Dropped the leading underscore on the helpers that are now a cross-module
public API: feedquery {filtered_query, sort_keys, order_by, keyset_predicate,
encode_cursor, decode_cursor}; plex.query {TS_CONFIG, FIELD_COLUMNS,
field_recheck, split_csv, added_cutoff, meta_filter_conds, apply_meta_filters,
like_escape}; plex.cards {movie_card, show_card, episode_card, leaf_card}.
Genuinely-internal helpers (_channel_url, _saved_expr, _ADDED_WINDOWS,
_lib_key) keep their underscore.
Pure code motion + rename. DB-lane suite 287 passed / 2 skipped; ruff clean.
66 lines
2.2 KiB
Python
66 lines
2.2 KiB
Python
"""Plex list/leaf card serializers: the small `model -> dict` projections the library grid,
|
|
playlists, and detail views render. Extracted from routes/plex/browse.py so the row-shaping
|
|
lives beside the other Plex read-model modules (detail.py, query.py) rather than inside the
|
|
router. Pure functions — no DB, no request state."""
|
|
|
|
from app.models import PlexItem, PlexShow, PlexState
|
|
|
|
|
|
def movie_card(it: PlexItem, st: PlexState | None) -> dict:
|
|
return {
|
|
"id": it.rating_key,
|
|
"type": "movie",
|
|
"title": it.title,
|
|
"year": it.year,
|
|
"duration_seconds": it.duration_s,
|
|
"thumb": f"/api/plex/image/{it.rating_key}",
|
|
"playable": it.playable,
|
|
"status": st.status if st else "new",
|
|
"position_seconds": st.position_seconds if st else 0,
|
|
}
|
|
|
|
|
|
def show_card(sh: PlexShow, status: str = "new") -> dict:
|
|
return {
|
|
"id": sh.rating_key,
|
|
"type": "show",
|
|
"title": sh.title,
|
|
"year": sh.year,
|
|
"thumb": f"/api/plex/image/{sh.rating_key}",
|
|
"season_count": sh.child_count,
|
|
# Aggregate watch-state across the show's episodes (new | in_progress | watched) → grid badge.
|
|
"status": status,
|
|
}
|
|
|
|
|
|
def episode_card(e: PlexItem, st: PlexState | None) -> dict:
|
|
return {
|
|
"id": e.rating_key,
|
|
"type": "episode",
|
|
"title": e.title,
|
|
"summary": e.summary,
|
|
"season_number": e.season_number,
|
|
"episode_number": e.episode_number,
|
|
"duration_seconds": e.duration_s,
|
|
"thumb": f"/api/plex/image/{e.rating_key}",
|
|
"playable": e.playable,
|
|
"status": st.status if st else "new",
|
|
"position_seconds": st.position_seconds if st else 0,
|
|
}
|
|
|
|
|
|
def leaf_card(
|
|
it: PlexItem,
|
|
st: PlexState | None,
|
|
show_title: str | None = None,
|
|
show_rk: str | None = None,
|
|
) -> dict:
|
|
"""A playable-leaf card (movie or episode). Used by playlists, which can mix both kinds.
|
|
`show_rk` (the show's rating_key) lets the client group a playlist's episodes by show/season."""
|
|
if it.kind == "episode":
|
|
card = episode_card(it, st)
|
|
card["show_title"] = show_title
|
|
card["show_id"] = show_rk
|
|
return card
|
|
return movie_card(it, st)
|