- Integrated client IP address retrieval in the audit logging functionality across multiple controllers, including admin, common code, department, flow, screen, and table management. - Updated the `auditLogService` to include a new method for obtaining the client's IP address, ensuring accurate logging of user actions. - This enhancement improves traceability and accountability by capturing the source of requests, thereby strengthening the overall logging mechanism within the application.
1142 lines
32 KiB
TypeScript
1142 lines
32 KiB
TypeScript
// @ts-nocheck
|
|
/**
|
|
* 플로우 관리 컨트롤러
|
|
*/
|
|
|
|
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";
|
|
import { FlowProcedureService } from "../services/flowProcedureService";
|
|
import { auditLogService, getClientIp } from "../services/auditLogService";
|
|
|
|
export class FlowController {
|
|
private flowDefinitionService: FlowDefinitionService;
|
|
private flowStepService: FlowStepService;
|
|
private flowConnectionService: FlowConnectionService;
|
|
private flowExecutionService: FlowExecutionService;
|
|
private flowDataMoveService: FlowDataMoveService;
|
|
private flowProcedureService: FlowProcedureService;
|
|
|
|
constructor() {
|
|
this.flowDefinitionService = new FlowDefinitionService();
|
|
this.flowStepService = new FlowStepService();
|
|
this.flowConnectionService = new FlowConnectionService();
|
|
this.flowExecutionService = new FlowExecutionService();
|
|
this.flowDataMoveService = new FlowDataMoveService();
|
|
this.flowProcedureService = new FlowProcedureService();
|
|
}
|
|
|
|
// ==================== 플로우 정의 ====================
|
|
|
|
/**
|
|
* 플로우 정의 생성
|
|
*/
|
|
createFlowDefinition = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const {
|
|
name,
|
|
description,
|
|
tableName,
|
|
dbSourceType,
|
|
dbConnectionId,
|
|
// REST API 관련 필드
|
|
restApiConnectionId,
|
|
restApiEndpoint,
|
|
restApiJsonPath,
|
|
} = req.body;
|
|
const userId = (req as any).user?.userId || "system";
|
|
const userCompanyCode = (req as any).user?.companyCode;
|
|
|
|
|
|
|
|
if (!name) {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: "Name is required",
|
|
});
|
|
return;
|
|
}
|
|
|
|
// REST API 또는 다중 연결인 경우 테이블 존재 확인 스킵
|
|
const isRestApi = dbSourceType === "restapi" || dbSourceType === "multi_restapi";
|
|
const isMultiConnection = dbSourceType === "multi_restapi" || dbSourceType === "multi_external_db";
|
|
|
|
// 테이블 이름이 제공된 경우에만 존재 확인 (REST API 및 다중 연결 제외)
|
|
if (tableName && !isRestApi && !isMultiConnection && !tableName.startsWith("_restapi_") && !tableName.startsWith("_multi_restapi_") && !tableName.startsWith("_multi_external_db_")) {
|
|
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,
|
|
restApiConnectionId,
|
|
restApiEndpoint,
|
|
restApiJsonPath,
|
|
restApiConnections: req.body.restApiConnections,
|
|
},
|
|
userId,
|
|
userCompanyCode
|
|
);
|
|
|
|
auditLogService.log({
|
|
companyCode: userCompanyCode || "",
|
|
userId: userId || "",
|
|
action: "CREATE",
|
|
resourceType: "FLOW",
|
|
resourceId: String(flowDef?.id || ""),
|
|
resourceName: flowDef?.name || name,
|
|
summary: `플로우 "${flowDef?.name || name}" 생성`,
|
|
changes: { after: { name, tableName } },
|
|
ipAddress: getClientIp(req as any),
|
|
requestPath: req.originalUrl,
|
|
});
|
|
|
|
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<void> => {
|
|
try {
|
|
const { tableName, isActive } = req.query;
|
|
const user = (req as any).user;
|
|
const userCompanyCode = user?.companyCode;
|
|
|
|
|
|
|
|
const flows = await this.flowDefinitionService.findAll(
|
|
tableName as string | undefined,
|
|
isActive !== undefined ? isActive === "true" : undefined,
|
|
userCompanyCode
|
|
);
|
|
|
|
|
|
|
|
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<void> => {
|
|
try {
|
|
const { id } = req.params;
|
|
const flowId = parseInt(id);
|
|
const userCompanyCode = (req as any).user?.companyCode;
|
|
|
|
const definition = await this.flowDefinitionService.findById(flowId, userCompanyCode);
|
|
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<void> => {
|
|
try {
|
|
const { id } = req.params;
|
|
const flowId = parseInt(id);
|
|
const { name, description, isActive } = req.body;
|
|
const userCompanyCode = (req as any).user?.companyCode;
|
|
|
|
const beforeFlow = await this.flowDefinitionService.findById(flowId);
|
|
const flowDef = await this.flowDefinitionService.update(flowId, {
|
|
name,
|
|
description,
|
|
isActive,
|
|
}, userCompanyCode);
|
|
|
|
if (!flowDef) {
|
|
res.status(404).json({
|
|
success: false,
|
|
message: "Flow definition not found",
|
|
});
|
|
return;
|
|
}
|
|
|
|
auditLogService.log({
|
|
companyCode: userCompanyCode || "",
|
|
userId: (req as any).user?.userId || "",
|
|
action: "UPDATE",
|
|
resourceType: "FLOW",
|
|
resourceId: String(flowId),
|
|
resourceName: flowDef?.name || name,
|
|
summary: `플로우 "${flowDef?.name || name}" 수정`,
|
|
changes: {
|
|
before: { name: beforeFlow?.name, description: beforeFlow?.description, isActive: beforeFlow?.isActive },
|
|
after: { name, description, isActive },
|
|
},
|
|
ipAddress: getClientIp(req as any),
|
|
requestPath: req.originalUrl,
|
|
});
|
|
|
|
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<void> => {
|
|
try {
|
|
const { id } = req.params;
|
|
const flowId = parseInt(id);
|
|
const userCompanyCode = (req as any).user?.companyCode;
|
|
|
|
const success = await this.flowDefinitionService.delete(flowId, userCompanyCode);
|
|
|
|
if (!success) {
|
|
res.status(404).json({
|
|
success: false,
|
|
message: "Flow definition not found",
|
|
});
|
|
return;
|
|
}
|
|
|
|
auditLogService.log({
|
|
companyCode: userCompanyCode || "",
|
|
userId: (req as any).user?.userId || "",
|
|
action: "DELETE",
|
|
resourceType: "FLOW",
|
|
resourceId: String(flowId),
|
|
summary: `플로우(ID:${flowId}) 삭제`,
|
|
ipAddress: getClientIp(req as any),
|
|
requestPath: req.originalUrl,
|
|
});
|
|
|
|
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<void> => {
|
|
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<void> => {
|
|
try {
|
|
const { flowId } = req.params;
|
|
const flowDefinitionId = parseInt(flowId);
|
|
const userCompanyCode = (req as any).user?.companyCode;
|
|
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 flowDef = await this.flowDefinitionService.findById(flowDefinitionId, userCompanyCode);
|
|
if (!flowDef) {
|
|
res.status(404).json({
|
|
success: false,
|
|
message: "Flow definition not found or access denied",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const step = await this.flowStepService.create({
|
|
flowDefinitionId,
|
|
stepName,
|
|
stepOrder,
|
|
tableName,
|
|
conditionJson,
|
|
color,
|
|
positionX,
|
|
positionY,
|
|
});
|
|
|
|
auditLogService.log({
|
|
companyCode: userCompanyCode || "",
|
|
userId: (req as any).user?.userId || "",
|
|
action: "CREATE",
|
|
resourceType: "FLOW_STEP",
|
|
resourceId: String(step?.id || ""),
|
|
resourceName: stepName,
|
|
summary: `플로우 스텝 "${stepName}" 생성 (플로우 ID:${flowDefinitionId})`,
|
|
changes: { after: { stepName, tableName, stepOrder } },
|
|
ipAddress: getClientIp(req as any),
|
|
requestPath: req.originalUrl,
|
|
});
|
|
|
|
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<void> => {
|
|
try {
|
|
const { stepId } = req.params;
|
|
const id = parseInt(stepId);
|
|
const userCompanyCode = (req as any).user?.companyCode;
|
|
const {
|
|
stepName,
|
|
stepOrder,
|
|
tableName,
|
|
conditionJson,
|
|
color,
|
|
positionX,
|
|
positionY,
|
|
moveType,
|
|
statusColumn,
|
|
statusValue,
|
|
targetTable,
|
|
fieldMappings,
|
|
integrationType,
|
|
integrationConfig,
|
|
displayConfig,
|
|
} = req.body;
|
|
|
|
// 스텝 소유권 검증: 스텝이 속한 플로우가 사용자 회사 소유인지 확인
|
|
const existingStep = await this.flowStepService.findById(id);
|
|
if (existingStep) {
|
|
const flowDef = await this.flowDefinitionService.findById(existingStep.flowDefinitionId, userCompanyCode);
|
|
if (!flowDef) {
|
|
res.status(403).json({
|
|
success: false,
|
|
message: "Access denied: flow does not belong to your company",
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
const beforeStep = existingStep;
|
|
const step = await this.flowStepService.update(id, {
|
|
stepName,
|
|
stepOrder,
|
|
tableName,
|
|
conditionJson,
|
|
color,
|
|
positionX,
|
|
positionY,
|
|
moveType,
|
|
statusColumn,
|
|
statusValue,
|
|
targetTable,
|
|
fieldMappings,
|
|
integrationType,
|
|
integrationConfig,
|
|
displayConfig,
|
|
});
|
|
|
|
if (!step) {
|
|
res.status(404).json({
|
|
success: false,
|
|
message: "Flow step not found",
|
|
});
|
|
return;
|
|
}
|
|
|
|
auditLogService.log({
|
|
companyCode: userCompanyCode || "",
|
|
userId: (req as any).user?.userId || "",
|
|
action: "UPDATE",
|
|
resourceType: "FLOW_STEP",
|
|
resourceId: String(id),
|
|
resourceName: step?.stepName || stepName,
|
|
summary: `플로우 스텝 "${step?.stepName || stepName}" 수정`,
|
|
changes: {
|
|
before: { stepName: beforeStep?.stepName, tableName: beforeStep?.tableName, stepOrder: beforeStep?.stepOrder },
|
|
after: { stepName, tableName, stepOrder },
|
|
},
|
|
ipAddress: getClientIp(req as any),
|
|
requestPath: req.originalUrl,
|
|
});
|
|
|
|
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<void> => {
|
|
try {
|
|
const { stepId } = req.params;
|
|
const id = parseInt(stepId);
|
|
const userCompanyCode = (req as any).user?.companyCode;
|
|
|
|
// 스텝 소유권 검증
|
|
const existingStep = await this.flowStepService.findById(id);
|
|
if (existingStep) {
|
|
const flowDef = await this.flowDefinitionService.findById(existingStep.flowDefinitionId, userCompanyCode);
|
|
if (!flowDef) {
|
|
res.status(403).json({
|
|
success: false,
|
|
message: "Access denied: flow does not belong to your company",
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
const success = await this.flowStepService.delete(id);
|
|
|
|
if (!success) {
|
|
res.status(404).json({
|
|
success: false,
|
|
message: "Flow step not found",
|
|
});
|
|
return;
|
|
}
|
|
|
|
auditLogService.log({
|
|
companyCode: userCompanyCode || "",
|
|
userId: (req as any).user?.userId || "",
|
|
action: "DELETE",
|
|
resourceType: "FLOW_STEP",
|
|
resourceId: String(id),
|
|
resourceName: existingStep?.stepName,
|
|
summary: `플로우 스텝 "${existingStep?.stepName || id}" 삭제`,
|
|
ipAddress: getClientIp(req as any),
|
|
requestPath: req.originalUrl,
|
|
});
|
|
|
|
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<void> => {
|
|
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<void> => {
|
|
try {
|
|
const { flowDefinitionId, fromStepId, toStepId, label } = req.body;
|
|
const userCompanyCode = (req as any).user?.companyCode;
|
|
|
|
if (!flowDefinitionId || !fromStepId || !toStepId) {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: "flowDefinitionId, fromStepId, and toStepId are required",
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 플로우 소유권 검증
|
|
const flowDef = await this.flowDefinitionService.findById(flowDefinitionId, userCompanyCode);
|
|
if (!flowDef) {
|
|
res.status(404).json({
|
|
success: false,
|
|
message: "Flow definition not found or access denied",
|
|
});
|
|
return;
|
|
}
|
|
|
|
// fromStepId, toStepId가 해당 flow에 속하는지 검증
|
|
const fromStep = await this.flowStepService.findById(fromStepId);
|
|
const toStep = await this.flowStepService.findById(toStepId);
|
|
if (!fromStep || fromStep.flowDefinitionId !== flowDefinitionId ||
|
|
!toStep || toStep.flowDefinitionId !== flowDefinitionId) {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: "fromStepId and toStepId must belong to the specified flow",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const connection = await this.flowConnectionService.create({
|
|
flowDefinitionId,
|
|
fromStepId,
|
|
toStepId,
|
|
label,
|
|
});
|
|
|
|
auditLogService.log({
|
|
companyCode: userCompanyCode || "",
|
|
userId: (req as any).user?.userId || "",
|
|
action: "CREATE",
|
|
resourceType: "FLOW",
|
|
resourceId: String(flowDefinitionId),
|
|
resourceName: flowDef?.name || "",
|
|
summary: `플로우 "${flowDef?.name}" 연결 생성 (${fromStep?.stepName} → ${toStep?.stepName})`,
|
|
changes: { after: { fromStepId, toStepId, label } },
|
|
ipAddress: getClientIp(req as any),
|
|
requestPath: req.originalUrl,
|
|
});
|
|
|
|
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<void> => {
|
|
try {
|
|
const { connectionId } = req.params;
|
|
const id = parseInt(connectionId);
|
|
const userCompanyCode = (req as any).user?.companyCode;
|
|
|
|
// 연결 소유권 검증
|
|
const existingConn = await this.flowConnectionService.findById(id);
|
|
if (existingConn) {
|
|
const flowDef = await this.flowDefinitionService.findById(existingConn.flowDefinitionId, userCompanyCode);
|
|
if (!flowDef) {
|
|
res.status(403).json({
|
|
success: false,
|
|
message: "Access denied: flow does not belong to your company",
|
|
});
|
|
return;
|
|
}
|
|
}
|
|
|
|
const success = await this.flowConnectionService.delete(id);
|
|
|
|
if (!success) {
|
|
res.status(404).json({
|
|
success: false,
|
|
message: "Connection not found",
|
|
});
|
|
return;
|
|
}
|
|
|
|
auditLogService.log({
|
|
companyCode: userCompanyCode || "",
|
|
userId: (req as any).user?.userId || "",
|
|
action: "DELETE",
|
|
resourceType: "FLOW",
|
|
resourceId: String(existingConn?.flowDefinitionId || id),
|
|
summary: `플로우 연결 삭제 (ID: ${id})`,
|
|
changes: { before: { connectionId: id } },
|
|
ipAddress: getClientIp(req as any),
|
|
requestPath: req.originalUrl,
|
|
});
|
|
|
|
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<void> => {
|
|
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<void> => {
|
|
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",
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 플로우 스텝의 컬럼 라벨 조회
|
|
*/
|
|
getStepColumnLabels = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { flowId, stepId } = req.params;
|
|
|
|
|
|
const step = await this.flowStepService.findById(parseInt(stepId));
|
|
if (!step) {
|
|
|
|
res.status(404).json({
|
|
success: false,
|
|
message: "Step not found",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const flowDef = await this.flowDefinitionService.findById(
|
|
parseInt(flowId)
|
|
);
|
|
if (!flowDef) {
|
|
|
|
res.status(404).json({
|
|
success: false,
|
|
message: "Flow definition not found",
|
|
});
|
|
return;
|
|
}
|
|
|
|
// 테이블명 결정 (스텝 테이블 우선, 없으면 플로우 테이블)
|
|
const tableName = step.tableName || flowDef.tableName;
|
|
|
|
|
|
if (!tableName) {
|
|
|
|
res.json({
|
|
success: true,
|
|
data: {},
|
|
});
|
|
return;
|
|
}
|
|
|
|
// table_type_columns 테이블에서 라벨 정보 조회
|
|
const { query } = await import("../database/db");
|
|
const labelRows = await query<{
|
|
column_name: string;
|
|
column_label: string | null;
|
|
}>(
|
|
`SELECT column_name, column_label
|
|
FROM table_type_columns
|
|
WHERE table_name = $1 AND column_label IS NOT NULL AND company_code = '*'`,
|
|
[tableName]
|
|
);
|
|
|
|
|
|
|
|
// { columnName: label } 형태의 객체로 변환
|
|
const labels: Record<string, string> = {};
|
|
labelRows.forEach((row) => {
|
|
if (row.column_label) {
|
|
labels[row.column_name] = row.column_label;
|
|
}
|
|
});
|
|
|
|
|
|
|
|
res.json({
|
|
success: true,
|
|
data: labels,
|
|
});
|
|
} catch (error: any) {
|
|
console.error("❌ [FlowController] 컬럼 라벨 조회 오류:", error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: error.message || "Failed to get step column labels",
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 플로우의 모든 단계별 카운트 조회
|
|
*/
|
|
getAllStepCounts = async (req: Request, res: Response): Promise<void> => {
|
|
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<void> => {
|
|
try {
|
|
const { flowId, fromStepId, recordId, toStepId, note } = req.body;
|
|
const userId = (req as any).user?.userId || "system";
|
|
|
|
if (!flowId || !fromStepId || !recordId || !toStepId) {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: "flowId, fromStepId, recordId, and toStepId are required",
|
|
});
|
|
return;
|
|
}
|
|
|
|
await this.flowDataMoveService.moveDataToStep(
|
|
flowId,
|
|
fromStepId,
|
|
toStepId,
|
|
recordId,
|
|
userId,
|
|
note ? { note } : undefined
|
|
);
|
|
|
|
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<void> => {
|
|
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<void> => {
|
|
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<void> => {
|
|
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",
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 스텝 데이터 업데이트 (인라인 편집)
|
|
*/
|
|
updateStepData = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { flowId, stepId, recordId } = req.params;
|
|
const updateData = req.body;
|
|
const userId = (req as any).user?.userId || "system";
|
|
const userCompanyCode = (req as any).user?.companyCode;
|
|
|
|
if (!flowId || !stepId || !recordId) {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: "flowId, stepId, and recordId are required",
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (!updateData || Object.keys(updateData).length === 0) {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: "Update data is required",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const result = await this.flowExecutionService.updateStepData(
|
|
parseInt(flowId),
|
|
parseInt(stepId),
|
|
recordId,
|
|
updateData,
|
|
userId,
|
|
userCompanyCode
|
|
);
|
|
|
|
res.json({
|
|
success: true,
|
|
message: "Data updated successfully",
|
|
data: result,
|
|
});
|
|
} catch (error: any) {
|
|
console.error("Error updating step data:", error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: error.message || "Failed to update step data",
|
|
});
|
|
}
|
|
};
|
|
|
|
// ==================== 프로시저/함수 ====================
|
|
|
|
/**
|
|
* 프로시저/함수 목록 조회
|
|
*/
|
|
listProcedures = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const dbSource = (req.query.dbSource as string) || "internal";
|
|
const connectionId = req.query.connectionId
|
|
? parseInt(req.query.connectionId as string)
|
|
: undefined;
|
|
const schema = req.query.schema as string | undefined;
|
|
|
|
if (dbSource !== "internal" && dbSource !== "external") {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: "dbSource는 internal 또는 external이어야 합니다",
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (dbSource === "external" && !connectionId) {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: "외부 DB 조회 시 connectionId가 필요합니다",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const procedures = await this.flowProcedureService.listProcedures(
|
|
dbSource,
|
|
connectionId,
|
|
schema
|
|
);
|
|
|
|
res.json({ success: true, data: procedures });
|
|
} catch (error: any) {
|
|
console.error("프로시저 목록 조회 실패:", error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: error.message || "프로시저 목록 조회에 실패했습니다",
|
|
});
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 프로시저/함수 파라미터 조회
|
|
*/
|
|
getProcedureParameters = async (req: Request, res: Response): Promise<void> => {
|
|
try {
|
|
const { name } = req.params;
|
|
const dbSource = (req.query.dbSource as string) || "internal";
|
|
const connectionId = req.query.connectionId
|
|
? parseInt(req.query.connectionId as string)
|
|
: undefined;
|
|
const schema = req.query.schema as string | undefined;
|
|
|
|
if (!name) {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: "프로시저 이름이 필요합니다",
|
|
});
|
|
return;
|
|
}
|
|
|
|
if (dbSource !== "internal" && dbSource !== "external") {
|
|
res.status(400).json({
|
|
success: false,
|
|
message: "dbSource는 internal 또는 external이어야 합니다",
|
|
});
|
|
return;
|
|
}
|
|
|
|
const parameters = await this.flowProcedureService.getProcedureParameters(
|
|
name,
|
|
dbSource as "internal" | "external",
|
|
connectionId,
|
|
schema
|
|
);
|
|
|
|
res.json({ success: true, data: parameters });
|
|
} catch (error: any) {
|
|
console.error("프로시저 파라미터 조회 실패:", error);
|
|
res.status(500).json({
|
|
success: false,
|
|
message: error.message || "프로시저 파라미터 조회에 실패했습니다",
|
|
});
|
|
}
|
|
};
|
|
}
|