import { query, queryOne, transaction } from "../database/db"; import { logger } from "../utils/logger"; // 테이블 관계 생성 데이터 타입 interface CreateTableRelationshipData { diagramId?: number; // 기존 관계도에 추가하는 경우 relationshipName: string; fromTableName: string; fromColumnName: string; toTableName: string; toColumnName: string; relationshipType: string; connectionType: string; companyCode: string; settings: any; createdBy: string; } // 테이블 관계 수정 데이터 타입 interface UpdateTableRelationshipData { relationshipName?: string; fromTableName?: string; fromColumnName?: string; toTableName?: string; toColumnName?: string; relationshipType?: string; connectionType?: string; settings?: any; updatedBy: string; } export class DataflowService { /** * 테이블 관계 생성 */ async createTableRelationship(data: CreateTableRelationshipData) { try { logger.info("DataflowService: 테이블 관계 생성 시작", data); // diagram_id 결정 로직 let diagramId = data.diagramId; if (!diagramId) { // 새로운 관계도인 경우, 새로운 diagram_id 생성 // 현재 최대 diagram_id + 1 const maxDiagramId = await queryOne<{ diagram_id: number }>( `SELECT diagram_id FROM table_relationships WHERE company_code = $1 ORDER BY diagram_id DESC LIMIT 1`, [data.companyCode] ); diagramId = (maxDiagramId?.diagram_id || 0) + 1; } // 중복 관계 확인 (같은 diagram_id 내에서) const existingRelationship = await queryOne( `SELECT * FROM table_relationships WHERE diagram_id = $1 AND from_table_name = $2 AND from_column_name = $3 AND to_table_name = $4 AND to_column_name = $5 AND company_code = $6 AND is_active = 'Y'`, [ diagramId, data.fromTableName, data.fromColumnName, data.toTableName, data.toColumnName, data.companyCode, ] ); if (existingRelationship) { throw new Error( `이미 존재하는 관계입니다: ${data.fromTableName}.${data.fromColumnName} → ${data.toTableName}.${data.toColumnName}` ); } // 새 관계 생성 const relationship = await queryOne( `INSERT INTO table_relationships ( diagram_id, relationship_name, from_table_name, from_column_name, to_table_name, to_column_name, relationship_type, connection_type, company_code, settings, created_by, updated_by, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, now(), now()) RETURNING *`, [ diagramId, data.relationshipName, data.fromTableName, data.fromColumnName, data.toTableName, data.toColumnName, data.relationshipType, data.connectionType, data.companyCode, JSON.stringify(data.settings), data.createdBy, data.createdBy, ] ); logger.info( `DataflowService: 테이블 관계 생성 완료 - ID: ${relationship.relationship_id}, Diagram ID: ${relationship.diagram_id}` ); return relationship; } catch (error) { logger.error("DataflowService: 테이블 관계 생성 실패", error); throw error; } } /** * 회사별 테이블 관계 목록 조회 */ async getTableRelationships(companyCode: string) { try { logger.info( `DataflowService: 테이블 관계 목록 조회 시작 - 회사코드: ${companyCode}` ); // 관리자는 모든 회사의 관계를 볼 수 있음 let queryText = `SELECT * FROM table_relationships WHERE is_active = 'Y'`; const params: any[] = []; if (companyCode !== "*") { queryText += ` AND company_code = $1`; params.push(companyCode); } queryText += ` ORDER BY created_date DESC`; const relationships = await query(queryText, params); logger.info( `DataflowService: 테이블 관계 목록 조회 완료 - ${relationships.length}개` ); return relationships; } catch (error) { logger.error("DataflowService: 테이블 관계 목록 조회 실패", error); throw error; } } /** * 특정 테이블 관계 조회 */ async getTableRelationship(relationshipId: number, companyCode: string) { try { logger.info( `DataflowService: 테이블 관계 조회 시작 - ID: ${relationshipId}, 회사코드: ${companyCode}` ); let queryText = `SELECT * FROM table_relationships WHERE relationship_id = $1 AND is_active = 'Y'`; const params: any[] = [relationshipId]; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { queryText += ` AND company_code = $2`; params.push(companyCode); } const relationship = await queryOne(queryText, params); if (relationship) { logger.info( `DataflowService: 테이블 관계 조회 완료 - ID: ${relationshipId}` ); } else { logger.warn( `DataflowService: 테이블 관계를 찾을 수 없음 - ID: ${relationshipId}` ); } return relationship; } catch (error) { logger.error("DataflowService: 테이블 관계 조회 실패", error); throw error; } } /** * 테이블 관계 수정 */ async updateTableRelationship( relationshipId: number, updateData: UpdateTableRelationshipData, companyCode: string ) { try { logger.info( `DataflowService: 테이블 관계 수정 시작 - ID: ${relationshipId}`, updateData ); // 기존 관계 확인 const existingRelationship = await this.getTableRelationship( relationshipId, companyCode ); if (!existingRelationship) { return null; } // 관계 수정 const updates: string[] = []; const params: any[] = []; let paramIndex = 1; if (updateData.relationshipName !== undefined) { updates.push(`relationship_name = $${paramIndex++}`); params.push(updateData.relationshipName); } if (updateData.fromTableName !== undefined) { updates.push(`from_table_name = $${paramIndex++}`); params.push(updateData.fromTableName); } if (updateData.fromColumnName !== undefined) { updates.push(`from_column_name = $${paramIndex++}`); params.push(updateData.fromColumnName); } if (updateData.toTableName !== undefined) { updates.push(`to_table_name = $${paramIndex++}`); params.push(updateData.toTableName); } if (updateData.toColumnName !== undefined) { updates.push(`to_column_name = $${paramIndex++}`); params.push(updateData.toColumnName); } if (updateData.relationshipType !== undefined) { updates.push(`relationship_type = $${paramIndex++}`); params.push(updateData.relationshipType); } if (updateData.connectionType !== undefined) { updates.push(`connection_type = $${paramIndex++}`); params.push(updateData.connectionType); } if (updateData.settings !== undefined) { updates.push(`settings = $${paramIndex++}`); params.push(JSON.stringify(updateData.settings)); } updates.push(`updated_by = $${paramIndex++}`); params.push(updateData.updatedBy); updates.push(`updated_date = now()`); params.push(relationshipId); const relationship = await queryOne( `UPDATE table_relationships SET ${updates.join(", ")} WHERE relationship_id = $${paramIndex} RETURNING *`, params ); logger.info( `DataflowService: 테이블 관계 수정 완료 - ID: ${relationshipId}` ); return relationship; } catch (error) { logger.error("DataflowService: 테이블 관계 수정 실패", error); throw error; } } /** * 테이블 관계 삭제 (소프트 삭제) */ async deleteTableRelationship(relationshipId: number, companyCode: string) { try { logger.info( `DataflowService: 테이블 관계 삭제 시작 - ID: ${relationshipId}, 회사코드: ${companyCode}` ); // 기존 관계 확인 const existingRelationship = await this.getTableRelationship( relationshipId, companyCode ); if (!existingRelationship) { return false; } // 소프트 삭제 (is_active = 'N') await query( `UPDATE table_relationships SET is_active = 'N', updated_date = now() WHERE relationship_id = $1`, [relationshipId] ); logger.info( `DataflowService: 테이블 관계 삭제 완료 - ID: ${relationshipId}` ); return true; } catch (error) { logger.error("DataflowService: 테이블 관계 삭제 실패", error); throw error; } } /** * 특정 테이블과 관련된 모든 관계 조회 */ async getRelationshipsByTable(tableName: string, companyCode: string) { try { logger.info( `DataflowService: 테이블별 관계 조회 시작 - 테이블: ${tableName}, 회사코드: ${companyCode}` ); let queryText = ` SELECT * FROM table_relationships WHERE (from_table_name = $1 OR to_table_name = $1) AND is_active = 'Y' `; const params: any[] = [tableName]; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { queryText += ` AND company_code = $2`; params.push(companyCode); } queryText += ` ORDER BY created_date DESC`; const relationships = await query(queryText, params); logger.info( `DataflowService: 테이블별 관계 조회 완료 - ${relationships.length}개` ); return relationships; } catch (error) { logger.error("DataflowService: 테이블별 관계 조회 실패", error); throw error; } } /** * 연결 타입별 관계 조회 */ async getRelationshipsByConnectionType( connectionType: string, companyCode: string ) { try { logger.info( `DataflowService: 연결타입별 관계 조회 시작 - 타입: ${connectionType}, 회사코드: ${companyCode}` ); let queryText = ` SELECT * FROM table_relationships WHERE connection_type = $1 AND is_active = 'Y' `; const params: any[] = [connectionType]; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { queryText += ` AND company_code = $2`; params.push(companyCode); } queryText += ` ORDER BY created_date DESC`; const relationships = await query(queryText, params); logger.info( `DataflowService: 연결타입별 관계 조회 완료 - ${relationships.length}개` ); return relationships; } catch (error) { logger.error("DataflowService: 연결타입별 관계 조회 실패", error); throw error; } } /** * 관계 통계 조회 */ async getRelationshipStats(companyCode: string) { try { logger.info( `DataflowService: 관계 통계 조회 시작 - 회사코드: ${companyCode}` ); let whereClause = `WHERE is_active = 'Y'`; const params: any[] = []; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { whereClause += ` AND company_code = $1`; params.push(companyCode); } // 전체 관계 수 const totalCountResult = await queryOne<{ count: string }>( `SELECT COUNT(*) as count FROM table_relationships ${whereClause}`, params ); const totalCount = parseInt(totalCountResult?.count || "0", 10); // 관계 타입별 통계 const relationshipTypeStats = await query<{ relationship_type: string; count: string; }>( `SELECT relationship_type, COUNT(*) as count FROM table_relationships ${whereClause} GROUP BY relationship_type`, params ); // 연결 타입별 통계 const connectionTypeStats = await query<{ connection_type: string; count: string; }>( `SELECT connection_type, COUNT(*) as count FROM table_relationships ${whereClause} GROUP BY connection_type`, params ); const stats = { totalCount, relationshipTypeStats: relationshipTypeStats.map((stat) => ({ type: stat.relationship_type, count: parseInt(stat.count, 10), })), connectionTypeStats: connectionTypeStats.map((stat) => ({ type: stat.connection_type, count: parseInt(stat.count, 10), })), }; logger.info(`DataflowService: 관계 통계 조회 완료`, stats); return stats; } catch (error) { logger.error("DataflowService: 관계 통계 조회 실패", error); throw error; } } // ==================== 데이터 중계 관리 ==================== /** * 데이터 관계 연결 생성 */ async createDataLink(linkData: { relationshipId: number; fromTableName: string; fromColumnName: string; toTableName: string; toColumnName: string; connectionType: string; companyCode: string; bridgeData?: any; createdBy: string; }) { try { logger.info( `DataflowService: 데이터 연결 생성 시작 - 관계ID: ${linkData.relationshipId}` ); const bridge = await queryOne( `INSERT INTO data_relationship_bridge ( relationship_id, from_table_name, from_column_name, to_table_name, to_column_name, connection_type, company_code, bridge_data, created_by, created_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, now()) RETURNING *`, [ linkData.relationshipId, linkData.fromTableName, linkData.fromColumnName, linkData.toTableName, linkData.toColumnName, linkData.connectionType, linkData.companyCode, JSON.stringify(linkData.bridgeData || {}), linkData.createdBy, ] ); logger.info( `DataflowService: 데이터 연결 생성 완료 - Bridge ID: ${bridge.bridge_id}` ); return bridge; } catch (error) { logger.error("DataflowService: 데이터 연결 생성 실패", error); throw error; } } /** * 관계별 연결된 데이터 조회 */ async getLinkedDataByRelationship( relationshipId: number, companyCode: string ) { try { logger.info( `DataflowService: 관계별 연결 데이터 조회 시작 - 관계ID: ${relationshipId}` ); let queryText = ` SELECT * FROM data_relationship_bridge WHERE relationship_id = $1 AND is_active = 'Y' `; const params: any[] = [relationshipId]; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { queryText += ` AND company_code = $2`; params.push(companyCode); } queryText += ` ORDER BY created_at DESC`; const linkedData = await query(queryText, params); logger.info( `DataflowService: 관계별 연결 데이터 조회 완료 - ${linkedData.length}건` ); return linkedData; } catch (error) { logger.error("DataflowService: 관계별 연결 데이터 조회 실패", error); throw error; } } /** * 특정 테이블의 연결된 데이터 조회 */ async getLinkedDataByTable( tableName: string, keyValue?: string, companyCode?: string ) { try { logger.info( `DataflowService: 테이블별 연결 데이터 조회 시작 - 테이블: ${tableName}` ); let queryText = ` SELECT * FROM data_relationship_bridge WHERE (from_table_name = $1 OR to_table_name = $1) AND is_active = 'Y' `; const params: any[] = [tableName]; // keyValue 파라미터는 더 이상 사용하지 않음 (key_value 필드 제거됨) // 회사코드 필터링 if (companyCode && companyCode !== "*") { queryText += ` AND company_code = $2`; params.push(companyCode); } queryText += ` ORDER BY created_at DESC`; const linkedData = await query(queryText, params); logger.info( `DataflowService: 테이블별 연결 데이터 조회 완료 - ${linkedData.length}건` ); return linkedData; } catch (error) { logger.error("DataflowService: 테이블별 연결 데이터 조회 실패", error); throw error; } } /** * 데이터 연결 수정 */ async updateDataLink( bridgeId: number, updateData: { bridgeData?: any; updatedBy: string; }, companyCode: string ) { try { logger.info( `DataflowService: 데이터 연결 수정 시작 - Bridge ID: ${bridgeId}` ); let queryText = ` UPDATE data_relationship_bridge SET bridge_data = $1, updated_by = $2, updated_at = now() WHERE bridge_id = $3 AND is_active = 'Y' `; const params: any[] = [ JSON.stringify(updateData.bridgeData), updateData.updatedBy, bridgeId, ]; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { queryText += ` AND company_code = $4`; params.push(companyCode); } queryText += ` RETURNING *`; const updatedBridge = await queryOne(queryText, params); logger.info( `DataflowService: 데이터 연결 수정 완료 - Bridge ID: ${bridgeId}` ); return updatedBridge; } catch (error) { logger.error("DataflowService: 데이터 연결 수정 실패", error); throw error; } } /** * 데이터 연결 삭제 (소프트 삭제) */ async deleteDataLink( bridgeId: number, companyCode: string, deletedBy: string ) { try { logger.info( `DataflowService: 데이터 연결 삭제 시작 - Bridge ID: ${bridgeId}` ); let queryText = ` UPDATE data_relationship_bridge SET is_active = 'N', updated_at = now(), updated_by = $1 WHERE bridge_id = $2 AND is_active = 'Y' `; const params: any[] = [deletedBy, bridgeId]; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { queryText += ` AND company_code = $3`; params.push(companyCode); } await query(queryText, params); logger.info( `DataflowService: 데이터 연결 삭제 완료 - Bridge ID: ${bridgeId}` ); return true; } catch (error) { logger.error("DataflowService: 데이터 연결 삭제 실패", error); throw error; } } /** * 관계 삭제 시 연결된 모든 데이터도 삭제 */ async deleteAllLinkedDataByRelationship( relationshipId: number, companyCode: string, deletedBy: string ) { try { logger.info( `DataflowService: 관계별 모든 데이터 연결 삭제 시작 - 관계ID: ${relationshipId}` ); let queryText = ` UPDATE data_relationship_bridge SET is_active = 'N', updated_at = now(), updated_by = $1 WHERE relationship_id = $2 AND is_active = 'Y' `; const params: any[] = [deletedBy, relationshipId]; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { queryText += ` AND company_code = $3`; params.push(companyCode); } const result = await query(queryText, params); logger.info( `DataflowService: 관계별 모든 데이터 연결 삭제 완료 - ${result.length}건` ); return result.length; } catch (error) { logger.error("DataflowService: 관계별 모든 데이터 연결 삭제 실패", error); throw error; } } // ==================== 테이블 데이터 조회 ==================== /** * 테이블 실제 데이터 조회 (페이징) */ async getTableData( tableName: string, page: number = 1, limit: number = 10, search: string = "", searchColumn: string = "", companyCode: string = "*" ) { try { logger.info(`DataflowService: 테이블 데이터 조회 시작 - ${tableName}`); // 테이블 존재 여부 확인 (정보 스키마 사용) const tableExists = await query( `SELECT table_name FROM information_schema.tables WHERE table_name = $1 AND table_schema = 'public'`, [tableName.toLowerCase()] ); if (!tableExists || tableExists.length === 0) { throw new Error(`테이블 '${tableName}'이 존재하지 않습니다.`); } // 전체 데이터 개수 조회 및 데이터 조회 let totalCountQuery = `SELECT COUNT(*) as total FROM "${tableName}"`; let dataQuery = `SELECT * FROM "${tableName}"`; const queryParams: any[] = []; // 검색 조건 추가 (SQL Injection 방지를 위해 파라미터 바인딩 사용) if (search && searchColumn) { const paramIndex = queryParams.length + 1; const whereCondition = `WHERE "${searchColumn}" ILIKE $${paramIndex}`; totalCountQuery += ` ${whereCondition}`; dataQuery += ` ${whereCondition}`; queryParams.push(`%${search}%`); } // 페이징 처리 const offset = (page - 1) * limit; const limitIndex = queryParams.length + 1; const offsetIndex = queryParams.length + 2; dataQuery += ` ORDER BY 1 LIMIT $${limitIndex} OFFSET $${offsetIndex}`; const dataQueryParams = [...queryParams, limit, offset]; // 실제 쿼리 실행 const [totalResult, dataResult] = await Promise.all([ query(totalCountQuery, queryParams.length > 0 ? queryParams : []), query(dataQuery, dataQueryParams), ]); const total = totalResult && totalResult.length > 0 ? Number((totalResult[0] as any).total) : 0; const data = dataResult || []; const result = { data, pagination: { page, limit, total, totalPages: Math.ceil(total / limit), hasNext: page < Math.ceil(total / limit), hasPrev: page > 1, }, }; logger.info( `DataflowService: 테이블 데이터 조회 완료 - ${tableName}, 총 ${total}건 중 ${data.length}건 조회` ); return result; } catch (error) { logger.error( `DataflowService: 테이블 데이터 조회 실패 - ${tableName}`, error ); throw error; } } /** * 관계도 그룹 목록 조회 (diagram_id별로 그룹화) */ async getDataFlowDiagrams( companyCode: string, page: number = 1, size: number = 20, searchTerm: string = "" ) { try { logger.info( `DataflowService: 관계도 목록 조회 시작 - ${companyCode}, page: ${page}, size: ${size}, search: ${searchTerm}` ); // WHERE 조건 구성 const params: any[] = [companyCode]; let whereClause = `WHERE company_code = $1 AND is_active = 'Y'`; if (searchTerm) { whereClause += ` AND ( relationship_name ILIKE $2 OR from_table_name ILIKE $2 OR to_table_name ILIKE $2 )`; params.push(`%${searchTerm}%`); } // diagram_id별로 그룹화된 데이터 조회 const relationships = await query<{ relationship_id: number; diagram_id: number; relationship_name: string; from_table_name: string; to_table_name: string; connection_type: string; relationship_type: string; created_date: Date; created_by: string; updated_date: Date; updated_by: string; }>( `SELECT relationship_id, diagram_id, relationship_name, from_table_name, to_table_name, connection_type, relationship_type, created_date, created_by, updated_date, updated_by FROM table_relationships ${whereClause} ORDER BY diagram_id ASC, created_date DESC`, params ); // diagram_id별로 그룹화 const diagramMap = new Map(); relationships.forEach((rel) => { const diagramId = rel.diagram_id; if (diagramId && !diagramMap.has(diagramId)) { diagramMap.set(diagramId, { diagramId: diagramId, diagramName: rel.relationship_name, // 첫 번째 관계의 이름을 사용 connectionType: rel.connection_type, relationshipType: rel.relationship_type, tableCount: new Set(), relationshipCount: 0, createdAt: rel.created_date, createdBy: rel.created_by, updatedAt: rel.updated_date, updatedBy: rel.updated_by, tables: [], }); } if (diagramId) { const diagram = diagramMap.get(diagramId); if (diagram) { diagram.tableCount.add(rel.from_table_name || ""); diagram.tableCount.add(rel.to_table_name || ""); } diagram.relationshipCount++; // 최신 업데이트 시간 유지 if (rel.updated_date && rel.updated_date > diagram.updatedAt) { diagram.updatedAt = rel.updated_date; diagram.updatedBy = rel.updated_by; } } }); // Set을 배열로 변환하고 테이블 개수 계산 const diagrams = Array.from(diagramMap.values()).map((diagram) => ({ ...diagram, tableCount: diagram.tableCount.size, tables: Array.from(diagram.tableCount), })); // 페이징 처리 const total = diagrams.length; const startIndex = (page - 1) * size; const endIndex = startIndex + size; const paginatedDiagrams = diagrams.slice(startIndex, endIndex); const result = { diagrams: paginatedDiagrams, total, page, size, totalPages: Math.ceil(total / size), hasNext: page < Math.ceil(total / size), hasPrev: page > 1, }; logger.info( `DataflowService: 관계도 목록 조회 완료 - 총 ${total}개 관계도 중 ${paginatedDiagrams.length}개 조회` ); return result; } catch (error) { logger.error("DataflowService: 관계도 목록 조회 실패", error); throw error; } } /** * 특정 관계도의 모든 관계 조회 */ async getDiagramRelationships(companyCode: string, diagramName: string) { try { logger.info( `DataflowService: 관계도 관계 조회 시작 - ${companyCode}, diagram: ${diagramName}` ); const relationships = await query( `SELECT * FROM table_relationships WHERE company_code = $1 AND relationship_name = $2 AND is_active = 'Y' ORDER BY created_date ASC`, [companyCode, diagramName] ); logger.info( `DataflowService: 관계도 관계 조회 완료 - ${diagramName}, ${relationships.length}개 관계` ); return relationships; } catch (error) { logger.error( `DataflowService: 관계도 관계 조회 실패 - ${diagramName}`, error ); throw error; } } /** * 관계도 복사 (diagram_id 기반) */ async copyDiagram( companyCode: string, originalDiagramName: string ): Promise { try { logger.info(`DataflowService: 관계도 복사 시작 - ${originalDiagramName}`); // 원본 관계도의 모든 관계 조회 const originalRelationships = await query<{ relationship_id: number; diagram_id: number; relationship_name: string; from_table_name: string; from_column_name: string; to_table_name: string; to_column_name: string; relationship_type: string; connection_type: string; settings: any; company_code: string; created_by: string; updated_by: string; }>( `SELECT * FROM table_relationships WHERE company_code = $1 AND relationship_name = $2 AND is_active = 'Y'`, [companyCode, originalDiagramName] ); if (originalRelationships.length === 0) { throw new Error("복사할 관계도를 찾을 수 없습니다."); } // 새로운 관계도 이름 생성 (중복 검사) let newDiagramName = `${originalDiagramName} (1)`; let counter = 1; while (true) { const existingDiagram = await queryOne( `SELECT relationship_id FROM table_relationships WHERE company_code = $1 AND relationship_name = $2 AND is_active = 'Y' LIMIT 1`, [companyCode, newDiagramName] ); if (!existingDiagram) { break; } counter++; newDiagramName = `${originalDiagramName} (${counter})`; } // 새로운 diagram_id 생성 const maxDiagramId = await queryOne<{ diagram_id: number }>( `SELECT diagram_id FROM table_relationships WHERE company_code = $1 ORDER BY diagram_id DESC LIMIT 1`, [companyCode] ); const newDiagramId = (maxDiagramId?.diagram_id || 0) + 1; // 트랜잭션으로 모든 관계 복사 const copiedRelationships = await transaction(async (client) => { const results: any[] = []; for (const rel of originalRelationships) { const result = await client.query( `INSERT INTO table_relationships ( diagram_id, relationship_name, from_table_name, from_column_name, to_table_name, to_column_name, relationship_type, connection_type, settings, company_code, is_active, created_by, updated_by, created_at, updated_at ) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, 'Y', $11, $12, now(), now()) RETURNING *`, [ newDiagramId, newDiagramName, rel.from_table_name, rel.from_column_name, rel.to_table_name, rel.to_column_name, rel.relationship_type, rel.connection_type, rel.settings, rel.company_code, rel.created_by, rel.updated_by, ] ); if (result.rows && result.rows.length > 0) { results.push(result.rows[0]); } } return results; }); logger.info( `DataflowService: 관계도 복사 완료 - ${originalDiagramName} → ${newDiagramName} (diagram_id: ${newDiagramId}), ${copiedRelationships.length}개 관계 복사` ); return newDiagramName; } catch (error) { logger.error( `DataflowService: 관계도 복사 실패 - ${originalDiagramName}`, error ); throw error; } } /** * 관계도 삭제 */ async deleteDiagram( companyCode: string, diagramName: string ): Promise { try { logger.info(`DataflowService: 관계도 삭제 시작 - ${diagramName}`); // 관계도의 모든 관계 삭제 (하드 삭제) const deleteResult = await query<{ count: number }>( `DELETE FROM table_relationships WHERE company_code = $1 AND relationship_name = $2 RETURNING relationship_id`, [companyCode, diagramName] ); const count = deleteResult.length; logger.info( `DataflowService: 관계도 삭제 완료 - ${diagramName}, ${count}개 관계 삭제` ); return count; } catch (error) { logger.error(`DataflowService: 관계도 삭제 실패 - ${diagramName}`, error); throw error; } } /** * diagram_id로 해당 관계도의 모든 관계 조회 */ async getDiagramRelationshipsByDiagramId( companyCode: string, diagramId: number ) { try { logger.info( `DataflowService: diagram_id로 관계도 관계 조회 - ${diagramId}` ); // diagram_id로 모든 관계 조회 const relationships = await query( `SELECT * FROM table_relationships WHERE diagram_id = $1 AND company_code = $2 AND is_active = 'Y' ORDER BY relationship_id ASC`, [diagramId, companyCode] ); logger.info( `DataflowService: diagram_id로 관계도 관계 조회 완료 - ${relationships.length}개 관계` ); return relationships.map((rel: any) => ({ ...rel, settings: rel.settings as any, })); } catch (error) { logger.error( `DataflowService: diagram_id로 관계도 관계 조회 실패 - ${diagramId}`, error ); throw error; } } /** * relationship_id로 해당 관계도의 모든 관계 조회 (하위 호환성 유지) */ async getDiagramRelationshipsByRelationshipId( companyCode: string, relationshipId: number ) { try { logger.info( `DataflowService: relationship_id로 관계도 관계 조회 - ${relationshipId}` ); // 먼저 해당 relationship_id의 diagram_id를 찾음 const targetRelationship = await queryOne<{ diagram_id: number }>( `SELECT diagram_id FROM table_relationships WHERE relationship_id = $1 AND company_code = $2 AND is_active = 'Y' LIMIT 1`, [relationshipId, companyCode] ); if (!targetRelationship) { throw new Error("해당 관계 ID를 찾을 수 없습니다."); } // diagram_id로 모든 관계 조회 if (targetRelationship.diagram_id) { return this.getDiagramRelationshipsByDiagramId( companyCode, targetRelationship.diagram_id ); } else { throw new Error("관계에 diagram_id가 없습니다."); } } catch (error) { logger.error( `DataflowService: relationship_id로 관계도 관계 조회 실패 - ${relationshipId}`, error ); throw error; } } }