afteer enabling dealer on frontend db_password fetch from google sectrets resolved , secret fech db connection order enhanced

This commit is contained in:
laxmanhalaki 2026-02-09 15:18:24 +05:30
parent 798688e4c2
commit 2282d29322
18 changed files with 298 additions and 250 deletions

View File

@ -1,2 +1,2 @@
import{a as s}from"./index-BgkDE8Pi.js";import"./radix-vendor-CYvDqP9X.js";import"./charts-vendor-BVfwAPj-.js";import"./utils-vendor-DNMmNUQL.js";import"./ui-vendor-DyksGUTu.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-B_rK4TXr.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};
//# sourceMappingURL=conclusionApi-VENY18zj.js.map
import{a as s}from"./index-PI_IMErM.js";import"./radix-vendor-CYvDqP9X.js";import"./charts-vendor-BVfwAPj-.js";import"./utils-vendor-DNMmNUQL.js";import"./ui-vendor-DfwWW08H.js";import"./socket-vendor-TjCxX7sJ.js";import"./redux-vendor-tbZCm13o.js";import"./router-vendor-B_rK4TXr.js";async function m(n){return(await s.post(`/conclusions/${n}/generate`)).data.data}async function f(n,t){return(await s.post(`/conclusions/${n}/finalize`,{finalRemark:t})).data.data}async function d(n){var t;try{return(await s.get(`/conclusions/${n}`)).data.data}catch(o){if(((t=o.response)==null?void 0:t.status)===404)return null;throw o}}export{f as finalizeConclusion,m as generateConclusion,d as getConclusion};
//# sourceMappingURL=conclusionApi-D5monZ70.js.map

View File

