From b58acfe8dc20d23b883f7c03d392da612cf7ab5f Mon Sep 17 00:00:00 2001 From: Mir Arif Hasan Date: Tue, 11 Jul 2023 16:58:17 +0600 Subject: [PATCH] feat: added all feedback --- .../team-invitation.resolver.ts | 12 ++--- .../team-invitation.service.ts | 50 ++++++++++++++----- .../team-invite-team-owner.guard.ts | 3 ++ .../team-invite-viewer.guard.ts | 10 +++- 4 files changed, 54 insertions(+), 21 deletions(-) diff --git a/packages/hoppscotch-backend/src/team-invitation/team-invitation.resolver.ts b/packages/hoppscotch-backend/src/team-invitation/team-invitation.resolver.ts index 2455a5e44..88c941aed 100644 --- a/packages/hoppscotch-backend/src/team-invitation/team-invitation.resolver.ts +++ b/packages/hoppscotch-backend/src/team-invitation/team-invitation.resolver.ts @@ -15,10 +15,7 @@ import * as TE from 'fp-ts/TaskEither'; import * as E from 'fp-ts/Either'; import * as O from 'fp-ts/Option'; import { Team, TeamMember, TeamMemberRole } from 'src/team/team.model'; -import { - TEAM_INVITE_NO_INVITE_FOUND, - USER_NOT_FOUND, -} from 'src/errors'; +import { TEAM_INVITE_NO_INVITE_FOUND, USER_NOT_FOUND } from 'src/errors'; import { GqlUser } from 'src/decorators/gql-user.decorator'; import { User } from 'src/user/user.model'; import { UseGuards } from '@nestjs/common'; @@ -34,6 +31,7 @@ import { UserService } from 'src/user/user.service'; import { PubSubService } from 'src/pubsub/pubsub.service'; import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard'; import { SkipThrottle } from '@nestjs/throttler'; +import { AuthUser } from 'src/types/AuthUser'; @UseGuards(GqlThrottlerGuard) @Resolver(() => TeamInvitation) @@ -78,7 +76,7 @@ export class TeamInvitationResolver { }) @UseGuards(GqlAuthGuard, TeamInviteViewerGuard) async teamInvitation( - @GqlUser() user: User, + @GqlUser() user: AuthUser, @Args({ name: 'inviteID', description: 'ID of the Team Invitation to lookup', @@ -100,7 +98,7 @@ export class TeamInvitationResolver { @RequiresTeamRole(TeamMemberRole.OWNER) async createTeamInvitation( @GqlUser() - user: User, + user: AuthUser, @Args({ name: 'teamID', @@ -156,7 +154,7 @@ export class TeamInvitationResolver { }) @UseGuards(GqlAuthGuard, TeamInviteeGuard) async acceptTeamInvitation( - @GqlUser() user: User, + @GqlUser() user: AuthUser, @Args({ name: 'inviteID', type: () => ID, diff --git a/packages/hoppscotch-backend/src/team-invitation/team-invitation.service.ts b/packages/hoppscotch-backend/src/team-invitation/team-invitation.service.ts index 3f683c065..3b3e2efd7 100644 --- a/packages/hoppscotch-backend/src/team-invitation/team-invitation.service.ts +++ b/packages/hoppscotch-backend/src/team-invitation/team-invitation.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import * as O from 'fp-ts/Option'; import * as E from 'fp-ts/Either'; -import * as TE from 'fp-ts/TaskEither'; import { PrismaService } from 'src/prisma/prisma.service'; import { TeamInvitation as DBTeamInvitation } from '@prisma/client'; import { TeamMember, TeamMemberRole } from 'src/team/team.model'; @@ -15,13 +14,13 @@ import { TEAM_INVITE_MEMBER_HAS_INVITE, TEAM_INVITE_NO_INVITE_FOUND, TEAM_MEMBER_NOT_FOUND, - USER_NOT_FOUND, } from 'src/errors'; import { TeamInvitation } from './team-invitation.model'; import { MailerService } from 'src/mailer/mailer.service'; import { UserService } from 'src/user/user.service'; import { PubSubService } from 'src/pubsub/pubsub.service'; import { validateEmail } from '../utils'; +import { AuthUser } from 'src/types/AuthUser'; @Injectable() export class TeamInvitationService { @@ -32,17 +31,25 @@ export class TeamInvitationService { private readonly mailerService: MailerService, private readonly pubsub: PubSubService, - ) { - this.getInvitation = this.getInvitation.bind(this); - } + ) {} - castToTeamInvitation(dbTeamInvitation: DBTeamInvitation): TeamInvitation { + /** + * Cast a DBTeamInvitation to a TeamInvitation + * @param dbTeamInvitation database TeamInvitation + * @returns TeamInvitation model + */ + cast(dbTeamInvitation: DBTeamInvitation): TeamInvitation { return { ...dbTeamInvitation, inviteeRole: TeamMemberRole[dbTeamInvitation.inviteeRole], }; } + /** + * Get the team invite + * @param inviteID invite id + * @returns an Option of team invitation or none + */ async getInvitation( inviteID: string, ): Promise> { @@ -53,7 +60,7 @@ export class TeamInvitationService { }, }); - return O.some(this.castToTeamInvitation(dbInvitation)); + return O.some(this.cast(dbInvitation)); } catch (e) { return O.none; } @@ -85,8 +92,16 @@ export class TeamInvitationService { } } + /** + * Create a team invitation + * @param creator creator of the invitation + * @param teamID team id + * @param inviteeEmail invitee email + * @param inviteeRole invitee role + * @returns an Either of team invitation or error message + */ async createInvitation( - creator: User, + creator: AuthUser, teamID: string, inviteeEmail: string, inviteeRole: TeamMemberRole, @@ -106,7 +121,7 @@ export class TeamInvitationService { ); if (!teamMemberCreator) return E.left(TEAM_MEMBER_NOT_FOUND); - // invitee email should be a infra user + // Checking to see if the invitee is already part of the team or not const inviteeUser = await this.userService.findUserByEmail(inviteeEmail); if (O.isSome(inviteeUser)) { // invitee should not already a member @@ -143,12 +158,17 @@ export class TeamInvitationService { }, }); - const invitation = this.castToTeamInvitation(dbInvitation); + const invitation = this.cast(dbInvitation); this.pubsub.publish(`team/${invitation.teamID}/invite_added`, invitation); return E.right(invitation); } + /** + * Revoke a team invitation + * @param inviteID invite id + * @returns an Either of true or error message + */ async revokeInvitation( inviteID: string, ): Promise | E.Right> { @@ -171,9 +191,15 @@ export class TeamInvitationService { return E.right(true); } + /** + * Accept a team invitation + * @param inviteID invite id + * @param acceptedBy user who accepted the invitation + * @returns an Either of team member or error message + */ async acceptInvitation( inviteID: string, - acceptedBy: User, + acceptedBy: AuthUser, ): Promise | E.Right> { // check if the invite exists const invitation = await this.getInvitation(inviteID); @@ -224,7 +250,7 @@ export class TeamInvitationService { }); const invitations: TeamInvitation[] = dbInvitations.map((dbInvitation) => - this.castToTeamInvitation(dbInvitation), + this.cast(dbInvitation), ); return invitations; diff --git a/packages/hoppscotch-backend/src/team-invitation/team-invite-team-owner.guard.ts b/packages/hoppscotch-backend/src/team-invitation/team-invite-team-owner.guard.ts index be875a5ec..571872eab 100644 --- a/packages/hoppscotch-backend/src/team-invitation/team-invite-team-owner.guard.ts +++ b/packages/hoppscotch-backend/src/team-invitation/team-invite-team-owner.guard.ts @@ -13,6 +13,9 @@ import { import { throwErr } from 'src/utils'; import { TeamMemberRole } from 'src/team/team.model'; +/** + * This guard only allows team owner to execute the resolver + */ @Injectable() export class TeamInviteTeamOwnerGuard implements CanActivate { constructor( diff --git a/packages/hoppscotch-backend/src/team-invitation/team-invite-viewer.guard.ts b/packages/hoppscotch-backend/src/team-invitation/team-invite-viewer.guard.ts index a971fff62..8be6f8a99 100644 --- a/packages/hoppscotch-backend/src/team-invitation/team-invite-viewer.guard.ts +++ b/packages/hoppscotch-backend/src/team-invitation/team-invite-viewer.guard.ts @@ -11,6 +11,13 @@ import { import { throwErr } from 'src/utils'; import { TeamService } from 'src/team/team.service'; +/** + * This guard only allows user to execute the resolver + * 1. If user is invitee, allow + * 2. Or else, if user is team member, allow + * + * TLDR: Allow if user is invitee or team member + */ @Injectable() export class TeamInviteViewerGuard implements CanActivate { constructor( @@ -33,8 +40,7 @@ export class TeamInviteViewerGuard implements CanActivate { const invitation = await this.teamInviteService.getInvitation(inviteID); if (O.isNone(invitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND); - // Check if the user and the invite email match, else if we can resolver the user as a team member - // any better solution ? + // Check if the user and the invite email match, else if user is a team member if ( user.email?.toLowerCase() !== invitation.value.inviteeEmail.toLowerCase() ) {