// 외부 DB 연결 API 라우트 // 작성일: 2024-12-17 import { Router, Response } from "express"; import { ExternalDbConnectionService } from "../services/externalDbConnectionService"; import { ExternalDbConnection, ExternalDbConnectionFilter, } from "../types/externalDbTypes"; import { authenticateToken } from "../middleware/authMiddleware"; import { AuthenticatedRequest } from "../types/auth"; import logger from "../utils/logger"; const router = Router(); /** * GET /api/external-db-connections * 외부 DB 연결 목록 조회 */ /** * GET /api/external-db-connections/types/supported * 지원하는 DB 타입 목록 조회 */ router.get( "/types/supported", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const { DB_TYPE_OPTIONS, DB_TYPE_DEFAULTS } = await import( "../types/externalDbTypes" ); return res.status(200).json({ success: true, data: { types: DB_TYPE_OPTIONS, defaults: DB_TYPE_DEFAULTS, }, message: "지원하는 DB 타입 목록을 조회했습니다.", }); } catch (error) { console.error("DB 타입 목록 조회 오류:", error); return res.status(500).json({ success: false, message: "서버 내부 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); router.get( "/", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const userCompanyCode = req.user?.companyCode; // 슈퍼 관리자는 쿼리 파라미터로 회사 지정 가능, 일반/회사 관리자는 자신의 회사만 let companyCodeFilter: string | undefined; if (userCompanyCode === "*") { // 슈퍼 관리자: 쿼리 파라미터 사용 또는 전체 companyCodeFilter = req.query.company_code as string; } else { // 회사 관리자/일반 사용자: 강제로 자신의 회사 코드 적용 companyCodeFilter = userCompanyCode; } const filter: ExternalDbConnectionFilter = { db_type: req.query.db_type as string, is_active: req.query.is_active as string, company_code: companyCodeFilter, search: req.query.search as string, }; // 빈 값 제거 Object.keys(filter).forEach((key) => { if (!filter[key as keyof ExternalDbConnectionFilter]) { delete filter[key as keyof ExternalDbConnectionFilter]; } }); logger.info("외부 DB 연결 목록 조회", { userId: req.user?.userId, userCompanyCode, filterCompanyCode: companyCodeFilter, filter, }); const result = await ExternalDbConnectionService.getConnections( filter, userCompanyCode ); if (result.success) { return res.status(200).json(result); } else { return res.status(400).json(result); } } catch (error) { console.error("외부 DB 연결 목록 조회 오류:", error); return res.status(500).json({ success: false, message: "서버 내부 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * GET /api/external-db-connections/pool-status * 연결 풀 상태 조회 */ router.get( "/pool-status", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const { ExternalDbConnectionPoolService } = await import( "../services/externalDbConnectionPoolService" ); const poolService = ExternalDbConnectionPoolService.getInstance(); const poolsStatus = poolService.getPoolsStatus(); return res.status(200).json({ success: true, data: { totalPools: poolsStatus.length, activePools: poolsStatus.filter((p) => p.activeConnections > 0) .length, pools: poolsStatus, }, message: `${poolsStatus.length}개의 연결 풀 상태를 조회했습니다.`, }); } catch (error) { console.error("연결 풀 상태 조회 오류:", error); return res.status(500).json({ success: false, message: "서버 내부 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * GET /api/external-db-connections/grouped * DB 타입별로 그룹화된 외부 DB 연결 목록 조회 */ router.get( "/grouped", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const filter: ExternalDbConnectionFilter = { db_type: req.query.db_type as string, is_active: req.query.is_active as string, company_code: req.query.company_code as string, search: req.query.search as string, }; // 빈 값 제거 Object.keys(filter).forEach((key) => { if (!filter[key as keyof ExternalDbConnectionFilter]) { delete filter[key as keyof ExternalDbConnectionFilter]; } }); const result = await ExternalDbConnectionService.getConnectionsGroupedByType(filter); if (result.success) { return res.status(200).json(result); } else { return res.status(400).json(result); } } catch (error) { console.error("그룹화된 외부 DB 연결 목록 조회 오류:", error); return res.status(500).json({ success: false, message: "그룹화된 연결 목록 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * GET /api/external-db-connections/:id * 특정 외부 DB 연결 조회 */ router.get( "/:id", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const id = parseInt(req.params.id); if (isNaN(id)) { return res.status(400).json({ success: false, message: "유효하지 않은 ID입니다.", }); } const result = await ExternalDbConnectionService.getConnectionById(id); if (result.success) { return res.status(200).json(result); } else { return res.status(404).json(result); } } catch (error) { console.error("외부 DB 연결 조회 오류:", error); return res.status(500).json({ success: false, message: "서버 내부 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * POST /api/external-db-connections * 새 외부 DB 연결 생성 */ router.post( "/", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const connectionData: ExternalDbConnection = req.body; // 사용자 정보 추가 if (req.user) { connectionData.created_by = req.user.userId; connectionData.updated_by = req.user.userId; } const result = await ExternalDbConnectionService.createConnection(connectionData); if (result.success) { return res.status(201).json(result); } else { return res.status(400).json(result); } } catch (error) { console.error("외부 DB 연결 생성 오류:", error); return res.status(500).json({ success: false, message: "서버 내부 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * PUT /api/external-db-connections/:id * 외부 DB 연결 수정 */ router.put( "/:id", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const id = parseInt(req.params.id); if (isNaN(id)) { return res.status(400).json({ success: false, message: "유효하지 않은 ID입니다.", }); } const updateData: Partial = req.body; // 사용자 정보 추가 if (req.user) { updateData.updated_by = req.user.userId; } const result = await ExternalDbConnectionService.updateConnection( id, updateData ); if (result.success) { return res.status(200).json(result); } else { return res.status(400).json(result); } } catch (error) { console.error("외부 DB 연결 수정 오류:", error); return res.status(500).json({ success: false, message: "서버 내부 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * DELETE /api/external-db-connections/:id * 외부 DB 연결 삭제 (물리 삭제) */ router.delete( "/:id", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const id = parseInt(req.params.id); if (isNaN(id)) { return res.status(400).json({ success: false, message: "유효하지 않은 ID입니다.", }); } const userCompanyCode = req.user?.companyCode; const result = await ExternalDbConnectionService.deleteConnection( id, userCompanyCode ); if (result.success) { return res.status(200).json(result); } else { return res.status(404).json(result); } } catch (error) { console.error("외부 DB 연결 삭제 오류:", error); return res.status(500).json({ success: false, message: "서버 내부 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * POST /api/external-db-connections/:id/test * 데이터베이스 연결 테스트 (ID 기반) */ router.post( "/:id/test", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const id = parseInt(req.params.id); if (isNaN(id)) { return res.status(400).json({ success: false, message: "유효하지 않은 연결 ID입니다.", error: { code: "INVALID_ID", details: "연결 ID는 숫자여야 합니다.", }, }); } // 테스트용 비밀번호가 제공된 경우 사용 const testData = req.body.password ? { password: req.body.password } : undefined; console.log( `🔍 [API] 연결테스트 요청 - ID: ${id}, 비밀번호 제공됨: ${!!req.body.password}` ); const result = await ExternalDbConnectionService.testConnectionById( id, testData ); return res.status(200).json({ success: result.success, data: result, message: result.message, }); } catch (error) { console.error("연결 테스트 오류:", error); return res.status(500).json({ success: false, message: "연결 테스트 중 서버 오류가 발생했습니다.", error: { code: "SERVER_ERROR", details: error instanceof Error ? error.message : "알 수 없는 오류", }, }); } } ); /** * POST /api/external-db-connections/:id/execute * SQL 쿼리 실행 */ router.post( "/:id/execute", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const id = parseInt(req.params.id); const { query } = req.body; if (!query?.trim()) { return res.status(400).json({ success: false, message: "쿼리가 입력되지 않았습니다.", }); } const result = await ExternalDbConnectionService.executeQuery(id, query); return res.json(result); } catch (error) { console.error("쿼리 실행 오류:", error); return res.status(500).json({ success: false, message: "쿼리 실행 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * GET /api/external-db-connections/:id/tables * 데이터베이스 테이블 목록 조회 */ router.get( "/:id/tables", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const id = parseInt(req.params.id); const result = await ExternalDbConnectionService.getTables(id); return res.json(result); } catch (error) { console.error("테이블 목록 조회 오류:", error); return res.status(500).json({ success: false, message: "테이블 목록 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * GET /api/external-db-connections/:id/tables/:tableName/columns * 특정 테이블의 컬럼 정보 조회 */ router.get( "/:id/tables/:tableName/columns", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const id = parseInt(req.params.id); const tableName = req.params.tableName; if (!tableName) { return res.status(400).json({ success: false, message: "테이블명이 입력되지 않았습니다.", }); } const result = await ExternalDbConnectionService.getTableColumns( id, tableName ); return res.json(result); } catch (error) { console.error("테이블 컬럼 조회 오류:", error); return res.status(500).json({ success: false, message: "테이블 컬럼 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * 🆕 GET /api/external-db-connections/active * 제어관리용 활성 커넥션 목록 조회 (현재 DB 포함) */ router.get( "/control/active", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { // 로그인한 사용자의 회사 코드 가져오기 const userCompanyCode = req.user?.companyCode; // 슈퍼 관리자는 쿼리 파라미터로 지정한 회사 또는 전체(*) 조회 가능 // 일반 사용자/회사 관리자는 자신의 회사만 조회 가능 let companyCodeFilter: string; if (userCompanyCode === "*") { // 슈퍼 관리자 companyCodeFilter = (req.query.company_code as string) || "*"; } else { // 회사 관리자 또는 일반 사용자 companyCodeFilter = userCompanyCode || "*"; } // 활성 상태의 외부 커넥션 조회 const filter: ExternalDbConnectionFilter = { is_active: "Y", company_code: companyCodeFilter, }; logger.info("제어관리용 활성 커넥션 조회", { userId: req.user?.userId, userCompanyCode, filterCompanyCode: companyCodeFilter, }); const externalConnections = await ExternalDbConnectionService.getConnections( filter, userCompanyCode ); if (!externalConnections.success) { return res.status(400).json(externalConnections); } // 외부 커넥션들에 대해 연결 테스트 수행 (병렬 처리, 타임아웃 5초) const testedConnections = await Promise.all( (externalConnections.data || []).map(async (connection) => { try { // 개별 연결 테스트에 5초 타임아웃 적용 const testPromise = ExternalDbConnectionService.testConnectionById( connection.id! ); const timeoutPromise = new Promise((_, reject) => { setTimeout(() => reject(new Error("연결 테스트 타임아웃")), 5000); }); const testResult = await Promise.race([ testPromise, timeoutPromise, ]); return testResult.success ? connection : null; } catch (error) { console.warn( `커넥션 테스트 실패 (ID: ${connection.id}):`, error instanceof Error ? error.message : error ); return null; } }) ); // 테스트에 성공한 커넥션만 필터링 const validExternalConnections = testedConnections.filter( (conn) => conn !== null ); // 현재 메인 DB를 첫 번째로 추가 const mainDbConnection = { id: 0, connection_name: "메인 데이터베이스 (현재 시스템)", description: "현재 시스템의 PostgreSQL 데이터베이스", db_type: "postgresql", host: "localhost", port: 5432, database_name: process.env.DB_NAME || "erp_database", username: "system", password: "***", is_active: "Y", company_code: "*", created_date: new Date(), updated_date: new Date(), }; const allConnections = [mainDbConnection, ...validExternalConnections]; return res.status(200).json({ success: true, data: allConnections, message: "제어관리용 활성 커넥션 목록을 조회했습니다.", }); } catch (error) { console.error("제어관리용 활성 커넥션 조회 오류:", error); return res.status(500).json({ success: false, message: "서버 내부 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); export default router;