diff --git a/src/Center/center.schema.ts b/src/Center/center.schema.ts index b9a015a..01f2231 100644 --- a/src/Center/center.schema.ts +++ b/src/Center/center.schema.ts @@ -215,10 +215,7 @@ export class CenterSchema extends PothosSchema { throw new Error('Center not found'); } // check if center is already approved or rejected - if ( - center.centerStatus === CenterStatus.APPROVED || - center.centerStatus === CenterStatus.REJECTED - ) { + if (center.centerStatus !== CenterStatus.PENDING) { throw new Error('Center is already approved or rejected'); } // find center owner and promote to staff diff --git a/src/CenterStaff/centerstaff.schema.ts b/src/CenterStaff/centerstaff.schema.ts index bf865b0..8e7dd73 100644 --- a/src/CenterStaff/centerstaff.schema.ts +++ b/src/CenterStaff/centerstaff.schema.ts @@ -7,12 +7,15 @@ import { } from '@smatch-corp/nestjs-pothos'; import { Builder } from '../Graphql/graphql.builder'; import { PrismaService } from '../Prisma/prisma.service'; - +import { MailService } from '../Mail/mail.service'; +import { JwtUtils } from '../common/utils/jwt.utils'; @Injectable() export class CenterStaffSchema extends PothosSchema { constructor( @Inject(SchemaBuilderToken) private readonly builder: Builder, private readonly prisma: PrismaService, + private readonly mailService: MailService, + private readonly jwtUtils: JwtUtils, ) { super(); } @@ -121,6 +124,45 @@ export class CenterStaffSchema extends PothosSchema { }); }, }), + inviteCenterStaff: t.prismaField({ + type: this.centerStaff(), + description: 'Invite a new center staff member.', + args: { + email: t.arg({ type: 'String', required: true }), + }, + resolve: async (query, root, args, ctx, info) => { + return this.prisma.$transaction(async (prisma) => { + // get centerId by user id from context + const userId = ctx.me.id; + if (!userId) { + throw new Error('User ID is required'); + } + // 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.sendEmail( + args.email, + 'Invite to center', + `You are invited to join the center ${center.name}. + Please click the link below to join the center: + ${inviteUrl}`, + ); + return null; + }); + }, + }), })); } } diff --git a/src/Graphql/graphql.builder.ts b/src/Graphql/graphql.builder.ts index 0cd4f35..26cbd3f 100644 --- a/src/Graphql/graphql.builder.ts +++ b/src/Graphql/graphql.builder.ts @@ -33,6 +33,7 @@ export interface SchemaContext { generator: PrismaCrudGenerator; } +// extend prisma types to contain string type export interface SchemaBuilderOption { Context: SchemaContext; PrismaTypes: PrismaTypes; diff --git a/src/Service/service.schema.ts b/src/Service/service.schema.ts index 923f530..9a0a1e3 100644 --- a/src/Service/service.schema.ts +++ b/src/Service/service.schema.ts @@ -241,10 +241,7 @@ export class ServiceSchema extends PothosSchema { if (!service) { throw new Error('Service not found'); } - if ( - service.status === ServiceStatus.APPROVED || - service.status === ServiceStatus.REJECTED - ) { + if (service.status !== ServiceStatus.PENDING) { throw new Error('Service is already approved or rejected'); } // update service status diff --git a/src/common/common.module.ts b/src/common/common.module.ts index 2553cd4..fd288a3 100644 --- a/src/common/common.module.ts +++ b/src/common/common.module.ts @@ -1,9 +1,11 @@ -import { Module } from '@nestjs/common'; -import { CommonGraphqlError } from './graphql/common.graphql.error'; +import { Global, Module } from '@nestjs/common'; +import { CommonGraphqlError } from './graphql/common.graphql.error'; +import { JwtUtils } from './utils/jwt.utils'; +@Global() @Module({ imports: [], - providers: [CommonGraphqlError], - exports: [CommonGraphqlError], + providers: [CommonGraphqlError, JwtUtils], + exports: [CommonGraphqlError, JwtUtils], }) export class CommonModule {} diff --git a/src/common/utils/jwt.utils.ts b/src/common/utils/jwt.utils.ts new file mode 100644 index 0000000..af2b7c4 --- /dev/null +++ b/src/common/utils/jwt.utils.ts @@ -0,0 +1,29 @@ +import { sign, verify } from 'jsonwebtoken'; + +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class JwtUtils { + signToken(payload: string, expiresIn: string) { + return sign(payload, process.env.JWT_SECRET!, { expiresIn }); + } + //eslint-disable-next-line @typescript-eslint/no-explicit-any + signTokenRS256(payload: any, expiresIn: string) { + const privateKey = process.env.JWT_RS256_PRIVATE_KEY!; + return sign(payload, privateKey, { + algorithm: 'RS256', + expiresIn, + }); + } + + verifyTokenRS256(token: string) { + const publicKey = process.env.JWT_RS256_PUBLIC_KEY!; + return verify(token, publicKey, { + algorithms: ['RS256'], + }); + } + + verifyToken(token: string) { + return verify(token, process.env.JWT_SECRET!); + } +}