// Go-live data correction: the earlier migrate-sqlite-to-pg.ts run pulled from local SQLite, // which turned out to be stale/near-empty — the real working data was in a local Postgres // instance instead. This wipes the TARGET (cloud) hq database's public schema and reloads it // from the real SOURCE (local Postgres). Both engines are Postgres here, so every column type // matches exactly — no conversion needed. // SOURCE_DATABASE_URL and TARGET_DATABASE_URL must both be set explicitly, no defaults, so // this can never accidentally point at (or wipe) the wrong database. // // Run from repo root: // SOURCE_DATABASE_URL=postgres://hq:@127.0.0.1:5432/hq ^ // TARGET_DATABASE_URL=postgres://postgres:@13.232.53.115:5432/hq ^ // npx tsx apps/hq/scripts/migrate-pg-to-pg.ts import { Pool } from 'pg' import { openPgDb, type PgDb } from '../src/db-pg' const SKIP_TABLES = new Set(['stg_client', 'stg_invoice', 'schema_migrations']) async function main(): Promise { const sourceUrl = process.env['SOURCE_DATABASE_URL'] const targetUrl = process.env['TARGET_DATABASE_URL'] if (sourceUrl === undefined || sourceUrl === '') throw new Error('Set SOURCE_DATABASE_URL (local Postgres).') if (targetUrl === undefined || targetUrl === '') throw new Error('Set TARGET_DATABASE_URL (cloud Postgres).') if (sourceUrl === targetUrl) throw new Error('SOURCE and TARGET are identical — refusing.') // Wipe the target's public schema completely, then let the app's own migrations rebuild // it clean — guarantees the target schema matches this codebase exactly, not a stale copy. const wipePool = new Pool({ connectionString: targetUrl, max: 1 }) await wipePool.query('DROP SCHEMA public CASCADE') await wipePool.query('CREATE SCHEMA public') await wipePool.end() console.log('Target schema wiped.') const target = (await openPgDb(targetUrl)) as PgDb // recreates schema via migrations, empty const source = (await openPgDb(sourceUrl)) as PgDb // idempotent no-op if already migrated const tables = await source.all<{ tablename: string }>(`SELECT tablename FROM pg_tables WHERE schemaname='public'`) const client = await target.pool.connect() try { await client.query('BEGIN') // Load in discovery order and ignore FK/trigger checks — safe: one transaction into a // target we just wiped and confirmed empty. await client.query('SET LOCAL session_replication_role = replica') for (const { tablename: name } of tables) { if (SKIP_TABLES.has(name)) { console.log(`skip ${name}`) continue } const rows = await source.all>(`SELECT * FROM ${name}`) if (rows.length === 0) { console.log(`${name}: 0 rows`) continue } const cols = Object.keys(rows[0]!) const placeholders = cols.map((_, i) => `$${i + 1}`).join(',') const insertSql = `INSERT INTO ${name} (${cols.join(',')}) VALUES (${placeholders})` for (const row of rows) { await client.query(insertSql, cols.map((c) => row[c])) } console.log(`${name}: ${rows.length} row(s) migrated`) } await client.query('COMMIT') } catch (err) { await client.query('ROLLBACK') throw err } finally { client.release() } console.log('Migration complete.') } main().catch((e: unknown) => { console.error(e) process.exit(1) })