@ -1 +1 @@
{"version":3,"file":"conclusionApi-VENY18zj.js","sources":["../../src/services/conclusionApi.ts"],"sourcesContent":["import apiClient from './authApi';\r\n\r\nexport interface ConclusionRemark {\r\n conclusionId: string;\r\n requestId: string;\r\n aiGeneratedRemark: string | null;\r\n aiModelUsed: string | null;\r\n aiConfidenceScore: number | null;\r\n finalRemark: string | null;\r\n editedBy: string | null;\r\n isEdited: boolean;\r\n editCount: number;\r\n approvalSummary: any;\r\n documentSummary: any;\r\n keyDiscussionPoints: string[];\r\n generatedAt: string | null;\r\n finalizedAt: string | null;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\n/**\r\n * Generate AI-powered conclusion remark\r\n */\r\nexport async function generateConclusion(requestId: string): Promise<{\r\n conclusionId: string;\r\n aiGeneratedRemark: string;\r\n keyDiscussionPoints: string[];\r\n confidence: number;\r\n generatedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/generate`);\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Update conclusion remark (edit by initiator)\r\n */\r\nexport async function updateConclusion(requestId: string, finalRemark: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.put(`/conclusions/${requestId}`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Finalize conclusion and close request\r\n */\r\nexport async function finalizeConclusion(requestId: string, finalRemark: string): Promise<{\r\n conclusionId: string;\r\n requestNumber: string;\r\n status: string;\r\n finalRemark: string;\r\n finalizedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/finalize`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Get conclusion for a request\r\n * Returns null if conclusion doesn't exist (404) instead of throwing error\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark | null> {\r\n try {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n } catch (error: any) {\r\n // Handle 404 gracefully - conclusion doesn't exist yet, which is normal\r\n if (error.response?.status === 404) {\r\n return null;\r\n }\r\n // Re-throw other errors\r\n throw error;\r\n }\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion","error","_a"],"mappings":"6RAwBA,eAAsBA,EAAmBC,EAMtC,CAED,OADiB,MAAMC,EAAU,KAAK,gBAAgBD,CAAS,WAAW,GAC1D,KAAK,IACvB,CAaA,eAAsBE,EAAmBF,EAAmBG,EAMzD,CAED,OADiB,MAAMF,EAAU,KAAK,gBAAgBD,CAAS,YAAa,CAAE,YAAAG,EAAa,GAC3E,KAAK,IACvB,CAMA,eAAsBC,EAAcJ,EAAqD,OACvF,GAAI,CAEF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB,OAASK,EAAY,CAEnB,KAAIC,EAAAD,EAAM,WAAN,YAAAC,EAAgB,UAAW,IAC7B,OAAO,KAGT,MAAMD,CACR,CACF"}
{"version":3,"file":"conclusionApi-D5monZ70.js","sources":["../../src/services/conclusionApi.ts"],"sourcesContent":["import apiClient from './authApi';\r\n\r\nexport interface ConclusionRemark {\r\n conclusionId: string;\r\n requestId: string;\r\n aiGeneratedRemark: string | null;\r\n aiModelUsed: string | null;\r\n aiConfidenceScore: number | null;\r\n finalRemark: string | null;\r\n editedBy: string | null;\r\n isEdited: boolean;\r\n editCount: number;\r\n approvalSummary: any;\r\n documentSummary: any;\r\n keyDiscussionPoints: string[];\r\n generatedAt: string | null;\r\n finalizedAt: string | null;\r\n createdAt: string;\r\n updatedAt: string;\r\n}\r\n\r\n/**\r\n * Generate AI-powered conclusion remark\r\n */\r\nexport async function generateConclusion(requestId: string): Promise<{\r\n conclusionId: string;\r\n aiGeneratedRemark: string;\r\n keyDiscussionPoints: string[];\r\n confidence: number;\r\n generatedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/generate`);\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Update conclusion remark (edit by initiator)\r\n */\r\nexport async function updateConclusion(requestId: string, finalRemark: string): Promise<ConclusionRemark> {\r\n const response = await apiClient.put(`/conclusions/${requestId}`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Finalize conclusion and close request\r\n */\r\nexport async function finalizeConclusion(requestId: string, finalRemark: string): Promise<{\r\n conclusionId: string;\r\n requestNumber: string;\r\n status: string;\r\n finalRemark: string;\r\n finalizedAt: string;\r\n}> {\r\n const response = await apiClient.post(`/conclusions/${requestId}/finalize`, { finalRemark });\r\n return response.data.data;\r\n}\r\n\r\n/**\r\n * Get conclusion for a request\r\n * Returns null if conclusion doesn't exist (404) instead of throwing error\r\n */\r\nexport async function getConclusion(requestId: string): Promise<ConclusionRemark | null> {\r\n try {\r\n const response = await apiClient.get(`/conclusions/${requestId}`);\r\n return response.data.data;\r\n } catch (error: any) {\r\n // Handle 404 gracefully - conclusion doesn't exist yet, which is normal\r\n if (error.response?.status === 404) {\r\n return null;\r\n }\r\n // Re-throw other errors\r\n throw error;\r\n }\r\n}\r\n\r\n"],"names":["generateConclusion","requestId","apiClient","finalizeConclusion","finalRemark","getConclusion","error","_a"],"mappings":"6RAwBA,eAAsBA,EAAmBC,EAMtC,CAED,OADiB,MAAMC,EAAU,KAAK,gBAAgBD,CAAS,WAAW,GAC1D,KAAK,IACvB,CAaA,eAAsBE,EAAmBF,EAAmBG,EAMzD,CAED,OADiB,MAAMF,EAAU,KAAK,gBAAgBD,CAAS,YAAa,CAAE,YAAAG,EAAa,GAC3E,KAAK,IACvB,CAMA,eAAsBC,EAAcJ,EAAqD,OACvF,GAAI,CAEF,OADiB,MAAMC,EAAU,IAAI,gBAAgBD,CAAS,EAAE,GAChD,KAAK,IACvB,OAASK,EAAY,CAEnB,KAAIC,EAAAD,EAAM,WAAN,YAAAC,EAAgB,UAAW,IAC7B,OAAO,KAGT,MAAMD,CACR,CACF"}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -13,11 +13,11 @@
<!-- Preload critical fonts and icons -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<script type="module" crossorigin src="/assets/index-BgkDE8Pi.js"></script>
<script type="module" crossorigin src="/assets/index-PI_IMErM.js"></script>
<link rel="modulepreload" crossorigin href="/assets/charts-vendor-BVfwAPj-.js">
<link rel="modulepreload" crossorigin href="/assets/radix-vendor-CYvDqP9X.js">
<link rel="modulepreload" crossorigin href="/assets/utils-vendor-DNMmNUQL.js">
<link rel="modulepreload" crossorigin href="/assets/ui-vendor-DyksGUTu.js">
<link rel="modulepreload" crossorigin href="/assets/ui-vendor-DfwWW08H.js">
<link rel="modulepreload" crossorigin href="/assets/socket-vendor-TjCxX7sJ.js">
<link rel="modulepreload" crossorigin href="/assets/redux-vendor-tbZCm13o.js">
<link rel="modulepreload" crossorigin href="/assets/router-vendor-B_rK4TXr.js">

