/** * 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 { 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 { 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 { 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 { 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;