Files
hoppscotch/packages/hoppscotch-backend/src/team-invitation/team-invitation.resolver.ts
2023-07-13 11:58:03 +05:30

201 lines
6.0 KiB
TypeScript

import {
Args,
ID,
Mutation,
Parent,
Query,
ResolveField,
Resolver,
Subscription,
} from '@nestjs/graphql';
import { TeamInvitation } from './team-invitation.model';
import { TeamInvitationService } from './team-invitation.service';
import { pipe } from 'fp-ts/function';
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 { GqlUser } from 'src/decorators/gql-user.decorator';
import { User } from 'src/user/user.model';
import { UseGuards } from '@nestjs/common';
import { GqlAuthGuard } from '../guards/gql-auth.guard';
import { TeamService } from 'src/team/team.service';
import { throwErr } from 'src/utils';
import { TeamInviteeGuard } from './team-invitee.guard';
import { GqlTeamMemberGuard } from 'src/team/guards/gql-team-member.guard';
import { RequiresTeamRole } from 'src/team/decorators/requires-team-role.decorator';
import { TeamInviteViewerGuard } from './team-invite-viewer.guard';
import { TeamInviteTeamOwnerGuard } from './team-invite-team-owner.guard';
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';
import { CreateTeamInvitationArgs } from './input-type.args';
@UseGuards(GqlThrottlerGuard)
@Resolver(() => TeamInvitation)
export class TeamInvitationResolver {
constructor(
private readonly userService: UserService,
private readonly teamService: TeamService,
private readonly teamInvitationService: TeamInvitationService,
private readonly pubsub: PubSubService,
) {}
@ResolveField(() => Team, {
complexity: 5,
description: 'Get the team associated to the invite',
})
async team(@Parent() teamInvitation: TeamInvitation): Promise<Team> {
return pipe(
this.teamService.getTeamWithIDTE(teamInvitation.teamID),
TE.getOrElse(throwErr),
)();
}
@ResolveField(() => User, {
complexity: 5,
description: 'Get the creator of the invite',
})
async creator(@Parent() teamInvitation: TeamInvitation): Promise<User> {
const user = await this.userService.findUserById(teamInvitation.creatorUid);
if (O.isNone(user)) throwErr(USER_NOT_FOUND);
return {
...user.value,
currentGQLSession: JSON.stringify(user.value.currentGQLSession),
currentRESTSession: JSON.stringify(user.value.currentRESTSession),
};
}
@Query(() => TeamInvitation, {
description:
'Gets the Team Invitation with the given ID, or null if not exists',
})
@UseGuards(GqlAuthGuard, TeamInviteViewerGuard)
async teamInvitation(
@GqlUser() user: AuthUser,
@Args({
name: 'inviteID',
description: 'ID of the Team Invitation to lookup',
type: () => ID,
})
inviteID: string,
): Promise<TeamInvitation> {
const teamInvitation = await this.teamInvitationService.getInvitation(
inviteID,
);
if (O.isNone(teamInvitation)) throwErr(TEAM_INVITE_NO_INVITE_FOUND);
return teamInvitation.value;
}
@Mutation(() => TeamInvitation, {
description: 'Creates a Team Invitation',
})
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(TeamMemberRole.OWNER)
async createTeamInvitation(
@GqlUser() user: AuthUser,
@Args() args: CreateTeamInvitationArgs,
): Promise<TeamInvitation> {
const teamInvitation = await this.teamInvitationService.createInvitation(
user,
args.teamID,
args.inviteeEmail,
args.inviteeRole,
);
if (E.isLeft(teamInvitation)) throwErr(teamInvitation.left);
return teamInvitation.right;
}
@Mutation(() => Boolean, {
description: 'Revokes an invitation and deletes it',
})
@UseGuards(GqlAuthGuard, TeamInviteTeamOwnerGuard)
@RequiresTeamRole(TeamMemberRole.OWNER)
async revokeTeamInvitation(
@Args({
name: 'inviteID',
type: () => ID,
description: 'ID of the invite to revoke',
})
inviteID: string,
): Promise<true> {
const isRevoked = await this.teamInvitationService.revokeInvitation(
inviteID,
);
if (E.isLeft(isRevoked)) throwErr(isRevoked.left);
return true;
}
@Mutation(() => TeamMember, {
description: 'Accept an Invitation',
})
@UseGuards(GqlAuthGuard, TeamInviteeGuard)
async acceptTeamInvitation(
@GqlUser() user: AuthUser,
@Args({
name: 'inviteID',
type: () => ID,
description: 'ID of the Invite to accept',
})
inviteID: string,
): Promise<TeamMember> {
const teamMember = await this.teamInvitationService.acceptInvitation(
inviteID,
user,
);
if (E.isLeft(teamMember)) throwErr(teamMember.left);
return teamMember.right;
}
// Subscriptions
@Subscription(() => TeamInvitation, {
description: 'Listens to when a Team Invitation is added',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.OWNER,
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
teamInvitationAdded(
@Args({
name: 'teamID',
type: () => ID,
description: 'ID of the Team to listen to',
})
teamID: string,
): AsyncIterator<TeamInvitation> {
return this.pubsub.asyncIterator(`team/${teamID}/invite_added`);
}
@Subscription(() => ID, {
description: 'Listens to when a Team Invitation is removed',
resolve: (value) => value,
})
@SkipThrottle()
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.OWNER,
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
teamInvitationRemoved(
@Args({
name: 'teamID',
type: () => ID,
description: 'ID of the Team to listen to',
})
teamID: string,
): AsyncIterator<string> {
return this.pubsub.asyncIterator(`team/${teamID}/invite_removed`);
}
}