170 lines
4.4 KiB
TypeScript
170 lines
4.4 KiB
TypeScript
import { PrismaClient as CentralPrismaClient } from '.prisma/central';
|
|
import knex, { Knex } from 'knex';
|
|
import { createDecipheriv } from 'crypto';
|
|
|
|
// Encryption configuration - must match the one used in tenant service
|
|
const ALGORITHM = 'aes-256-cbc';
|
|
|
|
/**
|
|
* Decrypt a tenant's database password
|
|
*/
|
|
function decryptPassword(encryptedPassword: string): string {
|
|
try {
|
|
// Check if password is already plaintext (for legacy/development)
|
|
if (!encryptedPassword.includes(':')) {
|
|
console.warn('⚠️ Password appears to be unencrypted, using as-is');
|
|
return encryptedPassword;
|
|
}
|
|
|
|
const key = Buffer.from(process.env.ENCRYPTION_KEY, 'hex');
|
|
const parts = encryptedPassword.split(':');
|
|
if (parts.length !== 2) {
|
|
throw new Error('Invalid encrypted password format');
|
|
}
|
|
|
|
const iv = Buffer.from(parts[0], 'hex');
|
|
const encrypted = parts[1];
|
|
|
|
const decipher = createDecipheriv(ALGORITHM, key, iv);
|
|
|
|
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
|
|
decrypted += decipher.final('utf8');
|
|
|
|
return decrypted;
|
|
} catch (error) {
|
|
console.error('Error decrypting password:', error);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a Knex connection for a specific tenant
|
|
*/
|
|
function createTenantKnexConnection(tenant: any): Knex {
|
|
const decryptedPassword = decryptPassword(tenant.dbPassword);
|
|
|
|
// Use Docker hostname 'db' when running inside container
|
|
// The dbHost will be 'db' for Docker connections or 'localhost' for local development
|
|
const dbHost = tenant.dbHost;
|
|
|
|
return knex({
|
|
client: 'mysql2',
|
|
connection: {
|
|
host: dbHost,
|
|
port: tenant.dbPort,
|
|
user: tenant.dbUsername,
|
|
password: decryptedPassword,
|
|
database: tenant.dbName,
|
|
},
|
|
migrations: {
|
|
tableName: 'knex_migrations',
|
|
directory: './migrations/tenant',
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Run migrations for a specific tenant
|
|
*/
|
|
async function migrateTenant(tenant: any): Promise<void> {
|
|
console.log(`\n🔄 Migrating tenant: ${tenant.name} (${tenant.dbName})`);
|
|
|
|
const tenantKnex = createTenantKnexConnection(tenant);
|
|
|
|
try {
|
|
const [batchNo, log] = await tenantKnex.migrate.latest();
|
|
|
|
if (log.length === 0) {
|
|
console.log(`✅ ${tenant.name}: Already up to date`);
|
|
} else {
|
|
console.log(`✅ ${tenant.name}: Ran ${log.length} migrations:`);
|
|
log.forEach((migration) => {
|
|
console.log(` - ${migration}`);
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error(`❌ ${tenant.name}: Migration failed:`, error);
|
|
throw error;
|
|
} finally {
|
|
await tenantKnex.destroy();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Main function to migrate all active tenants
|
|
*/
|
|
async function migrateAllTenants() {
|
|
console.log('🚀 Starting migration for all tenants...\n');
|
|
|
|
const centralPrisma = new CentralPrismaClient();
|
|
|
|
try {
|
|
// Fetch all active tenants
|
|
const tenants = await centralPrisma.tenant.findMany({
|
|
where: {
|
|
status: 'ACTIVE',
|
|
},
|
|
orderBy: {
|
|
name: 'asc',
|
|
},
|
|
});
|
|
|
|
if (tenants.length === 0) {
|
|
console.log('⚠️ No active tenants found.');
|
|
return;
|
|
}
|
|
|
|
console.log(`📋 Found ${tenants.length} active tenant(s)\n`);
|
|
|
|
let successCount = 0;
|
|
let failureCount = 0;
|
|
const failures: { tenant: string; error: string }[] = [];
|
|
|
|
// Migrate each tenant sequentially
|
|
for (const tenant of tenants) {
|
|
try {
|
|
await migrateTenant(tenant);
|
|
successCount++;
|
|
} catch (error) {
|
|
failureCount++;
|
|
failures.push({
|
|
tenant: tenant.name,
|
|
error: error.message,
|
|
});
|
|
}
|
|
}
|
|
|
|
// Print summary
|
|
console.log('\n' + '='.repeat(60));
|
|
console.log('📊 Migration Summary');
|
|
console.log('='.repeat(60));
|
|
console.log(`✅ Successful: ${successCount}`);
|
|
console.log(`❌ Failed: ${failureCount}`);
|
|
|
|
if (failures.length > 0) {
|
|
console.log('\n❌ Failed Tenants:');
|
|
failures.forEach(({ tenant, error }) => {
|
|
console.log(` - ${tenant}: ${error}`);
|
|
});
|
|
process.exit(1);
|
|
} else {
|
|
console.log('\n🎉 All tenant migrations completed successfully!');
|
|
}
|
|
} catch (error) {
|
|
console.error('❌ Fatal error:', error);
|
|
process.exit(1);
|
|
} finally {
|
|
await centralPrisma.$disconnect();
|
|
}
|
|
}
|
|
|
|
// Run the migration
|
|
migrateAllTenants()
|
|
.then(() => {
|
|
process.exit(0);
|
|
})
|
|
.catch((error) => {
|
|
console.error('Unhandled error:', error);
|
|
process.exit(1);
|
|
});
|