제어관리 외부 커넥션 설정기능

This commit is contained in:
kjs
2025-09-24 18:23:57 +09:00
parent 0d9ee4c40f
commit b41e645c74
25 changed files with 6775 additions and 209 deletions

View File

@@ -108,7 +108,8 @@ router.get(
}
});
const result = await ExternalDbConnectionService.getConnectionsGroupedByType(filter);
const result =
await ExternalDbConnectionService.getConnectionsGroupedByType(filter);
if (result.success) {
return res.status(200).json(result);
@@ -120,7 +121,7 @@ router.get(
return res.status(500).json({
success: false,
message: "그룹화된 연결 목록 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류"
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
@@ -290,7 +291,7 @@ router.post(
async (req: AuthenticatedRequest, res: Response) => {
try {
const id = parseInt(req.params.id);
if (isNaN(id)) {
return res.status(400).json({
success: false,
@@ -303,10 +304,17 @@ router.post(
}
// 테스트용 비밀번호가 제공된 경우 사용
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);
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,
@@ -342,7 +350,7 @@ router.post(
if (!query?.trim()) {
return res.status(400).json({
success: false,
message: "쿼리가 입력되지 않았습니다."
message: "쿼리가 입력되지 않았습니다.",
});
}
@@ -353,7 +361,7 @@ router.post(
return res.status(500).json({
success: false,
message: "쿼리 실행 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류"
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
@@ -376,7 +384,7 @@ router.get(
return res.status(500).json({
success: false,
message: "테이블 목록 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류"
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
@@ -393,26 +401,106 @@ router.get(
try {
const id = parseInt(req.params.id);
const tableName = req.params.tableName;
if (!tableName) {
return res.status(400).json({
success: false,
message: "테이블명이 입력되지 않았습니다."
message: "테이블명이 입력되지 않았습니다.",
});
}
const result = await ExternalDbConnectionService.getTableColumns(id, tableName);
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 : "알 수 없는 오류"
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 filter: ExternalDbConnectionFilter = {
is_active: "Y",
company_code: (req.query.company_code as string) || "*",
};
const externalConnections =
await ExternalDbConnectionService.getConnections(filter);
if (!externalConnections.success) {
return res.status(400).json(externalConnections);
}
// 외부 커넥션들에 대해 연결 테스트 수행 (병렬 처리)
const testedConnections = await Promise.all(
(externalConnections.data || []).map(async (connection) => {
try {
const testResult =
await ExternalDbConnectionService.testConnectionById(
connection.id!
);
return testResult.success ? connection : null;
} catch (error) {
console.warn(`커넥션 테스트 실패 (ID: ${connection.id}):`, 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;

View File

@@ -0,0 +1,367 @@
/**
* 다중 커넥션 관리 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;