feat: Digital Twin Editor 테이블 매핑 UI 및 백엔드 API 구현
This commit is contained in:
273
backend-node/src/controllers/digitalTwinDataController.ts
Normal file
273
backend-node/src/controllers/digitalTwinDataController.ts
Normal file
@@ -0,0 +1,273 @@
|
||||
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 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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Area 목록 조회 (사용자 지정 테이블)
|
||||
export const getAreas = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { externalDbConnectionId, tableName, warehouseKey } = req.query;
|
||||
|
||||
if (!externalDbConnectionId || !tableName) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "외부 DB 연결 ID와 테이블명이 필요합니다.",
|
||||
});
|
||||
}
|
||||
|
||||
const connector = await getExternalDbConnector(Number(externalDbConnectionId));
|
||||
|
||||
// 테이블명을 사용하여 모든 컬럼 조회
|
||||
let query = `SELECT * FROM ${tableName}`;
|
||||
|
||||
if (warehouseKey) {
|
||||
query += ` WHERE WAREKEY = '${warehouseKey}'`;
|
||||
}
|
||||
|
||||
query += ` LIMIT 1000`;
|
||||
|
||||
const result = await connector.executeQuery(query);
|
||||
|
||||
logger.info("Area 목록 조회", {
|
||||
externalDbConnectionId,
|
||||
tableName,
|
||||
warehouseKey,
|
||||
count: result.rows.length,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: result.rows,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error("Area 목록 조회 실패", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Area 목록 조회 중 오류가 발생했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Location 목록 조회 (사용자 지정 테이블)
|
||||
export const getLocations = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { externalDbConnectionId, tableName, areaKey } = req.query;
|
||||
|
||||
if (!externalDbConnectionId || !tableName) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "외부 DB 연결 ID와 테이블명이 필요합니다.",
|
||||
});
|
||||
}
|
||||
|
||||
const connector = await getExternalDbConnector(Number(externalDbConnectionId));
|
||||
|
||||
// 테이블명을 사용하여 모든 컬럼 조회
|
||||
let query = `SELECT * FROM ${tableName}`;
|
||||
|
||||
if (areaKey) {
|
||||
query += ` WHERE AREAKEY = '${areaKey}'`;
|
||||
}
|
||||
|
||||
query += ` LIMIT 1000`;
|
||||
|
||||
const result = await connector.executeQuery(query);
|
||||
|
||||
logger.info("Location 목록 조회", {
|
||||
externalDbConnectionId,
|
||||
tableName,
|
||||
areaKey,
|
||||
count: result.rows.length,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: result.rows,
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error("Location 목록 조회 실패", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "Location 목록 조회 중 오류가 발생했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 자재 목록 조회 (사용자 지정 테이블)
|
||||
export const getMaterials = async (req: Request, res: Response): Promise<Response> => {
|
||||
try {
|
||||
const { externalDbConnectionId, tableName, locaKey } = req.query;
|
||||
|
||||
if (!externalDbConnectionId || !tableName) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "외부 DB 연결 ID와 테이블명이 필요합니다.",
|
||||
});
|
||||
}
|
||||
|
||||
const connector = await getExternalDbConnector(Number(externalDbConnectionId));
|
||||
|
||||
// 테이블명을 사용하여 모든 컬럼 조회
|
||||
let query = `SELECT * FROM ${tableName}`;
|
||||
|
||||
if (locaKey) {
|
||||
query += ` WHERE LOCAKEY = '${locaKey}'`;
|
||||
}
|
||||
|
||||
query += ` LIMIT 1000`;
|
||||
|
||||
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, tableName, locaKeys } = req.query;
|
||||
|
||||
if (!externalDbConnectionId || !tableName || !locaKeys) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: "외부 DB 연결 ID, 테이블명, Location 키 목록이 필요합니다.",
|
||||
});
|
||||
}
|
||||
|
||||
const connector = await getExternalDbConnector(Number(externalDbConnectionId));
|
||||
|
||||
// locaKeys는 쉼표로 구분된 문자열
|
||||
const locaKeyArray = (locaKeys as string).split(",");
|
||||
const quotedKeys = locaKeyArray.map((key) => `'${key}'`).join(",");
|
||||
|
||||
const query = `
|
||||
SELECT
|
||||
LOCAKEY,
|
||||
COUNT(*) as material_count,
|
||||
MAX(LOLAYER) as max_layer
|
||||
FROM ${tableName}
|
||||
WHERE LOCAKEY IN (${quotedKeys})
|
||||
GROUP BY LOCAKEY
|
||||
`;
|
||||
|
||||
const result = await connector.executeQuery(query);
|
||||
|
||||
logger.info("자재 개수 조회", {
|
||||
externalDbConnectionId,
|
||||
tableName,
|
||||
locaKeyCount: locaKeyArray.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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user