View File

@ -4,7 +4,7 @@
"description": "Royal Enfield Workflow Management System - Backend API (TypeScript)",
"main": "dist/server.js",
"scripts": {
"start": "npm run build && npm run start:prod && npm run setup",
"start": "npm run build && npm run setup && npm run start:prod",
"dev": "npm run 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",

View File

@ -85,18 +85,17 @@ app.use(cookieParser());
const userService = new UserService();
// Initialize database connection
const initializeDatabase = async () => {
// Initializer for database connection (called from server.ts)
export const initializeAppDatabase = async () => {
try {
await sequelize.authenticate();
console.log('✅ App database connection established');
} catch (error) {
console.error('❌ Database connection failed:', error);
console.error('❌ App database connection failed:', error);
throw error;
}
};
// Initialize database
initializeDatabase();
// Trust proxy - Enable this when behind a reverse proxy (nginx, load balancer, etc.)
// This allows Express to read X-Forwarded-* headers correctly
// Set to true in production, false in development

View File

@ -132,10 +132,13 @@ export class AuthController {
// Set new access token in cookie if using cookie-based auth
const isProduction = process.env.NODE_ENV === 'production';
const isUat = process.env.NODE_ENV === 'uat';
const isSecureEnv = isProduction || isUat;
const cookieOptions = {
httpOnly: true,
secure: isProduction,
sameSite: isProduction ? 'lax' as const : 'lax' as const, // 'lax' is safer and works on same-domain
secure: isSecureEnv,
sameSite: isSecureEnv ? 'lax' as const : 'lax' as const, // 'lax' is safer and works on same-domain
maxAge: 24 * 60 * 60 * 1000, // 24 hours
};
@ -206,10 +209,13 @@ export class AuthController {
// Set tokens in httpOnly cookies (production) or return in body (development)
const isProduction = process.env.NODE_ENV === 'production';
const isUat = process.env.NODE_ENV === 'uat';
const isSecureEnv = isProduction || isUat;
const cookieOptions = {
httpOnly: true,
secure: isProduction,
sameSite: isProduction ? ('lax' as const) : ('lax' as const),
secure: isSecureEnv,
sameSite: isSecureEnv ? ('lax' as const) : ('lax' as const),
maxAge: 24 * 60 * 60 * 1000, // 24 hours
path: '/',
};
@ -256,10 +262,13 @@ export class AuthController {
// Set new access token in cookie
const isProduction = process.env.NODE_ENV === 'production';
const isUat = process.env.NODE_ENV === 'uat';
const isSecureEnv = isProduction || isUat;
const cookieOptions = {
httpOnly: true,
secure: isProduction,
sameSite: isProduction ? ('lax' as const) : ('lax' as const),
secure: isSecureEnv,
sameSite: isSecureEnv ? ('lax' as const) : ('lax' as const),
maxAge: 24 * 60 * 60 * 1000,
path: '/',
};
@ -293,13 +302,16 @@ export class AuthController {
// Helper function to clear cookies with all possible option combinations
const clearCookiesCompletely = () => {
const isProduction = process.env.NODE_ENV === 'production';
const isUat = process.env.NODE_ENV === 'uat';
const isSecureEnv = isProduction || isUat;
const cookieNames = ['accessToken', 'refreshToken'];
// Get the EXACT options used when setting cookies (from exchangeToken)
// These MUST match exactly: httpOnly, secure, sameSite, path
const cookieOptions = {
httpOnly: true,
secure: isProduction,
secure: isSecureEnv,
sameSite: 'lax' as const,
path: '/',
};
@ -469,10 +481,13 @@ export class AuthController {
// Set cookies for web clients
const isProduction = process.env.NODE_ENV === 'production';
const isUat = process.env.NODE_ENV === 'uat';
const isSecureEnv = isProduction || isUat;
const cookieOptions = {
httpOnly: true,
secure: isProduction,
sameSite: isProduction ? 'lax' as const : 'lax' as const,
secure: isSecureEnv,
sameSite: isSecureEnv ? 'lax' as const : 'lax' as const,
maxAge: 24 * 60 * 60 * 1000, // 24 hours
};
@ -549,10 +564,13 @@ export class AuthController {
// Set cookies with httpOnly flag for security
const isProduction = process.env.NODE_ENV === 'production';
const isUat = process.env.NODE_ENV === 'uat';
const isSecureEnv = isProduction || isUat;
const cookieOptions = {
httpOnly: true,
secure: isProduction,
sameSite: isProduction ? 'lax' as const : 'lax' as const, // 'lax' for same-domain
secure: isSecureEnv,
sameSite: isSecureEnv ? 'lax' as const : 'lax' as const, // 'lax' for same-domain
maxAge: 24 * 60 * 60 * 1000, // 24 hours for access token
};

View File

@ -10,25 +10,42 @@
* 5. Configs are auto-seeded by configSeed.service.ts on server start (30 configs)
*/
import fs from 'fs';
import path from 'path';
import dotenv from 'dotenv';
// 1. Initialize dotenv FIRST before any other internal imports
// This ensures that modules imported later (like GoogleSecretManagerService)
// can see the environment variables in their constructor/initialization
const envPath = path.resolve(process.cwd(), '.env');
if (fs.existsSync(envPath)) {
dotenv.config({ path: envPath });
} else {
const parentEnvPath = path.resolve(process.cwd(), '..', '.env');
if (fs.existsSync(parentEnvPath)) {
dotenv.config({ path: parentEnvPath });
}
}
// 2. Now import internal modules
import { Client } from 'pg';
import { QueryTypes } from 'sequelize';
import { initializeGoogleSecretManager } from '../services/googleSecretManager.service';
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);
// DB constants moved inside functions to ensure secrets are loaded first
const isSSL = (process.env.DB_SSL || '').trim() === 'true';
async function checkAndCreateDatabase(): Promise<boolean> {
// DB constants moved inside functions to ensure secrets are loaded first
const isSSL = (process.env.DB_SSL || '').trim() === 'true';
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';
const DB_PASSWORD = typeof process.env.DB_PASSWORD === 'string' ? process.env.DB_PASSWORD : '';
const DB_NAME = process.env.DB_NAME || 're_workflow_db';
console.log(`[Setup Debug] DB_HOST: ${DB_HOST}, DB_USER: ${DB_USER}, SSL: ${isSSL}, HasPassword: ${!!DB_PASSWORD}`);
const client = new Client({
host: DB_HOST,

View File

@ -4,13 +4,10 @@ import path from 'path';
// Load environment variables from .env file FIRST
dotenv.config({ path: path.resolve(__dirname, '../.env') });
import { initializeGoogleSecretManager } from './services/googleSecretManager.service';
import { seedDefaultActivityTypes } from './services/activityTypeSeed.service';
// Stop queue metrics collection on shutdown
// Note: This is imported statically but doesn't trigger database/queue activity until called
import { stopQueueMetrics } from './utils/queueMetrics';
// Dynamic imports will be used inside startServer to ensure secrets are loaded first
import { emailService } from './services/email.service';
const PORT: number = parseInt(process.env.PORT || '5000', 10);
// Start server
@ -18,12 +15,16 @@ const startServer = async (): Promise<void> => {
try {
// Initialize Google Secret Manager before starting server
// This will merge secrets from GCS into process.env if enabled
const { initializeGoogleSecretManager } = require('./services/googleSecretManager.service');
console.log('🔐 Initializing secrets...');
await initializeGoogleSecretManager();
// Dynamically import everything else after secrets are loaded
const app = require('./app').default;
const { default: app, initializeAppDatabase } = require('./app');
const { initSocket } = require('./realtime/socket');
// Initialize database connection explicitly after secrets are loaded
await initializeAppDatabase();
require('./queues/tatWorker'); // Initialize TAT worker
const { logTatConfig } = require('./config/tat.config');
const { logSystemConfig } = require('./config/system.config');
@ -34,22 +35,12 @@ const startServer = async (): Promise<void> => {
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)
// This ensures the email service uses production SMTP if credentials are available
// Initialize email service after secrets are loaded
try {
await emailService.initialize();
console.log('📧 Email service re-initialized after secrets loaded');
console.log('📧 Email service initialized');
} catch (error) {
console.warn('⚠️ Email service re-initialization warning (will use test account if SMTP not configured):', error);
}
// 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
try {
await emailService.initialize();
console.log('📧 Email service re-initialized after secrets loaded');
} catch (error) {
console.warn('⚠️ Email service re-initialization warning (will use test account if SMTP not configured):', error);
console.warn('⚠️ Email service initialization warning (will use test account if SMTP not configured):', error);
}
const server = http.createServer(app);
@ -63,6 +54,7 @@ const startServer = async (): Promise<void> => {
}
// Seed default activity types if table is empty
const { seedDefaultActivityTypes } = require('./services/activityTypeSeed.service');
try {
await seedDefaultActivityTypes();
} catch (error) {

View File

@ -264,10 +264,6 @@ export class EmailService {
// Singleton instance
export const emailService = new EmailService();
// Initialize on import (will use test account if SMTP not configured)
// Note: If secrets are loaded later, the service will re-initialize automatically
// when sendEmail is called (if SMTP credentials become available)
emailService.initialize().catch(error => {
logger.error('Failed to initialize email service:', error);
});
// Note: initialize() is now called explicitly in server.ts
// to ensure it happens after secrets are loaded.

View File

@ -30,6 +30,7 @@ class GoogleSecretManagerService {
private secretPrefix: string;
private secretMap: Record<string, string> = {};
private isInitialized: boolean = false;
private isLoaded: boolean = false;
constructor() {
this.projectId = process.env.GCP_PROJECT_ID || '';
@ -206,6 +207,15 @@ class GoogleSecretManagerService {
return;
}
if (this.isLoaded && !secretNames) {
logger.debug('[Secret Manager] Secrets already loaded in this process, skipping.');
return;
}
if (!this.projectId) {
this.projectId = process.env.GCP_PROJECT_ID || '';
}
if (!this.projectId) {
logger.warn('[Secret Manager] GCP_PROJECT_ID not set, skipping Google Secret Manager');
return;
@ -237,33 +247,40 @@ class GoogleSecretManagerService {
}
// Load each secret
for (const secretName of secretsToLoad) {
const fullSecretName = this.secretPrefix
? `${this.secretPrefix}-${secretName}`
: secretName;
const uatSecrets = ['OKTA_CLIENT_ID', 'OKTA_CLIENT_SECRET', 'OKTA_API_TOKEN', 'OKTA_DOMAIN', 'DB_PASSWORD'];
const isUat = process.env.NODE_ENV === 'uat';
// Log OKTA and EMAIL secret attempts in detail
const isOktaSecret = /^OKTA_/i.test(secretName);
const isEmailSecret = /^EMAIL_|^SMTP_/i.test(secretName);
if (isOktaSecret || isEmailSecret) {
logger.info(`[Secret Manager] Attempting to load: ${secretName} (full name: ${fullSecretName})`);
for (const secretName of secretsToLoad) {
// Handle UAT-specific secret names if in UAT environment
let secretNameToFetch = secretName;
if (isUat && uatSecrets.includes(secretName)) {
secretNameToFetch = `${secretName}_UAT`;
logger.info(`[Secret Manager] UAT mode: Fetching source ${secretNameToFetch} for ${secretName}`);
}
const secretValue = await this.getSecret(secretName);
const fullSecretName = this.secretPrefix
? `${this.secretPrefix}-${secretNameToFetch}`
: secretNameToFetch;
const rawValue = await this.getSecret(secretNameToFetch);
const secretValue = rawValue ? rawValue.trim() : null;
if (secretValue !== null) {
const envVarName = this.getEnvVarName(secretName);
loadedSecrets[envVarName] = secretValue;
loadedCount++;
if (isOktaSecret || isEmailSecret) {
logger.info(`[Secret Manager] ✅ Successfully loaded: ${secretName} -> ${envVarName}`);
}
// Print masked value for verification as requested by user
// Note: Trailing/leading whitespace is now trimmed to prevent auth errors
const maskedValue = secretValue.length > 8
? `${secretValue.substring(0, 3)}...${secretValue.substring(secretValue.length - 3)}`
: '***';
logger.info(`[Secret Manager] ✅ Loaded: ${secretNameToFetch} -> process.env.${envVarName} (Value: ${maskedValue})`);
} else {
// Track which secrets weren't found for better logging
notFoundSecrets.push(fullSecretName);
if (isOktaSecret || isEmailSecret) {
logger.warn(`[Secret Manager] ❌ Not found: ${secretName} (searched as: ${fullSecretName})`);
}
logger.warn(`[Secret Manager] ❌ Not found: ${secretNameToFetch} (searched as: ${fullSecretName})`);
}
}
@ -271,18 +288,18 @@ class GoogleSecretManagerService {
// Log when overriding existing env vars vs setting new ones
for (const [envVar, value] of Object.entries(loadedSecrets)) {
const existingValue = process.env[envVar];
const isOverriding = existingValue !== undefined;
const isAlreadySet = existingValue !== undefined && existingValue !== '';
if (isAlreadySet) {
logger.debug(`[Secret Manager] Skipping ${envVar}: Already set in local environment`);
continue;
}
process.env[envVar] = value;
// Log override behavior for debugging
if (isOverriding) {
logger.debug(`[Secret Manager] 🔄 Overrode existing env var: ${envVar} (was: ${existingValue ? 'set' : 'undefined'}, now: from Secret Manager)`);
} else {
logger.debug(`[Secret Manager] ✨ Set new env var: ${envVar} (from Secret Manager)`);
}
logger.debug(`[Secret Manager] ✨ Set new env var: ${envVar} (from Secret Manager)`);
}
this.isLoaded = true;
logger.info(`[Secret Manager] ✅ Successfully loaded ${loadedCount}/${secretsToLoad.length} secrets`);
if (loadedCount > 0) {
@ -345,9 +362,10 @@ class GoogleSecretManagerService {
'SESSION_SECRET',
// Okta/SSO
//'OKTA_CLIENT_ID',
//'OKTA_CLIENT_SECRET',
//'OKTA_API_TOKEN',
'OKTA_CLIENT_ID',
'OKTA_CLIENT_SECRET',
'OKTA_API_TOKEN',
'OKTA_DOMAIN',
// Email
'SMTP_HOST',

View File

@ -124,22 +124,21 @@ if (process.env.LOKI_HOST) {
}
}
// ============ CONSOLE TRANSPORT (Development) ============
if (!isProduction) {
transports.push(
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize(),
winston.format.printf(({ level, message, timestamp, ...meta }) => {
const metaStr = Object.keys(meta).length && !meta.service
? ` ${JSON.stringify(meta)}`
: '';
return `${timestamp} [${level}]: ${message}${metaStr}`;
})
),
})
);
}
// ============ CONSOLE TRANSPORT ============
// Enabled for all environments to ensure visibility in terminal/container logs
transports.push(
new winston.transports.Console({
format: winston.format.combine(
winston.format.colorize({ all: !isProduction }),
winston.format.printf(({ level, message, timestamp, ...meta }) => {
const metaStr = Object.keys(meta).length && !meta.service
? ` ${JSON.stringify(meta)}`
: '';
return `${timestamp} [${level}]: ${message}${metaStr}`;
})
),
})
);
// ============ ERROR SANITIZER ============
/**

View File

@ -2,7 +2,9 @@
"compilerOptions": {
"target": "ES2021",
"module": "commonjs",
"lib": ["ES2021"],
"lib": [
"ES2021"
],
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
@ -21,39 +23,50 @@
"noUnusedParameters": false,
"noImplicitReturns": true,
"noFallthroughCasesInSwitch": true,
"types": ["node", "jest"],
"typeRoots": ["./node_modules/@types", "./src/types"],
"types": [
"node",
"jest"
],
"typeRoots": [
"./node_modules/@types",
"./src/types"
],
"baseUrl": "./src",
"paths": {
"@/*": ["./*"],
"@controllers/*": ["./controllers/*"],
"@middlewares/*": ["./middlewares/*"],
"@services/*": ["./services/*"],
"@models/*": ["./models/*"],
"@routes/*": ["./routes/*"],
"@validators/*": ["./validators/*"],
"@utils/*": ["./utils/*"],
"@types/*": ["./types/*"],
"@config/*": ["./config/*"]
"@/*": [
"./*"
],
"@controllers/*": [
"./controllers/*"
],
"@middlewares/*": [
"./middlewares/*"
],
"@services/*": [
"./services/*"
],
"@models/*": [
"./models/*"
],
"@routes/*": [
"./routes/*"
],
"@validators/*": [
"./validators/*"
],
"@utils/*": [
"./utils/*"
],
"@types/*": [
"./types/*"
],
"@config/*": [
"./config/*"
]
}
},
"include": [
"src/app.ts",
"src/server.ts",
"src/routes/index.ts",
"src/routes/auth.routes.ts",
"src/controllers/auth.controller.ts",
"src/services/auth.service.ts",
"src/middlewares/auth.middleware.ts",
"src/middlewares/cors.middleware.ts",
"src/middlewares/validate.middleware.ts",
"src/middlewares/errorHandler.middleware.ts",
"src/utils/logger.ts",
"src/utils/responseHandler.ts",
"src/config/**/*",
"src/types/**/*",
"src/validators/auth.validator.ts",
"src/models/**/*"
"src/**/*"
],
"exclude": [
"node_modules",
@ -61,10 +74,6 @@
"tests",
"**/*.test.ts",
"**/*.spec.ts",
"src/routes/workflow.routes.ts",
"src/controllers/workflow.controller.ts",
"src/controllers/approval.controller.ts",
"src/services/workflow.service.ts",
"src/services/approval.service.ts"
"src/services/enhancedTemplate.service.ts"
]
}