feat: Add procedure and function management in flow controller

- Introduced new endpoints in FlowController for listing procedures and retrieving procedure parameters, enhancing the flow management capabilities.
- Updated FlowDataMoveService to support procedure calls during data movement, ensuring seamless integration with external and internal databases.
- Enhanced NodeFlowExecutionService to execute procedure call actions, allowing for dynamic execution of stored procedures within flow nodes.
- Updated frontend components to support procedure selection and parameter management, improving user experience in configuring flow steps.
- Added necessary types and API functions for handling procedure-related data, ensuring type safety and clarity in implementation.
This commit is contained in:
kjs
2026-03-03 14:33:17 +09:00
parent fd5c61b12a
commit f697e1e897
21 changed files with 2303 additions and 41 deletions

View File

@@ -26,16 +26,20 @@ import {
buildSelectQuery,
} from "./dbQueryBuilder";
import { FlowConditionParser } from "./flowConditionParser";
import { FlowProcedureService } from "./flowProcedureService";
import { FlowProcedureConfig } from "../types/flow";
export class FlowDataMoveService {
private flowDefinitionService: FlowDefinitionService;
private flowStepService: FlowStepService;
private externalDbIntegrationService: FlowExternalDbIntegrationService;
private flowProcedureService: FlowProcedureService;
constructor() {
this.flowDefinitionService = new FlowDefinitionService();
this.flowStepService = new FlowStepService();
this.externalDbIntegrationService = new FlowExternalDbIntegrationService();
this.flowProcedureService = new FlowProcedureService();
}
/**
@@ -90,6 +94,64 @@ export class FlowDataMoveService {
let sourceTable = fromStep.tableName;
let targetTable = toStep.tableName || fromStep.tableName;
// 1.5. 프로시저 호출 (스텝 이동 전 실행, 실패 시 전체 롤백)
if (
toStep.integrationType === "procedure" &&
toStep.integrationConfig &&
(toStep.integrationConfig as FlowProcedureConfig).type === "procedure"
) {
const procConfig = toStep.integrationConfig as FlowProcedureConfig;
// 레코드 데이터 조회 (파라미터 매핑용)
let recordData: Record<string, any> = {};
try {
const recordTable = FlowConditionParser.sanitizeTableName(
sourceTable || flowDefinition.tableName
);
const recordResult = await client.query(
`SELECT * FROM ${recordTable} WHERE id = $1 LIMIT 1`,
[dataId]
);
if (recordResult.rows && recordResult.rows.length > 0) {
recordData = recordResult.rows[0];
}
} catch (err: any) {
console.warn("프로시저 파라미터용 레코드 조회 실패:", err.message);
}
console.log(`프로시저 호출 시작: ${procConfig.procedureName}`, {
flowId,
fromStepId,
toStepId,
dataId,
dbSource: procConfig.dbSource,
});
const procResult = await this.flowProcedureService.executeProcedure(
procConfig,
recordData,
procConfig.dbSource === "internal" ? client : undefined
);
console.log(`프로시저 호출 완료: ${procConfig.procedureName}`, {
success: procResult.success,
});
// 프로시저 실행 로그 기록
await this.logIntegration(
flowId,
toStep.id,
dataId,
"procedure",
procConfig.connectionId,
procConfig,
procResult.result,
"success",
undefined,
0,
userId
);
}
// 2. 이동 방식에 따라 처리
switch (toStep.moveType || "status") {
case "status":
@@ -603,18 +665,19 @@ export class FlowDataMoveService {
}
break;
case "procedure":
// 프로시저는 데이터 이동 전에 이미 실행됨 (step 1.5)
break;
case "rest_api":
// REST API 연동 (추후 구현)
console.warn("REST API 연동은 아직 구현되지 않았습니다");
break;
case "webhook":
// Webhook 연동 (추후 구현)
console.warn("Webhook 연동은 아직 구현되지 않았습니다");
break;
case "hybrid":
// 복합 연동 (추후 구현)
console.warn("복합 연동은 아직 구현되지 않았습니다");
break;
@@ -716,6 +779,40 @@ export class FlowDataMoveService {
let sourceTable = fromStep.tableName;
let targetTable = toStep.tableName || fromStep.tableName;
// 1.5. 프로시저 호출 (외부 DB 경로 - 스텝 이동 전)
if (
toStep.integrationType === "procedure" &&
toStep.integrationConfig &&
(toStep.integrationConfig as FlowProcedureConfig).type === "procedure"
) {
const procConfig = toStep.integrationConfig as FlowProcedureConfig;
let recordData: Record<string, any> = {};
try {
const recordTable = FlowConditionParser.sanitizeTableName(
sourceTable || ""
);
if (recordTable) {
const placeholder = getPlaceholder(dbType, 1);
const recordResult = await externalClient.query(
`SELECT * FROM ${recordTable} WHERE id = ${placeholder}`,
[dataId]
);
const rows = recordResult.rows || recordResult;
if (Array.isArray(rows) && rows.length > 0) {
recordData = rows[0];
}
}
} catch (err: any) {
console.warn("프로시저 파라미터용 레코드 조회 실패 (외부):", err.message);
}
await this.flowProcedureService.executeProcedure(
procConfig,
recordData,
procConfig.dbSource === "external" ? undefined : undefined
);
}
// 2. 이동 방식에 따라 처리
switch (toStep.moveType || "status") {
case "status":