Files
vexplor/backend-node/src/controllers/digitalTwinDataController.ts
2025-11-21 02:25:25 +09:00

404 lines
11 KiB
TypeScript

import { Request, Response } from "express";
import { pool, queryOne } from "../database/db";
import logger from "../utils/logger";
import { PasswordEncryption } from "../utils/passwordEncryption";
import { DatabaseConnectorFactory } from "../database/DatabaseConnectorFactory";
// 외부 DB 커넥터를 가져오는 헬퍼 함수
export async function getExternalDbConnector(connectionId: number) {
// 외부 DB 연결 정보 조회
const connection = await queryOne<any>(
`SELECT * FROM external_db_connections WHERE id = $1`,
[connectionId]
);
if (!connection) {
throw new Error(`외부 DB 연결 정보를 찾을 수 없습니다. ID: ${connectionId}`);
}
// 패스워드 복호화
const decryptedPassword = PasswordEncryption.decrypt(connection.password);
// DB 연결 설정
const config = {
host: connection.host,
port: connection.port,
user: connection.username,
password: decryptedPassword,
database: connection.database_name,
};
// DB 커넥터 생성
return await DatabaseConnectorFactory.createConnector(
connection.db_type || "mariadb",
config,
connectionId
);
}
// 동적 계층 구조 데이터 조회 (범용)
export const getHierarchyData = async (req: Request, res: Response): Promise<Response> => {
try {
const { externalDbConnectionId, hierarchyConfig } = req.body;
if (!externalDbConnectionId || !hierarchyConfig) {
return res.status(400).json({
success: false,
message: "외부 DB 연결 ID와 계층 구조 설정이 필요합니다.",
});
}
const connector = await getExternalDbConnector(Number(externalDbConnectionId));
const config = JSON.parse(hierarchyConfig);
const result: any = {
warehouse: null,
levels: [],
materials: [],
};
// 창고 데이터 조회
if (config.warehouse) {
const warehouseQuery = `SELECT * FROM ${config.warehouse.tableName} LIMIT 100`;
const warehouseResult = await connector.executeQuery(warehouseQuery);
result.warehouse = warehouseResult.rows;
}
// 각 레벨 데이터 조회
if (config.levels && Array.isArray(config.levels)) {
for (const level of config.levels) {
const levelQuery = `SELECT * FROM ${level.tableName} LIMIT 1000`;
const levelResult = await connector.executeQuery(levelQuery);
result.levels.push({
level: level.level,
name: level.name,
data: levelResult.rows,
});
}
}
// 자재 데이터 조회 (개수만)
if (config.material) {
const materialQuery = `
SELECT
${config.material.locationKeyColumn} as location_key,
COUNT(*) as count
FROM ${config.material.tableName}
GROUP BY ${config.material.locationKeyColumn}
`;
const materialResult = await connector.executeQuery(materialQuery);
result.materials = materialResult.rows;
}
logger.info("동적 계층 구조 데이터 조회", {
externalDbConnectionId,
warehouseCount: result.warehouse?.length || 0,
levelCounts: result.levels.map((l: any) => ({ level: l.level, count: l.data.length })),
});
return res.json({
success: true,
data: result,
});
} catch (error: any) {
logger.error("동적 계층 구조 데이터 조회 실패", error);
return res.status(500).json({
success: false,
message: "데이터 조회 중 오류가 발생했습니다.",
error: error.message,
});
}
};
// 특정 레벨의 하위 데이터 조회
export const getChildrenData = async (req: Request, res: Response): Promise<Response> => {
try {
const { externalDbConnectionId, hierarchyConfig, parentLevel, parentKey } = req.body;
if (!externalDbConnectionId || !hierarchyConfig || !parentLevel || !parentKey) {
return res.status(400).json({
success: false,
message: "필수 파라미터가 누락되었습니다.",
});
}
const connector = await getExternalDbConnector(Number(externalDbConnectionId));
const config = JSON.parse(hierarchyConfig);
// 다음 레벨 찾기
const nextLevel = config.levels?.find((l: any) => l.level === parentLevel + 1);
if (!nextLevel) {
return res.json({
success: true,
data: [],
message: "하위 레벨이 없습니다.",
});
}
// 하위 데이터 조회
const query = `
SELECT * FROM ${nextLevel.tableName}
WHERE ${nextLevel.parentKeyColumn} = '${parentKey}'
LIMIT 1000
`;
const result = await connector.executeQuery(query);
logger.info("하위 데이터 조회", {
externalDbConnectionId,
parentLevel,
parentKey,
count: result.rows.length,
});
return res.json({
success: true,
data: result.rows,
});
} catch (error: any) {
logger.error("하위 데이터 조회 실패", error);
return res.status(500).json({
success: false,
message: "하위 데이터 조회 중 오류가 발생했습니다.",
error: error.message,
});
}
};
// 창고 목록 조회 (사용자 지정 테이블) - 레거시, 호환성 유지
export const getWarehouses = async (req: Request, res: Response): Promise<Response> => {
try {
const { externalDbConnectionId, tableName } = req.query;
if (!externalDbConnectionId) {
return res.status(400).json({
success: false,
message: "외부 DB 연결 ID가 필요합니다.",
});
}
if (!tableName) {
return res.status(400).json({
success: false,
message: "테이블명이 필요합니다.",
});
}
const connector = await getExternalDbConnector(Number(externalDbConnectionId));
// 테이블명을 사용하여 모든 컬럼 조회
const query = `SELECT * FROM ${tableName} LIMIT 100`;
const result = await connector.executeQuery(query);
logger.info("창고 목록 조회", {
externalDbConnectionId,
tableName,
count: result.rows.length,
data: result.rows, // 실제 데이터 확인
});
return res.json({
success: true,
data: result.rows,
});
} catch (error: any) {
logger.error("창고 목록 조회 실패", error);
return res.status(500).json({
success: false,
message: "창고 목록 조회 중 오류가 발생했습니다.",
error: error.message,
});
}
};
// 구역 목록 조회 (사용자 지정 테이블) - 레거시, 호환성 유지
export const getAreas = async (req: Request, res: Response): Promise<Response> => {
try {
const { externalDbConnectionId, warehouseKey, tableName } = req.query;
if (!externalDbConnectionId || !warehouseKey || !tableName) {
return res.status(400).json({
success: false,
message: "필수 파라미터가 누락되었습니다.",
});
}
const connector = await getExternalDbConnector(Number(externalDbConnectionId));
const query = `
SELECT * FROM ${tableName}
WHERE WAREKEY = '${warehouseKey}'
LIMIT 1000
`;
const result = await connector.executeQuery(query);
logger.info("구역 목록 조회", {
externalDbConnectionId,
tableName,
warehouseKey,
count: result.rows.length,
});
return res.json({
success: true,
data: result.rows,
});
} catch (error: any) {
logger.error("구역 목록 조회 실패", error);
return res.status(500).json({
success: false,
message: "구역 목록 조회 중 오류가 발생했습니다.",
error: error.message,
});
}
};
// 위치 목록 조회 (사용자 지정 테이블) - 레거시, 호환성 유지
export const getLocations = async (req: Request, res: Response): Promise<Response> => {
try {
const { externalDbConnectionId, areaKey, tableName } = req.query;
if (!externalDbConnectionId || !areaKey || !tableName) {
return res.status(400).json({
success: false,
message: "필수 파라미터가 누락되었습니다.",
});
}
const connector = await getExternalDbConnector(Number(externalDbConnectionId));
const query = `
SELECT * FROM ${tableName}
WHERE AREAKEY = '${areaKey}'
LIMIT 1000
`;
const result = await connector.executeQuery(query);
logger.info("위치 목록 조회", {
externalDbConnectionId,
tableName,
areaKey,
count: result.rows.length,
});
return res.json({
success: true,
data: result.rows,
});
} catch (error: any) {
logger.error("위치 목록 조회 실패", error);
return res.status(500).json({
success: false,
message: "위치 목록 조회 중 오류가 발생했습니다.",
error: error.message,
});
}
};
// 자재 목록 조회 (동적 컬럼 매핑 지원)
export const getMaterials = async (req: Request, res: Response): Promise<Response> => {
try {
const {
externalDbConnectionId,
locaKey,
tableName,
keyColumn,
locationKeyColumn,
layerColumn
} = req.query;
if (!externalDbConnectionId || !locaKey || !tableName || !locationKeyColumn) {
return res.status(400).json({
success: false,
message: "필수 파라미터가 누락되었습니다.",
});
}
const connector = await getExternalDbConnector(Number(externalDbConnectionId));
// 동적 쿼리 생성
const orderByClause = layerColumn ? `ORDER BY ${layerColumn}` : '';
const query = `
SELECT * FROM ${tableName}
WHERE ${locationKeyColumn} = '${locaKey}'
${orderByClause}
LIMIT 1000
`;
logger.info(`자재 조회 쿼리: ${query}`);
const result = await connector.executeQuery(query);
logger.info("자재 목록 조회", {
externalDbConnectionId,
tableName,
locaKey,
count: result.rows.length,
});
return res.json({
success: true,
data: result.rows,
});
} catch (error: any) {
logger.error("자재 목록 조회 실패", error);
return res.status(500).json({
success: false,
message: "자재 목록 조회 중 오류가 발생했습니다.",
error: error.message,
});
}
};
// 자재 개수 조회 (여러 Location 일괄) - 레거시, 호환성 유지
export const getMaterialCounts = async (req: Request, res: Response): Promise<Response> => {
try {
const { externalDbConnectionId, locationKeys, tableName } = req.body;
if (!externalDbConnectionId || !locationKeys || !tableName) {
return res.status(400).json({
success: false,
message: "필수 파라미터가 누락되었습니다.",
});
}
const connector = await getExternalDbConnector(Number(externalDbConnectionId));
const keysString = locationKeys.map((key: string) => `'${key}'`).join(",");
const query = `
SELECT
LOCAKEY as location_key,
COUNT(*) as count
FROM ${tableName}
WHERE LOCAKEY IN (${keysString})
GROUP BY LOCAKEY
`;
const result = await connector.executeQuery(query);
logger.info("자재 개수 조회", {
externalDbConnectionId,
tableName,
locationCount: locationKeys.length,
});
return res.json({
success: true,
data: result.rows,
});
} catch (error: any) {
logger.error("자재 개수 조회 실패", error);
return res.status(500).json({
success: false,
message: "자재 개수 조회 중 오류가 발생했습니다.",
error: error.message,
});
}
};