mirror of
https://github.com/HamzaSha1/zod-backend.git
synced 2026-03-10 20:51:44 +00:00
feat: add notification event handling and notification factory service
- Introduce constants for notification event names - Implement interfaces for transaction created events - Create a transaction notification listener to handle transaction notifications - Develop a notification factory service for sending notifications based on user preferences - Add a migration to include a data column in the notifications table
This commit is contained in:
@ -0,0 +1,160 @@
|
||||
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<string, any>;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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<void> {
|
||||
try {
|
||||
this.logger.log(`Sending notification to user ${payload.userId} - ${payload.title}`);
|
||||
|
||||
// Use provided preferences or default to push-only
|
||||
const preferences = payload.preferences || {
|
||||
isPushEnabled: true,
|
||||
isEmailEnabled: false,
|
||||
isSmsEnabled: false,
|
||||
};
|
||||
|
||||
const promises: Promise<any>[] = [];
|
||||
|
||||
// Route to enabled channels based on preferences
|
||||
// Currently only PUSH is implemented (extensible for EMAIL, SMS later)
|
||||
if (preferences.isPushEnabled) {
|
||||
this.logger.debug(`Routing to PUSH channel for user ${payload.userId}`);
|
||||
promises.push(
|
||||
this.sendToChannel(payload, NotificationChannel.PUSH)
|
||||
);
|
||||
}
|
||||
|
||||
// Future: Add EMAIL channel
|
||||
// if (preferences.isEmailEnabled) {
|
||||
// this.logger.debug(`Routing to EMAIL channel for user ${payload.userId}`);
|
||||
// promises.push(
|
||||
// this.sendToChannel(payload, NotificationChannel.EMAIL)
|
||||
// );
|
||||
// }
|
||||
|
||||
// Future: Add SMS channel
|
||||
// if (preferences.isSmsEnabled) {
|
||||
// this.logger.debug(`Routing to SMS channel for user ${payload.userId}`);
|
||||
// promises.push(
|
||||
// this.sendToChannel(payload, NotificationChannel.SMS)
|
||||
// );
|
||||
// }
|
||||
|
||||
// Send all notificaetions in parallel
|
||||
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<void> {
|
||||
await this.notificationsService.createNotification({
|
||||
userId: payload.userId,
|
||||
title: payload.title,
|
||||
message: payload.message,
|
||||
scope: payload.scope,
|
||||
channel,
|
||||
data: payload.data,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user