Apply Composer changes: comprehensive API updates, migrations, middleware, and infrastructure improvements
- Add comprehensive database migrations (001-024) for schema evolution - Enhance API schema with expanded type definitions and resolvers - Add new middleware: audit logging, rate limiting, MFA enforcement, security, tenant auth - Implement new services: AI optimization, billing, blockchain, compliance, marketplace - Add adapter layer for cloud integrations (Cloudflare, Kubernetes, Proxmox, storage) - Update Crossplane provider with enhanced VM management capabilities - Add comprehensive test suite for API endpoints and services - Update frontend components with improved GraphQL subscriptions and real-time updates - Enhance security configurations and headers (CSP, CORS, etc.) - Update documentation and configuration files - Add new CI/CD workflows and validation scripts - Implement design system improvements and UI enhancements
This commit is contained in:
163
api/src/db/migrate.ts
Normal file
163
api/src/db/migrate.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import 'dotenv/config'
|
||||
import { Pool } from 'pg'
|
||||
import { readdir } from 'fs/promises'
|
||||
import { join, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { getDb } from './index.js'
|
||||
import { logger } from '../lib/logger.js'
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
export interface Migration {
|
||||
up: (db: Pool) => Promise<void>
|
||||
down: (db: Pool) => Promise<void>
|
||||
}
|
||||
|
||||
const MIGRATIONS_TABLE = 'schema_migrations'
|
||||
|
||||
async function ensureMigrationsTable(db: Pool): Promise<void> {
|
||||
await db.query(`
|
||||
CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
|
||||
version VARCHAR(255) PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL,
|
||||
executed_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
)
|
||||
`)
|
||||
}
|
||||
|
||||
async function getExecutedMigrations(db: Pool): Promise<string[]> {
|
||||
await ensureMigrationsTable(db)
|
||||
const result = await db.query(`SELECT version FROM ${MIGRATIONS_TABLE} ORDER BY version`)
|
||||
return result.rows.map((row) => row.version)
|
||||
}
|
||||
|
||||
async function recordMigration(db: Pool, version: string, name: string): Promise<void> {
|
||||
await db.query(
|
||||
`INSERT INTO ${MIGRATIONS_TABLE} (version, name) VALUES ($1, $2) ON CONFLICT (version) DO NOTHING`,
|
||||
[version, name]
|
||||
)
|
||||
}
|
||||
|
||||
async function removeMigration(db: Pool, version: string): Promise<void> {
|
||||
await db.query(`DELETE FROM ${MIGRATIONS_TABLE} WHERE version = $1`, [version])
|
||||
}
|
||||
|
||||
async function loadMigration(version: string): Promise<Migration> {
|
||||
const migrationPath = join(__dirname, 'migrations', `${version}.ts`)
|
||||
try {
|
||||
const migration = await import(migrationPath)
|
||||
return {
|
||||
up: migration.up,
|
||||
down: migration.down,
|
||||
}
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to load migration ${version}: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function getAllMigrations(): Promise<string[]> {
|
||||
const migrationsDir = join(__dirname, 'migrations')
|
||||
const files = await readdir(migrationsDir)
|
||||
return files
|
||||
.filter((file) => file.endsWith('.ts') && file !== 'index.ts')
|
||||
.map((file) => file.replace('.ts', ''))
|
||||
.sort()
|
||||
}
|
||||
|
||||
async function migrateUp(db: Pool): Promise<void> {
|
||||
await ensureMigrationsTable(db)
|
||||
const executed = await getExecutedMigrations(db)
|
||||
const allMigrations = await getAllMigrations()
|
||||
const pending = allMigrations.filter((m) => !executed.includes(m))
|
||||
|
||||
logger.info(`Found ${pending.length} pending migrations`)
|
||||
|
||||
for (const version of pending) {
|
||||
logger.info(`Running migration ${version}...`)
|
||||
const migration = await loadMigration(version)
|
||||
await migration.up(db)
|
||||
const name = version.replace(/^\d+_/, '').replace(/_/g, ' ')
|
||||
await recordMigration(db, version, name)
|
||||
logger.info(`✓ Migration ${version} completed`)
|
||||
}
|
||||
|
||||
if (pending.length === 0) {
|
||||
logger.info('No pending migrations')
|
||||
}
|
||||
}
|
||||
|
||||
async function migrateDown(db: Pool, targetVersion?: string): Promise<void> {
|
||||
await ensureMigrationsTable(db)
|
||||
const executed = await getExecutedMigrations(db)
|
||||
|
||||
if (executed.length === 0) {
|
||||
logger.info('No migrations to roll back')
|
||||
return
|
||||
}
|
||||
|
||||
const toRollback = targetVersion
|
||||
? executed.slice(executed.indexOf(targetVersion) + 1).reverse()
|
||||
: [executed[executed.length - 1]]
|
||||
|
||||
for (const version of toRollback) {
|
||||
logger.info(`Rolling back migration ${version}...`)
|
||||
const migration = await loadMigration(version)
|
||||
await migration.down(db)
|
||||
await removeMigration(db, version)
|
||||
logger.info(`✓ Migration ${version} rolled back`)
|
||||
}
|
||||
}
|
||||
|
||||
async function showStatus(db: Pool): Promise<void> {
|
||||
await ensureMigrationsTable(db)
|
||||
const executed = await getExecutedMigrations(db)
|
||||
const allMigrations = await getAllMigrations()
|
||||
|
||||
logger.info('\nMigration Status:')
|
||||
logger.info('================\n')
|
||||
|
||||
for (const migration of allMigrations) {
|
||||
const status = executed.includes(migration) ? '✓' : '✗'
|
||||
const name = migration.replace(/^\d+_/, '').replace(/_/g, ' ')
|
||||
logger.info(`${status} ${migration} - ${name}`)
|
||||
}
|
||||
|
||||
logger.info(`\nTotal: ${executed.length}/${allMigrations.length} executed\n`)
|
||||
}
|
||||
|
||||
async function main(): Promise<void> {
|
||||
const command = process.argv[2]
|
||||
const db = getDb()
|
||||
|
||||
try {
|
||||
switch (command) {
|
||||
case 'up':
|
||||
await migrateUp(db)
|
||||
break
|
||||
case 'down':
|
||||
const targetVersion = process.argv[3]
|
||||
await migrateDown(db, targetVersion)
|
||||
break
|
||||
case 'status':
|
||||
await showStatus(db)
|
||||
break
|
||||
default:
|
||||
logger.info('Usage: npm run db:migrate [up|down|status]')
|
||||
logger.info(' up - Run all pending migrations')
|
||||
logger.info(' down - Roll back the last migration')
|
||||
logger.info(' status - Show migration status')
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error('Migration error', { error })
|
||||
process.exit(1)
|
||||
} finally {
|
||||
await db.end()
|
||||
}
|
||||
}
|
||||
|
||||
if (import.meta.url === `file://${process.argv[1]}`) {
|
||||
main()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user