// 외부 DB 연결 서비스 // 작성일: 2024-12-17 import { PrismaClient } from "@prisma/client"; import { ExternalDbConnection, ExternalDbConnectionFilter, ApiResponse, } from "../types/externalDbTypes"; import { PasswordEncryption } from "../utils/passwordEncryption"; const prisma = new PrismaClient(); export class ExternalDbConnectionService { /** * 외부 DB 연결 목록 조회 */ static async getConnections( filter: ExternalDbConnectionFilter ): Promise> { try { const where: any = {}; // 필터 조건 적용 if (filter.db_type) { where.db_type = filter.db_type; } if (filter.is_active) { where.is_active = filter.is_active; } if (filter.company_code) { where.company_code = filter.company_code; } // 검색 조건 적용 (연결명 또는 설명에서 검색) if (filter.search && filter.search.trim()) { where.OR = [ { connection_name: { contains: filter.search.trim(), mode: "insensitive", }, }, { description: { contains: filter.search.trim(), mode: "insensitive", }, }, ]; } const connections = await prisma.external_db_connections.findMany({ where, orderBy: [{ is_active: "desc" }, { connection_name: "asc" }], }); // 비밀번호는 반환하지 않음 (보안) const safeConnections = connections.map((conn) => ({ ...conn, password: "***ENCRYPTED***", // 실제 비밀번호 대신 마스킹 description: conn.description || undefined, })) as ExternalDbConnection[]; return { success: true, data: safeConnections, message: `${connections.length}개의 연결 설정을 조회했습니다.`, }; } catch (error) { console.error("외부 DB 연결 목록 조회 실패:", error); return { success: false, message: "연결 목록 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }; } } /** * 특정 외부 DB 연결 조회 */ static async getConnectionById( id: number ): Promise> { try { const connection = await prisma.external_db_connections.findUnique({ where: { id }, }); if (!connection) { return { success: false, message: "해당 연결 설정을 찾을 수 없습니다.", }; } // 비밀번호는 반환하지 않음 (보안) const safeConnection = { ...connection, password: "***ENCRYPTED***", description: connection.description || undefined, } as ExternalDbConnection; return { success: true, data: safeConnection, message: "연결 설정을 조회했습니다.", }; } catch (error) { console.error("외부 DB 연결 조회 실패:", error); return { success: false, message: "연결 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }; } } /** * 새 외부 DB 연결 생성 */ static async createConnection( data: ExternalDbConnection ): Promise> { try { // 데이터 검증 this.validateConnectionData(data); // 연결명 중복 확인 const existingConnection = await prisma.external_db_connections.findFirst( { where: { connection_name: data.connection_name, company_code: data.company_code, }, } ); if (existingConnection) { return { success: false, message: "이미 존재하는 연결명입니다.", }; } // 비밀번호 암호화 const encryptedPassword = PasswordEncryption.encrypt(data.password); const newConnection = await prisma.external_db_connections.create({ data: { connection_name: data.connection_name, description: data.description, db_type: data.db_type, host: data.host, port: data.port, database_name: data.database_name, username: data.username, password: encryptedPassword, connection_timeout: data.connection_timeout, query_timeout: data.query_timeout, max_connections: data.max_connections, ssl_enabled: data.ssl_enabled, ssl_cert_path: data.ssl_cert_path, connection_options: data.connection_options as any, company_code: data.company_code, is_active: data.is_active, created_by: data.created_by, updated_by: data.updated_by, created_date: new Date(), updated_date: new Date(), }, }); // 비밀번호는 반환하지 않음 const safeConnection = { ...newConnection, password: "***ENCRYPTED***", description: newConnection.description || undefined, } as ExternalDbConnection; return { success: true, data: safeConnection, message: "연결 설정이 생성되었습니다.", }; } catch (error) { console.error("외부 DB 연결 생성 실패:", error); return { success: false, message: "연결 생성 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }; } } /** * 외부 DB 연결 수정 */ static async updateConnection( id: number, data: Partial ): Promise> { try { // 기존 연결 확인 const existingConnection = await prisma.external_db_connections.findUnique({ where: { id }, }); if (!existingConnection) { return { success: false, message: "해당 연결 설정을 찾을 수 없습니다.", }; } // 연결명 중복 확인 (자신 제외) if (data.connection_name) { const duplicateConnection = await prisma.external_db_connections.findFirst({ where: { connection_name: data.connection_name, company_code: data.company_code || existingConnection.company_code, id: { not: id }, }, }); if (duplicateConnection) { return { success: false, message: "이미 존재하는 연결명입니다.", }; } } // 업데이트 데이터 준비 const updateData: any = { ...data, updated_date: new Date(), }; // 비밀번호가 변경된 경우 암호화 if (data.password && data.password !== "***ENCRYPTED***") { updateData.password = PasswordEncryption.encrypt(data.password); } else { // 비밀번호 필드 제거 (변경하지 않음) delete updateData.password; } const updatedConnection = await prisma.external_db_connections.update({ where: { id }, data: updateData, }); // 비밀번호는 반환하지 않음 const safeConnection = { ...updatedConnection, password: "***ENCRYPTED***", description: updatedConnection.description || undefined, } as ExternalDbConnection; return { success: true, data: safeConnection, message: "연결 설정이 수정되었습니다.", }; } catch (error) { console.error("외부 DB 연결 수정 실패:", error); return { success: false, message: "연결 수정 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }; } } /** * 외부 DB 연결 삭제 (논리 삭제) */ static async deleteConnection(id: number): Promise> { try { const existingConnection = await prisma.external_db_connections.findUnique({ where: { id }, }); if (!existingConnection) { return { success: false, message: "해당 연결 설정을 찾을 수 없습니다.", }; } // 논리 삭제 (is_active를 'N'으로 변경) await prisma.external_db_connections.update({ where: { id }, data: { is_active: "N", updated_date: new Date(), }, }); return { success: true, message: "연결 설정이 삭제되었습니다.", }; } catch (error) { console.error("외부 DB 연결 삭제 실패:", error); return { success: false, message: "연결 삭제 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }; } } /** * 연결 데이터 검증 */ private static validateConnectionData(data: ExternalDbConnection): void { const requiredFields = [ "connection_name", "db_type", "host", "port", "database_name", "username", "password", "company_code", ]; for (const field of requiredFields) { if (!data[field as keyof ExternalDbConnection]) { throw new Error(`필수 필드가 누락되었습니다: ${field}`); } } // 포트 번호 유효성 검사 if (data.port < 1 || data.port > 65535) { throw new Error("유효하지 않은 포트 번호입니다. (1-65535)"); } // DB 타입 유효성 검사 const validDbTypes = ["mysql", "postgresql", "oracle", "mssql", "sqlite"]; if (!validDbTypes.includes(data.db_type)) { throw new Error("지원하지 않는 DB 타입입니다."); } } /** * 저장된 연결의 실제 비밀번호 조회 (내부용) */ static async getDecryptedPassword(id: number): Promise { try { const connection = await prisma.external_db_connections.findUnique({ where: { id }, select: { password: true }, }); if (!connection) { return null; } return PasswordEncryption.decrypt(connection.password); } catch (error) { console.error("비밀번호 복호화 실패:", error); return null; } } }