d2a0b7d780
First of three stages for system-playlist sample-history dedup (Fable #415, server-side rotation per the operator's choice). Schema (migration 0027): - play_events.source (nullable text): which surface a play came from. 'for_you' / 'discover' feed rotation; NULL for library / user-playlist / radio / Subsonic. - system_playlist_rotation_state(user_id, playlist_kind, played_track_ids uuid[], rotation_started_at, updated_at): the per-(user,kind) set of already-heard tracks this rotation. Ingest: - New RecordPlayStartedWithSource on the writer; RecordPlayStarted is now a thin source="" wrapper so the frozen Subsonic shim is untouched (no signature ripple). - When source is a known system kind, the same txn appends the track to rotation state (AppendRotationPlayed keeps the array a set via the conflict CASE). - /api/events play_started accepts an optional "source". No serve-behavior change yet — Stage 2 makes shuffle prefer the unplayed tail + resets on exhaustion; Stage 3 wires the clients to send source and consume the rotation-aware order. Tests: rotation appends + dedupes for a system source; source-less play writes no rotation row. (Existing RecordPlayStarted tests are unchanged — same wrapper signature, identical behavior at source="".) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
27 lines
1.3 KiB
SQL
27 lines
1.3 KiB
SQL
-- 0027_system_playlist_rotation.up.sql — sample-history dedup for
|
|
-- system playlists (Fable #415). Two parts:
|
|
--
|
|
-- 1. play_events.source: which system playlist a play originated
|
|
-- from ('for_you' | 'discover' | future kinds), or NULL for any
|
|
-- play not launched from a system-playlist surface (library,
|
|
-- user playlist, radio, Subsonic). Lets the ingest path know a
|
|
-- play should count against that playlist's rotation.
|
|
--
|
|
-- 2. system_playlist_rotation_state: per (user, playlist_kind) set
|
|
-- of track ids already played in the current rotation window.
|
|
-- Shuffle-on-play prefers the unplayed tail; when the snapshot
|
|
-- is exhausted the set resets and rotation_started_at advances.
|
|
-- Centralized server-side so phone / web / watch share one
|
|
-- rotation, mirroring how radio dedup already lives server-side.
|
|
ALTER TABLE play_events
|
|
ADD COLUMN source text;
|
|
|
|
CREATE TABLE system_playlist_rotation_state (
|
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
playlist_kind text NOT NULL,
|
|
played_track_ids uuid[] NOT NULL DEFAULT '{}',
|
|
rotation_started_at timestamptz NOT NULL DEFAULT now(),
|
|
updated_at timestamptz NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (user_id, playlist_kind)
|
|
);
|