ERP-node/backend-node/src/routes/dataflow/node-external-connections.ts

232 lines
7.4 KiB
TypeScript

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<any> | null = null;
let timeoutId: NodeJS.Timeout | null = null;
try {
// 타임아웃과 함께 테스트 실행
testPromise = ExternalDbConnectionService.testConnectionById(
connection.id!
);
const timeoutPromise = new Promise<never>((_, 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;