From befa311e987b71d3daf270bf2cf7de765b672bea Mon Sep 17 00:00:00 2001 From: morteza-mortezai Date: Sat, 13 Dec 2025 23:26:07 +0330 Subject: [PATCH] notification queue --- .../entities/notification.entity.ts | 3 - .../events/notification.events.ts | 36 -------- .../interfaces/jobs-queue.interface.ts | 25 +++++ .../notification-queue.interface.ts | 21 ----- .../interfaces/notification.interface.ts | 8 +- .../listeners/notification.listeners.ts | 92 ------------------- .../notifications/notifications.gateway.ts | 2 +- .../notifications/notifications.module.ts | 12 +-- .../processors/push.processor.ts | 80 ++-------------- .../notifications/processors/sms.processor.ts | 82 +++++++---------- .../services/notification-queue.service.ts | 75 +++++++++------ .../services/notification.service.ts | 46 +++------- .../notifications/services/sms.adaptor.ts | 36 -------- .../orders/listeners/order.listeners.ts | 8 +- .../orders/providers/orders.service.ts | 19 ++-- src/modules/pager/events/pager.events.ts | 5 +- .../pager/listeners/notification.listeners.ts | 22 +++-- src/modules/pager/providers/pager.service.ts | 2 +- src/modules/utils/interface/sms.ts | 2 +- src/modules/utils/sms.service.ts | 5 +- 20 files changed, 176 insertions(+), 405 deletions(-) delete mode 100644 src/modules/notifications/events/notification.events.ts create mode 100644 src/modules/notifications/interfaces/jobs-queue.interface.ts delete mode 100644 src/modules/notifications/interfaces/notification-queue.interface.ts delete mode 100644 src/modules/notifications/listeners/notification.listeners.ts delete mode 100644 src/modules/notifications/services/sms.adaptor.ts diff --git a/src/modules/notifications/entities/notification.entity.ts b/src/modules/notifications/entities/notification.entity.ts index 3a6f7e8..0ff53ed 100644 --- a/src/modules/notifications/entities/notification.entity.ts +++ b/src/modules/notifications/entities/notification.entity.ts @@ -22,9 +22,6 @@ export class Notification extends BaseEntity { @Property() content!: string; - // @Property({ nullable: true, unique: true }) - // idempotencyKey?: string; - @Property({ nullable: true }) sentAt?: Date; } diff --git a/src/modules/notifications/events/notification.events.ts b/src/modules/notifications/events/notification.events.ts deleted file mode 100644 index b8aa2a1..0000000 --- a/src/modules/notifications/events/notification.events.ts +++ /dev/null @@ -1,36 +0,0 @@ -export class OrderCreatedEvent { - constructor( - public readonly restaurantId: string, - public readonly userId: string, - public readonly orderNumber: string, - public readonly totalAmount: number, - ) {} -} - -export class OrderPaymentSuccessEvent { - constructor( - public readonly restaurantId: string, - public readonly userId: string, - public readonly orderNumber: string, - public readonly totalAmount: number, - ) {} -} - -export class ReviewCreatedEvent { - constructor( - public readonly restaurantId: string, - public readonly userId: string, - public readonly reviewId: string, - public readonly reviewData: any, - ) {} -} - -export class OrderStatusChangedEvent { - constructor( - public readonly restaurantId: string, - public readonly userId: string, - public readonly orderId: string, - public readonly oldStatus: string, - public readonly newStatus: string, - ) {} -} diff --git a/src/modules/notifications/interfaces/jobs-queue.interface.ts b/src/modules/notifications/interfaces/jobs-queue.interface.ts new file mode 100644 index 0000000..14ac48c --- /dev/null +++ b/src/modules/notifications/interfaces/jobs-queue.interface.ts @@ -0,0 +1,25 @@ +import type { recipientType } from './notification.interface'; + +export interface SmsNotificationQueueJob { + recipient: recipientType; + templateId: string; + parameters?: Record; +} +export interface PushNotificationQueueJob { + title: string; + content: string; + icon: string; + action: { + type: string; //view order + url: string; + }; + pushToken?: string; // FCM token for push notifications + pushTokens?: string[]; // Multiple FCM tokens for bulk push notifications +} + +export interface SmsNotificationQueueJobResult { + success: boolean; + notificationId: string; + providerResponse?: Record; + error?: string; +} diff --git a/src/modules/notifications/interfaces/notification-queue.interface.ts b/src/modules/notifications/interfaces/notification-queue.interface.ts deleted file mode 100644 index 525f359..0000000 --- a/src/modules/notifications/interfaces/notification-queue.interface.ts +++ /dev/null @@ -1,21 +0,0 @@ -import type { NotifChannelEnum } from './notification.interface'; -import type { NotifTitleEnum } from './notification.interface'; - -export interface NotificationQueueJob { - restaurantId: string; - userId?: string; - title: NotifTitleEnum; - content: string; - idempotencyKey?: string; - notificationId?: string; // For retries - channels: NotifChannelEnum[]; - pushToken?: string; // FCM token for push notifications - pushTokens?: string[]; // Multiple FCM tokens for bulk push notifications -} - -export interface NotificationQueueJobResult { - success: boolean; - notificationId: string; - providerResponse?: Record; - error?: string; -} diff --git a/src/modules/notifications/interfaces/notification.interface.ts b/src/modules/notifications/interfaces/notification.interface.ts index 1b7a929..c97ca61 100644 --- a/src/modules/notifications/interfaces/notification.interface.ts +++ b/src/modules/notifications/interfaces/notification.interface.ts @@ -20,7 +20,10 @@ export type recipientType = { userId: string } | { adminId: string }; export interface NotifRequestMessage { title: NotifTitleEnum; content: string; - smsText: string; + sms: { + templateId: string; + parameters?: Record; + }; pushNotif: { title: string; content: string; @@ -42,7 +45,7 @@ export interface NotifRequest { message: NotifRequestMessage; metadata: { priority: number; - retries: number; + // retries: number; }; } //************************************************ */ @@ -54,6 +57,7 @@ interface INotifySms { subject: NotifTitleEnum; } export interface IInAppNotificationPayload { + notificationId: string; subject: NotifTitleEnum; body: string; } diff --git a/src/modules/notifications/listeners/notification.listeners.ts b/src/modules/notifications/listeners/notification.listeners.ts deleted file mode 100644 index 1a8203d..0000000 --- a/src/modules/notifications/listeners/notification.listeners.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { Injectable, Logger } from '@nestjs/common'; -import { OnEvent } from '@nestjs/event-emitter'; -import { NotificationService } from '../services/notification.service'; -import { NotifTitleEnum } from '../interfaces/notification.interface'; -import { - OrderCreatedEvent, - ReviewCreatedEvent, - OrderStatusChangedEvent, - OrderPaymentSuccessEvent, -} from '../events/notification.events'; - -@Injectable() -export class NotificationListeners { - private readonly logger = new Logger(NotificationListeners.name); - - constructor(private readonly notificationService: NotificationService) {} - - // @OnEvent(OrderCreatedEvent.name) - // async handleOrderCreated(event: OrderCreatedEvent) { - // try { - // this.logger.log(`Order created event received: ${event.orderNumber}`); - // console.log('Order created event received: ', event); - // await this.notificationService.sendNotification({ - // restaurantId: event.restaurantId, - // userId: event.userId, - // title: NotifTitleEnum.ORDER_CREATED, - // content: `Your order #${event.orderNumber} has created successfully`, - // }); - // } catch (error) { - // this.logger.error( - // `Failed to send notification for order.created event: ${event.orderNumber}`, - // error instanceof Error ? error.stack : String(error), - // ); - // } - // } - // @OnEvent(OrderPaymentSuccessEvent.name) - // async handleOrderPaymentSuccess(event: OrderPaymentSuccessEvent) { - // try { - // this.logger.log(`Order payment success event received: ${event.orderNumber}`); - - // await this.notificationService.sendNotification({ - // restaurantId: event.restaurantId, - // userId: event.userId, - // title: NotifTitleEnum.ORDER_CREATED, - // content: `Your Payement for order #${event.orderNumber} has been successful Paid ${event.totalAmount}`, - // }); - // } catch (error) { - // this.logger.error( - // `Failed to send notification for order.created event: ${event.orderNumber}`, - // error instanceof Error ? error.stack : String(error), - // ); - // } - // } - - // @OnEvent(ReviewCreatedEvent.name) - // async handleReviewCreated(event: ReviewCreatedEvent) { - // try { - // this.logger.log(`Review created event received: ${event.reviewId}`); - - // await this.notificationService.sendNotification({ - // restaurantId: event.restaurantId, - // userId: event.userId, - // title: NotifTitleEnum.REVIEW_CREATED, - // content: 'Thank you for your review!', - // }); - // } catch (error) { - // this.logger.error( - // `Failed to send notification for review.created event: ${event.reviewId}`, - // error instanceof Error ? error.stack : String(error), - // ); - // } - // } - - // @OnEvent(OrderStatusChangedEvent.name) - // async handleOrderStatusChanged(event: OrderStatusChangedEvent) { - // try { - // this.logger.log(`Order status changed: ${event.orderId} from ${event.oldStatus} to ${event.newStatus}`); - - // await this.notificationService.sendNotification({ - // restaurantId: event.restaurantId, - // userId: event.userId, - // title: NotifTitleEnum.ORDER_STATUS_CHANGED, - // content: `Your order #${event.orderId} is now ${event.newStatus}`, - // }); - // } catch (error) { - // this.logger.error( - // `Failed to send notification for order.status.changed event: ${event.orderId}`, - // error instanceof Error ? error.stack : String(error), - // ); - // } - // } -} diff --git a/src/modules/notifications/notifications.gateway.ts b/src/modules/notifications/notifications.gateway.ts index 7401e06..4107cba 100644 --- a/src/modules/notifications/notifications.gateway.ts +++ b/src/modules/notifications/notifications.gateway.ts @@ -8,7 +8,7 @@ import { // ConnectedSocket, } from '@nestjs/websockets'; import { Server, Socket } from 'socket.io'; -import { Logger, UseGuards, Inject, forwardRef } from '@nestjs/common'; +import { Logger, Inject, forwardRef } from '@nestjs/common'; import { NotificationService } from './services/notification.service'; import { WsAdminAuthGuard, type AuthenticatedSocket } from './guards/ws-admin-auth.guard'; import { ModuleRef } from '@nestjs/core'; diff --git a/src/modules/notifications/notifications.module.ts b/src/modules/notifications/notifications.module.ts index 8ca0968..d3a49fb 100644 --- a/src/modules/notifications/notifications.module.ts +++ b/src/modules/notifications/notifications.module.ts @@ -8,13 +8,11 @@ import { NotificationService } from './services/notification.service'; import { NotificationPreferenceService } from './services/notification-preference.service'; import { NotificationQueueService } from './services/notification-queue.service'; import { PushNotificationService } from './services/push-notification.service'; -import { SmsAdaptorService } from './services/sms.adaptor'; import { SmsProcessor } from './processors/sms.processor'; import { PushProcessor } from './processors/push.processor'; import { NotificationsController } from './controllers/notifications.controller'; import { User } from '../users/entities/user.entity'; import { Restaurant } from '../restaurants/entities/restaurant.entity'; -import { NotificationListeners } from './listeners/notification.listeners'; import { AuthModule } from '../auth/auth.module'; import { ConfigService } from '@nestjs/config'; import { NotificationQueueNameEnum } from './constants/queue'; @@ -46,19 +44,11 @@ import { WsAdminAuthGuard } from './guards/ws-admin-auth.guard'; NotificationPreferenceService, NotificationQueueService, PushNotificationService, - SmsAdaptorService, PushProcessor, SmsProcessor, - NotificationListeners, NotificationsGateway, WsAdminAuthGuard, ], - exports: [ - NotificationService, - NotificationPreferenceService, - NotificationQueueService, - PushNotificationService, - SmsAdaptorService, - ], + exports: [NotificationService, NotificationPreferenceService, NotificationQueueService, PushNotificationService], }) export class NotificationsModule {} diff --git a/src/modules/notifications/processors/push.processor.ts b/src/modules/notifications/processors/push.processor.ts index 1325dca..756ca05 100644 --- a/src/modules/notifications/processors/push.processor.ts +++ b/src/modules/notifications/processors/push.processor.ts @@ -4,7 +4,7 @@ import { Job } from 'bullmq'; import { InjectRepository } from '@mikro-orm/nestjs'; import { EntityRepository, EntityManager } from '@mikro-orm/postgresql'; import { Notification } from '../entities/notification.entity'; -import { NotificationQueueJob, NotificationQueueJobResult } from '../interfaces/notification-queue.interface'; +import { PushNotificationQueueJob } from '../interfaces/jobs-queue.interface'; import { NotificationQueueNameEnum } from '../constants/queue'; import { PushNotificationService } from '../services/push-notification.service'; @@ -21,70 +21,18 @@ export class PushProcessor extends WorkerHost { super(); } - async process(job: Job): Promise { - const { restaurantId, userId, title, content, idempotencyKey, notificationId } = job.data; + async process(job: Job) { + const { title, content, action, pushToken, pushTokens } = job.data; - this.logger.log(`Processing push notification job: ${job.id} - Title: ${title}, Restaurant: ${restaurantId}`); + this.logger.log(`Processing push notification job: ${job.id} - Title: ${title},`); try { - // Find existing notification record (should already exist from NotificationService.sendNotification) - let notification: Notification | null = null; - - if (notificationId) { - notification = await this.notificationRepository.findOne( - { id: notificationId }, - { populate: ['user', 'restaurant'] }, - ); - if (!notification) { - throw new Error(`Notification ${notificationId} not found`); - } - } else if (idempotencyKey) { - // Fallback: find by idempotency key if notificationId not provided - notification = await this.notificationRepository.findOne( - { - id: 'sdfpsdfpsdf', - }, - // { idempotencyKey }, - // { populate: ['user', 'restaurant'] }, - ); - // if (notification && notification.sentAt) { - // this.logger.warn(`Duplicate notification skipped: ${idempotencyKey}`); - // return { - // success: true, - // notificationId: notification.id, - // }; - // } - } - - if (!notification) { - throw new Error(`Notification not found for job ${job.id}. notificationId or idempotencyKey required.`); - } - - // Check if push notification service is configured - if (!this.pushNotificationService.isConfigured()) { - this.logger.warn(`Push notification service not configured. Skipping notification ${notification.id}`); - // Mark as sent even though we didn't send it (service unavailable) - notification.sentAt = new Date(); - await this.em.persistAndFlush(notification); - return { - success: false, - notificationId: notification.id, - error: 'Push notification service not configured', - }; - } - - // For push notifications, we need a push token - if (!userId || !notification.user) { - this.logger.warn(`No user found for push notification ${notification.id}`); - throw new Error('User is required for push notifications'); - } - // Get push token from job data const pushToken = job.data.pushToken; const pushTokens = job.data.pushTokens; if (!pushToken && (!pushTokens || pushTokens.length === 0)) { - this.logger.warn(`Push token(s) not provided for notification ${notification.id}`); + this.logger.warn(`Push token(s) not provided for notification `); throw new Error('Push token or pushTokens array is required for push notifications'); } @@ -102,10 +50,7 @@ export class PushProcessor extends WorkerHost { body: content, tokens: pushTokens, data: { - notificationId: notification.id, - restaurantId, - userId, - title: String(title), + action, }, }); } else if (pushToken) { @@ -115,10 +60,7 @@ export class PushProcessor extends WorkerHost { body: content, token: pushToken, data: { - notificationId: notification.id, - restaurantId, - userId, - title: String(title), + action, }, }); } else { @@ -129,15 +71,11 @@ export class PushProcessor extends WorkerHost { throw new Error(pushResult.error || 'Failed to send push notification'); } - // Mark notification as sent - notification.sentAt = new Date(); - await this.em.persistAndFlush(notification); - - this.logger.log(`Push notification ${notification.id} sent successfully`); + this.logger.log(`Push notification sent successfully`); return { success: true, - notificationId: notification.id, + providerResponse: { messageId: pushResult.messageId }, }; } catch (error) { diff --git a/src/modules/notifications/processors/sms.processor.ts b/src/modules/notifications/processors/sms.processor.ts index 77ac826..1560af3 100644 --- a/src/modules/notifications/processors/sms.processor.ts +++ b/src/modules/notifications/processors/sms.processor.ts @@ -2,14 +2,13 @@ import { Processor, WorkerHost, OnWorkerEvent } from '@nestjs/bullmq'; import { Logger } from '@nestjs/common'; import { Job } from 'bullmq'; import { InjectRepository } from '@mikro-orm/nestjs'; -import { EntityRepository, EntityManager } from '@mikro-orm/postgresql'; +import { EntityManager } from '@mikro-orm/postgresql'; import { Notification } from '../entities/notification.entity'; -import { NotificationQueueJob, NotificationQueueJobResult } from '../interfaces/notification-queue.interface'; +import { SmsNotificationQueueJob } from '../interfaces/jobs-queue.interface'; import { User } from '../../users/entities/user.entity'; import { NotificationQueueNameEnum } from '../constants/queue'; -import { SmsAdaptorService } from '../services/sms.adaptor'; -import { NotifTitleEnum } from '../interfaces/notification.interface'; -import { ISmsResponse } from '../../utils/interface/sms'; +import { SmsService } from 'src/modules/utils/sms.service'; +import { Admin } from 'src/modules/admin/entities/admin.entity'; @Processor(NotificationQueueNameEnum.SMS) export class SmsProcessor extends WorkerHost { @@ -17,65 +16,50 @@ export class SmsProcessor extends WorkerHost { constructor( @InjectRepository(Notification) - private readonly notificationRepository: EntityRepository, private readonly em: EntityManager, - private readonly smsAdaptorService: SmsAdaptorService, + private readonly smsService: SmsService, ) { super(); } - async process(job: Job): Promise { - const { restaurantId, userId, title } = job.data; + async process(job: Job) { + const { recipient, templateId, parameters } = job.data; - this.logger.log(`Processing SMS notification job: ${job.id} - Title: ${title}, - Restaurant: ${restaurantId}`); + this.logger.log(`Processing SMS notification - Recipient: ${JSON.stringify(recipient)}, templateID: ${templateId}`); try { - const user = await this.em.findOne(User, { id: userId }); - if (!user || !user.phone) { - this.logger.warn(`User ${userId} not found or has no phone number`); - throw new Error('User phone number is required for SMS notifications'); + let phone!: string; + if ('userId' in recipient) { + const user = await this.em.findOne(User, { id: recipient.userId }); + if (!user || !user.phone) { + this.logger.warn(`User ${recipient.userId} not found or has no phone number`); + throw new Error('User phone number is required for SMS notifications'); + } + phone = user.phone; } - - // Check if the notification type is supported for SMS - if (title !== NotifTitleEnum.ORDER_CREATED && title !== NotifTitleEnum.PAYMENT_SUCCESS) { - throw new Error(`SMS notification type ${title} is not supported`); + if ('adminId' in recipient) { + const admin = await this.em.findOne(Admin, { id: recipient.adminId }); + if (!admin || !admin.phone) { + this.logger.warn(`Admin ${recipient.adminId} not found or has no phone number`); + throw new Error('Admin phone number is required for SMS notifications'); + } + phone = admin.phone; + } + if (!phone) { + this.logger.warn(`Phone number not found for recipient ${JSON.stringify(recipient)}`); + throw new Error('Phone number is required for SMS notifications'); } - // Send SMS notification - let smsResult: ISmsResponse; - // if (title === NotifTitleEnum.ORDER_CREATED) { - // smsResult = await this.smsAdaptorService.sendNotifySms({ - // phone: user.phone, - // subject: NotifTitleEnum.ORDER_CREATED, - // payload: { - // orderNumber: '1234567890', - // orderAmount: 100000, - // orderDate: new Date(), - // }, - // }); - // } else { - // smsResult = await this.smsAdaptorService.sendNotifySms({ - // phone: user.phone, - // subject: NotifTitleEnum.PAYMENT_SUCCESS, - // payload: { - // amount: 100000, - // date: new Date(), - // }, - // }); - // } + await this.smsService.sendSms({ + phone, + templateId, + parameters, + }); - // if (smsResult.status !== 1) { - // throw new Error(smsResult.message ?? 'Failed to send SMS notification'); - // } - - // Mark notification as sent - - this.logger.log(`SMS notification sent successfully to ${user.phone}`); + this.logger.log(`SMS notification sent successfully to ${phone}`); return { success: true, - notificationId: 'sdfpsdfpsdf', }; } catch (error) { this.logger.error(`Error processing SMS notification job ${job.id}:`, error); diff --git a/src/modules/notifications/services/notification-queue.service.ts b/src/modules/notifications/services/notification-queue.service.ts index cea17af..2336aec 100644 --- a/src/modules/notifications/services/notification-queue.service.ts +++ b/src/modules/notifications/services/notification-queue.service.ts @@ -2,7 +2,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { InjectQueue } from '@nestjs/bullmq'; import { Queue } from 'bullmq'; import { NotificationQueueNameEnum } from '../constants/queue'; -import { NotificationQueueJob } from '../interfaces/notification-queue.interface'; +import { PushNotificationQueueJob, SmsNotificationQueueJob } from '../interfaces/jobs-queue.interface'; @Injectable() export class NotificationQueueService { @@ -13,9 +13,9 @@ export class NotificationQueueService { @InjectQueue(NotificationQueueNameEnum.PUSH) private readonly pushQueue: Queue, ) {} - async addSmsNotification(job: NotificationQueueJob): Promise { + async addSmsNotification(job: SmsNotificationQueueJob): Promise { try { - const jobId = job.idempotencyKey || `${job.restaurantId}-${job.title}-${Date.now()}`; + const jobId = `${job.templateId}-${JSON.stringify(job.parameters)}-${Date.now()}`; await this.smsQueue.add('sms-notification', job, { jobId, @@ -40,9 +40,9 @@ export class NotificationQueueService { } } - async addPushNotification(job: NotificationQueueJob): Promise { + async addPushNotification(job: PushNotificationQueueJob): Promise { try { - const jobId = job.idempotencyKey || `${job.restaurantId}-${job.title}-${Date.now()}`; + const jobId = `${job.title}-${JSON.stringify(job.action)}-${Date.now()}`; await this.pushQueue.add('push-notification', job, { jobId, @@ -67,26 +67,49 @@ export class NotificationQueueService { } } - // async addBulkNotifications(jobs: NotificationQueueJob[]): Promise { - // try { - // const queueJobs = jobs.map(job => ({ - // name: 'send-notification', - // data: job, - // opts: { - // jobId: job.idempotencyKey || `${job.restaurantId}-${job.title}-${Date.now()}`, - // attempts: 3, - // backoff: { - // type: 'exponential', - // delay: 2000, - // }, - // }, - // })); + async addBulkSmsNotifications(jobs: SmsNotificationQueueJob[]): Promise { + try { + const queueJobs = jobs.map(job => ({ + name: 'sms-notification', + data: job, + opts: { + jobId: `${job.templateId}-${JSON.stringify(job.parameters)}-${Date.now()}`, + attempts: 3, + backoff: { + type: 'exponential', + delay: 2000, + }, + }, + })); - // await this.notificationQueue.addBulk(queueJobs); - // this.logger.log(`Added ${jobs.length} notification jobs to queue`); - // } catch (error) { - // this.logger.error(`Failed to add bulk notifications to queue:`, error); - // throw error; - // } - // } + await this.smsQueue.addBulk(queueJobs); + this.logger.log(`Added ${jobs.length} SMS notification jobs to queue`); + } catch (error) { + this.logger.error(`Failed to add bulk notifications to queue:`, error); + throw error; + } + } + + async addBulkPushNotifications(jobs: PushNotificationQueueJob[]): Promise { + try { + const queueJobs = jobs.map(job => ({ + name: 'push-notification', + data: job, + opts: { + jobId: `${job.title}-${JSON.stringify(job.action)}-${Date.now()}`, + attempts: 3, + backoff: { + type: 'exponential', + delay: 2000, + }, + }, + })); + + await this.pushQueue.addBulk(queueJobs); + this.logger.log(`Added ${jobs.length} Push notification jobs to queue`); + } catch (error) { + this.logger.error(`Failed to add bulk notifications to queue:`, error); + throw error; + } + } } diff --git a/src/modules/notifications/services/notification.service.ts b/src/modules/notifications/services/notification.service.ts index 99830a1..90890fe 100644 --- a/src/modules/notifications/services/notification.service.ts +++ b/src/modules/notifications/services/notification.service.ts @@ -4,9 +4,6 @@ import { Notification } from '../entities/notification.entity'; import { NotificationPreferenceService } from './notification-preference.service'; import { NotificationQueueService } from './notification-queue.service'; import { NotificationsGateway } from '../notifications.gateway'; -import { NotificationQueueJob } from '../interfaces/notification-queue.interface'; -import { Restaurant } from '../../restaurants/entities/restaurant.entity'; -import { User } from '../../users/entities/user.entity'; import { NotifChannelEnum, NotifRequest, NotifTitleEnum } from '../interfaces/notification.interface'; @Injectable() @@ -44,11 +41,12 @@ export class NotificationService { // send in app notification if (preference?.channels?.includes(NotifChannelEnum.IN_APP)) { - recipients.forEach(recipient => { - if ('adminId' in recipient) { + notifications.forEach(notif => { + if (notif.admin) { this.notificationGateway.sendInAppNotification( - { adminId: recipient.adminId, restaurantId }, + { adminId: notif.admin.id, restaurantId }, { + notificationId: notif.id, subject: message.title, body: message.content, }, @@ -56,32 +54,16 @@ export class NotificationService { } }); } - // const job: NotificationQueueJob = { - // restaurantId, - // userId, - // title, - // content, - // // idempotencyKey: finalIdempotencyKey, - // notificationId: notification.id, - // channels: preference.channels, - // }; - - // preference.channels.forEach(channel => { - // if (channel === NotifChannelEnum.SMS) { - // await this.queueService.addSmsNotification(job); - // } else if (channel === NotifChannelEnum.PUSH) { - // await this.queueService.addPushNotification(job); - // } - // }); - // Add to queue - // if (preference.notificationType === NotificationType.SMS) { - // await this.queueService.addSmsNotification(job); - // } else if (preference.notificationType === NotificationType.PUSH) { - // await this.queueService.addPushNotification(job); - // } else if (preference.notificationType === NotificationType.Both) { - // await this.queueService.addSmsNotification(job); - // await this.queueService.addPushNotification(job); - // } + // add sms notifications to queue + if (preference?.channels?.includes(NotifChannelEnum.SMS)) { + await this.queueService.addBulkSmsNotifications( + recipients.map(recipient => ({ + recipient, + templateId: message.sms.templateId, + parameters: message.sms.parameters, + })), + ); + } this.logger.log(`Queued notification for restaurant ${restaurantId}, title ${message.title}`); diff --git a/src/modules/notifications/services/sms.adaptor.ts b/src/modules/notifications/services/sms.adaptor.ts deleted file mode 100644 index 8fbdd1a..0000000 --- a/src/modules/notifications/services/sms.adaptor.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { SmsService } from 'src/modules/utils/sms.service'; -import { ISmsNotifyPayload } from '../interfaces/notification.interface'; -import { NotifTitleEnum } from '../interfaces/notification.interface'; - -@Injectable() -export class SmsAdaptorService { - private readonly smsPatternOrderCreated: string; - private readonly smsPatternPaymentSuccess: string; - constructor( - private readonly configService: ConfigService, - private readonly smsService: SmsService, - ) { - this.smsPatternOrderCreated = this.configService.get('SMS_PATTERN_ORDER_CREATED') ?? '1'; - this.smsPatternPaymentSuccess = this.configService.get('SMS_PATTERN_PAYMENT_SUCCESS') ?? '2'; - } - - async sendNotifySms(params: ISmsNotifyPayload) { - const { phone, subject, payload } = params; - let templateId!: string; - switch (subject) { - // case NotifTitleEnum.ORDER_CREATED: - // templateId = this.smsPatternOrderCreated; - // break; - case NotifTitleEnum.PAYMENT_SUCCESS: - templateId = this.smsPatternPaymentSuccess; - break; - } - return this.smsService.sendSms({ - phone, - parameters: payload, - templateId, - }); - } -} diff --git a/src/modules/orders/listeners/order.listeners.ts b/src/modules/orders/listeners/order.listeners.ts index 23c0926..e0615db 100644 --- a/src/modules/orders/listeners/order.listeners.ts +++ b/src/modules/orders/listeners/order.listeners.ts @@ -31,7 +31,12 @@ export class OrderListeners { message: { title: NotifTitleEnum.ORDER_CREATED, content: `Order ${event.orderNumber} has been created successfully`, - smsText: `Order ${event.orderNumber} has been created successfully`, + sms: { + templateId: '1234567890', + parameters: { + orderNumber: event.orderNumber, + }, + }, pushNotif: { title: `Order ${event.orderNumber} has been created successfully`, content: `Order ${event.orderNumber} has been created successfully`, @@ -45,7 +50,6 @@ export class OrderListeners { recipients, metadata: { priority: 1, - retries: 3, }, }); } catch (error) { diff --git a/src/modules/orders/providers/orders.service.ts b/src/modules/orders/providers/orders.service.ts index 96396c8..2c7a3dc 100644 --- a/src/modules/orders/providers/orders.service.ts +++ b/src/modules/orders/providers/orders.service.ts @@ -20,8 +20,7 @@ import { OrderRepository } from '../repositories/order.repository'; import { FindOrdersDto } from '../dto/find-orders.dto'; import { PaginatedResult } from 'src/common/interfaces/pagination.interface'; import { EventEmitter2 } from '@nestjs/event-emitter'; -import { OrderCreatedEvent, OrderPaymentSuccessEvent } from '../../notifications/events/notification.events'; - + @Injectable() export class OrdersService { private readonly logger = new Logger(OrdersService.name); @@ -49,14 +48,14 @@ export class OrdersService { await this.cartService.clearCart(userId, restaurantId); - this.eventEmitter2.emit( - OrderCreatedEvent.name, - new OrderCreatedEvent(restaurantId, order.user.id, order.id, order.total), - ); - this.eventEmitter2.emit( - OrderPaymentSuccessEvent.name, - new OrderPaymentSuccessEvent(restaurantId, order.user.id, order.id, order.total), - ); + // this.eventEmitter2.emit( + // OrderCreatedEvent.name, + // new OrderCreatedEvent(restaurantId, order.user.id, order.id, order.total), + // ); + // this.eventEmitter2.emit( + // OrderPaymentSuccessEvent.name, + // new OrderPaymentSuccessEvent(restaurantId, order.user.id, order.id, order.total), + // ); console.log('Order created event emitted 111'); return { order, paymentUrl }; } diff --git a/src/modules/pager/events/pager.events.ts b/src/modules/pager/events/pager.events.ts index 9035efe..a5236a6 100644 --- a/src/modules/pager/events/pager.events.ts +++ b/src/modules/pager/events/pager.events.ts @@ -1,3 +1,6 @@ export class PagerCreatedEvent { - constructor(public readonly restaurantId: string) {} + constructor( + public readonly restaurantId: string, + public readonly tableNumber: string, + ) {} } diff --git a/src/modules/pager/listeners/notification.listeners.ts b/src/modules/pager/listeners/notification.listeners.ts index 2aa7531..0d8a1cd 100644 --- a/src/modules/pager/listeners/notification.listeners.ts +++ b/src/modules/pager/listeners/notification.listeners.ts @@ -28,23 +28,27 @@ export class PagerListeners { restaurantId: event.restaurantId, message: { title: NotifTitleEnum.PAGER_CREATED, - content: `Your pager has created successfully`, - smsText: `Your pager has created successfully`, + content: `میز شماره ${event.tableNumber} پیج جدیدی انجام داد`, + sms: { + templateId: '1234567890', + parameters: { + tableNumber: event.tableNumber.toString(), + }, + }, pushNotif: { - title: `Your pager has created successfully`, - content: `Your pager has created successfully`, - icon: `Your pager has created successfully`, + title: ` پیج جدید`, + content: `میز شماره ${event.tableNumber} پیج جدیدی انجام داد`, + icon: `/assets/images/logo.png`, action: { - type: `Your pager has created successfully`, - url: `Your pager has created successfully`, + type: NotifTitleEnum.PAGER_CREATED, + url: `/restaurants/${event.restaurantId}/pagers`, }, }, }, recipients, metadata: { priority: 1, - retries: 3, - }, + }, }); } catch (error) { this.logger.error( diff --git a/src/modules/pager/providers/pager.service.ts b/src/modules/pager/providers/pager.service.ts index 75a0154..1210551 100644 --- a/src/modules/pager/providers/pager.service.ts +++ b/src/modules/pager/providers/pager.service.ts @@ -70,7 +70,7 @@ export class PagerService { // Populate relations before emitting await this.em.populate(pager, ['restaurant']); // Emit socket event for new pager - this.eventEmitter.emit(PagerCreatedEvent.name, new PagerCreatedEvent(pager.restaurant.id)); + this.eventEmitter.emit(PagerCreatedEvent.name, new PagerCreatedEvent(pager.restaurant.id, pager.tableNumber)); return pager; } diff --git a/src/modules/utils/interface/sms.ts b/src/modules/utils/interface/sms.ts index d397dfe..fd0a746 100644 --- a/src/modules/utils/interface/sms.ts +++ b/src/modules/utils/interface/sms.ts @@ -5,7 +5,7 @@ export interface ISmsResponse { export interface ISmsParams { phone: string; - parameters: Record; + parameters?: Record; templateId: string; } diff --git a/src/modules/utils/sms.service.ts b/src/modules/utils/sms.service.ts index b671545..d941f69 100644 --- a/src/modules/utils/sms.service.ts +++ b/src/modules/utils/sms.service.ts @@ -28,7 +28,10 @@ export class SmsService { async sendSms(params: ISmsParams) { const { parameters, phone, templateId } = params; - const parametersArray = Object.entries(parameters).map(([name, value]) => ({ name, value: value.toString() })); + const parametersArray = Object.entries(parameters ?? {}).map(([name, value]) => ({ + name, + value: value.toString(), + })); const smsData: ISmsBodyParameters = { Parameters: parametersArray,