import { Router, Request, Response } from "express"; import { authenticateToken, AuthenticatedRequest, } from "../../middleware/authMiddleware"; import { ExternalDbConnectionService } from "../../services/externalDbConnectionService"; import { ExternalDbConnectionFilter } from "../../types/externalDbTypes"; import logger from "../../utils/logger"; const router = Router(); /** * GET /api/dataflow/node-external-connections/tested * 노드 플로우용: 테스트에 성공한 외부 DB 커넥션 목록 조회 */ router.get( "/tested", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { logger.info("🔍 노드 플로우용 테스트 완료된 커넥션 조회 요청"); // 활성 상태의 외부 커넥션 조회 const filter: ExternalDbConnectionFilter = { is_active: "Y", }; const externalConnections = await ExternalDbConnectionService.getConnections(filter); if (!externalConnections.success) { return res.status(400).json(externalConnections); } // 외부 커넥션들에 대해 연결 테스트 수행 (제한된 병렬 처리 + 타임아웃 관리) const validExternalConnections: any[] = []; const connections = externalConnections.data || []; const MAX_CONCURRENT = 3; // 최대 동시 연결 수 const TIMEOUT_MS = 3000; // 타임아웃 3초 // 청크 단위로 처리 (최대 3개씩) for (let i = 0; i < connections.length; i += MAX_CONCURRENT) { const chunk = connections.slice(i, i + MAX_CONCURRENT); const chunkResults = await Promise.allSettled( chunk.map(async (connection) => { let testPromise: Promise | null = null; let timeoutId: NodeJS.Timeout | null = null; try { // 타임아웃과 함께 테스트 실행 testPromise = ExternalDbConnectionService.testConnectionById( connection.id! ); const timeoutPromise = new Promise((_, reject) => { timeoutId = setTimeout(() => { reject(new Error("연결 테스트 타임아웃")); }, TIMEOUT_MS); }); const testResult = await Promise.race([ testPromise, timeoutPromise, ]); // 타임아웃 정리 if (timeoutId) clearTimeout(timeoutId); if (testResult.success) { return { id: connection.id, connection_name: connection.connection_name, description: connection.description, db_type: connection.db_type, host: connection.host, port: connection.port, database_name: connection.database_name, }; } return null; } catch (error) { // 타임아웃 정리 if (timeoutId) clearTimeout(timeoutId); // 🔥 타임아웃 시 연결 강제 해제 try { const { DatabaseConnectorFactory } = await import( "../../database/DatabaseConnectorFactory" ); await DatabaseConnectorFactory.closeConnector( connection.id!, connection.db_type ); logger.info( `🧹 타임아웃/실패로 인한 커넥션 정리 완료: ${connection.connection_name}` ); } catch (cleanupError) { logger.warn( `커넥션 정리 실패 (ID: ${connection.id}):`, cleanupError instanceof Error ? cleanupError.message : cleanupError ); } logger.warn( `커넥션 테스트 실패 (ID: ${connection.id}):`, error instanceof Error ? error.message : error ); return null; } }) ); // fulfilled 결과만 수집 chunkResults.forEach((result) => { if (result.status === "fulfilled" && result.value !== null) { validExternalConnections.push(result.value); } }); // 다음 청크 처리 전 짧은 대기 (연결 풀 안정화) if (i + MAX_CONCURRENT < connections.length) { await new Promise((resolve) => setTimeout(resolve, 100)); } } logger.info( `✅ 테스트 성공한 커넥션: ${validExternalConnections.length}/${externalConnections.data?.length || 0}개` ); return res.status(200).json({ success: true, data: validExternalConnections, message: `테스트에 성공한 ${validExternalConnections.length}개의 커넥션을 조회했습니다.`, }); } catch (error) { logger.error("노드 플로우용 커넥션 조회 오류:", error); return res.status(500).json({ success: false, message: "서버 내부 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * GET /api/dataflow/node-external-connections/:id/tables * 특정 외부 DB의 테이블 목록 조회 */ router.get( "/:id/tables", 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입니다.", }); } logger.info(`🔍 외부 DB 테이블 목록 조회: connectionId=${id}`); const result = await ExternalDbConnectionService.getTablesFromConnection(id); return res.status(200).json(result); } catch (error) { logger.error("외부 DB 테이블 목록 조회 오류:", error); return res.status(500).json({ success: false, message: "서버 내부 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * GET /api/dataflow/node-external-connections/:id/tables/:tableName/columns * 특정 외부 DB 테이블의 컬럼 목록 조회 */ router.get( "/:id/tables/:tableName/columns", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const id = parseInt(req.params.id); const { tableName } = req.params; if (isNaN(id)) { return res.status(400).json({ success: false, message: "유효하지 않은 연결 ID입니다.", }); } if (!tableName) { return res.status(400).json({ success: false, message: "테이블명이 필요합니다.", }); } logger.info( `🔍 외부 DB 컬럼 목록 조회: connectionId=${id}, table=${tableName}` ); const result = await ExternalDbConnectionService.getColumnsFromConnection( id, tableName ); return res.status(200).json(result); } catch (error) { logger.error("외부 DB 컬럼 목록 조회 오류:", error); return res.status(500).json({ success: false, message: "서버 내부 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); export default router;