/** * 다중 커넥션 관리 API 라우트 * 제어관리에서 외부 DB와의 통합 작업을 위한 API */ import { Router, Response } from "express"; import { MultiConnectionQueryService } from "../services/multiConnectionQueryService"; import { authenticateToken } from "../middleware/authMiddleware"; import { AuthenticatedRequest } from "../types/auth"; import { logger } from "../utils/logger"; const router = Router(); const multiConnectionService = new MultiConnectionQueryService(); /** * GET /api/multi-connection/connections/:connectionId/tables * 특정 커넥션의 테이블 목록 조회 (메인 DB 포함) */ router.get( "/connections/:connectionId/tables", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const connectionId = parseInt(req.params.connectionId); if (isNaN(connectionId)) { return res.status(400).json({ success: false, message: "유효하지 않은 커넥션 ID입니다.", }); } logger.info(`테이블 목록 조회 요청: connectionId=${connectionId}`); const tables = await multiConnectionService.getTablesFromConnection(connectionId); return res.status(200).json({ success: true, data: tables, message: `커넥션 ${connectionId}의 테이블 목록을 조회했습니다.`, }); } catch (error) { logger.error(`테이블 목록 조회 실패: ${error}`); return res.status(500).json({ success: false, message: "테이블 목록 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * GET /api/multi-connection/connections/:connectionId/tables/batch * 특정 커넥션의 모든 테이블 정보 배치 조회 (컬럼 수 포함) */ router.get( "/connections/:connectionId/tables/batch", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const connectionId = parseInt(req.params.connectionId); if (isNaN(connectionId)) { return res.status(400).json({ success: false, message: "유효하지 않은 커넥션 ID입니다.", }); } logger.info(`배치 테이블 정보 조회 요청: connectionId=${connectionId}`); const tables = await multiConnectionService.getBatchTablesWithColumns(connectionId); return res.status(200).json({ success: true, data: tables, message: `커넥션 ${connectionId}의 테이블 정보를 배치 조회했습니다.`, }); } catch (error) { logger.error(`배치 테이블 정보 조회 실패: ${error}`); return res.status(500).json({ success: false, message: "배치 테이블 정보 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * GET /api/multi-connection/connections/:connectionId/tables/:tableName/columns * 특정 커넥션의 테이블 컬럼 정보 조회 (메인 DB 포함) */ router.get( "/connections/:connectionId/tables/:tableName/columns", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const connectionId = parseInt(req.params.connectionId); const tableName = req.params.tableName; if (isNaN(connectionId)) { return res.status(400).json({ success: false, message: "유효하지 않은 커넥션 ID입니다.", }); } if (!tableName || tableName.trim() === "") { return res.status(400).json({ success: false, message: "테이블명이 입력되지 않았습니다.", }); } logger.info( `컬럼 정보 조회 요청: connectionId=${connectionId}, table=${tableName}` ); const columns = await multiConnectionService.getColumnsFromConnection( connectionId, tableName ); return res.status(200).json({ success: true, data: columns, message: `테이블 ${tableName}의 컬럼 정보를 조회했습니다.`, }); } catch (error) { logger.error(`컬럼 정보 조회 실패: ${error}`); return res.status(500).json({ success: false, message: "컬럼 정보 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * POST /api/multi-connection/connections/:connectionId/query * 특정 커넥션에서 데이터 조회 */ router.post( "/connections/:connectionId/query", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const connectionId = parseInt(req.params.connectionId); const { tableName, conditions } = req.body; if (isNaN(connectionId)) { return res.status(400).json({ success: false, message: "유효하지 않은 커넥션 ID입니다.", }); } if (!tableName) { return res.status(400).json({ success: false, message: "테이블명이 입력되지 않았습니다.", }); } logger.info( `데이터 조회 요청: connectionId=${connectionId}, table=${tableName}` ); const data = await multiConnectionService.fetchDataFromConnection( connectionId, tableName, conditions ); return res.status(200).json({ success: true, data: data, message: `데이터 조회가 완료되었습니다. (${data.length}건)`, }); } catch (error) { logger.error(`데이터 조회 실패: ${error}`); return res.status(500).json({ success: false, message: "데이터 조회 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * POST /api/multi-connection/connections/:connectionId/insert * 특정 커넥션에 데이터 삽입 */ router.post( "/connections/:connectionId/insert", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const connectionId = parseInt(req.params.connectionId); const { tableName, data } = req.body; if (isNaN(connectionId)) { return res.status(400).json({ success: false, message: "유효하지 않은 커넥션 ID입니다.", }); } if (!tableName || !data) { return res.status(400).json({ success: false, message: "테이블명과 데이터가 필요합니다.", }); } logger.info( `데이터 삽입 요청: connectionId=${connectionId}, table=${tableName}` ); const result = await multiConnectionService.insertDataToConnection( connectionId, tableName, data ); return res.status(201).json({ success: true, data: result, message: "데이터 삽입이 완료되었습니다.", }); } catch (error) { logger.error(`데이터 삽입 실패: ${error}`); return res.status(500).json({ success: false, message: "데이터 삽입 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * PUT /api/multi-connection/connections/:connectionId/update * 특정 커넥션의 데이터 업데이트 */ router.put( "/connections/:connectionId/update", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const connectionId = parseInt(req.params.connectionId); const { tableName, data, conditions } = req.body; if (isNaN(connectionId)) { return res.status(400).json({ success: false, message: "유효하지 않은 커넥션 ID입니다.", }); } if (!tableName || !data || !conditions) { return res.status(400).json({ success: false, message: "테이블명, 데이터, 조건이 모두 필요합니다.", }); } logger.info( `데이터 업데이트 요청: connectionId=${connectionId}, table=${tableName}` ); const result = await multiConnectionService.updateDataToConnection( connectionId, tableName, data, conditions ); return res.status(200).json({ success: true, data: result, message: `데이터 업데이트가 완료되었습니다. (${result.length}건)`, }); } catch (error) { logger.error(`데이터 업데이트 실패: ${error}`); return res.status(500).json({ success: false, message: "데이터 업데이트 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * DELETE /api/multi-connection/connections/:connectionId/delete * 특정 커넥션에서 데이터 삭제 */ router.delete( "/connections/:connectionId/delete", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const connectionId = parseInt(req.params.connectionId); const { tableName, conditions, maxDeleteCount } = req.body; if (isNaN(connectionId)) { return res.status(400).json({ success: false, message: "유효하지 않은 커넥션 ID입니다.", }); } if (!tableName || !conditions) { return res.status(400).json({ success: false, message: "테이블명과 삭제 조건이 필요합니다.", }); } logger.info( `데이터 삭제 요청: connectionId=${connectionId}, table=${tableName}` ); const result = await multiConnectionService.deleteDataFromConnection( connectionId, tableName, conditions, maxDeleteCount || 100 ); return res.status(200).json({ success: true, data: result, message: `데이터 삭제가 완료되었습니다. (${result.length}건)`, }); } catch (error) { logger.error(`데이터 삭제 실패: ${error}`); return res.status(500).json({ success: false, message: "데이터 삭제 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); /** * POST /api/multi-connection/validate-self-operation * 자기 자신 테이블 작업 검증 */ router.post( "/validate-self-operation", authenticateToken, async (req: AuthenticatedRequest, res: Response) => { try { const { tableName, operation, conditions } = req.body; if (!tableName || !operation || !conditions) { return res.status(400).json({ success: false, message: "테이블명, 작업 타입, 조건이 모두 필요합니다.", }); } if (!["update", "delete"].includes(operation)) { return res.status(400).json({ success: false, message: "작업 타입은 'update' 또는 'delete'만 허용됩니다.", }); } logger.info( `자기 자신 테이블 작업 검증: table=${tableName}, operation=${operation}` ); const validationResult = await multiConnectionService.validateSelfTableOperation( tableName, operation, conditions ); return res.status(200).json({ success: true, data: validationResult, message: "검증이 완료되었습니다.", }); } catch (error) { logger.error(`자기 자신 테이블 작업 검증 실패: ${error}`); return res.status(500).json({ success: false, message: "검증 중 오류가 발생했습니다.", error: error instanceof Error ? error.message : "알 수 없는 오류", }); } } ); export default router;