import { pool } from './client.js'; const migrations = [ { name: '001_create_scores_table', sql: ` CREATE TABLE IF NOT EXISTS scores ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), title VARCHAR(255), source_url TEXT, page_count INTEGER DEFAULT 0, data JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_scores_created_at ON scores(created_at DESC); `, }, { name: '002_create_tasks_table', sql: ` CREATE TYPE task_status AS ENUM ('pending', 'running', 'completed', 'failed'); CREATE TYPE task_type AS ENUM ('predict_page', 'predict_all', 'predict_custom'); CREATE TABLE IF NOT EXISTS tasks ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), score_id UUID REFERENCES scores(id) ON DELETE CASCADE, type task_type NOT NULL, status task_status DEFAULT 'pending', progress INTEGER DEFAULT 0, current_step VARCHAR(100), result JSONB, error TEXT, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), started_at TIMESTAMP WITH TIME ZONE, completed_at TIMESTAMP WITH TIME ZONE ); CREATE INDEX IF NOT EXISTS idx_tasks_score_id ON tasks(score_id); CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status); `, }, { name: '003_create_pages_table', sql: ` CREATE TABLE IF NOT EXISTS pages ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), score_id UUID REFERENCES scores(id) ON DELETE CASCADE, page_index INTEGER NOT NULL, image_path TEXT, width INTEGER, height INTEGER, data JSONB DEFAULT '{}', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), UNIQUE(score_id, page_index) ); CREATE INDEX IF NOT EXISTS idx_pages_score_id ON pages(score_id); `, }, { name: '004_create_music_sets_table', sql: ` CREATE TABLE IF NOT EXISTS music_sets ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(255), type VARCHAR(20) DEFAULT 'stave', content JSONB DEFAULT '{}', status VARCHAR(50) DEFAULT 'draft', created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_music_sets_updated_at ON music_sets(updated_at DESC); CREATE INDEX IF NOT EXISTS idx_music_sets_name ON music_sets(name); `, }, { name: '005_create_tags_table', sql: ` CREATE TABLE IF NOT EXISTS tags ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name VARCHAR(100) NOT NULL UNIQUE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS music_set_tags ( music_set_id UUID REFERENCES music_sets(id) ON DELETE CASCADE, tag_id UUID REFERENCES tags(id) ON DELETE CASCADE, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), PRIMARY KEY (music_set_id, tag_id) ); CREATE INDEX IF NOT EXISTS idx_music_set_tags_music_set_id ON music_set_tags(music_set_id); CREATE INDEX IF NOT EXISTS idx_music_set_tags_tag_id ON music_set_tags(tag_id); `, }, { name: '006_create_solution_cache_table', sql: ` CREATE TABLE IF NOT EXISTS solution_cache ( name VARCHAR(64) PRIMARY KEY, value JSONB NOT NULL, priority DOUBLE PRECISION, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); `, }, { name: '007_create_issue_measures_table', sql: ` CREATE TABLE IF NOT EXISTS issue_measures ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), score_id VARCHAR(255) NOT NULL, measure_index INTEGER NOT NULL, measure JSONB NOT NULL, status INTEGER NOT NULL DEFAULT 1, by_user BOOLEAN DEFAULT false, created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(), updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ); CREATE INDEX IF NOT EXISTS idx_issue_measures_score_id ON issue_measures(score_id); CREATE UNIQUE INDEX IF NOT EXISTS idx_issue_measures_score_measure ON issue_measures(score_id, measure_index) WHERE status > 0; `, }, { name: '008_add_annotator_to_issue_measures', sql: ` ALTER TABLE issue_measures ADD COLUMN IF NOT EXISTS annotator VARCHAR(100); `, }, ]; async function runMigrations() { console.log('Running migrations...'); // Create migrations tracking table await pool.query(` CREATE TABLE IF NOT EXISTS migrations ( name VARCHAR(255) PRIMARY KEY, executed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() ) `); for (const migration of migrations) { const { rows } = await pool.query('SELECT name FROM migrations WHERE name = $1', [migration.name]); if (rows.length === 0) { console.log(`Running migration: ${migration.name}`); await pool.query(migration.sql); await pool.query('INSERT INTO migrations (name) VALUES ($1)', [migration.name]); console.log(`Completed: ${migration.name}`); } else { console.log(`Skipping: ${migration.name} (already executed)`); } } console.log('All migrations completed.'); } runMigrations() .then(() => process.exit(0)) .catch((err) => { console.error('Migration failed:', err); process.exit(1); });