Files
5why-analyzer/models/AuditLog.js
donald f9ee43b73c feat: Add 5Why_ prefix to all database tables
- Rename all tables with 5Why_ prefix for namespace isolation
- Update models: User.js, Analysis.js, AuditLog.js
- Update routes: llmConfig.js
- Update scripts: seed-test-users.js, add-deepseek-config.js, add-ollama-config.js
- Add migrate-table-prefix.js script for database migration
- Update db_schema.sql with new table names
- Update views: 5Why_user_analysis_stats, 5Why_recent_analyses

Tables renamed:
- users -> 5Why_users
- analyses -> 5Why_analyses
- analysis_perspectives -> 5Why_analysis_perspectives
- analysis_whys -> 5Why_analysis_whys
- llm_configs -> 5Why_llm_configs
- system_settings -> 5Why_system_settings
- audit_logs -> 5Why_audit_logs
- sessions -> 5Why_sessions

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-09 18:19:53 +08:00

214 lines
5.3 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { pool } from '../config.js';
/**
* AuditLog Model
* 處理稽核日誌相關的資料庫操作
*/
class AuditLog {
/**
* 建立稽核日誌
*/
static async create(logData) {
const {
user_id = null,
action,
entity_type = null,
entity_id = null,
old_value = null,
new_value = null,
ip_address = null,
user_agent = null,
status = 'success',
error_message = null
} = logData;
try {
await pool.execute(
`INSERT INTO 5Why_audit_logs
(user_id, action, entity_type, entity_id, old_value, new_value,
ip_address, user_agent, status, error_message)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
[
user_id,
action,
entity_type,
entity_id,
old_value ? JSON.stringify(old_value) : null,
new_value ? JSON.stringify(new_value) : null,
ip_address,
user_agent,
status,
error_message
]
);
} catch (error) {
console.error('Error creating audit log:', error);
// 不拋出錯誤,以免影響主要業務邏輯
}
}
/**
* 記錄登入
*/
static async logLogin(userId, ipAddress, userAgent, success = true) {
await this.create({
user_id: userId,
action: 'login',
ip_address: ipAddress,
user_agent: userAgent,
status: success ? 'success' : 'failed'
});
}
/**
* 記錄登出
*/
static async logLogout(userId, ipAddress, userAgent) {
await this.create({
user_id: userId,
action: 'logout',
ip_address: ipAddress,
user_agent: userAgent,
status: 'success'
});
}
/**
* 記錄建立操作
*/
static async logCreate(userId, entityType, entityId, newValue, ipAddress, userAgent) {
await this.create({
user_id: userId,
action: `create_${entityType}`,
entity_type: entityType,
entity_id: entityId,
new_value: newValue,
ip_address: ipAddress,
user_agent: userAgent
});
}
/**
* 記錄更新操作
*/
static async logUpdate(userId, entityType, entityId, oldValue, newValue, ipAddress, userAgent) {
await this.create({
user_id: userId,
action: `update_${entityType}`,
entity_type: entityType,
entity_id: entityId,
old_value: oldValue,
new_value: newValue,
ip_address: ipAddress,
user_agent: userAgent
});
}
/**
* 記錄刪除操作
*/
static async logDelete(userId, entityType, entityId, oldValue, ipAddress, userAgent) {
await this.create({
user_id: userId,
action: `delete_${entityType}`,
entity_type: entityType,
entity_id: entityId,
old_value: oldValue,
ip_address: ipAddress,
user_agent: userAgent
});
}
/**
* 取得稽核日誌(分頁)
*/
static async getAll(page = 1, limit = 50, filters = {}) {
const offset = (page - 1) * limit;
let query = `
SELECT al.*, u.username, u.employee_id
FROM 5Why_audit_logs al
LEFT JOIN 5Why_users u ON al.user_id = u.id
`;
let countQuery = 'SELECT COUNT(*) as total FROM 5Why_audit_logs al';
const params = [];
const whereClauses = [];
// 篩選條件
if (filters.user_id) {
whereClauses.push('al.user_id = ?');
params.push(filters.user_id);
}
if (filters.action) {
whereClauses.push('al.action = ?');
params.push(filters.action);
}
if (filters.entity_type) {
whereClauses.push('al.entity_type = ?');
params.push(filters.entity_type);
}
if (filters.status) {
whereClauses.push('al.status = ?');
params.push(filters.status);
}
if (filters.date_from) {
whereClauses.push('al.created_at >= ?');
params.push(filters.date_from);
}
if (filters.date_to) {
whereClauses.push('al.created_at <= ?');
params.push(filters.date_to);
}
if (whereClauses.length > 0) {
const whereClause = ' WHERE ' + whereClauses.join(' AND ');
query += whereClause;
countQuery += whereClause;
}
query += ' ORDER BY al.created_at DESC LIMIT ? OFFSET ?';
params.push(parseInt(limit), parseInt(offset));
try {
// 使用 pool.query 而非 pool.execute因為 LIMIT/OFFSET 需要數字類型
const [rows] = await pool.query(query, params);
const [countResult] = await pool.query(countQuery, params.slice(0, -2));
return {
data: rows,
pagination: {
page,
limit,
total: countResult[0].total,
totalPages: Math.ceil(countResult[0].total / limit)
}
};
} catch (error) {
throw new Error(`Error getting audit logs: ${error.message}`);
}
}
/**
* 取得使用者的操作日誌
*/
static async getByUserId(userId, page = 1, limit = 50) {
return await this.getAll(page, limit, { user_id: userId });
}
/**
* 清理舊日誌(保留 N 天)
*/
static async cleanup(daysToKeep = 90) {
try {
const [result] = await pool.execute(
'DELETE FROM 5Why_audit_logs WHERE created_at < DATE_SUB(NOW(), INTERVAL ? DAY)',
[daysToKeep]
);
return result.affectedRows;
} catch (error) {
throw new Error(`Error cleaning up audit logs: ${error.message}`);
}
}
}
export default AuditLog;