- tracks carry a stream_url signed for the user for 12 h and the user's reaction; /stream checks the signature, expiry and that the user is not blocked, then hands the file to nginx by X-Accel-Redirect (or serves it with Range support when X_ACCEL=false) - original by default; formats browsers can't play and explicit high/medium/low go through Opus transcoding, never upwards: a lossy source under the target bitrate stays original; transcodes are cached per file and size, deduplicated in flight, limited by TRANSCODE_CONCURRENCY and trimmed hourly by LRU to the size limit - /events takes up to 500 events with client UUIDs, drops repeats and events older than 30 days, counts plays (≥ half or 4 min, or finished), early skips and history entries (≥ 30 s) - history with clearing, likes and dislikes with cursors and totals, track reactions, playback state with last-write-wins, tracks batch Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
87 lines
3.2 KiB
SQL
87 lines
3.2 KiB
SQL
-- +goose Up
|
|
CREATE TYPE reaction AS ENUM ('like', 'dislike');
|
|
|
|
CREATE TABLE track_reactions (
|
|
user_id uuid REFERENCES users(id) ON DELETE CASCADE,
|
|
track_id uuid REFERENCES tracks(id) ON DELETE CASCADE,
|
|
reaction reaction NOT NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (user_id, track_id)
|
|
);
|
|
CREATE INDEX track_reactions_list_idx ON track_reactions (user_id, reaction, created_at DESC);
|
|
|
|
CREATE TABLE artist_reactions (
|
|
user_id uuid REFERENCES users(id) ON DELETE CASCADE,
|
|
artist_id uuid REFERENCES artists(id) ON DELETE CASCADE,
|
|
reaction reaction NOT NULL,
|
|
created_at timestamptz NOT NULL DEFAULT now(),
|
|
PRIMARY KEY (user_id, artist_id)
|
|
);
|
|
|
|
-- Сырые события. Помесячные партиции добавим, когда объём того потребует; пока всё в DEFAULT.
|
|
CREATE TABLE play_events (
|
|
id uuid NOT NULL,
|
|
user_id uuid NOT NULL,
|
|
track_id uuid NOT NULL,
|
|
type text NOT NULL,
|
|
occurred_at timestamptz NOT NULL,
|
|
received_at timestamptz NOT NULL DEFAULT now(),
|
|
position_ms int,
|
|
played_ms int,
|
|
context_type text,
|
|
context_id text,
|
|
wave_session_id uuid,
|
|
client_type text NOT NULL,
|
|
offline boolean NOT NULL DEFAULT false,
|
|
PRIMARY KEY (id, occurred_at)
|
|
) PARTITION BY RANGE (occurred_at);
|
|
CREATE TABLE play_events_default PARTITION OF play_events DEFAULT;
|
|
CREATE INDEX play_events_user_idx ON play_events (user_id, occurred_at DESC);
|
|
|
|
CREATE TABLE user_track_stats (
|
|
user_id uuid REFERENCES users(id) ON DELETE CASCADE,
|
|
track_id uuid REFERENCES tracks(id) ON DELETE CASCADE,
|
|
play_count int NOT NULL DEFAULT 0,
|
|
complete_count int NOT NULL DEFAULT 0,
|
|
skip_count int NOT NULL DEFAULT 0,
|
|
early_skip_count int NOT NULL DEFAULT 0,
|
|
total_played_ms bigint NOT NULL DEFAULT 0,
|
|
last_played_at timestamptz,
|
|
implicit_score real NOT NULL DEFAULT 0,
|
|
PRIMARY KEY (user_id, track_id)
|
|
);
|
|
|
|
-- История: по строке на прослушивание ≥ 30 с (или коротких треков целиком). id — id события.
|
|
CREATE TABLE listen_history (
|
|
id uuid PRIMARY KEY,
|
|
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
|
track_id uuid NOT NULL REFERENCES tracks(id) ON DELETE CASCADE,
|
|
played_at timestamptz NOT NULL,
|
|
played_ms int NOT NULL,
|
|
hidden boolean NOT NULL DEFAULT false
|
|
);
|
|
CREATE INDEX listen_history_user_idx ON listen_history (user_id, played_at DESC, id DESC) WHERE NOT hidden;
|
|
|
|
CREATE TABLE playback_state (
|
|
user_id uuid PRIMARY KEY REFERENCES users(id) ON DELETE CASCADE,
|
|
track_id uuid REFERENCES tracks(id) ON DELETE SET NULL,
|
|
position_ms int NOT NULL DEFAULT 0,
|
|
queue jsonb NOT NULL DEFAULT '[]',
|
|
queue_index int NOT NULL DEFAULT 0,
|
|
context jsonb,
|
|
shuffle boolean NOT NULL DEFAULT false,
|
|
repeat_mode text NOT NULL DEFAULT 'off',
|
|
volume real NOT NULL DEFAULT 1,
|
|
device_id text,
|
|
updated_at timestamptz NOT NULL DEFAULT now()
|
|
);
|
|
|
|
-- +goose Down
|
|
DROP TABLE playback_state;
|
|
DROP TABLE listen_history;
|
|
DROP TABLE user_track_stats;
|
|
DROP TABLE play_events;
|
|
DROP TABLE artist_reactions;
|
|
DROP TABLE track_reactions;
|
|
DROP TYPE reaction;
|