feat(api): streaming, transcoding, events, history and likes (M3)
- 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>
This commit is contained in:
1 parent
8b76aa3964
commit
c05e523cbe
16 files changed
+3913
-155
No files matched your search
@@ -0,0 +1,86 @@
|
||||
-- +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;
|
||||
Reference in new issue
Block a user