feat(db): playlists schema for M7 #352 slice 1
Migration 0014 adds playlists + playlist_tracks. track_id is nullable with ON DELETE SET NULL — tracks can be removed from the library without silently dropping playlist entries; the denormalized snapshot (title/artist/album/duration) keeps the row legible afterwards. UI renders such rows greyed-out. Indexes: playlists by (user_id, updated_at DESC) and a partial public index for cross-user discovery; playlist_tracks partial index on track_id to support the FK SET NULL lookup. Queries provide CRUD + rollup recompute (track_count, duration_sec) + append/remove primitives. Reorder is service-layer orchestrated via raw tx.Exec; no SQL primitive needed.
This commit is contained in:
@@ -0,0 +1,2 @@
|
||||
DROP TABLE IF EXISTS playlist_tracks;
|
||||
DROP TABLE IF EXISTS playlists;
|
||||
@@ -0,0 +1,39 @@
|
||||
-- M7 #352 slice 1: playlists CRUD foundation.
|
||||
-- See docs/superpowers/specs/2026-05-03-m7-playlists-crud-design.md.
|
||||
|
||||
CREATE TABLE playlists (
|
||||
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
user_id uuid NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
name text NOT NULL,
|
||||
description text NOT NULL DEFAULT '',
|
||||
is_public boolean NOT NULL DEFAULT false,
|
||||
cover_path text,
|
||||
track_count integer NOT NULL DEFAULT 0,
|
||||
duration_sec integer NOT NULL DEFAULT 0,
|
||||
created_at timestamptz NOT NULL DEFAULT now(),
|
||||
updated_at timestamptz NOT NULL DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE INDEX playlists_user_idx ON playlists (user_id, updated_at DESC);
|
||||
CREATE INDEX playlists_public_idx ON playlists (is_public, updated_at DESC) WHERE is_public;
|
||||
|
||||
-- track_id is nullable + ON DELETE SET NULL so deleting a track from
|
||||
-- the library doesn't silently drop entries from operators' playlists.
|
||||
-- Denormalized title/artist/album/duration carry a snapshot so the row
|
||||
-- is still legible after the upstream track row is gone.
|
||||
CREATE TABLE playlist_tracks (
|
||||
playlist_id uuid NOT NULL REFERENCES playlists(id) ON DELETE CASCADE,
|
||||
position integer NOT NULL,
|
||||
track_id uuid REFERENCES tracks(id) ON DELETE SET NULL,
|
||||
title text NOT NULL,
|
||||
artist_name text NOT NULL,
|
||||
album_title text NOT NULL,
|
||||
duration_sec integer NOT NULL,
|
||||
added_at timestamptz NOT NULL DEFAULT now(),
|
||||
PRIMARY KEY (playlist_id, position)
|
||||
);
|
||||
|
||||
-- Partial index supports the FK lookup that fires when a track is
|
||||
-- deleted (ON DELETE SET NULL) — without it, deleting a track triggers
|
||||
-- a full scan of playlist_tracks.
|
||||
CREATE INDEX playlist_tracks_track_idx ON playlist_tracks (track_id) WHERE track_id IS NOT NULL;
|
||||
Reference in New Issue
Block a user