Files
hoppscotch/packages/hoppscotch-backend/src/team-collection/team-collection.service.ts
Balu Babu 18864bfecf feat: addition of data field into User and Team Collections (#3614)
* feat: added new columns into the TeamCollections and UserCollections models

* feat: completed addition of new data field in TeamCollection

* feat: completed addition of new data field in UserCollections

* chore: updated all tests in team-collection module

* chore: added tests for updateTeamCollection method in team-collection module

* chore: refactored all existing testcases in user-collection to reflect new changes

* chore: added new testcases for updateUserCollection method in user-collection module

* chore: made data field optional in team and user collections

* chore: fixed edgecases for data being null

* chore: resolved issue with team-request testcases

* chore: completed changes requested in PR review

* chore: changed target to prod in hoppscotch-old-backend service
2023-12-05 20:12:37 +05:30

1060 lines
32 KiB
TypeScript

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { TeamCollection } from './team-collection.model';
import {
TEAM_COLL_SHORT_TITLE,
TEAM_COLL_INVALID_JSON,
TEAM_INVALID_COLL_ID,
TEAM_NOT_OWNER,
TEAM_COLL_NOT_FOUND,
TEAM_COL_ALREADY_ROOT,
TEAM_COLL_DEST_SAME,
TEAM_COLL_NOT_SAME_TEAM,
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';
import * as E from 'fp-ts/Either';
import * as O from 'fp-ts/Option';
import { Prisma, TeamCollection as DBTeamCollection } from '@prisma/client';
import { CollectionFolder } from 'src/types/CollectionFolder';
import { stringToJson } from 'src/utils';
@Injectable()
export class TeamCollectionService {
constructor(
private readonly prisma: PrismaService,
private readonly pubsub: PubSubService,
) {}
TITLE_LENGTH = 3;
/**
* Generate a Prisma query object representation of a collection and its child collections and requests
*
* @param folder CollectionFolder from client
* @param teamID The Team ID
* @param orderIndex Initial OrderIndex of
* @returns A Prisma query object to create a collection, its child collections and requests
*/
private generatePrismaQueryObjForFBCollFolder(
folder: CollectionFolder,
teamID: string,
orderIndex: number,
): Prisma.TeamCollectionCreateInput {
return {
title: folder.name,
team: {
connect: {
id: teamID,
},
},
requests: {
create: folder.requests.map((r, index) => ({
title: r.name,
team: {
connect: {
id: teamID,
},
},
request: r,
orderIndex: index + 1,
})),
},
orderIndex: orderIndex,
children: {
create: folder.folders.map((f, index) =>
this.generatePrismaQueryObjForFBCollFolder(f, teamID, index + 1),
),
},
data: folder.data ?? undefined,
};
}
/**
* Generate a JSON containing all the contents of a collection
*
* @param teamID The Team ID
* @param collectionID The Collection ID
* @returns A JSON string containing all the contents of a collection
*/
private async exportCollectionToJSONObject(
teamID: string,
collectionID: string,
) {
const collection = await this.getCollection(collectionID);
if (E.isLeft(collection)) return E.left(TEAM_INVALID_COLL_ID);
const childrenCollection = await this.prisma.teamCollection.findMany({
where: {
teamID,
parentID: collectionID,
},
orderBy: {
orderIndex: 'asc',
},
});
const childrenCollectionObjects = [];
for (const coll of childrenCollection) {
const result = await this.exportCollectionToJSONObject(teamID, coll.id);
if (E.isLeft(result)) return E.left(result.left);
childrenCollectionObjects.push(result.right);
}
const requests = await this.prisma.teamRequest.findMany({
where: {
teamID,
collectionID,
},
orderBy: {
orderIndex: 'asc',
},
});
const result: CollectionFolder = {
name: collection.right.title,
folders: childrenCollectionObjects,
requests: requests.map((x) => x.request),
data: JSON.stringify(collection.right.data),
};
return E.right(result);
}
/**
* Generate a JSON containing all the contents of collections and requests of a team
*
* @param teamID The Team ID
* @returns A JSON string containing all the contents of collections and requests of a team
*/
async exportCollectionsToJSON(teamID: string) {
const rootCollections = await this.prisma.teamCollection.findMany({
where: {
teamID,
parentID: null,
},
});
const rootCollectionObjects = [];
for (const coll of rootCollections) {
const result = await this.exportCollectionToJSONObject(teamID, coll.id);
if (E.isLeft(result)) return E.left(result.left);
rootCollectionObjects.push(result.right);
}
return E.right(JSON.stringify(rootCollectionObjects));
}
/**
* Create new TeamCollections and TeamRequests from JSON string
*
* @param jsonString The JSON string of the content
* @param destTeamID The Team ID
* @param destCollectionID The Collection ID
* @returns An Either of a Boolean if the creation operation was successful
*/
async importCollectionsFromJSON(
jsonString: string,
destTeamID: string,
destCollectionID: string | null,
) {
// Check to see if jsonString is valid
const collectionsList = stringToJson<CollectionFolder[]>(jsonString);
if (E.isLeft(collectionsList)) return E.left(TEAM_COLL_INVALID_JSON);
// Check to see if parsed jsonString is an array
if (!Array.isArray(collectionsList.right))
return E.left(TEAM_COLL_INVALID_JSON);
// Get number of root or child collections for destCollectionID(if destcollectionID != null) or destTeamID(if destcollectionID == null)
const count = !destCollectionID
? await this.getRootCollectionsCount(destTeamID)
: await this.getChildCollectionsCount(destCollectionID);
// Generate Prisma Query Object for all child collections in collectionsList
const queryList = collectionsList.right.map((x) =>
this.generatePrismaQueryObjForFBCollFolder(x, destTeamID, count + 1),
);
const parent = destCollectionID
? {
connect: {
id: destCollectionID,
},
}
: undefined;
const teamCollections = await this.prisma.$transaction(
queryList.map((x) =>
this.prisma.teamCollection.create({
data: {
...x,
parent,
},
}),
),
);
teamCollections.forEach((collection) =>
this.pubsub.publish(
`team_coll/${destTeamID}/coll_added`,
this.cast(collection),
),
);
return E.right(true);
}
/**
* Replace all the existing contents of a collection (or root collections) with data from JSON String
*
* @param jsonString The JSON string of the content
* @param destTeamID The Team ID
* @param destCollectionID The Collection ID
* @returns An Either of a Boolean if the operation was successful
*/
async replaceCollectionsWithJSON(
jsonString: string,
destTeamID: string,
destCollectionID: string | null,
) {
// Check to see if jsonString is valid
const collectionsList = stringToJson<CollectionFolder[]>(jsonString);
if (E.isLeft(collectionsList)) return E.left(TEAM_COLL_INVALID_JSON);
// Check to see if parsed jsonString is an array
if (!Array.isArray(collectionsList.right))
return E.left(TEAM_COLL_INVALID_JSON);
// Fetch all child collections of destCollectionID
const childrenCollection = await this.prisma.teamCollection.findMany({
where: {
teamID: destTeamID,
parentID: destCollectionID,
},
});
for (const coll of childrenCollection) {
const deletedTeamCollection = await this.deleteCollection(coll.id);
if (E.isLeft(deletedTeamCollection))
return E.left(deletedTeamCollection.left);
}
// Get number of root or child collections for destCollectionID(if destcollectionID != null) or destTeamID(if destcollectionID == null)
const count = !destCollectionID
? await this.getRootCollectionsCount(destTeamID)
: await this.getChildCollectionsCount(destCollectionID);
const queryList = collectionsList.right.map((x) =>
this.generatePrismaQueryObjForFBCollFolder(x, destTeamID, count + 1),
);
const parent = destCollectionID
? {
connect: {
id: destCollectionID,
},
}
: undefined;
const teamCollections = await this.prisma.$transaction(
queryList.map((x) =>
this.prisma.teamCollection.create({
data: {
...x,
parent,
},
}),
),
);
teamCollections.forEach((collections) =>
this.pubsub.publish(
`team_coll/${destTeamID}/coll_added`,
this.cast(collections),
),
);
return E.right(true);
}
/**
* Typecast a database TeamCollection to a TeamCollection model
*
* @param teamCollection database TeamCollection
* @returns TeamCollection model
*/
private cast(teamCollection: DBTeamCollection): TeamCollection {
return <TeamCollection>{
id: teamCollection.id,
title: teamCollection.title,
parentID: teamCollection.parentID,
data: !teamCollection.data ? null : JSON.stringify(teamCollection.data),
};
}
/**
* Get Team of given Collection ID
*
* @param collectionID The collection ID
* @returns Team of given Collection ID
*/
async getTeamOfCollection(collectionID: string) {
try {
const teamCollection = await this.prisma.teamCollection.findUnique({
where: {
id: collectionID,
},
include: {
team: true,
},
});
return E.right(teamCollection.team);
} catch (error) {
return E.left(TEAM_INVALID_COLL_ID);
}
}
/**
* Get parent of given Collection ID
*
* @param collectionID The collection ID
* @returns Parent TeamCollection of given Collection ID
*/
async getParentOfCollection(collectionID: string) {
const teamCollection = await this.prisma.teamCollection.findUnique({
where: {
id: collectionID,
},
include: {
parent: true,
},
});
if (!teamCollection) return null;
return !teamCollection.parent ? null : this.cast(teamCollection.parent);
}
/**
* Get child collections of given Collection ID
*
* @param collectionID The collection ID
* @param cursor collectionID for pagination
* @param take Number of items we want returned
* @returns A list of child collections
*/
async getChildrenOfCollection(
collectionID: string,
cursor: string | null,
take: number,
) {
const res = await this.prisma.teamCollection.findMany({
where: {
parentID: collectionID,
},
orderBy: {
orderIndex: 'asc',
},
take: take, // default: 10
skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined,
});
const childCollections = res.map((teamCollection) =>
this.cast(teamCollection),
);
return childCollections;
}
/**
* Get root collections of given Collection ID
*
* @param teamID The Team ID
* @param cursor collectionID for pagination
* @param take Number of items we want returned
* @returns A list of root TeamCollections
*/
async getTeamRootCollections(
teamID: string,
cursor: string | null,
take: number,
) {
const res = await this.prisma.teamCollection.findMany({
where: {
teamID,
parentID: null,
},
orderBy: {
orderIndex: 'asc',
},
take: take, // default: 10
skip: cursor ? 1 : 0,
cursor: cursor ? { id: cursor } : undefined,
});
const teamCollections = res.map((teamCollection) =>
this.cast(teamCollection),
);
return teamCollections;
}
/**
* Get collection details
*
* @param collectionID The collection ID
* @returns An Either of the Collection details
*/
async getCollection(collectionID: string) {
try {
const teamCollection = await this.prisma.teamCollection.findUniqueOrThrow(
{
where: {
id: collectionID,
},
},
);
return E.right(teamCollection);
} catch (error) {
return E.left(TEAM_COLL_NOT_FOUND);
}
}
/**
* Check to see if Collection belongs to Team
*
* @param collectionID getChildCollectionsCount
* @param teamID The Team ID
* @returns An Option of a Boolean
*/
private async isOwnerCheck(collectionID: string, teamID: string) {
try {
await this.prisma.teamCollection.findFirstOrThrow({
where: {
id: collectionID,
teamID,
},
});
return O.some(true);
} catch (error) {
return O.none;
}
}
/**
* Returns the count of child collections present for a given collectionID
* * The count returned is highest OrderIndex + 1
*
* @param collectionID The Collection ID
* @returns Number of Child Collections
*/
private async getChildCollectionsCount(collectionID: string) {
const childCollectionCount = await this.prisma.teamCollection.findMany({
where: { parentID: collectionID },
orderBy: {
orderIndex: 'desc',
},
});
if (!childCollectionCount.length) return 0;
return childCollectionCount[0].orderIndex;
}
/**
* Returns the count of root collections present for a given teamID
* * The count returned is highest OrderIndex + 1
*
* @param teamID The Team ID
* @returns Number of Root Collections
*/
private async getRootCollectionsCount(teamID: string) {
const rootCollectionCount = await this.prisma.teamCollection.findMany({
where: { teamID, parentID: null },
orderBy: {
orderIndex: 'desc',
},
});
if (!rootCollectionCount.length) return 0;
return rootCollectionCount[0].orderIndex;
}
/**
* Create a new TeamCollection
*
* @param teamID The Team ID
* @param title The title of new TeamCollection
* @param parentTeamCollectionID The parent collectionID (null if root collection)
* @returns An Either of TeamCollection
*/
async createCollection(
teamID: string,
title: string,
data: string | null = null,
parentTeamCollectionID: string | null,
) {
const isTitleValid = isValidLength(title, this.TITLE_LENGTH);
if (!isTitleValid) return E.left(TEAM_COLL_SHORT_TITLE);
// Check to see if parentTeamCollectionID belongs to this Team
if (parentTeamCollectionID !== null) {
const isOwner = await this.isOwnerCheck(parentTeamCollectionID, teamID);
if (O.isNone(isOwner)) return E.left(TEAM_NOT_OWNER);
}
if (data === '') return E.left(TEAM_COLL_DATA_INVALID);
if (data) {
const jsonReq = stringToJson(data);
if (E.isLeft(jsonReq)) return E.left(TEAM_COLL_DATA_INVALID);
data = jsonReq.right;
}
const isParent = parentTeamCollectionID
? {
connect: {
id: parentTeamCollectionID,
},
}
: undefined;
const teamCollection = await this.prisma.teamCollection.create({
data: {
title: title,
team: {
connect: {
id: teamID,
},
},
parent: isParent,
data: data ?? undefined,
orderIndex: !parentTeamCollectionID
? (await this.getRootCollectionsCount(teamID)) + 1
: (await this.getChildCollectionsCount(parentTeamCollectionID)) + 1,
},
});
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
* @param newTitle The new title of collection
* @returns An Either of the updated TeamCollection
*/
async renameCollection(collectionID: string, newTitle: string) {
const isTitleValid = isValidLength(newTitle, this.TITLE_LENGTH);
if (!isTitleValid) return E.left(TEAM_COLL_SHORT_TITLE);
try {
const updatedTeamCollection = await this.prisma.teamCollection.update({
where: {
id: collectionID,
},
data: {
title: newTitle,
},
});
this.pubsub.publish(
`team_coll/${updatedTeamCollection.teamID}/coll_updated`,
this.cast(updatedTeamCollection),
);
return E.right(this.cast(updatedTeamCollection));
} catch (error) {
return E.left(TEAM_COLL_NOT_FOUND);
}
}
/**
* Update the OrderIndex of all collections in given parentID
*
* @param parentID The Parent collectionID
* @param orderIndexCondition Condition to decide what collections will be updated
* @param dataCondition Increment/Decrement OrderIndex condition
* @returns A Collection with updated OrderIndexes
*/
private async updateOrderIndex(
parentID: string,
orderIndexCondition: Prisma.IntFilter,
dataCondition: Prisma.IntFieldUpdateOperationsInput,
) {
const updatedTeamCollection = await this.prisma.teamCollection.updateMany({
where: {
parentID: parentID,
orderIndex: orderIndexCondition,
},
data: { orderIndex: dataCondition },
});
return updatedTeamCollection;
}
/**
* Delete a TeamCollection from the DB
*
* @param collectionID The Collection Id
* @returns The deleted TeamCollection
*/
private async removeTeamCollection(collectionID: string) {
try {
const deletedTeamCollection = await this.prisma.teamCollection.delete({
where: {
id: collectionID,
},
});
return E.right(deletedTeamCollection);
} catch (error) {
return E.left(TEAM_COLL_NOT_FOUND);
}
}
/**
* Delete child collection and requests of a TeamCollection
*
* @param collectionID The Collection Id
* @returns A Boolean of deletion status
*/
private async deleteCollectionData(collection: DBTeamCollection) {
// Get all child collections in collectionID
const childCollectionList = await this.prisma.teamCollection.findMany({
where: {
parentID: collection.id,
},
});
// Delete child collections
await Promise.all(
childCollectionList.map((coll) => this.deleteCollection(coll.id)),
);
// Delete all requests in collectionID
await this.prisma.teamRequest.deleteMany({
where: {
collectionID: collection.id,
},
});
// Delete collection from TeamCollection table
const deletedTeamCollection = await this.removeTeamCollection(
collection.id,
);
if (E.isLeft(deletedTeamCollection))
return E.left(deletedTeamCollection.left);
this.pubsub.publish(
`team_coll/${deletedTeamCollection.right.teamID}/coll_removed`,
deletedTeamCollection.right.id,
);
return E.right(deletedTeamCollection.right);
}
/**
* Delete a TeamCollection
*
* @param collectionID The Collection Id
* @returns An Either of Boolean of deletion status
*/
async deleteCollection(collectionID: string) {
const collection = await this.getCollection(collectionID);
if (E.isLeft(collection)) return E.left(collection.left);
// Delete all child collections and requests in the collection
const collectionData = await this.deleteCollectionData(collection.right);
if (E.isLeft(collectionData)) return E.left(collectionData.left);
// Update orderIndexes in TeamCollection table for user
await this.updateOrderIndex(
collectionData.right.parentID,
{ gt: collectionData.right.orderIndex },
{ decrement: 1 },
);
return E.right(true);
}
/**
* Change parentID of TeamCollection's
*
* @param collectionID The collection ID
* @param parentCollectionID The new parent's collection ID or change to root collection
* @returns If successful return an Either of true
*/
private async changeParent(
collection: DBTeamCollection,
parentCollectionID: string | null,
) {
try {
let collectionCount: number;
if (!parentCollectionID)
collectionCount = await this.getRootCollectionsCount(collection.teamID);
collectionCount = await this.getChildCollectionsCount(parentCollectionID);
const updatedCollection = await this.prisma.teamCollection.update({
where: {
id: collection.id,
},
data: {
// if parentCollectionID == null, collection becomes root collection
// if parentCollectionID != null, collection becomes child collection
parentID: parentCollectionID,
orderIndex: collectionCount + 1,
},
});
return E.right(this.cast(updatedCollection));
} catch (error) {
return E.left(TEAM_COLL_NOT_FOUND);
}
}
/**
* Check if collection is parent of destCollection
*
* @param collection The ID of collection being moved
* @param destCollection The ID of collection into which we are moving target collection into
* @returns An Option of boolean, is parent or not
*/
private async isParent(
collection: DBTeamCollection,
destCollection: DBTeamCollection,
): Promise<O.Option<boolean>> {
//* 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
//* Consider us moving Collection_E into Collection_D
//* Collection_A [parent:null !== Collection_E] return false, exit
//* |--> Collection_B [parent:Collection_A !== Collection_E] call isParent(Collection_E,Collection_A)
//* |--> Collection_C [parent:Collection_B !== Collection_E] call isParent(Collection_E,Collection_B)
//* |--> Collection_D [parent:Collection_C !== Collection_E] call isParent(Collection_E,Collection_C)
//* Invalid condition, isParent returns true
//* Consider us moving Collection_B into Collection_D
//* Collection_A
//* |--> Collection_B
//* |--> Collection_C [parent:Collection_B === Collection_B] return true, exit
//* |--> Collection_D [parent:Collection_C !== Collection_B] call isParent(Collection_B,Collection_C)
// Check if collection and destCollection are same
if (collection === destCollection) {
return O.none;
}
if (destCollection.parentID !== null) {
// Check if ID of collection is same as parent of destCollection
if (destCollection.parentID === collection.id) {
return O.none;
}
// Get collection details of collection one step above in the tree i.e the parent collection
const parentCollection = await this.getCollection(
destCollection.parentID,
);
if (E.isLeft(parentCollection)) {
return O.none;
}
// Call isParent again now with parent collection
return await this.isParent(collection, parentCollection.right);
} else {
return O.some(true);
}
}
/**
* Move TeamCollection into root or another collection
*
* @param collectionID The ID of collection being moved
* @param destCollectionID The ID of collection the target collection is being moved into or move target collection to root
* @returns An Either of the moved TeamCollection
*/
async moveCollection(collectionID: string, destCollectionID: string | null) {
// Get collection details of collectionID
const collection = await this.getCollection(collectionID);
if (E.isLeft(collection)) return E.left(collection.left);
// destCollectionID == null i.e move collection to root
if (!destCollectionID) {
if (!collection.right.parentID) {
// collection is a root collection
// Throw error if collection is already a root collection
return E.left(TEAM_COL_ALREADY_ROOT);
}
// Move child collection into root and update orderIndexes for root teamCollections
await this.updateOrderIndex(
collection.right.parentID,
{ gt: collection.right.orderIndex },
{ decrement: 1 },
);
// Change parent from child to root i.e child collection becomes a root collection
const updatedCollection = await this.changeParent(collection.right, null);
if (E.isLeft(updatedCollection)) return E.left(updatedCollection.left);
this.pubsub.publish(
`team_coll/${collection.right.teamID}/coll_moved`,
updatedCollection.right,
);
return E.right(updatedCollection.right);
}
// destCollectionID != null i.e move into another collection
if (collectionID === destCollectionID) {
// Throw error if collectionID and destCollectionID are the same
return E.left(TEAM_COLL_DEST_SAME);
}
// Get collection details of destCollectionID
const destCollection = await this.getCollection(destCollectionID);
if (E.isLeft(destCollection)) return E.left(TEAM_COLL_NOT_FOUND);
// Check if collection and destCollection belong to the same user account
if (collection.right.teamID !== destCollection.right.teamID) {
return E.left(TEAM_COLL_NOT_SAME_TEAM);
}
// Check if collection is present on the parent tree for destCollection
const checkIfParent = await this.isParent(
collection.right,
destCollection.right,
);
if (O.isNone(checkIfParent)) {
return E.left(TEAM_COLL_IS_PARENT_COLL);
}
// Move root/child collection into another child collection and update orderIndexes of the previous parent
await this.updateOrderIndex(
collection.right.parentID,
{ gt: collection.right.orderIndex },
{ decrement: 1 },
);
// Change parent from null to teamCollection i.e collection becomes a child collection
const updatedCollection = await this.changeParent(
collection.right,
destCollection.right.id,
);
if (E.isLeft(updatedCollection)) return E.left(updatedCollection.left);
this.pubsub.publish(
`team_coll/${collection.right.teamID}/coll_moved`,
updatedCollection.right,
);
return E.right(updatedCollection.right);
}
/**
* Find the number of child collections present in collectionID
*
* @param collectionID The Collection ID
* @returns Number of collections
*/
getCollectionCount(collectionID: string): Promise<number> {
return this.prisma.teamCollection.count({
where: { parentID: collectionID },
});
}
/**
* Update order of root or child collectionID's
*
* @param collectionID The ID of collection being re-ordered
* @param nextCollectionID The ID of collection that is after the moved collection in its new position
* @returns If successful return an Either of true
*/
async updateCollectionOrder(
collectionID: string,
nextCollectionID: string | null,
) {
// Throw error if collectionID and nextCollectionID are the same
if (collectionID === nextCollectionID)
return E.left(TEAM_COL_SAME_NEXT_COLL);
// Get collection details of collectionID
const collection = await this.getCollection(collectionID);
if (E.isLeft(collection)) return E.left(collection.left);
if (!nextCollectionID) {
// nextCollectionID == null i.e move collection to the end of the list
try {
await this.prisma.$transaction(async (tx) => {
// Step 1: Decrement orderIndex of all items that come after collection.orderIndex till end of list of items
await tx.teamCollection.updateMany({
where: {
parentID: collection.right.parentID,
orderIndex: {
gte: collection.right.orderIndex + 1,
},
},
data: {
orderIndex: { decrement: 1 },
},
});
// Step 2: Update orderIndex of collection to length of list
const updatedTeamCollection = await tx.teamCollection.update({
where: { id: collection.right.id },
data: {
orderIndex: await this.getCollectionCount(
collection.right.parentID,
),
},
});
});
this.pubsub.publish(
`team_coll/${collection.right.teamID}/coll_order_updated`,
{
collection: this.cast(collection.right),
nextCollection: null,
},
);
return E.right(true);
} catch (error) {
return E.left(TEAM_COL_REORDERING_FAILED);
}
}
// nextCollectionID != null i.e move to a certain position
// Get collection details of nextCollectionID
const subsequentCollection = await this.getCollection(nextCollectionID);
if (E.isLeft(subsequentCollection)) return E.left(TEAM_COLL_NOT_FOUND);
// Check if collection and subsequentCollection belong to the same collection team
if (collection.right.teamID !== subsequentCollection.right.teamID)
return E.left(TEAM_COLL_NOT_SAME_TEAM);
try {
await this.prisma.$transaction(async (tx) => {
// Step 1: Determine if we are moving collection up or down the list
const isMovingUp =
subsequentCollection.right.orderIndex < collection.right.orderIndex;
// Step 2: Update OrderIndex of items in list depending on moving up or down
const updateFrom = isMovingUp
? subsequentCollection.right.orderIndex
: collection.right.orderIndex + 1;
const updateTo = isMovingUp
? collection.right.orderIndex - 1
: subsequentCollection.right.orderIndex - 1;
await tx.teamCollection.updateMany({
where: {
parentID: collection.right.parentID,
orderIndex: { gte: updateFrom, lte: updateTo },
},
data: {
orderIndex: isMovingUp ? { increment: 1 } : { decrement: 1 },
},
});
// Step 3: Update OrderIndex of collection
const updatedTeamCollection = await tx.teamCollection.update({
where: { id: collection.right.id },
data: {
orderIndex: isMovingUp
? subsequentCollection.right.orderIndex
: subsequentCollection.right.orderIndex - 1,
},
});
});
this.pubsub.publish(
`team_coll/${collection.right.teamID}/coll_order_updated`,
{
collection: this.cast(collection.right),
nextCollection: this.cast(subsequentCollection.right),
},
);
return E.right(true);
} catch (error) {
return E.left(TEAM_COL_REORDERING_FAILED);
}
}
/**
* Fetch list of all the Team Collections in DB for a particular team
* @param teamID Team ID
* @returns number of Team Collections in the DB
*/
async totalCollectionsInTeam(teamID: string) {
const collCount = await this.prisma.teamCollection.count({
where: {
teamID: teamID,
},
});
return collCount;
}
/**
* Fetch list of all the Team Collections in DB
*
* @returns number of Team Collections in the DB
*/
async getTeamCollectionsCount() {
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 = null,
newTitle: string = null,
) {
try {
if (newTitle != null) {
const isTitleValid = isValidLength(newTitle, this.TITLE_LENGTH);
if (!isTitleValid) return E.left(TEAM_COLL_SHORT_TITLE);
}
if (collectionData === '') return E.left(TEAM_COLL_DATA_INVALID);
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 ?? undefined,
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);
}
}
}