-- +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;