db ssl related changes postman submit bug resolved
This commit is contained in:
parent
e03049a861
commit
b925ee5217
@ -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,
|
||||||
|
|||||||
@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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();
|
||||||
|
|||||||
@ -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',
|
||||||
|
|||||||
@ -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');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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 {
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user