mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-23 23:31:47 +00:00
feat(notifications): add unified multi-channel notification system
Introduces a fully featured notification system with three delivery channels (in-app, email, webhook), normalized per-user/per-event/ per-channel preferences, admin-scoped notifications, scheduled trip reminders and version update alerts. - New notificationService.send() as the single orchestration entry point - In-app notifications with simple/boolean/navigate types and WebSocket push - Per-user preference matrix with normalized notification_channel_preferences table - Admin notification preferences stored globally in app_settings - Migration 69 normalizes legacy notification_preferences table - Scheduler hooks for daily trip reminders and version checks - DevNotificationsPanel for testing in dev mode - All new tests passing, covering dispatch, preferences, migration, boolean responses, resilience, and full API integration (NSVC, NPREF, INOTIF, MIGR, VNOTIF, NROUTE series) - Previous tests passing
This commit is contained in:
@@ -54,9 +54,9 @@ router.post('/', authenticate, (req: Request, res: Response) => {
|
||||
broadcast(tripId, 'reservation:created', { reservation }, req.headers['x-socket-id'] as string);
|
||||
|
||||
// Notify trip members about new booking
|
||||
import('../services/notifications').then(({ notifyTripMembers }) => {
|
||||
import('../services/notificationService').then(({ send }) => {
|
||||
const tripInfo = db.prepare('SELECT title FROM trips WHERE id = ?').get(tripId) as { title: string } | undefined;
|
||||
notifyTripMembers(Number(tripId), authReq.user.id, 'booking_change', { trip: tripInfo?.title || 'Untitled', actor: authReq.user.email, booking: title, type: type || 'booking' }).catch(() => {});
|
||||
send({ event: 'booking_change', actorId: authReq.user.id, scope: 'trip', targetId: Number(tripId), params: { trip: tripInfo?.title || 'Untitled', actor: authReq.user.email, booking: title, type: type || 'booking', tripId: String(tripId) } }).catch(() => {});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -107,9 +107,9 @@ router.put('/:id', authenticate, (req: Request, res: Response) => {
|
||||
res.json({ reservation });
|
||||
broadcast(tripId, 'reservation:updated', { reservation }, req.headers['x-socket-id'] as string);
|
||||
|
||||
import('../services/notifications').then(({ notifyTripMembers }) => {
|
||||
import('../services/notificationService').then(({ send }) => {
|
||||
const tripInfo = db.prepare('SELECT title FROM trips WHERE id = ?').get(tripId) as { title: string } | undefined;
|
||||
notifyTripMembers(Number(tripId), authReq.user.id, 'booking_change', { trip: tripInfo?.title || 'Untitled', actor: authReq.user.email, booking: title || current.title, type: type || current.type || 'booking' }).catch(() => {});
|
||||
send({ event: 'booking_change', actorId: authReq.user.id, scope: 'trip', targetId: Number(tripId), params: { trip: tripInfo?.title || 'Untitled', actor: authReq.user.email, booking: title || current.title, type: type || current.type || 'booking', tripId: String(tripId) } }).catch(() => {});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -133,9 +133,9 @@ router.delete('/:id', authenticate, (req: Request, res: Response) => {
|
||||
res.json({ success: true });
|
||||
broadcast(tripId, 'reservation:deleted', { reservationId: Number(id) }, req.headers['x-socket-id'] as string);
|
||||
|
||||
import('../services/notifications').then(({ notifyTripMembers }) => {
|
||||
import('../services/notificationService').then(({ send }) => {
|
||||
const tripInfo = db.prepare('SELECT title FROM trips WHERE id = ?').get(tripId) as { title: string } | undefined;
|
||||
notifyTripMembers(Number(tripId), authReq.user.id, 'booking_change', { trip: tripInfo?.title || 'Untitled', actor: authReq.user.email, booking: reservation.title, type: reservation.type || 'booking' }).catch(() => {});
|
||||
send({ event: 'booking_change', actorId: authReq.user.id, scope: 'trip', targetId: Number(tripId), params: { trip: tripInfo?.title || 'Untitled', actor: authReq.user.email, booking: reservation.title, type: reservation.type || 'booking', tripId: String(tripId) } }).catch(() => {});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user