import { Injectable, Logger } from '@nestjs/common'; import { NotificationsService } from './notifications.service'; import { NotificationChannel } from '../enums/notification-channel.enum'; import { NotificationScope } from '../enums/notification-scope.enum'; /** * User notification preferences * Determines which channels are enabled for a user */ export interface NotificationPreferences { /** Whether push notifications are enabled */ isPushEnabled: boolean; /** Whether email notifications are enabled */ isEmailEnabled: boolean; /** Whether SMS notifications are enabled */ isSmsEnabled: boolean; } /** * Payload for sending a notification */ export interface NotificationPayload { /** ID of the user to notify */ userId: string; /** Notification title */ title: string; /** Notification message body */ message: string; /** Category/type of notification */ scope: NotificationScope; /** * User's notification preferences * If not provided, defaults to push-only */ preferences?: NotificationPreferences; /** Additional data to attach to the notification */ data?: Record; } /** * NotificationFactory * * Central service for sending notifications. * Independent service with no external dependencies (microservice-ready). * * Handles: * - Channel routing based on provided preferences * - Parallel notification delivery * - Error handling * * Note: Caller is responsible for providing user preferences. * This keeps the factory independent and testable. * * Usage: * await notificationFactory.send({ * userId: 'user-123', * title: 'Transaction Alert', * message: 'You spent $50.00', * scope: NotificationScope.CHILD_SPENDING, * preferences: { * isPushEnabled: true, * isEmailEnabled: false, * isSmsEnabled: false, * }, * }); */ @Injectable() export class NotificationFactory { private readonly logger = new Logger(NotificationFactory.name); constructor( private readonly notificationsService: NotificationsService, ) {} /** * Send a notification to a user * Routes to enabled channels based on provided preferences * * @param payload - Notification payload including preferences */ async send(payload: NotificationPayload): Promise { try { this.logger.log(`Sending notification to user ${payload.userId} - ${payload.title}`); const preferences = payload.preferences || { isPushEnabled: true, isEmailEnabled: false, isSmsEnabled: false, }; const promises: Promise[] = []; if (preferences.isPushEnabled) { this.logger.debug(`Routing to PUSH channel for user ${payload.userId}`); promises.push( this.sendToChannel(payload, NotificationChannel.PUSH) ); } await Promise.all(promises); this.logger.log( `Notification sent to user ${payload.userId} via ${promises.length} channel(s)` ); } catch (error: any) { this.logger.error( `Failed to send notification to user ${payload.userId}: ${error?.message || 'Unknown error'}`, error?.stack ); // Don't throw - prevents breaking the main business flow // Notification failures should not break transactions, etc. } } /** * Send notification via a specific channel * Creates the notification record and publishes it for delivery */ private async sendToChannel( payload: NotificationPayload, channel: NotificationChannel ): Promise { await this.notificationsService.createNotification({ userId: payload.userId, title: payload.title, message: payload.message, scope: payload.scope, channel, data: payload.data, }); } }