131 lines
4.2 KiB
JavaScript
131 lines
4.2 KiB
JavaScript
const fs = require('fs');
|
|
const path = require('path');
|
|
const { executeQuery, executeTransaction } = require('../database');
|
|
|
|
/**
|
|
* Run all database migrations
|
|
*/
|
|
async function runMigrations() {
|
|
console.log('🚀 Starting database migrations...');
|
|
|
|
try {
|
|
// Create migrations table to track what's been run
|
|
await executeQuery(`
|
|
CREATE TABLE IF NOT EXISTS migrations (
|
|
id SERIAL PRIMARY KEY,
|
|
filename VARCHAR(255) NOT NULL UNIQUE,
|
|
executed_at BIGINT NOT NULL DEFAULT EXTRACT(epoch FROM NOW()) * 1000
|
|
);
|
|
`);
|
|
|
|
// Get list of executed migrations
|
|
const executedMigrations = await executeQuery(
|
|
'SELECT filename FROM migrations ORDER BY executed_at'
|
|
);
|
|
const executedFiles = new Set(executedMigrations.map(m => m.filename));
|
|
|
|
// Read migration files
|
|
const migrationsDir = path.join(__dirname);
|
|
const migrationFiles = fs.readdirSync(migrationsDir)
|
|
.filter(file => file.endsWith('.sql'))
|
|
.sort();
|
|
|
|
console.log(`📁 Found ${migrationFiles.length} migration files`);
|
|
|
|
for (const file of migrationFiles) {
|
|
if (executedFiles.has(file)) {
|
|
console.log(`⏭️ Skipping ${file} (already executed)`);
|
|
continue;
|
|
}
|
|
|
|
console.log(`🔄 Executing ${file}...`);
|
|
|
|
const filePath = path.join(migrationsDir, file);
|
|
const sql = fs.readFileSync(filePath, 'utf8');
|
|
|
|
await executeTransaction(async (client) => {
|
|
// Execute the migration SQL
|
|
if (process.env.USE_NEON_SERVERLESS === 'true') {
|
|
// For Neon serverless, split by statements and execute individually
|
|
const statements = sql
|
|
.split(';')
|
|
.map(s => s.trim())
|
|
.filter(s => s.length > 0);
|
|
|
|
for (const statement of statements) {
|
|
if (statement.trim()) {
|
|
await client(statement);
|
|
}
|
|
}
|
|
} else {
|
|
await client.query(sql);
|
|
}
|
|
|
|
// Record that this migration was executed
|
|
if (process.env.USE_NEON_SERVERLESS === 'true') {
|
|
await client('INSERT INTO migrations (filename) VALUES ($1)', [file]);
|
|
} else {
|
|
await client.query('INSERT INTO migrations (filename) VALUES ($1)', [file]);
|
|
}
|
|
});
|
|
|
|
console.log(`✅ Completed ${file}`);
|
|
}
|
|
|
|
console.log('🎉 All migrations completed successfully!');
|
|
|
|
} catch (error) {
|
|
console.error('❌ Migration failed:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a new migration file
|
|
* @param {string} name - Migration name
|
|
*/
|
|
function createMigration(name) {
|
|
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0];
|
|
const filename = `${timestamp}_${name}.sql`;
|
|
const filepath = path.join(__dirname, filename);
|
|
|
|
const template = `-- Migration: ${filename}
|
|
-- Description: ${name}
|
|
|
|
-- Add your SQL here
|
|
|
|
`;
|
|
|
|
fs.writeFileSync(filepath, template);
|
|
console.log(`📝 Created migration: ${filename}`);
|
|
return filename;
|
|
}
|
|
|
|
// Run migrations if this file is executed directly
|
|
if (require.main === module) {
|
|
const command = process.argv[2];
|
|
|
|
if (command === 'create') {
|
|
const name = process.argv[3];
|
|
if (!name) {
|
|
console.error('❌ Please provide a migration name: npm run db:migrate create migration_name');
|
|
process.exit(1);
|
|
}
|
|
createMigration(name);
|
|
} else {
|
|
runMigrations()
|
|
.then(() => {
|
|
console.log('🏁 Migration process completed');
|
|
process.exit(0);
|
|
})
|
|
.catch(error => {
|
|
console.error('💥 Migration process failed:', error);
|
|
process.exit(1);
|
|
});
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
runMigrations,
|
|
createMigration
|
|
}; |