import { BadRequestException, ForbiddenException, Injectable, NotFoundException, } from '@nestjs/common'; import { OrganizationService } from 'src/organization/organization.service'; import { UserService } from 'src/user/user.service'; import { InviteUserToOrganizationRequestDTO } from './dto'; import { PrismaService } from 'src/prisma/prisma.service'; import { ORGANIZATION_JOIN_REQUEST, ORGANIZATION_JOIN_REQUEST_TYPE, } from 'prisma/generated/prisma/enums'; import { AuthorizationService } from 'src/authorization/authorization.service'; import { USER_ORGANIZATION_OPERATIONS } from 'src/authorization/operations'; import { Prisma } from 'prisma/generated/prisma/client'; @Injectable() export class OrganizationMembershipService { constructor( private readonly userService: UserService, private readonly orgService: OrganizationService, private readonly prisma: PrismaService, private readonly authorization: AuthorizationService, ) {} async inviteUserToOrg( userId: string, dto: InviteUserToOrganizationRequestDTO, ) { const { invitedUserEmail, ...rest } = dto; const [orgExists, invitedUser] = await Promise.all([ this.orgService.findById(dto.orgId), this.userService.findByEmail(invitedUserEmail), ]); if (!orgExists) throw new NotFoundException('Organization'); if (!invitedUser) throw new NotFoundException('User'); const userAlreadyPart = await this.prisma.organizationUserJoinTable.findUnique({ where: { userId_orgId: { orgId: dto.orgId, userId: invitedUser.id, }, }, }); if (userAlreadyPart) throw new BadRequestException('User already part of this organization'); const canInviteUser = await this.authorization.canPerformOperation( userId, dto.orgId, USER_ORGANIZATION_OPERATIONS.INVITE_USERS, ); if (!canInviteUser) throw new ForbiddenException('Insufficient Permission'); try { const invitation = await this.prisma.organizationJoinRequest.create({ data: { ...rest, userId: invitedUser.id, requestType: ORGANIZATION_JOIN_REQUEST_TYPE.INVITED, }, }); return invitation; } catch (err) { if (err instanceof Prisma.PrismaClientKnownRequestError) { if (err.code === 'P2002') throw new BadRequestException('User invitation already sent.'); } else { throw err; } } } requestToJoin() {} // TODO: reject, rejectReason async acceptInvitation(userId: string, orgId: string) { const orgExists = await this.orgService.findById(orgId); if (!orgExists) throw new NotFoundException('Organization'); const [userAlreadyPart, isUserInvited] = await Promise.all([ this.prisma.organizationUserJoinTable.findUnique({ where: { userId_orgId: { orgId, userId, }, }, }), this.prisma.organizationJoinRequest.findUnique({ where: { userId_orgId: { orgId, userId, }, status: ORGANIZATION_JOIN_REQUEST.PENDING, }, }), ]); if (userAlreadyPart) throw new BadRequestException('User already part of this organization'); if (!isUserInvited) throw new BadRequestException( 'User has no invitations from this organization', ); return await this.prisma.$transaction(async (tx) => { await tx.organizationJoinRequest.update({ where: { userId_orgId: { userId, orgId, }, }, data: { status: ORGANIZATION_JOIN_REQUEST.ACCEPTED, }, }); return await tx.organizationUserJoinTable.create({ data: { orgId, userId, }, }); }); } async getUserInvitations( userId: string, requestType: ORGANIZATION_JOIN_REQUEST_TYPE = ORGANIZATION_JOIN_REQUEST_TYPE.INVITED, status: ORGANIZATION_JOIN_REQUEST = ORGANIZATION_JOIN_REQUEST.PENDING, ) { return await this.prisma.organizationJoinRequest.findMany({ where: { userId: userId, status: status, requestType: requestType, }, include: { organization: { select: { name: true }, }, }, }); } async getMemebersOfOrganization(orgId: string) { const orgExists = await this.orgService.findById(orgId); if (!orgExists) throw new NotFoundException('Organization'); return await this.prisma.organizationUserJoinTable.findMany({ where: { orgId: orgId, }, include: { user: { select: { email: true, }, }, }, }); } }