From 5fe82bb661705b3f496886adb1cca7a451a168c2 Mon Sep 17 00:00:00 2001 From: mahyargdz Date: Sat, 12 Jul 2025 14:54:16 +0330 Subject: [PATCH] feat: fix --- .../handlers/admin-notifications.handler.ts | 79 +++++++++ .../handlers/announcement.handler.ts | 23 +++ .../handlers/base-notification.handler.ts | 31 ++++ .../handlers/blog-comment.handler.ts | 23 +++ src/modules/notifications/handlers/index.ts | 38 +++++ .../notifications/handlers/invoice.handler.ts | 108 ++++++++++++ .../handlers/notification-handler.factory.ts | 123 ++++++++++++++ .../notifications/handlers/otp.handler.ts | 25 +++ .../notifications/handlers/payment.handler.ts | 40 +++++ .../notifications/handlers/service.handler.ts | 23 +++ .../notifications/handlers/ticket.handler.ts | 74 ++++++++ .../handlers/user-login.handler.ts | 24 +++ .../notifications/handlers/wallet.handler.ts | 40 +++++ .../notifications/notifications.module.ts | 64 ++++++- .../providers/notifications.service.ts | 136 +++++++-------- .../queue/notification.processor.ts | 160 ++++-------------- src/modules/subscriptions/constants/index.ts | 2 +- .../providers/subscriptions.service.ts | 5 +- 18 files changed, 815 insertions(+), 203 deletions(-) create mode 100644 src/modules/notifications/handlers/admin-notifications.handler.ts create mode 100644 src/modules/notifications/handlers/announcement.handler.ts create mode 100644 src/modules/notifications/handlers/base-notification.handler.ts create mode 100644 src/modules/notifications/handlers/blog-comment.handler.ts create mode 100644 src/modules/notifications/handlers/index.ts create mode 100644 src/modules/notifications/handlers/invoice.handler.ts create mode 100644 src/modules/notifications/handlers/notification-handler.factory.ts create mode 100644 src/modules/notifications/handlers/otp.handler.ts create mode 100644 src/modules/notifications/handlers/payment.handler.ts create mode 100644 src/modules/notifications/handlers/service.handler.ts create mode 100644 src/modules/notifications/handlers/ticket.handler.ts create mode 100644 src/modules/notifications/handlers/user-login.handler.ts create mode 100644 src/modules/notifications/handlers/wallet.handler.ts diff --git a/src/modules/notifications/handlers/admin-notifications.handler.ts b/src/modules/notifications/handlers/admin-notifications.handler.ts new file mode 100644 index 0000000..7ae0b4c --- /dev/null +++ b/src/modules/notifications/handlers/admin-notifications.handler.ts @@ -0,0 +1,79 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { QueryRunner } from "typeorm"; + +import { BaseNotificationHandler } from "./base-notification.handler"; +import { + INewCriticismNotificationData, + INewCustomerNotificationData, + INewSubscriptionNotificationData, + IServiceReviewNotificationData, +} from "../interfaces/ISendNotificationData"; +import { NotificationsService } from "../providers/notifications.service"; + +@Injectable() +export class ServiceReviewHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(ServiceReviewHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IServiceReviewNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createNewServiceReviewNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "ServiceReview"; + } +} + +@Injectable() +export class NewCustomerHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(NewCustomerHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: INewCustomerNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createNewCustomerNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "NewCustomer"; + } +} + +@Injectable() +export class NewSubscriptionHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(NewSubscriptionHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: INewSubscriptionNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createNewSubscriptionNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "NewSubscription"; + } +} + +@Injectable() +export class NewCriticismHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(NewCriticismHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: INewCriticismNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createNewCriticismNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "NewCriticism"; + } +} diff --git a/src/modules/notifications/handlers/announcement.handler.ts b/src/modules/notifications/handlers/announcement.handler.ts new file mode 100644 index 0000000..4b34a89 --- /dev/null +++ b/src/modules/notifications/handlers/announcement.handler.ts @@ -0,0 +1,23 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { QueryRunner } from "typeorm"; + +import { BaseNotificationHandler } from "./base-notification.handler"; +import { IAnnouncementNotificationData } from "../interfaces/ISendNotificationData"; +import { NotificationsService } from "../providers/notifications.service"; + +@Injectable() +export class AnnouncementHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(AnnouncementHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IAnnouncementNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createAnnouncementNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "Announcement"; + } +} diff --git a/src/modules/notifications/handlers/base-notification.handler.ts b/src/modules/notifications/handlers/base-notification.handler.ts new file mode 100644 index 0000000..f27b147 --- /dev/null +++ b/src/modules/notifications/handlers/base-notification.handler.ts @@ -0,0 +1,31 @@ +import { Logger } from "@nestjs/common"; +import { QueryRunner } from "typeorm"; + +export abstract class BaseNotificationHandler { + protected abstract readonly logger: Logger; + + /** + * Process the notification with automatic transaction management + */ + async processWithTransaction(recipientId: string, data: TData, queryRunner: QueryRunner): Promise { + try { + await this.handle(recipientId, data, queryRunner); + } catch (error) { + this.logger.error( + `Failed to process ${this.getHandlerName()} notification: ${error instanceof Error ? error.message : "Unknown error"}`, + error instanceof Error ? error.stack : undefined, + ); + throw error; + } + } + + /** + * Handle the specific notification logic - to be implemented by each handler + */ + protected abstract handle(recipientId: string, data: TData, queryRunner: QueryRunner): Promise; + + /** + * Get the handler name for logging purposes + */ + protected abstract getHandlerName(): string; +} diff --git a/src/modules/notifications/handlers/blog-comment.handler.ts b/src/modules/notifications/handlers/blog-comment.handler.ts new file mode 100644 index 0000000..c1aceb7 --- /dev/null +++ b/src/modules/notifications/handlers/blog-comment.handler.ts @@ -0,0 +1,23 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { QueryRunner } from "typeorm"; + +import { BaseNotificationHandler } from "./base-notification.handler"; +import { IBlogCommentNotificationData } from "../interfaces/ISendNotificationData"; +import { NotificationsService } from "../providers/notifications.service"; + +@Injectable() +export class BlogCommentHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(BlogCommentHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IBlogCommentNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createNewBlogCommentNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "BlogComment"; + } +} diff --git a/src/modules/notifications/handlers/index.ts b/src/modules/notifications/handlers/index.ts new file mode 100644 index 0000000..021a359 --- /dev/null +++ b/src/modules/notifications/handlers/index.ts @@ -0,0 +1,38 @@ +// Base handler +export { BaseNotificationHandler } from "./base-notification.handler"; + +// Factory +export { NotificationHandlerFactory } from "./notification-handler.factory"; + +// Special handlers (non-transactional) +export { OtpHandler } from "./otp.handler"; +export { UserLoginHandler } from "./user-login.handler"; + +// Wallet handlers +export { WalletChargeHandler, WalletDeductionHandler } from "./wallet.handler"; + +// Invoice handlers +export { + CreateInvoiceHandler, + BillInvoiceReminderHandler, + BillInvoiceHandler, + ApproveInvoiceHandler, + InvoiceOverdueHandler, + RecurringInvoiceHandler, +} from "./invoice.handler"; + +// Admin notification handlers +export { BlogCommentHandler } from "./blog-comment.handler"; +export { ServiceReviewHandler, NewCustomerHandler, NewSubscriptionHandler, NewCriticismHandler } from "./admin-notifications.handler"; + +// User notification handlers +export { AnnouncementHandler } from "./announcement.handler"; + +// Ticket handlers +export { NewTicketHandler, AnswerTicketHandler, CreateTicketHandler, AssignTicketHandler } from "./ticket.handler"; + +// Service handlers +export { BlockServiceHandler } from "./service.handler"; + +// Payment handlers +export { PaymentReminderHandler, PaymentCancellationHandler } from "./payment.handler"; diff --git a/src/modules/notifications/handlers/invoice.handler.ts b/src/modules/notifications/handlers/invoice.handler.ts new file mode 100644 index 0000000..07098fe --- /dev/null +++ b/src/modules/notifications/handlers/invoice.handler.ts @@ -0,0 +1,108 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { QueryRunner } from "typeorm"; + +import { BaseNotificationHandler } from "./base-notification.handler"; +import { IInvoiceNotificationData } from "../interfaces/ISendNotificationData"; +import { NotificationsService } from "../providers/notifications.service"; + +@Injectable() +export class CreateInvoiceHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(CreateInvoiceHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createInvoiceCreationNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "CreateInvoice"; + } +} + +@Injectable() +export class BillInvoiceReminderHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(BillInvoiceReminderHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createBillInvoiceReminderNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "BillInvoiceReminder"; + } +} + +@Injectable() +export class BillInvoiceHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(BillInvoiceHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createBillInvoiceNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "BillInvoice"; + } +} + +@Injectable() +export class ApproveInvoiceHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(ApproveInvoiceHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createApprovedInvoiceNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "ApproveInvoice"; + } +} + +@Injectable() +export class InvoiceOverdueHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(InvoiceOverdueHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createInvoiceOverdueNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "InvoiceOverdue"; + } +} + +@Injectable() +export class RecurringInvoiceHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(RecurringInvoiceHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createRecurringInvoiceNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "RecurringInvoice"; + } +} diff --git a/src/modules/notifications/handlers/notification-handler.factory.ts b/src/modules/notifications/handlers/notification-handler.factory.ts new file mode 100644 index 0000000..6833e45 --- /dev/null +++ b/src/modules/notifications/handlers/notification-handler.factory.ts @@ -0,0 +1,123 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { QueryRunner } from "typeorm"; + +import { NewCriticismHandler, NewCustomerHandler, NewSubscriptionHandler, ServiceReviewHandler } from "./admin-notifications.handler"; +import { AnnouncementHandler } from "./announcement.handler"; +import { BaseNotificationHandler } from "./base-notification.handler"; +import { BlogCommentHandler } from "./blog-comment.handler"; +import { + ApproveInvoiceHandler, + BillInvoiceHandler, + BillInvoiceReminderHandler, + CreateInvoiceHandler, + InvoiceOverdueHandler, + RecurringInvoiceHandler, +} from "./invoice.handler"; +import { PaymentCancellationHandler, PaymentReminderHandler } from "./payment.handler"; +import { BlockServiceHandler } from "./service.handler"; +import { AnswerTicketHandler, AssignTicketHandler, CreateTicketHandler, NewTicketHandler } from "./ticket.handler"; +import { WalletChargeHandler, WalletDeductionHandler } from "./wallet.handler"; +import { NotifType } from "../../settings/enums/notif-settings.enum"; + +@Injectable() +export class NotificationHandlerFactory { + private readonly logger = new Logger(NotificationHandlerFactory.name); + private readonly handlers: Map = new Map(); + + constructor( + // Wallet handlers + private readonly walletChargeHandler: WalletChargeHandler, + private readonly walletDeductionHandler: WalletDeductionHandler, + + // Invoice handlers + private readonly createInvoiceHandler: CreateInvoiceHandler, + private readonly billInvoiceReminderHandler: BillInvoiceReminderHandler, + private readonly billInvoiceHandler: BillInvoiceHandler, + private readonly approveInvoiceHandler: ApproveInvoiceHandler, + private readonly invoiceOverdueHandler: InvoiceOverdueHandler, + private readonly recurringInvoiceHandler: RecurringInvoiceHandler, + + // Admin handlers + private readonly blogCommentHandler: BlogCommentHandler, + private readonly serviceReviewHandler: ServiceReviewHandler, + private readonly newCustomerHandler: NewCustomerHandler, + private readonly newSubscriptionHandler: NewSubscriptionHandler, + private readonly newCriticismHandler: NewCriticismHandler, + private readonly newTicketHandler: NewTicketHandler, + + // User handlers + private readonly announcementHandler: AnnouncementHandler, + + // Ticket handlers + private readonly answerTicketHandler: AnswerTicketHandler, + private readonly createTicketHandler: CreateTicketHandler, + private readonly assignTicketHandler: AssignTicketHandler, + + // Service handlers + private readonly blockServiceHandler: BlockServiceHandler, + + // Payment handlers + private readonly paymentReminderHandler: PaymentReminderHandler, + private readonly paymentCancellationHandler: PaymentCancellationHandler, + ) { + this.registerHandlers(); + } + + private registerHandlers(): void { + // Wallet notifications + this.handlers.set(NotifType.WALLET_CHARGE, this.walletChargeHandler); + this.handlers.set(NotifType.WALLET_DEDUCTION, this.walletDeductionHandler); + + // Invoice notifications + this.handlers.set(NotifType.CREATE_INVOICE, this.createInvoiceHandler); + this.handlers.set(NotifType.BILL_INVOICE_REMINDER, this.billInvoiceReminderHandler); + this.handlers.set(NotifType.BILL_INVOICE, this.billInvoiceHandler); + this.handlers.set(NotifType.APPROVE_INVOICE, this.approveInvoiceHandler); + this.handlers.set(NotifType.INVOICE_OVERDUE, this.invoiceOverdueHandler); + this.handlers.set(NotifType.RECURRING_INVOICE, this.recurringInvoiceHandler); + + // Admin notifications + this.handlers.set(NotifType.NEW_BLOG_COMMENT, this.blogCommentHandler); + this.handlers.set(NotifType.NEW_SERVICE_REVIEW, this.serviceReviewHandler); + this.handlers.set(NotifType.NEW_CUSTOMER, this.newCustomerHandler); + this.handlers.set(NotifType.NEW_SUBSCRIPTION, this.newSubscriptionHandler); + this.handlers.set(NotifType.NEW_CRITICISM, this.newCriticismHandler); + this.handlers.set(NotifType.NEW_TICKET, this.newTicketHandler); + + // User notifications + this.handlers.set(NotifType.ANNOUNCEMENT, this.announcementHandler); + + // Ticket notifications + this.handlers.set(NotifType.ANSWER_TICKET, this.answerTicketHandler); + this.handlers.set(NotifType.CREATE_TICKET, this.createTicketHandler); + this.handlers.set(NotifType.ASSIGN_TICKET, this.assignTicketHandler); + + // Service notifications + this.handlers.set(NotifType.BLOCK_SERVICE, this.blockServiceHandler); + + // Payment notifications + this.handlers.set(NotifType.PAYMENT_REMINDER, this.paymentReminderHandler); + this.handlers.set(NotifType.PAYMENT_CANCELLATION, this.paymentCancellationHandler); + + this.logger.log(`Registered ${this.handlers.size} notification handlers`); + } + + async processNotification(type: NotifType, recipientId: string, data: unknown, queryRunner: QueryRunner): Promise { + const handler = this.handlers.get(type); + + if (!handler) { + this.logger.warn(`No handler found for notification type: ${type}`); + throw new Error(`No handler registered for notification type: ${type}`); + } + + await handler.processWithTransaction(recipientId, data, queryRunner); + } + + getRegisteredTypes(): NotifType[] { + return Array.from(this.handlers.keys()); + } + + isTypeSupported(type: NotifType): boolean { + return this.handlers.has(type); + } +} diff --git a/src/modules/notifications/handlers/otp.handler.ts b/src/modules/notifications/handlers/otp.handler.ts new file mode 100644 index 0000000..bde0ec3 --- /dev/null +++ b/src/modules/notifications/handlers/otp.handler.ts @@ -0,0 +1,25 @@ +import { Injectable, Logger } from "@nestjs/common"; + +import { SmsService } from "../../utils/providers/sms.service"; +import { OtpJobData } from "../interfaces/INotification-job-data"; + +@Injectable() +export class OtpHandler { + private readonly logger = new Logger(OtpHandler.name); + + constructor(private readonly smsService: SmsService) {} + + async processOtp(otpData: OtpJobData): Promise { + try { + const { phone, code } = otpData; + await this.smsService.sendSmsVerifyCode(phone, code); + this.logger.log(`OTP sent successfully to ${phone}`); + } catch (error) { + this.logger.error( + `Failed to send OTP: ${error instanceof Error ? error.message : "Unknown error"}`, + error instanceof Error ? error.stack : undefined, + ); + throw error; + } + } +} diff --git a/src/modules/notifications/handlers/payment.handler.ts b/src/modules/notifications/handlers/payment.handler.ts new file mode 100644 index 0000000..7ceb076 --- /dev/null +++ b/src/modules/notifications/handlers/payment.handler.ts @@ -0,0 +1,40 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { QueryRunner } from "typeorm"; + +import { BaseNotificationHandler } from "./base-notification.handler"; +import { IPaymentNotificationData } from "../interfaces/ISendNotificationData"; +import { NotificationsService } from "../providers/notifications.service"; + +@Injectable() +export class PaymentReminderHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(PaymentReminderHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IPaymentNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createPaymentReminderNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "PaymentReminder"; + } +} + +@Injectable() +export class PaymentCancellationHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(PaymentCancellationHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IPaymentNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createPaymentCancellationNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "PaymentCancellation"; + } +} diff --git a/src/modules/notifications/handlers/service.handler.ts b/src/modules/notifications/handlers/service.handler.ts new file mode 100644 index 0000000..097288f --- /dev/null +++ b/src/modules/notifications/handlers/service.handler.ts @@ -0,0 +1,23 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { QueryRunner } from "typeorm"; + +import { BaseNotificationHandler } from "./base-notification.handler"; +import { ISubscriptionNotificationData } from "../interfaces/ISendNotificationData"; +import { NotificationsService } from "../providers/notifications.service"; + +@Injectable() +export class BlockServiceHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(BlockServiceHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: ISubscriptionNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createBlockServiceNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "BlockService"; + } +} diff --git a/src/modules/notifications/handlers/ticket.handler.ts b/src/modules/notifications/handlers/ticket.handler.ts new file mode 100644 index 0000000..b0c54bf --- /dev/null +++ b/src/modules/notifications/handlers/ticket.handler.ts @@ -0,0 +1,74 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { QueryRunner } from "typeorm"; + +import { BaseNotificationHandler } from "./base-notification.handler"; +import { INewTicketNotificationData, ITicketNotificationData } from "../interfaces/ISendNotificationData"; +import { NotificationsService } from "../providers/notifications.service"; + +@Injectable() +export class NewTicketHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(NewTicketHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: INewTicketNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createNewTicketGlobalNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "NewTicket"; + } +} + +@Injectable() +export class AnswerTicketHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(AnswerTicketHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: ITicketNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createAnswerTicketNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "AnswerTicket"; + } +} + +@Injectable() +export class CreateTicketHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(CreateTicketHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: ITicketNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createTicketNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "CreateTicket"; + } +} + +@Injectable() +export class AssignTicketHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(AssignTicketHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: ITicketNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createAssignTicketNotificationForAdmin(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "AssignTicket"; + } +} diff --git a/src/modules/notifications/handlers/user-login.handler.ts b/src/modules/notifications/handlers/user-login.handler.ts new file mode 100644 index 0000000..f57070b --- /dev/null +++ b/src/modules/notifications/handlers/user-login.handler.ts @@ -0,0 +1,24 @@ +import { Injectable, Logger } from "@nestjs/common"; + +import { IBaseNotificationData } from "../interfaces/ISendNotificationData"; +import { NotificationsService } from "../providers/notifications.service"; + +@Injectable() +export class UserLoginHandler { + private readonly logger = new Logger(UserLoginHandler.name); + + constructor(private readonly notificationsService: NotificationsService) {} + + async processUserLogin(recipientId: string, data: IBaseNotificationData): Promise { + try { + await this.notificationsService.createLoginNotification(recipientId, data); + this.logger.log(`User login notification sent successfully to ${recipientId}`); + } catch (error) { + this.logger.error( + `Failed to send user login notification: ${error instanceof Error ? error.message : "Unknown error"}`, + error instanceof Error ? error.stack : undefined, + ); + throw error; + } + } +} diff --git a/src/modules/notifications/handlers/wallet.handler.ts b/src/modules/notifications/handlers/wallet.handler.ts new file mode 100644 index 0000000..e8ea158 --- /dev/null +++ b/src/modules/notifications/handlers/wallet.handler.ts @@ -0,0 +1,40 @@ +import { Injectable, Logger } from "@nestjs/common"; +import { QueryRunner } from "typeorm"; + +import { BaseNotificationHandler } from "./base-notification.handler"; +import { IWalletNotificationData } from "../interfaces/ISendNotificationData"; +import { NotificationsService } from "../providers/notifications.service"; + +@Injectable() +export class WalletChargeHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(WalletChargeHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IWalletNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createWalletChargeNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "WalletCharge"; + } +} + +@Injectable() +export class WalletDeductionHandler extends BaseNotificationHandler { + protected readonly logger = new Logger(WalletDeductionHandler.name); + + constructor(private readonly notificationsService: NotificationsService) { + super(); + } + + protected async handle(recipientId: string, data: IWalletNotificationData, queryRunner: QueryRunner): Promise { + await this.notificationsService.createWalletDeductionNotification(recipientId, data, queryRunner); + } + + protected getHandlerName(): string { + return "WalletDeduction"; + } +} diff --git a/src/modules/notifications/notifications.module.ts b/src/modules/notifications/notifications.module.ts index 1981288..660d424 100755 --- a/src/modules/notifications/notifications.module.ts +++ b/src/modules/notifications/notifications.module.ts @@ -4,16 +4,42 @@ import { ConfigModule } from "@nestjs/config"; import { TypeOrmModule } from "@nestjs/typeorm"; import { NOTIFICATION } from "./constants"; -import { LoggerModule } from "../logger/logger.module"; -import { SettingModule } from "../settings/settings.module"; -import { UtilsModule } from "../utils/utils.module"; import { Notification } from "./entities/notification.entity"; +import { + AnnouncementHandler, + AnswerTicketHandler, + ApproveInvoiceHandler, + AssignTicketHandler, + BillInvoiceHandler, + BillInvoiceReminderHandler, + BlockServiceHandler, + BlogCommentHandler, + CreateInvoiceHandler, + CreateTicketHandler, + InvoiceOverdueHandler, + NewCriticismHandler, + NewCustomerHandler, + NewSubscriptionHandler, + NewTicketHandler, + NotificationHandlerFactory, + OtpHandler, + PaymentCancellationHandler, + PaymentReminderHandler, + RecurringInvoiceHandler, + ServiceReviewHandler, + UserLoginHandler, + WalletChargeHandler, + WalletDeductionHandler, +} from "./handlers"; import { NotificationController } from "./notifications.controller"; +import { LoggerModule } from "../logger/logger.module"; import { NotificationsService } from "./providers/notifications.service"; import { NotificationProcessor } from "./queue/notification.processor"; import { NotificationQueue } from "./queue/notification.queue"; import { NotificationRepository } from "./repositories/notifications.repository"; import { NotificationSetting } from "../settings/entities/notification-setting.entity"; +import { SettingModule } from "../settings/settings.module"; +import { UtilsModule } from "../utils/utils.module"; @Module({ imports: [ @@ -36,7 +62,37 @@ import { NotificationSetting } from "../settings/entities/notification-setting.e UtilsModule, SettingModule, ], - providers: [NotificationRepository, NotificationsService, NotificationQueue, NotificationProcessor], + providers: [ + NotificationRepository, + NotificationsService, + NotificationQueue, + NotificationProcessor, + // notification handlers + NotificationHandlerFactory, + OtpHandler, + UserLoginHandler, + WalletChargeHandler, + WalletDeductionHandler, + CreateInvoiceHandler, + BillInvoiceReminderHandler, + BillInvoiceHandler, + ApproveInvoiceHandler, + InvoiceOverdueHandler, + RecurringInvoiceHandler, + BlogCommentHandler, + ServiceReviewHandler, + NewCustomerHandler, + NewSubscriptionHandler, + NewCriticismHandler, + NewTicketHandler, + AnnouncementHandler, + AnswerTicketHandler, + CreateTicketHandler, + AssignTicketHandler, + BlockServiceHandler, + PaymentReminderHandler, + PaymentCancellationHandler, + ], controllers: [NotificationController], exports: [NotificationRepository, NotificationsService, NotificationQueue], }) diff --git a/src/modules/notifications/providers/notifications.service.ts b/src/modules/notifications/providers/notifications.service.ts index c5635ba..82bf2dc 100755 --- a/src/modules/notifications/providers/notifications.service.ts +++ b/src/modules/notifications/providers/notifications.service.ts @@ -4,7 +4,7 @@ import { DataSource, QueryRunner } from "typeorm"; import { CommonMessage, NotificationMessage } from "../../../common/enums/message.enum"; import { NotifType } from "../../settings/enums/notif-settings.enum"; import { UserSettingsService } from "../../settings/providers/user-settings.service"; -import { EmailService } from "../../utils/providers/email.service"; +// import { EmailService } from "../../utils/providers/email.service"; import { dateFormat, numberFormat } from "../../utils/providers/international.utils"; import { SmsService } from "../../utils/providers/sms.service"; import { CreateNotificationDto } from "../DTO/create-notification.dto"; @@ -35,7 +35,7 @@ export class NotificationsService { private readonly notificationRepository: NotificationRepository, private readonly smsService: SmsService, private readonly userSettingsService: UserSettingsService, - private readonly emailService: EmailService, + // private readonly emailService: EmailService, ) {} //************************ */ @@ -133,7 +133,7 @@ export class NotificationsService { const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.USER_LOGIN, recipientId); if (isActive) { await this.smsService.sendLoginSms(data.userPhone, loginDate); - if (data.userEmail) await this.emailService.sendLoginEmail(data); + // if (data.userEmail) await this.emailService.sendLoginEmail(data); } return this.createNotification({ title: NotificationMessage.LOGIN, type: NotifType.USER_LOGIN, message, recipientId }); } @@ -156,7 +156,7 @@ export class NotificationsService { dateFormat(data.createDate), dateFormat(data.dueDate), ); - if (data.userEmail) await this.emailService.sendInvoiceEmail(data); + // if (data.userEmail) await this.emailService.sendInvoiceEmail(data); } return this.createNotification( { title: NotificationMessage.INVOICE_CREATION, type: NotifType.CREATE_INVOICE, message, recipientId }, @@ -175,7 +175,7 @@ export class NotificationsService { ); if (isActive) { await this.smsService.sendAnnouncementSms(data.userPhone, data.title, data.description, dateFormat(data.date)); - if (data.userEmail) await this.emailService.sendAnnouncementEmail(data); + // if (data.userEmail) await this.emailService.sendAnnouncementEmail(data); } return this.createNotification( { title: NotificationMessage.ANNOUNCEMENT, type: NotifType.ANNOUNCEMENT, message, recipientId }, @@ -195,7 +195,7 @@ export class NotificationsService { ); if (isActive) { await this.smsService.sendWalletChargeSms(data.userPhone, data.amount, data.balance, dateFormat(data.date)); - if (data.userEmail) await this.emailService.sendWalletEmail(data); + // if (data.userEmail) await this.emailService.sendWalletEmail(data); } return this.createNotification( { title: NotificationMessage.WALLET_CHARGE, type: NotifType.WALLET_CHARGE, message, recipientId }, @@ -215,7 +215,7 @@ export class NotificationsService { ); if (isActive) { await this.smsService.sendWalletDeductionSms(data.userPhone, data.amount, data.balance, data.reason, dateFormat(data.date)); - if (data.userEmail) await this.emailService.sendWalletEmail(data); + // if (data.userEmail) await this.emailService.sendWalletEmail(data); } return this.createNotification( { title: NotificationMessage.WALLET_DEDUCTION, type: NotifType.WALLET_DEDUCTION, message, recipientId }, @@ -235,7 +235,7 @@ export class NotificationsService { ); if (isActive) { await this.smsService.sendAnswerTicketSms(data.userPhone, data.ticketId, data.subject); - if (data.userEmail) await this.emailService.sendTicketEmail(data); + // if (data.userEmail) await this.emailService.sendTicketEmail(data); } return this.createNotification( { title: NotificationMessage.ANSWER_TICKET, type: NotifType.ANSWER_TICKET, message, recipientId }, @@ -255,7 +255,7 @@ export class NotificationsService { ); if (isActive) { await this.smsService.sendCreateTicketSms(data.userPhone, data.ticketId, data.subject, dateFormat(data.date)); - if (data.userEmail) await this.emailService.sendTicketEmail(data); + // if (data.userEmail) await this.emailService.sendTicketEmail(data); } return this.createNotification( { title: NotificationMessage.CREATE_TICKET, type: NotifType.CREATE_TICKET, message, recipientId }, @@ -273,7 +273,7 @@ export class NotificationsService { ); if (isActive) { await this.smsService.sendTicketAssignedToAdminSms(data.userPhone, data.ticketId, data.subject, data.user!); - if (data.userEmail) await this.emailService.sendTicketEmail(data); + // if (data.userEmail) await this.emailService.sendTicketEmail(data); } return this.createNotification( { title: NotificationMessage.ASSIGN_TICKET, type: NotifType.ASSIGN_TICKET, message, recipientId }, @@ -289,7 +289,7 @@ export class NotificationsService { const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.BILL_INVOICE_REMINDER, recipientId); if (isActive) { await this.smsService.sendInvoicePaymentReminderSms(data.userPhone, data.invoiceId, data.price, dateFormat(data.dueDate)); - if (data.userEmail) await this.emailService.sendInvoiceEmail(data); + // if (data.userEmail) await this.emailService.sendInvoiceEmail(data); } return this.createNotification( { title: NotificationMessage.BILL_INVOICE_REMINDER, type: NotifType.BILL_INVOICE_REMINDER, message, recipientId }, @@ -305,7 +305,7 @@ export class NotificationsService { const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.BILL_INVOICE, recipientId); if (isActive) { await this.smsService.sendInvoicePaidSms(data.userPhone, data.invoiceId, data.price, dateFormat(data.paidAt!)); - if (data.userEmail) await this.emailService.sendInvoiceEmail(data); + // if (data.userEmail) await this.emailService.sendInvoiceEmail(data); } return this.createNotification( { title: NotificationMessage.BILL_INVOICE, type: NotifType.BILL_INVOICE, message, recipientId }, @@ -320,7 +320,7 @@ export class NotificationsService { const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.APPROVE_INVOICE, recipientId); if (isActive) { await this.smsService.sendInvoiceApprovedSms(data.userPhone, data.invoiceId, data.price, dateFormat(data.dueDate)); - if (data.userEmail) await this.emailService.sendInvoiceEmail(data); + // if (data.userEmail) await this.emailService.sendInvoiceEmail(data); } return this.createNotification( { title: NotificationMessage.APPROVED_INVOICE, type: NotifType.BILL_INVOICE, message, recipientId }, @@ -340,7 +340,7 @@ export class NotificationsService { const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.INVOICE_OVERDUE, recipientId); if (isActive) { await this.smsService.sendInvoiceOverdueSms(data.userPhone, data.invoiceId, data.price, data.lateFee || 0, dateFormat(data.dueDate)); - if (data.userEmail) await this.emailService.sendInvoiceEmail(data); + // if (data.userEmail) await this.emailService.sendInvoiceEmail(data); } return this.createNotification( { title: NotificationMessage.INVOICE_OVERDUE, type: NotifType.INVOICE_OVERDUE, message, recipientId }, @@ -356,7 +356,7 @@ export class NotificationsService { const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.RECURRING_INVOICE, recipientId); if (isActive) { await this.smsService.sendRecurringInvoiceDraftSms(data.userPhone, data.invoiceId, data.price); - if (data.userEmail) await this.emailService.sendInvoiceEmail(data); + // if (data.userEmail) await this.emailService.sendInvoiceEmail(data); } return this.createNotification( { title: NotificationMessage.RECURRING_INVOICE, type: NotifType.RECURRING_INVOICE, message, recipientId }, @@ -371,14 +371,14 @@ export class NotificationsService { const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.BLOCK_SERVICE, recipientId); if (isActive) { await this.smsService.sendBlockServiceSms(data.userPhone, data.invoiceId, data.planName); - if (data.userEmail) - await this.emailService.sendAnnouncementEmail({ - userEmail: data.userEmail, - userPhone: data.userPhone, - title: NotificationMessage.BLOCK_SERVICE, - description: message, - date: new Date(), - }); + // if (data.userEmail) + // await this.emailService.sendAnnouncementEmail({ + // userEmail: data.userEmail, + // userPhone: data.userPhone, + // title: NotificationMessage.BLOCK_SERVICE, + // description: message, + // date: new Date(), + // }); } return this.createNotification( { title: NotificationMessage.BLOCK_SERVICE, type: NotifType.BLOCK_SERVICE, message, recipientId }, @@ -399,7 +399,7 @@ export class NotificationsService { if (isActive) { await this.smsService.sendPaymentReminderSms(data.userPhone, data.amount); - if (data.userEmail) await this.emailService.sendPaymentReminderEmail(data); + // if (data.userEmail) await this.emailService.sendPaymentReminderEmail(data); } return this.createNotification( @@ -425,7 +425,7 @@ export class NotificationsService { if (isActive) { await this.smsService.sendPaymentCancellationSms(data.userPhone, data.amount); - if (data.userEmail) await this.emailService.sendPaymentCancellationEmail(data); + // if (data.userEmail) await this.emailService.sendPaymentCancellationEmail(data); } return this.createNotification( @@ -448,13 +448,13 @@ export class NotificationsService { const message = NotificationMessage.NEW_BLOG_COMMENT_MESSAGE.replace("[blogTitle]", data.blogTitle); await this.smsService.sendBlogNewCommentSms(data.userPhone, data.blogTitle, data.fullName); - if (data.userEmail) { - await this.emailService.sendAdminNotificationEmail({ - userEmail: data.userEmail, - title: NotificationMessage.NEW_BLOG_COMMENT, - message, - }); - } + // if (data.userEmail) { + // await this.emailService.sendAdminNotificationEmail({ + // userEmail: data.userEmail, + // title: NotificationMessage.NEW_BLOG_COMMENT, + // message, + // }); + // } return this.createNotification( { title: NotificationMessage.NEW_BLOG_COMMENT, type: NotifType.NEW_BLOG_COMMENT, message, recipientId }, queryRunner, @@ -465,13 +465,13 @@ export class NotificationsService { const message = NotificationMessage.NEW_SERVICE_REVIEW_MESSAGE.replace("[serviceName]", data.serviceName); await this.smsService.sendNewServiceReviewSms(data.userPhone, data.serviceName, data.fullName); - if (data.userEmail) { - await this.emailService.sendAdminNotificationEmail({ - userEmail: data.userEmail, - title: NotificationMessage.NEW_SERVICE_REVIEW, - message, - }); - } + // if (data.userEmail) { + // await this.emailService.sendAdminNotificationEmail({ + // userEmail: data.userEmail, + // title: NotificationMessage.NEW_SERVICE_REVIEW, + // message, + // }); + // } return this.createNotification( { title: NotificationMessage.NEW_SERVICE_REVIEW, type: NotifType.NEW_SERVICE_REVIEW, message, recipientId }, @@ -484,13 +484,13 @@ export class NotificationsService { const message = NotificationMessage.NEW_CUSTOMER_MESSAGE.replace("[fullName]", data.fullName); await this.smsService.sendNewCustomerSms(data.userPhone, data.mobile, data.fullName); - if (data.userEmail) { - await this.emailService.sendAdminNotificationEmail({ - userEmail: data.userEmail, - title: NotificationMessage.NEW_CUSTOMER, - message, - }); - } + // if (data.userEmail) { + // await this.emailService.sendAdminNotificationEmail({ + // userEmail: data.userEmail, + // title: NotificationMessage.NEW_CUSTOMER, + // message, + // }); + // } return this.createNotification( { title: NotificationMessage.NEW_CUSTOMER, type: NotifType.NEW_CUSTOMER, message, recipientId }, queryRunner, @@ -502,14 +502,14 @@ export class NotificationsService { const message = NotificationMessage.NEW_SUBSCRIPTION_MESSAGE.replace("[serviceName]", data.serviceName); await this.smsService.sendNewSubscriptionSms(data.userPhone, data.serviceName, data.fullName, dateFormat(data.date)); - if (data.userEmail) { - await this.emailService.sendAdminNotificationEmail({ - userEmail: data.userEmail, - title: NotificationMessage.NEW_SUBSCRIPTION, - fullName: data.fullName, - message, - }); - } + // if (data.userEmail) { + // await this.emailService.sendAdminNotificationEmail({ + // userEmail: data.userEmail, + // title: NotificationMessage.NEW_SUBSCRIPTION, + // fullName: data.fullName, + // message, + // }); + // } return this.createNotification( { title: NotificationMessage.NEW_SUBSCRIPTION, type: NotifType.NEW_SUBSCRIPTION, message, recipientId }, queryRunner, @@ -521,13 +521,13 @@ export class NotificationsService { const message = NotificationMessage.NEW_TICKET_MESSAGE.replace("[ticketSubject]", data.ticketSubject); await this.smsService.sendGlobalNewTicketSms(data.userPhone, data.ticketId, data.ticketSubject, data.fullName); - if (data.userEmail) { - await this.emailService.sendAdminNotificationEmail({ - userEmail: data.userEmail, - title: NotificationMessage.NEW_TICKET, - message, - }); - } + // if (data.userEmail) { + // await this.emailService.sendAdminNotificationEmail({ + // userEmail: data.userEmail, + // title: NotificationMessage.NEW_TICKET, + // message, + // }); + // } return this.createNotification( { title: NotificationMessage.NEW_TICKET, type: NotifType.NEW_TICKET, message, recipientId }, queryRunner, @@ -539,13 +539,13 @@ export class NotificationsService { const message = NotificationMessage.NEW_CRITICISM_MESSAGE.replace("[title]", data.title); await this.smsService.sendNewCriticismSms(data.userPhone, data.title, data.fullName); - if (data.userEmail) { - await this.emailService.sendAdminNotificationEmail({ - userEmail: data.userEmail, - title: NotificationMessage.NEW_CRITICISM, - message, - }); - } + // if (data.userEmail) { + // await this.emailService.sendAdminNotificationEmail({ + // userEmail: data.userEmail, + // title: NotificationMessage.NEW_CRITICISM, + // message, + // }); + // } return this.createNotification( { title: NotificationMessage.NEW_CRITICISM, type: NotifType.NEW_CRITICISM, message, recipientId }, queryRunner, diff --git a/src/modules/notifications/queue/notification.processor.ts b/src/modules/notifications/queue/notification.processor.ts index c63bc77..7553a78 100644 --- a/src/modules/notifications/queue/notification.processor.ts +++ b/src/modules/notifications/queue/notification.processor.ts @@ -6,24 +6,12 @@ import { DataSource } from "typeorm"; import { WorkerProcessor } from "../../../common/queues/worker.processor"; import { LoggerService } from "../../logger/logger.service"; import { NotifType } from "../../settings/enums/notif-settings.enum"; -import { SmsService } from "../../utils/providers/sms.service"; import { NOTIFICATION } from "../constants"; +import { NotificationHandlerFactory } from "../handlers/notification-handler.factory"; +import { OtpHandler } from "../handlers/otp.handler"; +import { UserLoginHandler } from "../handlers/user-login.handler"; import { NotificationJobData, OtpJobData } from "../interfaces/INotification-job-data"; -import { - IAnnouncementNotificationData, - IBlogCommentNotificationData, - IInvoiceNotificationData, - INewCriticismNotificationData, - INewCustomerNotificationData, - INewSubscriptionNotificationData, - INewTicketNotificationData, - IPaymentNotificationData, - IServiceReviewNotificationData, - ISubscriptionNotificationData, - ITicketNotificationData, - IWalletNotificationData, -} from "../interfaces/ISendNotificationData"; -import { NotificationsService } from "../providers/notifications.service"; +import { IBaseNotificationData } from "../interfaces/ISendNotificationData"; @Processor(NOTIFICATION.QUEUE_NAME, { concurrency: NOTIFICATION.SEND_NOTIFICATION_JOB_CONCURRENCY, @@ -35,145 +23,59 @@ export class NotificationProcessor extends WorkerProcessor { protected readonly logger = new Logger(NotificationProcessor.name); constructor( - private readonly notificationsService: NotificationsService, private readonly dataSource: DataSource, protected readonly loggerService: LoggerService, - private readonly smsService: SmsService, + private readonly notificationHandlerFactory: NotificationHandlerFactory, + private readonly otpHandler: OtpHandler, + private readonly userLoginHandler: UserLoginHandler, ) { super(); } async process(job: Job, token?: string) { this.logger.log(`Processing notification job: ${job.name} ${token ? `with token: ${token}` : ""}`); - const queryRunner = this.dataSource.createQueryRunner(); - let heartbeat: NodeJS.Timeout | undefined; try { - await queryRunner.connect(); - await queryRunner.startTransaction(); - heartbeat = setInterval(() => job.updateProgress(50), 10000); - if (job.name === NOTIFICATION.SEND_LOGIN_OTP_JOB_NAME) { - const { phone, code } = job.data as OtpJobData; - await this.smsService.sendSmsVerifyCode(phone, code); - // + await this.otpHandler.processOtp(job.data as OtpJobData); } else if (job.name === NOTIFICATION.SEND_NOTIFICATION_JOB_NAME) { const { type, recipientId, data } = job.data as NotificationJobData; - switch (type) { - // Admin Notifications - case NotifType.NEW_BLOG_COMMENT: - await this.notificationsService.createNewBlogCommentNotification( - recipientId, - data as IBlogCommentNotificationData, - queryRunner, - ); - break; - case NotifType.NEW_SERVICE_REVIEW: - await this.notificationsService.createNewServiceReviewNotification( - recipientId, - data as IServiceReviewNotificationData, - queryRunner, - ); - break; - case NotifType.NEW_CUSTOMER: - await this.notificationsService.createNewCustomerNotification(recipientId, data as INewCustomerNotificationData, queryRunner); - break; - case NotifType.NEW_SUBSCRIPTION: - await this.notificationsService.createNewSubscriptionNotification( - recipientId, - data as INewSubscriptionNotificationData, - queryRunner, - ); - break; - case NotifType.NEW_CRITICISM: - await this.notificationsService.createNewCriticismNotification(recipientId, data as INewCriticismNotificationData, queryRunner); - break; - case NotifType.NEW_TICKET: - await this.notificationsService.createNewTicketGlobalNotification(recipientId, data as INewTicketNotificationData, queryRunner); - break; - // User Notifications - case NotifType.USER_LOGIN: - await this.notificationsService.createLoginNotification(recipientId, data); - break; - case NotifType.ANNOUNCEMENT: - await this.notificationsService.createAnnouncementNotification(recipientId, data as IAnnouncementNotificationData, queryRunner); - break; - // Wallet Notifications - case NotifType.WALLET_CHARGE: - await this.notificationsService.createWalletChargeNotification(recipientId, data as IWalletNotificationData, queryRunner); - break; - case NotifType.WALLET_DEDUCTION: - await this.notificationsService.createWalletDeductionNotification(recipientId, data as IWalletNotificationData, queryRunner); - break; - // Ticket Notifications - case NotifType.ANSWER_TICKET: - await this.notificationsService.createAnswerTicketNotification(recipientId, data as ITicketNotificationData, queryRunner); - break; - case NotifType.CREATE_TICKET: - await this.notificationsService.createTicketNotification(recipientId, data as ITicketNotificationData, queryRunner); - break; - case NotifType.ASSIGN_TICKET: - await this.notificationsService.createAssignTicketNotificationForAdmin( - recipientId, - data as ITicketNotificationData, - queryRunner, - ); - break; - // Invoice Notifications - case NotifType.CREATE_INVOICE: - await this.notificationsService.createInvoiceCreationNotification(recipientId, data as IInvoiceNotificationData, queryRunner); - break; - case NotifType.BILL_INVOICE_REMINDER: - await this.notificationsService.createBillInvoiceReminderNotification( - recipientId, - data as IInvoiceNotificationData, - queryRunner, - ); - break; - case NotifType.BILL_INVOICE: - await this.notificationsService.createBillInvoiceNotification(recipientId, data as IInvoiceNotificationData, queryRunner); - break; - case NotifType.APPROVE_INVOICE: - await this.notificationsService.createApprovedInvoiceNotification(recipientId, data as IInvoiceNotificationData, queryRunner); - break; - case NotifType.INVOICE_OVERDUE: - await this.notificationsService.createInvoiceOverdueNotification(recipientId, data as IInvoiceNotificationData, queryRunner); - break; - case NotifType.RECURRING_INVOICE: - await this.notificationsService.createRecurringInvoiceNotification(recipientId, data as IInvoiceNotificationData, queryRunner); - break; - // Service Notifications - case NotifType.BLOCK_SERVICE: - await this.notificationsService.createBlockServiceNotification(recipientId, data as ISubscriptionNotificationData, queryRunner); - break; - // Payment Notifications - case NotifType.PAYMENT_REMINDER: - await this.notificationsService.createPaymentReminderNotification(recipientId, data as IPaymentNotificationData, queryRunner); - break; - case NotifType.PAYMENT_CANCELLATION: - await this.notificationsService.createPaymentCancellationNotification( - recipientId, - data as IPaymentNotificationData, - queryRunner, - ); - break; - default: - this.logger.warn(`Unknown notification type: ${type}`); + + // Handle special cases that don't use transactions + if (type === NotifType.USER_LOGIN) { + await this.userLoginHandler.processUserLogin(recipientId, data as IBaseNotificationData); + } else { + // Handle notifications that require transactions + await this.processWithTransaction(type, recipientId, data); } } else { this.logger.warn(`Unknown job name: ${job.name}`); } - await queryRunner.commitTransaction(); + return true; } catch (error) { this.logger.error( `Failed to process notification: ${error instanceof Error ? error.message : "Unknown error"}`, error instanceof Error ? error.stack : undefined, ); + throw error; + } + } + + private async processWithTransaction(type: NotifType, recipientId: string, data: any): Promise { + const queryRunner = this.dataSource.createQueryRunner(); + + try { + await queryRunner.connect(); + await queryRunner.startTransaction(); + + await this.notificationHandlerFactory.processNotification(type, recipientId, data, queryRunner); + + await queryRunner.commitTransaction(); + } catch (error) { await queryRunner.rollbackTransaction(); throw error; } finally { - if (heartbeat) clearInterval(heartbeat); await queryRunner.release(); } } diff --git a/src/modules/subscriptions/constants/index.ts b/src/modules/subscriptions/constants/index.ts index cd38ef6..69f6f17 100644 --- a/src/modules/subscriptions/constants/index.ts +++ b/src/modules/subscriptions/constants/index.ts @@ -1,5 +1,5 @@ export const SUBSCRIPTIONS = Object.freeze({ - PROVISIONING_QUEUE_PREFIX: "provisioning", + PROVISIONING_QUEUE_PREFIX: "subs", PROVISIONING_QUEUE_NAME: "provisioning", PROVISIONING_JOB_NAME: "business.created", diff --git a/src/modules/subscriptions/providers/subscriptions.service.ts b/src/modules/subscriptions/providers/subscriptions.service.ts index a13872c..6ebc9f8 100755 --- a/src/modules/subscriptions/providers/subscriptions.service.ts +++ b/src/modules/subscriptions/providers/subscriptions.service.ts @@ -1,5 +1,5 @@ import { InjectQueue } from "@nestjs/bullmq"; -import { BadRequestException, ForbiddenException, Injectable } from "@nestjs/common"; +import { BadRequestException, ForbiddenException, Injectable, Logger } from "@nestjs/common"; import { Queue } from "bullmq"; import dayjs from "dayjs"; import Decimal from "decimal.js"; @@ -28,6 +28,8 @@ import { UserSubscriptionsRepository } from "../repositories/user-subscriptions. @Injectable() export class SubscriptionsService { + private readonly logger: Logger = new Logger(SubscriptionsService.name); + constructor( @InjectQueue(SUBSCRIPTIONS.PROVISIONING_QUEUE_NAME) private readonly provisioningQueue: Queue, private readonly subscriptionsPlanRepository: SubscriptionsPlanRepository, @@ -337,6 +339,7 @@ export class SubscriptionsService { //************************************ */ async addProvisioningJob(userSubscription: UserSubscription, plan: SubscriptionPlan, user: User) { + this.logger.debug(`Adding provisioning job for user ${user.id} and subscription ${userSubscription.id}`); await this.provisioningQueue.add( SUBSCRIPTIONS.PROVISIONING_JOB_NAME, {