diff --git a/src/common/enums/permission.enum.ts b/src/common/enums/permission.enum.ts index ef5ac4b..da07249 100644 --- a/src/common/enums/permission.enum.ts +++ b/src/common/enums/permission.enum.ts @@ -48,8 +48,9 @@ export enum PermissionEnum { MANAGE_TICKETS = 'manage_tickets', // Notif Permsissions - NEW_REQUEST_NOTIFICATION='new_request_notification', - NEW_PAYMENT_NOTIFICATION='new_payment_notification', + NEW_REQUEST_NOTIFICATION = 'new_request_notification', + NEW_PAYMENT_NOTIFICATION = 'new_payment_notification', + NEW_TICKET_NOTIFICATION = 'new_ticket_notification', } @@ -86,5 +87,6 @@ export const PermissionTitles: Record = { [PermissionEnum.CREATE_USER]: "", [PermissionEnum.UPDATE_USER]: "", [PermissionEnum.DELETE_USER]: "", - [PermissionEnum.VIEW_USERS]: "" + [PermissionEnum.VIEW_USERS]: "", + [PermissionEnum.NEW_TICKET_NOTIFICATION]: "" }; diff --git a/src/modules/ticket/events/ticket.events.ts b/src/modules/ticket/events/ticket.events.ts new file mode 100644 index 0000000..96b31f0 --- /dev/null +++ b/src/modules/ticket/events/ticket.events.ts @@ -0,0 +1,7 @@ +export class TicketCreatedByUserEvent { + constructor( + public readonly ticketId: string, + public readonly subject: string | null, + public readonly userId: string, + ) {} +} diff --git a/src/modules/ticket/listeners/ticket.listeners.ts b/src/modules/ticket/listeners/ticket.listeners.ts new file mode 100644 index 0000000..eea2dbc --- /dev/null +++ b/src/modules/ticket/listeners/ticket.listeners.ts @@ -0,0 +1,69 @@ +import { Injectable, Logger } from '@nestjs/common'; +import { OnEvent } from '@nestjs/event-emitter'; +import { EntityManager } from '@mikro-orm/postgresql'; +import { AdminRepository } from 'src/modules/admin/repositories/admin.repository'; +import { PermissionEnum } from 'src/common/enums/permission.enum'; +import { NotifTitle } from 'src/modules/notification/interfaces/notification.interface'; +import { Notification } from 'src/modules/notification/entities/notification.entity'; +import { NotificationQueueService } from 'src/modules/notification/services/notification-queue.service'; +import { TicketCreatedByUserEvent } from '../events/ticket.events'; + +@Injectable() +export class TicketListeners { + private readonly logger = new Logger(TicketListeners.name); + + constructor( + private readonly adminRepository: AdminRepository, + private readonly em: EntityManager, + private readonly notificationQueueService: NotificationQueueService, + ) {} + + @OnEvent(TicketCreatedByUserEvent.name) + async handleTicketCreatedByUser(event: InstanceType) { + try { + this.logger.log( + `Ticket created by user: ticketId=${event.ticketId}, userId=${event.userId}`, + ); + + const admins = await this.adminRepository.findAdminsWithPermission( + PermissionEnum.NEW_TICKET_NOTIFICATION, + ); + + if (admins.length === 0) { + this.logger.log('No admins with NEW_TICKET_NOTIFICATION permission'); + return; + } + + const subjectText = event.subject ? `: ${event.subject}` : ''; + const body = `تیکت جدید${subjectText}`; + + const notifications = admins.map((admin) => + this.em.create(Notification, { + admin, + title: NotifTitle.NEW_TICKET, + content: body, + }), + ); + + await this.em.persistAndFlush(notifications); + + await this.notificationQueueService.addBulkInAppNotifications( + notifications.map((n) => ({ + recipient: { adminId: n.admin!.id }, + subject: NotifTitle.NEW_TICKET, + body, + notificationId: n.id, + })), + ); + + this.logger.log( + `Notification sent to ${admins.length} admin(s) for new ticket ${event.ticketId}`, + ); + } catch (error) { + this.logger.error( + `Failed to send notification for ticket ${event.ticketId}`, + error instanceof Error ? error.stack : String(error), + ); + } + } +} diff --git a/src/modules/ticket/ticket.module.ts b/src/modules/ticket/ticket.module.ts index d16372a..6603dcd 100644 --- a/src/modules/ticket/ticket.module.ts +++ b/src/modules/ticket/ticket.module.ts @@ -6,11 +6,20 @@ import { Ticket } from './entities/ticket.entity'; import { TicketRepository } from './repositories/ticket.repository'; import { AuthModule } from '../auth/auth.module'; import { JwtModule } from '@nestjs/jwt'; +import { AdminModule } from '../admin/admin.module'; +import { NotificationsModule } from '../notification/notifications.module'; +import { TicketListeners } from './listeners/ticket.listeners'; @Module({ - imports: [MikroOrmModule.forFeature([Ticket]), AuthModule,JwtModule], + imports: [ + MikroOrmModule.forFeature([Ticket]), + AuthModule, + JwtModule, + AdminModule, + NotificationsModule, + ], controllers: [TicketController], - providers: [TicketService, TicketRepository], + providers: [TicketService, TicketRepository, TicketListeners], exports: [TicketService], }) export class TicketModule {} diff --git a/src/modules/ticket/ticket.service.ts b/src/modules/ticket/ticket.service.ts index 7cdc80c..1d59952 100644 --- a/src/modules/ticket/ticket.service.ts +++ b/src/modules/ticket/ticket.service.ts @@ -1,5 +1,6 @@ import { Injectable, NotFoundException, ForbiddenException } from '@nestjs/common'; import { EntityManager } from '@mikro-orm/postgresql'; +import { EventEmitter2 } from '@nestjs/event-emitter'; import { CreateTicketDto } from './dto/create-ticket.dto'; import { UpdateTicketDto } from './dto/update-ticket.dto'; import { UpdateTicketStatusDto } from './dto/update-ticket-status.dto'; @@ -12,12 +13,14 @@ import { TicketRepository } from './repositories/ticket.repository'; import { Admin } from 'src/modules/admin/entities/admin.entity'; import { User } from 'src/modules/user/entities/user.entity'; import { TicketStatusEnum } from './enum/status.enum'; +import { TicketCreatedByUserEvent } from './events/ticket.events'; @Injectable() export class TicketService { constructor( private readonly em: EntityManager, private readonly ticketRepository: TicketRepository, + private readonly eventEmitter: EventEmitter2, ) { } async createAsAdmin(createTicketDto: CreateTicketDto, adminId: string) { @@ -83,6 +86,13 @@ export class TicketService { status: TicketStatusEnum.OPEN, }); await this.em.persistAndFlush(ticket); + + if (!parent) { + this.eventEmitter.emit( + TicketCreatedByUserEvent.name, + new TicketCreatedByUserEvent(ticket.id, ticket.subject ?? null, userId), + ); + } return ticket; }