You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

126 lines
3.2 KiB

/**
* Database Migration Script
* Executes SQL migration files in order
*/
import { readFileSync, readdirSync } from 'fs';
import { join } from 'path';
import pg from 'pg';
const { Pool } = pg;
// Database configuration
const dbConfig = {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5433', 10),
database: process.env.DB_NAME || 'clinic_tests',
user: process.env.DB_USER || 'developer',
password: process.env.DB_PASSWORD || 'dev_password',
};
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
*/
async function migrate() {
const pool = new Pool(dbConfig);
try {
console.log('Connecting to database...');
await pool.connect();
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) {
console.error('\n✗ Migration failed:', error.message);
process.exit(1);
} finally {
await pool.end();
}
}
// Run migrations if this script is executed directly
migrate();