@onurege3467/zerohelper
Version:
ZeroHelper is a versatile JavaScript library offering helper functions, validation, logging, database utilities and migration system for developers. It supports MongoDB, MySQL, SQLite, Redis, and PostgreSQL with increment/decrement operations.
228 lines (185 loc) • 6.58 kB
JavaScript
const fs = require('fs');
const path = require('path');
class MigrationManager {
constructor(database, options = {}) {
this.db = database;
this.migrationsDir = options.migrationsDir || './migrations';
this.migrationsTable = options.migrationsTable || 'migrations';
this.ensureMigrationsDir();
}
ensureMigrationsDir() {
if (!fs.existsSync(this.migrationsDir)) {
fs.mkdirSync(this.migrationsDir, { recursive: true });
}
}
async ensureMigrationsTable() {
try {
// Migrations tablosu var mı kontrol et
await this.db.selectOne(this.migrationsTable, {});
} catch (error) {
// Tablo yoksa oluştur
const createTableSQL = `
CREATE TABLE IF NOT EXISTS ${this.migrationsTable} (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name VARCHAR(255) NOT NULL UNIQUE,
executed_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`;
// SQLite, MySQL, PostgreSQL için farklı SQL'ler
if (this.db.constructor.name === 'MySQLDatabase') {
await this.db.query(createTableSQL.replace('AUTOINCREMENT', 'AUTO_INCREMENT'));
} else if (this.db.constructor.name === 'PostgreSQLDatabase') {
const pgSQL = `
CREATE TABLE IF NOT EXISTS ${this.migrationsTable} (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`;
await this.db.query(pgSQL);
} else {
// SQLite
await this.db.query(createTableSQL);
}
}
}
createMigration(name, description = '') {
const timestamp = Date.now();
const filename = `${timestamp}_${name}.js`;
const filePath = path.join(this.migrationsDir, filename);
const template = `// Migration: ${name}
// Description: ${description}
// Created: ${new Date().toISOString()}
module.exports = {
async up(db) {
// Migration işlemlerini buraya yazın
// Örnek:
// await db.query(\`
// CREATE TABLE users (
// id INTEGER PRIMARY KEY AUTOINCREMENT,
// name VARCHAR(255) NOT NULL,
// email VARCHAR(255) UNIQUE NOT NULL
// )
// \`);
console.log('Migration ${name} executed (up)');
},
async down(db) {
// Rollback işlemlerini buraya yazın
// Örnek:
// await db.query('DROP TABLE IF EXISTS users');
console.log('Migration ${name} rolled back (down)');
}
};
`;
fs.writeFileSync(filePath, template);
console.log(`Migration oluşturuldu: ${filename}`);
return filename;
}
getMigrationFiles() {
const files = fs.readdirSync(this.migrationsDir)
.filter(file => file.endsWith('.js'))
.sort();
return files.map(file => ({
name: file,
path: path.join(this.migrationsDir, file)
}));
}
async getExecutedMigrations() {
await this.ensureMigrationsTable();
try {
const executed = await this.db.select(this.migrationsTable, {});
return executed.map(row => row.name);
} catch (error) {
console.error('Executed migrations alınırken hata:', error);
return [];
}
}
async getPendingMigrations() {
const allMigrations = this.getMigrationFiles();
const executedMigrations = await this.getExecutedMigrations();
return allMigrations.filter(migration =>
!executedMigrations.includes(migration.name)
);
}
async runMigration(migrationFile, direction = 'up') {
try {
delete require.cache[require.resolve(migrationFile.path)];
const migration = require(migrationFile.path);
if (typeof migration[direction] !== 'function') {
throw new Error(`Migration ${migrationFile.name} has no ${direction} method`);
}
await migration[direction](this.db);
if (direction === 'up') {
await this.db.insert(this.migrationsTable, {
name: migrationFile.name
});
} else if (direction === 'down') {
await this.db.delete(this.migrationsTable, {
name: migrationFile.name
});
}
console.log(`✅ Migration ${migrationFile.name} (${direction}) başarılı`);
return true;
} catch (error) {
console.error(`❌ Migration ${migrationFile.name} (${direction}) hatası:`, error);
throw error;
}
}
async migrate() {
const pending = await this.getPendingMigrations();
if (pending.length === 0) {
console.log('✅ Tüm migration\'lar güncel');
return;
}
console.log(`🔄 ${pending.length} migration çalıştırılacak...`);
for (const migration of pending) {
await this.runMigration(migration, 'up');
}
console.log('✅ Tüm migration\'lar tamamlandı');
}
async rollback(steps = 1) {
const executed = await this.getExecutedMigrations();
if (executed.length === 0) {
console.log('❌ Rollback yapılacak migration yok');
return;
}
const allMigrations = this.getMigrationFiles();
const toRollback = executed
.slice(-steps)
.reverse()
.map(name => allMigrations.find(m => m.name === name))
.filter(Boolean);
console.log(`🔄 ${toRollback.length} migration rollback edilecek...`);
for (const migration of toRollback) {
await this.runMigration(migration, 'down');
}
console.log('✅ Rollback tamamlandı');
}
async status() {
const allMigrations = this.getMigrationFiles();
const executedMigrations = await this.getExecutedMigrations();
console.log('\n📋 Migration Durumu:');
console.log('==================');
if (allMigrations.length === 0) {
console.log('❌ Migration dosyası bulunamadı');
return;
}
allMigrations.forEach(migration => {
const status = executedMigrations.includes(migration.name) ? '✅ Executed' : '⏳ Pending';
console.log(`${status} ${migration.name}`);
});
const pendingCount = allMigrations.length - executedMigrations.length;
console.log(`\n📊 Toplam: ${allMigrations.length}, Executed: ${executedMigrations.length}, Pending: ${pendingCount}`);
}
async reset() {
const executed = await this.getExecutedMigrations();
if (executed.length === 0) {
console.log('❌ Reset edilecek migration yok');
return;
}
console.log(`🔄 ${executed.length} migration reset edilecek...`);
await this.rollback(executed.length);
console.log('✅ Tüm migration\'lar reset edildi');
}
}
module.exports = MigrationManager;