ALTER TABLE pieces ADD COLUMN is_favorite INTEGER NOT NULL DEFAULT 0 CHECK (is_favorite IN (0, 1)); CREATE TABLE tools ( id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, start_url TEXT NOT NULL DEFAULT '', description TEXT NOT NULL DEFAULT '', is_active INTEGER NOT NULL DEFAULT 1 CHECK (is_active IN (0, 1)), created_at_utc TEXT NOT NULL, updated_at_utc TEXT NOT NULL ); CREATE TABLE tool_formats ( tool_id TEXT NOT NULL REFERENCES tools(id) ON DELETE CASCADE, format_id TEXT NOT NULL REFERENCES formats(id) ON DELETE CASCADE, PRIMARY KEY (tool_id, format_id) ); CREATE TABLE piece_tools ( id TEXT PRIMARY KEY, piece_id TEXT NOT NULL REFERENCES pieces(id) ON DELETE CASCADE, tool_id TEXT NOT NULL REFERENCES tools(id) ON DELETE RESTRICT, work_url TEXT NOT NULL DEFAULT '', note TEXT NOT NULL DEFAULT '', position INTEGER NOT NULL CHECK (position >= 0), UNIQUE (piece_id, tool_id), UNIQUE (piece_id, position) ); CREATE TABLE usage_source_snapshots ( id TEXT PRIMARY KEY, usage_id TEXT NOT NULL REFERENCES piece_usages(id) ON DELETE CASCADE, label TEXT NOT NULL, url TEXT NOT NULL, timecode_seconds INTEGER CHECK (timecode_seconds IS NULL OR timecode_seconds >= 0), position INTEGER NOT NULL CHECK (position >= 0), UNIQUE (usage_id, position) ); CREATE TABLE usage_tool_snapshots ( id TEXT PRIMARY KEY, usage_id TEXT NOT NULL REFERENCES piece_usages(id) ON DELETE CASCADE, original_tool_id TEXT REFERENCES tools(id) ON DELETE SET NULL, name TEXT NOT NULL, start_url TEXT NOT NULL DEFAULT '', work_url TEXT NOT NULL DEFAULT '', note TEXT NOT NULL DEFAULT '', position INTEGER NOT NULL CHECK (position >= 0), UNIQUE (usage_id, position) ); CREATE TABLE usage_topic_snapshots ( usage_id TEXT NOT NULL REFERENCES piece_usages(id) ON DELETE CASCADE, name TEXT NOT NULL, position INTEGER NOT NULL CHECK (position >= 0), PRIMARY KEY (usage_id, position) ); CREATE TABLE live_section_timers ( id TEXT PRIMARY KEY, show_id TEXT NOT NULL REFERENCES shows(id) ON DELETE CASCADE, section_id TEXT NOT NULL REFERENCES show_sections(id) ON DELETE CASCADE, planned_duration_seconds INTEGER NOT NULL CHECK (planned_duration_seconds > 0), started_at_utc TEXT NOT NULL, ended_at_utc TEXT, CHECK (ended_at_utc IS NULL OR ended_at_utc >= started_at_utc) ); CREATE TABLE live_section_timer_runs ( id TEXT PRIMARY KEY, timer_id TEXT NOT NULL REFERENCES live_section_timers(id) ON DELETE CASCADE, started_at_utc TEXT NOT NULL, ended_at_utc TEXT, CHECK (ended_at_utc IS NULL OR ended_at_utc >= started_at_utc) ); CREATE INDEX idx_piece_tools_piece ON piece_tools(piece_id, position); CREATE INDEX idx_tool_formats_format ON tool_formats(format_id, tool_id); CREATE INDEX idx_usage_sources_usage ON usage_source_snapshots(usage_id, position); CREATE INDEX idx_usage_tools_usage ON usage_tool_snapshots(usage_id, position); CREATE UNIQUE INDEX idx_one_open_section_timer_per_show ON live_section_timers(show_id) WHERE ended_at_utc IS NULL; CREATE UNIQUE INDEX idx_one_open_run_per_section_timer ON live_section_timer_runs(timer_id) WHERE ended_at_utc IS NULL; INSERT INTO usage_source_snapshots ( id, usage_id, label, url, timecode_seconds, position ) SELECT lower(hex(randomblob(16))), u.id, ps.label, ps.url, ps.timecode_seconds, ps.position FROM piece_usages u JOIN piece_sources ps ON ps.piece_id = u.original_piece_id; INSERT INTO usage_topic_snapshots (usage_id, name, position) SELECT usage_id, name, position FROM ( SELECT u.id AS usage_id, t.name, row_number() OVER (PARTITION BY u.id ORDER BY t.name, t.id) - 1 AS position FROM piece_usages u JOIN piece_topics pt ON pt.piece_id = u.original_piece_id JOIN topics t ON t.id = pt.topic_id );