refactor(api): shared serializer + ownership + write-scope helpers (R6 S1a)
Establish the R6 API-contract infra before the S1b payload:dict migration: - iso(dt) in utils.py replaces the ~16 hand-rolled `x.isoformat() if x else None` serializer idioms across the routes. - get_or_404 / owned_or_404 in routes/_common.py — one 404 + existence-leak policy; plex's _own_playlist_or_404 and saved_views' _own_view now delegate to it. - require_write_scope dependency in auth.py collapses the inline has_write_scope→403 checks (channels subscribe/unsubscribe, playlists push/push-plan) that had drifted into three different messages into one; the conditional delete_playlist guard stays inline (its message was already canonical). No response-shape change (iso == isoformat for a present value). Gate: ruff clean, pytest 163 green (+ test_utils for iso).
This commit is contained in:
@@ -10,6 +10,7 @@ from sqlalchemy.orm import Session
|
||||
from app import audit
|
||||
from app.audit import AuditAction
|
||||
from app.db import get_db
|
||||
from app.utils import iso
|
||||
from app.models import AuditLog, User
|
||||
from app.routes.admin import admin_user
|
||||
|
||||
@@ -23,7 +24,7 @@ MAX_ROWS = 2000
|
||||
def _serialize(row: AuditLog, actors: dict[int, str]) -> dict:
|
||||
return {
|
||||
"id": row.id,
|
||||
"created_at": row.created_at.isoformat() if row.created_at else None,
|
||||
"created_at": iso(row.created_at),
|
||||
# actor_id NULL = the scheduler/background did it (client shows "System"); a set id that
|
||||
# can't be resolved would mean a deleted user, but SET NULL makes that NULL — so present.
|
||||
"actor": actors.get(row.actor_id) if row.actor_id is not None else None,
|
||||
|
||||
Reference in New Issue
Block a user