From 092a15753bc068c0888f5d69d91a683b701281da Mon Sep 17 00:00:00 2001 From: Ly Tuan Kiet Date: Mon, 9 Dec 2024 19:38:36 +0700 Subject: [PATCH] feat: enhance notification system and role-based access across schemas - Updated MessageSchema to include context-aware filtering for notifications, allowing retrieval based on recipientId for NOTIFICATION and SYSTEM contexts. - Enhanced RefundTicketSchema to notify moderators upon refund requests, improving communication and response times. - Modified ResumeSchema to send notifications to mentors and center owners when a new resume is submitted, ensuring timely updates. - Improved ServiceSchema to notify center owners and mentors about service approvals, enhancing user engagement and awareness. - Implemented role-based access control checks across schemas to ensure only authorized users can perform specific actions, enhancing security and user experience. --- src/Message/message.schema.ts | 11 ++++-- src/RefundTicket/refundticket.schema.ts | 27 +++++++++++--- src/Resume/resume.schema.ts | 49 +++++++++++++++++++++++-- src/Service/service.schema.ts | 40 +++++++++++++++++++- 4 files changed, 115 insertions(+), 12 deletions(-) diff --git a/src/Message/message.schema.ts b/src/Message/message.schema.ts index 4d46a8a..51adc21 100644 --- a/src/Message/message.schema.ts +++ b/src/Message/message.schema.ts @@ -81,9 +81,14 @@ export class MessageSchema extends PothosSchema { if (ctx.isSubscription) { throw new Error('Not allowed') } - // if message.context is NOTIFICATION, filter by recipientId - if (args.filter?.context === MessageContextType.NOTIFICATION) { - args.filter.recipientId = ctx.http.me?.id + if (args.filter?.context && typeof args.filter.context === 'object') { + // if args.context is NOTIFICATION or SYSTEM, filter by recipientId + if ( + args.filter.context.in?.toString().includes(MessageContextType.NOTIFICATION) || + args.filter.context.in?.toString().includes(MessageContextType.SYSTEM) + ) { + args.filter.recipientId = ctx.http.me?.id + } } return await this.prisma.message.findMany({ ...query, diff --git a/src/RefundTicket/refundticket.schema.ts b/src/RefundTicket/refundticket.schema.ts index 4d6a19a..392af6b 100644 --- a/src/RefundTicket/refundticket.schema.ts +++ b/src/RefundTicket/refundticket.schema.ts @@ -1,6 +1,7 @@ import { Inject, Injectable } from '@nestjs/common' -import { OrderStatus, PaymentStatus, RefundTicketStatus, Role } from '@prisma/client' +import { MessageContextType, MessageType, OrderStatus, PaymentStatus, RefundTicketStatus, Role } from '@prisma/client' import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos' +import { PubSubEvent } from 'src/common/pubsub/pubsub-event' import { DateTimeUtils } from 'src/common/utils/datetime.utils' import { Builder } from '../Graphql/graphql.builder' import { PrismaService } from '../Prisma/prisma.service' @@ -122,7 +123,7 @@ export class RefundTicketSchema extends PothosSchema { if (ctx.isSubscription) { throw new Error('Subscription is not allowed') } - + // Check if the user is a customer or a center mentor if (ctx.http.me?.role !== Role.CUSTOMER && ctx.http.me?.role !== Role.CENTER_MENTOR) { throw new Error('Only customers and center mentors can request refund') @@ -165,9 +166,9 @@ export class RefundTicketSchema extends PothosSchema { const orderDate = DateTimeUtils.fromDate(order.createdAt) const diffTime = Math.abs(now.diff(orderDate).toMillis()) const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24)) - + let refundAmount = 0 - + // Special handling for center mentors - full refund always allowed if (ctx.http.me?.role === Role.CENTER_MENTOR) { refundAmount = order.total @@ -217,7 +218,23 @@ export class RefundTicketSchema extends PothosSchema { bankName: bankName, }, }) - + // notify all Moderator + const moderators = await this.prisma.user.findMany({ + where: { role: Role.MODERATOR }, + }) + for (const moderator of moderators) { + const message = await this.prisma.message.create({ + data: { + senderId: ctx.http.me?.id ?? '', + recipientId: moderator.id, + type: MessageType.TEXT, + content: `Có yêu cầu hoàn tiền mới từ ${ctx.http.me?.name}`, + sentAt: DateTimeUtils.nowAsJSDate(), + context: MessageContextType.NOTIFICATION, + }, + }) + ctx.http.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${moderator.id}`, message) + } return refundTicket }, }), diff --git a/src/Resume/resume.schema.ts b/src/Resume/resume.schema.ts index 8cfd050..7ab07a8 100644 --- a/src/Resume/resume.schema.ts +++ b/src/Resume/resume.schema.ts @@ -1,6 +1,8 @@ import { Inject, Injectable, Logger } from '@nestjs/common' -import { ResumeStatus, Role } from '@prisma/client' +import { MessageContextType, MessageType, ResumeStatus, Role } from '@prisma/client' import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos' +import { PubSubEvent } from 'src/common/pubsub/pubsub-event' +import { DateTimeUtils } from 'src/common/utils/datetime.utils' import { Builder } from '../Graphql/graphql.builder' import { MinioService } from '../Minio/minio.service' import { PrismaService } from '../Prisma/prisma.service' @@ -202,7 +204,13 @@ export class ResumeSchema extends PothosSchema { required: true, }), }, - resolve: async (_query, _root, args) => { + resolve: async (_query, _root, args, ctx, _info) => { + if (ctx.isSubscription) { + throw new Error('Not allowed') + } + if (ctx.http.me?.role !== Role.CUSTOMER) { + throw new Error('Not allowed') + } const { resumeFile } = args const { mimetype } = await resumeFile const { filename, actualFileName } = await this.minioService.uploadFile(resumeFile, 'resumes') @@ -233,9 +241,44 @@ export class ResumeSchema extends PothosSchema { }) } // if resume does not exist, create new resume - return await this.prisma.resume.create({ + const resume = await this.prisma.resume.create({ data: { userId, centerId, resumeFile: { create: { fileUrl, type: mimetype, actualFileName } } }, }) + // notify all mentor or center owner for the center + const center = await this.prisma.center.findUnique({ + where: { id: centerId }, + }) + if (!center?.centerOwnerId) { + throw new Error('Center owner not found') + } + const centerOwner = await this.prisma.user.findUnique({ + where: { id: center.centerOwnerId }, + }) + if (!centerOwner) { + throw new Error('Center owner not found') + } + const centerMentor = await this.prisma.centerMentor.findMany({ + where: { centerId: center.id }, + }) + const mentorIds = centerMentor.map((mentor) => mentor.mentorId) + // send notification to all Moderator + const moderators = await this.prisma.user.findMany({ + where: { id: { in: mentorIds } }, + }) + for (const moderator of moderators) { + const message = await this.prisma.message.create({ + data: { + senderId: ctx.http.me?.id ?? '', + recipientId: moderator.id, + type: MessageType.TEXT, + content: `Có yêu cầu hồ sơ mới từ ${ctx.http.me?.name}`, + sentAt: DateTimeUtils.nowAsJSDate(), + context: MessageContextType.NOTIFICATION, + }, + }) + ctx.http.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${moderator.id}`, message) + } + return resume }, }), diff --git a/src/Service/service.schema.ts b/src/Service/service.schema.ts index a8ecc4b..e30eaa3 100644 --- a/src/Service/service.schema.ts +++ b/src/Service/service.schema.ts @@ -263,10 +263,48 @@ export class ServiceSchema extends PothosSchema { } // replace userId with current user id args.input.user = { connect: { id: ctx.http.me?.id ?? '' } } - return await this.prisma.service.create({ + const service = await this.prisma.service.create({ ...query, data: args.input, }) + // send notification to all mentor or center owner for the center + const center = await this.prisma.center.findUnique({ + where: { id: service.centerId }, + }) + if (!center?.centerOwnerId) { + throw new Error('Center owner not found') + } + const centerOwner = await this.prisma.user.findUnique({ + where: { id: center.centerOwnerId }, + }) + if (!centerOwner) { + throw new Error('Center owner not found') + } + const centerMentor = await this.prisma.centerMentor.findMany({ + where: { centerId: service.centerId }, + }) + const mentorIds = centerMentor.map((mentor) => mentor.mentorId) + const mentorEmails = await this.prisma.user.findMany({ + where: { id: { in: mentorIds } }, + }) + const emails = [centerOwner.email, ...mentorEmails.map((mentor) => mentor.email)] + await this.mailService.sendTemplateEmail(emails, 'Thông báo về trạng thái dịch vụ', 'ServiceApproved', { + SERVICE_NAME: service.name, + CENTER_NAME: center.name, + }) + // send notification to all mentor or center owner for the center using context + const message = await this.prisma.message.create({ + data: { + senderId: ctx.http.me?.id ?? '', + recipientId: centerOwner.id, + type: MessageType.TEXT, + content: `Dịch vụ ${service.name} của bạn đã được chấp thuận`, + sentAt: DateTimeUtils.nowAsJSDate(), + context: MessageContextType.NOTIFICATION, + }, + }) + ctx.http.pubSub.publish(`${PubSubEvent.NOTIFICATION}.${centerOwner.id}`, message) + return service }, }), updateService: t.prismaField({