Files
clicktrack/docker/init.sql
AJ Avezzano 5b772655c6 feat: initial scaffold for ClickTrack monorepo
Full self-hosted click track generator for cover bands.

Core technical pieces implemented:
- CTP (Click Track Protocol) TypeScript schema, Zod validator, and WAV
  renderer (44.1 kHz, 16-bit PCM, accented downbeats, ramp sections)
- MusicBrainz API client with 1 req/s rate limiting
- PostgreSQL schema (songs, tempo_maps, registry_sync_log) with triggers
- Git registry sync logic (clone/pull → validate CTP → upsert DB)
- Next.js 14 App Router: search page, track page, API routes
  (/api/songs, /api/tracks, /api/generate)
- UI components: SearchBar, SongResult, TempoMapEditor, ClickTrackPlayer
  (Web Audio API in-browser playback + WAV download)
- Docker Compose stack: app + postgres + redis + nginx + registry-sync
- Multi-stage Dockerfile with standalone Next.js output
- .env.example documenting all configuration variables
- README with setup instructions, CTP format spec, and API reference

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 11:14:46 -04:00

76 lines
3.4 KiB
PL/PgSQL

-- init.sql
-- Executed by PostgreSQL on first container start.
-- The full schema is copied here so it runs automatically via
-- docker-entrypoint-initdb.d without requiring a separate psql call.
-- Enable UUID generation
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
-- ─── songs ────────────────────────────────────────────────────────────────────
CREATE TABLE IF NOT EXISTS songs (
mbid UUID PRIMARY KEY,
title TEXT NOT NULL,
artist TEXT NOT NULL,
duration_seconds NUMERIC(8, 3),
acousticbrainz_bpm NUMERIC(6, 2),
acousticbrainz_time_sig_num INTEGER,
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 ───────────────────────────────────────────────────────────────
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,
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);
ALTER TABLE tempo_maps
ADD CONSTRAINT ctp_data_has_version
CHECK (ctp_data ? 'version');
-- ─── registry_sync_log ────────────────────────────────────────────────────────
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,
status TEXT NOT NULL,
message TEXT
);
-- ─── Triggers ─────────────────────────────────────────────────────────────────
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;
$$;