조건 그룹핑 구현

This commit is contained in:
2025-09-15 11:17:46 +09:00
parent dbad9bbc0c
commit 41f40ac216
3 changed files with 819 additions and 385 deletions

View File

@@ -5,19 +5,21 @@ const prisma = new PrismaClient();
// 조건 노드 타입 정의
interface ConditionNode {
type: "group" | "condition";
operator?: "AND" | "OR";
children?: ConditionNode[];
id: string; // 고유 ID
type: "condition" | "group-start" | "group-end";
field?: string;
operator_type?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
value?: any;
dataType?: string;
logicalOperator?: "AND" | "OR"; // 다음 조건과의 논리 연산자
groupId?: string; // 그룹 ID (group-start와 group-end가 같은 groupId를 가짐)
groupLevel?: number; // 중첩 레벨 (0, 1, 2, ...)
}
// 조건 제어 정보
interface ConditionControl {
triggerType: "insert" | "update" | "delete" | "insert_update";
conditionTree: ConditionNode | null;
conditionTree: ConditionNode | ConditionNode[] | null;
}
// 연결 카테고리 정보
@@ -237,32 +239,103 @@ export class EventTriggerService {
}
/**
* 조건 평가
* 조건 평가 (플랫 구조 + 그룹핑 지원)
*/
private static async evaluateCondition(
condition: ConditionNode,
condition: ConditionNode | ConditionNode[],
data: Record<string, any>
): Promise<boolean> {
if (condition.type === "group") {
if (!condition.children || condition.children.length === 0) {
return true;
// 단일 조건인 경우 (하위 호환성)
if (!Array.isArray(condition)) {
if (condition.type === "condition") {
return this.evaluateSingleCondition(condition, data);
}
const results = await Promise.all(
condition.children.map((child) => this.evaluateCondition(child, data))
);
if (condition.operator === "OR") {
return results.some((result) => result);
} else {
// AND
return results.every((result) => result);
}
} else if (condition.type === "condition") {
return this.evaluateSingleCondition(condition, data);
return true;
}
return false;
// 조건 배열인 경우 (새로운 그룹핑 시스템)
return this.evaluateConditionList(condition, data);
}
/**
* 조건 리스트 평가 (괄호 그룹핑 지원)
*/
private static async evaluateConditionList(
conditions: ConditionNode[],
data: Record<string, any>
): Promise<boolean> {
if (conditions.length === 0) {
return true;
}
// 조건을 평가 가능한 표현식으로 변환
const expression = await this.buildConditionExpression(conditions, data);
// 표현식 평가
return this.evaluateExpression(expression);
}
/**
* 조건들을 평가 가능한 표현식으로 변환
*/
private static async buildConditionExpression(
conditions: ConditionNode[],
data: Record<string, any>
): Promise<string> {
const tokens: string[] = [];
for (let i = 0; i < conditions.length; i++) {
const condition = conditions[i];
if (condition.type === "group-start") {
// 이전 조건과의 논리 연산자 추가
if (i > 0 && condition.logicalOperator) {
tokens.push(condition.logicalOperator);
}
tokens.push("(");
} else if (condition.type === "group-end") {
tokens.push(")");
} else if (condition.type === "condition") {
// 이전 조건과의 논리 연산자 추가
if (i > 0 && condition.logicalOperator) {
tokens.push(condition.logicalOperator);
}
// 조건 평가 결과를 토큰으로 추가
const result = await this.evaluateSingleCondition(condition, data);
tokens.push(result.toString());
}
}
return tokens.join(" ");
}
/**
* 논리 표현식 평가 (괄호 우선순위 지원)
*/
private static evaluateExpression(expression: string): boolean {
try {
// 안전한 논리 표현식 평가
// true/false와 AND/OR/괄호만 포함된 표현식을 평가
const sanitizedExpression = expression
.replace(/\bAND\b/g, "&&")
.replace(/\bOR\b/g, "||")
.replace(/\btrue\b/g, "true")
.replace(/\bfalse\b/g, "false");
// 보안을 위해 허용된 문자만 확인
if (!/^[true|false|\s|&|\||\(|\)]+$/.test(sanitizedExpression)) {
logger.warn(`Invalid expression: ${expression}`);
return false;
}
// Function constructor를 사용한 안전한 평가
const result = new Function(`return ${sanitizedExpression}`)();
return Boolean(result);
} catch (error) {
logger.error(`Error evaluating expression: ${expression}`, error);
return false;
}
}
/**

File diff suppressed because it is too large Load Diff

View File

@@ -5,17 +5,19 @@ import { apiClient, ApiResponse } from "./client";
// 조건부 연결 관련 타입들
export interface ConditionControl {
triggerType: "insert" | "update" | "delete" | "insert_update";
conditionTree: ConditionNode;
conditionTree: ConditionNode | ConditionNode[] | null;
}
export interface ConditionNode {
type: "group" | "condition";
operator?: "AND" | "OR";
children?: ConditionNode[];
id: string; // 고유 ID
type: "condition" | "group-start" | "group-end";
field?: string;
operator_type?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
value?: any;
dataType?: string;
logicalOperator?: "AND" | "OR"; // 다음 조건과의 논리 연산자
groupId?: string; // 그룹 ID (group-start와 group-end가 같은 groupId를 가짐)
groupLevel?: number; // 중첩 레벨 (0, 1, 2, ...)
}
export interface ConnectionCategory {