* 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>
418 lines
12 KiB
TypeScript
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`);
|
|
}
|
|
}
|