import { Request, Response } from 'express'; import { WorkflowService } from '@services/workflow.service'; import { validateCreateWorkflow, validateUpdateWorkflow } from '@validators/workflow.validator'; import { ResponseHandler } from '@utils/responseHandler'; import type { AuthenticatedRequest } from '../types/express'; import { Priority } from '../types/common.types'; import type { UpdateWorkflowRequest } from '../types/workflow.types'; import { Document } from '@models/Document'; import fs from 'fs'; import path from 'path'; import crypto from 'crypto'; const workflowService = new WorkflowService(); export class WorkflowController { async createWorkflow(req: AuthenticatedRequest, res: Response): Promise { try { const validatedData = validateCreateWorkflow(req.body); // Convert string literal priority to enum const workflowData = { ...validatedData, priority: validatedData.priority as Priority }; const workflow = await workflowService.createWorkflow(req.user.userId, workflowData); ResponseHandler.success(res, workflow, 'Workflow created successfully', 201); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; ResponseHandler.error(res, 'Failed to create workflow', 400, errorMessage); } } // Multipart create: accepts payload JSON and files[] async createWorkflowMultipart(req: AuthenticatedRequest, res: Response): Promise { try { const userId = req.user?.userId; if (!userId) { ResponseHandler.error(res, 'Unauthorized', 401); return; } const raw = String(req.body?.payload || ''); if (!raw) { ResponseHandler.error(res, 'payload is required', 400); return; } const parsed = JSON.parse(raw); const validated = validateCreateWorkflow(parsed); const workflowData = { ...validated, priority: validated.priority as Priority } as any; const workflow = await workflowService.createWorkflow(userId, workflowData); // Attach files as documents (category defaults to SUPPORTING) const files = (req as any).files as Express.Multer.File[] | undefined; const category = (req.body?.category as string) || 'OTHER'; const docs: any[] = []; if (files && files.length > 0) { for (const file of files) { const buffer = fs.readFileSync(file.path); const checksum = crypto.createHash('sha256').update(buffer).digest('hex'); const extension = path.extname(file.originalname).replace('.', '').toLowerCase(); const doc = await Document.create({ requestId: workflow.requestId, uploadedBy: userId, fileName: path.basename(file.filename || file.originalname), originalFileName: file.originalname, fileType: extension, fileExtension: extension, fileSize: file.size, filePath: file.path, storageUrl: `/uploads/${path.basename(file.path)}`, mimeType: file.mimetype, checksum, isGoogleDoc: false, googleDocUrl: null as any, category: category || 'OTHER', version: 1, parentDocumentId: null as any, isDeleted: false, downloadCount: 0, } as any); docs.push(doc); } } ResponseHandler.success(res, { requestId: workflow.requestId, documents: docs }, 'Workflow created with documents', 201); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; ResponseHandler.error(res, 'Failed to create workflow', 400, errorMessage); } } async getWorkflow(req: Request, res: Response): Promise { try { const { id } = req.params; const workflow = await workflowService.getWorkflowById(id); if (!workflow) { ResponseHandler.notFound(res, 'Workflow not found'); return; } ResponseHandler.success(res, workflow, 'Workflow retrieved successfully'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; ResponseHandler.error(res, 'Failed to get workflow', 500, errorMessage); } } async getWorkflowDetails(req: Request, res: Response): Promise { try { const { id } = req.params as any; const result = await workflowService.getWorkflowDetails(id); if (!result) { ResponseHandler.notFound(res, 'Workflow not found'); return; } ResponseHandler.success(res, result, 'Workflow details fetched'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; ResponseHandler.error(res, 'Failed to fetch workflow details', 500, errorMessage); } } async listWorkflows(req: Request, res: Response): Promise { try { const page = Math.max(parseInt(String(req.query.page || '1'), 10), 1); const limit = Math.min(Math.max(parseInt(String(req.query.limit || '20'), 10), 1), 100); const result = await workflowService.listWorkflows(page, limit); ResponseHandler.success(res, result, 'Workflows fetched'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; ResponseHandler.error(res, 'Failed to list workflows', 500, errorMessage); } } async listMyRequests(req: Request, res: Response): Promise { try { const userId = (req as any).user?.userId || (req as any).user?.id || (req as any).auth?.userId; const page = Math.max(parseInt(String(req.query.page || '1'), 10), 1); const limit = Math.min(Math.max(parseInt(String(req.query.limit || '20'), 10), 1), 100); const result = await workflowService.listMyRequests(userId, page, limit); ResponseHandler.success(res, result, 'My requests fetched'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; ResponseHandler.error(res, 'Failed to fetch my requests', 500, errorMessage); } } async listOpenForMe(req: Request, res: Response): Promise { try { const userId = (req as any).user?.userId || (req as any).user?.id || (req as any).auth?.userId; const page = Math.max(parseInt(String(req.query.page || '1'), 10), 1); const limit = Math.min(Math.max(parseInt(String(req.query.limit || '20'), 10), 1), 100); const result = await workflowService.listOpenForMe(userId, page, limit); ResponseHandler.success(res, result, 'Open requests for user fetched'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; ResponseHandler.error(res, 'Failed to fetch open requests for user', 500, errorMessage); } } async listClosedByMe(req: Request, res: Response): Promise { try { const userId = (req as any).user?.userId || (req as any).user?.id || (req as any).auth?.userId; const page = Math.max(parseInt(String(req.query.page || '1'), 10), 1); const limit = Math.min(Math.max(parseInt(String(req.query.limit || '20'), 10), 1), 100); const result = await workflowService.listClosedByMe(userId, page, limit); ResponseHandler.success(res, result, 'Closed requests by user fetched'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; ResponseHandler.error(res, 'Failed to fetch closed requests by user', 500, errorMessage); } } async updateWorkflow(req: Request, res: Response): Promise { try { const { id } = req.params; const validatedData = validateUpdateWorkflow(req.body); // Build a strongly-typed payload for the service layer const updateData: UpdateWorkflowRequest = { ...validatedData } as any; if (validatedData.priority) { // Map string literal to enum value explicitly updateData.priority = validatedData.priority === 'EXPRESS' ? Priority.EXPRESS : Priority.STANDARD; } const workflow = await workflowService.updateWorkflow(id, updateData); if (!workflow) { ResponseHandler.notFound(res, 'Workflow not found'); return; } ResponseHandler.success(res, workflow, 'Workflow updated successfully'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; ResponseHandler.error(res, 'Failed to update workflow', 400, errorMessage); } } // Multipart update for drafts: accepts payload JSON and files[] async updateWorkflowMultipart(req: AuthenticatedRequest, res: Response): Promise { try { const userId = req.user?.userId; if (!userId) { ResponseHandler.error(res, 'Unauthorized', 401); return; } const { id } = req.params; const raw = String(req.body?.payload || ''); if (!raw) { ResponseHandler.error(res, 'payload is required', 400); return; } const parsed = JSON.parse(raw); const validated = validateUpdateWorkflow(parsed); const updateData: UpdateWorkflowRequest = { ...validated } as any; if (validated.priority) { updateData.priority = validated.priority === 'EXPRESS' ? Priority.EXPRESS : Priority.STANDARD; } // Update workflow const workflow = await workflowService.updateWorkflow(id, updateData); if (!workflow) { ResponseHandler.notFound(res, 'Workflow not found'); return; } // Attach new files as documents const files = (req as any).files as Express.Multer.File[] | undefined; const category = (req.body?.category as string) || 'SUPPORTING'; const docs: any[] = []; if (files && files.length > 0) { const actualRequestId = (workflow as any).requestId; for (const file of files) { const buffer = fs.readFileSync(file.path); const checksum = crypto.createHash('sha256').update(buffer).digest('hex'); const extension = path.extname(file.originalname).replace('.', '').toLowerCase(); const doc = await Document.create({ requestId: actualRequestId, uploadedBy: userId, fileName: path.basename(file.filename || file.originalname), originalFileName: file.originalname, fileType: extension, fileExtension: extension, fileSize: file.size, filePath: file.path, storageUrl: `/uploads/${path.basename(file.path)}`, mimeType: file.mimetype, checksum, isGoogleDoc: false, googleDocUrl: null as any, category: category || 'OTHER', version: 1, parentDocumentId: null as any, isDeleted: false, downloadCount: 0, } as any); docs.push(doc); } } ResponseHandler.success(res, { workflow, newDocuments: docs }, 'Workflow updated with documents', 200); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; ResponseHandler.error(res, 'Failed to update workflow', 400, errorMessage); } } async submitWorkflow(req: Request, res: Response): Promise { try { const { id } = req.params; const workflow = await workflowService.submitWorkflow(id); if (!workflow) { ResponseHandler.notFound(res, 'Workflow not found'); return; } ResponseHandler.success(res, workflow, 'Workflow submitted successfully'); } catch (error) { const errorMessage = error instanceof Error ? error.message : 'Unknown error'; ResponseHandler.error(res, 'Failed to submit workflow', 400, errorMessage); } } }