feat: fix

This commit is contained in:
mahyargdz
2025-07-12 14:54:16 +03:30
parent 633b23cf73
commit 5fe82bb661
18 changed files with 815 additions and 203 deletions
@@ -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<IServiceReviewNotificationData> {
protected readonly logger = new Logger(ServiceReviewHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IServiceReviewNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createNewServiceReviewNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "ServiceReview";
}
}
@Injectable()
export class NewCustomerHandler extends BaseNotificationHandler<INewCustomerNotificationData> {
protected readonly logger = new Logger(NewCustomerHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: INewCustomerNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createNewCustomerNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "NewCustomer";
}
}
@Injectable()
export class NewSubscriptionHandler extends BaseNotificationHandler<INewSubscriptionNotificationData> {
protected readonly logger = new Logger(NewSubscriptionHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: INewSubscriptionNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createNewSubscriptionNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "NewSubscription";
}
}
@Injectable()
export class NewCriticismHandler extends BaseNotificationHandler<INewCriticismNotificationData> {
protected readonly logger = new Logger(NewCriticismHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: INewCriticismNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createNewCriticismNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "NewCriticism";
}
}
@@ -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<IAnnouncementNotificationData> {
protected readonly logger = new Logger(AnnouncementHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IAnnouncementNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createAnnouncementNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "Announcement";
}
}
@@ -0,0 +1,31 @@
import { Logger } from "@nestjs/common";
import { QueryRunner } from "typeorm";
export abstract class BaseNotificationHandler<TData = unknown> {
protected abstract readonly logger: Logger;
/**
* Process the notification with automatic transaction management
*/
async processWithTransaction(recipientId: string, data: TData, queryRunner: QueryRunner): Promise<void> {
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<void>;
/**
* Get the handler name for logging purposes
*/
protected abstract getHandlerName(): string;
}
@@ -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<IBlogCommentNotificationData> {
protected readonly logger = new Logger(BlogCommentHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IBlogCommentNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createNewBlogCommentNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "BlogComment";
}
}
@@ -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";
@@ -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<IInvoiceNotificationData> {
protected readonly logger = new Logger(CreateInvoiceHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createInvoiceCreationNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "CreateInvoice";
}
}
@Injectable()
export class BillInvoiceReminderHandler extends BaseNotificationHandler<IInvoiceNotificationData> {
protected readonly logger = new Logger(BillInvoiceReminderHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createBillInvoiceReminderNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "BillInvoiceReminder";
}
}
@Injectable()
export class BillInvoiceHandler extends BaseNotificationHandler<IInvoiceNotificationData> {
protected readonly logger = new Logger(BillInvoiceHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createBillInvoiceNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "BillInvoice";
}
}
@Injectable()
export class ApproveInvoiceHandler extends BaseNotificationHandler<IInvoiceNotificationData> {
protected readonly logger = new Logger(ApproveInvoiceHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createApprovedInvoiceNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "ApproveInvoice";
}
}
@Injectable()
export class InvoiceOverdueHandler extends BaseNotificationHandler<IInvoiceNotificationData> {
protected readonly logger = new Logger(InvoiceOverdueHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createInvoiceOverdueNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "InvoiceOverdue";
}
}
@Injectable()
export class RecurringInvoiceHandler extends BaseNotificationHandler<IInvoiceNotificationData> {
protected readonly logger = new Logger(RecurringInvoiceHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IInvoiceNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createRecurringInvoiceNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "RecurringInvoice";
}
}
@@ -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<NotifType, BaseNotificationHandler> = 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<void> {
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);
}
}
@@ -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<void> {
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;
}
}
}
@@ -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<IPaymentNotificationData> {
protected readonly logger = new Logger(PaymentReminderHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IPaymentNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createPaymentReminderNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "PaymentReminder";
}
}
@Injectable()
export class PaymentCancellationHandler extends BaseNotificationHandler<IPaymentNotificationData> {
protected readonly logger = new Logger(PaymentCancellationHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IPaymentNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createPaymentCancellationNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "PaymentCancellation";
}
}
@@ -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<ISubscriptionNotificationData> {
protected readonly logger = new Logger(BlockServiceHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: ISubscriptionNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createBlockServiceNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "BlockService";
}
}
@@ -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<INewTicketNotificationData> {
protected readonly logger = new Logger(NewTicketHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: INewTicketNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createNewTicketGlobalNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "NewTicket";
}
}
@Injectable()
export class AnswerTicketHandler extends BaseNotificationHandler<ITicketNotificationData> {
protected readonly logger = new Logger(AnswerTicketHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: ITicketNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createAnswerTicketNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "AnswerTicket";
}
}
@Injectable()
export class CreateTicketHandler extends BaseNotificationHandler<ITicketNotificationData> {
protected readonly logger = new Logger(CreateTicketHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: ITicketNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createTicketNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "CreateTicket";
}
}
@Injectable()
export class AssignTicketHandler extends BaseNotificationHandler<ITicketNotificationData> {
protected readonly logger = new Logger(AssignTicketHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: ITicketNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createAssignTicketNotificationForAdmin(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "AssignTicket";
}
}
@@ -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<void> {
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;
}
}
}
@@ -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<IWalletNotificationData> {
protected readonly logger = new Logger(WalletChargeHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IWalletNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createWalletChargeNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "WalletCharge";
}
}
@Injectable()
export class WalletDeductionHandler extends BaseNotificationHandler<IWalletNotificationData> {
protected readonly logger = new Logger(WalletDeductionHandler.name);
constructor(private readonly notificationsService: NotificationsService) {
super();
}
protected async handle(recipientId: string, data: IWalletNotificationData, queryRunner: QueryRunner): Promise<void> {
await this.notificationsService.createWalletDeductionNotification(recipientId, data, queryRunner);
}
protected getHandlerName(): string {
return "WalletDeduction";
}
}
@@ -4,16 +4,42 @@ import { ConfigModule } from "@nestjs/config";
import { TypeOrmModule } from "@nestjs/typeorm"; import { TypeOrmModule } from "@nestjs/typeorm";
import { NOTIFICATION } from "./constants"; 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 { 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 { NotificationController } from "./notifications.controller";
import { LoggerModule } from "../logger/logger.module";
import { NotificationsService } from "./providers/notifications.service"; import { NotificationsService } from "./providers/notifications.service";
import { NotificationProcessor } from "./queue/notification.processor"; import { NotificationProcessor } from "./queue/notification.processor";
import { NotificationQueue } from "./queue/notification.queue"; import { NotificationQueue } from "./queue/notification.queue";
import { NotificationRepository } from "./repositories/notifications.repository"; import { NotificationRepository } from "./repositories/notifications.repository";
import { NotificationSetting } from "../settings/entities/notification-setting.entity"; import { NotificationSetting } from "../settings/entities/notification-setting.entity";
import { SettingModule } from "../settings/settings.module";
import { UtilsModule } from "../utils/utils.module";
@Module({ @Module({
imports: [ imports: [
@@ -36,7 +62,37 @@ import { NotificationSetting } from "../settings/entities/notification-setting.e
UtilsModule, UtilsModule,
SettingModule, 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], controllers: [NotificationController],
exports: [NotificationRepository, NotificationsService, NotificationQueue], exports: [NotificationRepository, NotificationsService, NotificationQueue],
}) })
@@ -4,7 +4,7 @@ import { DataSource, QueryRunner } from "typeorm";
import { CommonMessage, NotificationMessage } from "../../../common/enums/message.enum"; import { CommonMessage, NotificationMessage } from "../../../common/enums/message.enum";
import { NotifType } from "../../settings/enums/notif-settings.enum"; import { NotifType } from "../../settings/enums/notif-settings.enum";
import { UserSettingsService } from "../../settings/providers/user-settings.service"; 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 { dateFormat, numberFormat } from "../../utils/providers/international.utils";
import { SmsService } from "../../utils/providers/sms.service"; import { SmsService } from "../../utils/providers/sms.service";
import { CreateNotificationDto } from "../DTO/create-notification.dto"; import { CreateNotificationDto } from "../DTO/create-notification.dto";
@@ -35,7 +35,7 @@ export class NotificationsService {
private readonly notificationRepository: NotificationRepository, private readonly notificationRepository: NotificationRepository,
private readonly smsService: SmsService, private readonly smsService: SmsService,
private readonly userSettingsService: UserSettingsService, 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); const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.USER_LOGIN, recipientId);
if (isActive) { if (isActive) {
await this.smsService.sendLoginSms(data.userPhone, loginDate); 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 }); return this.createNotification({ title: NotificationMessage.LOGIN, type: NotifType.USER_LOGIN, message, recipientId });
} }
@@ -156,7 +156,7 @@ export class NotificationsService {
dateFormat(data.createDate), dateFormat(data.createDate),
dateFormat(data.dueDate), dateFormat(data.dueDate),
); );
if (data.userEmail) await this.emailService.sendInvoiceEmail(data); // if (data.userEmail) await this.emailService.sendInvoiceEmail(data);
} }
return this.createNotification( return this.createNotification(
{ title: NotificationMessage.INVOICE_CREATION, type: NotifType.CREATE_INVOICE, message, recipientId }, { title: NotificationMessage.INVOICE_CREATION, type: NotifType.CREATE_INVOICE, message, recipientId },
@@ -175,7 +175,7 @@ export class NotificationsService {
); );
if (isActive) { if (isActive) {
await this.smsService.sendAnnouncementSms(data.userPhone, data.title, data.description, dateFormat(data.date)); 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( return this.createNotification(
{ title: NotificationMessage.ANNOUNCEMENT, type: NotifType.ANNOUNCEMENT, message, recipientId }, { title: NotificationMessage.ANNOUNCEMENT, type: NotifType.ANNOUNCEMENT, message, recipientId },
@@ -195,7 +195,7 @@ export class NotificationsService {
); );
if (isActive) { if (isActive) {
await this.smsService.sendWalletChargeSms(data.userPhone, data.amount, data.balance, dateFormat(data.date)); 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( return this.createNotification(
{ title: NotificationMessage.WALLET_CHARGE, type: NotifType.WALLET_CHARGE, message, recipientId }, { title: NotificationMessage.WALLET_CHARGE, type: NotifType.WALLET_CHARGE, message, recipientId },
@@ -215,7 +215,7 @@ export class NotificationsService {
); );
if (isActive) { if (isActive) {
await this.smsService.sendWalletDeductionSms(data.userPhone, data.amount, data.balance, data.reason, dateFormat(data.date)); 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( return this.createNotification(
{ title: NotificationMessage.WALLET_DEDUCTION, type: NotifType.WALLET_DEDUCTION, message, recipientId }, { title: NotificationMessage.WALLET_DEDUCTION, type: NotifType.WALLET_DEDUCTION, message, recipientId },
@@ -235,7 +235,7 @@ export class NotificationsService {
); );
if (isActive) { if (isActive) {
await this.smsService.sendAnswerTicketSms(data.userPhone, data.ticketId, data.subject); 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( return this.createNotification(
{ title: NotificationMessage.ANSWER_TICKET, type: NotifType.ANSWER_TICKET, message, recipientId }, { title: NotificationMessage.ANSWER_TICKET, type: NotifType.ANSWER_TICKET, message, recipientId },
@@ -255,7 +255,7 @@ export class NotificationsService {
); );
if (isActive) { if (isActive) {
await this.smsService.sendCreateTicketSms(data.userPhone, data.ticketId, data.subject, dateFormat(data.date)); 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( return this.createNotification(
{ title: NotificationMessage.CREATE_TICKET, type: NotifType.CREATE_TICKET, message, recipientId }, { title: NotificationMessage.CREATE_TICKET, type: NotifType.CREATE_TICKET, message, recipientId },
@@ -273,7 +273,7 @@ export class NotificationsService {
); );
if (isActive) { if (isActive) {
await this.smsService.sendTicketAssignedToAdminSms(data.userPhone, data.ticketId, data.subject, data.user!); 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( return this.createNotification(
{ title: NotificationMessage.ASSIGN_TICKET, type: NotifType.ASSIGN_TICKET, message, recipientId }, { 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); const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.BILL_INVOICE_REMINDER, recipientId);
if (isActive) { if (isActive) {
await this.smsService.sendInvoicePaymentReminderSms(data.userPhone, data.invoiceId, data.price, dateFormat(data.dueDate)); 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( return this.createNotification(
{ title: NotificationMessage.BILL_INVOICE_REMINDER, type: NotifType.BILL_INVOICE_REMINDER, message, recipientId }, { 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); const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.BILL_INVOICE, recipientId);
if (isActive) { if (isActive) {
await this.smsService.sendInvoicePaidSms(data.userPhone, data.invoiceId, data.price, dateFormat(data.paidAt!)); 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( return this.createNotification(
{ title: NotificationMessage.BILL_INVOICE, type: NotifType.BILL_INVOICE, message, recipientId }, { 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); const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.APPROVE_INVOICE, recipientId);
if (isActive) { if (isActive) {
await this.smsService.sendInvoiceApprovedSms(data.userPhone, data.invoiceId, data.price, dateFormat(data.dueDate)); 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( return this.createNotification(
{ title: NotificationMessage.APPROVED_INVOICE, type: NotifType.BILL_INVOICE, message, recipientId }, { 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); const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.INVOICE_OVERDUE, recipientId);
if (isActive) { if (isActive) {
await this.smsService.sendInvoiceOverdueSms(data.userPhone, data.invoiceId, data.price, data.lateFee || 0, dateFormat(data.dueDate)); 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( return this.createNotification(
{ title: NotificationMessage.INVOICE_OVERDUE, type: NotifType.INVOICE_OVERDUE, message, recipientId }, { 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); const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.RECURRING_INVOICE, recipientId);
if (isActive) { if (isActive) {
await this.smsService.sendRecurringInvoiceDraftSms(data.userPhone, data.invoiceId, data.price); 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( return this.createNotification(
{ title: NotificationMessage.RECURRING_INVOICE, type: NotifType.RECURRING_INVOICE, message, recipientId }, { 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); const { isActive } = await this.userSettingsService.getUserNotificationSettingWithType(NotifType.BLOCK_SERVICE, recipientId);
if (isActive) { if (isActive) {
await this.smsService.sendBlockServiceSms(data.userPhone, data.invoiceId, data.planName); await this.smsService.sendBlockServiceSms(data.userPhone, data.invoiceId, data.planName);
if (data.userEmail) // if (data.userEmail)
await this.emailService.sendAnnouncementEmail({ // await this.emailService.sendAnnouncementEmail({
userEmail: data.userEmail, // userEmail: data.userEmail,
userPhone: data.userPhone, // userPhone: data.userPhone,
title: NotificationMessage.BLOCK_SERVICE, // title: NotificationMessage.BLOCK_SERVICE,
description: message, // description: message,
date: new Date(), // date: new Date(),
}); // });
} }
return this.createNotification( return this.createNotification(
{ title: NotificationMessage.BLOCK_SERVICE, type: NotifType.BLOCK_SERVICE, message, recipientId }, { title: NotificationMessage.BLOCK_SERVICE, type: NotifType.BLOCK_SERVICE, message, recipientId },
@@ -399,7 +399,7 @@ export class NotificationsService {
if (isActive) { if (isActive) {
await this.smsService.sendPaymentReminderSms(data.userPhone, data.amount); 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( return this.createNotification(
@@ -425,7 +425,7 @@ export class NotificationsService {
if (isActive) { if (isActive) {
await this.smsService.sendPaymentCancellationSms(data.userPhone, data.amount); 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( return this.createNotification(
@@ -448,13 +448,13 @@ export class NotificationsService {
const message = NotificationMessage.NEW_BLOG_COMMENT_MESSAGE.replace("[blogTitle]", data.blogTitle); const message = NotificationMessage.NEW_BLOG_COMMENT_MESSAGE.replace("[blogTitle]", data.blogTitle);
await this.smsService.sendBlogNewCommentSms(data.userPhone, data.blogTitle, data.fullName); await this.smsService.sendBlogNewCommentSms(data.userPhone, data.blogTitle, data.fullName);
if (data.userEmail) { // if (data.userEmail) {
await this.emailService.sendAdminNotificationEmail({ // await this.emailService.sendAdminNotificationEmail({
userEmail: data.userEmail, // userEmail: data.userEmail,
title: NotificationMessage.NEW_BLOG_COMMENT, // title: NotificationMessage.NEW_BLOG_COMMENT,
message, // message,
}); // });
} // }
return this.createNotification( return this.createNotification(
{ title: NotificationMessage.NEW_BLOG_COMMENT, type: NotifType.NEW_BLOG_COMMENT, message, recipientId }, { title: NotificationMessage.NEW_BLOG_COMMENT, type: NotifType.NEW_BLOG_COMMENT, message, recipientId },
queryRunner, queryRunner,
@@ -465,13 +465,13 @@ export class NotificationsService {
const message = NotificationMessage.NEW_SERVICE_REVIEW_MESSAGE.replace("[serviceName]", data.serviceName); const message = NotificationMessage.NEW_SERVICE_REVIEW_MESSAGE.replace("[serviceName]", data.serviceName);
await this.smsService.sendNewServiceReviewSms(data.userPhone, data.serviceName, data.fullName); await this.smsService.sendNewServiceReviewSms(data.userPhone, data.serviceName, data.fullName);
if (data.userEmail) { // if (data.userEmail) {
await this.emailService.sendAdminNotificationEmail({ // await this.emailService.sendAdminNotificationEmail({
userEmail: data.userEmail, // userEmail: data.userEmail,
title: NotificationMessage.NEW_SERVICE_REVIEW, // title: NotificationMessage.NEW_SERVICE_REVIEW,
message, // message,
}); // });
} // }
return this.createNotification( return this.createNotification(
{ title: NotificationMessage.NEW_SERVICE_REVIEW, type: NotifType.NEW_SERVICE_REVIEW, message, recipientId }, { 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); const message = NotificationMessage.NEW_CUSTOMER_MESSAGE.replace("[fullName]", data.fullName);
await this.smsService.sendNewCustomerSms(data.userPhone, data.mobile, data.fullName); await this.smsService.sendNewCustomerSms(data.userPhone, data.mobile, data.fullName);
if (data.userEmail) { // if (data.userEmail) {
await this.emailService.sendAdminNotificationEmail({ // await this.emailService.sendAdminNotificationEmail({
userEmail: data.userEmail, // userEmail: data.userEmail,
title: NotificationMessage.NEW_CUSTOMER, // title: NotificationMessage.NEW_CUSTOMER,
message, // message,
}); // });
} // }
return this.createNotification( return this.createNotification(
{ title: NotificationMessage.NEW_CUSTOMER, type: NotifType.NEW_CUSTOMER, message, recipientId }, { title: NotificationMessage.NEW_CUSTOMER, type: NotifType.NEW_CUSTOMER, message, recipientId },
queryRunner, queryRunner,
@@ -502,14 +502,14 @@ export class NotificationsService {
const message = NotificationMessage.NEW_SUBSCRIPTION_MESSAGE.replace("[serviceName]", data.serviceName); const message = NotificationMessage.NEW_SUBSCRIPTION_MESSAGE.replace("[serviceName]", data.serviceName);
await this.smsService.sendNewSubscriptionSms(data.userPhone, data.serviceName, data.fullName, dateFormat(data.date)); await this.smsService.sendNewSubscriptionSms(data.userPhone, data.serviceName, data.fullName, dateFormat(data.date));
if (data.userEmail) { // if (data.userEmail) {
await this.emailService.sendAdminNotificationEmail({ // await this.emailService.sendAdminNotificationEmail({
userEmail: data.userEmail, // userEmail: data.userEmail,
title: NotificationMessage.NEW_SUBSCRIPTION, // title: NotificationMessage.NEW_SUBSCRIPTION,
fullName: data.fullName, // fullName: data.fullName,
message, // message,
}); // });
} // }
return this.createNotification( return this.createNotification(
{ title: NotificationMessage.NEW_SUBSCRIPTION, type: NotifType.NEW_SUBSCRIPTION, message, recipientId }, { title: NotificationMessage.NEW_SUBSCRIPTION, type: NotifType.NEW_SUBSCRIPTION, message, recipientId },
queryRunner, queryRunner,
@@ -521,13 +521,13 @@ export class NotificationsService {
const message = NotificationMessage.NEW_TICKET_MESSAGE.replace("[ticketSubject]", data.ticketSubject); const message = NotificationMessage.NEW_TICKET_MESSAGE.replace("[ticketSubject]", data.ticketSubject);
await this.smsService.sendGlobalNewTicketSms(data.userPhone, data.ticketId, data.ticketSubject, data.fullName); await this.smsService.sendGlobalNewTicketSms(data.userPhone, data.ticketId, data.ticketSubject, data.fullName);
if (data.userEmail) { // if (data.userEmail) {
await this.emailService.sendAdminNotificationEmail({ // await this.emailService.sendAdminNotificationEmail({
userEmail: data.userEmail, // userEmail: data.userEmail,
title: NotificationMessage.NEW_TICKET, // title: NotificationMessage.NEW_TICKET,
message, // message,
}); // });
} // }
return this.createNotification( return this.createNotification(
{ title: NotificationMessage.NEW_TICKET, type: NotifType.NEW_TICKET, message, recipientId }, { title: NotificationMessage.NEW_TICKET, type: NotifType.NEW_TICKET, message, recipientId },
queryRunner, queryRunner,
@@ -539,13 +539,13 @@ export class NotificationsService {
const message = NotificationMessage.NEW_CRITICISM_MESSAGE.replace("[title]", data.title); const message = NotificationMessage.NEW_CRITICISM_MESSAGE.replace("[title]", data.title);
await this.smsService.sendNewCriticismSms(data.userPhone, data.title, data.fullName); await this.smsService.sendNewCriticismSms(data.userPhone, data.title, data.fullName);
if (data.userEmail) { // if (data.userEmail) {
await this.emailService.sendAdminNotificationEmail({ // await this.emailService.sendAdminNotificationEmail({
userEmail: data.userEmail, // userEmail: data.userEmail,
title: NotificationMessage.NEW_CRITICISM, // title: NotificationMessage.NEW_CRITICISM,
message, // message,
}); // });
} // }
return this.createNotification( return this.createNotification(
{ title: NotificationMessage.NEW_CRITICISM, type: NotifType.NEW_CRITICISM, message, recipientId }, { title: NotificationMessage.NEW_CRITICISM, type: NotifType.NEW_CRITICISM, message, recipientId },
queryRunner, queryRunner,
@@ -6,24 +6,12 @@ import { DataSource } from "typeorm";
import { WorkerProcessor } from "../../../common/queues/worker.processor"; import { WorkerProcessor } from "../../../common/queues/worker.processor";
import { LoggerService } from "../../logger/logger.service"; import { LoggerService } from "../../logger/logger.service";
import { NotifType } from "../../settings/enums/notif-settings.enum"; import { NotifType } from "../../settings/enums/notif-settings.enum";
import { SmsService } from "../../utils/providers/sms.service";
import { NOTIFICATION } from "../constants"; 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 { NotificationJobData, OtpJobData } from "../interfaces/INotification-job-data";
import { import { IBaseNotificationData } from "../interfaces/ISendNotificationData";
IAnnouncementNotificationData,
IBlogCommentNotificationData,
IInvoiceNotificationData,
INewCriticismNotificationData,
INewCustomerNotificationData,
INewSubscriptionNotificationData,
INewTicketNotificationData,
IPaymentNotificationData,
IServiceReviewNotificationData,
ISubscriptionNotificationData,
ITicketNotificationData,
IWalletNotificationData,
} from "../interfaces/ISendNotificationData";
import { NotificationsService } from "../providers/notifications.service";
@Processor(NOTIFICATION.QUEUE_NAME, { @Processor(NOTIFICATION.QUEUE_NAME, {
concurrency: NOTIFICATION.SEND_NOTIFICATION_JOB_CONCURRENCY, concurrency: NOTIFICATION.SEND_NOTIFICATION_JOB_CONCURRENCY,
@@ -35,145 +23,59 @@ export class NotificationProcessor extends WorkerProcessor {
protected readonly logger = new Logger(NotificationProcessor.name); protected readonly logger = new Logger(NotificationProcessor.name);
constructor( constructor(
private readonly notificationsService: NotificationsService,
private readonly dataSource: DataSource, private readonly dataSource: DataSource,
protected readonly loggerService: LoggerService, protected readonly loggerService: LoggerService,
private readonly smsService: SmsService, private readonly notificationHandlerFactory: NotificationHandlerFactory,
private readonly otpHandler: OtpHandler,
private readonly userLoginHandler: UserLoginHandler,
) { ) {
super(); super();
} }
async process(job: Job<NotificationJobData | OtpJobData>, token?: string) { async process(job: Job<NotificationJobData | OtpJobData>, token?: string) {
this.logger.log(`Processing notification job: ${job.name} ${token ? `with token: ${token}` : ""}`); this.logger.log(`Processing notification job: ${job.name} ${token ? `with token: ${token}` : ""}`);
const queryRunner = this.dataSource.createQueryRunner();
let heartbeat: NodeJS.Timeout | undefined;
try { try {
await queryRunner.connect();
await queryRunner.startTransaction();
heartbeat = setInterval(() => job.updateProgress(50), 10000);
if (job.name === NOTIFICATION.SEND_LOGIN_OTP_JOB_NAME) { if (job.name === NOTIFICATION.SEND_LOGIN_OTP_JOB_NAME) {
const { phone, code } = job.data as OtpJobData; await this.otpHandler.processOtp(job.data as OtpJobData);
await this.smsService.sendSmsVerifyCode(phone, code);
//
} else if (job.name === NOTIFICATION.SEND_NOTIFICATION_JOB_NAME) { } else if (job.name === NOTIFICATION.SEND_NOTIFICATION_JOB_NAME) {
const { type, recipientId, data } = job.data as NotificationJobData; const { type, recipientId, data } = job.data as NotificationJobData;
switch (type) {
// Admin Notifications // Handle special cases that don't use transactions
case NotifType.NEW_BLOG_COMMENT: if (type === NotifType.USER_LOGIN) {
await this.notificationsService.createNewBlogCommentNotification( await this.userLoginHandler.processUserLogin(recipientId, data as IBaseNotificationData);
recipientId, } else {
data as IBlogCommentNotificationData, // Handle notifications that require transactions
queryRunner, await this.processWithTransaction(type, recipientId, data);
);
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}`);
} }
} else { } else {
this.logger.warn(`Unknown job name: ${job.name}`); this.logger.warn(`Unknown job name: ${job.name}`);
} }
await queryRunner.commitTransaction();
return true; return true;
} catch (error) { } catch (error) {
this.logger.error( this.logger.error(
`Failed to process notification: ${error instanceof Error ? error.message : "Unknown error"}`, `Failed to process notification: ${error instanceof Error ? error.message : "Unknown error"}`,
error instanceof Error ? error.stack : undefined, error instanceof Error ? error.stack : undefined,
); );
throw error;
}
}
private async processWithTransaction(type: NotifType, recipientId: string, data: any): Promise<void> {
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(); await queryRunner.rollbackTransaction();
throw error; throw error;
} finally { } finally {
if (heartbeat) clearInterval(heartbeat);
await queryRunner.release(); await queryRunner.release();
} }
} }
+1 -1
View File
@@ -1,5 +1,5 @@
export const SUBSCRIPTIONS = Object.freeze({ export const SUBSCRIPTIONS = Object.freeze({
PROVISIONING_QUEUE_PREFIX: "provisioning", PROVISIONING_QUEUE_PREFIX: "subs",
PROVISIONING_QUEUE_NAME: "provisioning", PROVISIONING_QUEUE_NAME: "provisioning",
PROVISIONING_JOB_NAME: "business.created", PROVISIONING_JOB_NAME: "business.created",
@@ -1,5 +1,5 @@
import { InjectQueue } from "@nestjs/bullmq"; 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 { Queue } from "bullmq";
import dayjs from "dayjs"; import dayjs from "dayjs";
import Decimal from "decimal.js"; import Decimal from "decimal.js";
@@ -28,6 +28,8 @@ import { UserSubscriptionsRepository } from "../repositories/user-subscriptions.
@Injectable() @Injectable()
export class SubscriptionsService { export class SubscriptionsService {
private readonly logger: Logger = new Logger(SubscriptionsService.name);
constructor( constructor(
@InjectQueue(SUBSCRIPTIONS.PROVISIONING_QUEUE_NAME) private readonly provisioningQueue: Queue, @InjectQueue(SUBSCRIPTIONS.PROVISIONING_QUEUE_NAME) private readonly provisioningQueue: Queue,
private readonly subscriptionsPlanRepository: SubscriptionsPlanRepository, private readonly subscriptionsPlanRepository: SubscriptionsPlanRepository,
@@ -337,6 +339,7 @@ export class SubscriptionsService {
//************************************ */ //************************************ */
async addProvisioningJob(userSubscription: UserSubscription, plan: SubscriptionPlan, user: User) { 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( await this.provisioningQueue.add(
SUBSCRIPTIONS.PROVISIONING_JOB_NAME, SUBSCRIPTIONS.PROVISIONING_JOB_NAME,
{ {