db ssl related changes postman submit bug resolved

This commit is contained in:
laxmanhalaki 2026-02-06 19:53:47 +05:30
parent e03049a861
commit b925ee5217
7 changed files with 250 additions and 198 deletions

View File

@ -3,6 +3,16 @@ import dotenv from 'dotenv';
dotenv.config(); dotenv.config();
// 1. Debugging: Print what the app actually sees
console.log('--- Database Config Debug ---');
console.log(`DB_HOST: ${process.env.DB_HOST}`);
console.log(`DB_SSL (Raw): '${process.env.DB_SSL}`); // Quotes help see trailing spaces
// 2. Fix: Trim whitespace to ensure "true " becomes "true"
const isSSL = (process.env.DB_SSL || '').trim() === 'true';
console.log(`SSL Enabled: ${isSSL}`);
console.log('---------------------------');
const sequelize = new Sequelize({ const sequelize = new Sequelize({
host: process.env.DB_HOST || 'localhost', host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432', 10), port: parseInt(process.env.DB_PORT || '5432', 10),
@ -10,7 +20,7 @@ const sequelize = new Sequelize({
username: process.env.DB_USER || 'postgres', username: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || 'postgres', password: process.env.DB_PASSWORD || 'postgres',
dialect: 'postgres', dialect: 'postgres',
logging: false, // Disable SQL query logging for cleaner console output logging: false,
pool: { pool: {
min: parseInt(process.env.DB_POOL_MIN || '2', 10), min: parseInt(process.env.DB_POOL_MIN || '2', 10),
max: parseInt(process.env.DB_POOL_MAX || '10', 10), max: parseInt(process.env.DB_POOL_MAX || '10', 10),
@ -18,7 +28,8 @@ const sequelize = new Sequelize({
idle: 10000, idle: 10000,
}, },
dialectOptions: { dialectOptions: {
ssl: process.env.DB_SSL === 'true' ? { // 3. Use the robust boolean we calculated above
ssl: isSSL ? {
require: true, require: true,
rejectUnauthorized: false, rejectUnauthorized: false,
} : false, } : false,

View File

@ -236,6 +236,7 @@ export class WorkflowController {
priority: validated.priority as Priority, priority: validated.priority as Priority,
approvalLevels: enrichedApprovalLevels, approvalLevels: enrichedApprovalLevels,
participants: autoGeneratedParticipants, participants: autoGeneratedParticipants,
isDraft: parsed.isDraft === true, // Submit by default unless isDraft is explicitly true
} as any; } as any;
const requestMeta = getRequestMetadata(req); const requestMeta = getRequestMetadata(req);
@ -682,7 +683,10 @@ export class WorkflowController {
} }
const parsed = JSON.parse(raw); const parsed = JSON.parse(raw);
const validated = validateUpdateWorkflow(parsed); const validated = validateUpdateWorkflow(parsed);
const updateData: UpdateWorkflowRequest = { ...validated } as any; const updateData: UpdateWorkflowRequest = {
...validated,
isDraft: parsed.isDraft !== undefined ? (parsed.isDraft === true) : undefined
} as any;
if (validated.priority) { if (validated.priority) {
updateData.priority = validated.priority === 'EXPRESS' ? Priority.EXPRESS : Priority.STANDARD; updateData.priority = validated.priority === 'EXPRESS' ? Priority.EXPRESS : Priority.STANDARD;
} }

View File

@ -22,7 +22,7 @@ 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 // DB constants moved inside functions to ensure secrets are loaded first
const isSSL = (process.env.DB_SSL || '').trim() === 'true';
async function checkAndCreateDatabase(): Promise<boolean> { 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');
@ -36,6 +36,7 @@ async function checkAndCreateDatabase(): Promise<boolean> {
user: DB_USER, user: DB_USER,
password: DB_PASSWORD, password: DB_PASSWORD,
database: 'postgres', // Connect to default postgres database database: 'postgres', // Connect to default postgres database
ssl: isSSL ? { rejectUnauthorized: false } : undefined,
}); });
try { try {
@ -64,6 +65,7 @@ async function checkAndCreateDatabase(): Promise<boolean> {
user: DB_USER, user: DB_USER,
password: DB_PASSWORD, password: DB_PASSWORD,
database: DB_NAME, database: DB_NAME,
ssl: isSSL ? { rejectUnauthorized: false } : undefined,
}); });
await newDbClient.connect(); await newDbClient.connect();

View File

@ -337,7 +337,7 @@ class GoogleSecretManagerService {
private getDefaultSecretNames(): string[] { private getDefaultSecretNames(): string[] {
return [ return [
// Database // Database
//'DB_PASSWORD', 'DB_PASSWORD',
// JWT & Session // JWT & Session
'JWT_SECRET', 'JWT_SECRET',

View File

@ -2450,6 +2450,9 @@ export class WorkflowService {
try { try {
const requestNumber = await generateRequestNumber(); const requestNumber = await generateRequestNumber();
const totalTatHours = workflowData.approvalLevels.reduce((sum, level) => sum + level.tatHours, 0); const totalTatHours = workflowData.approvalLevels.reduce((sum, level) => sum + level.tatHours, 0);
const isDraftRequested = workflowData.isDraft === true;
const initialStatus = isDraftRequested ? WorkflowStatus.DRAFT : WorkflowStatus.PENDING;
const now = new Date();
const workflow = await WorkflowRequest.create({ const workflow = await WorkflowRequest.create({
requestNumber, requestNumber,
@ -2461,9 +2464,10 @@ export class WorkflowService {
currentLevel: 1, currentLevel: 1,
totalLevels: workflowData.approvalLevels.length, totalLevels: workflowData.approvalLevels.length,
totalTatHours, totalTatHours,
status: WorkflowStatus.DRAFT, status: initialStatus,
isDraft: true, isDraft: isDraftRequested,
isDeleted: false isDeleted: false,
submissionDate: isDraftRequested ? undefined : now
}); });
// Create approval levels // Create approval levels
@ -2549,15 +2553,18 @@ export class WorkflowService {
type: 'created', type: 'created',
user: { userId: initiatorId, name: initiatorName }, user: { userId: initiatorId, name: initiatorName },
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
action: 'Initial request submitted', action: isDraftRequested ? 'Draft request created' : 'Initial request submitted',
details: `Initial request submitted for ${workflowData.title} by ${initiatorName}`, details: isDraftRequested
? `Draft request "${workflowData.title}" created by ${initiatorName}`
: `Initial request submitted for ${workflowData.title} by ${initiatorName}`,
ipAddress: requestMetadata?.ipAddress || undefined, ipAddress: requestMetadata?.ipAddress || undefined,
userAgent: requestMetadata?.userAgent || undefined userAgent: requestMetadata?.userAgent || undefined
}); });
// NOTE: Notifications are NOT sent here because workflows are created as DRAFTS // If not a draft, initiate the workflow (approvals, notifications, etc.)
// Notifications will be sent in submitWorkflow() when the draft is actually submitted if (!isDraftRequested) {
// This prevents approvers from being notified about draft requests return await this.internalSubmitWorkflow(workflow, now);
}
return workflow; return workflow;
} catch (error) { } catch (error) {
@ -3112,6 +3119,9 @@ export class WorkflowService {
// Only allow full updates (approval levels, participants) for DRAFT workflows // Only allow full updates (approval levels, participants) for DRAFT workflows
const isDraft = (workflow as any).status === WorkflowStatus.DRAFT || (workflow as any).isDraft; const isDraft = (workflow as any).status === WorkflowStatus.DRAFT || (workflow as any).isDraft;
// Determine if this is a transition from draft to submitted
const isTransitioningToSubmitted = updateData.isDraft === false && (workflow as any).isDraft;
// Update basic workflow fields // Update basic workflow fields
const basicUpdate: any = {}; const basicUpdate: any = {};
if (updateData.title) basicUpdate.title = updateData.title; if (updateData.title) basicUpdate.title = updateData.title;
@ -3119,6 +3129,13 @@ export class WorkflowService {
if (updateData.priority) basicUpdate.priority = updateData.priority; if (updateData.priority) basicUpdate.priority = updateData.priority;
if (updateData.status) basicUpdate.status = updateData.status; if (updateData.status) basicUpdate.status = updateData.status;
if (updateData.conclusionRemark !== undefined) basicUpdate.conclusionRemark = updateData.conclusionRemark; if (updateData.conclusionRemark !== undefined) basicUpdate.conclusionRemark = updateData.conclusionRemark;
if (updateData.isDraft !== undefined) basicUpdate.isDraft = updateData.isDraft;
// If transitioning, ensure status and submissionDate are set
if (isTransitioningToSubmitted) {
basicUpdate.status = WorkflowStatus.PENDING;
basicUpdate.submissionDate = new Date();
}
await workflow.update(basicUpdate); await workflow.update(basicUpdate);
@ -3267,8 +3284,13 @@ export class WorkflowService {
logger.info(`Marked ${deleteResult[0]} documents as deleted in database (out of ${updateData.deleteDocumentIds.length} requested)`); logger.info(`Marked ${deleteResult[0]} documents as deleted in database (out of ${updateData.deleteDocumentIds.length} requested)`);
} }
// Reload the workflow instance to get latest data (without associations to avoid the error) // If transitioning, call the internal submission logic (notifications, TAT, etc.)
// The associations issue occurs when trying to include them, so we skip that if (isTransitioningToSubmitted) {
logger.info(`[Workflow] Transitioning draft ${actualRequestId} to submitted state`);
return await this.internalSubmitWorkflow(workflow, (workflow as any).submissionDate);
}
// Reload the workflow instance to get latest data
const refreshed = await WorkflowRequest.findByPk(actualRequestId); const refreshed = await WorkflowRequest.findByPk(actualRequestId);
return refreshed; return refreshed;
} catch (error) { } catch (error) {
@ -3290,12 +3312,24 @@ export class WorkflowService {
const workflow = await this.findWorkflowByIdentifier(requestId); const workflow = await this.findWorkflowByIdentifier(requestId);
if (!workflow) return null; if (!workflow) return null;
const now = new Date();
return await this.internalSubmitWorkflow(workflow, now);
} catch (error) {
logger.error(`Failed to submit workflow ${requestId}:`, error);
throw new Error('Failed to submit workflow');
}
}
/**
* Internal method to handle workflow submission logic (status update, notifications, TAT scheduling)
* Centralized here to support both direct creation-submission and draft-to-submission flows.
*/
private async internalSubmitWorkflow(workflow: WorkflowRequest, now: Date): Promise<WorkflowRequest> {
// Get the actual requestId (UUID) - handle both UUID and requestNumber cases // Get the actual requestId (UUID) - handle both UUID and requestNumber cases
const actualRequestId = (workflow as any).getDataValue const actualRequestId = (workflow as any).getDataValue
? (workflow as any).getDataValue('requestId') ? (workflow as any).getDataValue('requestId')
: (workflow as any).requestId; : (workflow as any).requestId;
const now = new Date();
const updated = await workflow.update({ const updated = await workflow.update({
status: WorkflowStatus.PENDING, status: WorkflowStatus.PENDING,
isDraft: false, isDraft: false,
@ -3440,10 +3474,7 @@ export class WorkflowService {
logger.error(`[Workflow] Failed to send spectator notifications for request ${requestNumber} (requestId: ${actualRequestId}):`, spectatorError); logger.error(`[Workflow] Failed to send spectator notifications for request ${requestNumber} (requestId: ${actualRequestId}):`, spectatorError);
// Don't fail the submission if spectator notifications fail // Don't fail the submission if spectator notifications fail
} }
return updated; return updated;
} catch (error) {
logger.error(`Failed to submit workflow ${requestId}:`, error);
throw new Error('Failed to submit workflow');
}
} }
} }

View File

@ -29,6 +29,7 @@ export interface CreateWorkflowRequest {
priority: Priority; priority: Priority;
approvalLevels: CreateApprovalLevel[]; approvalLevels: CreateApprovalLevel[];
participants?: CreateParticipant[]; participants?: CreateParticipant[];
isDraft?: boolean;
} }
export interface UpdateWorkflowRequest { export interface UpdateWorkflowRequest {
@ -42,6 +43,7 @@ export interface UpdateWorkflowRequest {
participants?: CreateParticipant[]; participants?: CreateParticipant[];
// Document updates (add new documents via multipart, delete via IDs) // Document updates (add new documents via multipart, delete via IDs)
deleteDocumentIds?: string[]; deleteDocumentIds?: string[];
isDraft?: boolean;
} }
export interface CreateApprovalLevel { export interface CreateApprovalLevel {

View File

@ -44,6 +44,7 @@ export const createWorkflowSchema = z.object({
priorityUi: z.string().optional(), priorityUi: z.string().optional(),
templateId: z.string().optional(), templateId: z.string().optional(),
ccList: z.array(z.any()).optional(), ccList: z.array(z.any()).optional(),
isDraft: z.boolean().optional(),
}); });
export const updateWorkflowSchema = z.object({ export const updateWorkflowSchema = z.object({
@ -73,6 +74,7 @@ export const updateWorkflowSchema = z.object({
notificationEnabled: z.boolean().optional(), notificationEnabled: z.boolean().optional(),
})).optional(), })).optional(),
deleteDocumentIds: z.array(z.string().uuid()).optional(), deleteDocumentIds: z.array(z.string().uuid()).optional(),
isDraft: z.boolean().optional(),
}); });
// Helper to validate UUID or requestNumber format // Helper to validate UUID or requestNumber format