Files
brazil-swift-ops/apps/api/src/db/migrate.ts
defiQUG 4f637ede8c Implement Phase 1a, 1b, 1c: Database, Auth, and API Endpoints
Phase 1a - Database Setup:
- Add PostgreSQL connection pooling with pg client
- Create 8 SQL migrations for all database schemas
- Implement migration execution system with tracking
- Add environment configuration for database and JWT settings

Phase 1b - Authentication & Authorization:
- Implement password hashing with bcrypt
- Create JWT token generation (access + refresh tokens)
- Implement RBAC with 5 roles (Admin, Manager, Analyst, Auditor, Viewer)
- Create auth middleware for authentication and authorization
- Add auth routes (login, register, refresh, logout, profile)

Phase 1c - API Endpoints (Full CRUD):
- Transaction endpoints with evaluation and batch processing
- Account management (treasury and subledger accounts)
- User management (admin-only)
- FX contract management
- Compliance endpoints (rules, results, thresholds)
- Reporting endpoints (summary, compliance, audit logs)
- Health check endpoints with database status

Phase 1d - Data Seeding:
- Create database seeding system with roles, permissions, users
- Add sample data (treasury accounts, FX contracts)
- Implement admin user creation from environment variables

All endpoints protected with authentication and role-based access control.
2026-01-23 18:48:59 -08:00

130 lines
3.4 KiB
TypeScript

/**
* Database Migration System
* Handles reading and executing SQL migrations
*/
import { readdir, readFile } from 'fs/promises';
import { join } from 'path';
import { query } from './connection';
/**
* Run all pending migrations
*/
export async function runMigrations(): Promise<void> {
console.log('Starting database migrations...');
try {
// Ensure schema_migrations table exists
await query(`
CREATE TABLE IF NOT EXISTS schema_migrations (
version INTEGER PRIMARY KEY,
name VARCHAR(255) NOT NULL,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
// Get all migration files
const migrationsDir = join(__dirname, 'migrations');
const files = await readdir(migrationsDir);
const migrationFiles = files
.filter((f) => f.endsWith('.sql'))
.sort();
if (migrationFiles.length === 0) {
console.log('No migration files found');
return;
}
// Get executed migrations
const executedResult = await query<{ version: number }>(
'SELECT version FROM schema_migrations ORDER BY version'
);
const executedVersions = new Set(executedResult.rows.map((r) => r.version));
// Execute pending migrations
let executedCount = 0;
for (const file of migrationFiles) {
const match = file.match(/^(\d+)_/);
if (!match) continue;
const version = parseInt(match[1], 10);
if (executedVersions.has(version)) {
console.log(`✓ Migration ${version} already executed`);
continue;
}
try {
const filePath = join(migrationsDir, file);
const sqlContent = await readFile(filePath, 'utf-8');
// Split into individual statements (simple approach)
const statements = sqlContent
.split(';')
.map((s) => s.trim())
.filter((s) => s && !s.startsWith('--'));
for (const statement of statements) {
await query(statement + ';');
}
console.log(`✓ Migration ${version} executed: ${file}`);
executedCount++;
} catch (error) {
console.error(`✗ Migration ${version} failed:`, error);
throw error;
}
}
console.log(
`✓ Database migrations completed (${executedCount} new migrations executed)`
);
} catch (error) {
console.error('Migration failed:', error);
throw error;
}
}
/**
* Get migration status
*/
export async function getMigrationStatus(): Promise<{
executed: number;
total: number;
pending: string[];
}> {
try {
const migrationsDir = join(__dirname, 'migrations');
const files = await readdir(migrationsDir);
const migrationFiles = files.filter((f) => f.endsWith('.sql')).sort();
const executedResult = await query<{ version: number; name: string }>(
'SELECT version, name FROM schema_migrations ORDER BY version'
);
const executedVersions = new Set(executedResult.rows.map((r) => r.version));
const pending: string[] = [];
for (const file of migrationFiles) {
const match = file.match(/^(\d+)_/);
if (!match) continue;
const version = parseInt(match[1], 10);
if (!executedVersions.has(version)) {
pending.push(file);
}
}
return {
executed: executedResult.rows.length,
total: migrationFiles.length,
pending,
};
} catch (error) {
console.error('Failed to get migration status:', error);
return {
executed: 0,
total: 0,
pending: [],
};
}
}