import { PrismaClient as CentralPrismaClient } from '.prisma/central'; import knex, { Knex } from 'knex'; import { createDecipheriv } from 'crypto'; // Encryption configuration 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); return knex({ client: 'mysql2', connection: { host: tenant.dbHost, port: tenant.dbPort, user: tenant.dbUsername, password: decryptedPassword, database: tenant.dbName, }, migrations: { tableName: 'knex_migrations', directory: './migrations/tenant', }, }); } /** * Migrate a specific tenant by slug or ID */ async function migrateTenant() { const tenantIdentifier = process.argv[2]; if (!tenantIdentifier) { console.error('āŒ Usage: npm run migrate:tenant '); process.exit(1); } console.log(`šŸ” Looking for tenant: ${tenantIdentifier}\n`); const centralPrisma = new CentralPrismaClient(); try { // Find tenant by slug or ID const tenant = await centralPrisma.tenant.findFirst({ where: { OR: [ { slug: tenantIdentifier }, { id: tenantIdentifier }, ], }, }); if (!tenant) { console.error(`āŒ Tenant not found: ${tenantIdentifier}`); process.exit(1); } console.log(`šŸ“‹ Tenant: ${tenant.name} (${tenant.slug})`); console.log(`šŸ“Š Database: ${tenant.dbName}`); console.log(`šŸ”„ Running migrations...\n`); const tenantKnex = createTenantKnexConnection(tenant); try { const [batchNo, log] = await tenantKnex.migrate.latest(); if (log.length === 0) { console.log(`āœ… Already up to date (batch ${batchNo})`); } else { console.log(`āœ… Ran ${log.length} migration(s) (batch ${batchNo}):`); log.forEach((migration) => { console.log(` - ${migration}`); }); } console.log('\nšŸŽ‰ Migration completed successfully!'); } catch (error) { console.error('āŒ Migration failed:', error.message); throw error; } finally { await tenantKnex.destroy(); } } catch (error) { console.error('āŒ Fatal error:', error); process.exit(1); } finally { await centralPrisma.$disconnect(); } } // Run the migration migrateTenant() .then(() => { process.exit(0); }) .catch((error) => { console.error('Unhandled error:', error); process.exit(1); });