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:
parent
5c6469c75c
commit
fd90e3d761
|
|
@ -8,6 +8,7 @@ import { logger } from "../../utils/logger";
|
||||||
import { NodeFlowExecutionService } from "../../services/nodeFlowExecutionService";
|
import { NodeFlowExecutionService } from "../../services/nodeFlowExecutionService";
|
||||||
import { AuthenticatedRequest } from "../../types/auth";
|
import { AuthenticatedRequest } from "../../types/auth";
|
||||||
import { authenticateToken } from "../../middleware/authMiddleware";
|
import { authenticateToken } from "../../middleware/authMiddleware";
|
||||||
|
import { auditLogService, getClientIp } from "../../services/auditLogService";
|
||||||
|
|
||||||
const router = Router();
|
const router = Router();
|
||||||
|
|
||||||
|
|
@ -124,6 +125,21 @@ router.post("/", async (req: AuthenticatedRequest, res: Response) => {
|
||||||
`플로우 저장 성공: ${result.flowId} (회사: ${userCompanyCode})`
|
`플로우 저장 성공: ${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({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: "플로우가 저장되었습니다.",
|
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 {
|
try {
|
||||||
const { flowId, flowName, flowDescription, flowData } = req.body;
|
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(
|
await query(
|
||||||
`
|
`
|
||||||
UPDATE node_flows
|
UPDATE node_flows
|
||||||
|
|
@ -168,6 +189,25 @@ router.put("/", async (req: Request, res: Response) => {
|
||||||
|
|
||||||
logger.info(`플로우 수정 성공: ${flowId}`);
|
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({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: "플로우가 수정되었습니다.",
|
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 {
|
try {
|
||||||
const { flowId } = req.params;
|
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(
|
await query(
|
||||||
`
|
`
|
||||||
DELETE FROM node_flows
|
DELETE FROM node_flows
|
||||||
|
|
@ -201,6 +246,25 @@ router.delete("/:flowId", async (req: Request, res: Response) => {
|
||||||
|
|
||||||
logger.info(`플로우 삭제 성공: ${flowId}`);
|
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({
|
return res.json({
|
||||||
success: true,
|
success: true,
|
||||||
message: "플로우가 삭제되었습니다.",
|
message: "플로우가 삭제되었습니다.",
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,8 @@ export type AuditResourceType =
|
||||||
| "DATA"
|
| "DATA"
|
||||||
| "TABLE"
|
| "TABLE"
|
||||||
| "NUMBERING_RULE"
|
| "NUMBERING_RULE"
|
||||||
| "BATCH";
|
| "BATCH"
|
||||||
|
| "NODE_FLOW";
|
||||||
|
|
||||||
export interface AuditLogParams {
|
export interface AuditLogParams {
|
||||||
companyCode: string;
|
companyCode: string;
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,7 @@ const RESOURCE_TYPE_CONFIG: Record<
|
||||||
SCREEN_LAYOUT: { label: "레이아웃", icon: Monitor, color: "bg-purple-100 text-purple-700" },
|
SCREEN_LAYOUT: { label: "레이아웃", icon: Monitor, color: "bg-purple-100 text-purple-700" },
|
||||||
FLOW: { label: "플로우", icon: GitBranch, color: "bg-emerald-100 text-emerald-700" },
|
FLOW: { label: "플로우", icon: GitBranch, color: "bg-emerald-100 text-emerald-700" },
|
||||||
FLOW_STEP: { 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" },
|
USER: { label: "사용자", icon: User, color: "bg-amber-100 text-orange-700" },
|
||||||
ROLE: { label: "권한", icon: Shield, color: "bg-destructive/10 text-destructive" },
|
ROLE: { label: "권한", icon: Shield, color: "bg-destructive/10 text-destructive" },
|
||||||
PERMISSION: { label: "권한", icon: Shield, color: "bg-destructive/10 text-destructive" },
|
PERMISSION: { label: "권한", icon: Shield, color: "bg-destructive/10 text-destructive" },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue