db-password from google secrets aligned to server start gogle secrets initialized before migration

This commit is contained in:
laxmanhalaki 2026-01-23 12:51:49 +05:30
parent be220bbb0c
commit 088ac173a7
5 changed files with 77 additions and 62 deletions

View File

@ -4,8 +4,8 @@
"description": "Royal Enfield Workflow Management System - Backend API (TypeScript)", "description": "Royal Enfield Workflow Management System - Backend API (TypeScript)",
"main": "dist/server.js", "main": "dist/server.js",
"scripts": { "scripts": {
"start": "npm run setup && npm run build && npm run start:prod", "start": "npm run build && npm run start:prod && npm run setup",
"dev": "npm run setup && nodemon --exec ts-node -r tsconfig-paths/register src/server.ts", "dev": "nodemon --exec ts-node -r tsconfig-paths/register src/server.ts && npm run setup",
"dev:no-setup": "nodemon --exec ts-node -r tsconfig-paths/register src/server.ts", "dev:no-setup": "nodemon --exec ts-node -r tsconfig-paths/register src/server.ts",
"build": "tsc && tsc-alias", "build": "tsc && tsc-alias",
"build:watch": "tsc --watch", "build:watch": "tsc --watch",

View File

@ -16,17 +16,7 @@ import path from 'path';
// Load environment variables from .env file first // Load environment variables from .env file first
dotenv.config(); dotenv.config();
// Initialize Google Secret Manager (async, but we'll wait for it in server.ts) // Secrets are now initialized in server.ts before app is imported
// This will merge secrets from GCS into process.env if USE_GOOGLE_SECRET_MANAGER=true
// Export initialization function so server.ts can await it before starting
export async function initializeSecrets(): Promise<void> {
try {
await initializeGoogleSecretManager();
} catch (error) {
// Log error but don't throw - allow fallback to .env
console.error('⚠️ Failed to initialize Google Secret Manager, using .env file:', error);
}
}
const app: express.Application = express(); const app: express.Application = express();
const userService = new UserService(); const userService = new UserService();

View File

@ -11,8 +11,8 @@
*/ */
import { Client } from 'pg'; import { Client } from 'pg';
import { sequelize } from '../config/database';
import { QueryTypes } from 'sequelize'; import { QueryTypes } from 'sequelize';
import { initializeGoogleSecretManager } from '../services/googleSecretManager.service';
import { exec } from 'child_process'; import { exec } from 'child_process';
import { promisify } from 'util'; import { promisify } from 'util';
import dotenv from 'dotenv'; import dotenv from 'dotenv';
@ -21,14 +21,15 @@ import path from 'path';
dotenv.config({ path: path.resolve(__dirname, '../../.env') }); dotenv.config({ path: path.resolve(__dirname, '../../.env') });
const execAsync = promisify(exec); const execAsync = promisify(exec);
// DB constants moved inside functions to ensure secrets are loaded first
async function checkAndCreateDatabase(): Promise<boolean> {
const DB_HOST = process.env.DB_HOST || 'localhost'; const DB_HOST = process.env.DB_HOST || 'localhost';
const DB_PORT = parseInt(process.env.DB_PORT || '5432'); const DB_PORT = parseInt(process.env.DB_PORT || '5432');
const DB_USER = process.env.DB_USER || 'postgres'; const DB_USER = process.env.DB_USER || 'postgres';
const DB_PASSWORD = process.env.DB_PASSWORD || ''; const DB_PASSWORD = process.env.DB_PASSWORD || '';
const DB_NAME = process.env.DB_NAME || 'royal_enfield_workflow'; const DB_NAME = process.env.DB_NAME || 'royal_enfield_workflow';
async function checkAndCreateDatabase(): Promise<boolean> {
const client = new Client({ const client = new Client({
host: DB_HOST, host: DB_HOST,
port: DB_PORT, port: DB_PORT,
@ -156,6 +157,8 @@ async function runMigrations(): Promise<void> {
{ name: '20260122-create-workflow-templates', module: m30 }, { name: '20260122-create-workflow-templates', module: m30 },
]; ];
// Dynamically import sequelize after secrets are loaded
const { sequelize } = require('../config/database');
const queryInterface = sequelize.getQueryInterface(); const queryInterface = sequelize.getQueryInterface();
// Ensure migrations tracking table exists // Ensure migrations tracking table exists
@ -171,10 +174,10 @@ async function runMigrations(): Promise<void> {
} }
// Get already executed migrations // Get already executed migrations
const executedResults = await sequelize.query<{ name: string }>( const executedResults = await sequelize.query(
'SELECT name FROM migrations ORDER BY id', 'SELECT name FROM migrations ORDER BY id',
{ type: QueryTypes.SELECT } { type: QueryTypes.SELECT }
); ) as { name: string }[];
const executedMigrations = executedResults.map(r => r.name); const executedMigrations = executedResults.map(r => r.name);
// Find pending migrations // Find pending migrations
@ -222,6 +225,7 @@ async function runMigrations(): Promise<void> {
async function testConnection(): Promise<void> { async function testConnection(): Promise<void> {
try { try {
console.log('🔌 Testing database connection...'); console.log('🔌 Testing database connection...');
const { sequelize } = require('../config/database');
await sequelize.authenticate(); await sequelize.authenticate();
console.log('✅ Database connection established!'); console.log('✅ Database connection established!');
} catch (error: any) { } catch (error: any) {
@ -236,6 +240,10 @@ async function autoSetup(): Promise<void> {
console.log('========================================\n'); console.log('========================================\n');
try { try {
// Step 0: Initialize secrets
console.log('🔐 Initializing secrets...');
await initializeGoogleSecretManager();
// Step 1: Check and create database if needed // Step 1: Check and create database if needed
const wasCreated = await checkAndCreateDatabase(); const wasCreated = await checkAndCreateDatabase();

View File

@ -1,5 +1,5 @@
import { sequelize } from '../config/database';
import { QueryInterface, QueryTypes } from 'sequelize'; import { QueryInterface, QueryTypes } from 'sequelize';
import { initializeGoogleSecretManager } from '../services/googleSecretManager.service';
import * as m0 from '../migrations/2025103000-create-users'; import * as m0 from '../migrations/2025103000-create-users';
import * as m1 from '../migrations/2025103001-create-workflow-requests'; import * as m1 from '../migrations/2025103001-create-workflow-requests';
import * as m2 from '../migrations/2025103002-create-approval-levels'; import * as m2 from '../migrations/2025103002-create-approval-levels';
@ -102,12 +102,12 @@ async function ensureMigrationsTable(queryInterface: QueryInterface): Promise<vo
/** /**
* Get list of already executed migrations * Get list of already executed migrations
*/ */
async function getExecutedMigrations(): Promise<string[]> { async function getExecutedMigrations(sequelize: any): Promise<string[]> {
try { try {
const results = await sequelize.query<{ name: string }>( const results = await sequelize.query(
'SELECT name FROM migrations ORDER BY id', 'SELECT name FROM migrations ORDER BY id',
{ type: QueryTypes.SELECT } { type: QueryTypes.SELECT }
); ) as { name: string }[];
return results.map(r => r.name); return results.map(r => r.name);
} catch (error) { } catch (error) {
// Table might not exist yet // Table might not exist yet
@ -118,7 +118,7 @@ async function getExecutedMigrations(): Promise<string[]> {
/** /**
* Mark migration as executed * Mark migration as executed
*/ */
async function markMigrationExecuted(name: string): Promise<void> { async function markMigrationExecuted(sequelize: any, name: string): Promise<void> {
await sequelize.query( await sequelize.query(
'INSERT INTO migrations (name) VALUES (:name) ON CONFLICT (name) DO NOTHING', 'INSERT INTO migrations (name) VALUES (:name) ON CONFLICT (name) DO NOTHING',
{ {
@ -133,6 +133,12 @@ async function markMigrationExecuted(name: string): Promise<void> {
*/ */
async function run() { async function run() {
try { try {
console.log('🔐 Initializing secrets...');
await initializeGoogleSecretManager();
// Dynamically import sequelize after secrets are loaded
const { sequelize } = require('../config/database');
await sequelize.authenticate(); await sequelize.authenticate();
const queryInterface = sequelize.getQueryInterface(); const queryInterface = sequelize.getQueryInterface();
@ -141,7 +147,7 @@ async function run() {
await ensureMigrationsTable(queryInterface); await ensureMigrationsTable(queryInterface);
// Get already executed migrations // Get already executed migrations
const executedMigrations = await getExecutedMigrations(); const executedMigrations = await getExecutedMigrations(sequelize);
// Find pending migrations // Find pending migrations
const pendingMigrations = migrations.filter( const pendingMigrations = migrations.filter(
@ -160,7 +166,7 @@ async function run() {
for (const migration of pendingMigrations) { for (const migration of pendingMigrations) {
try { try {
await migration.module.up(queryInterface); await migration.module.up(queryInterface);
await markMigrationExecuted(migration.name); await markMigrationExecuted(sequelize, migration.name);
console.log(`${migration.name}`); console.log(`${migration.name}`);
} catch (error: any) { } catch (error: any) {
console.error(`❌ Migration failed: ${migration.name} - ${error.message}`); console.error(`❌ Migration failed: ${migration.name} - ${error.message}`);

View File

@ -1,16 +1,13 @@
import http from 'http'; import http from 'http';
import { initializeSecrets } from './app'; // Import initialization function import dotenv from 'dotenv';
import app from './app'; import path from 'path';
import { initSocket } from './realtime/socket';
import './queues/tatWorker'; // Initialize TAT worker // Load environment variables from .env file FIRST
import { logTatConfig } from './config/tat.config'; dotenv.config({ path: path.resolve(__dirname, '../.env') });
import { logSystemConfig } from './config/system.config'; import { initializeGoogleSecretManager } from './services/googleSecretManager.service';
import { initializeHolidaysCache } from './utils/tatTimeUtils'; import { stopQueueMetrics } from './utils/queueMetrics';
import { seedDefaultConfigurations } from './services/configSeed.service';
import { startPauseResumeJob } from './jobs/pauseResumeJob'; // Dynamic imports will be used inside startServer to ensure secrets are loaded first
import './queues/pauseResumeWorker'; // Initialize pause resume worker
import { initializeQueueMetrics, stopQueueMetrics } from './utils/queueMetrics';
import { emailService } from './services/email.service';
const PORT: number = parseInt(process.env.PORT || '5000', 10); const PORT: number = parseInt(process.env.PORT || '5000', 10);
@ -19,7 +16,21 @@ const startServer = async (): Promise<void> => {
try { try {
// Initialize Google Secret Manager before starting server // Initialize Google Secret Manager before starting server
// This will merge secrets from GCS into process.env if enabled // This will merge secrets from GCS into process.env if enabled
await initializeSecrets(); console.log('🔐 Initializing secrets...');
await initializeGoogleSecretManager();
// Dynamically import everything else after secrets are loaded
const app = require('./app').default;
const { initSocket } = require('./realtime/socket');
require('./queues/tatWorker'); // Initialize TAT worker
const { logTatConfig } = require('./config/tat.config');
const { logSystemConfig } = require('./config/system.config');
const { initializeHolidaysCache } = require('./utils/tatTimeUtils');
const { seedDefaultConfigurations } = require('./services/configSeed.service');
const { startPauseResumeJob } = require('./jobs/pauseResumeJob');
require('./queues/pauseResumeWorker'); // Initialize pause resume worker
const { initializeQueueMetrics } = require('./utils/queueMetrics');
const { emailService } = require('./services/email.service');
// Re-initialize email service after secrets are loaded (in case SMTP credentials were loaded) // Re-initialize email service after secrets are loaded (in case SMTP credentials were loaded)
// This ensures the email service uses production SMTP if credentials are available // This ensures the email service uses production SMTP if credentials are available