From ea6d2b3ee806d7695edbafec2b34cdeba89cff9c Mon Sep 17 00:00:00 2001 From: Chris Date: Sat, 18 Apr 2026 17:51:27 +0300 Subject: [PATCH] fix(notifications): Add missing test notification endpoint (Issue #1002) (#1047) The frontend test notification feature was calling /api/test-notifications/trigger, but this endpoint didn't exist in the backend, causing a 404 HTML response that failed JSON parsing with the error "JSON.parse: unexpected character at line 1 column 1". Changes: - Added triggerTestNotification service method to handle test notification creation - Added triggerTestNotification controller method to handle the API request - Registered POST /test-notifications/trigger route - Implemented type mapping for all notification types (task_due_soon, task_overdue, defer_until, project_due_soon, project_overdue) - Test notifications respect user notification preferences and send to enabled channels - Returns notification details with sources (in-app, telegram) sent to Fixes #1002 --- backend/modules/notifications/controller.js | 21 +++++ backend/modules/notifications/routes.js | 4 + backend/modules/notifications/service.js | 97 +++++++++++++++++++++ 3 files changed, 122 insertions(+) diff --git a/backend/modules/notifications/controller.js b/backend/modules/notifications/controller.js index dddc5f2..5d5c38f 100644 --- a/backend/modules/notifications/controller.js +++ b/backend/modules/notifications/controller.js @@ -81,6 +81,27 @@ const notificationsController = { next(error); } }, + + async triggerTestNotification(req, res, next) { + try { + const userId = requireUserId(req); + const { type } = req.body; + + if (!type) { + return res + .status(400) + .json({ error: 'Notification type is required' }); + } + + const result = await notificationsService.triggerTestNotification( + userId, + type + ); + res.json(result); + } catch (error) { + next(error); + } + }, }; module.exports = notificationsController; diff --git a/backend/modules/notifications/routes.js b/backend/modules/notifications/routes.js index d1b3570..ec76b37 100644 --- a/backend/modules/notifications/routes.js +++ b/backend/modules/notifications/routes.js @@ -16,5 +16,9 @@ router.post( notificationsController.markAllAsRead ); router.delete('/notifications/:id', notificationsController.dismiss); +router.post( + '/test-notifications/trigger', + notificationsController.triggerTestNotification +); module.exports = router; diff --git a/backend/modules/notifications/service.js b/backend/modules/notifications/service.js index 625f2ca..45ab386 100644 --- a/backend/modules/notifications/service.js +++ b/backend/modules/notifications/service.js @@ -60,6 +60,103 @@ class NotificationsService { await notification.dismiss(); return { message: 'Notification dismissed successfully' }; } + + async triggerTestNotification(userId, testType) { + const { User, Notification } = require('../../models'); + const { + shouldSendTelegramNotification, + } = require('../../utils/notificationPreferences'); + + const user = await User.findByPk(userId, { + attributes: [ + 'id', + 'name', + 'notification_preferences', + 'telegram_bot_token', + 'telegram_chat_id', + ], + }); + + if (!user) { + throw new NotFoundError('User not found'); + } + + const typeMapping = { + task_due_soon: { + backendType: 'task_due_soon', + preferenceKey: 'dueTasks', + title: 'Test: Task Due Soon', + message: + 'This is a test notification for tasks that are due within 24 hours', + data: { test: true, taskName: 'Sample Task' }, + }, + task_overdue: { + backendType: 'task_overdue', + preferenceKey: 'overdueTasks', + title: 'Test: Task Overdue', + message: 'This is a test notification for overdue tasks', + data: { test: true, taskName: 'Sample Overdue Task' }, + }, + defer_until: { + backendType: 'task_due_soon', + preferenceKey: 'deferUntil', + title: 'Test: Task Now Active', + message: + 'This is a test notification for tasks that are now available to work on', + data: { + test: true, + taskName: 'Sample Deferred Task', + reason: 'defer_until_reached', + }, + }, + project_due_soon: { + backendType: 'project_due_soon', + preferenceKey: 'dueProjects', + title: 'Test: Project Due Soon', + message: + 'This is a test notification for projects that are due within 24 hours', + data: { test: true, projectName: 'Sample Project' }, + }, + project_overdue: { + backendType: 'project_overdue', + preferenceKey: 'overdueProjects', + title: 'Test: Project Overdue', + message: 'This is a test notification for overdue projects', + data: { test: true, projectName: 'Sample Overdue Project' }, + }, + }; + + const config = typeMapping[testType]; + if (!config) { + throw new Error(`Invalid test type: ${testType}`); + } + + const sources = []; + if (shouldSendTelegramNotification(user, config.preferenceKey)) { + sources.push('telegram'); + } + + const notification = await Notification.createNotification({ + userId: user.id, + type: config.backendType, + title: config.title, + message: config.message, + data: config.data, + sources, + sentAt: new Date(), + }); + + return { + notification: { + id: notification.id, + type: notification.type, + title: notification.title, + message: notification.message, + sources: notification.sources, + }, + message: 'Test notification created successfully', + }; + } } module.exports = new NotificationsService();