/** * Restore a data-only backup (INSERT dumps from npm run db:backup) into a database. * * Dev: node scripts/restore-db-data.js --file db/DataBackup/gallery_dev_data_....txt * Prod: npm run db:restore:prod -- --file db/DataBackup/gallery_dev_data_....txt */ const fs = require('fs'); const path = require('path'); const pg = require('pg'); const { assertProdDatabase, assertDevDatabase, confirmProdAction, loadDevPgConfig, loadProdPgConfig, } = require('./db-env'); const { Client } = pg; function parseArgs(argv) { const fileIdx = argv.indexOf('--file'); if (fileIdx === -1 || !argv[fileIdx + 1]) { throw new Error('--file is required'); } return { filePath: path.resolve(argv[fileIdx + 1]), prod: argv.includes('--prod'), }; } function extractInsertStatements(content) { return content .split(/\r?\n/) .map((line) => line.trim()) .filter((line) => line.startsWith('INSERT INTO ')); } async function truncatePublicTables(client) { const tablesRes = await client.query( `SELECT table_name FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name`, ); const names = tablesRes.rows.map((r) => `"${r.table_name}"`).join(', '); if (!names) return; await client.query(`TRUNCATE TABLE ${names} RESTART IDENTITY CASCADE`); } async function main() { const { filePath, prod } = parseArgs(process.argv.slice(2)); if (!fs.existsSync(filePath)) { throw new Error(`Backup file not found: ${filePath}`); } const config = prod ? loadProdPgConfig() : loadDevPgConfig(); const dbName = config.database; const inserts = extractInsertStatements(fs.readFileSync(filePath, 'utf8')); if (inserts.length === 0) { throw new Error('No INSERT statements found in backup file'); } const prompt = prod ? `RESTORE ${inserts.length} rows into PRODUCTION "${dbName}" from:\n ${filePath}\nThis TRUNCATES all public tables first.` : `Restore ${inserts.length} rows into "${dbName}" from:\n ${filePath}\nThis TRUNCATES all public tables first.`; if (prod) { assertProdDatabase(dbName); } else { assertDevDatabase(dbName); } await confirmProdAction(prompt); const client = new Client(config); await client.connect(); console.log(`Truncating public tables in "${dbName}"...`); await truncatePublicTables(client); console.log(`Restoring ${inserts.length} INSERT statements...`); await client.query('SET session_replication_role = replica'); let restored = 0; try { for (const statement of inserts) { await client.query(statement); restored += 1; if (restored % 500 === 0) { console.log(` ${restored}/${inserts.length}`); } } } finally { await client.query('SET session_replication_role = DEFAULT'); } await client.end(); console.log(`Restore complete: ${restored} statements into "${dbName}".`); } main().catch((error) => { console.error(error); process.exit(1); });