ERP-node/backend-node/src/routes/externalDbConnectionRoutes.ts

605 lines
17 KiB
TypeScript

// 외부 DB 연결 API 라우트
// 작성일: 2024-12-17
import { Router, Response } from "express";
import { ExternalDbConnectionService } from "../services/externalDbConnectionService";
import {
ExternalDbConnection,
ExternalDbConnectionFilter,
} from "../types/externalDbTypes";
import { authenticateToken } from "../middleware/authMiddleware";
import { AuthenticatedRequest } from "../types/auth";
import logger from "../utils/logger";
const router = Router();
/**
* GET /api/external-db-connections
* 외부 DB 연결 목록 조회
*/
/**
* GET /api/external-db-connections/types/supported
* 지원하는 DB 타입 목록 조회
*/
router.get(
"/types/supported",
authenticateToken,
async (req: AuthenticatedRequest, res: Response) => {
try {
const { DB_TYPE_OPTIONS, DB_TYPE_DEFAULTS } = await import(
"../types/externalDbTypes"
);
return res.status(200).json({
success: true,
data: {
types: DB_TYPE_OPTIONS,
defaults: DB_TYPE_DEFAULTS,
},
message: "지원하는 DB 타입 목록을 조회했습니다.",
});
} catch (error) {
console.error("DB 타입 목록 조회 오류:", error);
return res.status(500).json({
success: false,
message: "서버 내부 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
);
router.get(
"/",
authenticateToken,
async (req: AuthenticatedRequest, res: Response) => {
try {
const userCompanyCode = req.user?.companyCode;
// 슈퍼 관리자는 쿼리 파라미터로 회사 지정 가능, 일반/회사 관리자는 자신의 회사만
let companyCodeFilter: string | undefined;
if (userCompanyCode === "*") {
// 슈퍼 관리자: 쿼리 파라미터 사용 또는 전체
companyCodeFilter = req.query.company_code as string;
} else {
// 회사 관리자/일반 사용자: 강제로 자신의 회사 코드 적용
companyCodeFilter = userCompanyCode;
}
const filter: ExternalDbConnectionFilter = {
db_type: req.query.db_type as string,
is_active: req.query.is_active as string,
company_code: companyCodeFilter,
search: req.query.search as string,
};
// 빈 값 제거
Object.keys(filter).forEach((key) => {
if (!filter[key as keyof ExternalDbConnectionFilter]) {
delete filter[key as keyof ExternalDbConnectionFilter];
}
});
logger.info("외부 DB 연결 목록 조회", {
userId: req.user?.userId,
userCompanyCode,
filterCompanyCode: companyCodeFilter,
filter,
});
const result = await ExternalDbConnectionService.getConnections(
filter,
userCompanyCode
);
if (result.success) {
return res.status(200).json(result);
} else {
return res.status(400).json(result);
}
} catch (error) {
console.error("외부 DB 연결 목록 조회 오류:", error);
return res.status(500).json({
success: false,
message: "서버 내부 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
);
/**
* GET /api/external-db-connections/pool-status
* 연결 풀 상태 조회
*/
router.get(
"/pool-status",
authenticateToken,
async (req: AuthenticatedRequest, res: Response) => {
try {
const { ExternalDbConnectionPoolService } = await import(
"../services/externalDbConnectionPoolService"
);
const poolService = ExternalDbConnectionPoolService.getInstance();
const poolsStatus = poolService.getPoolsStatus();
return res.status(200).json({
success: true,
data: {
totalPools: poolsStatus.length,
activePools: poolsStatus.filter((p) => p.activeConnections > 0)
.length,
pools: poolsStatus,
},
message: `${poolsStatus.length}개의 연결 풀 상태를 조회했습니다.`,
});
} catch (error) {
console.error("연결 풀 상태 조회 오류:", error);
return res.status(500).json({
success: false,
message: "서버 내부 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
);
/**
* GET /api/external-db-connections/grouped
* DB 타입별로 그룹화된 외부 DB 연결 목록 조회
*/
router.get(
"/grouped",
authenticateToken,
async (req: AuthenticatedRequest, res: Response) => {
try {
const filter: ExternalDbConnectionFilter = {
db_type: req.query.db_type as string,
is_active: req.query.is_active as string,
company_code: req.query.company_code as string,
search: req.query.search as string,
};
// 빈 값 제거
Object.keys(filter).forEach((key) => {
if (!filter[key as keyof ExternalDbConnectionFilter]) {
delete filter[key as keyof ExternalDbConnectionFilter];
}
});
const result =
await ExternalDbConnectionService.getConnectionsGroupedByType(filter);
if (result.success) {
return res.status(200).json(result);
} else {
return res.status(400).json(result);
}
} catch (error) {
console.error("그룹화된 외부 DB 연결 목록 조회 오류:", error);
return res.status(500).json({
success: false,
message: "그룹화된 연결 목록 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
);
/**
* GET /api/external-db-connections/:id
* 특정 외부 DB 연결 조회
*/
router.get(
"/:id",
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입니다.",
});
}
const result = await ExternalDbConnectionService.getConnectionById(id);
if (result.success) {
return res.status(200).json(result);
} else {
return res.status(404).json(result);
}
} catch (error) {
console.error("외부 DB 연결 조회 오류:", error);
return res.status(500).json({
success: false,
message: "서버 내부 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
);
/**
* POST /api/external-db-connections
* 새 외부 DB 연결 생성
*/
router.post(
"/",
authenticateToken,
async (req: AuthenticatedRequest, res: Response) => {
try {
const connectionData: ExternalDbConnection = req.body;
// 사용자 정보 추가
if (req.user) {
connectionData.created_by = req.user.userId;
connectionData.updated_by = req.user.userId;
}
const result =
await ExternalDbConnectionService.createConnection(connectionData);
if (result.success) {
return res.status(201).json(result);
} else {
return res.status(400).json(result);
}
} catch (error) {
console.error("외부 DB 연결 생성 오류:", error);
return res.status(500).json({
success: false,
message: "서버 내부 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
);
/**
* PUT /api/external-db-connections/:id
* 외부 DB 연결 수정
*/
router.put(
"/:id",
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입니다.",
});
}
const updateData: Partial<ExternalDbConnection> = req.body;
// 사용자 정보 추가
if (req.user) {
updateData.updated_by = req.user.userId;
}
const result = await ExternalDbConnectionService.updateConnection(
id,
updateData
);
if (result.success) {
return res.status(200).json(result);
} else {
return res.status(400).json(result);
}
} catch (error) {
console.error("외부 DB 연결 수정 오류:", error);
return res.status(500).json({
success: false,
message: "서버 내부 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
);
/**
* DELETE /api/external-db-connections/:id
* 외부 DB 연결 삭제 (물리 삭제)
*/
router.delete(
"/:id",
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입니다.",
});
}
const userCompanyCode = req.user?.companyCode;
const result = await ExternalDbConnectionService.deleteConnection(
id,
userCompanyCode
);
if (result.success) {
return res.status(200).json(result);
} else {
return res.status(404).json(result);
}
} catch (error) {
console.error("외부 DB 연결 삭제 오류:", error);
return res.status(500).json({
success: false,
message: "서버 내부 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
);
/**
* POST /api/external-db-connections/:id/test
* 데이터베이스 연결 테스트 (ID 기반)
*/
router.post(
"/:id/test",
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입니다.",
error: {
code: "INVALID_ID",
details: "연결 ID는 숫자여야 합니다.",
},
});
}
// 테스트용 비밀번호가 제공된 경우 사용
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,
data: result,
message: result.message,
});
} catch (error) {
console.error("연결 테스트 오류:", error);
return res.status(500).json({
success: false,
message: "연결 테스트 중 서버 오류가 발생했습니다.",
error: {
code: "SERVER_ERROR",
details: error instanceof Error ? error.message : "알 수 없는 오류",
},
});
}
}
);
/**
* POST /api/external-db-connections/:id/execute
* SQL 쿼리 실행
*/
router.post(
"/:id/execute",
authenticateToken,
async (req: AuthenticatedRequest, res: Response) => {
try {
const id = parseInt(req.params.id);
const { query } = req.body;
if (!query?.trim()) {
return res.status(400).json({
success: false,
message: "쿼리가 입력되지 않았습니다.",
});
}
const result = await ExternalDbConnectionService.executeQuery(id, query);
return res.json(result);
} catch (error) {
console.error("쿼리 실행 오류:", error);
return res.status(500).json({
success: false,
message: "쿼리 실행 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
);
/**
* GET /api/external-db-connections/:id/tables
* 데이터베이스 테이블 목록 조회
*/
router.get(
"/:id/tables",
authenticateToken,
async (req: AuthenticatedRequest, res: Response) => {
try {
const id = parseInt(req.params.id);
const result = await ExternalDbConnectionService.getTables(id);
return res.json(result);
} catch (error) {
console.error("테이블 목록 조회 오류:", error);
return res.status(500).json({
success: false,
message: "테이블 목록 조회 중 오류가 발생했습니다.",
error: error instanceof Error ? error.message : "알 수 없는 오류",
});
}
}
);
/**
* GET /api/external-db-connections/:id/tables/:tableName/columns
* 특정 테이블의 컬럼 정보 조회
*/
router.get(
"/:id/tables/:tableName/columns",
authenticateToken,
async (req: AuthenticatedRequest, res: Response) => {
try {
const id = parseInt(req.params.id);
const tableName = req.params.tableName;
if (!tableName) {
return res.status(400).json({
success: false,
message: "테이블명이 입력되지 않았습니다.",
});
}
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 : "알 수 없는 오류",
});
}
}
);
/**
* 🆕 GET /api/external-db-connections/active
* 제어관리용 활성 커넥션 목록 조회 (현재 DB 포함)
*/
router.get(
"/control/active",
authenticateToken,
async (req: AuthenticatedRequest, res: Response) => {
try {
// 로그인한 사용자의 회사 코드 가져오기
const userCompanyCode = req.user?.companyCode;
// 슈퍼 관리자는 쿼리 파라미터로 지정한 회사 또는 전체(*) 조회 가능
// 일반 사용자/회사 관리자는 자신의 회사만 조회 가능
let companyCodeFilter: string;
if (userCompanyCode === "*") {
// 슈퍼 관리자
companyCodeFilter = (req.query.company_code as string) || "*";
} else {
// 회사 관리자 또는 일반 사용자
companyCodeFilter = userCompanyCode || "*";
}
// 활성 상태의 외부 커넥션 조회
const filter: ExternalDbConnectionFilter = {
is_active: "Y",
company_code: companyCodeFilter,
};
logger.info("제어관리용 활성 커넥션 조회", {
userId: req.user?.userId,
userCompanyCode,
filterCompanyCode: companyCodeFilter,
});
const externalConnections =
await ExternalDbConnectionService.getConnections(
filter,
userCompanyCode
);
if (!externalConnections.success) {
return res.status(400).json(externalConnections);
}
// 외부 커넥션들에 대해 연결 테스트 수행 (병렬 처리, 타임아웃 5초)
const testedConnections = await Promise.all(
(externalConnections.data || []).map(async (connection) => {
try {
// 개별 연결 테스트에 5초 타임아웃 적용
const testPromise = ExternalDbConnectionService.testConnectionById(
connection.id!
);
const timeoutPromise = new Promise<any>((_, reject) => {
setTimeout(() => reject(new Error("연결 테스트 타임아웃")), 5000);
});
const testResult = await Promise.race([
testPromise,
timeoutPromise,
]);
return testResult.success ? connection : null;
} catch (error) {
console.warn(
`커넥션 테스트 실패 (ID: ${connection.id}):`,
error instanceof Error ? error.message : 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;