699277be07
Made-with: Cursor
148 lines
4.0 KiB
JavaScript
148 lines
4.0 KiB
JavaScript
/**
|
|
* Database Migration Script
|
|
* Executes SQL migration files in order
|
|
*/
|
|
|
|
import { readFileSync, readdirSync } from 'fs';
|
|
import { join } from 'path';
|
|
import pg from 'pg';
|
|
import { getPoolConfig } from './poolConfig.js';
|
|
|
|
const { Pool } = pg;
|
|
|
|
const MIGRATIONS_DIR = join(process.cwd(), 'src', 'db', 'migrations');
|
|
|
|
/**
|
|
* Get list of migration files sorted by name
|
|
*/
|
|
function getMigrationFiles() {
|
|
const files = readdirSync(MIGRATIONS_DIR)
|
|
.filter((file) => file.endsWith('.sql'))
|
|
.sort();
|
|
|
|
return files;
|
|
}
|
|
|
|
/**
|
|
* Create migrations tracking table if not exists
|
|
*/
|
|
async function ensureMigrationsTable(pool) {
|
|
await pool.query(`
|
|
CREATE TABLE IF NOT EXISTS migrations (
|
|
id SERIAL PRIMARY KEY,
|
|
name VARCHAR(255) NOT NULL UNIQUE,
|
|
executed_at TIMESTAMP DEFAULT NOW()
|
|
)
|
|
`);
|
|
}
|
|
|
|
/**
|
|
* Get list of already executed migrations
|
|
*/
|
|
async function getExecutedMigrations(pool) {
|
|
const result = await pool.query('SELECT name FROM migrations ORDER BY name');
|
|
return result.rows.map((row) => row.name);
|
|
}
|
|
|
|
/**
|
|
* Execute a single migration file
|
|
*/
|
|
async function executeMigration(pool, filename) {
|
|
const filePath = join(MIGRATIONS_DIR, filename);
|
|
const sql = readFileSync(filePath, 'utf-8');
|
|
|
|
console.log(`Executing migration: ${filename}`);
|
|
|
|
await pool.query('BEGIN');
|
|
try {
|
|
await pool.query(sql);
|
|
await pool.query(
|
|
'INSERT INTO migrations (name) VALUES ($1)',
|
|
[filename]
|
|
);
|
|
await pool.query('COMMIT');
|
|
console.log(`✓ Migration ${filename} completed successfully`);
|
|
} catch (error) {
|
|
await pool.query('ROLLBACK');
|
|
console.error(`✗ Migration ${filename} failed:`, error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main migration function
|
|
*/
|
|
function logMigrationError(error) {
|
|
const msg = error?.message || String(error);
|
|
console.error('\n✗ Migration failed:', msg || '(no message)');
|
|
if (error?.code) {
|
|
console.error(' PG code:', error.code);
|
|
}
|
|
if (error?.address && error?.port) {
|
|
console.error(' connect:', `${error.address}:${error.port}`);
|
|
}
|
|
if (error?.name === 'AggregateError' && Array.isArray(error.errors)) {
|
|
for (const e of error.errors) {
|
|
console.error(' —', e?.message || e);
|
|
}
|
|
}
|
|
if (error?.code === 'ECONNREFUSED') {
|
|
console.error(
|
|
' hint: проверьте, что Postgres запущен и DATABASE_URL / DB_* в backend/.env совпадают с портом (часто 5432 для Postgres_TG_Bots или 5433 для локального compose).'
|
|
);
|
|
}
|
|
if (process.env.DEBUG_MIGRATE === '1' || !msg) {
|
|
console.error(error);
|
|
}
|
|
}
|
|
|
|
async function migrate() {
|
|
const pool = new Pool(getPoolConfig());
|
|
|
|
try {
|
|
console.log('Connecting to database...');
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('SELECT 1');
|
|
} finally {
|
|
client.release();
|
|
}
|
|
console.log('Connected to database\n');
|
|
|
|
// Ensure migrations table exists
|
|
await ensureMigrationsTable(pool);
|
|
|
|
// Get migration files and already executed migrations
|
|
const migrationFiles = getMigrationFiles();
|
|
const executedMigrations = await getExecutedMigrations(pool);
|
|
|
|
console.log(`Found ${migrationFiles.length} migration file(s)`);
|
|
console.log(`Already executed: ${executedMigrations.length} migration(s)\n`);
|
|
|
|
// Execute pending migrations
|
|
const pendingMigrations = migrationFiles.filter(
|
|
(file) => !executedMigrations.includes(file)
|
|
);
|
|
|
|
if (pendingMigrations.length === 0) {
|
|
console.log('All migrations already executed.');
|
|
} else {
|
|
console.log(`Pending migrations: ${pendingMigrations.length}\n`);
|
|
|
|
for (const filename of pendingMigrations) {
|
|
await executeMigration(pool, filename);
|
|
}
|
|
|
|
console.log(`\n✓ Successfully executed ${pendingMigrations.length} migration(s)`);
|
|
}
|
|
} catch (error) {
|
|
logMigrationError(error);
|
|
process.exit(1);
|
|
} finally {
|
|
await pool.end();
|
|
}
|
|
}
|
|
|
|
// Run migrations if this script is executed directly
|
|
migrate();
|