/** * 플로우 관리 컨트롤러 */ import { Request, Response } from "express"; import { FlowDefinitionService } from "../services/flowDefinitionService"; import { FlowStepService } from "../services/flowStepService"; import { FlowConnectionService } from "../services/flowConnectionService"; import { FlowExecutionService } from "../services/flowExecutionService"; import { FlowDataMoveService } from "../services/flowDataMoveService"; export class FlowController { private flowDefinitionService: FlowDefinitionService; private flowStepService: FlowStepService; private flowConnectionService: FlowConnectionService; private flowExecutionService: FlowExecutionService; private flowDataMoveService: FlowDataMoveService; constructor() { this.flowDefinitionService = new FlowDefinitionService(); this.flowStepService = new FlowStepService(); this.flowConnectionService = new FlowConnectionService(); this.flowExecutionService = new FlowExecutionService(); this.flowDataMoveService = new FlowDataMoveService(); } // ==================== 플로우 정의 ==================== /** * 플로우 정의 생성 */ createFlowDefinition = async (req: Request, res: Response): Promise => { try { const { name, description, tableName, dbSourceType, dbConnectionId } = req.body; const userId = (req as any).user?.userId || "system"; console.log("🔍 createFlowDefinition called with:", { name, description, tableName, dbSourceType, dbConnectionId, }); if (!name) { res.status(400).json({ success: false, message: "Name is required", }); return; } // 테이블 이름이 제공된 경우에만 존재 확인 if (tableName) { const tableExists = await this.flowDefinitionService.checkTableExists(tableName); if (!tableExists) { res.status(400).json({ success: false, message: `Table '${tableName}' does not exist`, }); return; } } const flowDef = await this.flowDefinitionService.create( { name, description, tableName, dbSourceType, dbConnectionId }, userId ); res.json({ success: true, data: flowDef, }); } catch (error: any) { console.error("Error creating flow definition:", error); res.status(500).json({ success: false, message: error.message || "Failed to create flow definition", }); } }; /** * 플로우 정의 목록 조회 */ getFlowDefinitions = async (req: Request, res: Response): Promise => { try { const { tableName, isActive } = req.query; const flows = await this.flowDefinitionService.findAll( tableName as string | undefined, isActive !== undefined ? isActive === "true" : undefined ); res.json({ success: true, data: flows, }); } catch (error: any) { console.error("Error fetching flow definitions:", error); res.status(500).json({ success: false, message: error.message || "Failed to fetch flow definitions", }); } }; /** * 플로우 정의 상세 조회 (단계 및 연결 포함) */ getFlowDefinitionDetail = async ( req: Request, res: Response ): Promise => { try { const { id } = req.params; const flowId = parseInt(id); const definition = await this.flowDefinitionService.findById(flowId); if (!definition) { res.status(404).json({ success: false, message: "Flow definition not found", }); return; } const steps = await this.flowStepService.findByFlowId(flowId); const connections = await this.flowConnectionService.findByFlowId(flowId); res.json({ success: true, data: { definition, steps, connections, }, }); } catch (error: any) { console.error("Error fetching flow definition detail:", error); res.status(500).json({ success: false, message: error.message || "Failed to fetch flow definition detail", }); } }; /** * 플로우 정의 수정 */ updateFlowDefinition = async (req: Request, res: Response): Promise => { try { const { id } = req.params; const flowId = parseInt(id); const { name, description, isActive } = req.body; const flowDef = await this.flowDefinitionService.update(flowId, { name, description, isActive, }); if (!flowDef) { res.status(404).json({ success: false, message: "Flow definition not found", }); return; } res.json({ success: true, data: flowDef, }); } catch (error: any) { console.error("Error updating flow definition:", error); res.status(500).json({ success: false, message: error.message || "Failed to update flow definition", }); } }; /** * 플로우 정의 삭제 */ deleteFlowDefinition = async (req: Request, res: Response): Promise => { try { const { id } = req.params; const flowId = parseInt(id); const success = await this.flowDefinitionService.delete(flowId); if (!success) { res.status(404).json({ success: false, message: "Flow definition not found", }); return; } res.json({ success: true, message: "Flow definition deleted successfully", }); } catch (error: any) { console.error("Error deleting flow definition:", error); res.status(500).json({ success: false, message: error.message || "Failed to delete flow definition", }); } }; // ==================== 플로우 단계 ==================== /** * 플로우 단계 목록 조회 */ getFlowSteps = async (req: Request, res: Response): Promise => { try { const { flowId } = req.params; const flowDefinitionId = parseInt(flowId); const steps = await this.flowStepService.findByFlowId(flowDefinitionId); res.json({ success: true, data: steps, }); return; } catch (error: any) { console.error("Error fetching flow steps:", error); res.status(500).json({ success: false, message: error.message || "Failed to fetch flow steps", }); return; } }; /** * 플로우 단계 생성 */ createFlowStep = async (req: Request, res: Response): Promise => { try { const { flowId } = req.params; const flowDefinitionId = parseInt(flowId); const { stepName, stepOrder, tableName, conditionJson, color, positionX, positionY, } = req.body; if (!stepName || stepOrder === undefined) { res.status(400).json({ success: false, message: "stepName and stepOrder are required", }); return; } const step = await this.flowStepService.create({ flowDefinitionId, stepName, stepOrder, tableName, conditionJson, color, positionX, positionY, }); res.json({ success: true, data: step, }); } catch (error: any) { console.error("Error creating flow step:", error); res.status(500).json({ success: false, message: error.message || "Failed to create flow step", }); } }; /** * 플로우 단계 수정 */ updateFlowStep = async (req: Request, res: Response): Promise => { try { const { stepId } = req.params; const id = parseInt(stepId); const { stepName, stepOrder, tableName, conditionJson, color, positionX, positionY, moveType, statusColumn, statusValue, targetTable, fieldMappings, integrationType, integrationConfig, } = req.body; const step = await this.flowStepService.update(id, { stepName, stepOrder, tableName, conditionJson, color, positionX, positionY, moveType, statusColumn, statusValue, targetTable, fieldMappings, integrationType, integrationConfig, }); if (!step) { res.status(404).json({ success: false, message: "Flow step not found", }); return; } res.json({ success: true, data: step, }); } catch (error: any) { console.error("Error updating flow step:", error); res.status(500).json({ success: false, message: error.message || "Failed to update flow step", }); } }; /** * 플로우 단계 삭제 */ deleteFlowStep = async (req: Request, res: Response): Promise => { try { const { stepId } = req.params; const id = parseInt(stepId); const success = await this.flowStepService.delete(id); if (!success) { res.status(404).json({ success: false, message: "Flow step not found", }); return; } res.json({ success: true, message: "Flow step deleted successfully", }); } catch (error: any) { console.error("Error deleting flow step:", error); res.status(500).json({ success: false, message: error.message || "Failed to delete flow step", }); } }; // ==================== 플로우 연결 ==================== /** * 플로우 연결 목록 조회 */ getFlowConnections = async (req: Request, res: Response): Promise => { try { const { flowId } = req.params; const flowDefinitionId = parseInt(flowId); const connections = await this.flowConnectionService.findByFlowId(flowDefinitionId); res.json({ success: true, data: connections, }); return; } catch (error: any) { console.error("Error fetching flow connections:", error); res.status(500).json({ success: false, message: error.message || "Failed to fetch flow connections", }); return; } }; /** * 플로우 연결 생성 */ createConnection = async (req: Request, res: Response): Promise => { try { const { flowDefinitionId, fromStepId, toStepId, label } = req.body; if (!flowDefinitionId || !fromStepId || !toStepId) { res.status(400).json({ success: false, message: "flowDefinitionId, fromStepId, and toStepId are required", }); return; } const connection = await this.flowConnectionService.create({ flowDefinitionId, fromStepId, toStepId, label, }); res.json({ success: true, data: connection, }); } catch (error: any) { console.error("Error creating connection:", error); res.status(500).json({ success: false, message: error.message || "Failed to create connection", }); } }; /** * 플로우 연결 삭제 */ deleteConnection = async (req: Request, res: Response): Promise => { try { const { connectionId } = req.params; const id = parseInt(connectionId); const success = await this.flowConnectionService.delete(id); if (!success) { res.status(404).json({ success: false, message: "Connection not found", }); return; } res.json({ success: true, message: "Connection deleted successfully", }); } catch (error: any) { console.error("Error deleting connection:", error); res.status(500).json({ success: false, message: error.message || "Failed to delete connection", }); } }; // ==================== 플로우 실행 ==================== /** * 단계별 데이터 카운트 조회 */ getStepDataCount = async (req: Request, res: Response): Promise => { try { const { flowId, stepId } = req.params; const count = await this.flowExecutionService.getStepDataCount( parseInt(flowId), parseInt(stepId) ); res.json({ success: true, data: { count }, }); } catch (error: any) { console.error("Error getting step data count:", error); res.status(500).json({ success: false, message: error.message || "Failed to get step data count", }); } }; /** * 단계별 데이터 리스트 조회 */ getStepDataList = async (req: Request, res: Response): Promise => { try { const { flowId, stepId } = req.params; const { page = 1, pageSize = 20 } = req.query; const data = await this.flowExecutionService.getStepDataList( parseInt(flowId), parseInt(stepId), parseInt(page as string), parseInt(pageSize as string) ); res.json({ success: true, data, }); } catch (error: any) { console.error("Error getting step data list:", error); res.status(500).json({ success: false, message: error.message || "Failed to get step data list", }); } }; /** * 플로우의 모든 단계별 카운트 조회 */ getAllStepCounts = async (req: Request, res: Response): Promise => { try { const { flowId } = req.params; const counts = await this.flowExecutionService.getAllStepCounts( parseInt(flowId) ); res.json({ success: true, data: counts, }); } catch (error: any) { console.error("Error getting all step counts:", error); res.status(500).json({ success: false, message: error.message || "Failed to get all step counts", }); } }; /** * 데이터를 다음 단계로 이동 */ moveData = async (req: Request, res: Response): Promise => { try { const { flowId, recordId, toStepId, note } = req.body; const userId = (req as any).user?.userId || "system"; if (!flowId || !recordId || !toStepId) { res.status(400).json({ success: false, message: "flowId, recordId, and toStepId are required", }); return; } await this.flowDataMoveService.moveDataToStep( flowId, recordId, toStepId, userId, note ); res.json({ success: true, message: "Data moved successfully", }); } catch (error: any) { console.error("Error moving data:", error); res.status(500).json({ success: false, message: error.message || "Failed to move data", }); } }; /** * 여러 데이터를 동시에 이동 */ moveBatchData = async (req: Request, res: Response): Promise => { try { const { flowId, fromStepId, toStepId, dataIds } = req.body; const userId = (req as any).user?.userId || "system"; if ( !flowId || !fromStepId || !toStepId || !dataIds || !Array.isArray(dataIds) ) { res.status(400).json({ success: false, message: "flowId, fromStepId, toStepId, and dataIds (array) are required", }); return; } const result = await this.flowDataMoveService.moveBatchData( flowId, fromStepId, toStepId, dataIds, userId ); const successCount = result.results.filter((r) => r.success).length; const failureCount = result.results.filter((r) => !r.success).length; res.json({ success: result.success, message: result.success ? `${successCount}건의 데이터를 성공적으로 이동했습니다` : `${successCount}건 성공, ${failureCount}건 실패`, data: { successCount, failureCount, total: dataIds.length, }, results: result.results, }); } catch (error: any) { console.error("Error moving batch data:", error); res.status(500).json({ success: false, message: error.message || "Failed to move batch data", }); } }; /** * 데이터의 플로우 이력 조회 */ getAuditLogs = async (req: Request, res: Response): Promise => { try { const { flowId, recordId } = req.params; const logs = await this.flowDataMoveService.getAuditLogs( parseInt(flowId), recordId ); res.json({ success: true, data: logs, }); } catch (error: any) { console.error("Error getting audit logs:", error); res.status(500).json({ success: false, message: error.message || "Failed to get audit logs", }); } }; /** * 플로우의 모든 이력 조회 */ getFlowAuditLogs = async (req: Request, res: Response): Promise => { try { const { flowId } = req.params; const { limit = 100 } = req.query; const logs = await this.flowDataMoveService.getFlowAuditLogs( parseInt(flowId), parseInt(limit as string) ); res.json({ success: true, data: logs, }); } catch (error: any) { console.error("Error getting flow audit logs:", error); res.status(500).json({ success: false, message: error.message || "Failed to get flow audit logs", }); } }; }