2025-10-27 16:40:59 +09:00
|
|
|
// @ts-nocheck
|
2025-10-20 10:55:33 +09:00
|
|
|
/**
|
|
|
|
|
* 플로우 연결 서비스
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import db from "../database/db";
|
|
|
|
|
import { FlowStepConnection, CreateFlowConnectionRequest } from "../types/flow";
|
|
|
|
|
|
|
|
|
|
export class FlowConnectionService {
|
|
|
|
|
/**
|
|
|
|
|
* 플로우 단계 연결 생성
|
|
|
|
|
*/
|
|
|
|
|
async create(
|
|
|
|
|
request: CreateFlowConnectionRequest
|
|
|
|
|
): Promise<FlowStepConnection> {
|
|
|
|
|
// 순환 참조 체크
|
|
|
|
|
if (
|
|
|
|
|
await this.wouldCreateCycle(
|
|
|
|
|
request.flowDefinitionId,
|
|
|
|
|
request.fromStepId,
|
|
|
|
|
request.toStepId
|
|
|
|
|
)
|
|
|
|
|
) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
"Creating this connection would create a cycle in the flow"
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const query = `
|
|
|
|
|
INSERT INTO flow_step_connection (
|
|
|
|
|
flow_definition_id, from_step_id, to_step_id, label
|
|
|
|
|
)
|
|
|
|
|
VALUES ($1, $2, $3, $4)
|
|
|
|
|
RETURNING *
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const result = await db.query(query, [
|
|
|
|
|
request.flowDefinitionId,
|
|
|
|
|
request.fromStepId,
|
|
|
|
|
request.toStepId,
|
|
|
|
|
request.label || null,
|
|
|
|
|
]);
|
|
|
|
|
|
|
|
|
|
return this.mapToFlowConnection(result[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 특정 플로우의 모든 연결 조회
|
|
|
|
|
*/
|
|
|
|
|
async findByFlowId(flowDefinitionId: number): Promise<FlowStepConnection[]> {
|
|
|
|
|
const query = `
|
|
|
|
|
SELECT * FROM flow_step_connection
|
|
|
|
|
WHERE flow_definition_id = $1
|
|
|
|
|
ORDER BY id ASC
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const result = await db.query(query, [flowDefinitionId]);
|
|
|
|
|
return result.map(this.mapToFlowConnection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 플로우 연결 단일 조회
|
|
|
|
|
*/
|
|
|
|
|
async findById(id: number): Promise<FlowStepConnection | null> {
|
|
|
|
|
const query = "SELECT * FROM flow_step_connection WHERE id = $1";
|
|
|
|
|
const result = await db.query(query, [id]);
|
|
|
|
|
|
|
|
|
|
if (result.length === 0) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return this.mapToFlowConnection(result[0]);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 플로우 연결 삭제
|
|
|
|
|
*/
|
|
|
|
|
async delete(id: number): Promise<boolean> {
|
|
|
|
|
const query = "DELETE FROM flow_step_connection WHERE id = $1 RETURNING id";
|
|
|
|
|
const result = await db.query(query, [id]);
|
|
|
|
|
return result.length > 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 특정 단계에서 나가는 연결 조회
|
|
|
|
|
*/
|
|
|
|
|
async findOutgoingConnections(stepId: number): Promise<FlowStepConnection[]> {
|
|
|
|
|
const query = `
|
|
|
|
|
SELECT * FROM flow_step_connection
|
|
|
|
|
WHERE from_step_id = $1
|
|
|
|
|
ORDER BY id ASC
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const result = await db.query(query, [stepId]);
|
|
|
|
|
return result.map(this.mapToFlowConnection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 특정 단계로 들어오는 연결 조회
|
|
|
|
|
*/
|
|
|
|
|
async findIncomingConnections(stepId: number): Promise<FlowStepConnection[]> {
|
|
|
|
|
const query = `
|
|
|
|
|
SELECT * FROM flow_step_connection
|
|
|
|
|
WHERE to_step_id = $1
|
|
|
|
|
ORDER BY id ASC
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const result = await db.query(query, [stepId]);
|
|
|
|
|
return result.map(this.mapToFlowConnection);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 순환 참조 체크 (DFS)
|
|
|
|
|
*/
|
|
|
|
|
private async wouldCreateCycle(
|
|
|
|
|
flowDefinitionId: number,
|
|
|
|
|
fromStepId: number,
|
|
|
|
|
toStepId: number
|
|
|
|
|
): Promise<boolean> {
|
|
|
|
|
// toStepId에서 출발해서 fromStepId에 도달할 수 있는지 확인
|
|
|
|
|
const visited = new Set<number>();
|
|
|
|
|
const stack = [toStepId];
|
|
|
|
|
|
|
|
|
|
while (stack.length > 0) {
|
|
|
|
|
const current = stack.pop()!;
|
|
|
|
|
|
|
|
|
|
if (current === fromStepId) {
|
|
|
|
|
return true; // 순환 발견
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (visited.has(current)) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
visited.add(current);
|
|
|
|
|
|
|
|
|
|
// 현재 노드에서 나가는 모든 연결 조회
|
|
|
|
|
const query = `
|
|
|
|
|
SELECT to_step_id
|
|
|
|
|
FROM flow_step_connection
|
|
|
|
|
WHERE flow_definition_id = $1 AND from_step_id = $2
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const result = await db.query(query, [flowDefinitionId, current]);
|
|
|
|
|
|
|
|
|
|
for (const row of result) {
|
|
|
|
|
stack.push(row.to_step_id);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false; // 순환 없음
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* DB 행을 FlowStepConnection 객체로 변환
|
|
|
|
|
*/
|
|
|
|
|
private mapToFlowConnection(row: any): FlowStepConnection {
|
|
|
|
|
return {
|
|
|
|
|
id: row.id,
|
|
|
|
|
flowDefinitionId: row.flow_definition_id,
|
|
|
|
|
fromStepId: row.from_step_id,
|
|
|
|
|
toStepId: row.to_step_id,
|
|
|
|
|
label: row.label,
|
|
|
|
|
createdAt: row.created_at,
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
}
|