feat(week1): complete foundation - schema, migrations, enhanced SQL, sanitizer
This commit is contained in:
100
src/lib/db/migrate.ts
Normal file
100
src/lib/db/migrate.ts
Normal file
@@ -0,0 +1,100 @@
|
||||
import { pool } from './db';
|
||||
|
||||
/**
|
||||
* Migration System for God Mode
|
||||
* Handles transactional execution of SQL migration files
|
||||
*/
|
||||
|
||||
export interface MigrationResult {
|
||||
success: boolean;
|
||||
migrationsRun: number;
|
||||
error?: string;
|
||||
rolledBack?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Run multiple SQL commands in a single transaction
|
||||
* Automatically rolls back if ANY command fails
|
||||
*/
|
||||
export async function runMigrations(sqlCommands: string[]): Promise<MigrationResult> {
|
||||
const client = await pool.connect();
|
||||
let migrationsRun = 0;
|
||||
|
||||
try {
|
||||
await client.query('BEGIN');
|
||||
console.log('🔱 [Migration] Starting transaction...');
|
||||
|
||||
for (const command of sqlCommands) {
|
||||
// Skip empty commands or comments
|
||||
const trimmed = command.trim();
|
||||
if (!trimmed || trimmed.startsWith('--')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
console.log(`[Migration] Executing: ${trimmed.substring(0, 100)}...`);
|
||||
await client.query(trimmed);
|
||||
migrationsRun++;
|
||||
}
|
||||
|
||||
await client.query('COMMIT');
|
||||
console.log(`✅ [Migration] Successfully committed ${migrationsRun} migrations`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
migrationsRun
|
||||
};
|
||||
|
||||
} catch (error: any) {
|
||||
await client.query('ROLLBACK');
|
||||
console.error('❌ [Migration] Error - Rolling back all changes:', error.message);
|
||||
|
||||
return {
|
||||
success: false,
|
||||
migrationsRun,
|
||||
error: error.message,
|
||||
rolledBack: true
|
||||
};
|
||||
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a single large SQL file (like migrations)
|
||||
* Splits by semicolon and runs each statement in transaction
|
||||
*/
|
||||
export async function runMigrationFile(sqlContent: string): Promise<MigrationResult> {
|
||||
// Split by semicolon, but be smart about it
|
||||
const statements = sqlContent
|
||||
.split(';')
|
||||
.map(s => s.trim())
|
||||
.filter(s => s.length > 0);
|
||||
|
||||
return runMigrations(statements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if migrations have been run
|
||||
*/
|
||||
export async function getMigrationStatus(): Promise<{
|
||||
tables: string[];
|
||||
lastMigration?: Date;
|
||||
}> {
|
||||
try {
|
||||
const result = await pool.query(`
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name
|
||||
`);
|
||||
|
||||
return {
|
||||
tables: result.rows.map(r => r.table_name)
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
tables: []
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user