mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-22 06:41:46 +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:
+1674
-1607
File diff suppressed because it is too large
Load Diff
+1669
-1602
File diff suppressed because it is too large
Load Diff
+1674
-1607
File diff suppressed because it is too large
Load Diff
+1671
-1604
File diff suppressed because it is too large
Load Diff
@@ -163,23 +163,44 @@ const en: Record<string, string | { name: string; category: string }[]> = {
|
||||
'settings.notifyCollabMessage': 'Chat messages (Collab)',
|
||||
'settings.notifyPackingTagged': 'Packing list: assignments',
|
||||
'settings.notifyWebhook': 'Webhook notifications',
|
||||
'settings.notifyVersionAvailable': 'New version available',
|
||||
'settings.notificationPreferences.email': 'Email',
|
||||
'settings.notificationPreferences.webhook': 'Webhook',
|
||||
'settings.notificationPreferences.inapp': 'In-App',
|
||||
'settings.notificationPreferences.noChannels': 'No notification channels are configured. Ask an admin to set up email or webhook notifications.',
|
||||
'settings.webhookUrl.label': 'Webhook URL',
|
||||
'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
|
||||
'settings.webhookUrl.hint': 'Enter your Discord, Slack, or custom webhook URL to receive notifications.',
|
||||
'settings.webhookUrl.save': 'Save',
|
||||
'settings.webhookUrl.saved': 'Webhook URL saved',
|
||||
'settings.webhookUrl.test': 'Test',
|
||||
'settings.webhookUrl.testSuccess': 'Test webhook sent successfully',
|
||||
'settings.webhookUrl.testFailed': 'Test webhook failed',
|
||||
'admin.notifications.title': 'Notifications',
|
||||
'admin.notifications.hint': 'Choose one notification channel. Only one can be active at a time.',
|
||||
'admin.notifications.none': 'Disabled',
|
||||
'admin.notifications.email': 'Email (SMTP)',
|
||||
'admin.notifications.webhook': 'Webhook',
|
||||
'admin.notifications.events': 'Notification Events',
|
||||
'admin.notifications.eventsHint': 'Choose which events trigger notifications for all users.',
|
||||
'admin.notifications.configureFirst': 'Configure the SMTP or webhook settings below first, then enable events.',
|
||||
'admin.notifications.save': 'Save notification settings',
|
||||
'admin.notifications.saved': 'Notification settings saved',
|
||||
'admin.notifications.testWebhook': 'Send test webhook',
|
||||
'admin.notifications.testWebhookSuccess': 'Test webhook sent successfully',
|
||||
'admin.notifications.testWebhookFailed': 'Test webhook failed',
|
||||
'admin.notifications.emailPanel.title': 'Email (SMTP)',
|
||||
'admin.notifications.webhookPanel.title': 'Webhook',
|
||||
'admin.notifications.inappPanel.title': 'In-App',
|
||||
'admin.notifications.inappPanel.hint': 'In-app notifications are always active and cannot be disabled globally.',
|
||||
'admin.notifications.adminWebhookPanel.title': 'Admin Webhook',
|
||||
'admin.notifications.adminWebhookPanel.hint': 'This webhook is used exclusively for admin notifications (e.g. version alerts). It is separate from per-user webhooks and always fires when set.',
|
||||
'admin.notifications.adminWebhookPanel.saved': 'Admin webhook URL saved',
|
||||
'admin.notifications.adminWebhookPanel.testSuccess': 'Test webhook sent successfully',
|
||||
'admin.notifications.adminWebhookPanel.testFailed': 'Test webhook failed',
|
||||
'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Admin webhook always fires when a URL is configured',
|
||||
'admin.notifications.adminNotificationsHint': 'Configure which channels deliver admin-only notifications (e.g. version alerts).',
|
||||
'admin.smtp.title': 'Email & Notifications',
|
||||
'admin.smtp.hint': 'SMTP configuration for sending email notifications.',
|
||||
'admin.smtp.testButton': 'Send test email',
|
||||
'admin.webhook.hint': 'Send notifications to an external webhook (Discord, Slack, etc.).',
|
||||
'admin.webhook.hint': 'Allow users to configure their own webhook URLs for notifications (Discord, Slack, etc.).',
|
||||
'admin.smtp.testSuccess': 'Test email sent successfully',
|
||||
'admin.smtp.testFailed': 'Test email failed',
|
||||
'settings.notificationsDisabled': 'Notifications are not configured. Ask an admin to enable email or webhook notifications.',
|
||||
@@ -383,6 +404,9 @@ const en: Record<string, string | { name: string; category: string }[]> = {
|
||||
'admin.tabs.users': 'Users',
|
||||
'admin.tabs.categories': 'Categories',
|
||||
'admin.tabs.backup': 'Backup',
|
||||
'admin.tabs.notifications': 'Notifications',
|
||||
'admin.tabs.notificationChannels': 'Notification Channels',
|
||||
'admin.tabs.adminNotifications': 'Admin Notifications',
|
||||
'admin.tabs.audit': 'Audit log',
|
||||
'admin.stats.users': 'Users',
|
||||
'admin.stats.trips': 'Trips',
|
||||
@@ -1551,6 +1575,9 @@ const en: Record<string, string | { name: string; category: string }[]> = {
|
||||
'notifications.system': 'System',
|
||||
|
||||
// Notification test keys (dev only)
|
||||
'notifications.versionAvailable.title': 'Update Available',
|
||||
'notifications.versionAvailable.text': 'TREK {version} is now available.',
|
||||
'notifications.versionAvailable.button': 'View Details',
|
||||
'notifications.test.title': 'Test notification from {actor}',
|
||||
'notifications.test.text': 'This is a simple test notification.',
|
||||
'notifications.test.booleanTitle': '{actor} asks for your approval',
|
||||
@@ -1598,6 +1625,43 @@ const en: Record<string, string | { name: string; category: string }[]> = {
|
||||
'todo.detail.priority': 'Priority',
|
||||
'todo.detail.noPriority': 'None',
|
||||
'todo.detail.create': 'Create task',
|
||||
|
||||
// Notifications — dev test events
|
||||
'notif.test.title': '[Test] Notification',
|
||||
'notif.test.simple.text': 'This is a simple test notification.',
|
||||
'notif.test.boolean.text': 'Do you accept this test notification?',
|
||||
'notif.test.navigate.text': 'Click below to navigate to the dashboard.',
|
||||
|
||||
// Notifications
|
||||
'notif.trip_invite.title': 'Trip Invitation',
|
||||
'notif.trip_invite.text': '{actor} invited you to {trip}',
|
||||
'notif.booking_change.title': 'Booking Updated',
|
||||
'notif.booking_change.text': '{actor} updated a booking in {trip}',
|
||||
'notif.trip_reminder.title': 'Trip Reminder',
|
||||
'notif.trip_reminder.text': 'Your trip {trip} is coming up soon!',
|
||||
'notif.vacay_invite.title': 'Vacay Fusion Invite',
|
||||
'notif.vacay_invite.text': '{actor} invited you to fuse vacation plans',
|
||||
'notif.photos_shared.title': 'Photos Shared',
|
||||
'notif.photos_shared.text': '{actor} shared {count} photo(s) in {trip}',
|
||||
'notif.collab_message.title': 'New Message',
|
||||
'notif.collab_message.text': '{actor} sent a message in {trip}',
|
||||
'notif.packing_tagged.title': 'Packing Assignment',
|
||||
'notif.packing_tagged.text': '{actor} assigned you to {category} in {trip}',
|
||||
'notif.version_available.title': 'New Version Available',
|
||||
'notif.version_available.text': 'TREK {version} is now available',
|
||||
'notif.action.view_trip': 'View Trip',
|
||||
'notif.action.view_collab': 'View Messages',
|
||||
'notif.action.view_packing': 'View Packing',
|
||||
'notif.action.view_photos': 'View Photos',
|
||||
'notif.action.view_vacay': 'View Vacay',
|
||||
'notif.action.view_admin': 'Go to Admin',
|
||||
'notif.action.view': 'View',
|
||||
'notif.action.accept': 'Accept',
|
||||
'notif.action.decline': 'Decline',
|
||||
'notif.generic.title': 'Notification',
|
||||
'notif.generic.text': 'You have a new notification',
|
||||
'notif.dev.unknown_event.title': '[DEV] Unknown Event',
|
||||
'notif.dev.unknown_event.text': 'Event type "{event}" is not registered in EVENT_NOTIFICATION_CONFIG',
|
||||
}
|
||||
|
||||
export default en
|
||||
|
||||
+1676
-1609
File diff suppressed because it is too large
Load Diff
+1670
-1603
File diff suppressed because it is too large
Load Diff
+1671
-1604
File diff suppressed because it is too large
Load Diff
+1671
-1604
File diff suppressed because it is too large
Load Diff
+1670
-1603
File diff suppressed because it is too large
Load Diff
@@ -149,6 +149,7 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
|
||||
'settings.notifyCollabMessage': 'Wiadomości czatu (Collab)',
|
||||
'settings.notifyPackingTagged': 'Lista pakowania: przypisania',
|
||||
'settings.notifyWebhook': 'Powiadomienia Webhook',
|
||||
'settings.notifyVersionAvailable': 'New version available',
|
||||
'admin.smtp.title': 'E-maile i powiadomienia',
|
||||
'admin.smtp.hint': 'Konfiguracja SMTP dla powiadomień e-mail. Opcjonalnie: URL Webhooka dla Discorda, Slacka, itp.',
|
||||
'admin.smtp.testButton': 'Wyślij testowego e-maila',
|
||||
@@ -349,6 +350,9 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
|
||||
'admin.tabs.users': 'Użytkownicy',
|
||||
'admin.tabs.categories': 'Kategorie',
|
||||
'admin.tabs.backup': 'Backupy',
|
||||
'admin.tabs.notifications': 'Notifications',
|
||||
'admin.tabs.notificationChannels': 'Kanały powiadomień',
|
||||
'admin.tabs.adminNotifications': 'Powiadomienia admina',
|
||||
'admin.tabs.audit': 'Aktywność',
|
||||
'admin.stats.users': 'Użytkownicy',
|
||||
'admin.stats.trips': 'Podróże',
|
||||
@@ -1438,8 +1442,31 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
|
||||
'admin.notifications.testWebhook': 'Wyślij testowy webhook',
|
||||
'admin.notifications.testWebhookSuccess': 'Testowy webhook wysłany pomyślnie',
|
||||
'admin.notifications.testWebhookFailed': 'Testowy webhook nie powiódł się',
|
||||
'admin.webhook.hint': 'Wysyłaj powiadomienia do zewnętrznego webhooka.',
|
||||
'admin.notifications.emailPanel.title': 'Email (SMTP)',
|
||||
'admin.notifications.webhookPanel.title': 'Webhook',
|
||||
'admin.notifications.inappPanel.title': 'In-App',
|
||||
'admin.notifications.inappPanel.hint': 'In-app notifications are always active and cannot be disabled globally.',
|
||||
'admin.notifications.adminWebhookPanel.title': 'Webhook admina',
|
||||
'admin.notifications.adminWebhookPanel.hint': 'Ten webhook służy wyłącznie do powiadomień admina (np. alertów o nowych wersjach). Jest niezależny od webhooków użytkowników i wysyła automatycznie, gdy URL jest skonfigurowany.',
|
||||
'admin.notifications.adminWebhookPanel.saved': 'URL webhooka admina zapisany',
|
||||
'admin.notifications.adminWebhookPanel.testSuccess': 'Testowy webhook wysłany pomyślnie',
|
||||
'admin.notifications.adminWebhookPanel.testFailed': 'Wysyłanie testowego webhooka nie powiodło się',
|
||||
'admin.notifications.adminWebhookPanel.alwaysOnHint': 'Webhook admina wysyła automatycznie, gdy URL jest skonfigurowany',
|
||||
'admin.notifications.adminNotificationsHint': 'Skonfiguruj, które kanały dostarczają powiadomienia admina (np. alerty o wersjach). Webhook wysyła automatycznie, gdy ustawiony jest URL webhooka admina.',
|
||||
'admin.webhook.hint': 'Pozwól użytkownikom konfigurować własne adresy URL webhooka dla powiadomień (Discord, Slack itp.).',
|
||||
'settings.notificationsDisabled': 'Powiadomienia nie są skonfigurowane.',
|
||||
'settings.notificationPreferences.noChannels': 'No notification channels are configured. Ask an admin to set up email or webhook notifications.',
|
||||
'settings.webhookUrl.label': 'URL webhooka',
|
||||
'settings.webhookUrl.placeholder': 'https://discord.com/api/webhooks/...',
|
||||
'settings.webhookUrl.hint': 'Wprowadź adres URL webhooka Discord, Slack lub własnego, aby otrzymywać powiadomienia.',
|
||||
'settings.webhookUrl.save': 'Zapisz',
|
||||
'settings.webhookUrl.saved': 'URL webhooka zapisany',
|
||||
'settings.webhookUrl.test': 'Test',
|
||||
'settings.webhookUrl.testSuccess': 'Testowy webhook wysłany pomyślnie',
|
||||
'settings.webhookUrl.testFailed': 'Wysyłanie testowego webhooka nie powiodło się',
|
||||
'settings.notificationPreferences.inapp': 'In-App',
|
||||
'settings.notificationPreferences.webhook': 'Webhook',
|
||||
'settings.notificationPreferences.email': 'Email',
|
||||
'settings.notificationsActive': 'Aktywny kanał',
|
||||
'settings.notificationsManagedByAdmin': 'Zdarzenia konfigurowane przez administratora.',
|
||||
'settings.mustChangePassword': 'Musisz zmienić hasło przed kontynuowaniem.',
|
||||
@@ -1543,6 +1570,9 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
|
||||
'notifications.markUnread': 'Oznacz jako nieprzeczytane',
|
||||
'notifications.delete': 'Usuń',
|
||||
'notifications.system': 'System',
|
||||
'notifications.versionAvailable.title': 'Update Available',
|
||||
'notifications.versionAvailable.text': 'TREK {version} is now available.',
|
||||
'notifications.versionAvailable.button': 'View Details',
|
||||
'notifications.test.title': 'Testowe powiadomienie od {actor}',
|
||||
'notifications.test.text': 'To jest powiadomienie testowe.',
|
||||
'notifications.test.booleanTitle': '{actor} prosi o akceptację',
|
||||
@@ -1590,6 +1620,43 @@ const pl: Record<string, string | { name: string; category: string }[]> = {
|
||||
'todo.detail.priority': 'Priority',
|
||||
'todo.detail.noPriority': 'None',
|
||||
'todo.sortByPrio': 'Priority',
|
||||
|
||||
// Notifications — dev test events
|
||||
'notif.test.title': '[Test] Notification',
|
||||
'notif.test.simple.text': 'This is a simple test notification.',
|
||||
'notif.test.boolean.text': 'Do you accept this test notification?',
|
||||
'notif.test.navigate.text': 'Click below to navigate to the dashboard.',
|
||||
|
||||
// Notifications
|
||||
'notif.trip_invite.title': 'Zaproszenie do podróży',
|
||||
'notif.trip_invite.text': '{actor} zaprosił Cię do {trip}',
|
||||
'notif.booking_change.title': 'Rezerwacja zaktualizowana',
|
||||
'notif.booking_change.text': '{actor} zaktualizował rezerwację w {trip}',
|
||||
'notif.trip_reminder.title': 'Przypomnienie o podróży',
|
||||
'notif.trip_reminder.text': 'Twoja podróż {trip} zbliża się!',
|
||||
'notif.vacay_invite.title': 'Zaproszenie Vacay Fusion',
|
||||
'notif.vacay_invite.text': '{actor} zaprosił Cię do połączenia planów urlopowych',
|
||||
'notif.photos_shared.title': 'Zdjęcia udostępnione',
|
||||
'notif.photos_shared.text': '{actor} udostępnił {count} zdjęcie/zdjęcia w {trip}',
|
||||
'notif.collab_message.title': 'Nowa wiadomość',
|
||||
'notif.collab_message.text': '{actor} wysłał wiadomość w {trip}',
|
||||
'notif.packing_tagged.title': 'Zadanie pakowania',
|
||||
'notif.packing_tagged.text': '{actor} przypisał Cię do {category} w {trip}',
|
||||
'notif.version_available.title': 'Nowa wersja dostępna',
|
||||
'notif.version_available.text': 'TREK {version} jest teraz dostępny',
|
||||
'notif.action.view_trip': 'Zobacz podróż',
|
||||
'notif.action.view_collab': 'Zobacz wiadomości',
|
||||
'notif.action.view_packing': 'Zobacz pakowanie',
|
||||
'notif.action.view_photos': 'Zobacz zdjęcia',
|
||||
'notif.action.view_vacay': 'Zobacz Vacay',
|
||||
'notif.action.view_admin': 'Przejdź do admina',
|
||||
'notif.action.view': 'Zobacz',
|
||||
'notif.action.accept': 'Akceptuj',
|
||||
'notif.action.decline': 'Odrzuć',
|
||||
'notif.generic.title': 'Powiadomienie',
|
||||
'notif.generic.text': 'Masz nowe powiadomienie',
|
||||
'notif.dev.unknown_event.title': '[DEV] Unknown Event',
|
||||
'notif.dev.unknown_event.text': 'Event type "{event}" is not registered in EVENT_NOTIFICATION_CONFIG',
|
||||
}
|
||||
|
||||
export default pl
|
||||
|
||||
+1670
-1603
File diff suppressed because it is too large
Load Diff
+1670
-1603
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user