helpyourneighbour/backend/migrations/runner.js
OpenClaw 3916dd42bf
Some checks are pending
Docker Test / test (push) Waiting to run
fix(#14): Implement database migrations system with baseline migration
2026-03-06 23:37:39 +00:00

114 lines
No EOL
3.1 KiB
JavaScript

import mysql from 'mysql2';
import fs from 'fs/promises';
import path from 'path';
import { fileURLToPath } from 'url';
import config from './config.js';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Create database connection
const connection = mysql.createConnection(config.connection);
// Ensure migrations table exists
function ensureMigrationsTable() {
const createTableQuery = `
CREATE TABLE IF NOT EXISTS ${config.tableName} (
id INT AUTO_INCREMENT PRIMARY KEY,
name VARCHAR(255) NOT NULL,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`;
return new Promise((resolve, reject) => {
connection.execute(createTableQuery, (err) => {
if (err) reject(err);
else resolve();
});
});
}
// Get list of already executed migrations
function getExecutedMigrations() {
const query = `SELECT name FROM ${config.tableName} ORDER BY executed_at`;
return new Promise((resolve, reject) => {
connection.execute(query, (err, results) => {
if (err) reject(err);
else resolve(results.map(row => row.name));
});
});
}
// Execute a migration file
function executeMigration(migrationName, sqlContent) {
return new Promise((resolve, reject) => {
connection.execute(sqlContent, (err) => {
if (err) reject(err);
else {
// Log the execution in migrations table
const logQuery = `INSERT INTO ${config.tableName} (name) VALUES (?)`;
connection.execute(logQuery, [migrationName], (logErr) => {
if (logErr) reject(logErr);
else resolve();
});
}
});
});
}
// Get all migration files
async function getMigrationFiles() {
try {
const files = await fs.readdir(config.migrationsDir);
return files
.filter(file => file.endsWith('.sql'))
.sort((a, b) => a.localeCompare(b));
} catch (err) {
if (err.code === 'ENOENT') {
// Directory doesn't exist, create it
await fs.mkdir(config.migrationsDir, { recursive: true });
return [];
}
throw err;
}
}
// Run migrations
async function runMigrations() {
try {
await ensureMigrationsTable();
const executed = await getExecutedMigrations();
const allMigrations = await getMigrationFiles();
const pending = allMigrations.filter(name => !executed.includes(name));
if (pending.length === 0) {
console.log('No pending migrations');
return;
}
console.log(`Running ${pending.length} migrations...`);
for (const migrationName of pending) {
console.log(`Executing ${migrationName}`);
const filePath = path.join(config.migrationsDir, migrationName);
const sqlContent = await fs.readFile(filePath, 'utf8');
await executeMigration(migrationName, sqlContent);
}
console.log('All migrations executed successfully');
} catch (err) {
console.error('Error running migrations:', err);
throw err;
} finally {
connection.end();
}
}
// Run the migrations
if (process.argv.includes('--run')) {
runMigrations().catch(console.error);
}
export { runMigrations };