diff --git a/services/mam-api/src/db/migrations/003-editor-sequences.sql b/services/mam-api/src/db/migrations/003-editor-sequences.sql new file mode 100644 index 0000000..91117f5 --- /dev/null +++ b/services/mam-api/src/db/migrations/003-editor-sequences.sql @@ -0,0 +1,74 @@ +-- Wild Dragon MAM – Editor sequences +-- Idempotent: safe to re-run (IF NOT EXISTS / DO $$ BEGIN guards throughout) + +-- Named timelines within a project (multiple per project, like Premiere) +CREATE TABLE IF NOT EXISTS sequences ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + project_id UUID NOT NULL REFERENCES projects ON DELETE CASCADE, + name TEXT NOT NULL DEFAULT 'Sequence 1', + frame_rate NUMERIC(6,3) NOT NULL DEFAULT 59.94, + width INTEGER NOT NULL DEFAULT 1920, + height INTEGER NOT NULL DEFAULT 1080, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_sequences_project_id ON sequences(project_id); +CREATE INDEX IF NOT EXISTS idx_sequences_updated_at ON sequences(updated_at DESC); + +-- Clips placed on a sequence timeline +CREATE TABLE IF NOT EXISTS sequence_clips ( + id UUID PRIMARY KEY DEFAULT uuid_generate_v4(), + sequence_id UUID NOT NULL REFERENCES sequences ON DELETE CASCADE, + asset_id UUID NOT NULL REFERENCES assets ON DELETE CASCADE, + track INTEGER NOT NULL DEFAULT 0 CHECK (track >= 0), + -- track encoding: 0=V1, 1=V2, 100=A1, 101=A2 + timeline_in_frames BIGINT NOT NULL, + timeline_out_frames BIGINT NOT NULL, + source_in_frames BIGINT NOT NULL DEFAULT 0, + source_out_frames BIGINT NOT NULL, + created_at TIMESTAMPTZ DEFAULT NOW(), + updated_at TIMESTAMPTZ DEFAULT NOW() +); + +CREATE INDEX IF NOT EXISTS idx_sequence_clips_sequence_id ON sequence_clips(sequence_id); +CREATE INDEX IF NOT EXISTS idx_sequence_clips_track_position ON sequence_clips(sequence_id, track, timeline_in_frames); +CREATE INDEX IF NOT EXISTS idx_sequence_clips_asset_id ON sequence_clips(asset_id); + +-- Unique sequence name per project +DO $$ BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'uq_sequences_project_name' AND conrelid = 'sequences'::regclass + ) THEN + ALTER TABLE sequences ADD CONSTRAINT uq_sequences_project_name UNIQUE (project_id, name); + END IF; +END $$; + +-- Timeline range constraints +DO $$ BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'chk_timeline_range' AND conrelid = 'sequence_clips'::regclass + ) THEN + ALTER TABLE sequence_clips ADD CONSTRAINT chk_timeline_range CHECK (timeline_out_frames > timeline_in_frames); + END IF; +END $$; + +DO $$ BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'chk_source_range' AND conrelid = 'sequence_clips'::regclass + ) THEN + ALTER TABLE sequence_clips ADD CONSTRAINT chk_source_range CHECK (source_out_frames > source_in_frames); + END IF; +END $$; + +DO $$ BEGIN + IF NOT EXISTS ( + SELECT 1 FROM pg_constraint + WHERE conname = 'chk_track_valid' AND conrelid = 'sequence_clips'::regclass + ) THEN + ALTER TABLE sequence_clips ADD CONSTRAINT chk_track_valid CHECK (track >= 0); + END IF; +END $$;