Re_Backend/src/scripts/runMigrations.ts

92 lines
4.1 KiB
TypeScript

/**
* Run pending DB migrations. Used by server on startup and by npm run migrate.
* Export runMigrations(sequelize) - does not load secrets or connect; caller must pass connected sequelize.
*/
import { QueryInterface, QueryTypes } from 'sequelize';
import type { Sequelize } from 'sequelize';
import * as m46 from '../migrations/20260216-add-qty-hsn-to-expenses';
import * as m47 from '../migrations/20260217-add-is-service-to-expenses';
import * as m48 from '../migrations/20260217-create-claim-invoice-items';
import * as m49 from '../migrations/20260220-create-form16-tables';
import * as m50 from '../migrations/20260220000001-add-form16-ocr-extracted-data';
import * as m51 from '../migrations/20260222000001-create-tds-26as-entries';
import * as m52 from '../migrations/20260223000001-create-form-16-debit-notes';
import * as m53 from '../migrations/20260224000001-create-form-16-26as-upload-log';
import * as m53a from '../migrations/20260225000001-add-form16-26as-upload-log-id-and-tables';
import * as m54 from '../migrations/20260225000001-create-form16-non-submitted-notifications';
import * as m54a from '../migrations/20260225100001-add-form16-archived-at';
import * as m55 from '../migrations/20260303100001-drop-form16a-number-unique';
import * as m56 from '../migrations/20260302-refine-dealer-claim-schema';
interface Migration {
name: string;
module: any;
}
const migrations: Migration[] = [
{ name: '20260216-add-qty-hsn-to-expenses', module: m46 },
{ name: '20260217-add-is-service-to-expenses', module: m47 },
{ name: '20260217-create-claim-invoice-items', module: m48 },
{ name: '20260220-create-form16-tables', module: m49 },
{ name: '20260220000001-add-form16-ocr-extracted-data', module: m50 },
{ name: '20260222000001-create-tds-26as-entries', module: m51 },
{ name: '20260223000001-create-form-16-debit-notes', module: m52 },
{ name: '20260224000001-create-form-16-26as-upload-log', module: m53 },
{ name: '20260225000001-add-form16-26as-upload-log-id-and-tables', module: m53a },
{ name: '20260225000001-create-form16-non-submitted-notifications', module: m54 },
{ name: '20260225100001-add-form16-archived-at', module: m54a },
{ name: '20260302-refine-dealer-claim-schema', module: m56 },
{ name: '20260303100001-drop-form16a-number-unique', module: m55 },
];
async function ensureMigrationsTable(queryInterface: QueryInterface): Promise<void> {
const tables = await queryInterface.showAllTables();
const tableName = (t: string) => (typeof t === 'string' ? t.toLowerCase() : (t as any));
if (!tables.some((t) => tableName(t) === 'migrations')) {
await queryInterface.sequelize.query(`
CREATE TABLE IF NOT EXISTS migrations (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL UNIQUE,
executed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
`);
}
}
async function getExecutedMigrations(sequelize: Sequelize): Promise<string[]> {
try {
const results = (await sequelize.query('SELECT name FROM migrations ORDER BY id', {
type: QueryTypes.SELECT,
})) as { name: string }[];
return results.map((r) => r.name);
} catch {
return [];
}
}
async function markMigrationExecuted(sequelize: Sequelize, name: string): Promise<void> {
await sequelize.query(
'INSERT INTO migrations (name) VALUES (:name) ON CONFLICT (name) DO NOTHING',
{ replacements: { name }, type: QueryTypes.RAW }
);
}
/**
* Run all pending migrations. Call after DB is connected (e.g. after sequelize.authenticate()).
* @returns Number of migrations applied (0 if already up-to-date).
*/
export async function runMigrations(sequelize: Sequelize): Promise<number> {
const queryInterface = sequelize.getQueryInterface();
await ensureMigrationsTable(queryInterface);
const executedMigrations = await getExecutedMigrations(sequelize);
const pendingMigrations = migrations.filter((m) => !executedMigrations.includes(m.name));
if (pendingMigrations.length === 0) return 0;
for (const migration of pendingMigrations) {
await migration.module.up(queryInterface);
await markMigrationExecuted(sequelize, migration.name);
console.log(`✅ Migration: ${migration.name}`);
}
return pendingMigrations.length;
}