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.
This commit is contained in:
@@ -81,9 +81,14 @@ export class MessageSchema extends PothosSchema {
|
|||||||
if (ctx.isSubscription) {
|
if (ctx.isSubscription) {
|
||||||
throw new Error('Not allowed')
|
throw new Error('Not allowed')
|
||||||
}
|
}
|
||||||
// if message.context is NOTIFICATION, filter by recipientId
|
if (args.filter?.context && typeof args.filter.context === 'object') {
|
||||||
if (args.filter?.context === MessageContextType.NOTIFICATION) {
|
// if args.context is NOTIFICATION or SYSTEM, filter by recipientId
|
||||||
args.filter.recipientId = ctx.http.me?.id
|
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({
|
return await this.prisma.message.findMany({
|
||||||
...query,
|
...query,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Inject, Injectable } from '@nestjs/common'
|
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 { 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 { DateTimeUtils } from 'src/common/utils/datetime.utils'
|
||||||
import { Builder } from '../Graphql/graphql.builder'
|
import { Builder } from '../Graphql/graphql.builder'
|
||||||
import { PrismaService } from '../Prisma/prisma.service'
|
import { PrismaService } from '../Prisma/prisma.service'
|
||||||
@@ -122,7 +123,7 @@ export class RefundTicketSchema extends PothosSchema {
|
|||||||
if (ctx.isSubscription) {
|
if (ctx.isSubscription) {
|
||||||
throw new Error('Subscription is not allowed')
|
throw new Error('Subscription is not allowed')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the user is a customer or a center mentor
|
// 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) {
|
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')
|
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 orderDate = DateTimeUtils.fromDate(order.createdAt)
|
||||||
const diffTime = Math.abs(now.diff(orderDate).toMillis())
|
const diffTime = Math.abs(now.diff(orderDate).toMillis())
|
||||||
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24))
|
||||||
|
|
||||||
let refundAmount = 0
|
let refundAmount = 0
|
||||||
|
|
||||||
// Special handling for center mentors - full refund always allowed
|
// Special handling for center mentors - full refund always allowed
|
||||||
if (ctx.http.me?.role === Role.CENTER_MENTOR) {
|
if (ctx.http.me?.role === Role.CENTER_MENTOR) {
|
||||||
refundAmount = order.total
|
refundAmount = order.total
|
||||||
@@ -217,7 +218,23 @@ export class RefundTicketSchema extends PothosSchema {
|
|||||||
bankName: bankName,
|
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
|
return refundTicket
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { Inject, Injectable, Logger } from '@nestjs/common'
|
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 { 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 { Builder } from '../Graphql/graphql.builder'
|
||||||
import { MinioService } from '../Minio/minio.service'
|
import { MinioService } from '../Minio/minio.service'
|
||||||
import { PrismaService } from '../Prisma/prisma.service'
|
import { PrismaService } from '../Prisma/prisma.service'
|
||||||
@@ -202,7 +204,13 @@ export class ResumeSchema extends PothosSchema {
|
|||||||
required: true,
|
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 { resumeFile } = args
|
||||||
const { mimetype } = await resumeFile
|
const { mimetype } = await resumeFile
|
||||||
const { filename, actualFileName } = await this.minioService.uploadFile(resumeFile, 'resumes')
|
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
|
// 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 } } },
|
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
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@@ -263,10 +263,48 @@ export class ServiceSchema extends PothosSchema {
|
|||||||
}
|
}
|
||||||
// replace userId with current user id
|
// replace userId with current user id
|
||||||
args.input.user = { connect: { id: ctx.http.me?.id ?? '' } }
|
args.input.user = { connect: { id: ctx.http.me?.id ?? '' } }
|
||||||
return await this.prisma.service.create({
|
const service = await this.prisma.service.create({
|
||||||
...query,
|
...query,
|
||||||
data: args.input,
|
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({
|
updateService: t.prismaField({
|
||||||
|
|||||||
Reference in New Issue
Block a user