feat: team request module added
This commit is contained in:
@@ -11,6 +11,7 @@ import { subscriptionContextCookieParser } from './auth/helper';
|
|||||||
import { TeamModule } from './team/team.module';
|
import { TeamModule } from './team/team.module';
|
||||||
import { TeamEnvironmentsModule } from './team-environments/team-environments.module';
|
import { TeamEnvironmentsModule } from './team-environments/team-environments.module';
|
||||||
import { TeamCollectionModule } from './team-collection/team-collection.module';
|
import { TeamCollectionModule } from './team-collection/team-collection.module';
|
||||||
|
import { TeamRequestModule } from './team-request/team-request.module';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@@ -51,6 +52,7 @@ import { TeamCollectionModule } from './team-collection/team-collection.module';
|
|||||||
TeamModule,
|
TeamModule,
|
||||||
TeamEnvironmentsModule,
|
TeamEnvironmentsModule,
|
||||||
TeamCollectionModule,
|
TeamCollectionModule,
|
||||||
|
TeamRequestModule,
|
||||||
],
|
],
|
||||||
providers: [GQLComplexityPlugin],
|
providers: [GQLComplexityPlugin],
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { UserHistory } from '../user-history/user-history.model';
|
|||||||
import { TeamMember } from 'src/team/team.model';
|
import { TeamMember } from 'src/team/team.model';
|
||||||
import { TeamEnvironment } from 'src/team-environments/team-environments.model';
|
import { TeamEnvironment } from 'src/team-environments/team-environments.model';
|
||||||
import { TeamCollection } from 'src/team-collection/team-collection.model';
|
import { TeamCollection } from 'src/team-collection/team-collection.model';
|
||||||
|
import { TeamRequest } from 'src/team-request/team-request.model';
|
||||||
|
|
||||||
// A custom message type that defines the topic and the corresponding payload.
|
// A custom message type that defines the topic and the corresponding payload.
|
||||||
// For every module that publishes a subscription add its type def and the possible subscription type.
|
// For every module that publishes a subscription add its type def and the possible subscription type.
|
||||||
@@ -30,4 +31,6 @@ export type TopicDef = {
|
|||||||
| 'coll_removed'}`
|
| 'coll_removed'}`
|
||||||
]: TeamCollection;
|
]: TeamCollection;
|
||||||
[topic: `user_history/${string}/deleted_many`]: number;
|
[topic: `user_history/${string}/deleted_many`]: number;
|
||||||
|
[topic: `team_req/${string}/${'req_created' | 'req_updated'}`]: TeamRequest;
|
||||||
|
[topic: `team_req/${string}/req_deleted`]: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||||
|
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||||
|
import { TeamRequestService } from '../team-request.service';
|
||||||
|
import { TeamService } from '../../team/team.service';
|
||||||
|
import { User } from '../../user/user.model';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { TeamMemberRole } from '../../team/team.model';
|
||||||
|
import {
|
||||||
|
BUG_AUTH_NO_USER_CTX,
|
||||||
|
BUG_TEAM_REQ_NO_REQ_ID,
|
||||||
|
TEAM_REQ_NOT_REQUIRED_ROLE,
|
||||||
|
TEAM_REQ_NOT_MEMBER,
|
||||||
|
} from 'src/errors';
|
||||||
|
import { throwErr } from 'src/utils';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class GqlRequestTeamMemberGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
private readonly reflector: Reflector,
|
||||||
|
private readonly teamRequestService: TeamRequestService,
|
||||||
|
private readonly teamService: TeamService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const requireRoles = this.reflector.get<TeamMemberRole[]>(
|
||||||
|
'requiresTeamRole',
|
||||||
|
context.getHandler(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const gqlExecCtx = GqlExecutionContext.create(context);
|
||||||
|
|
||||||
|
const { user } = gqlExecCtx.getContext().req;
|
||||||
|
if (!user) throw new Error(BUG_AUTH_NO_USER_CTX);
|
||||||
|
|
||||||
|
const { requestID } = gqlExecCtx.getArgs<{ requestID: string }>();
|
||||||
|
if (!requestID) throw new Error(BUG_TEAM_REQ_NO_REQ_ID);
|
||||||
|
|
||||||
|
const team = await this.teamRequestService.getTeamOfRequestFromID(
|
||||||
|
requestID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const member =
|
||||||
|
(await this.teamService.getTeamMember(team.id, user.uid)) ??
|
||||||
|
throwErr(TEAM_REQ_NOT_MEMBER);
|
||||||
|
|
||||||
|
if (requireRoles) {
|
||||||
|
if (requireRoles.includes(member.role)) {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
throw new Error(TEAM_REQ_NOT_REQUIRED_ROLE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (member) return true;
|
||||||
|
|
||||||
|
throw new Error(TEAM_REQ_NOT_MEMBER);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
import { ObjectType, Field, ID, InputType } from '@nestjs/graphql';
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
export class CreateTeamRequestInput {
|
||||||
|
@Field(() => ID, {
|
||||||
|
description: 'ID of the team the collection belongs to',
|
||||||
|
})
|
||||||
|
teamID: string;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
description: 'JSON string representing the request data',
|
||||||
|
})
|
||||||
|
request: string;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
description: 'Displayed title of the request',
|
||||||
|
})
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@InputType()
|
||||||
|
export class UpdateTeamRequestInput {
|
||||||
|
@Field({
|
||||||
|
description: 'JSON string representing the request data',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
request?: string;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
description: 'Displayed title of the request',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
title?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ObjectType()
|
||||||
|
export class TeamRequest {
|
||||||
|
@Field(() => ID, {
|
||||||
|
description: 'ID of the request',
|
||||||
|
})
|
||||||
|
id: string;
|
||||||
|
|
||||||
|
@Field(() => ID, {
|
||||||
|
description: 'ID of the collection the request belongs to.',
|
||||||
|
})
|
||||||
|
collectionID: string;
|
||||||
|
|
||||||
|
@Field(() => ID, {
|
||||||
|
description: 'ID of the team the request belongs to.',
|
||||||
|
})
|
||||||
|
teamID: string;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
description: 'JSON string representing the request data',
|
||||||
|
})
|
||||||
|
request: string;
|
||||||
|
|
||||||
|
@Field({
|
||||||
|
description: 'Displayed title of the request',
|
||||||
|
})
|
||||||
|
title: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TeamRequestService } from './team-request.service';
|
||||||
|
import { TeamRequestResolver } from './team-request.resolver';
|
||||||
|
import { PrismaModule } from '../prisma/prisma.module';
|
||||||
|
import { TeamModule } from '../team/team.module';
|
||||||
|
import { TeamCollectionModule } from '../team-collection/team-collection.module';
|
||||||
|
import { GqlRequestTeamMemberGuard } from './guards/gql-request-team-member.guard';
|
||||||
|
import { UserModule } from '../user/user.module';
|
||||||
|
import { PubSubModule } from 'src/pubsub/pubsub.module';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
PrismaModule,
|
||||||
|
TeamModule,
|
||||||
|
TeamCollectionModule,
|
||||||
|
UserModule,
|
||||||
|
PubSubModule,
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
TeamRequestService,
|
||||||
|
TeamRequestResolver,
|
||||||
|
GqlRequestTeamMemberGuard,
|
||||||
|
],
|
||||||
|
exports: [TeamRequestService, GqlRequestTeamMemberGuard],
|
||||||
|
})
|
||||||
|
export class TeamRequestModule {}
|
||||||
@@ -0,0 +1,282 @@
|
|||||||
|
import {
|
||||||
|
Resolver,
|
||||||
|
ResolveField,
|
||||||
|
Parent,
|
||||||
|
Args,
|
||||||
|
Query,
|
||||||
|
Mutation,
|
||||||
|
Subscription,
|
||||||
|
ID,
|
||||||
|
} from '@nestjs/graphql';
|
||||||
|
import {
|
||||||
|
TeamRequest,
|
||||||
|
CreateTeamRequestInput,
|
||||||
|
UpdateTeamRequestInput,
|
||||||
|
} from './team-request.model';
|
||||||
|
import { Team, TeamMemberRole } from '../team/team.model';
|
||||||
|
import { TeamRequestService } from './team-request.service';
|
||||||
|
import { TeamCollection } from '../team-collection/team-collection.model';
|
||||||
|
import { UseGuards } from '@nestjs/common';
|
||||||
|
import { GqlAuthGuard } from '../guards/gql-auth.guard';
|
||||||
|
import { GqlRequestTeamMemberGuard } from './guards/gql-request-team-member.guard';
|
||||||
|
import { GqlCollectionTeamMemberGuard } from '../team-collection/guards/gql-collection-team-member.guard';
|
||||||
|
import { RequiresTeamRole } from '../team/decorators/requires-team-role.decorator';
|
||||||
|
import { GqlTeamMemberGuard } from '../team/guards/gql-team-member.guard';
|
||||||
|
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||||
|
import { pipe } from 'fp-ts/function';
|
||||||
|
import * as TE from 'fp-ts/TaskEither';
|
||||||
|
import { throwErr } from 'src/utils';
|
||||||
|
|
||||||
|
@Resolver(() => TeamRequest)
|
||||||
|
export class TeamRequestResolver {
|
||||||
|
constructor(
|
||||||
|
private readonly teamRequestService: TeamRequestService,
|
||||||
|
private readonly pubsub: PubSubService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
// Field resolvers
|
||||||
|
@ResolveField(() => Team, {
|
||||||
|
description: 'Team the request belongs to',
|
||||||
|
complexity: 3,
|
||||||
|
})
|
||||||
|
team(@Parent() req: TeamRequest): Promise<Team> {
|
||||||
|
return this.teamRequestService.getTeamOfRequest(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
@ResolveField(() => TeamCollection, {
|
||||||
|
description: 'Collection the request belongs to',
|
||||||
|
complexity: 3,
|
||||||
|
})
|
||||||
|
collection(@Parent() req: TeamRequest): Promise<TeamCollection> {
|
||||||
|
return this.teamRequestService.getCollectionOfRequest(req);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Query
|
||||||
|
@Query(() => [TeamRequest], {
|
||||||
|
description: 'Search the team for a specific request with title',
|
||||||
|
})
|
||||||
|
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
|
||||||
|
searchForRequest(
|
||||||
|
@Args({
|
||||||
|
name: 'teamID',
|
||||||
|
description: 'ID of the team to look in',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
teamID: string,
|
||||||
|
@Args({ name: 'searchTerm', description: 'The title to search for' })
|
||||||
|
searchTerm: string,
|
||||||
|
@Args({
|
||||||
|
name: 'cursor',
|
||||||
|
type: () => ID,
|
||||||
|
description: 'ID of the last returned request or null',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
cursor?: string,
|
||||||
|
): Promise<TeamRequest[]> {
|
||||||
|
return this.teamRequestService.searchRequest(
|
||||||
|
teamID,
|
||||||
|
searchTerm,
|
||||||
|
cursor ?? null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query(() => TeamRequest, {
|
||||||
|
description: 'Gives a request with the given ID or null (if not exists)',
|
||||||
|
nullable: true,
|
||||||
|
})
|
||||||
|
@UseGuards(GqlAuthGuard, GqlRequestTeamMemberGuard)
|
||||||
|
request(
|
||||||
|
@Args({
|
||||||
|
name: 'requestID',
|
||||||
|
description: 'ID of the request',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
requestID: string,
|
||||||
|
): Promise<TeamRequest | null> {
|
||||||
|
return this.teamRequestService.getRequest(requestID);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Query(() => [TeamRequest], {
|
||||||
|
description: 'Gives a list of requests in the collection',
|
||||||
|
})
|
||||||
|
@UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard)
|
||||||
|
@RequiresTeamRole(
|
||||||
|
TeamMemberRole.EDITOR,
|
||||||
|
TeamMemberRole.OWNER,
|
||||||
|
TeamMemberRole.VIEWER,
|
||||||
|
)
|
||||||
|
requestsInCollection(
|
||||||
|
@Args({
|
||||||
|
name: 'collectionID',
|
||||||
|
description: 'ID of the collection',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
collectionID: string,
|
||||||
|
@Args({
|
||||||
|
name: 'cursor',
|
||||||
|
nullable: true,
|
||||||
|
type: () => ID,
|
||||||
|
description: 'ID of the last returned request (for pagination)',
|
||||||
|
})
|
||||||
|
cursor?: string,
|
||||||
|
): Promise<TeamRequest[]> {
|
||||||
|
return this.teamRequestService.getRequestsInCollection(
|
||||||
|
collectionID,
|
||||||
|
cursor ?? null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mutation
|
||||||
|
@Mutation(() => TeamRequest, {
|
||||||
|
description: 'Create a request in the given collection.',
|
||||||
|
})
|
||||||
|
@UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard)
|
||||||
|
@RequiresTeamRole(TeamMemberRole.EDITOR, TeamMemberRole.OWNER)
|
||||||
|
createRequestInCollection(
|
||||||
|
@Args({
|
||||||
|
name: 'collectionID',
|
||||||
|
description: 'ID of the collection',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
collectionID: string,
|
||||||
|
@Args({
|
||||||
|
name: 'data',
|
||||||
|
type: () => CreateTeamRequestInput,
|
||||||
|
description:
|
||||||
|
'The request data (stringified JSON of Hoppscotch request object)',
|
||||||
|
})
|
||||||
|
data: CreateTeamRequestInput,
|
||||||
|
): Promise<TeamRequest> {
|
||||||
|
return this.teamRequestService.createTeamRequest(collectionID, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => TeamRequest, {
|
||||||
|
description: 'Update a request with the given ID',
|
||||||
|
})
|
||||||
|
@UseGuards(GqlAuthGuard, GqlRequestTeamMemberGuard)
|
||||||
|
@RequiresTeamRole(TeamMemberRole.EDITOR, TeamMemberRole.OWNER)
|
||||||
|
updateRequest(
|
||||||
|
@Args({
|
||||||
|
name: 'requestID',
|
||||||
|
description: 'ID of the request',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
requestID: string,
|
||||||
|
@Args({
|
||||||
|
name: 'data',
|
||||||
|
type: () => UpdateTeamRequestInput,
|
||||||
|
description:
|
||||||
|
'The updated request data (stringified JSON of Hoppscotch request object)',
|
||||||
|
})
|
||||||
|
data: UpdateTeamRequestInput,
|
||||||
|
): Promise<TeamRequest> {
|
||||||
|
return this.teamRequestService.updateTeamRequest(requestID, data);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => Boolean, {
|
||||||
|
description: 'Delete a request with the given ID',
|
||||||
|
})
|
||||||
|
@UseGuards(GqlAuthGuard, GqlRequestTeamMemberGuard)
|
||||||
|
@RequiresTeamRole(TeamMemberRole.EDITOR, TeamMemberRole.OWNER)
|
||||||
|
async deleteRequest(
|
||||||
|
@Args({
|
||||||
|
name: 'requestID',
|
||||||
|
description: 'ID of the request',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
requestID: string,
|
||||||
|
): Promise<boolean> {
|
||||||
|
await this.teamRequestService.deleteTeamRequest(requestID);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Mutation(() => TeamRequest, {
|
||||||
|
description: 'Move a request to the given collection',
|
||||||
|
})
|
||||||
|
@UseGuards(GqlAuthGuard, GqlRequestTeamMemberGuard)
|
||||||
|
@RequiresTeamRole(TeamMemberRole.EDITOR, TeamMemberRole.OWNER)
|
||||||
|
moveRequest(
|
||||||
|
@Args({
|
||||||
|
name: 'requestID',
|
||||||
|
description: 'ID of the request to move',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
requestID: string,
|
||||||
|
@Args({
|
||||||
|
name: 'destCollID',
|
||||||
|
description: 'ID of the collection to move the request to',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
destCollID: string,
|
||||||
|
): Promise<TeamRequest> {
|
||||||
|
return pipe(
|
||||||
|
this.teamRequestService.moveRequest(requestID, destCollID),
|
||||||
|
TE.getOrElse((e) => throwErr(e)),
|
||||||
|
)();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subscriptions
|
||||||
|
@Subscription(() => TeamRequest, {
|
||||||
|
description: 'Emits when a new request is added to a team',
|
||||||
|
resolve: (value) => value,
|
||||||
|
})
|
||||||
|
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
|
||||||
|
@RequiresTeamRole(
|
||||||
|
TeamMemberRole.VIEWER,
|
||||||
|
TeamMemberRole.EDITOR,
|
||||||
|
TeamMemberRole.OWNER,
|
||||||
|
)
|
||||||
|
teamRequestAdded(
|
||||||
|
@Args({
|
||||||
|
name: 'teamID',
|
||||||
|
description: 'ID of the team to listen to',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
teamID: string,
|
||||||
|
): AsyncIterator<TeamRequest> {
|
||||||
|
return this.pubsub.asyncIterator(`team_req/${teamID}/req_created`);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscription(() => TeamRequest, {
|
||||||
|
description: 'Emitted when a request has been updated',
|
||||||
|
resolve: (value) => value,
|
||||||
|
})
|
||||||
|
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
|
||||||
|
@RequiresTeamRole(
|
||||||
|
TeamMemberRole.VIEWER,
|
||||||
|
TeamMemberRole.EDITOR,
|
||||||
|
TeamMemberRole.OWNER,
|
||||||
|
)
|
||||||
|
teamRequestUpdated(
|
||||||
|
@Args({
|
||||||
|
name: 'teamID',
|
||||||
|
description: 'ID of the team to listen to',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
teamID: string,
|
||||||
|
): AsyncIterator<TeamRequest> {
|
||||||
|
return this.pubsub.asyncIterator(`team_req/${teamID}/req_updated`);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Subscription(() => ID, {
|
||||||
|
description:
|
||||||
|
'Emitted when a request has been deleted. Only the id of the request is emitted.',
|
||||||
|
resolve: (value) => value,
|
||||||
|
})
|
||||||
|
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
|
||||||
|
@RequiresTeamRole(
|
||||||
|
TeamMemberRole.VIEWER,
|
||||||
|
TeamMemberRole.EDITOR,
|
||||||
|
TeamMemberRole.OWNER,
|
||||||
|
)
|
||||||
|
teamRequestDeleted(
|
||||||
|
@Args({
|
||||||
|
name: 'teamID',
|
||||||
|
description: 'ID of the team to listen to',
|
||||||
|
type: () => ID,
|
||||||
|
})
|
||||||
|
teamID: string,
|
||||||
|
): AsyncIterator<string> {
|
||||||
|
return this.pubsub.asyncIterator(`team_req/${teamID}/req_deleted`);
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,331 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { TeamService } from '../team/team.service';
|
||||||
|
import { PrismaService } from '../prisma/prisma.service';
|
||||||
|
import {
|
||||||
|
TeamRequest,
|
||||||
|
CreateTeamRequestInput,
|
||||||
|
UpdateTeamRequestInput,
|
||||||
|
} from './team-request.model';
|
||||||
|
import { Team } from '../team/team.model';
|
||||||
|
import { TeamCollectionService } from '../team-collection/team-collection.service';
|
||||||
|
import { TeamCollection } from '../team-collection/team-collection.model';
|
||||||
|
import {
|
||||||
|
TEAM_REQ_INVALID_TARGET_COLL_ID,
|
||||||
|
TEAM_INVALID_COLL_ID,
|
||||||
|
TEAM_INVALID_ID,
|
||||||
|
TEAM_REQ_NOT_FOUND,
|
||||||
|
} from 'src/errors';
|
||||||
|
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||||
|
import { throwErr } from 'src/utils';
|
||||||
|
import { pipe } from 'fp-ts/function';
|
||||||
|
import * as TO from 'fp-ts/TaskOption';
|
||||||
|
import * as TE from 'fp-ts/TaskEither';
|
||||||
|
import { Prisma } from '@prisma/client';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class TeamRequestService {
|
||||||
|
constructor(
|
||||||
|
private readonly prisma: PrismaService,
|
||||||
|
private readonly teamService: TeamService,
|
||||||
|
private readonly teamCollectionService: TeamCollectionService,
|
||||||
|
private readonly pubsub: PubSubService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async updateTeamRequest(
|
||||||
|
requestID: string,
|
||||||
|
input: UpdateTeamRequestInput,
|
||||||
|
): Promise<TeamRequest> {
|
||||||
|
const updateData = {};
|
||||||
|
if (input.request) updateData['request'] = JSON.parse(input.request);
|
||||||
|
if (input.title !== null) updateData['title'] = input.title;
|
||||||
|
|
||||||
|
const data = await this.prisma.teamRequest.update({
|
||||||
|
where: {
|
||||||
|
id: requestID,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
...updateData,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result: TeamRequest = {
|
||||||
|
id: data.id,
|
||||||
|
collectionID: data.collectionID,
|
||||||
|
request: JSON.stringify(data.request),
|
||||||
|
title: data.title,
|
||||||
|
teamID: data.teamID,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pubsub.publish(`team_req/${data.teamID}/req_updated`, result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async searchRequest(
|
||||||
|
teamID: string,
|
||||||
|
searchTerm: string,
|
||||||
|
cursor: string | null,
|
||||||
|
): Promise<TeamRequest[]> {
|
||||||
|
if (!cursor) {
|
||||||
|
const data = await this.prisma.teamRequest.findMany({
|
||||||
|
take: 10,
|
||||||
|
where: {
|
||||||
|
teamID,
|
||||||
|
title: {
|
||||||
|
contains: searchTerm,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.map((d) => {
|
||||||
|
return {
|
||||||
|
title: d.title,
|
||||||
|
id: d.id,
|
||||||
|
teamID: d.teamID,
|
||||||
|
collectionID: d.collectionID,
|
||||||
|
request: d.request!.toString(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const data = await this.prisma.teamRequest.findMany({
|
||||||
|
take: 10,
|
||||||
|
skip: 1,
|
||||||
|
cursor: {
|
||||||
|
id: cursor,
|
||||||
|
},
|
||||||
|
where: {
|
||||||
|
teamID,
|
||||||
|
title: {
|
||||||
|
contains: searchTerm,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return data.map((d) => {
|
||||||
|
return {
|
||||||
|
title: d.title,
|
||||||
|
id: d.id,
|
||||||
|
teamID: d.teamID,
|
||||||
|
collectionID: d.collectionID,
|
||||||
|
request: d.request!.toString(),
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteTeamRequest(requestID: string): Promise<void> {
|
||||||
|
const req = await this.getRequest(requestID);
|
||||||
|
|
||||||
|
if (!req) throw new Error(TEAM_REQ_NOT_FOUND);
|
||||||
|
|
||||||
|
await this.prisma.teamRequest.delete({
|
||||||
|
where: {
|
||||||
|
id: requestID,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.pubsub.publish(`team_req/${req.teamID}/req_deleted`, requestID);
|
||||||
|
}
|
||||||
|
|
||||||
|
async createTeamRequest(
|
||||||
|
collectionID: string,
|
||||||
|
input: CreateTeamRequestInput,
|
||||||
|
): Promise<TeamRequest> {
|
||||||
|
const team = await this.teamCollectionService.getTeamOfCollection(
|
||||||
|
collectionID,
|
||||||
|
);
|
||||||
|
|
||||||
|
const data = await this.prisma.teamRequest.create({
|
||||||
|
data: {
|
||||||
|
team: {
|
||||||
|
connect: {
|
||||||
|
id: team.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
request: JSON.parse(input.request),
|
||||||
|
title: input.title,
|
||||||
|
collection: {
|
||||||
|
connect: {
|
||||||
|
id: collectionID,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
id: data.id,
|
||||||
|
collectionID: data.collectionID,
|
||||||
|
title: data.title,
|
||||||
|
request: JSON.stringify(data.request),
|
||||||
|
teamID: data.teamID,
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pubsub.publish(`team_req/${result.teamID}/req_created`, result);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRequestsInCollection(
|
||||||
|
collectionID: string,
|
||||||
|
cursor: string | null,
|
||||||
|
): Promise<TeamRequest[]> {
|
||||||
|
if (!cursor) {
|
||||||
|
const res = await this.prisma.teamRequest.findMany({
|
||||||
|
take: 10,
|
||||||
|
where: {
|
||||||
|
collectionID,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.map((e) => {
|
||||||
|
return {
|
||||||
|
id: e.id,
|
||||||
|
collectionID: e.collectionID,
|
||||||
|
teamID: e.teamID,
|
||||||
|
request: JSON.stringify(e.request),
|
||||||
|
title: e.title,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const res = await this.prisma.teamRequest.findMany({
|
||||||
|
cursor: {
|
||||||
|
id: cursor,
|
||||||
|
},
|
||||||
|
take: 10,
|
||||||
|
skip: 1,
|
||||||
|
where: {
|
||||||
|
collectionID,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.map((e) => {
|
||||||
|
return {
|
||||||
|
id: e.id,
|
||||||
|
collectionID: e.collectionID,
|
||||||
|
teamID: e.teamID,
|
||||||
|
request: JSON.stringify(e.request),
|
||||||
|
title: e.title,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getRequest(reqID: string): Promise<TeamRequest | null> {
|
||||||
|
const res = await this.prisma.teamRequest.findUnique({
|
||||||
|
where: {
|
||||||
|
id: reqID,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!res) return null;
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: res.id,
|
||||||
|
teamID: res.teamID,
|
||||||
|
collectionID: res.collectionID,
|
||||||
|
request: JSON.stringify(res.request),
|
||||||
|
title: res.title,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getRequestTO(reqID: string): TO.TaskOption<TeamRequest> {
|
||||||
|
return pipe(
|
||||||
|
TO.fromTask(() => this.getRequest(reqID)),
|
||||||
|
TO.chain(TO.fromNullable),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTeamOfRequest(req: TeamRequest): Promise<Team> {
|
||||||
|
return (
|
||||||
|
(await this.teamService.getTeamWithID(req.teamID)) ??
|
||||||
|
throwErr(TEAM_INVALID_ID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCollectionOfRequest(req: TeamRequest): Promise<TeamCollection> {
|
||||||
|
return (
|
||||||
|
(await this.teamCollectionService.getCollection(req.collectionID)) ??
|
||||||
|
throwErr(TEAM_INVALID_COLL_ID)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async getTeamOfRequestFromID(reqID: string): Promise<Team> {
|
||||||
|
const req =
|
||||||
|
(await this.prisma.teamRequest.findUnique({
|
||||||
|
where: {
|
||||||
|
id: reqID,
|
||||||
|
},
|
||||||
|
include: {
|
||||||
|
team: true,
|
||||||
|
},
|
||||||
|
})) ?? throwErr(TEAM_REQ_NOT_FOUND);
|
||||||
|
|
||||||
|
return req.team;
|
||||||
|
}
|
||||||
|
|
||||||
|
moveRequest(reqID: string, destinationCollID: string) {
|
||||||
|
return pipe(
|
||||||
|
TE.Do,
|
||||||
|
|
||||||
|
// Check if the request exists
|
||||||
|
TE.bind('request', () =>
|
||||||
|
pipe(
|
||||||
|
this.getRequestTO(reqID),
|
||||||
|
TE.fromTaskOption(() => TEAM_REQ_NOT_FOUND),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Check if the destination collection exists (or null)
|
||||||
|
TE.bindW('targetCollection', () =>
|
||||||
|
pipe(
|
||||||
|
this.teamCollectionService.getCollectionTO(destinationCollID),
|
||||||
|
TE.fromTaskOption(() => TEAM_REQ_INVALID_TARGET_COLL_ID),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Block operation if target collection is not part of the same team
|
||||||
|
// as the request
|
||||||
|
TE.chainW(
|
||||||
|
TE.fromPredicate(
|
||||||
|
({ request, targetCollection }) =>
|
||||||
|
request.teamID === targetCollection.teamID,
|
||||||
|
() => TEAM_REQ_INVALID_TARGET_COLL_ID,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Update the collection
|
||||||
|
TE.chain(({ request, targetCollection }) =>
|
||||||
|
TE.fromTask(() =>
|
||||||
|
this.prisma.teamRequest.update({
|
||||||
|
where: {
|
||||||
|
id: request.id,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
collectionID: targetCollection.id,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
// Generate TeamRequest model object
|
||||||
|
TE.map(
|
||||||
|
(request) =>
|
||||||
|
<TeamRequest>{
|
||||||
|
id: request.id,
|
||||||
|
collectionID: request.collectionID,
|
||||||
|
request: JSON.stringify(request.request),
|
||||||
|
teamID: request.teamID,
|
||||||
|
title: request.title,
|
||||||
|
},
|
||||||
|
),
|
||||||
|
|
||||||
|
// Update on PubSub
|
||||||
|
TE.chainFirst((req) => {
|
||||||
|
this.pubsub.publish(`team_req/${req.teamID}/req_deleted`, req.id);
|
||||||
|
this.pubsub.publish(`team_req/${req.teamID}/req_created`, req);
|
||||||
|
|
||||||
|
return TE.of({}); // We don't care about the return type
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user