feat: teamCollection module added
This commit is contained in:
@@ -10,6 +10,7 @@ import { UserHistoryModule } from './user-history/user-history.module';
|
||||
import { subscriptionContextCookieParser } from './auth/helper';
|
||||
import { TeamModule } from './team/team.module';
|
||||
import { TeamEnvironmentsModule } from './team-environments/team-environments.module';
|
||||
import { TeamCollectionModule } from './team-collection/team-collection.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -49,6 +50,7 @@ import { TeamEnvironmentsModule } from './team-environments/team-environments.mo
|
||||
UserHistoryModule,
|
||||
TeamModule,
|
||||
TeamEnvironmentsModule,
|
||||
TeamCollectionModule,
|
||||
],
|
||||
providers: [GQLComplexityPlugin],
|
||||
})
|
||||
|
||||
@@ -4,6 +4,7 @@ import { UserEnvironment } from '../user-environment/user-environments.model';
|
||||
import { UserHistory } from '../user-history/user-history.model';
|
||||
import { TeamMember } from 'src/team/team.model';
|
||||
import { TeamEnvironment } from 'src/team-environments/team-environments.model';
|
||||
import { TeamCollection } from 'src/team-collection/team-collection.model';
|
||||
|
||||
// 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.
|
||||
@@ -18,10 +19,15 @@ export type TopicDef = {
|
||||
topic: `user_history/${string}/${'created' | 'updated' | 'deleted'}`
|
||||
]: UserHistory;
|
||||
[topic: `team/${string}/member_removed`]: string;
|
||||
[topic: `team/${string}/member_added`]: TeamMember;
|
||||
[topic: `team/${string}/member_updated`]: TeamMember;
|
||||
[topic: `team_environment/${string}/created`]: TeamEnvironment;
|
||||
[topic: `team_environment/${string}/updated`]: TeamEnvironment;
|
||||
[topic: `team_environment/${string}/deleted`]: TeamEnvironment;
|
||||
[topic: `team/${string}/${'member_added' | 'member_updated'}`]: TeamMember;
|
||||
[
|
||||
topic: `team_environment/${string}/${'created' | 'updated' | 'deleted'}`
|
||||
]: TeamEnvironment;
|
||||
[
|
||||
topic: `team_coll/${string}/${
|
||||
| 'coll_added'
|
||||
| 'coll_updated'
|
||||
| 'coll_removed'}`
|
||||
]: TeamCollection;
|
||||
[topic: `user_history/${string}/deleted_many`]: number;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
|
||||
import { Reflector } from '@nestjs/core';
|
||||
import { GqlExecutionContext } from '@nestjs/graphql';
|
||||
import { User } from '../../user/user.model';
|
||||
import { TeamCollectionService } from '../team-collection.service';
|
||||
import { TeamService } from '../../team/team.service';
|
||||
import { TeamMemberRole } from '../../team/team.model';
|
||||
import {
|
||||
BUG_TEAM_NO_REQUIRE_TEAM_ROLE,
|
||||
BUG_AUTH_NO_USER_CTX,
|
||||
BUG_TEAM_COLL_NO_COLL_ID,
|
||||
TEAM_INVALID_COLL_ID,
|
||||
TEAM_REQ_NOT_MEMBER,
|
||||
} from 'src/errors';
|
||||
|
||||
@Injectable()
|
||||
export class GqlCollectionTeamMemberGuard implements CanActivate {
|
||||
constructor(
|
||||
private readonly reflector: Reflector,
|
||||
private readonly teamService: TeamService,
|
||||
private readonly teamCollectionService: TeamCollectionService,
|
||||
) {}
|
||||
|
||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||
const requireRoles = this.reflector.get<TeamMemberRole[]>(
|
||||
'requiresTeamRole',
|
||||
context.getHandler(),
|
||||
);
|
||||
if (!requireRoles) throw new Error(BUG_TEAM_NO_REQUIRE_TEAM_ROLE);
|
||||
|
||||
const gqlExecCtx = GqlExecutionContext.create(context);
|
||||
const { user } = gqlExecCtx.getContext().req;
|
||||
if (user == undefined) throw new Error(BUG_AUTH_NO_USER_CTX);
|
||||
|
||||
const { collectionID } = gqlExecCtx.getArgs<{ collectionID: string }>();
|
||||
if (!collectionID) throw new Error(BUG_TEAM_COLL_NO_COLL_ID);
|
||||
|
||||
const collection = await this.teamCollectionService.getCollection(
|
||||
collectionID,
|
||||
);
|
||||
if (!collection) throw new Error(TEAM_INVALID_COLL_ID);
|
||||
|
||||
const member = await this.teamService.getTeamMember(
|
||||
collection.teamID,
|
||||
user.uid,
|
||||
);
|
||||
if (!member) throw new Error(TEAM_REQ_NOT_MEMBER);
|
||||
|
||||
return requireRoles.includes(member.role);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { ObjectType, Field, ID } from '@nestjs/graphql';
|
||||
|
||||
@ObjectType()
|
||||
export class TeamCollection {
|
||||
@Field(() => ID, {
|
||||
description: 'ID of the collection',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@Field({
|
||||
description: 'Displayed title of the collection',
|
||||
})
|
||||
title: string;
|
||||
|
||||
parentID: string | null;
|
||||
teamID: string;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { PrismaModule } from '../prisma/prisma.module';
|
||||
import { TeamCollectionService } from './team-collection.service';
|
||||
import { TeamCollectionResolver } from './team-collection.resolver';
|
||||
import { GqlCollectionTeamMemberGuard } from './guards/gql-collection-team-member.guard';
|
||||
import { TeamModule } from '../team/team.module';
|
||||
import { UserModule } from '../user/user.module';
|
||||
// import { FirebaseModule } from '../firebase/firebase.module';
|
||||
import { PubSubModule } from '../pubsub/pubsub.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
PrismaModule,
|
||||
// FirebaseModule,
|
||||
TeamModule,
|
||||
UserModule,
|
||||
PubSubModule,
|
||||
],
|
||||
providers: [
|
||||
TeamCollectionService,
|
||||
TeamCollectionResolver,
|
||||
GqlCollectionTeamMemberGuard,
|
||||
],
|
||||
exports: [TeamCollectionService, GqlCollectionTeamMemberGuard],
|
||||
})
|
||||
export class TeamCollectionModule {}
|
||||
@@ -0,0 +1,417 @@
|
||||
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 whom 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`);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,509 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import { Team } from '../team/team.model';
|
||||
import { TeamCollection } from './team-collection.model';
|
||||
// import { FirebaseService } from '../firebase/firebase.service';
|
||||
import {
|
||||
TEAM_USER_NO_FB_SYNCDATA,
|
||||
TEAM_FB_COLL_PATH_RESOLVE_FAIL,
|
||||
TEAM_COLL_SHORT_TITLE,
|
||||
TEAM_COLL_INVALID_JSON,
|
||||
TEAM_INVALID_COLL_ID,
|
||||
} from '../errors';
|
||||
import { PubSubService } from '../pubsub/pubsub.service';
|
||||
import { throwErr } from 'src/utils';
|
||||
import { pipe } from 'fp-ts/function';
|
||||
import * as TO from 'fp-ts/TaskOption';
|
||||
|
||||
@Injectable()
|
||||
export class TeamCollectionService {
|
||||
constructor(
|
||||
private readonly prisma: PrismaService,
|
||||
// private readonly fb: FirebaseService,
|
||||
private readonly pubsub: PubSubService,
|
||||
) {}
|
||||
|
||||
// TODO: Change return type
|
||||
// private generatePrismaQueryObjForFBCollFolder(
|
||||
// folder: FBCollectionFolder,
|
||||
// teamID: string,
|
||||
// ): any {
|
||||
// return {
|
||||
// title: folder.name,
|
||||
// team: {
|
||||
// connect: {
|
||||
// id: teamID,
|
||||
// },
|
||||
// },
|
||||
// requests: {
|
||||
// create: folder.requests.map((r) => ({
|
||||
// title: r.name,
|
||||
// team: {
|
||||
// connect: {
|
||||
// id: teamID,
|
||||
// },
|
||||
// },
|
||||
// request: r,
|
||||
// })),
|
||||
// },
|
||||
// children: {
|
||||
// create: folder.folders.map((f) =>
|
||||
// this.generatePrismaQueryObjForFBCollFolder(f, teamID),
|
||||
// ),
|
||||
// },
|
||||
// };
|
||||
// }
|
||||
|
||||
// async importCollectionFromFirestore(
|
||||
// userUid: string,
|
||||
// fbCollectionPath: string,
|
||||
// teamID: string,
|
||||
// parentCollectionID?: string,
|
||||
// ): Promise<TeamCollection> {
|
||||
// const syncDoc = await this.fb.firestore
|
||||
// .doc(`users/${userUid}/collections/sync`)
|
||||
// .get();
|
||||
|
||||
// if (!syncDoc.exists) throw new Error(TEAM_USER_NO_FB_SYNCDATA);
|
||||
|
||||
// const doc = syncDoc.data();
|
||||
|
||||
// if (!doc) throw new Error(TEAM_USER_NO_FB_SYNCDATA);
|
||||
|
||||
// // The 'target' variable will have the intended path to reach
|
||||
// let target: FBCollectionFolder | null | undefined;
|
||||
// try {
|
||||
// const indexPaths = fbCollectionPath.split('/').map((x) => parseInt(x));
|
||||
// target = doc.collection[indexPaths.shift() as number];
|
||||
// while (indexPaths.length > 0)
|
||||
// {
|
||||
// const index = indexPaths.shift() as number;
|
||||
// target = target?.folders[index];
|
||||
// }
|
||||
// } catch (e) {
|
||||
// target = null;
|
||||
// }
|
||||
|
||||
// if (!target) throw new Error(TEAM_FB_COLL_PATH_RESOLVE_FAIL);
|
||||
|
||||
// const queryGen = this.generatePrismaQueryObjForFBCollFolder(target, teamID);
|
||||
|
||||
// let result: TeamCollection;
|
||||
|
||||
// if (parentCollectionID) {
|
||||
// result = await this.prisma.teamCollection.create({
|
||||
// data: {
|
||||
// ...queryGen,
|
||||
// parent: {
|
||||
// connect: {
|
||||
// id: parentCollectionID,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// });
|
||||
// } else {
|
||||
// result = await this.prisma.teamCollection.create({
|
||||
// data: queryGen,
|
||||
// });
|
||||
// }
|
||||
|
||||
// this.pubsub.publish(`team_coll/${teamID}/coll_added`, result);
|
||||
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// private async exportCollectionToJSONObject(
|
||||
// teamID: string,
|
||||
// collectionID: string,
|
||||
// ): Promise<FBCollectionFolder> {
|
||||
// const collection = await this.getCollection(collectionID);
|
||||
|
||||
// if (!collection) throw new Error(TEAM_INVALID_COLL_ID)
|
||||
|
||||
// const childrenCollection = await this.prisma.teamCollection.findMany({
|
||||
// where: {
|
||||
// teamID,
|
||||
// parentID: collectionID,
|
||||
// },
|
||||
// });
|
||||
|
||||
// const childrenCollectionObjects = await Promise.all(
|
||||
// childrenCollection.map((coll) =>
|
||||
// this.exportCollectionToJSONObject(teamID, coll.id),
|
||||
// ),
|
||||
// );
|
||||
|
||||
// const requests = await this.prisma.teamRequest.findMany({
|
||||
// where: {
|
||||
// teamID,
|
||||
// collectionID,
|
||||
// },
|
||||
// });
|
||||
|
||||
// return {
|
||||
// name: collection.title,
|
||||
// folders: childrenCollectionObjects,
|
||||
// requests: requests.map((x) => x.request),
|
||||
// };
|
||||
// }
|
||||
|
||||
// async exportCollectionsToJSON(teamID: string): Promise<string> {
|
||||
// const rootCollections = await this.prisma.teamCollection.findMany({
|
||||
// where: {
|
||||
// teamID,
|
||||
// parentID: null,
|
||||
// },
|
||||
// });
|
||||
|
||||
// const rootCollectionObjects = await Promise.all(
|
||||
// rootCollections.map((coll) =>
|
||||
// this.exportCollectionToJSONObject(teamID, coll.id),
|
||||
// ),
|
||||
// );
|
||||
|
||||
// return JSON.stringify(rootCollectionObjects);
|
||||
// }
|
||||
|
||||
// async importCollectionsFromJSON(
|
||||
// jsonString: string,
|
||||
// destTeamID: string,
|
||||
// destCollectionID: string | null,
|
||||
// ): Promise<void> {
|
||||
// let collectionsList: FBCollectionFolder[];
|
||||
|
||||
// try {
|
||||
// collectionsList = JSON.parse(jsonString);
|
||||
|
||||
// if (!Array.isArray(collectionsList))
|
||||
// throw new Error(TEAM_COLL_INVALID_JSON);
|
||||
// } catch (e) {
|
||||
// throw new Error(TEAM_COLL_INVALID_JSON);
|
||||
// }
|
||||
|
||||
// const queryList = collectionsList.map((x) =>
|
||||
// this.generatePrismaQueryObjForFBCollFolder(x, destTeamID),
|
||||
// );
|
||||
|
||||
// let requests: TeamCollection[];
|
||||
|
||||
// if (destCollectionID) {
|
||||
// requests = await this.prisma.$transaction(
|
||||
// queryList.map((x) =>
|
||||
// this.prisma.teamCollection.create({
|
||||
// data: {
|
||||
// ...x,
|
||||
// parent: {
|
||||
// connect: {
|
||||
// id: destCollectionID,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// }),
|
||||
// ),
|
||||
// );
|
||||
// } else {
|
||||
// requests = await this.prisma.$transaction(
|
||||
// queryList.map((x) =>
|
||||
// this.prisma.teamCollection.create({
|
||||
// data: {
|
||||
// ...x,
|
||||
// },
|
||||
// }),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// requests.forEach((x) =>
|
||||
// this.pubsub.publish(`team_coll/${destTeamID}/coll_added`, x),
|
||||
// );
|
||||
// }
|
||||
|
||||
// async replaceCollectionsWithJSON(
|
||||
// jsonString: string,
|
||||
// destTeamID: string,
|
||||
// destCollectionID: string | null,
|
||||
// ): Promise<void> {
|
||||
// let collectionsList: FBCollectionFolder[];
|
||||
|
||||
// try {
|
||||
// collectionsList = JSON.parse(jsonString);
|
||||
|
||||
// if (!Array.isArray(collectionsList))
|
||||
// throw new Error(TEAM_COLL_INVALID_JSON);
|
||||
// } catch (e) {
|
||||
// throw new Error(TEAM_COLL_INVALID_JSON);
|
||||
// }
|
||||
// const childrenCollection = await this.prisma.teamCollection.findMany({
|
||||
// where: {
|
||||
// teamID: destTeamID,
|
||||
// parentID: destCollectionID,
|
||||
// },
|
||||
// });
|
||||
|
||||
// await Promise.all(
|
||||
// childrenCollection.map(async (coll) => {
|
||||
// await this.deleteCollection(coll.id);
|
||||
// }),
|
||||
// );
|
||||
|
||||
// const queryList = collectionsList.map((x) =>
|
||||
// this.generatePrismaQueryObjForFBCollFolder(x, destTeamID),
|
||||
// );
|
||||
|
||||
// let requests: TeamCollection[];
|
||||
|
||||
// if (destCollectionID) {
|
||||
// requests = await this.prisma.$transaction(
|
||||
// queryList.map((x) =>
|
||||
// this.prisma.teamCollection.create({
|
||||
// data: {
|
||||
// ...x,
|
||||
// parent: {
|
||||
// connect: {
|
||||
// id: destCollectionID,
|
||||
// },
|
||||
// },
|
||||
// },
|
||||
// }),
|
||||
// ),
|
||||
// );
|
||||
// } else {
|
||||
// requests = await this.prisma.$transaction(
|
||||
// queryList.map((x) =>
|
||||
// this.prisma.teamCollection.create({
|
||||
// data: {
|
||||
// ...x,
|
||||
// },
|
||||
// }),
|
||||
// ),
|
||||
// );
|
||||
// }
|
||||
|
||||
// requests.forEach((x) =>
|
||||
// this.pubsub.publish(`team_coll/${destTeamID}/coll_added`, x),
|
||||
// );
|
||||
// }
|
||||
|
||||
async getTeamOfCollection(collectionID: string): Promise<Team> {
|
||||
const { team } =
|
||||
(await this.prisma.teamCollection.findUnique({
|
||||
where: {
|
||||
id: collectionID,
|
||||
},
|
||||
include: {
|
||||
team: true,
|
||||
},
|
||||
})) ?? throwErr(TEAM_INVALID_COLL_ID);
|
||||
|
||||
return team;
|
||||
}
|
||||
|
||||
async getParentOfCollection(
|
||||
collectionID: string,
|
||||
): Promise<TeamCollection | null> {
|
||||
const { parent } =
|
||||
(await this.prisma.teamCollection.findUnique({
|
||||
where: {
|
||||
id: collectionID,
|
||||
},
|
||||
include: {
|
||||
parent: true,
|
||||
},
|
||||
})) ?? throwErr(TEAM_INVALID_COLL_ID);
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
getChildrenOfCollection(
|
||||
collectionID: string,
|
||||
cursor: string | null,
|
||||
): Promise<TeamCollection[]> {
|
||||
if (!cursor) {
|
||||
return this.prisma.teamCollection.findMany({
|
||||
take: 10,
|
||||
where: {
|
||||
parent: {
|
||||
id: collectionID,
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return this.prisma.teamCollection.findMany({
|
||||
take: 10,
|
||||
skip: 1,
|
||||
cursor: {
|
||||
id: cursor,
|
||||
},
|
||||
where: {
|
||||
parent: {
|
||||
id: collectionID,
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async getTeamRootCollections(
|
||||
teamID: string,
|
||||
cursor: string | null,
|
||||
): Promise<TeamCollection[]> {
|
||||
if (!cursor) {
|
||||
return await this.prisma.teamCollection.findMany({
|
||||
take: 10,
|
||||
where: {
|
||||
teamID,
|
||||
parentID: null,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return this.prisma.teamCollection.findMany({
|
||||
take: 10,
|
||||
skip: 1,
|
||||
cursor: {
|
||||
id: cursor,
|
||||
},
|
||||
where: {
|
||||
teamID,
|
||||
parentID: null,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getTeamCollections(
|
||||
teamID: string,
|
||||
cursor: string | null,
|
||||
): Promise<TeamCollection[]> {
|
||||
if (!cursor) {
|
||||
return this.prisma.teamCollection.findMany({
|
||||
take: 10,
|
||||
where: {
|
||||
teamID,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
return this.prisma.teamCollection.findMany({
|
||||
take: 10,
|
||||
skip: 1,
|
||||
cursor: {
|
||||
id: cursor,
|
||||
},
|
||||
where: {
|
||||
teamID,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
getCollection(collectionID: string): Promise<TeamCollection | null> {
|
||||
return this.prisma.teamCollection.findUnique({
|
||||
where: {
|
||||
id: collectionID,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getCollectionTO(collectionID: string): TO.TaskOption<TeamCollection> {
|
||||
return pipe(
|
||||
TO.fromTask(() => this.getCollection(collectionID)),
|
||||
TO.chain(TO.fromNullable),
|
||||
);
|
||||
}
|
||||
|
||||
async createCollection(
|
||||
teamID: string,
|
||||
title: string,
|
||||
parentID: string | null,
|
||||
): Promise<TeamCollection> {
|
||||
if (title.length < 3) {
|
||||
throw new Error(TEAM_COLL_SHORT_TITLE);
|
||||
}
|
||||
|
||||
let result: TeamCollection;
|
||||
|
||||
if (!parentID) {
|
||||
result = await this.prisma.teamCollection.create({
|
||||
data: {
|
||||
title: title,
|
||||
team: {
|
||||
connect: {
|
||||
id: teamID,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
} else {
|
||||
result = await this.prisma.teamCollection.create({
|
||||
data: {
|
||||
title: title,
|
||||
team: {
|
||||
connect: {
|
||||
id: teamID,
|
||||
},
|
||||
},
|
||||
parent: {
|
||||
connect: {
|
||||
id: parentID,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
this.pubsub.publish(`team_coll/${teamID}/coll_added`, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async renameCollection(
|
||||
collectionID: string,
|
||||
newTitle: string,
|
||||
): Promise<TeamCollection> {
|
||||
if (newTitle.length < 3) {
|
||||
throw new Error(TEAM_COLL_SHORT_TITLE);
|
||||
}
|
||||
|
||||
const res = await this.prisma.teamCollection.update({
|
||||
where: {
|
||||
id: collectionID,
|
||||
},
|
||||
data: {
|
||||
title: newTitle,
|
||||
},
|
||||
});
|
||||
|
||||
this.pubsub.publish(`team_coll/${res.teamID}/coll_updated`, res);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
async deleteCollection(collectionID: string): Promise<void> {
|
||||
const coll =
|
||||
(await this.getCollection(collectionID)) ??
|
||||
throwErr(TEAM_INVALID_COLL_ID);
|
||||
|
||||
const childrenCollection = await this.prisma.teamCollection.findMany({
|
||||
where: {
|
||||
parentID: coll.id,
|
||||
},
|
||||
});
|
||||
|
||||
await Promise.all(
|
||||
childrenCollection.map((coll) => this.deleteCollection(coll.id)),
|
||||
);
|
||||
|
||||
await this.prisma.teamRequest.deleteMany({
|
||||
where: {
|
||||
collectionID: coll.id,
|
||||
},
|
||||
});
|
||||
|
||||
await this.prisma.teamCollection.delete({
|
||||
where: {
|
||||
id: collectionID,
|
||||
},
|
||||
});
|
||||
|
||||
this.pubsub.publish(`team_coll/${coll.teamID}/coll_removed`, coll);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user