Files
pipu-music/services/api/migrations/00005_playback.sql
T
mrleo1nidandClaude Opus 5.5 c05e523cbe
ci / api / api (push) Successful in 29s
ci / contract / contract (сгенерированный код совпадает с openapi.yaml) (push) Successful in 35s
ci / ts / ts (packages + web) (push) Failing after 49s
ci / mobile / mobile (Metro-бандл Android) (push) Successful in 1m1s
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>
2026-10-20 07:25:15 +03:00

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;