feat: Integrate audit logging for various operations

- Added audit logging functionality across multiple controllers, including menu, user, department, flow, screen, and table management.
- Implemented logging for create, update, and delete actions, capturing relevant details such as company code, user information, and changes made.
- Enhanced the category tree service with a new endpoint to check if category values are in use, improving data integrity checks.
- Updated routes to include new functionalities and ensure proper logging for batch operations and individual record changes.
- This integration improves traceability and accountability for data modifications within the application.
This commit is contained in:
kjs
2026-03-04 13:49:08 +09:00
parent f04d224b09
commit b4d5367e2b
26 changed files with 2620 additions and 140 deletions

View File

@@ -0,0 +1,11 @@
import { Router } from "express";
import { authenticateToken } from "../middleware/authMiddleware";
import { getAuditLogs, getAuditLogStats, getAuditLogUsers } from "../controllers/auditLogController";
const router = Router();
router.get("/", authenticateToken, getAuditLogs);
router.get("/stats", authenticateToken, getAuditLogStats);
router.get("/users", authenticateToken, getAuditLogUsers);
export default router;

View File

@@ -3,6 +3,7 @@ import { dataService } from "../services/dataService";
import { masterDetailExcelService } from "../services/masterDetailExcelService";
import { authenticateToken } from "../middleware/authMiddleware";
import { AuthenticatedRequest } from "../types/auth";
import { auditLogService } from "../services/auditLogService";
const router = express.Router();
@@ -736,17 +737,39 @@ router.post(
return res.status(400).json(result);
}
const inserted = result.data?.inserted || 0;
const updated = result.data?.updated || 0;
const deleted = result.data?.deleted || 0;
console.log(`✅ 그룹화된 데이터 UPSERT 성공: ${tableName}`, {
inserted: result.data?.inserted || 0,
updated: result.data?.updated || 0,
deleted: result.data?.deleted || 0,
inserted, updated, deleted,
});
const parts: string[] = [];
if (inserted > 0) parts.push(`${inserted}건 생성`);
if (updated > 0) parts.push(`${updated}건 수정`);
if (deleted > 0) parts.push(`${deleted}건 삭제`);
if (parts.length > 0) {
auditLogService.log({
companyCode: req.user?.companyCode || "",
userId: req.user?.userId || "",
userName: req.user?.userName || "",
action: inserted > 0 && updated === 0 && deleted === 0 ? "BATCH_CREATE" : "UPDATE",
resourceType: "DATA",
tableName,
summary: `${tableName} 테이블 배치 처리: ${parts.join(", ")}`,
changes: { after: { inserted, updated, deleted } },
ipAddress: (req as any).ip,
requestPath: req.originalUrl,
});
}
return res.json({
success: true,
message: "데이터가 저장되었습니다.",
inserted: result.data?.inserted || 0,
updated: result.data?.updated || 0,
inserted,
updated,
deleted: result.data?.deleted || 0,
savedIds: result.data?.savedIds || [],
});
@@ -824,6 +847,19 @@ router.post(
console.log(`✅ 레코드 생성 성공: ${tableName}`);
auditLogService.log({
companyCode: req.user?.companyCode || "",
userId: req.user?.userId || "",
userName: req.user?.userName || "",
action: "CREATE",
resourceType: "DATA",
resourceId: result.data?.id ? String(result.data.id) : undefined,
tableName,
summary: `${tableName} 테이블에 데이터 1건 생성`,
ipAddress: (req as any).ip,
requestPath: req.originalUrl,
});
return res.status(201).json({
success: true,
data: result.data,
@@ -880,6 +916,20 @@ router.put(
console.log(`✅ 레코드 수정 성공: ${tableName}/${id}`);
auditLogService.log({
companyCode: req.user?.companyCode || "",
userId: req.user?.userId || "",
userName: req.user?.userName || "",
action: "UPDATE",
resourceType: "DATA",
resourceId: String(id),
tableName,
summary: `${tableName} 테이블 데이터 수정 (ID:${id})`,
changes: { after: data, fields: Object.keys(data || {}) },
ipAddress: (req as any).ip,
requestPath: req.originalUrl,
});
return res.json({
success: true,
data: result.data,
@@ -940,6 +990,20 @@ router.post(
}
console.log(`✅ 레코드 삭제 성공: ${tableName}`);
auditLogService.log({
companyCode: req.user?.companyCode || "",
userId: req.user?.userId || "",
userName: req.user?.userName || "",
action: "DELETE",
resourceType: "DATA",
tableName,
summary: `${tableName} 테이블 데이터 삭제 (복합키)`,
changes: { before: compositeKey },
ipAddress: (req as any).ip,
requestPath: req.originalUrl,
});
return res.json(result);
} catch (error: any) {
console.error(`레코드 삭제 오류 (${req.params.tableName}):`, error);
@@ -1032,6 +1096,19 @@ router.delete(
console.log(`✅ 레코드 삭제 성공: ${tableName}/${id}`);
auditLogService.log({
companyCode: req.user?.companyCode || "",
userId: req.user?.userId || "",
userName: req.user?.userName || "",
action: "DELETE",
resourceType: "DATA",
resourceId: String(id),
tableName,
summary: `${tableName} 테이블 데이터 삭제 (ID:${id})`,
ipAddress: (req as any).ip,
requestPath: req.originalUrl,
});
return res.json({
success: true,
message: "레코드가 삭제되었습니다.",

View File

@@ -7,6 +7,7 @@ import {
createScreen,
updateScreen,
updateScreenInfo,
updateScreenTableName,
deleteScreen,
bulkDeleteScreens,
checkScreenDependencies,
@@ -65,6 +66,7 @@ router.get("/screens/:id/menu", getScreenMenu); // 화면에 할당된 메뉴
router.post("/screens", createScreen);
router.put("/screens/:id", updateScreen);
router.put("/screens/:id/info", updateScreenInfo); // 화면 정보만 수정
router.patch("/screens/:screenId/table-name", updateScreenTableName); // 화면 테이블명 변경
router.get("/screens/:id/dependencies", checkScreenDependencies); // 의존성 체크
router.delete("/screens/:id", deleteScreen); // 휴지통으로 이동
router.delete("/screens/bulk/delete", bulkDeleteScreens); // 활성 화면 일괄 삭제 (휴지통으로 이동)