This commit is contained in:
Baobhan Sith
2025-04-26 20:04:01 +08:00
parent e025c4f7a1
commit 38248cfc1d
14 changed files with 149 additions and 115 deletions
-2
View File
@@ -26,8 +26,6 @@
"CONNECTION_UPDATED": "Connection Updated",
"CONNECTION_DELETED": "Connection Deleted",
"CONNECTION_TESTED": "Connection Tested",
"CONNECTIONS_IMPORTED": "Connections Imported",
"CONNECTIONS_EXPORTED": "Connections Exported",
"PROXY_CREATED": "Proxy Created",
"PROXY_UPDATED": "Proxy Updated",
"PROXY_DELETED": "Proxy Deleted",
-2
View File
@@ -26,8 +26,6 @@
"CONNECTION_UPDATED": "接続を更新しました",
"CONNECTION_DELETED": "接続を削除しました",
"CONNECTION_TESTED": "接続をテストしました",
"CONNECTIONS_IMPORTED": "接続をインポートしました",
"CONNECTIONS_EXPORTED": "接続をエクスポートしました",
"PROXY_CREATED": "プロキシを作成しました",
"PROXY_UPDATED": "プロキシを更新しました",
"PROXY_DELETED": "プロキシを削除しました",
-2
View File
@@ -12,8 +12,6 @@
"CONNECTION_UPDATED": "连接已更新",
"CONNECTION_DELETED": "连接已删除",
"CONNECTION_TESTED": "连接已测试",
"CONNECTIONS_IMPORTED": "连接已导入",
"CONNECTIONS_EXPORTED": "连接已导出",
"PROXY_CREATED": "代理已创建",
"PROXY_UPDATED": "代理已更新",
"PROXY_DELETED": "代理已删除",
@@ -1,8 +1,9 @@
import { Request, Response } from 'express';
import { NotificationSettingsRepository } from '../repositories/notification.repository'; // Use repository
import { NotificationSetting, NotificationChannelType, NotificationChannelConfig, WebhookConfig, EmailConfig, TelegramConfig, NotificationEvent } from '../types/notification.types';
import { AuditLogService } from '../services/audit.service'; // Keep for now if other parts use it
// import { AuditLogService } from '../services/audit.service'; // Keep for now if other parts use it - Removed as eventService is used
import { AppEventType, default as eventService } from '../services/event.service'; // Import event service
import i18next from '../i18n'; // Import the i18next instance
// Remove sender imports as they are no longer called directly for testing
// import telegramSenderService from '../services/senders/telegram.sender.service';
@@ -12,7 +13,7 @@ import { AppEventType, default as eventService } from '../services/event.service
// Removed escapeTelegramMarkdownV2 helper function
const auditLogService = new AuditLogService(); // Keep for now if other parts use it, but prefer eventService
// const auditLogService = new AuditLogService(); // Removed as eventService is used
export class NotificationController {
private repository: NotificationSettingsRepository; // Use repository
@@ -27,18 +28,20 @@ export class NotificationController {
const settings = await this.repository.getAll(); // Use repository
res.status(200).json(settings);
} catch (error: any) {
res.status(500).json({ message: '获取通知设置失败', error: error.message });
}
};
// Use i18next.t for i18n
res.status(500).json({ message: i18next.t('notificationController.errorFetchSettings'), error: error.message });
}
};
// POST /api/v1/notifications
create = async (req: Request, res: Response): Promise<void> => {
const settingData: Omit<NotificationSetting, 'id' | 'created_at' | 'updated_at'> = req.body;
if (!settingData.channel_type || !settingData.name || !settingData.config) {
res.status(400).json({ message: '缺少必要的通知设置字段 (channel_type, name, config)' });
return;
}
// Use i18next.t for i18n
res.status(400).json({ message: i18next.t('notificationController.errorMissingFields') });
return;
}
try {
const newSettingId = await this.repository.create(settingData); // Use repository
@@ -52,9 +55,10 @@ export class NotificationController {
}
res.status(201).json(newSetting);
} catch (error: any) {
res.status(500).json({ message: '创建通知设置失败', error: error.message });
}
};
// Use i18next.t for i18n
res.status(500).json({ message: i18next.t('notificationController.errorCreateSetting'), error: error.message });
}
};
// PUT /api/v1/notifications/:id
update = async (req: Request, res: Response): Promise<void> => {
@@ -62,13 +66,15 @@ export class NotificationController {
const settingData: Partial<Omit<NotificationSetting, 'id' | 'created_at' | 'updated_at'>> = req.body;
if (isNaN(id)) {
res.status(400).json({ message: '无效的通知设置 ID' });
return;
}
// Use i18next.t for i18n
res.status(400).json({ message: i18next.t('notificationController.errorInvalidId') });
return;
}
if (Object.keys(settingData).length === 0) {
res.status(400).json({ message: '没有提供要更新的数据' });
return;
}
// Use i18next.t for i18n
res.status(400).json({ message: i18next.t('notificationController.errorNoUpdateData') });
return;
}
try {
const success = await this.repository.update(id, settingData); // Use repository
@@ -81,28 +87,32 @@ export class NotificationController {
});
res.status(200).json(updatedSetting);
} else {
res.status(404).json({ message: `未找到 ID 为 ${id} 的通知设置` });
}
} catch (error: any) {
res.status(500).json({ message: '更新通知设置失败', error: error.message });
}
};
// Use i18next.t for i18n with interpolation
res.status(404).json({ message: i18next.t('notificationController.errorNotFound', { id }) });
}
} catch (error: any) {
// Use i18next.t for i18n
res.status(500).json({ message: i18next.t('notificationController.errorUpdateSetting'), error: error.message });
}
};
// DELETE /api/v1/notifications/:id
delete = async (req: Request, res: Response): Promise<void> => {
const id = parseInt(req.params.id, 10);
if (isNaN(id)) {
res.status(400).json({ message: '无效的通知设置 ID' });
return;
}
// Use i18next.t for i18n
res.status(400).json({ message: i18next.t('notificationController.errorInvalidId') });
return;
}
try {
const settingToDelete = await this.repository.getById(id); // Get details before deleting for audit log
if (!settingToDelete) {
res.status(404).json({ message: `未找到 ID 为 ${id} 的通知设置` });
return;
}
// Use i18next.t for i18n with interpolation
res.status(404).json({ message: i18next.t('notificationController.errorNotFound', { id }) });
return;
}
const success = await this.repository.delete(id); // Use repository
if (success) {
// 记录审计日志 (Use event service)
@@ -112,13 +122,15 @@ export class NotificationController {
});
res.status(204).send(); // No Content
} else {
// Should not happen if getById succeeded, but handle defensively
res.status(404).json({ message: `删除 ID 为 ${id} 的通知设置失败,可能已被删除` });
}
} catch (error: any) {
res.status(500).json({ message: '删除通知设置失败', error: error.message });
}
};
// Should not happen if getById succeeded, but handle defensively
// Use i18next.t for i18n with interpolation
res.status(404).json({ message: i18next.t('notificationController.errorDeleteNotFound', { id }) });
}
} catch (error: any) {
// Use i18next.t for i18n
res.status(500).json({ message: i18next.t('notificationController.errorDeleteSetting'), error: error.message });
}
};
// --- Refactored Test Endpoints ---
@@ -130,68 +142,78 @@ export class NotificationController {
const id = parseInt(req.params.id, 10);
if (isNaN(id)) {
res.status(400).json({ message: '无效的通知设置 ID' });
return;
}
// Use i18next.t for i18n
res.status(400).json({ message: i18next.t('notificationController.errorInvalidId') });
return;
}
try {
const settingToTest = await this.repository.getById(id);
if (!settingToTest) {
res.status(404).json({ message: `未找到 ID 为 ${id} 的通知设置` });
return;
}
// Use i18next.t for i18n with interpolation
res.status(404).json({ message: i18next.t('notificationController.errorNotFound', { id }) });
return;
}
// Trigger the standard test event, passing the config to be used by the processor
eventService.emitEvent(AppEventType.TestNotification, {
userId: (req.session as any).userId, // Optional: associate test with user
details: {
message: `为设置 ID ${id} (${settingToTest.name}) 触发的测试`,
testTargetConfig: settingToTest.config, // Pass the config to use
testTargetChannelType: settingToTest.channel_type // Pass the channel type
details: {
// Use i18next.t for i18n with interpolation
message: i18next.t('notificationController.testMessageSaved', { id, name: settingToTest.name }),
testTargetConfig: settingToTest.config, // Pass the config to use
testTargetChannelType: settingToTest.channel_type // Pass the channel type
}
});
// Respond immediately confirming the event was triggered
res.status(200).json({ message: '测试通知事件已触发。请检查对应渠道的接收情况。' });
// Respond immediately confirming the event was triggered
// Use i18next.t for i18n
res.status(200).json({ message: i18next.t('notificationController.testEventTriggered') });
} catch (error: any) {
console.error(`[NotificationController] Error triggering test for setting ${id}:`, error);
res.status(500).json({ message: '触发测试通知时发生内部错误', error: error.message });
}
};
} catch (error: any) {
console.error(`[NotificationController] Error triggering test for setting ${id}:`, error);
// Use i18next.t for i18n
res.status(500).json({ message: i18next.t('notificationController.errorTriggerTest'), error: error.message });
}
};
// POST /api/v1/notifications/test-unsaved
// Tests configuration data provided in the request body by triggering a test event
testUnsavedSetting = async (req: Request, res: Response): Promise<void> => {
const { channel_type, config } = req.body as { channel_type: NotificationChannelType, config: NotificationChannelConfig };
if (!channel_type || !config) {
res.status(400).json({ message: '缺少必要的测试信息 (channel_type, config)' });
return;
}
if (!channel_type || !config) {
// Use i18next.t for i18n
res.status(400).json({ message: i18next.t('notificationController.errorMissingTestInfo') });
return;
}
if (!['webhook', 'email', 'telegram'].includes(channel_type)) {
res.status(400).json({ message: '无效的渠道类型' });
return;
}
if (!['webhook', 'email', 'telegram'].includes(channel_type)) {
// Use i18next.t for i18n
res.status(400).json({ message: i18next.t('notificationController.errorInvalidChannelType') });
return;
}
try {
// Trigger the standard test event, passing the unsaved config to be used by the processor
eventService.emitEvent(AppEventType.TestNotification, {
userId: (req.session as any).userId,
details: {
message: `为未保存的 ${channel_type} 配置触发的测试`,
testTargetConfig: config, // Pass the unsaved config to use
testTargetChannelType: channel_type // Pass the channel type
details: {
// Use i18next.t for i18n with interpolation
message: i18next.t('notificationController.testMessageUnsaved', { channelType: channel_type }),
testTargetConfig: config, // Pass the unsaved config to use
testTargetChannelType: channel_type // Pass the channel type
}
});
// Respond immediately confirming the event was triggered
res.status(200).json({ message: '测试通知事件已触发。请检查对应渠道的接收情况。' });
// Respond immediately confirming the event was triggered
// Use i18next.t for i18n
res.status(200).json({ message: i18next.t('notificationController.testEventTriggered') });
} catch (error: any) {
console.error(`[NotificationController] Error triggering test for unsaved ${channel_type}:`, error);
res.status(500).json({ message: '触发测试通知时发生内部错误', error: error.message });
}
};
} catch (error: any) {
console.error(`[NotificationController] Error triggering test for unsaved ${channel_type}:`, error);
// Use i18next.t for i18n
res.status(500).json({ message: i18next.t('notificationController.errorTriggerTest'), error: error.message });
}
};
} // End of class NotificationController
@@ -16,8 +16,6 @@ export enum AppEventType {
ConnectionUpdated = 'CONNECTION_UPDATED',
ConnectionDeleted = 'CONNECTION_DELETED',
ConnectionTested = 'CONNECTION_TESTED',
ConnectionsImported = 'CONNECTIONS_IMPORTED',
ConnectionsExported = 'CONNECTIONS_EXPORTED',
ProxyCreated = 'PROXY_CREATED',
ProxyUpdated = 'PROXY_UPDATED',
ProxyDeleted = 'PROXY_DELETED',
@@ -15,8 +15,6 @@ export type AuditLogActionType =
| 'CONNECTION_UPDATED'
| 'CONNECTION_DELETED'
| 'CONNECTION_TESTED'
| 'CONNECTIONS_IMPORTED'
| 'CONNECTIONS_EXPORTED'
// Proxies
| 'PROXY_CREATED'
@@ -39,9 +37,6 @@ export type AuditLogActionType =
| 'NOTIFICATION_SETTING_UPDATED'
| 'NOTIFICATION_SETTING_DELETED'
// API Keys (Removed from audit log types)
// | 'API_KEY_CREATED'
// | 'API_KEY_DELETED'
// SFTP (Consider logging specific actions if needed, e.g., UPLOAD, DOWNLOAD, DELETE_FILE)
| 'SFTP_ACTION' // Generic SFTP action for now
@@ -5,7 +5,6 @@ export type NotificationEvent =
| 'LOGIN_SUCCESS' | 'LOGIN_FAILURE' | 'LOGOUT' | 'PASSWORD_CHANGED'
| '2FA_ENABLED' | '2FA_DISABLED' | 'PASSKEY_REGISTERED' | 'PASSKEY_DELETED'
| 'CONNECTION_CREATED' | 'CONNECTION_UPDATED' | 'CONNECTION_DELETED' | 'CONNECTION_TESTED'
| 'CONNECTIONS_IMPORTED' | 'CONNECTIONS_EXPORTED'
| 'PROXY_CREATED' | 'PROXY_UPDATED' | 'PROXY_DELETED'
| 'TAG_CREATED' | 'TAG_UPDATED' | 'TAG_DELETED'
| 'SETTINGS_UPDATED' | 'IP_WHITELIST_UPDATED' | 'IP_BLOCKED'