import { PrismaClient } from "@prisma/client"; import { logger } from "../utils/logger"; const prisma = new PrismaClient(); // 테이블 관계 생성 데이터 타입 interface CreateTableRelationshipData { 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); // 중복 관계 확인 const existingRelationship = await prisma.table_relationships.findFirst({ where: { from_table_name: data.fromTableName, from_column_name: data.fromColumnName, to_table_name: data.toTableName, to_column_name: data.toColumnName, company_code: data.companyCode, is_active: "Y", }, }); if (existingRelationship) { throw new Error( `이미 존재하는 관계입니다: ${data.fromTableName}.${data.fromColumnName} → ${data.toTableName}.${data.toColumnName}` ); } // 새 관계 생성 (중계 테이블은 별도로 생성하지 않음) const relationship = await prisma.table_relationships.create({ data: { relationship_name: data.relationshipName, from_table_name: data.fromTableName, from_column_name: data.fromColumnName, to_table_name: data.toTableName, to_column_name: data.toColumnName, relationship_type: data.relationshipType, connection_type: data.connectionType, company_code: data.companyCode, settings: data.settings, created_by: data.createdBy, updated_by: data.createdBy, }, }); logger.info( `DataflowService: 테이블 관계 생성 완료 - ID: ${relationship.relationship_id}` ); return relationship; } catch (error) { logger.error("DataflowService: 테이블 관계 생성 실패", error); throw error; } } /** * 회사별 테이블 관계 목록 조회 */ async getTableRelationships(companyCode: string) { try { logger.info( `DataflowService: 테이블 관계 목록 조회 시작 - 회사코드: ${companyCode}` ); // 관리자는 모든 회사의 관계를 볼 수 있음 const whereCondition: any = { is_active: "Y", }; if (companyCode !== "*") { whereCondition.company_code = companyCode; } const relationships = await prisma.table_relationships.findMany({ where: whereCondition, orderBy: { created_date: "desc", }, }); 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}` ); const whereCondition: any = { relationship_id: relationshipId, is_active: "Y", }; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { whereCondition.company_code = companyCode; } const relationship = await prisma.table_relationships.findFirst({ where: whereCondition, }); 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 relationship = await prisma.table_relationships.update({ where: { relationship_id: relationshipId, }, data: { ...updateData, updated_date: new Date(), }, }); 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 prisma.table_relationships.update({ where: { relationship_id: relationshipId, }, data: { is_active: "N", updated_date: new Date(), }, }); 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}` ); const whereCondition: any = { OR: [{ from_table_name: tableName }, { to_table_name: tableName }], is_active: "Y", }; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { whereCondition.company_code = companyCode; } const relationships = await prisma.table_relationships.findMany({ where: whereCondition, orderBy: { created_date: "desc", }, }); 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}` ); const whereCondition: any = { connection_type: connectionType, is_active: "Y", }; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { whereCondition.company_code = companyCode; } const relationships = await prisma.table_relationships.findMany({ where: whereCondition, orderBy: { created_date: "desc", }, }); logger.info( `DataflowService: 연결타입별 관계 조회 완료 - ${relationships.length}개` ); return relationships; } catch (error) { logger.error("DataflowService: 연결타입별 관계 조회 실패", error); throw error; } } /** * 관계 통계 조회 */ async getRelationshipStats(companyCode: string) { try { logger.info( `DataflowService: 관계 통계 조회 시작 - 회사코드: ${companyCode}` ); const whereCondition: any = { is_active: "Y", }; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { whereCondition.company_code = companyCode; } // 전체 관계 수 const totalCount = await prisma.table_relationships.count({ where: whereCondition, }); // 관계 타입별 통계 const relationshipTypeStats = await prisma.table_relationships.groupBy({ by: ["relationship_type"], where: whereCondition, _count: { relationship_id: true, }, }); // 연결 타입별 통계 const connectionTypeStats = await prisma.table_relationships.groupBy({ by: ["connection_type"], where: whereCondition, _count: { relationship_id: true, }, }); const stats = { totalCount, relationshipTypeStats: relationshipTypeStats.map((stat) => ({ type: stat.relationship_type, count: stat._count.relationship_id, })), connectionTypeStats: connectionTypeStats.map((stat) => ({ type: stat.connection_type, count: stat._count.relationship_id, })), }; 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 prisma.data_relationship_bridge.create({ data: { relationship_id: linkData.relationshipId, from_table_name: linkData.fromTableName, from_column_name: linkData.fromColumnName, to_table_name: linkData.toTableName, to_column_name: linkData.toColumnName, connection_type: linkData.connectionType, company_code: linkData.companyCode, bridge_data: linkData.bridgeData || {}, created_by: 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}` ); const whereCondition: any = { relationship_id: relationshipId, is_active: "Y", }; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { whereCondition.company_code = companyCode; } const linkedData = await prisma.data_relationship_bridge.findMany({ where: whereCondition, orderBy: { created_at: "desc" }, include: { relationship: { select: { relationship_name: true, relationship_type: true, connection_type: true, }, }, }, }); 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}` ); const whereCondition: any = { OR: [{ from_table_name: tableName }, { to_table_name: tableName }], is_active: "Y", }; // keyValue 파라미터는 더 이상 사용하지 않음 (key_value 필드 제거됨) // 회사코드 필터링 if (companyCode && companyCode !== "*") { whereCondition.company_code = companyCode; } const linkedData = await prisma.data_relationship_bridge.findMany({ where: whereCondition, orderBy: { created_at: "desc" }, include: { relationship: { select: { relationship_name: true, relationship_type: true, connection_type: true, }, }, }, }); 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}` ); const whereCondition: any = { bridge_id: bridgeId, is_active: "Y", }; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { whereCondition.company_code = companyCode; } const updatedBridge = await prisma.data_relationship_bridge.update({ where: whereCondition, data: { ...updateData, updated_at: new Date(), }, }); 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}` ); const whereCondition: any = { bridge_id: bridgeId, is_active: "Y", }; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { whereCondition.company_code = companyCode; } await prisma.data_relationship_bridge.update({ where: whereCondition, data: { is_active: "N", updated_at: new Date(), updated_by: deletedBy, }, }); 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}` ); const whereCondition: any = { relationship_id: relationshipId, is_active: "Y", }; // 관리자가 아닌 경우 회사코드 제한 if (companyCode !== "*") { whereCondition.company_code = companyCode; } const result = await prisma.data_relationship_bridge.updateMany({ where: whereCondition, data: { is_active: "N", updated_at: new Date(), updated_by: deletedBy, }, }); logger.info( `DataflowService: 관계별 모든 데이터 연결 삭제 완료 - ${result.count}건` ); return result.count; } 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 prisma.$queryRaw` SELECT table_name FROM information_schema.tables WHERE table_name = ${tableName.toLowerCase()} AND table_schema = 'public' `; if ( !tableExists || (Array.isArray(tableExists) && tableExists.length === 0) ) { throw new Error(`테이블 '${tableName}'이 존재하지 않습니다.`); } // 전체 데이터 개수 조회 let totalCountQuery = `SELECT COUNT(*) as total FROM "${tableName}"`; let dataQuery = `SELECT * FROM "${tableName}"`; // 검색 조건 추가 if (search && searchColumn) { const whereCondition = `WHERE "${searchColumn}" ILIKE '%${search}%'`; totalCountQuery += ` ${whereCondition}`; dataQuery += ` ${whereCondition}`; } // 페이징 처리 const offset = (page - 1) * limit; dataQuery += ` ORDER BY 1 LIMIT ${limit} OFFSET ${offset}`; // 실제 쿼리 실행 const [totalResult, dataResult] = await Promise.all([ prisma.$queryRawUnsafe(totalCountQuery), prisma.$queryRawUnsafe(dataQuery), ]); const total = Array.isArray(totalResult) && totalResult.length > 0 ? Number((totalResult[0] as any).total) : 0; const data = Array.isArray(dataResult) ? 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; } } }