refactor(api): Pydantic request models for plex routes + status unification (R6 S1b)
All 14 plex payload:dict endpoints typed (watch-link, collections, native
playlists, item/show/season state & progress). Shared PlexItemKeysIn (bulk add/
remove/reorder) and WatchedIn (show/season). item_progress's manual int()+400
becomes a 422 on a non-number.
Status-code unification (ROADMAP S1):
- the 5 collection write-failure 502s → 422, matching the house "external write
failed" convention (playlists' YouTube 422). The image/subtitle proxy-fetch 502s
stay (genuine gateway fetches).
- the field-validation 422s ("title is required", "title and item_rating_key are
required") → 400, matching every other module's required-field 400.
Ends S1b: no `payload: dict` remains in app/routes. Gate: ruff clean, pytest 164.
This commit is contained in:
+107
-45
@@ -21,6 +21,7 @@ from pathlib import Path
|
|||||||
import httpx
|
import httpx
|
||||||
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, Request, Response
|
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException, Query, Request, Response
|
||||||
from fastapi.responses import FileResponse
|
from fastapi.responses import FileResponse
|
||||||
|
from pydantic import BaseModel
|
||||||
from sqlalchemy import Integer, String, and_, case, cast, func, literal, null, or_
|
from sqlalchemy import Integer, String, and_, case, cast, func, literal, null, or_
|
||||||
from sqlalchemy.orm import Session, aliased
|
from sqlalchemy.orm import Session, aliased
|
||||||
|
|
||||||
@@ -156,9 +157,13 @@ def watch_link_status(
|
|||||||
return _link_dict(link)
|
return _link_dict(link)
|
||||||
|
|
||||||
|
|
||||||
|
class WatchLinkIn(BaseModel):
|
||||||
|
enabled: bool = False
|
||||||
|
|
||||||
|
|
||||||
@router.post("/watch/link")
|
@router.post("/watch/link")
|
||||||
def watch_link_set(
|
def watch_link_set(
|
||||||
payload: dict,
|
body: WatchLinkIn,
|
||||||
user: User = Depends(admin_user),
|
user: User = Depends(admin_user),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
) -> dict:
|
) -> dict:
|
||||||
@@ -167,7 +172,7 @@ def watch_link_set(
|
|||||||
"Plex is master" import immediately so the owner's existing Plex history shows up in Siftlode."""
|
"Plex is master" import immediately so the owner's existing Plex history shows up in Siftlode."""
|
||||||
if not sysconfig.get_bool(db, "plex_enabled"):
|
if not sysconfig.get_bool(db, "plex_enabled"):
|
||||||
raise HTTPException(status_code=409, detail="The Plex module is disabled.")
|
raise HTTPException(status_code=409, detail="The Plex module is disabled.")
|
||||||
enabled = bool(payload.get("enabled"))
|
enabled = body.enabled
|
||||||
link = db.query(PlexLink).filter_by(user_id=user.id).first()
|
link = db.query(PlexLink).filter_by(user_id=user.id).first()
|
||||||
if link is None:
|
if link is None:
|
||||||
link = PlexLink(user_id=user.id, uses_admin=True, linked_at=datetime.now(timezone.utc))
|
link = PlexLink(user_id=user.id, uses_admin=True, linked_at=datetime.now(timezone.utc))
|
||||||
@@ -797,14 +802,22 @@ def _editable_collection_or_403(db: Session, rating_key: str) -> PlexCollection:
|
|||||||
return col
|
return col
|
||||||
|
|
||||||
|
|
||||||
|
class CreateCollectionIn(BaseModel):
|
||||||
|
library: str = ""
|
||||||
|
title: str = ""
|
||||||
|
item_rating_key: str = ""
|
||||||
|
|
||||||
|
|
||||||
@router.post("/collections")
|
@router.post("/collections")
|
||||||
def create_collection(payload: dict, _: User = Depends(admin_user), db: Session = Depends(get_db)) -> dict:
|
def create_collection(
|
||||||
|
body: CreateCollectionIn, _: User = Depends(admin_user), db: Session = Depends(get_db)
|
||||||
|
) -> dict:
|
||||||
"""Create a manual collection (seeded with one item — Plex needs at least one). Editable by default."""
|
"""Create a manual collection (seeded with one item — Plex needs at least one). Editable by default."""
|
||||||
library = str(payload.get("library") or "")
|
library = body.library
|
||||||
title = (payload.get("title") or "").strip()
|
title = body.title.strip()
|
||||||
seed = str(payload.get("item_rating_key") or "")
|
seed = body.item_rating_key
|
||||||
if not title or not seed:
|
if not title or not seed:
|
||||||
raise HTTPException(status_code=422, detail="title and item_rating_key are required")
|
raise HTTPException(status_code=400, detail="title and item_rating_key are required")
|
||||||
lib = db.query(PlexLibrary).filter_by(plex_key=library).first()
|
lib = db.query(PlexLibrary).filter_by(plex_key=library).first()
|
||||||
if lib is None:
|
if lib is None:
|
||||||
raise HTTPException(status_code=404, detail="Unknown library")
|
raise HTTPException(status_code=404, detail="Unknown library")
|
||||||
@@ -816,7 +829,7 @@ def create_collection(payload: dict, _: User = Depends(admin_user), db: Session
|
|||||||
db.commit()
|
db.commit()
|
||||||
except (PlexError, PlexNotConfigured) as e:
|
except (PlexError, PlexNotConfigured) as e:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
raise HTTPException(status_code=502, detail=f"Plex write failed: {e}")
|
raise HTTPException(status_code=422, detail=f"Plex write failed: {e}")
|
||||||
return _collection_card(col)
|
return _collection_card(col)
|
||||||
|
|
||||||
|
|
||||||
@@ -833,7 +846,7 @@ def collection_add_item(
|
|||||||
db.commit()
|
db.commit()
|
||||||
except (PlexError, PlexNotConfigured) as e:
|
except (PlexError, PlexNotConfigured) as e:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
raise HTTPException(status_code=502, detail=f"Plex write failed: {e}")
|
raise HTTPException(status_code=422, detail=f"Plex write failed: {e}")
|
||||||
return _collection_card(col)
|
return _collection_card(col)
|
||||||
|
|
||||||
|
|
||||||
@@ -850,18 +863,22 @@ def collection_remove_item(
|
|||||||
db.commit()
|
db.commit()
|
||||||
except (PlexError, PlexNotConfigured) as e:
|
except (PlexError, PlexNotConfigured) as e:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
raise HTTPException(status_code=502, detail=f"Plex write failed: {e}")
|
raise HTTPException(status_code=422, detail=f"Plex write failed: {e}")
|
||||||
return _collection_card(col)
|
return _collection_card(col)
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionRenameIn(BaseModel):
|
||||||
|
title: str = ""
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/collections/{rating_key}")
|
@router.patch("/collections/{rating_key}")
|
||||||
def collection_rename(
|
def collection_rename(
|
||||||
rating_key: str, payload: dict, _: User = Depends(admin_user), db: Session = Depends(get_db)
|
rating_key: str, body: CollectionRenameIn, _: User = Depends(admin_user), db: Session = Depends(get_db)
|
||||||
) -> dict:
|
) -> dict:
|
||||||
col = _editable_collection_or_403(db, rating_key)
|
col = _editable_collection_or_403(db, rating_key)
|
||||||
title = (payload.get("title") or "").strip()
|
title = body.title.strip()
|
||||||
if not title:
|
if not title:
|
||||||
raise HTTPException(status_code=422, detail="title is required")
|
raise HTTPException(status_code=400, detail="title is required")
|
||||||
lib = db.get(PlexLibrary, col.library_id)
|
lib = db.get(PlexLibrary, col.library_id)
|
||||||
try:
|
try:
|
||||||
with PlexClient(db) as plex:
|
with PlexClient(db) as plex:
|
||||||
@@ -870,7 +887,7 @@ def collection_rename(
|
|||||||
db.commit()
|
db.commit()
|
||||||
except (PlexError, PlexNotConfigured) as e:
|
except (PlexError, PlexNotConfigured) as e:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
raise HTTPException(status_code=502, detail=f"Plex write failed: {e}")
|
raise HTTPException(status_code=422, detail=f"Plex write failed: {e}")
|
||||||
return _collection_card(col)
|
return _collection_card(col)
|
||||||
|
|
||||||
|
|
||||||
@@ -887,20 +904,24 @@ def collection_delete(
|
|||||||
db.commit()
|
db.commit()
|
||||||
except (PlexError, PlexNotConfigured) as e:
|
except (PlexError, PlexNotConfigured) as e:
|
||||||
db.rollback()
|
db.rollback()
|
||||||
raise HTTPException(status_code=502, detail=f"Plex write failed: {e}")
|
raise HTTPException(status_code=422, detail=f"Plex write failed: {e}")
|
||||||
return {"deleted": rating_key}
|
return {"deleted": rating_key}
|
||||||
|
|
||||||
|
|
||||||
|
class CollectionEditableIn(BaseModel):
|
||||||
|
editable: bool = False
|
||||||
|
|
||||||
|
|
||||||
@router.post("/collections/{rating_key}/editable")
|
@router.post("/collections/{rating_key}/editable")
|
||||||
def collection_set_editable(
|
def collection_set_editable(
|
||||||
rating_key: str, payload: dict, _: User = Depends(admin_user), db: Session = Depends(get_db)
|
rating_key: str, body: CollectionEditableIn, _: User = Depends(admin_user), db: Session = Depends(get_db)
|
||||||
) -> dict:
|
) -> dict:
|
||||||
"""Mark an existing Plex collection as 'mine to edit' (or unmark). Smart / auto-list collections
|
"""Mark an existing Plex collection as 'mine to edit' (or unmark). Smart / auto-list collections
|
||||||
(IMDb/TMDb/…) can never be made editable — they're regenerated outside Plex."""
|
(IMDb/TMDb/…) can never be made editable — they're regenerated outside Plex."""
|
||||||
col = db.query(PlexCollection).filter_by(rating_key=str(rating_key)).first()
|
col = db.query(PlexCollection).filter_by(rating_key=str(rating_key)).first()
|
||||||
if col is None:
|
if col is None:
|
||||||
raise HTTPException(status_code=404, detail="Unknown collection")
|
raise HTTPException(status_code=404, detail="Unknown collection")
|
||||||
want = bool(payload.get("editable"))
|
want = body.editable
|
||||||
if want and (col.smart or _collection_source(col) != "collection"):
|
if want and (col.smart or _collection_source(col) != "collection"):
|
||||||
raise HTTPException(status_code=400, detail="Smart / auto-list collections can't be made editable")
|
raise HTTPException(status_code=400, detail="Smart / auto-list collections can't be made editable")
|
||||||
col.editable = want
|
col.editable = want
|
||||||
@@ -975,15 +996,22 @@ def playlists(
|
|||||||
return resp
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
class CreatePlexPlaylistIn(BaseModel):
|
||||||
|
title: str = ""
|
||||||
|
item_rating_key: str = ""
|
||||||
|
|
||||||
|
|
||||||
@router.post("/playlists")
|
@router.post("/playlists")
|
||||||
def create_playlist(payload: dict, user: User = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
def create_playlist(
|
||||||
title = (payload.get("title") or "").strip()
|
body: CreatePlexPlaylistIn, user: User = Depends(current_user), db: Session = Depends(get_db)
|
||||||
|
) -> dict:
|
||||||
|
title = body.title.strip()
|
||||||
if not title:
|
if not title:
|
||||||
raise HTTPException(status_code=422, detail="title is required")
|
raise HTTPException(status_code=400, detail="title is required")
|
||||||
pl = PlexPlaylist(user_id=user.id, title=title)
|
pl = PlexPlaylist(user_id=user.id, title=title)
|
||||||
db.add(pl)
|
db.add(pl)
|
||||||
db.flush()
|
db.flush()
|
||||||
seed = str(payload.get("item_rating_key") or "")
|
seed = body.item_rating_key
|
||||||
count = 0
|
count = 0
|
||||||
thumb_rk = None
|
thumb_rk = None
|
||||||
if seed:
|
if seed:
|
||||||
@@ -1022,12 +1050,18 @@ def playlist_detail(pid: int, user: User = Depends(current_user), db: Session =
|
|||||||
return {"id": pl.id, "title": pl.title, "items": cards}
|
return {"id": pl.id, "title": pl.title, "items": cards}
|
||||||
|
|
||||||
|
|
||||||
|
class RenamePlexPlaylistIn(BaseModel):
|
||||||
|
title: str = ""
|
||||||
|
|
||||||
|
|
||||||
@router.patch("/playlists/{pid}")
|
@router.patch("/playlists/{pid}")
|
||||||
def rename_playlist(pid: int, payload: dict, user: User = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
def rename_playlist(
|
||||||
|
pid: int, body: RenamePlexPlaylistIn, user: User = Depends(current_user), db: Session = Depends(get_db)
|
||||||
|
) -> dict:
|
||||||
pl = _own_playlist_or_404(db, pid, user)
|
pl = _own_playlist_or_404(db, pid, user)
|
||||||
title = (payload.get("title") or "").strip()
|
title = body.title.strip()
|
||||||
if not title:
|
if not title:
|
||||||
raise HTTPException(status_code=422, detail="title is required")
|
raise HTTPException(status_code=400, detail="title is required")
|
||||||
pl.title = title
|
pl.title = title
|
||||||
db.commit()
|
db.commit()
|
||||||
count = db.query(func.count(PlexPlaylistItem.id)).filter_by(playlist_id=pl.id).scalar() or 0
|
count = db.query(func.count(PlexPlaylistItem.id)).filter_by(playlist_id=pl.id).scalar() or 0
|
||||||
@@ -1043,10 +1077,16 @@ def delete_playlist(pid: int, user: User = Depends(current_user), db: Session =
|
|||||||
return {"deleted": pid}
|
return {"deleted": pid}
|
||||||
|
|
||||||
|
|
||||||
|
class PlexItemRefIn(BaseModel):
|
||||||
|
item_rating_key: str = ""
|
||||||
|
|
||||||
|
|
||||||
@router.post("/playlists/{pid}/items")
|
@router.post("/playlists/{pid}/items")
|
||||||
def playlist_add_item(pid: int, payload: dict, user: User = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
def playlist_add_item(
|
||||||
|
pid: int, body: PlexItemRefIn, user: User = Depends(current_user), db: Session = Depends(get_db)
|
||||||
|
) -> dict:
|
||||||
pl = _own_playlist_or_404(db, pid, user)
|
pl = _own_playlist_or_404(db, pid, user)
|
||||||
it = db.query(PlexItem).filter_by(rating_key=str(payload.get("item_rating_key") or "")).first()
|
it = db.query(PlexItem).filter_by(rating_key=body.item_rating_key).first()
|
||||||
if it is None:
|
if it is None:
|
||||||
raise HTTPException(status_code=404, detail="Unknown item")
|
raise HTTPException(status_code=404, detail="Unknown item")
|
||||||
exists = db.query(PlexPlaylistItem).filter_by(playlist_id=pid, item_id=it.id).first()
|
exists = db.query(PlexPlaylistItem).filter_by(playlist_id=pid, item_id=it.id).first()
|
||||||
@@ -1069,6 +1109,10 @@ def playlist_remove_item(pid: int, item_rating_key: str, user: User = Depends(cu
|
|||||||
return {"ok": True}
|
return {"ok": True}
|
||||||
|
|
||||||
|
|
||||||
|
class PlexItemKeysIn(BaseModel):
|
||||||
|
item_rating_keys: list[str] = []
|
||||||
|
|
||||||
|
|
||||||
def _items_by_rks(db: Session, rks: list) -> list[PlexItem]:
|
def _items_by_rks(db: Session, rks: list) -> list[PlexItem]:
|
||||||
"""Resolve rating_keys to PlexItems, preserving the input order (drops unknown keys)."""
|
"""Resolve rating_keys to PlexItems, preserving the input order (drops unknown keys)."""
|
||||||
order = [str(k) for k in rks if str(k)]
|
order = [str(k) for k in rks if str(k)]
|
||||||
@@ -1086,11 +1130,13 @@ def _items_by_rks(db: Session, rks: list) -> list[PlexItem]:
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/playlists/{pid}/items/bulk")
|
@router.post("/playlists/{pid}/items/bulk")
|
||||||
def playlist_add_bulk(pid: int, payload: dict, user: User = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
def playlist_add_bulk(
|
||||||
|
pid: int, body: PlexItemKeysIn, user: User = Depends(current_user), db: Session = Depends(get_db)
|
||||||
|
) -> dict:
|
||||||
"""Append many items at once (a whole season/show). Already-present items are skipped; the input
|
"""Append many items at once (a whole season/show). Already-present items are skipped; the input
|
||||||
order is preserved for the newly-added ones. Returns how many were added + the new total."""
|
order is preserved for the newly-added ones. Returns how many were added + the new total."""
|
||||||
pl = _own_playlist_or_404(db, pid, user)
|
pl = _own_playlist_or_404(db, pid, user)
|
||||||
items = _items_by_rks(db, payload.get("item_rating_keys") or [])
|
items = _items_by_rks(db, body.item_rating_keys)
|
||||||
existing = {row[0] for row in db.query(PlexPlaylistItem.item_id).filter_by(playlist_id=pid)}
|
existing = {row[0] for row in db.query(PlexPlaylistItem.item_id).filter_by(playlist_id=pid)}
|
||||||
pos = int(db.query(func.coalesce(func.max(PlexPlaylistItem.position), -1)).filter_by(playlist_id=pid).scalar())
|
pos = int(db.query(func.coalesce(func.max(PlexPlaylistItem.position), -1)).filter_by(playlist_id=pid).scalar())
|
||||||
added = 0
|
added = 0
|
||||||
@@ -1109,11 +1155,13 @@ def playlist_add_bulk(pid: int, payload: dict, user: User = Depends(current_user
|
|||||||
|
|
||||||
|
|
||||||
@router.post("/playlists/{pid}/items/remove-bulk")
|
@router.post("/playlists/{pid}/items/remove-bulk")
|
||||||
def playlist_remove_bulk(pid: int, payload: dict, user: User = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
def playlist_remove_bulk(
|
||||||
|
pid: int, body: PlexItemKeysIn, user: User = Depends(current_user), db: Session = Depends(get_db)
|
||||||
|
) -> dict:
|
||||||
"""Remove many items at once (a whole season/show, or a multi-select). Returns how many were
|
"""Remove many items at once (a whole season/show, or a multi-select). Returns how many were
|
||||||
removed + the new total."""
|
removed + the new total."""
|
||||||
pl = _own_playlist_or_404(db, pid, user)
|
pl = _own_playlist_or_404(db, pid, user)
|
||||||
ids = [it.id for it in _items_by_rks(db, payload.get("item_rating_keys") or [])]
|
ids = [it.id for it in _items_by_rks(db, body.item_rating_keys)]
|
||||||
removed = 0
|
removed = 0
|
||||||
if ids:
|
if ids:
|
||||||
removed = (
|
removed = (
|
||||||
@@ -1129,10 +1177,12 @@ def playlist_remove_bulk(pid: int, payload: dict, user: User = Depends(current_u
|
|||||||
|
|
||||||
|
|
||||||
@router.put("/playlists/{pid}/order")
|
@router.put("/playlists/{pid}/order")
|
||||||
def reorder_playlist(pid: int, payload: dict, user: User = Depends(current_user), db: Session = Depends(get_db)) -> dict:
|
def reorder_playlist(
|
||||||
|
pid: int, body: PlexItemKeysIn, user: User = Depends(current_user), db: Session = Depends(get_db)
|
||||||
|
) -> dict:
|
||||||
"""Set the playlist order from an explicit list of item rating_keys (0-based positions)."""
|
"""Set the playlist order from an explicit list of item rating_keys (0-based positions)."""
|
||||||
pl = _own_playlist_or_404(db, pid, user)
|
pl = _own_playlist_or_404(db, pid, user)
|
||||||
order = [str(k) for k in (payload.get("item_rating_keys") or [])]
|
order = body.item_rating_keys
|
||||||
id_by_rk = {it.rating_key: it.id for it in db.query(PlexItem).filter(PlexItem.rating_key.in_(order or [""]))}
|
id_by_rk = {it.rating_key: it.id for it in db.query(PlexItem).filter(PlexItem.rating_key.in_(order or [""]))}
|
||||||
for pos, rk in enumerate(order):
|
for pos, rk in enumerate(order):
|
||||||
iid = id_by_rk.get(rk)
|
iid = id_by_rk.get(rk)
|
||||||
@@ -1892,10 +1942,16 @@ def _push_watch(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class ItemProgressIn(BaseModel):
|
||||||
|
position_seconds: int = 0
|
||||||
|
duration_seconds: int = 0
|
||||||
|
final: bool = False
|
||||||
|
|
||||||
|
|
||||||
@router.post("/item/{rating_key}/progress")
|
@router.post("/item/{rating_key}/progress")
|
||||||
def item_progress(
|
def item_progress(
|
||||||
rating_key: str,
|
rating_key: str,
|
||||||
payload: dict,
|
body: ItemProgressIn,
|
||||||
background: BackgroundTasks,
|
background: BackgroundTasks,
|
||||||
user: User = Depends(current_user),
|
user: User = Depends(current_user),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
@@ -1906,12 +1962,9 @@ def item_progress(
|
|||||||
debounces the Plex resume push: we mirror the settled position to Plex only on a final flush, so
|
debounces the Plex resume push: we mirror the settled position to Plex only on a final flush, so
|
||||||
a single watch doesn't spray `/:/timeline` calls at the server every 10 seconds."""
|
a single watch doesn't spray `/:/timeline` calls at the server every 10 seconds."""
|
||||||
it = _item_or_404(db, rating_key)
|
it = _item_or_404(db, rating_key)
|
||||||
try:
|
position = max(0, body.position_seconds)
|
||||||
position = max(0, int(payload.get("position_seconds") or 0))
|
duration = body.duration_seconds
|
||||||
duration = int(payload.get("duration_seconds") or 0)
|
final = body.final
|
||||||
except (TypeError, ValueError):
|
|
||||||
raise HTTPException(status_code=400, detail="position_seconds must be an integer")
|
|
||||||
final = bool(payload.get("final"))
|
|
||||||
now = datetime.now(timezone.utc)
|
now = datetime.now(timezone.utc)
|
||||||
st = db.query(PlexState).filter_by(user_id=user.id, item_id=it.id).first()
|
st = db.query(PlexState).filter_by(user_id=user.id, item_id=it.id).first()
|
||||||
was_watched = st is not None and st.status == "watched"
|
was_watched = st is not None and st.status == "watched"
|
||||||
@@ -1957,10 +2010,14 @@ def item_progress(
|
|||||||
return {"position_seconds": position}
|
return {"position_seconds": position}
|
||||||
|
|
||||||
|
|
||||||
|
class ItemStateIn(BaseModel):
|
||||||
|
status: str = ""
|
||||||
|
|
||||||
|
|
||||||
@router.post("/item/{rating_key}/state")
|
@router.post("/item/{rating_key}/state")
|
||||||
def item_state(
|
def item_state(
|
||||||
rating_key: str,
|
rating_key: str,
|
||||||
payload: dict,
|
body: ItemStateIn,
|
||||||
background: BackgroundTasks,
|
background: BackgroundTasks,
|
||||||
user: User = Depends(current_user),
|
user: User = Depends(current_user),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
@@ -1969,7 +2026,7 @@ def item_state(
|
|||||||
to a linked Plex account immediately; `hidden` is a Siftlode-only concept with no Plex analogue,
|
to a linked Plex account immediately; `hidden` is a Siftlode-only concept with no Plex analogue,
|
||||||
so it never touches Plex."""
|
so it never touches Plex."""
|
||||||
it = _item_or_404(db, rating_key)
|
it = _item_or_404(db, rating_key)
|
||||||
status = payload.get("status")
|
status = body.status
|
||||||
if status not in ("new", "watched", "hidden"):
|
if status not in ("new", "watched", "hidden"):
|
||||||
raise HTTPException(status_code=400, detail="Invalid status")
|
raise HTTPException(status_code=400, detail="Invalid status")
|
||||||
st = db.query(PlexState).filter_by(user_id=user.id, item_id=it.id).first()
|
st = db.query(PlexState).filter_by(user_id=user.id, item_id=it.id).first()
|
||||||
@@ -2035,10 +2092,15 @@ def _bulk_state(
|
|||||||
return len(to_push)
|
return len(to_push)
|
||||||
|
|
||||||
|
|
||||||
|
# Shared by the show- and season-level "mark all episodes watched/unwatched" endpoints.
|
||||||
|
class WatchedIn(BaseModel):
|
||||||
|
watched: bool = False
|
||||||
|
|
||||||
|
|
||||||
@router.post("/show/{rating_key}/state")
|
@router.post("/show/{rating_key}/state")
|
||||||
def show_state(
|
def show_state(
|
||||||
rating_key: str,
|
rating_key: str,
|
||||||
payload: dict,
|
body: WatchedIn,
|
||||||
background: BackgroundTasks,
|
background: BackgroundTasks,
|
||||||
user: User = Depends(current_user),
|
user: User = Depends(current_user),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
@@ -2047,7 +2109,7 @@ def show_state(
|
|||||||
sh = db.query(PlexShow).filter_by(rating_key=str(rating_key)).first()
|
sh = db.query(PlexShow).filter_by(rating_key=str(rating_key)).first()
|
||||||
if sh is None:
|
if sh is None:
|
||||||
raise HTTPException(status_code=404, detail="Unknown Plex show")
|
raise HTTPException(status_code=404, detail="Unknown Plex show")
|
||||||
watched = bool(payload.get("watched"))
|
watched = body.watched
|
||||||
eps = db.query(PlexItem).filter_by(show_id=sh.id, kind="episode").all()
|
eps = db.query(PlexItem).filter_by(show_id=sh.id, kind="episode").all()
|
||||||
changed = _bulk_state(background, db, user.id, eps, watched)
|
changed = _bulk_state(background, db, user.id, eps, watched)
|
||||||
return {"changed": changed, "watched": watched}
|
return {"changed": changed, "watched": watched}
|
||||||
@@ -2056,7 +2118,7 @@ def show_state(
|
|||||||
@router.post("/season/{rating_key}/state")
|
@router.post("/season/{rating_key}/state")
|
||||||
def season_state(
|
def season_state(
|
||||||
rating_key: str,
|
rating_key: str,
|
||||||
payload: dict,
|
body: WatchedIn,
|
||||||
background: BackgroundTasks,
|
background: BackgroundTasks,
|
||||||
user: User = Depends(current_user),
|
user: User = Depends(current_user),
|
||||||
db: Session = Depends(get_db),
|
db: Session = Depends(get_db),
|
||||||
@@ -2065,7 +2127,7 @@ def season_state(
|
|||||||
se = db.query(PlexSeason).filter_by(rating_key=str(rating_key)).first()
|
se = db.query(PlexSeason).filter_by(rating_key=str(rating_key)).first()
|
||||||
if se is None:
|
if se is None:
|
||||||
raise HTTPException(status_code=404, detail="Unknown Plex season")
|
raise HTTPException(status_code=404, detail="Unknown Plex season")
|
||||||
watched = bool(payload.get("watched"))
|
watched = body.watched
|
||||||
eps = db.query(PlexItem).filter_by(season_id=se.id, kind="episode").all()
|
eps = db.query(PlexItem).filter_by(season_id=se.id, kind="episode").all()
|
||||||
changed = _bulk_state(background, db, user.id, eps, watched)
|
changed = _bulk_state(background, db, user.id, eps, watched)
|
||||||
return {"changed": changed, "watched": watched}
|
return {"changed": changed, "watched": watched}
|
||||||
|
|||||||
Reference in New Issue
Block a user