15b59a214d
New client-reported playback-error log. Surfaces zero-duration
tracks (and future load_failed / stalled kinds) into an admin
inbox so the operator can hide / delete / re-request the
offending track.
Schema (migration 0032):
- playback_errors table with CHECK constraints on the kind +
resolution enums (per the standing rule that new enum values
need a migration to add)
- Partial index on unresolved rows for fast inbox lookup
- ON DELETE CASCADE from tracks + users so cleanup is automatic
Endpoints:
- POST /api/playback-errors: any signed-in user reports. Body
validates track existence + kind whitelist; client_id required
so support can correlate reports from the same device.
- GET /api/admin/playback-errors?resolved=false&offset=&limit=:
admin list with join to track/album/artist for table render
without per-row round-trips. Pagination capped at 200/page.
- POST /api/admin/playback-errors/{id}/resolve: admin marks
resolved with a resolution enum string.
Auto-resolve on Hide/Delete/Re-request from the inbox row is
driven from the web client (two sequential calls) — keeps the
existing track-action endpoints unchanged.
42 lines
1.8 KiB
SQL
42 lines
1.8 KiB
SQL
-- Client-reported playback errors. Populated by clients when a track
|
|
-- fails to play (zero duration, decode error, etc.); surfaced in the
|
|
-- admin /admin/playback-errors inbox so the operator can hide / delete
|
|
-- / re-request the offending track.
|
|
--
|
|
-- resolved_at + resolved_by + resolution are NULL until an admin acts
|
|
-- on the row. Auto-resolved by the client when the admin clicks Hide /
|
|
-- Delete / Re-request from the inbox row, with the resolution string
|
|
-- recording which path was taken. The partial index keeps the
|
|
-- unresolved-queue lookup fast even as resolved history accumulates.
|
|
--
|
|
-- CHECK constraints on kind/resolution gate the enum values so client
|
|
-- typos can't pollute the data (per the standing rule about
|
|
-- enum-CHECK whitelists needing migrations).
|
|
|
|
CREATE TABLE playback_errors (
|
|
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
track_id uuid NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
|
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
client_id text NOT NULL,
|
|
kind text NOT NULL,
|
|
detail text,
|
|
occurred_at timestamptz NOT NULL DEFAULT now(),
|
|
resolved_at timestamptz,
|
|
resolved_by uuid REFERENCES users(id),
|
|
resolution text,
|
|
CONSTRAINT playback_errors_kind_check
|
|
CHECK (kind IN ('zero_duration', 'load_failed', 'stalled')),
|
|
CONSTRAINT playback_errors_resolution_check
|
|
CHECK (resolution IS NULL OR resolution IN
|
|
('hidden', 'deleted', 'requested', 'fixed', 'ignored')),
|
|
CONSTRAINT playback_errors_resolved_consistency
|
|
CHECK ((resolved_at IS NULL) = (resolution IS NULL))
|
|
);
|
|
|
|
CREATE INDEX idx_playback_errors_unresolved
|
|
ON playback_errors (occurred_at DESC)
|
|
WHERE resolved_at IS NULL;
|
|
|
|
CREATE INDEX idx_playback_errors_track
|
|
ON playback_errors (track_id);
|