Merge branch 'main' of http://39.117.244.52:3000/kjs/ERP-node into feature/screen-management
This commit is contained in:
@@ -59,6 +59,7 @@ import bookingRoutes from "./routes/bookingRoutes"; // 예약 요청 관리
|
||||
import mapDataRoutes from "./routes/mapDataRoutes"; // 지도 데이터 관리
|
||||
import yardLayoutRoutes from "./routes/yardLayoutRoutes"; // 야드 관리 3D
|
||||
//import materialRoutes from "./routes/materialRoutes"; // 자재 관리
|
||||
import digitalTwinRoutes from "./routes/digitalTwinRoutes"; // 디지털 트윈 (야드 관제)
|
||||
import flowRoutes from "./routes/flowRoutes"; // 플로우 관리
|
||||
import flowExternalDbConnectionRoutes from "./routes/flowExternalDbConnectionRoutes"; // 플로우 전용 외부 DB 연결
|
||||
import workHistoryRoutes from "./routes/workHistoryRoutes"; // 작업 이력 관리
|
||||
@@ -223,6 +224,7 @@ app.use("/api/bookings", bookingRoutes); // 예약 요청 관리
|
||||
app.use("/api/map-data", mapDataRoutes); // 지도 데이터 조회
|
||||
app.use("/api/yard-layouts", yardLayoutRoutes); // 야드 관리 3D
|
||||
// app.use("/api/materials", materialRoutes); // 자재 관리 (임시 주석)
|
||||
app.use("/api/digital-twin", digitalTwinRoutes); // 디지털 트윈 (야드 관제)
|
||||
app.use("/api/flow-external-db", flowExternalDbConnectionRoutes); // 플로우 전용 외부 DB 연결
|
||||
app.use("/api/flow", flowRoutes); // 플로우 관리 (마지막에 등록하여 다른 라우트와 충돌 방지)
|
||||
app.use("/api/work-history", workHistoryRoutes); // 작업 이력 관리
|
||||
|
||||
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,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
386
backend-node/src/controllers/digitalTwinLayoutController.ts
Normal file
386
backend-node/src/controllers/digitalTwinLayoutController.ts
Normal file
@@ -0,0 +1,386 @@
|
||||
import { Request, Response } from "express";
|
||||
import { pool } from "../database/db";
|
||||
import logger from "../utils/logger";
|
||||
|
||||
// 레이아웃 목록 조회
|
||||
export const getLayouts = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<Response> => {
|
||||
try {
|
||||
const companyCode = req.user?.companyCode;
|
||||
const { externalDbConnectionId, warehouseKey } = req.query;
|
||||
|
||||
let query = `
|
||||
SELECT
|
||||
l.*,
|
||||
u1.user_name as created_by_name,
|
||||
u2.user_name as updated_by_name,
|
||||
COUNT(o.id) as object_count
|
||||
FROM digital_twin_layout l
|
||||
LEFT JOIN user_info u1 ON l.created_by = u1.user_id
|
||||
LEFT JOIN user_info u2 ON l.updated_by = u2.user_id
|
||||
LEFT JOIN digital_twin_objects o ON l.id = o.layout_id
|
||||
WHERE l.company_code = $1
|
||||
`;
|
||||
|
||||
const params: any[] = [companyCode];
|
||||
let paramIndex = 2;
|
||||
|
||||
if (externalDbConnectionId) {
|
||||
query += ` AND l.external_db_connection_id = $${paramIndex}`;
|
||||
params.push(externalDbConnectionId);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
if (warehouseKey) {
|
||||
query += ` AND l.warehouse_key = $${paramIndex}`;
|
||||
params.push(warehouseKey);
|
||||
paramIndex++;
|
||||
}
|
||||
|
||||
query += `
|
||||
GROUP BY l.id, u1.user_name, u2.user_name
|
||||
ORDER BY l.updated_at DESC
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, params);
|
||||
|
||||
logger.info("레이아웃 목록 조회", {
|
||||
companyCode,
|
||||
count: result.rowCount,
|
||||
});
|
||||
|
||||
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 getLayoutById = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<Response> => {
|
||||
try {
|
||||
const companyCode = req.user?.companyCode;
|
||||
const { id } = req.params;
|
||||
|
||||
// 레이아웃 기본 정보
|
||||
const layoutQuery = `
|
||||
SELECT l.*
|
||||
FROM digital_twin_layout l
|
||||
WHERE l.id = $1 AND l.company_code = $2
|
||||
`;
|
||||
|
||||
const layoutResult = await pool.query(layoutQuery, [id, companyCode]);
|
||||
|
||||
if (layoutResult.rowCount === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "레이아웃을 찾을 수 없습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
// 배치된 객체들 조회
|
||||
const objectsQuery = `
|
||||
SELECT *
|
||||
FROM digital_twin_objects
|
||||
WHERE layout_id = $1
|
||||
ORDER BY display_order, created_at
|
||||
`;
|
||||
|
||||
const objectsResult = await pool.query(objectsQuery, [id]);
|
||||
|
||||
logger.info("레이아웃 상세 조회", {
|
||||
companyCode,
|
||||
layoutId: id,
|
||||
objectCount: objectsResult.rowCount,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: {
|
||||
layout: layoutResult.rows[0],
|
||||
objects: objectsResult.rows,
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error("레이아웃 상세 조회 실패", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "레이아웃 조회 중 오류가 발생했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// 레이아웃 생성
|
||||
export const createLayout = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<Response> => {
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
const companyCode = req.user?.companyCode;
|
||||
const userId = req.user?.userId;
|
||||
const {
|
||||
externalDbConnectionId,
|
||||
warehouseKey,
|
||||
layoutName,
|
||||
description,
|
||||
objects,
|
||||
} = req.body;
|
||||
|
||||
await client.query("BEGIN");
|
||||
|
||||
// 레이아웃 생성
|
||||
const layoutQuery = `
|
||||
INSERT INTO digital_twin_layout (
|
||||
company_code, external_db_connection_id, warehouse_key,
|
||||
layout_name, description, created_by, updated_by
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $6)
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const layoutResult = await client.query(layoutQuery, [
|
||||
companyCode,
|
||||
externalDbConnectionId,
|
||||
warehouseKey,
|
||||
layoutName,
|
||||
description,
|
||||
userId,
|
||||
]);
|
||||
|
||||
const layoutId = layoutResult.rows[0].id;
|
||||
|
||||
// 객체들 저장
|
||||
if (objects && objects.length > 0) {
|
||||
const objectQuery = `
|
||||
INSERT INTO digital_twin_objects (
|
||||
layout_id, object_type, object_name,
|
||||
position_x, position_y, position_z,
|
||||
size_x, size_y, size_z,
|
||||
rotation, color,
|
||||
area_key, loca_key, loc_type,
|
||||
material_count, material_preview_height,
|
||||
parent_id, display_order, locked
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
|
||||
`;
|
||||
|
||||
for (const obj of objects) {
|
||||
await client.query(objectQuery, [
|
||||
layoutId,
|
||||
obj.type,
|
||||
obj.name,
|
||||
obj.position.x,
|
||||
obj.position.y,
|
||||
obj.position.z,
|
||||
obj.size.x,
|
||||
obj.size.y,
|
||||
obj.size.z,
|
||||
obj.rotation || 0,
|
||||
obj.color,
|
||||
obj.areaKey || null,
|
||||
obj.locaKey || null,
|
||||
obj.locType || null,
|
||||
obj.materialCount || 0,
|
||||
obj.materialPreview?.height || null,
|
||||
obj.parentId || null,
|
||||
obj.displayOrder || 0,
|
||||
obj.locked || false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
await client.query("COMMIT");
|
||||
|
||||
logger.info("레이아웃 생성", {
|
||||
companyCode,
|
||||
layoutId,
|
||||
objectCount: objects?.length || 0,
|
||||
});
|
||||
|
||||
return res.status(201).json({
|
||||
success: true,
|
||||
data: layoutResult.rows[0],
|
||||
});
|
||||
} catch (error: any) {
|
||||
await client.query("ROLLBACK");
|
||||
logger.error("레이아웃 생성 실패", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "레이아웃 생성 중 오류가 발생했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
};
|
||||
|
||||
// 레이아웃 수정
|
||||
export const updateLayout = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<Response> => {
|
||||
const client = await pool.connect();
|
||||
|
||||
try {
|
||||
const companyCode = req.user?.companyCode;
|
||||
const userId = req.user?.userId;
|
||||
const { id } = req.params;
|
||||
const { layoutName, description, objects } = req.body;
|
||||
|
||||
await client.query("BEGIN");
|
||||
|
||||
// 레이아웃 기본 정보 수정
|
||||
const updateLayoutQuery = `
|
||||
UPDATE digital_twin_layout
|
||||
SET layout_name = $1,
|
||||
description = $2,
|
||||
updated_by = $3,
|
||||
updated_at = NOW()
|
||||
WHERE id = $4 AND company_code = $5
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const layoutResult = await client.query(updateLayoutQuery, [
|
||||
layoutName,
|
||||
description,
|
||||
userId,
|
||||
id,
|
||||
companyCode,
|
||||
]);
|
||||
|
||||
if (layoutResult.rowCount === 0) {
|
||||
await client.query("ROLLBACK");
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "레이아웃을 찾을 수 없습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
// 기존 객체 삭제
|
||||
await client.query(
|
||||
"DELETE FROM digital_twin_objects WHERE layout_id = $1",
|
||||
[id]
|
||||
);
|
||||
|
||||
// 새 객체 저장
|
||||
if (objects && objects.length > 0) {
|
||||
const objectQuery = `
|
||||
INSERT INTO digital_twin_objects (
|
||||
layout_id, object_type, object_name,
|
||||
position_x, position_y, position_z,
|
||||
size_x, size_y, size_z,
|
||||
rotation, color,
|
||||
area_key, loca_key, loc_type,
|
||||
material_count, material_preview_height,
|
||||
parent_id, display_order, locked
|
||||
)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19)
|
||||
`;
|
||||
|
||||
for (const obj of objects) {
|
||||
await client.query(objectQuery, [
|
||||
id,
|
||||
obj.type,
|
||||
obj.name,
|
||||
obj.position.x,
|
||||
obj.position.y,
|
||||
obj.position.z,
|
||||
obj.size.x,
|
||||
obj.size.y,
|
||||
obj.size.z,
|
||||
obj.rotation || 0,
|
||||
obj.color,
|
||||
obj.areaKey || null,
|
||||
obj.locaKey || null,
|
||||
obj.locType || null,
|
||||
obj.materialCount || 0,
|
||||
obj.materialPreview?.height || null,
|
||||
obj.parentId || null,
|
||||
obj.displayOrder || 0,
|
||||
obj.locked || false,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
await client.query("COMMIT");
|
||||
|
||||
logger.info("레이아웃 수정", {
|
||||
companyCode,
|
||||
layoutId: id,
|
||||
objectCount: objects?.length || 0,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
data: layoutResult.rows[0],
|
||||
});
|
||||
} catch (error: any) {
|
||||
await client.query("ROLLBACK");
|
||||
logger.error("레이아웃 수정 실패", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "레이아웃 수정 중 오류가 발생했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
} finally {
|
||||
client.release();
|
||||
}
|
||||
};
|
||||
|
||||
// 레이아웃 삭제
|
||||
export const deleteLayout = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<Response> => {
|
||||
try {
|
||||
const companyCode = req.user?.companyCode;
|
||||
const { id } = req.params;
|
||||
|
||||
const query = `
|
||||
DELETE FROM digital_twin_layout
|
||||
WHERE id = $1 AND company_code = $2
|
||||
RETURNING id
|
||||
`;
|
||||
|
||||
const result = await pool.query(query, [id, companyCode]);
|
||||
|
||||
if (result.rowCount === 0) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: "레이아웃을 찾을 수 없습니다.",
|
||||
});
|
||||
}
|
||||
|
||||
logger.info("레이아웃 삭제", {
|
||||
companyCode,
|
||||
layoutId: id,
|
||||
});
|
||||
|
||||
return res.json({
|
||||
success: true,
|
||||
message: "레이아웃이 삭제되었습니다.",
|
||||
});
|
||||
} catch (error: any) {
|
||||
logger.error("레이아웃 삭제 실패", error);
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: "레이아웃 삭제 중 오류가 발생했습니다.",
|
||||
error: error.message,
|
||||
});
|
||||
}
|
||||
};
|
||||
66
backend-node/src/routes/digitalTwinRoutes.ts
Normal file
66
backend-node/src/routes/digitalTwinRoutes.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import express from "express";
|
||||
import { authenticateToken } from "../middleware/authMiddleware";
|
||||
|
||||
// 레이아웃 관리
|
||||
import {
|
||||
getLayouts,
|
||||
getLayoutById,
|
||||
createLayout,
|
||||
updateLayout,
|
||||
deleteLayout,
|
||||
} from "../controllers/digitalTwinLayoutController";
|
||||
|
||||
// 외부 DB 데이터 조회
|
||||
import {
|
||||
getWarehouses,
|
||||
getAreas,
|
||||
getLocations,
|
||||
getMaterials,
|
||||
getMaterialCounts,
|
||||
} from "../controllers/digitalTwinDataController";
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// 모든 라우트에 인증 미들웨어 적용
|
||||
router.use(authenticateToken);
|
||||
|
||||
// ========== 레이아웃 관리 API ==========
|
||||
router.get("/layouts", getLayouts); // 레이아웃 목록
|
||||
router.get("/layouts/:id", getLayoutById); // 레이아웃 상세
|
||||
router.post("/layouts", createLayout); // 레이아웃 생성
|
||||
router.put("/layouts/:id", updateLayout); // 레이아웃 수정
|
||||
router.delete("/layouts/:id", deleteLayout); // 레이아웃 삭제
|
||||
|
||||
// ========== 외부 DB 데이터 조회 API ==========
|
||||
router.get("/data/tables/:connectionId", async (req, res) => {
|
||||
// 테이블 목록 조회
|
||||
try {
|
||||
const { ExternalDbConnectionService } = await import("../services/externalDbConnectionService");
|
||||
const result = await ExternalDbConnectionService.getTablesFromConnection(Number(req.params.connectionId));
|
||||
return res.json(result);
|
||||
} catch (error: any) {
|
||||
return res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/data/table-preview/:connectionId/:tableName", async (req, res) => {
|
||||
// 테이블 미리보기 (10개 레코드)
|
||||
try {
|
||||
const { connectionId, tableName } = req.params;
|
||||
const { getExternalDbConnector } = await import("../controllers/digitalTwinDataController");
|
||||
const connector = await getExternalDbConnector(Number(connectionId));
|
||||
const result = await connector.executeQuery(`SELECT * FROM ${tableName} LIMIT 10`);
|
||||
return res.json({ success: true, data: result.rows });
|
||||
} catch (error: any) {
|
||||
return res.status(500).json({ success: false, error: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
router.get("/data/warehouses", getWarehouses); // 창고 목록
|
||||
router.get("/data/areas", getAreas); // Area 목록
|
||||
router.get("/data/locations", getLocations); // Location 목록
|
||||
router.get("/data/materials", getMaterials); // 자재 목록 (특정 Location)
|
||||
router.get("/data/material-counts", getMaterialCounts); // 자재 개수 (여러 Location)
|
||||
|
||||
export default router;
|
||||
|
||||
Reference in New Issue
Block a user