// ๐Ÿ”ง Prisma ํด๋ผ์ด์–ธํŠธ ์ค‘๋ณต ์ƒ์„ฑ ๋ฐฉ์ง€ - ๊ธฐ์กด ์ธ์Šคํ„ด์Šค ์žฌ์‚ฌ์šฉ import prisma = require("../config/database"); export interface ControlCondition { id: string; type: "condition" | "group-start" | "group-end"; field?: string; value?: any; operator?: "=" | "!=" | ">" | "<" | ">=" | "<=" | "LIKE" | "IN"; dataType?: "string" | "number" | "date" | "boolean"; logicalOperator?: "AND" | "OR"; groupId?: string; groupLevel?: number; tableType?: "from" | "to"; } export interface ControlAction { id: string; name: string; actionType: "insert" | "update" | "delete"; logicalOperator?: "AND" | "OR"; // ์•ก์…˜ ๊ฐ„ ๋…ผ๋ฆฌ ์—ฐ์‚ฐ์ž (์ฒซ ๋ฒˆ์งธ ์•ก์…˜ ์ œ์™ธ) conditions: ControlCondition[]; fieldMappings: { sourceField?: string; sourceTable?: string; targetField: string; targetTable: string; defaultValue?: any; }[]; splitConfig?: { delimiter?: string; sourceField?: string; targetField?: string; }; // ๐Ÿ†• ๋‹ค์ค‘ ์ปค๋„ฅ์…˜ ์ง€์› ์ถ”๊ฐ€ fromConnection?: { id: number; name?: string; }; toConnection?: { id: number; name?: string; }; targetTable?: string; } export interface ControlPlan { id: string; sourceTable: string; actions: ControlAction[]; } export interface ControlRule { id: string; triggerType: "insert" | "update" | "delete"; conditions: ControlCondition[]; } export class DataflowControlService { /** * ์ œ์–ด๊ด€๋ฆฌ ์‹คํ–‰ ๋ฉ”์ธ ํ•จ์ˆ˜ */ async executeDataflowControl( diagramId: number, relationshipId: string, triggerType: "insert" | "update" | "delete", sourceData: Record, tableName: string ): Promise<{ success: boolean; message: string; executedActions?: any[]; errors?: string[]; }> { try { console.log(`๐ŸŽฏ ์ œ์–ด๊ด€๋ฆฌ ์‹คํ–‰ ์‹œ์ž‘:`, { diagramId, relationshipId, triggerType, sourceData, tableName, }); // ๊ด€๊ณ„๋„ ์ •๋ณด ์กฐํšŒ const diagram = await prisma.dataflow_diagrams.findUnique({ where: { diagram_id: diagramId }, }); if (!diagram) { return { success: false, message: `๊ด€๊ณ„๋„๋ฅผ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. (ID: ${diagramId})`, }; } // ์ œ์–ด ๊ทœ์น™๊ณผ ์‹คํ–‰ ๊ณ„ํš ์ถ”์ถœ (๊ธฐ์กด ๊ตฌ์กฐ + redesigned UI ๊ตฌ์กฐ ์ง€์›) let controlRules: ControlRule[] = []; let executionPlans: ControlPlan[] = []; // ๐Ÿ†• redesigned UI ๊ตฌ์กฐ ์ฒ˜๋ฆฌ if (diagram.relationships && typeof diagram.relationships === "object") { const relationships = diagram.relationships as any; // Case 1: redesigned UI ๋‹จ์ผ ๊ด€๊ณ„ ๊ตฌ์กฐ if (relationships.controlConditions && relationships.fieldMappings) { console.log("๐Ÿ”„ Redesigned UI ๊ตฌ์กฐ ๊ฐ์ง€, ๊ธฐ์กด ๊ตฌ์กฐ๋กœ ๋ณ€ํ™˜ ์ค‘"); // redesigned โ†’ ๊ธฐ์กด ๊ตฌ์กฐ ๋ณ€ํ™˜ controlRules = [ { id: relationshipId, triggerType: triggerType, conditions: relationships.controlConditions || [], }, ]; executionPlans = [ { id: relationshipId, sourceTable: relationships.fromTable || tableName, actions: [ { id: "action_1", name: "์•ก์…˜ 1", actionType: relationships.actionType || "insert", conditions: relationships.actionConditions || [], fieldMappings: relationships.fieldMappings || [], fromConnection: relationships.fromConnection, toConnection: relationships.toConnection, targetTable: relationships.toTable, }, ], }, ]; console.log("โœ… Redesigned โ†’ ๊ธฐ์กด ๊ตฌ์กฐ ๋ณ€ํ™˜ ์™„๋ฃŒ"); } } // ๊ธฐ์กด ๊ตฌ์กฐ ์ฒ˜๋ฆฌ (ํ•˜์œ„ ํ˜ธํ™˜์„ฑ) if (controlRules.length === 0) { controlRules = Array.isArray(diagram.control) ? (diagram.control as unknown as ControlRule[]) : []; executionPlans = Array.isArray(diagram.plan) ? (diagram.plan as unknown as ControlPlan[]) : []; } console.log(`๐Ÿ“‹ ์ œ์–ด ๊ทœ์น™:`, controlRules); console.log(`๐Ÿ“‹ ์‹คํ–‰ ๊ณ„ํš:`, executionPlans); // ํ•ด๋‹น ๊ด€๊ณ„์˜ ์ œ์–ด ๊ทœ์น™ ์ฐพ๊ธฐ const targetRule = controlRules.find( (rule) => rule.id === relationshipId && rule.triggerType === triggerType ); if (!targetRule) { console.log( `โš ๏ธ ํ•ด๋‹น ๊ด€๊ณ„์˜ ์ œ์–ด ๊ทœ์น™์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค: ${relationshipId}` ); return { success: true, message: "ํ•ด๋‹น ๊ด€๊ณ„์˜ ์ œ์–ด ๊ทœ์น™์ด ์—†์Šต๋‹ˆ๋‹ค.", }; } // ์ œ์–ด ์กฐ๊ฑด ๊ฒ€์ฆ const conditionResult = await this.evaluateConditions( targetRule.conditions, sourceData ); console.log(`๐Ÿ” [์ „์ฒด ์‹คํ–‰ ์กฐ๊ฑด] ๊ฒ€์ฆ ๊ฒฐ๊ณผ:`, conditionResult); if (!conditionResult.satisfied) { return { success: true, message: `์ œ์–ด ์กฐ๊ฑด์„ ๋งŒ์กฑํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: ${conditionResult.reason}`, }; } // ์‹คํ–‰ ๊ณ„ํš ์ฐพ๊ธฐ const targetPlan = executionPlans.find( (plan) => plan.id === relationshipId ); if (!targetPlan) { return { success: true, message: "์‹คํ–‰ํ•  ๊ณ„ํš์ด ์—†์Šต๋‹ˆ๋‹ค.", }; } // ์•ก์…˜ ์‹คํ–‰ (๋…ผ๋ฆฌ ์—ฐ์‚ฐ์ž ์ง€์›) const executedActions = []; const errors = []; let previousActionSuccess = false; let shouldSkipRemainingActions = false; for (let i = 0; i < targetPlan.actions.length; i++) { const action = targetPlan.actions[i]; try { // ๋…ผ๋ฆฌ ์—ฐ์‚ฐ์ž์— ๋”ฐ๋ฅธ ์‹คํ–‰ ์—ฌ๋ถ€ ๊ฒฐ์ • if ( i > 0 && action.logicalOperator === "OR" && previousActionSuccess ) { console.log( `โญ๏ธ OR ์กฐ๊ฑด์œผ๋กœ ์ธํ•ด ์•ก์…˜ ๊ฑด๋„ˆ๋›ฐ๊ธฐ: ${action.name} (์ด์ „ ์•ก์…˜ ์„ฑ๊ณต)` ); continue; } if (shouldSkipRemainingActions && action.logicalOperator === "AND") { console.log( `โญ๏ธ ์ด์ „ ์•ก์…˜ ์‹คํŒจ๋กœ ์ธํ•ด AND ์ฒด์ธ ์•ก์…˜ ๊ฑด๋„ˆ๋›ฐ๊ธฐ: ${action.name}` ); continue; } console.log(`โšก ์•ก์…˜ ์‹คํ–‰: ${action.name} (${action.actionType})`); console.log(`๐Ÿ“‹ ์•ก์…˜ ์ƒ์„ธ ์ •๋ณด:`, { actionId: action.id, actionName: action.name, actionType: action.actionType, logicalOperator: action.logicalOperator, conditions: action.conditions, fieldMappings: action.fieldMappings, fromConnection: (action as any).fromConnection, toConnection: (action as any).toConnection, targetTable: (action as any).targetTable, }); // ๐Ÿ†• ๋‹ค์ค‘ ์ปค๋„ฅ์…˜ ์ง€์› ์•ก์…˜ ์‹คํ–‰ const actionResult = await this.executeMultiConnectionAction( action, sourceData, targetPlan.sourceTable ); executedActions.push({ actionId: action.id, actionName: action.name, actionType: action.actionType, result: actionResult, timestamp: new Date().toISOString(), }); previousActionSuccess = actionResult?.success !== false; // ์•ก์…˜ ์กฐ๊ฑด ๊ฒ€์ฆ์€ ์ด๋ฏธ ์œ„์—์„œ ์ฒ˜๋ฆฌ๋จ (์ค‘๋ณต ์ œ๊ฑฐ) } catch (error) { console.error(`โŒ ์•ก์…˜ ์‹คํ–‰ ์˜ค๋ฅ˜: ${action.name}`, error); const errorMessage = error instanceof Error ? error.message : String(error); errors.push(`์•ก์…˜ '${action.name}' ์‹คํ–‰ ์˜ค๋ฅ˜: ${errorMessage}`); previousActionSuccess = false; if (action.logicalOperator === "AND") { shouldSkipRemainingActions = true; } } } return { success: true, message: `์ œ์–ด๊ด€๋ฆฌ ์‹คํ–‰ ์™„๋ฃŒ. ${executedActions.length}๊ฐœ ์•ก์…˜ ์‹คํ–‰๋จ.`, executedActions, errors: errors.length > 0 ? errors : undefined, }; } catch (error) { console.error("โŒ ์ œ์–ด๊ด€๋ฆฌ ์‹คํ–‰ ์˜ค๋ฅ˜:", error); const errorMessage = error instanceof Error ? error.message : String(error); return { success: false, message: `์ œ์–ด๊ด€๋ฆฌ ์‹คํ–‰ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: ${errorMessage}`, }; } } /** * ๐Ÿ†• ๋‹ค์ค‘ ์ปค๋„ฅ์…˜ ์•ก์…˜ ์‹คํ–‰ */ private async executeMultiConnectionAction( action: ControlAction, sourceData: Record, sourceTable: string ): Promise { try { const extendedAction = action as any; // redesigned UI ๊ตฌ์กฐ ์ ‘๊ทผ // ์—ฐ๊ฒฐ ์ •๋ณด ์ถ”์ถœ const fromConnection = extendedAction.fromConnection || { id: 0 }; const toConnection = extendedAction.toConnection || { id: 0 }; const targetTable = extendedAction.targetTable || sourceTable; console.log(`๐Ÿ”— ๋‹ค์ค‘ ์ปค๋„ฅ์…˜ ์•ก์…˜ ์‹คํ–‰:`, { actionType: action.actionType, fromConnectionId: fromConnection.id, toConnectionId: toConnection.id, sourceTable, targetTable, }); // MultiConnectionQueryService import ํ•„์š” const { MultiConnectionQueryService } = await import( "./multiConnectionQueryService" ); const multiConnService = new MultiConnectionQueryService(); switch (action.actionType) { case "insert": return await this.executeMultiConnectionInsert( action, sourceData, sourceTable, targetTable, fromConnection.id, toConnection.id, multiConnService ); case "update": return await this.executeMultiConnectionUpdate( action, sourceData, sourceTable, targetTable, fromConnection.id, toConnection.id, multiConnService ); case "delete": return await this.executeMultiConnectionDelete( action, sourceData, sourceTable, targetTable, fromConnection.id, toConnection.id, multiConnService ); default: throw new Error(`์ง€์›๋˜์ง€ ์•Š๋Š” ์•ก์…˜ ํƒ€์ž…: ${action.actionType}`); } } catch (error) { console.error(`โŒ ๋‹ค์ค‘ ์ปค๋„ฅ์…˜ ์•ก์…˜ ์‹คํ–‰ ์‹คํŒจ:`, error); return { success: false, message: error instanceof Error ? error.message : String(error), }; } } /** * ๐Ÿ†• ๋‹ค์ค‘ ์ปค๋„ฅ์…˜ INSERT ์‹คํ–‰ */ private async executeMultiConnectionInsert( action: ControlAction, sourceData: Record, sourceTable: string, targetTable: string, fromConnectionId: number, toConnectionId: number, multiConnService: any ): Promise { try { // ํ•„๋“œ ๋งคํ•‘ ์ ์šฉ const mappedData: Record = {}; for (const mapping of action.fieldMappings) { const sourceField = mapping.sourceField; const targetField = mapping.targetField; if (mapping.defaultValue !== undefined) { // ๊ธฐ๋ณธ๊ฐ’ ์‚ฌ์šฉ mappedData[targetField] = mapping.defaultValue; } else if (sourceField && sourceData[sourceField] !== undefined) { // ์†Œ์Šค ๋ฐ์ดํ„ฐ์—์„œ ๋งคํ•‘ mappedData[targetField] = sourceData[sourceField]; } } console.log(`๐Ÿ“‹ ๋งคํ•‘๋œ ๋ฐ์ดํ„ฐ:`, mappedData); // ๋Œ€์ƒ ์—ฐ๊ฒฐ์— ๋ฐ์ดํ„ฐ ์‚ฝ์ž… const result = await multiConnService.insertDataToConnection( toConnectionId, targetTable, mappedData ); return { success: true, message: `${targetTable}์— ๋ฐ์ดํ„ฐ ์‚ฝ์ž… ์™„๋ฃŒ`, insertedCount: 1, data: result, }; } catch (error) { console.error(`โŒ INSERT ์‹คํ–‰ ์‹คํŒจ:`, error); return { success: false, message: error instanceof Error ? error.message : String(error), }; } } /** * ๐Ÿ†• ๋‹ค์ค‘ ์ปค๋„ฅ์…˜ UPDATE ์‹คํ–‰ */ private async executeMultiConnectionUpdate( action: ControlAction, sourceData: Record, sourceTable: string, targetTable: string, fromConnectionId: number, toConnectionId: number, multiConnService: any ): Promise { try { // UPDATE ๋กœ์ง ๊ตฌํ˜„ (ํ–ฅํ›„ ํ™•์žฅ) console.log(`โš ๏ธ UPDATE ์•ก์…˜์€ ํ–ฅํ›„ ๊ตฌํ˜„ ์˜ˆ์ •`); return { success: true, message: "UPDATE ์•ก์…˜ ์‹คํ–‰๋จ (ํ–ฅํ›„ ๊ตฌํ˜„)", updatedCount: 0, }; } catch (error) { return { success: false, message: error instanceof Error ? error.message : String(error), }; } } /** * ๐Ÿ†• ๋‹ค์ค‘ ์ปค๋„ฅ์…˜ DELETE ์‹คํ–‰ */ private async executeMultiConnectionDelete( action: ControlAction, sourceData: Record, sourceTable: string, targetTable: string, fromConnectionId: number, toConnectionId: number, multiConnService: any ): Promise { try { // DELETE ๋กœ์ง ๊ตฌํ˜„ (ํ–ฅํ›„ ํ™•์žฅ) console.log(`โš ๏ธ DELETE ์•ก์…˜์€ ํ–ฅํ›„ ๊ตฌํ˜„ ์˜ˆ์ •`); return { success: true, message: "DELETE ์•ก์…˜ ์‹คํ–‰๋จ (ํ–ฅํ›„ ๊ตฌํ˜„)", deletedCount: 0, }; } catch (error) { return { success: false, message: error instanceof Error ? error.message : String(error), }; } } /** * ์•ก์…˜๋ณ„ ์กฐ๊ฑด ํ‰๊ฐ€ (๋™์  ํ…Œ์ด๋ธ” ์ง€์›) */ private async evaluateActionConditions( action: ControlAction, sourceData: Record, sourceTable: string ): Promise<{ satisfied: boolean; reason?: string }> { if (!action.conditions || action.conditions.length === 0) { return { satisfied: true }; } try { // ์กฐ๊ฑด๋ณ„๋กœ ํ…Œ์ด๋ธ” ํƒ€์ž…์— ๋”ฐ๋ผ ๋ฐ์ดํ„ฐ ์†Œ์Šค ๊ฒฐ์ • for (const condition of action.conditions) { if (!condition.field || condition.value === undefined) { continue; } let dataToCheck: Record; let tableName: string; // UPDATE/DELETE ์•ก์…˜์˜ ๊ฒฝ์šฐ ์กฐ๊ฑด์€ ํ•ญ์ƒ ๋Œ€์ƒ ํ…Œ์ด๋ธ”์—์„œ ํ™•์ธ (์—…๋ฐ์ดํŠธ/์‚ญ์ œํ•  ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ์ฐพ๋Š” ์šฉ๋„) if ( action.actionType === "update" || action.actionType === "delete" || condition.tableType === "to" ) { // ๋Œ€์ƒ ํ…Œ์ด๋ธ”(to)์—์„œ ์กฐ๊ฑด ํ™•์ธ const targetTable = action.fieldMappings?.[0]?.targetTable; if (!targetTable) { console.error("โŒ ๋Œ€์ƒ ํ…Œ์ด๋ธ”์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค:", action); return { satisfied: false, reason: "๋Œ€์ƒ ํ…Œ์ด๋ธ” ์ •๋ณด๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.", }; } tableName = targetTable; console.log( `๐Ÿ” ๋Œ€์ƒ ํ…Œ์ด๋ธ”(${tableName})์—์„œ ์กฐ๊ฑด ํ™•์ธ: ${condition.field} = ${condition.value} (${action.actionType.toUpperCase()} ์•ก์…˜)` ); // ๋Œ€์ƒ ํ…Œ์ด๋ธ”์—์„œ ์ปฌ๋Ÿผ ์กด์žฌ ์—ฌ๋ถ€ ๋จผ์ € ํ™•์ธ const columnExists = await this.checkColumnExists( tableName, condition.field ); if (!columnExists) { console.error( `โŒ ์ปฌ๋Ÿผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: ${tableName}.${condition.field}` ); return { satisfied: false, reason: `์ปฌ๋Ÿผ์ด ์กด์žฌํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค: ${tableName}.${condition.field}`, }; } // ๋Œ€์ƒ ํ…Œ์ด๋ธ”์—์„œ ์กฐ๊ฑด์— ๋งž๋Š” ๋ฐ์ดํ„ฐ ์กฐํšŒ const queryResult = await prisma.$queryRawUnsafe( `SELECT ${condition.field} FROM ${tableName} WHERE ${condition.field} = $1 LIMIT 1`, condition.value ); dataToCheck = Array.isArray(queryResult) && queryResult.length > 0 ? (queryResult[0] as Record) : {}; } else { // ์†Œ์Šค ํ…Œ์ด๋ธ”(from) ๋˜๋Š” ๊ธฐ๋ณธ๊ฐ’์—์„œ ์กฐ๊ฑด ํ™•์ธ tableName = sourceTable; dataToCheck = sourceData; console.log( `๐Ÿ” ์†Œ์Šค ํ…Œ์ด๋ธ”(${tableName})์—์„œ ์กฐ๊ฑด ํ™•์ธ: ${condition.field} = ${condition.value}` ); } const fieldValue = dataToCheck[condition.field]; console.log( `๐Ÿ” [์•ก์…˜ ์‹คํ–‰ ์กฐ๊ฑด] ์กฐ๊ฑด ํ‰๊ฐ€ ๊ฒฐ๊ณผ: ${condition.field} = ${condition.value} (ํ…Œ์ด๋ธ” ${tableName} ์‹ค์ œ๊ฐ’: ${fieldValue})` ); // ์•ก์…˜ ์‹คํ–‰ ์กฐ๊ฑด ํ‰๊ฐ€ if ( action.actionType === "update" || action.actionType === "delete" || condition.tableType === "to" ) { // UPDATE/DELETE ์•ก์…˜์ด๊ฑฐ๋‚˜ ๋Œ€์ƒ ํ…Œ์ด๋ธ”์˜ ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ ์กด์žฌ ์—ฌ๋ถ€๋กœ ํŒ๋‹จ if (!fieldValue || fieldValue !== condition.value) { return { satisfied: false, reason: `[์•ก์…˜ ์‹คํ–‰ ์กฐ๊ฑด] ์กฐ๊ฑด ๋ฏธ์ถฉ์กฑ: ${tableName}.${condition.field} = ${condition.value} (ํ…Œ์ด๋ธ” ์‹ค์ œ๊ฐ’: ${fieldValue})`, }; } } else { // ์†Œ์Šค ํ…Œ์ด๋ธ”์˜ ๊ฒฝ์šฐ ๊ฐ’ ๋น„๊ต if (fieldValue !== condition.value) { return { satisfied: false, reason: `[์•ก์…˜ ์‹คํ–‰ ์กฐ๊ฑด] ์กฐ๊ฑด ๋ฏธ์ถฉ์กฑ: ${tableName}.${condition.field} = ${condition.value} (ํ…Œ์ด๋ธ” ์‹ค์ œ๊ฐ’: ${fieldValue})`, }; } } } return { satisfied: true }; } catch (error) { console.error("โŒ ์•ก์…˜ ์กฐ๊ฑด ํ‰๊ฐ€ ์˜ค๋ฅ˜:", error); const errorMessage = error instanceof Error ? error.message : String(error); return { satisfied: false, reason: `์•ก์…˜ ์กฐ๊ฑด ํ‰๊ฐ€ ์˜ค๋ฅ˜: ${errorMessage}`, }; } } /** * ์กฐ๊ฑด ํ‰๊ฐ€ */ private async evaluateConditions( conditions: ControlCondition[], data: Record ): Promise<{ satisfied: boolean; reason?: string }> { if (!conditions || conditions.length === 0) { return { satisfied: true }; } try { // ์กฐ๊ฑด์„ SQL WHERE ์ ˆ๋กœ ๋ณ€ํ™˜ const whereClause = this.buildWhereClause(conditions, data); console.log(`๐Ÿ” [์ „์ฒด ์‹คํ–‰ ์กฐ๊ฑด] ์ƒ์„ฑ๋œ WHERE ์ ˆ:`, whereClause); // ์ „์ฒด ์‹คํ–‰ ์กฐ๊ฑด ํ‰๊ฐ€ (ํผ ๋ฐ์ดํ„ฐ ๊ธฐ๋ฐ˜) for (const condition of conditions) { if ( condition.type === "condition" && condition.field && condition.operator ) { const fieldValue = data[condition.field]; const conditionValue = condition.value; console.log( `๐Ÿ” [์ „์ฒด ์‹คํ–‰ ์กฐ๊ฑด] ์กฐ๊ฑด ํ‰๊ฐ€: ${condition.field} ${condition.operator} ${conditionValue} (ํผ ๋ฐ์ดํ„ฐ ์‹ค์ œ๊ฐ’: ${fieldValue})` ); const result = this.evaluateSingleCondition( fieldValue, condition.operator, conditionValue, condition.dataType || "string" ); if (!result) { return { satisfied: false, reason: `[์ „์ฒด ์‹คํ–‰ ์กฐ๊ฑด] ์กฐ๊ฑด ๋ฏธ์ถฉ์กฑ: ${condition.field} ${condition.operator} ${conditionValue} (ํผ ๋ฐ์ดํ„ฐ ๊ธฐ์ค€)`, }; } } } return { satisfied: true }; } catch (error) { console.error("์กฐ๊ฑด ํ‰๊ฐ€ ์˜ค๋ฅ˜:", error); const errorMessage = error instanceof Error ? error.message : String(error); return { satisfied: false, reason: `์กฐ๊ฑด ํ‰๊ฐ€ ์˜ค๋ฅ˜: ${errorMessage}`, }; } } /** * ๋‹จ์ผ ์กฐ๊ฑด ํ‰๊ฐ€ */ private evaluateSingleCondition( fieldValue: any, operator: string, conditionValue: any, dataType: string ): boolean { // ํƒ€์ž… ๋ณ€ํ™˜ let actualValue = fieldValue; let expectedValue = conditionValue; if (dataType === "number") { actualValue = parseFloat(fieldValue) || 0; expectedValue = parseFloat(conditionValue) || 0; } else if (dataType === "string") { actualValue = String(fieldValue || ""); expectedValue = String(conditionValue || ""); } // ์—ฐ์‚ฐ์ž๋ณ„ ํ‰๊ฐ€ switch (operator) { case "=": return actualValue === expectedValue; case "!=": return actualValue !== expectedValue; case ">": return actualValue > expectedValue; case "<": return actualValue < expectedValue; case ">=": return actualValue >= expectedValue; case "<=": return actualValue <= expectedValue; case "LIKE": return String(actualValue).includes(String(expectedValue)); default: console.warn(`์ง€์›๋˜์ง€ ์•Š๋Š” ์—ฐ์‚ฐ์ž: ${operator}`); return false; } } /** * WHERE ์ ˆ ์ƒ์„ฑ (๋ณต์žกํ•œ ๊ทธ๋ฃน ์กฐ๊ฑด ์ฒ˜๋ฆฌ) */ private buildWhereClause( conditions: ControlCondition[], data: Record ): string { // ์‹ค์ œ๋กœ๋Š” ๋” ๋ณต์žกํ•œ ๊ทธ๋ฃน ์ฒ˜๋ฆฌ ๋กœ์ง์ด ํ•„์š” // ํ˜„์žฌ๋Š” ๊ฐ„๋‹จํ•œ AND/OR ์ฒ˜๋ฆฌ๋งŒ ๊ตฌํ˜„ const clauses = []; for (const condition of conditions) { if (condition.type === "condition") { const clause = `${condition.field} ${condition.operator} '${condition.value}'`; clauses.push(clause); } } return clauses.join(" AND "); } /** * ์•ก์…˜ ์‹คํ–‰ */ private async executeAction( action: ControlAction, sourceData: Record ): Promise { console.log(`๐Ÿš€ ์•ก์…˜ ์‹คํ–‰: ${action.actionType}`, action); switch (action.actionType) { case "insert": return await this.executeInsertAction(action, sourceData); case "update": return await this.executeUpdateAction(action, sourceData); case "delete": return await this.executeDeleteAction(action, sourceData); default: throw new Error(`์ง€์›๋˜์ง€ ์•Š๋Š” ์•ก์…˜ ํƒ€์ž…: ${action.actionType}`); } } /** * INSERT ์•ก์…˜ ์‹คํ–‰ */ private async executeInsertAction( action: ControlAction, sourceData: Record ): Promise { const results = []; for (const mapping of action.fieldMappings) { const { targetTable, targetField, defaultValue, sourceField } = mapping; // ์‚ฝ์ž…ํ•  ๋ฐ์ดํ„ฐ ์ค€๋น„ const insertData: Record = {}; if (sourceField && sourceData[sourceField]) { insertData[targetField] = sourceData[sourceField]; } else if (defaultValue !== undefined) { insertData[targetField] = defaultValue; } // ๊ธฐ๋ณธ ํ•„๋“œ ์ถ”๊ฐ€ insertData.created_at = new Date(); insertData.updated_at = new Date(); console.log(`๐Ÿ“ INSERT ์‹คํ–‰: ${targetTable}.${targetField}`, insertData); try { // ๋™์  ํ…Œ์ด๋ธ” INSERT ์‹คํ–‰ const result = await prisma.$executeRawUnsafe( ` INSERT INTO ${targetTable} (${Object.keys(insertData).join(", ")}) VALUES (${Object.keys(insertData) .map(() => "?") .join(", ")}) `, ...Object.values(insertData) ); results.push({ table: targetTable, field: targetField, data: insertData, result, }); console.log(`โœ… INSERT ์„ฑ๊ณต: ${targetTable}.${targetField}`); } catch (error) { console.error(`โŒ INSERT ์‹คํŒจ: ${targetTable}.${targetField}`, error); throw error; } } return results; } /** * UPDATE ์•ก์…˜ ์‹คํ–‰ */ private async executeUpdateAction( action: ControlAction, sourceData: Record ): Promise { console.log(`๐Ÿ”„ UPDATE ์•ก์…˜ ์‹คํ–‰: ${action.name}`); console.log(`๐Ÿ“‹ ์•ก์…˜ ์ •๋ณด:`, JSON.stringify(action, null, 2)); console.log(`๐Ÿ“‹ ์†Œ์Šค ๋ฐ์ดํ„ฐ:`, JSON.stringify(sourceData, null, 2)); // fieldMappings์—์„œ ๋Œ€์ƒ ํ…Œ์ด๋ธ”๊ณผ ํ•„๋“œ ์ •๋ณด ์ถ”์ถœ if (!action.fieldMappings || action.fieldMappings.length === 0) { console.error("โŒ fieldMappings๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค:", action); throw new Error("UPDATE ์•ก์…˜์—๋Š” fieldMappings๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."); } console.log(`๐ŸŽฏ ์ฒ˜๋ฆฌํ•  ๋งคํ•‘ ๊ฐœ์ˆ˜: ${action.fieldMappings.length}`); const results = []; // ๊ฐ ํ•„๋“œ ๋งคํ•‘๋ณ„๋กœ ๊ฐœ๋ณ„ UPDATE ์‹คํ–‰ for (let i = 0; i < action.fieldMappings.length; i++) { const mapping = action.fieldMappings[i]; const targetTable = mapping.targetTable; const targetField = mapping.targetField; const updateValue = mapping.defaultValue || (mapping.sourceField ? sourceData[mapping.sourceField] : null); console.log(`๐ŸŽฏ ๋งคํ•‘ ${i + 1}/${action.fieldMappings.length}:`, { targetTable, targetField, updateValue, defaultValue: mapping.defaultValue, sourceField: mapping.sourceField, }); if (!targetTable || !targetField) { console.error("โŒ ํ•„์ˆ˜ ํ•„๋“œ๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค:", { targetTable, targetField }); continue; // ๋‹ค์Œ ๋งคํ•‘์œผ๋กœ ๊ณ„์† } try { // WHERE ์กฐ๊ฑด ๊ตฌ์„ฑ let whereClause = ""; const whereValues: any[] = []; // action.conditions์—์„œ WHERE ์กฐ๊ฑด ์ƒ์„ฑ (PostgreSQL ํ˜•์‹) let conditionParamIndex = 2; // $1์€ SET ๊ฐ’์šฉ, $2๋ถ€ํ„ฐ WHERE ์กฐ๊ฑด์šฉ if (action.conditions && Array.isArray(action.conditions)) { const conditions = action.conditions .filter((cond) => cond.field && cond.value !== undefined) .map((cond) => `${cond.field} = $${conditionParamIndex++}`); if (conditions.length > 0) { whereClause = conditions.join(" AND "); whereValues.push( ...action.conditions .filter((cond) => cond.field && cond.value !== undefined) .map((cond) => cond.value) ); } } // WHERE ์กฐ๊ฑด์ด ์—†์œผ๋ฉด ๊ธฐ๋ณธ ์กฐ๊ฑด ์‚ฌ์šฉ (๊ฐ™์€ ํ•„๋“œ๋กœ ์ฐพ๊ธฐ) if (!whereClause) { whereClause = `${targetField} = $${conditionParamIndex}`; whereValues.push("๊น€์ฒ ์ˆ˜"); // ๊ธฐ์กด ๊ฐ’์œผ๋กœ ์ฐพ๊ธฐ } console.log( `๐Ÿ“ UPDATE ์ฟผ๋ฆฌ ์ค€๋น„ (${i + 1}/${action.fieldMappings.length}):`, { targetTable, targetField, updateValue, whereClause, whereValues, } ); // ๋™์  ํ…Œ์ด๋ธ” UPDATE ์‹คํ–‰ (PostgreSQL ํ˜•์‹) const updateQuery = `UPDATE ${targetTable} SET ${targetField} = $1 WHERE ${whereClause}`; const allValues = [updateValue, ...whereValues]; console.log( `๐Ÿš€ ์‹คํ–‰ํ•  ์ฟผ๋ฆฌ (${i + 1}/${action.fieldMappings.length}):`, updateQuery ); console.log(`๐Ÿ“Š ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ:`, allValues); const result = await prisma.$executeRawUnsafe( updateQuery, ...allValues ); console.log( `โœ… UPDATE ์„ฑ๊ณต (${i + 1}/${action.fieldMappings.length}):`, { table: targetTable, field: targetField, value: updateValue, affectedRows: result, } ); results.push({ message: `UPDATE ์„ฑ๊ณต: ${targetTable}.${targetField} = ${updateValue}`, affectedRows: result, targetTable, targetField, updateValue, }); } catch (error) { console.error( `โŒ UPDATE ์‹คํŒจ (${i + 1}/${action.fieldMappings.length}):`, { table: targetTable, field: targetField, value: updateValue, error: error, } ); // ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•ด๋„ ๋‹ค์Œ ๋งคํ•‘์€ ๊ณ„์† ์ฒ˜๋ฆฌ results.push({ message: `UPDATE ์‹คํŒจ: ${targetTable}.${targetField} = ${updateValue}`, error: error instanceof Error ? error.message : String(error), targetTable, targetField, updateValue, }); } } // ์ „์ฒด ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜ const successCount = results.filter((r) => !r.error).length; const totalCount = results.length; console.log(`๐ŸŽฏ ์ „์ฒด UPDATE ๊ฒฐ๊ณผ: ${successCount}/${totalCount} ์„ฑ๊ณต`); return { message: `UPDATE ์™„๋ฃŒ: ${successCount}/${totalCount} ์„ฑ๊ณต`, results, successCount, totalCount, }; } /** * DELETE ์•ก์…˜ ์‹คํ–‰ - ์กฐ๊ฑด ๊ธฐ๋ฐ˜์œผ๋กœ๋งŒ ์‚ญ์ œ */ private async executeDeleteAction( action: ControlAction, sourceData: Record ): Promise { console.log(`๐Ÿ—‘๏ธ DELETE ์•ก์…˜ ์‹คํ–‰ ์‹œ์ž‘:`, { actionName: action.name, conditions: action.conditions, }); // DELETE๋Š” ์กฐ๊ฑด์ด ํ•„์ˆ˜ if (!action.conditions || action.conditions.length === 0) { throw new Error( "DELETE ์•ก์…˜์—๋Š” ๋ฐ˜๋“œ์‹œ ์กฐ๊ฑด์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ „์ฒด ํ…Œ์ด๋ธ” ์‚ญ์ œ๋Š” ์œ„ํ—˜ํ•ฉ๋‹ˆ๋‹ค." ); } const results = []; // ์กฐ๊ฑด์—์„œ ํ…Œ์ด๋ธ”๋ณ„๋กœ ๊ทธ๋ฃนํ™”ํ•˜์—ฌ ์‚ญ์ œ ์‹คํ–‰ const tableGroups = new Map(); for (const condition of action.conditions) { if ( condition.type === "condition" && condition.field && condition.value !== undefined ) { // ์กฐ๊ฑด์—์„œ ํ…Œ์ด๋ธ”๋ช…์„ ์ถ”์ถœ (ํ…Œ์ด๋ธ”๋ช….ํ•„๋“œ๋ช… ํ˜•์‹์ด๊ฑฐ๋‚˜ ๊ธฐ๋ณธ ์†Œ์Šค ํ…Œ์ด๋ธ”) const parts = condition.field.split("."); let tableName: string; let fieldName: string; if (parts.length === 2) { // "ํ…Œ์ด๋ธ”๋ช….ํ•„๋“œ๋ช…" ํ˜•์‹ tableName = parts[0]; fieldName = parts[1]; } else { // ํ•„๋“œ๋ช…๋งŒ ์žˆ๋Š” ๊ฒฝ์šฐ, ์กฐ๊ฑด์— ๋ช…์‹œ๋œ ํ…Œ์ด๋ธ” ๋˜๋Š” ์†Œ์Šค ํ…Œ์ด๋ธ” ์‚ฌ์šฉ // fieldMappings์ด ์žˆ๋‹ค๋ฉด targetTable ์‚ฌ์šฉ, ์—†๋‹ค๋ฉด ์—๋Ÿฌ if (action.fieldMappings && action.fieldMappings.length > 0) { tableName = action.fieldMappings[0].targetTable; } else { throw new Error( `DELETE ์กฐ๊ฑด์—์„œ ํ…Œ์ด๋ธ”์„ ๊ฒฐ์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ํ•„๋“œ๋ฅผ "ํ…Œ์ด๋ธ”๋ช….ํ•„๋“œ๋ช…" ํ˜•์‹์œผ๋กœ ์ง€์ •ํ•˜๊ฑฐ๋‚˜ fieldMappings์— targetTable์„ ์„ค์ •ํ•˜์„ธ์š”.` ); } fieldName = condition.field; } if (!tableGroups.has(tableName)) { tableGroups.set(tableName, []); } tableGroups.get(tableName)!.push({ field: fieldName, value: condition.value, operator: condition.operator || "=", }); } } if (tableGroups.size === 0) { throw new Error("DELETE ์•ก์…˜์—์„œ ์œ ํšจํ•œ ์กฐ๊ฑด์„ ์ฐพ์„ ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค."); } console.log( `๐ŸŽฏ ์‚ญ์ œ ๋Œ€์ƒ ํ…Œ์ด๋ธ”: ${Array.from(tableGroups.keys()).join(", ")}` ); // ๊ฐ ํ…Œ์ด๋ธ”๋ณ„๋กœ DELETE ์‹คํ–‰ for (const [tableName, conditions] of tableGroups) { try { console.log(`๐Ÿ—‘๏ธ ${tableName} ํ…Œ์ด๋ธ”์—์„œ ์‚ญ์ œ ์‹คํ–‰:`, conditions); // WHERE ์กฐ๊ฑด ๊ตฌ์„ฑ let conditionParamIndex = 1; const whereConditions = conditions.map( (cond) => `${cond.field} ${cond.operator} $${conditionParamIndex++}` ); const whereClause = whereConditions.join(" AND "); const whereValues = conditions.map((cond) => cond.value); console.log(`๐Ÿ“ DELETE ์ฟผ๋ฆฌ ์ค€๋น„:`, { tableName, whereClause, whereValues, }); // ๋™์  ํ…Œ์ด๋ธ” DELETE ์‹คํ–‰ (PostgreSQL ํ˜•์‹) const deleteQuery = `DELETE FROM ${tableName} WHERE ${whereClause}`; console.log(`๐Ÿš€ ์‹คํ–‰ํ•  ์ฟผ๋ฆฌ:`, deleteQuery); console.log(`๐Ÿ“Š ์ฟผ๋ฆฌ ํŒŒ๋ผ๋ฏธํ„ฐ:`, whereValues); const result = await prisma.$executeRawUnsafe( deleteQuery, ...whereValues ); console.log(`โœ… DELETE ์„ฑ๊ณต:`, { table: tableName, affectedRows: result, whereClause, }); results.push({ message: `DELETE ์„ฑ๊ณต: ${tableName}์—์„œ ${result}๊ฐœ ํ–‰ ์‚ญ์ œ`, affectedRows: result, targetTable: tableName, whereClause, }); } catch (error) { console.error(`โŒ DELETE ์‹คํŒจ:`, { table: tableName, error: error, }); const userFriendlyMessage = error instanceof Error ? error.message : String(error); results.push({ message: `DELETE ์‹คํŒจ: ${tableName}`, error: userFriendlyMessage, targetTable: tableName, }); } } // ์ „์ฒด ๊ฒฐ๊ณผ ๋ฐ˜ํ™˜ const successCount = results.filter((r) => !r.error).length; const totalCount = results.length; console.log(`๐ŸŽฏ ์ „์ฒด DELETE ๊ฒฐ๊ณผ: ${successCount}/${totalCount} ์„ฑ๊ณต`); return { message: `DELETE ์™„๋ฃŒ: ${successCount}/${totalCount} ์„ฑ๊ณต`, results, successCount, totalCount, }; } /** * ํ…Œ์ด๋ธ”์— ํŠน์ • ์ปฌ๋Ÿผ์ด ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธ */ private async checkColumnExists( tableName: string, columnName: string ): Promise { try { const result = await prisma.$queryRawUnsafe>( ` SELECT EXISTS ( SELECT 1 FROM information_schema.columns WHERE table_name = $1 AND column_name = $2 AND table_schema = 'public' ) as exists `, tableName, columnName ); return result[0]?.exists || false; } catch (error) { console.error( `โŒ ์ปฌ๋Ÿผ ์กด์žฌ ์—ฌ๋ถ€ ํ™•์ธ ์˜ค๋ฅ˜: ${tableName}.${columnName}`, error ); return false; } } }