From f13d00465d9eefb534f0bc5e8ec177f44ae0fae6 Mon Sep 17 00:00:00 2001 From: Balu Babu Date: Fri, 1 Dec 2023 12:59:34 +0530 Subject: [PATCH] feat: completed addition of new data field in TeamCollection --- docker-compose.yml | 4 +- packages/hoppscotch-backend/src/errors.ts | 7 ++ .../src/team-collection/input-type.args.ts | 36 +++++++ .../team-collection/team-collection.model.ts | 6 ++ .../team-collection.resolver.ts | 30 +++++- .../team-collection.service.ts | 93 ++++++++++++++++--- .../src/types/CollectionFolder.ts | 1 + 7 files changed, 162 insertions(+), 15 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 37af3d1fe..d2506f4cc 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -111,7 +111,7 @@ services: build: dockerfile: packages/hoppscotch-backend/Dockerfile context: . - target: prod + target: dev env_file: - ./.env restart: always @@ -121,7 +121,7 @@ services: - PORT=3000 volumes: # Uncomment the line below when modifying code. Only applicable when using the "dev" target. - # - ./packages/hoppscotch-backend/:/usr/src/app + - ./packages/hoppscotch-backend/:/usr/src/app - /usr/src/app/node_modules/ depends_on: hoppscotch-db: diff --git a/packages/hoppscotch-backend/src/errors.ts b/packages/hoppscotch-backend/src/errors.ts index 2a98aef38..8f305b9b2 100644 --- a/packages/hoppscotch-backend/src/errors.ts +++ b/packages/hoppscotch-backend/src/errors.ts @@ -254,6 +254,13 @@ export const TEAM_COLL_INVALID_JSON = 'team_coll/invalid_json'; */ export const TEAM_NOT_OWNER = 'team_coll/team_not_owner' as const; +/** + * The Team Collection data is not valid + * (TeamCollectionService) + */ +export const TEAM_COLL_DATA_INVALID = + 'team_coll/team_coll_data_invalid' as const; + /** * Tried to perform an action on a request that doesn't accept their member role level * (GqlRequestTeamMemberGuard) diff --git a/packages/hoppscotch-backend/src/team-collection/input-type.args.ts b/packages/hoppscotch-backend/src/team-collection/input-type.args.ts index 1dbd7c91f..edd696e8a 100644 --- a/packages/hoppscotch-backend/src/team-collection/input-type.args.ts +++ b/packages/hoppscotch-backend/src/team-collection/input-type.args.ts @@ -14,6 +14,12 @@ export class CreateRootTeamCollectionArgs { @Field({ name: 'title', description: 'Title of the new collection' }) title: string; + + @Field({ + name: 'data', + description: 'JSON string representing the collection data', + }) + data: string; } @ArgsType() @@ -26,6 +32,12 @@ export class CreateChildTeamCollectionArgs { @Field({ name: 'childTitle', description: 'Title of the new collection' }) childTitle: string; + + @Field({ + name: 'data', + description: 'JSON string representing the collection data', + }) + data: string; } @ArgsType() @@ -33,12 +45,14 @@ export class RenameTeamCollectionArgs { @Field(() => ID, { name: 'collectionID', description: 'ID of the collection', + deprecationReason: 'Switch to updateTeamCollection mutation instead', }) collectionID: string; @Field({ name: 'newTitle', description: 'The updated title of the collection', + deprecationReason: 'Switch to updateTeamCollection mutation instead', }) newTitle: string; } @@ -98,3 +112,25 @@ export class ReplaceTeamCollectionArgs { }) parentCollectionID?: string; } + +@ArgsType() +export class UpdateTeamCollectionArgs { + @Field(() => ID, { + name: 'collectionID', + description: 'ID of the collection', + }) + collectionID: string; + + @Field({ + name: 'newTitle', + description: 'The updated title of the collection', + nullable: true, + }) + newTitle: string; + + @Field({ + name: 'data', + description: 'JSON string representing the collection data', + }) + data: string; +} diff --git a/packages/hoppscotch-backend/src/team-collection/team-collection.model.ts b/packages/hoppscotch-backend/src/team-collection/team-collection.model.ts index f45784327..df1edac10 100644 --- a/packages/hoppscotch-backend/src/team-collection/team-collection.model.ts +++ b/packages/hoppscotch-backend/src/team-collection/team-collection.model.ts @@ -12,6 +12,12 @@ export class TeamCollection { }) title: string; + @Field({ + description: 'JSON string representing the collection data', + nullable: true, + }) + data: string; + @Field(() => ID, { description: 'ID of the collection', nullable: true, diff --git a/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts b/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts index 7ba886884..f5a2ccfb8 100644 --- a/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts +++ b/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts @@ -25,6 +25,7 @@ import { MoveTeamCollectionArgs, RenameTeamCollectionArgs, ReplaceTeamCollectionArgs, + UpdateTeamCollectionArgs, UpdateTeamCollectionOrderArgs, } from './input-type.args'; import * as E from 'fp-ts/Either'; @@ -141,7 +142,14 @@ export class TeamCollectionResolver { ); if (E.isLeft(teamCollections)) throwErr(teamCollections.left); - return teamCollections.right; + return { + id: teamCollections.right.id, + title: teamCollections.right.title, + parentID: teamCollections.right.parentID, + data: !teamCollections.right.data + ? null + : JSON.stringify(teamCollections.right.data), + }; } // Mutations @@ -155,6 +163,7 @@ export class TeamCollectionResolver { const teamCollection = await this.teamCollectionService.createCollection( args.teamID, args.title, + args.data, null, ); @@ -230,6 +239,7 @@ export class TeamCollectionResolver { const teamCollection = await this.teamCollectionService.createCollection( team.right.id, args.childTitle, + args.data, args.collectionID, ); @@ -239,6 +249,7 @@ export class TeamCollectionResolver { @Mutation(() => TeamCollection, { description: 'Rename a collection', + deprecationReason: 'Switch to updateTeamCollection mutation instead', }) @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) @@ -303,6 +314,23 @@ export class TeamCollectionResolver { return request.right; } + @Mutation(() => TeamCollection, { + description: 'Update Team Collection details', + }) + @UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard) + @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR) + async updateTeamCollection(@Args() args: UpdateTeamCollectionArgs) { + const updatedTeamCollection = + await this.teamCollectionService.updateTeamCollection( + args.collectionID, + args.data, + args.newTitle, + ); + + if (E.isLeft(updatedTeamCollection)) throwErr(updatedTeamCollection.left); + return updatedTeamCollection.right; + } + // Subscriptions @Subscription(() => TeamCollection, { diff --git a/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts b/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts index 01a2ab78a..3841d57e9 100644 --- a/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts +++ b/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts @@ -13,6 +13,7 @@ import { TEAM_COLL_IS_PARENT_COLL, TEAM_COL_SAME_NEXT_COLL, TEAM_COL_REORDERING_FAILED, + TEAM_COLL_DATA_INVALID, } from '../errors'; import { PubSubService } from '../pubsub/pubsub.service'; import { isValidLength } from 'src/utils'; @@ -69,6 +70,7 @@ export class TeamCollectionService { this.generatePrismaQueryObjForFBCollFolder(f, teamID, index + 1), ), }, + data: folder.data ?? undefined, }; } @@ -118,6 +120,7 @@ export class TeamCollectionService { name: collection.right.title, folders: childrenCollectionObjects, requests: requests.map((x) => x.request), + data: JSON.stringify(collection.right.data), }; return E.right(result); @@ -199,7 +202,7 @@ export class TeamCollectionService { ); teamCollections.forEach((x) => - this.pubsub.publish(`team_coll/${destTeamID}/coll_added`, x), + this.pubsub.publish(`team_coll/${destTeamID}/coll_added`, this.cast(x)), ); return E.right(true); @@ -269,7 +272,7 @@ export class TeamCollectionService { ); teamCollections.forEach((x) => - this.pubsub.publish(`team_coll/${destTeamID}/coll_added`, x), + this.pubsub.publish(`team_coll/${destTeamID}/coll_added`, this.cast(x)), ); return E.right(true); @@ -277,11 +280,17 @@ export class TeamCollectionService { /** * Typecast a database TeamCollection to a TeamCollection model + * * @param teamCollection database TeamCollection * @returns TeamCollection model */ private cast(teamCollection: DBTeamCollection): TeamCollection { - return { ...teamCollection }; + return { + id: teamCollection.id, + title: teamCollection.title, + parentID: teamCollection.parentID, + data: !teamCollection.data ? null : JSON.stringify(teamCollection.data), + }; } /** @@ -324,7 +333,7 @@ export class TeamCollectionService { }); if (!teamCollection) return null; - return teamCollection.parent; + return !teamCollection.parent ? null : this.cast(teamCollection.parent); } /** @@ -335,12 +344,12 @@ export class TeamCollectionService { * @param take Number of items we want returned * @returns A list of child collections */ - getChildrenOfCollection( + async getChildrenOfCollection( collectionID: string, cursor: string | null, take: number, ) { - return this.prisma.teamCollection.findMany({ + const res = await this.prisma.teamCollection.findMany({ where: { parentID: collectionID, }, @@ -351,6 +360,12 @@ export class TeamCollectionService { skip: cursor ? 1 : 0, cursor: cursor ? { id: cursor } : undefined, }); + + const childCollections = res.map((teamCollection) => + this.cast(teamCollection), + ); + + return childCollections; } /** @@ -366,7 +381,7 @@ export class TeamCollectionService { cursor: string | null, take: number, ) { - return this.prisma.teamCollection.findMany({ + const res = await this.prisma.teamCollection.findMany({ where: { teamID, parentID: null, @@ -378,6 +393,12 @@ export class TeamCollectionService { skip: cursor ? 1 : 0, cursor: cursor ? { id: cursor } : undefined, }); + + const teamCollections = res.map((teamCollection) => + this.cast(teamCollection), + ); + + return teamCollections; } /** @@ -470,6 +491,7 @@ export class TeamCollectionService { async createCollection( teamID: string, title: string, + data: string, parentTeamCollectionID: string | null, ) { const isTitleValid = isValidLength(title, this.TITLE_LENGTH); @@ -481,6 +503,9 @@ export class TeamCollectionService { if (O.isNone(isOwner)) return E.left(TEAM_NOT_OWNER); } + const collectionData = stringToJson(data); + if (E.isLeft(collectionData)) return E.left(TEAM_COLL_DATA_INVALID); + const isParent = parentTeamCollectionID ? { connect: { @@ -498,18 +523,23 @@ export class TeamCollectionService { }, }, parent: isParent, + data: collectionData.right, orderIndex: !parentTeamCollectionID ? (await this.getRootCollectionsCount(teamID)) + 1 : (await this.getChildCollectionsCount(parentTeamCollectionID)) + 1, }, }); - this.pubsub.publish(`team_coll/${teamID}/coll_added`, teamCollection); + this.pubsub.publish( + `team_coll/${teamID}/coll_added`, + this.cast(teamCollection), + ); return E.right(this.cast(teamCollection)); } /** + * @deprecated Use updateTeamCollection method instead * Update the title of a TeamCollection * * @param collectionID The Collection ID @@ -532,10 +562,10 @@ export class TeamCollectionService { this.pubsub.publish( `team_coll/${updatedTeamCollection.teamID}/coll_updated`, - updatedTeamCollection, + this.cast(updatedTeamCollection), ); - return E.right(updatedTeamCollection); + return E.right(this.cast(updatedTeamCollection)); } catch (error) { return E.left(TEAM_COLL_NOT_FOUND); } @@ -694,8 +724,8 @@ export class TeamCollectionService { * @returns An Option of boolean, is parent or not */ private async isParent( - collection: TeamCollection, - destCollection: TeamCollection, + collection: DBTeamCollection, + destCollection: DBTeamCollection, ): Promise> { //* Recursively check if collection is a parent by going up the tree of child-parent collections until we reach a root collection i.e parentID === null //* Valid condition, isParent returns false @@ -971,4 +1001,43 @@ export class TeamCollectionService { const teamCollectionsCount = this.prisma.teamCollection.count(); return teamCollectionsCount; } + + /** + * Update Team Collection details + * + * @param collectionID Collection ID + * @param collectionData new header data in a JSONified string form + * @param newTitle New title of the collection + * @returns Updated TeamCollection + */ + async updateTeamCollection( + collectionID: string, + collectionData: string, + newTitle: string = null, + ) { + try { + if (collectionData) { + const jsonReq = stringToJson(collectionData); + if (E.isLeft(jsonReq)) return E.left(TEAM_COLL_DATA_INVALID); + collectionData = jsonReq.right; + } + + const updatedTeamCollection = await this.prisma.teamCollection.update({ + where: { id: collectionID }, + data: { + data: collectionData, + title: newTitle ?? undefined, + }, + }); + + this.pubsub.publish( + `team_coll/${updatedTeamCollection.teamID}/coll_updated`, + this.cast(updatedTeamCollection), + ); + + return E.right(this.cast(updatedTeamCollection)); + } catch (e) { + return E.left(TEAM_COLL_NOT_FOUND); + } + } } diff --git a/packages/hoppscotch-backend/src/types/CollectionFolder.ts b/packages/hoppscotch-backend/src/types/CollectionFolder.ts index e7dd7e194..572ed9ad3 100644 --- a/packages/hoppscotch-backend/src/types/CollectionFolder.ts +++ b/packages/hoppscotch-backend/src/types/CollectionFolder.ts @@ -3,4 +3,5 @@ export interface CollectionFolder { folders: CollectionFolder[]; requests: any[]; name: string; + data?: string; }