313 lines
13 KiB
TypeScript
313 lines
13 KiB
TypeScript
/**
|
|
* Automatic Database Setup Script
|
|
* Runs before server starts to ensure database is ready
|
|
*
|
|
* This script:
|
|
* 1. Checks if database exists
|
|
* 2. Creates database if missing
|
|
* 3. Installs required extensions
|
|
* 4. Runs all pending migrations (checks migrations table to avoid re-running)
|
|
* 5. Configs are auto-seeded by configSeed.service.ts on server start (30 configs)
|
|
*/
|
|
|
|
import { Client } from 'pg';
|
|
import { sequelize } from '../config/database';
|
|
import { QueryTypes } from 'sequelize';
|
|
import { exec } from 'child_process';
|
|
import { promisify } from 'util';
|
|
import dotenv from 'dotenv';
|
|
import path from 'path';
|
|
|
|
dotenv.config({ path: path.resolve(__dirname, '../../.env') });
|
|
|
|
const execAsync = promisify(exec);
|
|
|
|
const DB_HOST = process.env.DB_HOST || 'localhost';
|
|
const DB_PORT = parseInt(process.env.DB_PORT || '5432');
|
|
const DB_USER = process.env.DB_USER || 'postgres';
|
|
const DB_PASSWORD = process.env.DB_PASSWORD || '';
|
|
const DB_NAME = process.env.DB_NAME || 'royal_enfield_workflow';
|
|
|
|
async function checkAndCreateDatabase(): Promise<boolean> {
|
|
const client = new Client({
|
|
host: DB_HOST,
|
|
port: DB_PORT,
|
|
user: DB_USER,
|
|
password: DB_PASSWORD,
|
|
database: 'postgres', // Connect to default postgres database
|
|
});
|
|
|
|
try {
|
|
await client.connect();
|
|
console.log('🔍 Checking if database exists...');
|
|
|
|
// Check if database exists
|
|
const result = await client.query(
|
|
`SELECT 1 FROM pg_database WHERE datname = $1`,
|
|
[DB_NAME]
|
|
);
|
|
|
|
if (result.rows.length === 0) {
|
|
console.log(`📦 Database '${DB_NAME}' not found. Creating...`);
|
|
|
|
// Create database
|
|
await client.query(`CREATE DATABASE "${DB_NAME}"`);
|
|
console.log(`✅ Database '${DB_NAME}' created successfully!`);
|
|
|
|
await client.end();
|
|
|
|
// Connect to new database and install extensions
|
|
const newDbClient = new Client({
|
|
host: DB_HOST,
|
|
port: DB_PORT,
|
|
user: DB_USER,
|
|
password: DB_PASSWORD,
|
|
database: DB_NAME,
|
|
});
|
|
|
|
await newDbClient.connect();
|
|
console.log('📦 Installing uuid-ossp extension...');
|
|
await newDbClient.query('CREATE EXTENSION IF NOT EXISTS "uuid-ossp"');
|
|
console.log('✅ Extension installed!');
|
|
await newDbClient.end();
|
|
|
|
return true; // Database was created
|
|
} else {
|
|
console.log(`✅ Database '${DB_NAME}' already exists.`);
|
|
await client.end();
|
|
return false; // Database already existed
|
|
}
|
|
} catch (error: any) {
|
|
console.error('❌ Database check/creation failed:', error.message);
|
|
await client.end();
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function runMigrations(): Promise<void> {
|
|
try {
|
|
console.log('🔄 Checking and running pending migrations...');
|
|
|
|
// Import all migrations using require for CommonJS compatibility
|
|
// Some migrations use module.exports, others use export
|
|
const m0 = require('../migrations/2025103000-create-users');
|
|
const m1 = require('../migrations/2025103001-create-workflow-requests');
|
|
const m2 = require('../migrations/2025103002-create-approval-levels');
|
|
const m3 = require('../migrations/2025103003-create-participants');
|
|
const m4 = require('../migrations/2025103004-create-documents');
|
|
const m5 = require('../migrations/20251031_01_create_subscriptions');
|
|
const m6 = require('../migrations/20251031_02_create_activities');
|
|
const m7 = require('../migrations/20251031_03_create_work_notes');
|
|
const m8 = require('../migrations/20251031_04_create_work_note_attachments');
|
|
const m9 = require('../migrations/20251104-add-tat-alert-fields');
|
|
const m10 = require('../migrations/20251104-create-tat-alerts');
|
|
const m11 = require('../migrations/20251104-create-kpi-views');
|
|
const m12 = require('../migrations/20251104-create-holidays');
|
|
const m13 = require('../migrations/20251104-create-admin-config');
|
|
const m14 = require('../migrations/20251105-add-skip-fields-to-approval-levels');
|
|
const m15 = require('../migrations/2025110501-alter-tat-days-to-generated');
|
|
const m16 = require('../migrations/20251111-create-notifications');
|
|
const m17 = require('../migrations/20251111-create-conclusion-remarks');
|
|
const m18 = require('../migrations/20251118-add-breach-reason-to-approval-levels');
|
|
const m19 = require('../migrations/20251121-add-ai-model-configs');
|
|
const m20 = require('../migrations/20250122-create-request-summaries');
|
|
const m21 = require('../migrations/20250122-create-shared-summaries');
|
|
const m22 = require('../migrations/20250123-update-request-number-format');
|
|
const m23 = require('../migrations/20250126-add-paused-to-enum');
|
|
const m24 = require('../migrations/20250126-add-paused-to-workflow-status-enum');
|
|
const m25 = require('../migrations/20250126-add-pause-fields-to-workflow-requests');
|
|
const m26 = require('../migrations/20250126-add-pause-fields-to-approval-levels');
|
|
const m27 = require('../migrations/20250127-migrate-in-progress-to-pending');
|
|
// Base branch migrations (m28-m29)
|
|
const m28 = require('../migrations/20250130-migrate-to-vertex-ai');
|
|
const m29 = require('../migrations/20251203-add-user-notification-preferences');
|
|
// Dealer claim branch migrations (m30-m39)
|
|
const m30 = require('../migrations/20251210-add-workflow-type-support');
|
|
const m31 = require('../migrations/20251210-enhance-workflow-templates');
|
|
const m32 = require('../migrations/20251210-add-template-id-foreign-key');
|
|
const m33 = require('../migrations/20251210-create-dealer-claim-tables');
|
|
const m34 = require('../migrations/20251210-create-proposal-cost-items-table');
|
|
const m35 = require('../migrations/20251211-create-internal-orders-table');
|
|
const m36 = require('../migrations/20251211-create-claim-budget-tracking-table');
|
|
const m37 = require('../migrations/20251213-drop-claim-details-invoice-columns');
|
|
const m38 = require('../migrations/20251213-create-claim-invoice-credit-note-tables');
|
|
const m39 = require('../migrations/20251214-create-dealer-completion-expenses');
|
|
const m40 = require('../migrations/20251218-fix-claim-invoice-credit-note-columns');
|
|
const m41 = require('../migrations/20250120-create-dealers-table');
|
|
|
|
const migrations = [
|
|
{ name: '2025103000-create-users', module: m0 },
|
|
{ name: '2025103001-create-workflow-requests', module: m1 },
|
|
{ name: '2025103002-create-approval-levels', module: m2 },
|
|
{ name: '2025103003-create-participants', module: m3 },
|
|
{ name: '2025103004-create-documents', module: m4 },
|
|
{ name: '20251031_01_create_subscriptions', module: m5 },
|
|
{ name: '20251031_02_create_activities', module: m6 },
|
|
{ name: '20251031_03_create_work_notes', module: m7 },
|
|
{ name: '20251031_04_create_work_note_attachments', module: m8 },
|
|
{ name: '20251104-add-tat-alert-fields', module: m9 },
|
|
{ name: '20251104-create-tat-alerts', module: m10 },
|
|
{ name: '20251104-create-kpi-views', module: m11 },
|
|
{ name: '20251104-create-holidays', module: m12 },
|
|
{ name: '20251104-create-admin-config', module: m13 },
|
|
{ name: '20251105-add-skip-fields-to-approval-levels', module: m14 },
|
|
{ name: '2025110501-alter-tat-days-to-generated', module: m15 },
|
|
{ name: '20251111-create-notifications', module: m16 },
|
|
{ name: '20251111-create-conclusion-remarks', module: m17 },
|
|
{ name: '20251118-add-breach-reason-to-approval-levels', module: m18 },
|
|
{ name: '20251121-add-ai-model-configs', module: m19 },
|
|
{ name: '20250122-create-request-summaries', module: m20 },
|
|
{ name: '20250122-create-shared-summaries', module: m21 },
|
|
{ name: '20250123-update-request-number-format', module: m22 },
|
|
{ name: '20250126-add-paused-to-enum', module: m23 },
|
|
{ name: '20250126-add-paused-to-workflow-status-enum', module: m24 },
|
|
{ name: '20250126-add-pause-fields-to-workflow-requests', module: m25 },
|
|
{ name: '20250126-add-pause-fields-to-approval-levels', module: m26 },
|
|
{ name: '20250127-migrate-in-progress-to-pending', module: m27 },
|
|
// Base branch migrations (m28-m29)
|
|
{ name: '20250130-migrate-to-vertex-ai', module: m28 },
|
|
{ name: '20251203-add-user-notification-preferences', module: m29 },
|
|
// Dealer claim branch migrations (m30-m39)
|
|
{ name: '20251210-add-workflow-type-support', module: m30 },
|
|
{ name: '20251210-enhance-workflow-templates', module: m31 },
|
|
{ name: '20251210-add-template-id-foreign-key', module: m32 },
|
|
{ name: '20251210-create-dealer-claim-tables', module: m33 },
|
|
{ name: '20251210-create-proposal-cost-items-table', module: m34 },
|
|
{ name: '20251211-create-internal-orders-table', module: m35 },
|
|
{ name: '20251211-create-claim-budget-tracking-table', module: m36 },
|
|
{ name: '20251213-drop-claim-details-invoice-columns', module: m37 },
|
|
{ name: '20251213-create-claim-invoice-credit-note-tables', module: m38 },
|
|
{ name: '20251214-create-dealer-completion-expenses', module: m39 },
|
|
{ name: '20251218-fix-claim-invoice-credit-note-columns', module: m40 },
|
|
{ name: '20250120-create-dealers-table', module: m41 },
|
|
];
|
|
|
|
const queryInterface = sequelize.getQueryInterface();
|
|
|
|
// Ensure migrations tracking table exists
|
|
const tables = await queryInterface.showAllTables();
|
|
if (!tables.includes('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
|
|
)
|
|
`);
|
|
}
|
|
|
|
// Get already executed migrations
|
|
const executedResults = await sequelize.query<{ name: string }>(
|
|
'SELECT name FROM migrations ORDER BY id',
|
|
{ type: QueryTypes.SELECT }
|
|
);
|
|
const executedMigrations = executedResults.map(r => r.name);
|
|
|
|
// Find pending migrations
|
|
const pendingMigrations = migrations.filter(
|
|
m => !executedMigrations.includes(m.name)
|
|
);
|
|
|
|
if (pendingMigrations.length === 0) {
|
|
console.log('✅ Migrations up-to-date');
|
|
return;
|
|
}
|
|
|
|
console.log(`🔄 Running ${pendingMigrations.length} pending migration(s)...`);
|
|
|
|
// Run each pending migration
|
|
for (const migration of pendingMigrations) {
|
|
try {
|
|
console.log(` → ${migration.name}`);
|
|
|
|
// Call the up function - works for both module.exports and export styles
|
|
await migration.module.up(queryInterface);
|
|
|
|
// Mark as executed
|
|
await sequelize.query(
|
|
'INSERT INTO migrations (name) VALUES (:name) ON CONFLICT (name) DO NOTHING',
|
|
{
|
|
replacements: { name: migration.name },
|
|
type: QueryTypes.INSERT
|
|
}
|
|
);
|
|
console.log(` ✅ ${migration.name}`);
|
|
} catch (error: any) {
|
|
console.error(` ❌ ${migration.name} failed: ${error.message}`);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
console.log(`✅ Applied ${pendingMigrations.length} migration(s)`);
|
|
} catch (error: any) {
|
|
console.error('❌ Migration failed:', error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function testConnection(): Promise<void> {
|
|
try {
|
|
console.log('🔌 Testing database connection...');
|
|
await sequelize.authenticate();
|
|
console.log('✅ Database connection established!');
|
|
} catch (error: any) {
|
|
console.error('❌ Unable to connect to database:', error.message);
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
async function autoSetup(): Promise<void> {
|
|
console.log('\n========================================');
|
|
console.log('🚀 Royal Enfield Workflow - Auto Setup');
|
|
console.log('========================================\n');
|
|
|
|
try {
|
|
// Step 1: Check and create database if needed
|
|
const wasCreated = await checkAndCreateDatabase();
|
|
|
|
// Step 2: Test connection
|
|
await testConnection();
|
|
|
|
// Step 3: Run migrations (always, to catch any pending migrations)
|
|
await runMigrations();
|
|
|
|
console.log('\n========================================');
|
|
console.log('✅ Setup completed successfully!');
|
|
console.log('========================================\n');
|
|
|
|
console.log('📝 Note: Admin configurations will be auto-seeded on server start if table is empty.');
|
|
console.log('📝 Note: Dealers table will be empty - import dealers using CSV import script.\n');
|
|
|
|
if (wasCreated) {
|
|
console.log('💡 Next steps:');
|
|
console.log(' 1. Server will start automatically');
|
|
console.log(' 2. Log in via SSO');
|
|
console.log(' 3. Run this SQL to make yourself admin:');
|
|
console.log(` UPDATE users SET role = 'ADMIN' WHERE email = 'your-email@royalenfield.com';\n`);
|
|
}
|
|
|
|
} catch (error: any) {
|
|
console.error('\n========================================');
|
|
console.error('❌ Setup failed!');
|
|
console.error('========================================');
|
|
console.error('Error:', error.message);
|
|
console.error('\nPlease check:');
|
|
console.error('1. PostgreSQL is running');
|
|
console.error('2. DB credentials in .env are correct');
|
|
console.error('3. User has permission to create databases\n');
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
// Run if called directly
|
|
if (require.main === module) {
|
|
autoSetup().then(() => {
|
|
process.exit(0);
|
|
}).catch(() => {
|
|
process.exit(1);
|
|
});
|
|
}
|
|
|
|
export default autoSetup;
|
|
|