-- ClickTrack PostgreSQL schema -- Run via: psql $DATABASE_URL -f lib/db/schema.sql -- Or applied automatically by docker/init.sql on first container start. -- Enable UUID generation CREATE EXTENSION IF NOT EXISTS "pgcrypto"; -- ─── songs ──────────────────────────────────────────────────────────────────── -- Canonical song records, populated from MusicBrainz lookups and/or -- community contributions. mbid is the MusicBrainz Recording UUID. CREATE TABLE IF NOT EXISTS songs ( mbid UUID PRIMARY KEY, title TEXT NOT NULL, artist TEXT NOT NULL, -- Duration in seconds as reported by MusicBrainz duration_seconds NUMERIC(8, 3), -- AcousticBrainz / AcousticBrainz-derived BPM estimate (nullable) acousticbrainz_bpm NUMERIC(6, 2), -- AcousticBrainz time signature numerator (e.g. 4 for 4/4) acousticbrainz_time_sig_num INTEGER, -- How this record was created: 'musicbrainz' | 'manual' | 'registry' source TEXT NOT NULL DEFAULT 'musicbrainz', created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS songs_title_artist_idx ON songs USING gin(to_tsvector('english', title || ' ' || artist)); -- ─── tempo_maps ─────────────────────────────────────────────────────────────── -- Community-contributed CTP documents for a given song. -- Multiple maps per song are allowed; clients pick the highest-voted verified one. CREATE TABLE IF NOT EXISTS tempo_maps ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), song_mbid UUID NOT NULL REFERENCES songs(mbid) ON DELETE CASCADE, -- Full CTP document stored as JSONB for flexible querying ctp_data JSONB NOT NULL, contributed_by TEXT NOT NULL, verified BOOLEAN NOT NULL DEFAULT FALSE, upvotes INTEGER NOT NULL DEFAULT 0, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS tempo_maps_song_mbid_idx ON tempo_maps (song_mbid); CREATE INDEX IF NOT EXISTS tempo_maps_verified_upvotes_idx ON tempo_maps (verified DESC, upvotes DESC); -- Ensure ctp_data contains at least a version field ALTER TABLE tempo_maps ADD CONSTRAINT ctp_data_has_version CHECK (ctp_data ? 'version'); -- ─── registry_sync_log ──────────────────────────────────────────────────────── -- Audit log for Git registry sync operations. CREATE TABLE IF NOT EXISTS registry_sync_log ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), synced_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), records_added INTEGER NOT NULL DEFAULT 0, records_updated INTEGER NOT NULL DEFAULT 0, -- 'success' | 'partial' | 'failed' status TEXT NOT NULL, -- Error message or notes message TEXT ); -- ─── Helpers ────────────────────────────────────────────────────────────────── -- Auto-update updated_at columns via trigger CREATE OR REPLACE FUNCTION set_updated_at() RETURNS TRIGGER LANGUAGE plpgsql AS $$ BEGIN NEW.updated_at = NOW(); RETURN NEW; END; $$; DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_trigger WHERE tgname = 'songs_set_updated_at' ) THEN CREATE TRIGGER songs_set_updated_at BEFORE UPDATE ON songs FOR EACH ROW EXECUTE FUNCTION set_updated_at(); END IF; IF NOT EXISTS ( SELECT 1 FROM pg_trigger WHERE tgname = 'tempo_maps_set_updated_at' ) THEN CREATE TRIGGER tempo_maps_set_updated_at BEFORE UPDATE ON tempo_maps FOR EACH ROW EXECUTE FUNCTION set_updated_at(); END IF; END; $$;