// Migration: Add flexible Jira ticket creation support // - Drops NOT NULL on cve_id and vendor columns // - Adds source_context column with CHECK constraint // - Backfills existing rows with source_context = 'manual' // - Adds index on source_context // Idempotent — safe to run multiple times. const pool = require('../db'); async function run() { console.log('Starting flexible Jira ticket creation migration...'); // Verify jira_tickets table exists before proceeding const { rows } = await pool.query(` SELECT 1 FROM information_schema.tables WHERE table_schema = 'public' AND table_name = 'jira_tickets' `); if (rows.length === 0) { console.error('✗ jira_tickets table does not exist. Cannot proceed.'); process.exit(1); } console.log('✓ jira_tickets table exists'); // Drop NOT NULL constraint on cve_id (idempotent — no-op if already nullable) await pool.query(`ALTER TABLE jira_tickets ALTER COLUMN cve_id DROP NOT NULL`); console.log('✓ cve_id NOT NULL constraint dropped (or was already nullable)'); // Drop NOT NULL constraint on vendor (idempotent — no-op if already nullable) await pool.query(`ALTER TABLE jira_tickets ALTER COLUMN vendor DROP NOT NULL`); console.log('✓ vendor NOT NULL constraint dropped (or was already nullable)'); // Add jira_id, jira_status, last_synced_at, created_by columns // (originally from SQLite migration add_jira_sync_columns.js — never ported to Postgres run-all) await pool.query(`ALTER TABLE jira_tickets ADD COLUMN IF NOT EXISTS jira_id TEXT`); console.log('✓ jira_id column added (or already exists)'); await pool.query(`ALTER TABLE jira_tickets ADD COLUMN IF NOT EXISTS jira_status TEXT`); console.log('✓ jira_status column added (or already exists)'); await pool.query(`ALTER TABLE jira_tickets ADD COLUMN IF NOT EXISTS last_synced_at TIMESTAMPTZ`); console.log('✓ last_synced_at column added (or already exists)'); await pool.query(`ALTER TABLE jira_tickets ADD COLUMN IF NOT EXISTS created_by INTEGER`); console.log('✓ created_by column added (or already exists)'); await pool.query(`CREATE INDEX IF NOT EXISTS idx_jira_tickets_jira_id ON jira_tickets(jira_id)`); console.log('✓ jira_id index created (or already exists)'); // Add source_context column with default value (IF NOT EXISTS makes it idempotent) await pool.query(` ALTER TABLE jira_tickets ADD COLUMN IF NOT EXISTS source_context TEXT DEFAULT 'manual' `); console.log('✓ source_context column added (or already exists)'); // Add CHECK constraint for allowed source_context values (idempotent guard) await pool.query(` DO $$ BEGIN IF NOT EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'jira_tickets_source_context_check' ) THEN ALTER TABLE jira_tickets ADD CONSTRAINT jira_tickets_source_context_check CHECK (source_context IN ('cve', 'archer', 'ivanti_queue', 'email', 'manual')); END IF; END $$; `); console.log('✓ source_context CHECK constraint added (or already exists)'); // Backfill existing rows where source_context is NULL const result = await pool.query(` UPDATE jira_tickets SET source_context = 'manual' WHERE source_context IS NULL `); console.log(`✓ Backfilled ${result.rowCount} rows with source_context = 'manual'`); // Add index on source_context for filtering performance await pool.query(` CREATE INDEX IF NOT EXISTS idx_jira_tickets_source_context ON jira_tickets(source_context) `); console.log('✓ source_context index created (or already exists)'); console.log('Migration complete.'); process.exit(0); } run().catch(err => { console.error('Migration failed:', err.message); process.exit(1); });