import { Inject, Injectable } from '@nestjs/common'; import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken, } from '@smatch-corp/nestjs-pothos'; import { Builder } from '../Graphql/graphql.builder'; import { PrismaService } from '../Prisma/prisma.service'; import { MinioService } from '../Minio/minio.service'; import { ServiceStatus } from '@prisma/client'; import { MailService } from '../Mail/mail.service'; @Injectable() export class ServiceSchema extends PothosSchema { constructor( @Inject(SchemaBuilderToken) private readonly builder: Builder, private readonly prisma: PrismaService, private readonly minioService: MinioService, private readonly mailService: MailService, ) { super(); } @PothosRef() service() { return this.builder.prismaObject('Service', { description: 'A service offered by a center.', fields: (t) => ({ id: t.exposeID('id', { description: 'The ID of the service.', }), name: t.exposeString('name', { description: 'The name of the service.', }), description: t.exposeString('description', { description: 'The description of the service.', }), content: t.exposeString('content', { description: 'The content of the service.', }), centerId: t.exposeID('centerId', { description: 'The ID of the center that offers the service.', }), userId: t.exposeID('userId', { description: 'The ID of the user who requested the service.', }), price: t.exposeFloat('price', { description: 'The price of the service.', }), rating: t.expose('rating', { type: 'Float', nullable: true, description: 'The rating of the service.', }), imageFile: t.relation('imageFile', { description: 'The image file for the service.', }), imageFileId: t.exposeID('imageFileId', { description: 'The ID of the image file for the service.', }), imageFileUrl: t.exposeString('imageFileUrl', { description: 'The URL of the image file for the service.', }), status: t.expose('status', { type: ServiceStatus, description: 'The status of the service.', }), isActive: t.exposeBoolean('isActive', { description: 'Whether the service is active.', }), createdAt: t.expose('createdAt', { type: 'DateTime', description: 'The date and time the service was created.', }), updatedAt: t.expose('updatedAt', { type: 'DateTime', description: 'The date and time the service was updated.', }), feedbacks: t.relation('feedbacks', { description: 'The feedbacks for the service.', }), order: t.relation('order', { description: 'The order for the service.', }), center: t.relation('center', { description: 'The center that offers the service.', }), workshop: t.relation('workshop', { description: 'The workshop for the service.', }), milestone: t.relation('milestone', { description: 'The milestone for the service.', }), serviceAndCategory: t.relation('serviceAndCategory', { description: 'The service and category for the service.', }), workshopOrganization: t.relation('workshopOrganization', { description: 'The workshop organization for the service.', }), user: t.relation('user', { description: 'The user who requested the service.', }), managedService: t.relation('managedService', { description: 'The managed service for the service.', }), }), }); } @Pothos() init() { this.builder.queryFields((t) => ({ testServices: t.prismaConnection( { description: 'A test connection for services', type: this.service(), cursor: 'id', args: this.builder.generator.findManyArgs('Service'), resolve: async (query, root, args, ctx, info) => { return await this.prisma.service.findMany({ ...query, }); }, totalCount: (query) => { return this.prisma.service.count({ ...query, }); }, }, {}, ), services: t.prismaField({ description: 'Retrieve a list of services with optional filtering, ordering, and pagination.', type: [this.service()], args: this.builder.generator.findManyArgs('Service'), resolve: async (query, root, args, ctx, info) => { return await this.prisma.service.findMany({ ...query, where: args.filter ?? undefined, orderBy: args.orderBy ?? undefined, skip: args.skip ?? undefined, take: args.take ?? 10, cursor: args.cursor ?? undefined, }); }, }), service: t.prismaField({ description: 'Retrieve a single service by its unique identifier.', type: this.service(), args: { input: t.arg({ type: this.builder.generator.getWhereUnique('Service'), required: true, }), }, resolve: async (query, root, args, ctx, info) => { return await this.prisma.service.findUnique({ ...query, where: args.input, include: { feedbacks: true, }, }); }, }), })); // Mutation section this.builder.mutationFields((t) => ({ createService: t.prismaField({ description: 'Create a new service.', type: this.service(), args: { input: t.arg({ type: this.builder.generator.getCreateInput('Service'), required: true, }), }, resolve: async (query, root, args, ctx, info) => { return await this.prisma.service.create({ ...query, data: args.input, }); }, }), updateService: t.prismaField({ description: 'Update an existing service.', type: this.service(), args: { input: t.arg({ type: this.builder.generator.getUpdateInput('Service'), required: true, }), where: t.arg({ type: this.builder.generator.getWhereUnique('Service'), required: true, }), }, resolve: async (query, root, args, ctx, info) => { return await this.prisma.service.update({ ...query, where: args.where, data: args.input, }); }, }), deleteService: t.prismaField({ description: 'Delete an existing service.', type: this.service(), args: { where: t.arg({ type: this.builder.generator.getWhereUnique('Service'), required: true, }), }, resolve: async (query, root, args, ctx, info) => { return await this.prisma.service.delete({ ...query, where: args.where, }); }, }), approveOrRejectService: t.prismaField({ description: 'Approve or reject a service. For moderator only.', type: this.service(), args: { serviceId: t.arg({ type: 'String', required: true, }), approve: t.arg({ type: 'Boolean', required: true, }), }, resolve: async (query, root, args, ctx, info) => { return await this.prisma.$transaction(async (prisma) => { // check if service is already approved or rejected const service = await prisma.service.findUnique({ where: { id: args.serviceId }, }); if (!service) { throw new Error('Service not found'); } if (service.status !== ServiceStatus.PENDING) { throw new Error('Service is already approved or rejected'); } // update service status const updatedService = await prisma.service.update({ ...query, where: { id: args.serviceId }, data: { status: args.approve ? ServiceStatus.APPROVED : ServiceStatus.REJECTED, }, }); // mail to center owner and staff who requested the service const center = await prisma.center.findUnique({ where: { id: service.centerId }, }); if (!center?.centerOwnerId) { throw new Error('Center owner not found'); } const centerOwner = await prisma.user.findUnique({ where: { id: center.centerOwnerId }, }); if (!centerOwner) { throw new Error('Center owner not found'); } const centerStaff = await prisma.centerStaff.findMany({ where: { centerId: service.centerId }, }); const staffEmails = centerStaff.map((staff) => staff.staffId); const emails = [centerOwner.email, ...staffEmails]; for (const email of emails) { await this.mailService.sendEmail( email, args.approve ? 'Your service has been approved' : 'Your service has been rejected', args.approve ? 'service-approved' : 'service-rejected', ); } return updatedService; }); }, }), })); } }