조건 그룹핑 구현
This commit is contained in:
parent
dbad9bbc0c
commit
41f40ac216
|
|
@ -5,19 +5,21 @@ const prisma = new PrismaClient();
|
||||||
|
|
||||||
// 조건 노드 타입 정의
|
// 조건 노드 타입 정의
|
||||||
interface ConditionNode {
|
interface ConditionNode {
|
||||||
type: "group" | "condition";
|
id: string; // 고유 ID
|
||||||
operator?: "AND" | "OR";
|
type: "condition" | "group-start" | "group-end";
|
||||||
children?: ConditionNode[];
|
|
||||||
field?: string;
|
field?: string;
|
||||||
operator_type?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
|
operator_type?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
|
||||||
value?: any;
|
value?: any;
|
||||||
dataType?: string;
|
dataType?: string;
|
||||||
|
logicalOperator?: "AND" | "OR"; // 다음 조건과의 논리 연산자
|
||||||
|
groupId?: string; // 그룹 ID (group-start와 group-end가 같은 groupId를 가짐)
|
||||||
|
groupLevel?: number; // 중첩 레벨 (0, 1, 2, ...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 조건 제어 정보
|
// 조건 제어 정보
|
||||||
interface ConditionControl {
|
interface ConditionControl {
|
||||||
triggerType: "insert" | "update" | "delete" | "insert_update";
|
triggerType: "insert" | "update" | "delete" | "insert_update";
|
||||||
conditionTree: ConditionNode | null;
|
conditionTree: ConditionNode | ConditionNode[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 연결 카테고리 정보
|
// 연결 카테고리 정보
|
||||||
|
|
@ -237,32 +239,103 @@ export class EventTriggerService {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 조건 평가
|
* 조건 평가 (플랫 구조 + 그룹핑 지원)
|
||||||
*/
|
*/
|
||||||
private static async evaluateCondition(
|
private static async evaluateCondition(
|
||||||
condition: ConditionNode,
|
condition: ConditionNode | ConditionNode[],
|
||||||
data: Record<string, any>
|
data: Record<string, any>
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
if (condition.type === "group") {
|
// 단일 조건인 경우 (하위 호환성)
|
||||||
if (!condition.children || condition.children.length === 0) {
|
if (!Array.isArray(condition)) {
|
||||||
return true;
|
if (condition.type === "condition") {
|
||||||
|
return this.evaluateSingleCondition(condition, data);
|
||||||
}
|
}
|
||||||
|
return true;
|
||||||
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 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
|
|
@ -5,17 +5,19 @@ import { apiClient, ApiResponse } from "./client";
|
||||||
// 조건부 연결 관련 타입들
|
// 조건부 연결 관련 타입들
|
||||||
export interface ConditionControl {
|
export interface ConditionControl {
|
||||||
triggerType: "insert" | "update" | "delete" | "insert_update";
|
triggerType: "insert" | "update" | "delete" | "insert_update";
|
||||||
conditionTree: ConditionNode;
|
conditionTree: ConditionNode | ConditionNode[] | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConditionNode {
|
export interface ConditionNode {
|
||||||
type: "group" | "condition";
|
id: string; // 고유 ID
|
||||||
operator?: "AND" | "OR";
|
type: "condition" | "group-start" | "group-end";
|
||||||
children?: ConditionNode[];
|
|
||||||
field?: string;
|
field?: string;
|
||||||
operator_type?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
|
operator_type?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE";
|
||||||
value?: any;
|
value?: any;
|
||||||
dataType?: string;
|
dataType?: string;
|
||||||
|
logicalOperator?: "AND" | "OR"; // 다음 조건과의 논리 연산자
|
||||||
|
groupId?: string; // 그룹 ID (group-start와 group-end가 같은 groupId를 가짐)
|
||||||
|
groupLevel?: number; // 중첩 레벨 (0, 1, 2, ...)
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ConnectionCategory {
|
export interface ConnectionCategory {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue