import 'dotenv/config'; import { query, close } from './index.js'; const up = ` CREATE TABLE IF NOT EXISTS notes ( id VARCHAR(64) PRIMARY KEY, text TEXT NOT NULL, x INTEGER DEFAULT 0, y INTEGER DEFAULT 0, author VARCHAR(128), color VARCHAR(32) DEFAULT 'yellow', source_meta JSONB DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE IF NOT EXISTS integrations ( id SERIAL PRIMARY KEY, provider VARCHAR(32) NOT NULL, external_workspace_id VARCHAR(128), display_name VARCHAR(255), api_key_hash CHAR(64), access_token_ciphertext TEXT, refresh_token_ciphertext TEXT, signing_secret_ciphertext TEXT, token_expires_at TIMESTAMPTZ, scopes TEXT[] DEFAULT '{}', created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE (provider, external_workspace_id) ); CREATE INDEX IF NOT EXISTS integrations_api_key_hash_idx ON integrations (api_key_hash); -- The unique constraint is the deduplication mechanism: providers deliver -- at least once, so a redelivery must collide rather than insert. CREATE TABLE IF NOT EXISTS ingest_events ( id SERIAL PRIMARY KEY, provider VARCHAR(32) NOT NULL, external_id VARCHAR(255) NOT NULL, integration_id INTEGER REFERENCES integrations(id) ON DELETE SET NULL, status VARCHAR(16) NOT NULL DEFAULT 'pending', attempts INTEGER NOT NULL DEFAULT 0, last_error TEXT, received_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), UNIQUE (provider, external_id) ); CREATE INDEX IF NOT EXISTS ingest_events_status_idx ON ingest_events (status, updated_at); `; const run = async (): Promise => { try { await query(up); console.log('Migration complete — notes, integrations, ingest_events ready.'); } catch (err) { console.error('Migration failed:', err instanceof Error ? err.message : err); process.exit(1); } finally { await close(); } }; run();