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
This commit is contained in:
Chris 2026-04-18 17:51:27 +03:00 committed by GitHub
parent dcb711c515
commit ea6d2b3ee8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 122 additions and 0 deletions

View file

@ -81,6 +81,27 @@ const notificationsController = {
next(error); 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; module.exports = notificationsController;

View file

@ -16,5 +16,9 @@ router.post(
notificationsController.markAllAsRead notificationsController.markAllAsRead
); );
router.delete('/notifications/:id', notificationsController.dismiss); router.delete('/notifications/:id', notificationsController.dismiss);
router.post(
'/test-notifications/trigger',
notificationsController.triggerTestNotification
);
module.exports = router; module.exports = router;

View file

@ -60,6 +60,103 @@ class NotificationsService {
await notification.dismiss(); await notification.dismiss();
return { message: 'Notification dismissed successfully' }; 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(); module.exports = new NotificationsService();