|
|
|
@@ -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
|
|
|
|
|