import { clerkClient } from '@clerk/express' import { Inject, Injectable } from '@nestjs/common' import { Role } from '@prisma/client' import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos' import { RedisService } from 'src/Redis/redis.service' import { Builder } from '../Graphql/graphql.builder' import { MailService } from '../Mail/mail.service' import { PrismaService } from '../Prisma/prisma.service' import { JwtUtils } from '../common/utils/jwt.utils' @Injectable() export class CenterMentorSchema extends PothosSchema { constructor( @Inject(SchemaBuilderToken) private readonly builder: Builder, private readonly prisma: PrismaService, private readonly mailService: MailService, private readonly jwtUtils: JwtUtils, private readonly redisService: RedisService, ) { super() } @PothosRef() centerMentor() { return this.builder.prismaObject('CenterMentor', { description: 'A mentor of a center.', fields: (t) => ({ mentorId: t.exposeID('mentorId', { description: 'The ID of the mentor.', }), centerId: t.exposeID('centerId', { description: 'The ID of the center.', }), isCenterOwner: t.exposeBoolean('isCenterOwner', { description: 'Whether the mentor is the center owner.', }), mentor: t.relation('mentor', { description: 'The mentor.', }), center: t.relation('center', { description: 'The center.', }), active: t.exposeBoolean('active', { description: 'Whether the mentor is active.', }), createdWorkshop: t.relation('createdWorkshop', { description: 'The workshops created by the center mentor.', }), managedService: t.relation('managedService', { description: 'The managed services of the center mentor.', }), adminNote: t.relation('adminNote', { description: 'The admin note of the center mentor.', }), }), }) } @Pothos() init(): void { this.builder.queryFields((t) => ({ centerMentors: t.prismaField({ description: 'Retrieve a list of center mentors with optional filtering, ordering, and pagination.', type: [this.centerMentor()], args: this.builder.generator.findManyArgs('CenterMentor'), resolve: async (query, _root, args) => { return await this.prisma.centerMentor.findMany({ ...query, skip: args.skip ?? undefined, take: args.take ?? undefined, orderBy: args.orderBy ?? undefined, where: args.filter ?? undefined, }) }, }), })) // mutations this.builder.mutationFields((t) => ({ createCenterMentor: t.prismaField({ type: this.centerMentor(), description: 'Create a new center mentor.', args: { data: t.arg({ type: this.builder.generator.getCreateInput('CenterMentor'), required: true, }), }, resolve: async (query, _root, args) => { return await this.prisma.centerMentor.create({ ...query, data: { ...args.data, active: false }, }) }, }), updateCenterMentor: t.prismaField({ type: this.centerMentor(), description: 'Update an existing center mentor.', args: { where: t.arg({ type: this.builder.generator.getWhereUnique('CenterMentor'), required: true, }), data: t.arg({ type: this.builder.generator.getUpdateInput('CenterMentor'), required: true, }), }, resolve: async (query, _root, args) => { return await this.prisma.centerMentor.update({ ...query, where: args.where, data: args.data, }) }, }), deleteCenterMentor: t.prismaField({ type: this.centerMentor(), description: 'Delete an existing center mentor.', args: { where: t.arg({ type: this.builder.generator.getWhereUnique('CenterMentor'), required: true, }), }, resolve: async (query, _root, args) => { return await this.prisma.centerMentor.delete({ ...query, where: args.where, }) }, }), inviteCenterMentor: t.prismaField({ type: this.centerMentor(), description: 'Invite a new center mentor.', args: { email: t.arg({ type: 'String', required: true }), }, resolve: async (_query, _root, args, ctx) => { return this.prisma.$transaction(async (prisma) => { if (!ctx.me) { throw new Error('User not found') } // get centerId by user id from context const userId = ctx.me.id if (!userId) { throw new Error('User ID is required') } // get user info const user = await prisma.user.findUnique({ where: { email: args.email }, }) if (!user) { throw new Error('User not found') } // block invite center owner from another center const centerOwner = await prisma.center.findFirst({ where: { centerOwnerId: user.id }, }) if (centerOwner) { throw new Error('Center owner cannot be invited as a mentor') } // block invite mentor to own center and owner from another center const centerMentor = await prisma.centerMentor.findUnique({ where: { mentorId: userId }, }) if (centerMentor) { throw new Error('Mentor already has a center') } // get centerId by user id const center = await prisma.center.findUnique({ where: { centerOwnerId: userId }, }) if (!center) { throw new Error('Center not found') } // build signature const token = this.jwtUtils.signTokenRS256({ centerId: center.id, email: args.email }, '1d') // build invite url const inviteUrl = `${process.env.CENTER_BASE_URL}/invite?token=${token}` // mail to user with params centerId, email await this.mailService.sendTemplateEmail( [args.email], `Thư mời làm việc tại trung tâm ${center.name}`, 'MentorInvitation', { center_name: center.name, invite_url: inviteUrl, }, ) return null }) }, }), approveOrRejectCenterMentor: t.prismaField({ type: this.centerMentor(), description: 'Approve or reject a center mentor.', args: { where: t.arg({ type: this.builder.generator.getWhereUnique('User'), required: true, }), approved: t.arg({ type: 'Boolean', required: true }), adminNote: t.arg({ type: 'String', required: false }), }, resolve: async (_query, _root, args, ctx, _info) => { if (!ctx.me) { throw new Error('User not found') } return this.prisma.$transaction(async (prisma) => { // get mentor info const mentor = await prisma.user.findUnique({ where: args.where, }) if (!mentor) { throw new Error('Mentor not found') } // get centerMentor const centerMentor = await prisma.centerMentor.findUnique({ where: { mentorId: mentor.id }, }) if (!centerMentor) { throw new Error('Center mentor not found') } // get center const center = await prisma.center.findUnique({ where: { id: centerMentor.centerId }, }) if (!center) { throw new Error('Center not found') } // get email const email = await prisma.user.findUnique({ where: args.where, select: { email: true }, }) if (!email) { throw new Error('Email is required') } // if approved, update role to mentor if (args.approved) { // send mail to user await this.mailService.sendTemplateEmail( [email.email], 'Thông báo về việc được chấp nhận làm mentor', 'MentorApproved', { CENTER_NAME: center.name, USER_NAME: mentor.name, }, ) // create adminNote const adminNote = await prisma.adminNote.create({ data: { content: args.adminNote ?? '', mentorId: mentor.id, notedByUserId: ctx.me.id, }, }) // update user role await prisma.user.update({ where: args.where, data: { role: Role.CENTER_MENTOR, updatedAt: new Date(), }, }) // get active clerk session and invalidate cache const sessionList = await clerkClient.sessions.getSessionList({ userId: mentor.id, }) // clear all session cache in redis sessionList.data.forEach(async (session) => { await this.redisService.del(session.id) }) // update centerMentor const updatedCenterMentor = await prisma.centerMentor.update({ where: { mentorId_centerId: { mentorId: mentor.id, centerId: centerMentor.centerId, }, }, data: { adminNote: { connect: { id: adminNote.id } }, active: true, }, }) return updatedCenterMentor } // if rejected, update adminNote await this.mailService.sendTemplateEmail( [email.email], 'Thông báo về việc không được chấp nhận làm mentor', 'MentorRejected', { CENTER_NAME: center.name, USER_NAME: mentor.name, }, ) return await prisma.centerMentor.update({ where: { mentorId_centerId: { mentorId: mentor.id, centerId: centerMentor.centerId, }, }, data: { adminNote: { create: { content: args.adminNote ?? '', notedByUserId: ctx.me.id, updatedAt: new Date(), }, }, }, }) }) }, }), })) } }