368 lines
11 KiB
TypeScript
368 lines
11 KiB
TypeScript
|
|
/**
|
||
|
|
* 다중 커넥션 관리 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/: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;
|