feat: add audit logging for node flow operations

- Integrated audit logging for create, update, and delete actions in the node flows API.
- Enhanced the logging service to capture relevant details such as user information, action type, resource details, and IP address.
- Updated the audit log service to include NODE_FLOW as a resource type.
- Improved the overall traceability of node flow changes within the system.

Made-with: Cursor
This commit is contained in:
kjs 2026-03-12 02:13:15 +09:00
parent 5c6469c75c
commit fd90e3d761
3 changed files with 69 additions and 3 deletions

View File

@ -8,6 +8,7 @@ import { logger } from "../../utils/logger";
import { NodeFlowExecutionService } from "../../services/nodeFlowExecutionService";
import { AuthenticatedRequest } from "../../types/auth";
import { authenticateToken } from "../../middleware/authMiddleware";
import { auditLogService, getClientIp } from "../../services/auditLogService";
const router = Router();
@ -124,6 +125,21 @@ router.post("/", async (req: AuthenticatedRequest, res: Response) => {
`플로우 저장 성공: ${result.flowId} (회사: ${userCompanyCode})`
);
auditLogService.log({
companyCode: userCompanyCode,
userId: req.user?.userId || "",
userName: req.user?.userName,
action: "CREATE",
resourceType: "NODE_FLOW",
resourceId: String(result.flowId),
resourceName: flowName,
tableName: "node_flows",
summary: `노드 플로우 "${flowName}" 생성`,
changes: { after: { flowName, flowDescription } },
ipAddress: getClientIp(req as any),
requestPath: req.originalUrl,
});
return res.json({
success: true,
message: "플로우가 저장되었습니다.",
@ -143,7 +159,7 @@ router.post("/", async (req: AuthenticatedRequest, res: Response) => {
/**
*
*/
router.put("/", async (req: Request, res: Response) => {
router.put("/", async (req: AuthenticatedRequest, res: Response) => {
try {
const { flowId, flowName, flowDescription, flowData } = req.body;
@ -154,6 +170,11 @@ router.put("/", async (req: Request, res: Response) => {
});
}
const oldFlow = await queryOne(
`SELECT flow_name, flow_description FROM node_flows WHERE flow_id = $1`,
[flowId]
);
await query(
`
UPDATE node_flows
@ -168,6 +189,25 @@ router.put("/", async (req: Request, res: Response) => {
logger.info(`플로우 수정 성공: ${flowId}`);
const userCompanyCode = req.user?.companyCode || "*";
auditLogService.log({
companyCode: userCompanyCode,
userId: req.user?.userId || "",
userName: req.user?.userName,
action: "UPDATE",
resourceType: "NODE_FLOW",
resourceId: String(flowId),
resourceName: flowName,
tableName: "node_flows",
summary: `노드 플로우 "${flowName}" 수정`,
changes: {
before: oldFlow ? { flowName: (oldFlow as any).flow_name, flowDescription: (oldFlow as any).flow_description } : undefined,
after: { flowName, flowDescription },
},
ipAddress: getClientIp(req as any),
requestPath: req.originalUrl,
});
return res.json({
success: true,
message: "플로우가 수정되었습니다.",
@ -187,10 +227,15 @@ router.put("/", async (req: Request, res: Response) => {
/**
*
*/
router.delete("/:flowId", async (req: Request, res: Response) => {
router.delete("/:flowId", async (req: AuthenticatedRequest, res: Response) => {
try {
const { flowId } = req.params;
const oldFlow = await queryOne(
`SELECT flow_name, flow_description, company_code FROM node_flows WHERE flow_id = $1`,
[flowId]
);
await query(
`
DELETE FROM node_flows
@ -201,6 +246,25 @@ router.delete("/:flowId", async (req: Request, res: Response) => {
logger.info(`플로우 삭제 성공: ${flowId}`);
const userCompanyCode = req.user?.companyCode || "*";
const flowName = (oldFlow as any)?.flow_name || `ID:${flowId}`;
auditLogService.log({
companyCode: userCompanyCode,
userId: req.user?.userId || "",
userName: req.user?.userName,
action: "DELETE",
resourceType: "NODE_FLOW",
resourceId: String(flowId),
resourceName: flowName,
tableName: "node_flows",
summary: `노드 플로우 "${flowName}" 삭제`,
changes: {
before: oldFlow ? { flowName: (oldFlow as any).flow_name, flowDescription: (oldFlow as any).flow_description } : undefined,
},
ipAddress: getClientIp(req as any),
requestPath: req.originalUrl,
});
return res.json({
success: true,
message: "플로우가 삭제되었습니다.",

View File

@ -41,7 +41,8 @@ export type AuditResourceType =
| "DATA"
| "TABLE"
| "NUMBERING_RULE"
| "BATCH";
| "BATCH"
| "NODE_FLOW";
export interface AuditLogParams {
companyCode: string;

View File

@ -74,6 +74,7 @@ const RESOURCE_TYPE_CONFIG: Record<
SCREEN_LAYOUT: { label: "레이아웃", icon: Monitor, color: "bg-purple-100 text-purple-700" },
FLOW: { label: "플로우", icon: GitBranch, color: "bg-emerald-100 text-emerald-700" },
FLOW_STEP: { label: "플로우 스텝", icon: GitBranch, color: "bg-emerald-100 text-emerald-700" },
NODE_FLOW: { label: "플로우 제어", icon: GitBranch, color: "bg-teal-100 text-teal-700" },
USER: { label: "사용자", icon: User, color: "bg-amber-100 text-orange-700" },
ROLE: { label: "권한", icon: Shield, color: "bg-destructive/10 text-destructive" },
PERMISSION: { label: "권한", icon: Shield, color: "bg-destructive/10 text-destructive" },