update
This commit is contained in:
@@ -0,0 +1,112 @@
|
||||
import { Database } from 'sqlite3';
|
||||
import { getDb } from '../database';
|
||||
import { AuditLogEntry, AuditLogActionType } from '../types/audit.types';
|
||||
|
||||
export class AuditLogRepository {
|
||||
private db: Database;
|
||||
|
||||
constructor() {
|
||||
this.db = getDb();
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加一条审计日志记录
|
||||
* @param actionType 操作类型
|
||||
* @param details 可选的详细信息 (对象或字符串)
|
||||
*/
|
||||
async addLog(actionType: AuditLogActionType, details?: Record<string, any> | string | null): Promise<void> {
|
||||
const timestamp = Math.floor(Date.now() / 1000); // Unix timestamp in seconds
|
||||
let detailsString: string | null = null;
|
||||
|
||||
if (details) {
|
||||
try {
|
||||
detailsString = typeof details === 'string' ? details : JSON.stringify(details);
|
||||
} catch (error) {
|
||||
console.error(`[Audit Log] Failed to stringify details for action ${actionType}:`, error);
|
||||
detailsString = JSON.stringify({ error: 'Failed to stringify details', originalDetails: details });
|
||||
}
|
||||
}
|
||||
|
||||
const sql = 'INSERT INTO audit_logs (timestamp, action_type, details) VALUES (?, ?, ?)';
|
||||
const params = [timestamp, actionType, detailsString];
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(sql, params, (err) => {
|
||||
if (err) {
|
||||
console.error(`[Audit Log] Error adding log entry for action ${actionType}: ${err.message}`);
|
||||
// 不拒绝 Promise,记录日志失败不应阻止核心操作
|
||||
// 但可以在这里触发一个 SERVER_ERROR 通知或日志
|
||||
resolve(); // Or potentially reject if logging is critical
|
||||
} else {
|
||||
// console.log(`[Audit Log] Logged action: ${actionType}`); // Optional: verbose logging
|
||||
resolve();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取审计日志列表 (支持分页和基本过滤)
|
||||
* @param limit 每页数量
|
||||
* @param offset 偏移量
|
||||
* @param actionType 可选的操作类型过滤
|
||||
* @param startDate 可选的开始时间戳 (秒)
|
||||
* @param endDate 可选的结束时间戳 (秒)
|
||||
*/
|
||||
async getLogs(
|
||||
limit: number = 50,
|
||||
offset: number = 0,
|
||||
actionType?: AuditLogActionType,
|
||||
startDate?: number,
|
||||
endDate?: number
|
||||
): Promise<{ logs: AuditLogEntry[], total: number }> {
|
||||
let baseSql = 'SELECT * FROM audit_logs';
|
||||
let countSql = 'SELECT COUNT(*) as total FROM audit_logs';
|
||||
const whereClauses: string[] = [];
|
||||
const params: (string | number)[] = [];
|
||||
const countParams: (string | number)[] = [];
|
||||
|
||||
if (actionType) {
|
||||
whereClauses.push('action_type = ?');
|
||||
params.push(actionType);
|
||||
countParams.push(actionType);
|
||||
}
|
||||
if (startDate) {
|
||||
whereClauses.push('timestamp >= ?');
|
||||
params.push(startDate);
|
||||
countParams.push(startDate);
|
||||
}
|
||||
if (endDate) {
|
||||
whereClauses.push('timestamp <= ?');
|
||||
params.push(endDate);
|
||||
countParams.push(endDate);
|
||||
}
|
||||
|
||||
if (whereClauses.length > 0) {
|
||||
const whereSql = ` WHERE ${whereClauses.join(' AND ')}`;
|
||||
baseSql += whereSql;
|
||||
countSql += whereSql;
|
||||
}
|
||||
|
||||
baseSql += ' ORDER BY timestamp DESC LIMIT ? OFFSET ?';
|
||||
params.push(limit, offset);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
// First get the total count
|
||||
this.db.get(countSql, countParams, (err, row: { total: number }) => {
|
||||
if (err) {
|
||||
return reject(new Error(`Error counting audit logs: ${err.message}`));
|
||||
}
|
||||
const total = row.total;
|
||||
|
||||
// Then get the paginated logs
|
||||
this.db.all(baseSql, params, (err, rows: AuditLogEntry[]) => {
|
||||
if (err) {
|
||||
return reject(new Error(`Error fetching audit logs: ${err.message}`));
|
||||
}
|
||||
resolve({ logs: rows, total });
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -104,7 +104,6 @@ export const findFullConnectionById = async (id: number): Promise<any | null> =>
|
||||
c.*, -- 选择 connections 表所有列
|
||||
p.id as proxy_db_id, p.name as proxy_name, p.type as proxy_type,
|
||||
p.host as proxy_host, p.port as proxy_port, p.username as proxy_username,
|
||||
p.auth_method as proxy_auth_method, -- 包含代理的 auth_method
|
||||
p.encrypted_password as proxy_encrypted_password,
|
||||
p.encrypted_private_key as proxy_encrypted_private_key, -- 包含代理的 key
|
||||
p.encrypted_passphrase as proxy_encrypted_passphrase -- 包含代理的 passphrase
|
||||
|
||||
@@ -0,0 +1,151 @@
|
||||
import { Database } from 'sqlite3';
|
||||
import { getDb } from '../database';
|
||||
import { NotificationSetting, RawNotificationSetting, NotificationChannelType, NotificationEvent, NotificationChannelConfig } from '../types/notification.types';
|
||||
|
||||
// Helper to parse raw data from DB
|
||||
const parseRawSetting = (raw: RawNotificationSetting): NotificationSetting => {
|
||||
try {
|
||||
return {
|
||||
...raw,
|
||||
enabled: Boolean(raw.enabled),
|
||||
config: JSON.parse(raw.config || '{}'),
|
||||
enabled_events: JSON.parse(raw.enabled_events || '[]'),
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error parsing notification setting ID ${raw.id}:`, error);
|
||||
// Return a default/error state or re-throw, depending on desired handling
|
||||
// For now, return partially parsed with defaults for JSON fields
|
||||
// Cast to satisfy type checker, but this indicates a parsing error.
|
||||
return {
|
||||
...raw,
|
||||
enabled: Boolean(raw.enabled),
|
||||
config: {} as NotificationChannelConfig, // Config is invalid due to parsing error
|
||||
enabled_events: [],
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
export class NotificationSettingsRepository {
|
||||
private db: Database;
|
||||
|
||||
constructor() {
|
||||
this.db = getDb();
|
||||
}
|
||||
|
||||
async getAll(): Promise<NotificationSetting[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.all('SELECT * FROM notification_settings ORDER BY created_at ASC', (err, rows: RawNotificationSetting[]) => {
|
||||
if (err) {
|
||||
return reject(new Error(`Error fetching notification settings: ${err.message}`));
|
||||
}
|
||||
resolve(rows.map(parseRawSetting));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getById(id: number): Promise<NotificationSetting | null> {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.get('SELECT * FROM notification_settings WHERE id = ?', [id], (err, row: RawNotificationSetting) => {
|
||||
if (err) {
|
||||
return reject(new Error(`Error fetching notification setting by ID ${id}: ${err.message}`));
|
||||
}
|
||||
resolve(row ? parseRawSetting(row) : null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async getEnabledByEvent(event: NotificationEvent): Promise<NotificationSetting[]> {
|
||||
return new Promise((resolve, reject) => {
|
||||
// Note: This query is inefficient as it fetches all enabled settings and filters in code.
|
||||
// For better performance with many settings, consider normalizing enabled_events
|
||||
// or using JSON functions if the SQLite version supports them well.
|
||||
this.db.all('SELECT * FROM notification_settings WHERE enabled = 1', (err, rows: RawNotificationSetting[]) => {
|
||||
if (err) {
|
||||
return reject(new Error(`Error fetching enabled notification settings: ${err.message}`));
|
||||
}
|
||||
const parsedRows = rows.map(parseRawSetting);
|
||||
const filteredRows = parsedRows.filter(setting => setting.enabled_events.includes(event));
|
||||
resolve(filteredRows);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async create(setting: Omit<NotificationSetting, 'id' | 'created_at' | 'updated_at'>): Promise<number> {
|
||||
const sql = `
|
||||
INSERT INTO notification_settings (channel_type, name, enabled, config, enabled_events)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`;
|
||||
const params = [
|
||||
setting.channel_type,
|
||||
setting.name,
|
||||
setting.enabled ? 1 : 0,
|
||||
JSON.stringify(setting.config || {}),
|
||||
JSON.stringify(setting.enabled_events || [])
|
||||
];
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(sql, params, function (err) { // Use function() to access this.lastID
|
||||
if (err) {
|
||||
return reject(new Error(`Error creating notification setting: ${err.message}`));
|
||||
}
|
||||
resolve(this.lastID);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async update(id: number, setting: Partial<Omit<NotificationSetting, 'id' | 'created_at' | 'updated_at'>>): Promise<boolean> {
|
||||
// Build the SET part of the query dynamically
|
||||
const fields: string[] = [];
|
||||
const params: (string | number | null)[] = [];
|
||||
|
||||
if (setting.channel_type !== undefined) {
|
||||
fields.push('channel_type = ?');
|
||||
params.push(setting.channel_type);
|
||||
}
|
||||
if (setting.name !== undefined) {
|
||||
fields.push('name = ?');
|
||||
params.push(setting.name);
|
||||
}
|
||||
if (setting.enabled !== undefined) {
|
||||
fields.push('enabled = ?');
|
||||
params.push(setting.enabled ? 1 : 0);
|
||||
}
|
||||
if (setting.config !== undefined) {
|
||||
fields.push('config = ?');
|
||||
params.push(JSON.stringify(setting.config || {}));
|
||||
}
|
||||
if (setting.enabled_events !== undefined) {
|
||||
fields.push('enabled_events = ?');
|
||||
params.push(JSON.stringify(setting.enabled_events || []));
|
||||
}
|
||||
|
||||
if (fields.length === 0) {
|
||||
return Promise.resolve(true); // Nothing to update
|
||||
}
|
||||
|
||||
fields.push('updated_at = strftime(\'%s\', \'now\')'); // Always update timestamp
|
||||
|
||||
const sql = `UPDATE notification_settings SET ${fields.join(', ')} WHERE id = ?`;
|
||||
params.push(id);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(sql, params, function (err) { // Use function() to access this.changes
|
||||
if (err) {
|
||||
return reject(new Error(`Error updating notification setting ID ${id}: ${err.message}`));
|
||||
}
|
||||
resolve(this.changes > 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async delete(id: number): Promise<boolean> {
|
||||
const sql = 'DELETE FROM notification_settings WHERE id = ?';
|
||||
return new Promise((resolve, reject) => {
|
||||
this.db.run(sql, [id], function (err) { // Use function() to access this.changes
|
||||
if (err) {
|
||||
return reject(new Error(`Error deleting notification setting ID ${id}: ${err.message}`));
|
||||
}
|
||||
resolve(this.changes > 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user