← back to Rentv Adintel
db/migrate.js
65 lines
'use strict';
/**
* Migration runner. Applies db/schema.sql (idempotent) plus any numbered
* files in db/migrations/*.sql not yet recorded in schema_migrations.
* Usage: node db/migrate.js [--reset]
* --reset drops and recreates the public schema first (DESTRUCTIVE, local dev only).
*/
const fs = require('fs');
const path = require('path');
const { pool } = require('./index');
async function main() {
const reset = process.argv.includes('--reset');
const client = await pool.connect();
try {
if (reset) {
// eslint-disable-next-line no-console
console.log('[migrate] --reset: dropping public schema');
await client.query('DROP SCHEMA public CASCADE; CREATE SCHEMA public;');
}
const baseSql = fs.readFileSync(path.join(__dirname, 'schema.sql'), 'utf8');
await client.query(baseSql);
console.log('[migrate] base schema applied');
// ensure bookkeeping table exists (schema.sql creates it too)
await client.query(
'CREATE TABLE IF NOT EXISTS schema_migrations (version text PRIMARY KEY, applied_at timestamptz NOT NULL DEFAULT now())'
);
const migDir = path.join(__dirname, 'migrations');
const files = fs.existsSync(migDir)
? fs.readdirSync(migDir).filter((f) => f.endsWith('.sql')).sort()
: [];
for (const f of files) {
const version = f.replace(/\.sql$/, '');
const { rowCount } = await client.query(
'SELECT 1 FROM schema_migrations WHERE version=$1',
[version]
);
if (rowCount) continue;
const sql = fs.readFileSync(path.join(migDir, f), 'utf8');
await client.query('BEGIN');
try {
await client.query(sql);
await client.query('INSERT INTO schema_migrations(version) VALUES ($1)', [version]);
await client.query('COMMIT');
console.log(`[migrate] applied ${f}`);
} catch (e) {
await client.query('ROLLBACK');
throw e;
}
}
console.log('[migrate] done');
} finally {
client.release();
await pool.end();
}
}
main().catch((e) => {
console.error('[migrate] FAILED', e.message);
process.exit(1);
});