Files
vexplor_dev/backend-node/src/controllers/auditLogController.ts
kjs 7c96461f59 feat: enhance audit log functionality and file upload components
- Updated the audit log controller to determine super admin status based on user type instead of company code.
- Added detailed logging for column settings updates and batch updates in the table management controller, capturing user actions and changes made.
- Implemented security measures in the audit log service to mask sensitive data for non-super admin users.
- Introduced a new TableCellFile component to handle file attachments, supporting both objid and JSON array formats for file information.
- Enhanced the file upload component to manage file states more effectively during record changes and mode transitions.

These updates aim to improve the audit logging capabilities and file management features within the ERP system, ensuring better security and user experience.
2026-03-17 11:31:54 +09:00

177 lines
5.2 KiB
TypeScript

import { Response } from "express";
import { AuthenticatedRequest } from "../middleware/authMiddleware";
import { auditLogService, getClientIp, AuditAction, AuditResourceType } from "../services/auditLogService";
import { query } from "../database/db";
import logger from "../utils/logger";
export const getAuditLogs = async (
req: AuthenticatedRequest,
res: Response
): Promise<void> => {
try {
const userCompanyCode = req.user?.companyCode;
const isSuperAdmin = req.user?.userType === "SUPER_ADMIN";
const {
companyCode,
userId,
resourceType,
action,
tableName,
dateFrom,
dateTo,
search,
page,
limit,
} = req.query;
const result = await auditLogService.queryLogs(
{
companyCode: (companyCode as string) || (isSuperAdmin ? undefined : userCompanyCode),
userId: userId as string,
resourceType: resourceType as string,
action: action as string,
tableName: tableName as string,
dateFrom: dateFrom as string,
dateTo: dateTo as string,
search: search as string,
page: page ? parseInt(page as string, 10) : 1,
limit: limit ? parseInt(limit as string, 10) : 50,
},
isSuperAdmin
);
res.json({
success: true,
data: result.data,
total: result.total,
page: page ? parseInt(page as string, 10) : 1,
limit: limit ? parseInt(limit as string, 10) : 50,
});
} catch (error: any) {
logger.error("감사 로그 조회 실패", { error: error.message });
res.status(500).json({
success: false,
message: "감사 로그 조회 중 오류가 발생했습니다.",
});
}
};
export const getAuditLogStats = async (
req: AuthenticatedRequest,
res: Response
): Promise<void> => {
try {
const userCompanyCode = req.user?.companyCode;
const isSuperAdmin = req.user?.userType === "SUPER_ADMIN";
const { companyCode, days } = req.query;
const targetCompany = isSuperAdmin
? (companyCode as string) || undefined
: userCompanyCode;
const stats = await auditLogService.getStats(
targetCompany,
days ? parseInt(days as string, 10) : 30
);
res.json({ success: true, data: stats });
} catch (error: any) {
logger.error("감사 로그 통계 조회 실패", { error: error.message });
res.status(500).json({
success: false,
message: "감사 로그 통계 조회 중 오류가 발생했습니다.",
});
}
};
export const getAuditLogUsers = async (
req: AuthenticatedRequest,
res: Response
): Promise<void> => {
try {
const userCompanyCode = req.user?.companyCode;
const isSuperAdmin = req.user?.userType === "SUPER_ADMIN";
const { companyCode } = req.query;
const conditions: string[] = ["LOWER(u.status) = 'active'"];
const params: any[] = [];
let paramIndex = 1;
if (!isSuperAdmin) {
conditions.push(`u.company_code = $${paramIndex++}`);
params.push(userCompanyCode);
} else if (companyCode) {
conditions.push(`u.company_code = $${paramIndex++}`);
params.push(companyCode);
}
if (!isSuperAdmin) {
conditions.push(`u.company_code != '*'`);
}
const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
const users = await query<{ user_id: string; user_name: string; count: number }>(
`SELECT
u.user_id,
u.user_name,
COALESCE(sal.log_count, 0)::int as count
FROM user_info u
LEFT JOIN (
SELECT user_id, COUNT(*) as log_count
FROM system_audit_log
GROUP BY user_id
) sal ON u.user_id = sal.user_id
${whereClause}
ORDER BY count DESC, u.user_name ASC`,
params
);
res.json({ success: true, data: users });
} catch (error: any) {
logger.error("감사 로그 사용자 목록 조회 실패", { error: error.message });
res.status(500).json({
success: false,
message: "사용자 목록 조회 중 오류가 발생했습니다.",
});
}
};
/**
* 프론트엔드에서 직접 감사 로그 기록 (그룹 복제 등 프론트 오케스트레이션 작업용)
*/
export const createAuditLog = async (
req: AuthenticatedRequest,
res: Response
): Promise<void> => {
try {
const { action, resourceType, resourceId, resourceName, tableName, summary, changes } = req.body;
if (!action || !resourceType) {
res.status(400).json({ success: false, message: "action, resourceType은 필수입니다." });
return;
}
await auditLogService.log({
companyCode: req.user?.companyCode || "",
userId: req.user?.userId || "",
userName: req.user?.userName || "",
action: action as AuditAction,
resourceType: resourceType as AuditResourceType,
resourceId: resourceId || undefined,
resourceName: resourceName || undefined,
tableName: tableName || undefined,
summary: summary || undefined,
changes: changes || undefined,
ipAddress: getClientIp(req),
requestPath: req.originalUrl,
});
res.json({ success: true });
} catch (error: any) {
logger.error("감사 로그 기록 실패", { error: error.message });
res.status(500).json({ success: false, message: "감사 로그 기록 실패" });
}
};