Files
hoppscotch/packages/hoppscotch-backend/src/team-collection/team-collection.resolver.ts
Balu Babu a938be3712 feat: Introducing user-collections into self-host (HBE-98) (#18)
* feat: team module added

* feat: teamEnvironment module added

* feat: teamCollection module added

* feat: team request module added

* feat: team invitation module added

* feat: selfhost auth frontend (#15)

Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>

* feat: bringing shortcodes from central to selfhost

* chore: added review changes in resolver

* chore: commented out subscriptions

* chore: bump backend prettier version

* feat: created new user-collections module with base files

* feat: added new models for user-collection and user-request tables in schema.prisma file

* feat: mutations to create user-collections complete

* feat: added user field resolver for userCollections

* feat: added parent field resolver for userCollections

* feat: added child field resolver with pagination for userCollections

* feat: added query to fetch root user-collections with pagination for userCollections

* feat: added query to fetch user-collections for userCollections

* feat: added mutation to rename user-collections

* feat: added mutation to delete user-collections

* feat: added mutation to delete user-collections

* refactor: changed the way we fetch root and child user-collection counts for other operations

* feat: added mutation to move user-collections between root and other child collections

* refactor: abstracted orderIndex update logic into helpert function

* chore: mutation to update order root user-collections complete

* feat: user-collections order can be updated when moving it to the end of list

* feat: user-collections order update feature complete

* feat: subscriptions for user-collection module complete

* chore: removed all console.logs from user-collection.service file

* test: added tests for all field resolvers for user-collection module

* test: test cases for getUserCollection is complete

* test: test cases for getUserRootCollections is complete

* test: test cases for createUserCollection is complete

* test: test cases for renameCollection is complete

* test: test cases for moveUserCollection is complete

* test: test cases for updateUserCollectionOrder is complete

* chore: added createdOn and updatedOn fields to userCollections and userRequests schema

* chore: created function to check if title are of valid size

* refactor: simplified user-collection creation code

* chore: made changed requested in initial PR review

* chore: added requestType enum to user-collections

* refactor: created two seperate queries to fetch root REST or GQL queries

* chore: created seperate mutations and queries for REST and GQL root/child collections

* chore: migrated all input args classess into a single file

* chore: modified createUserCollection service method to work with different creation inputs args type

* chore: rewrote all test cases for user-collections service methods with new CollType

* fix: added updated and deleted subscription changes

* fix: made all the changes requested in the initial PR review

* fix: made all the changes requested in the second PR review

* chore: removed migrations from prisma directory

* fix: made all the changes requested in the third PR review

* chore: added collection type checking to updateUserCollectionOrder service method

* chore: refactored all test cases to reflect new additions to service methods

* chore: fixed issues with pnpm-lock

* chore: removed migrations from prisma directory

* chore: hopefully fixed pnpm-lock issues

* chore: removed console logs in auth controller

---------

Co-authored-by: Mir Arif Hasan <arif.ishan05@gmail.com>
Co-authored-by: Akash K <57758277+amk-dev@users.noreply.github.com>
Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
Co-authored-by: ankitsridhar16 <ankit.sridhar16@gmail.com>
2023-03-03 15:03:05 +05:30

418 lines
12 KiB
TypeScript

import {
Resolver,
ResolveField,
Parent,
Args,
Query,
Mutation,
Subscription,
ID,
} from '@nestjs/graphql';
import { TeamCollection } from './team-collection.model';
import { Team, TeamMemberRole } from '../team/team.model';
import { TeamCollectionService } from './team-collection.service';
import { GqlAuthGuard } from '../guards/gql-auth.guard';
import { GqlTeamMemberGuard } from '../team/guards/gql-team-member.guard';
import { UseGuards } from '@nestjs/common';
import { RequiresTeamRole } from '../team/decorators/requires-team-role.decorator';
import { GqlCollectionTeamMemberGuard } from './guards/gql-collection-team-member.guard';
import { PubSubService } from 'src/pubsub/pubsub.service';
@Resolver(() => TeamCollection)
export class TeamCollectionResolver {
constructor(
private readonly teamCollectionService: TeamCollectionService,
private readonly pubsub: PubSubService,
) {}
// Field resolvers
@ResolveField(() => Team, {
description: 'Team the collection belongs to',
complexity: 5,
})
team(@Parent() collection: TeamCollection): Promise<Team> {
return this.teamCollectionService.getTeamOfCollection(collection.id);
}
@ResolveField(() => TeamCollection, {
description:
'The collection who is the parent of this collection (null if this is root collection)',
nullable: true,
complexity: 3,
})
parent(@Parent() collection: TeamCollection): Promise<TeamCollection | null> {
return this.teamCollectionService.getParentOfCollection(collection.id);
}
@ResolveField(() => [TeamCollection], {
description: 'List of children collection',
complexity: 3,
})
children(
@Parent() collection: TeamCollection,
@Args({
name: 'cursor',
nullable: true,
description: 'ID of the last returned collection (for pagination)',
})
cursor?: string,
): Promise<TeamCollection[]> {
return this.teamCollectionService.getChildrenOfCollection(
collection.id,
cursor ?? null,
);
}
// Queries
// @Query(() => String, {
// description:
// 'Returns the JSON string giving the collections and their contents of the team',
// })
// @UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
// @RequiresTeamRole(
// TeamMemberRole.VIEWER,
// TeamMemberRole.EDITOR,
// TeamMemberRole.OWNER,
// )
// exportCollectionsToJSON(
// @Args({ name: 'teamID', description: 'ID of the team', type: () => ID }) teamID: string,
// ): Promise<string> {
// return this.teamCollectionService.exportCollectionsToJSON(teamID);
// }
@Query(() => [TeamCollection], {
description: 'Returns the collections of the team',
})
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.VIEWER,
TeamMemberRole.EDITOR,
TeamMemberRole.OWNER,
)
rootCollectionsOfTeam(
@Args({ name: 'teamID', description: 'ID of the team', type: () => ID })
teamID: string,
@Args({
name: 'cursor',
nullable: true,
type: () => ID,
description: 'ID of the last returned collection (for pagination)',
})
cursor?: string,
): Promise<TeamCollection[]> {
return this.teamCollectionService.getTeamRootCollections(
teamID,
cursor ?? null,
);
}
@Query(() => [TeamCollection], {
description: 'Returns the collections of the team',
deprecationReason:
'Deprecated because of no practical use. Use `rootCollectionsOfTeam` instead.',
})
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.VIEWER,
TeamMemberRole.EDITOR,
TeamMemberRole.OWNER,
)
collectionsOfTeam(
@Args({ name: 'teamID', description: 'ID of the team', type: () => ID })
teamID: string,
@Args({
name: 'cursor',
type: () => ID,
nullable: true,
description: 'ID of the last returned collection (for pagination)',
})
cursor?: string,
): Promise<TeamCollection[]> {
return this.teamCollectionService.getTeamCollections(
teamID,
cursor ?? null,
);
}
@Query(() => TeamCollection, {
description: 'Get a collection with the given ID or null (if not exists)',
nullable: true,
})
@UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard)
@RequiresTeamRole(
TeamMemberRole.VIEWER,
TeamMemberRole.EDITOR,
TeamMemberRole.OWNER,
)
collection(
@Args({
name: 'collectionID',
description: 'ID of the collection',
type: () => ID,
})
collectionID: string,
): Promise<TeamCollection | null> {
return this.teamCollectionService.getCollection(collectionID);
}
// Mutations
@Mutation(() => TeamCollection, {
description:
'Creates a collection at the root of the team hierarchy (no parent collection)',
})
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
createRootCollection(
@Args({ name: 'teamID', description: 'ID of the team', type: () => ID })
teamID: string,
@Args({ name: 'title', description: 'Title of the new collection' })
title: string,
): Promise<TeamCollection> {
return this.teamCollectionService.createCollection(teamID, title, null);
}
// @Mutation(() => TeamCollection, {
// description: 'Import collection from user firestore',
// })
// @UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
// @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
// importCollectionFromUserFirestore(
// @Args({
// name: 'teamID',
// type: () => ID,
// description: 'ID of the team to add to',
// })
// teamID: string,
// @Args({
// name: 'fbCollectionPath',
// description:
// 'slash separated array indicies path to the target collection',
// })
// fbCollectionPath: string,
// @GqlUser() user: User,
// @Args({
// name: 'parentCollectionID',
// type: () => ID,
// description:
// 'ID to the collection which is going to be parent to the result (null if root)',
// nullable: true,
// })
// parentCollectionID?: string
// ): Promise<TeamCollection> {
// if (parentCollectionID) {
// return this.teamCollectionService.importCollectionFromFirestore(
// user.uid,
// fbCollectionPath,
// teamID,
// parentCollectionID,
// );
// } else {
// return this.teamCollectionService.importCollectionFromFirestore(
// user.uid,
// fbCollectionPath,
// teamID,
// );
// }
// }
// @Mutation(() => Boolean, {
// description: 'Import collections from JSON string to the specified Team',
// })
// @UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
// @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
// async importCollectionsFromJSON(
// @Args({
// name: 'teamID',
// type: () => ID,
// description: 'Id of the team to add to',
// })
// teamID: string,
// @Args({
// name: 'jsonString',
// description: 'JSON string to import',
// })
// jsonString: string,
// @Args({
// name: 'parentCollectionID',
// type: () => ID,
// description:
// 'ID to the collection to which to import to (null if to import to the root of team)',
// nullable: true,
// })
// parentCollectionID?: string,
// ): Promise<boolean> {
// await this.teamCollectionService.importCollectionsFromJSON(
// jsonString,
// teamID,
// parentCollectionID ?? null,
// );
// return true;
// }
// @Mutation(() => Boolean, {
// description:
// 'Replace existing collections of a specific team with collections in JSON string',
// })
// @UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
// @RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
// async replaceCollectionsWithJSON(
// @Args({
// name: 'teamID',
// type: () => ID,
// description: 'Id of the team to add to',
// })
// teamID: string,
// @Args({
// name: 'jsonString',
// description: 'JSON string to replace with',
// })
// jsonString: string,
// @Args({
// name: 'parentCollectionID',
// type: () => ID,
// description:
// 'ID to the collection to which to import to (null if to import to the root of team)',
// nullable: true,
// })
// parentCollectionID?: string,
// ): Promise<boolean> {
// await this.teamCollectionService.replaceCollectionsWithJSON(
// jsonString,
// teamID,
// parentCollectionID ?? null,
// );
// return true;
// }
@Mutation(() => TeamCollection, {
description: 'Create a collection that has a parent collection',
})
@UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard)
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
async createChildCollection(
@Args({
name: 'collectionID',
type: () => ID,
description: 'ID of the parent to the new collection',
})
collectionID: string,
@Args({ name: 'childTitle', description: 'Title of the new collection' })
childTitle: string,
): Promise<TeamCollection> {
const team = await this.teamCollectionService.getTeamOfCollection(
collectionID,
);
return await this.teamCollectionService.createCollection(
team.id,
childTitle,
collectionID,
);
}
@Mutation(() => TeamCollection, {
description: 'Rename a collection',
})
@UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard)
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
renameCollection(
@Args({
name: 'collectionID',
description: 'ID of the collection',
type: () => ID,
})
collectionID: string,
@Args({
name: 'newTitle',
description: 'The updated title of the collection',
})
newTitle: string,
): Promise<TeamCollection> {
return this.teamCollectionService.renameCollection(collectionID, newTitle);
}
@Mutation(() => Boolean, {
description: 'Delete a collection',
})
@UseGuards(GqlAuthGuard, GqlCollectionTeamMemberGuard)
@RequiresTeamRole(TeamMemberRole.OWNER, TeamMemberRole.EDITOR)
async deleteCollection(
@Args({
name: 'collectionID',
description: 'ID of the collection',
type: () => ID,
})
collectionID: string,
): Promise<boolean> {
this.teamCollectionService.deleteCollection(collectionID);
return true;
}
// Subscriptions
@Subscription(() => TeamCollection, {
description:
'Listen to when a collection has been added to a team. The emitted value is the team added',
resolve: (value) => value,
})
@RequiresTeamRole(
TeamMemberRole.OWNER,
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
teamCollectionAdded(
@Args({
name: 'teamID',
description: 'ID of the team to listen to',
type: () => ID,
})
teamID: string,
): AsyncIterator<TeamCollection> {
return this.pubsub.asyncIterator(`team_coll/${teamID}/coll_added`);
}
@Subscription(() => TeamCollection, {
description: 'Listen to when a collection has been updated.',
resolve: (value) => value,
})
@RequiresTeamRole(
TeamMemberRole.OWNER,
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
teamCollectionUpdated(
@Args({
name: 'teamID',
description: 'ID of the team to listen to',
type: () => ID,
})
teamID: string,
): AsyncIterator<TeamCollection> {
return this.pubsub.asyncIterator(`team_coll/${teamID}/coll_updated`);
}
@Subscription(() => ID, {
description: 'Listen to when a collection has been removed',
resolve: (value) => value,
})
@RequiresTeamRole(
TeamMemberRole.OWNER,
TeamMemberRole.EDITOR,
TeamMemberRole.VIEWER,
)
@UseGuards(GqlAuthGuard, GqlTeamMemberGuard)
teamCollectionRemoved(
@Args({
name: 'teamID',
description: 'ID of the team to listen to',
type: () => ID,
})
teamID: string,
): AsyncIterator<TeamCollection> {
return this.pubsub.asyncIterator(`team_coll/${teamID}/coll_removed`);
}
}