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:
jubnl
2026-04-05 01:20:33 +02:00
parent 179938e904
commit fc29c5f7d0
46 changed files with 21923 additions and 18383 deletions

View File

@@ -480,3 +480,29 @@ export function createInviteToken(
).run(token, overrides.max_uses ?? 1, overrides.expires_at ?? null, createdBy);
return db.prepare('SELECT * FROM invite_tokens WHERE id = ?').get(result.lastInsertRowid) as TestInviteToken;
}
// ---------------------------------------------------------------------------
// Notification helpers
// ---------------------------------------------------------------------------
/** Upsert a key/value pair into app_settings. */
export function setAppSetting(db: Database.Database, key: string, value: string): void {
db.prepare('INSERT OR REPLACE INTO app_settings (key, value) VALUES (?, ?)').run(key, value);
}
/** Set the active notification channels (e.g. 'email', 'webhook', 'email,webhook', 'none'). */
export function setNotificationChannels(db: Database.Database, channels: string): void {
setAppSetting(db, 'notification_channels', channels);
}
/** Explicitly disable a per-user notification preference for a given event+channel combo. */
export function disableNotificationPref(
db: Database.Database,
userId: number,
eventType: string,
channel: string
): void {
db.prepare(
'INSERT OR REPLACE INTO notification_channel_preferences (user_id, event_type, channel, enabled) VALUES (?, ?, ?, 0)'
).run(userId, eventType, channel);
}

View File

@@ -56,6 +56,7 @@ const RESET_TABLES = [
'vacay_plans',
'atlas_visited_countries',
'atlas_bucket_list',
'notification_channel_preferences',
'notifications',
'audit_log',
'user_settings',