refactor: adding JSON import/export functions to UserCollections module (HBE-169) (#37)
* feat: created exportUserCollectionsToJSON mutation for UserCollection module * chore: added comments to export functions * chore: added type and user ownership checking to creation methods * chore: replaced request property with spread request object instead * chore: completed all changes requested in inital review of PR * chore: explicitly exporting request title in export function * chore: explicitly exporting request title in export function * chore: added codegen folder to gitignore * chore: removed gql-code gen file from repo
This commit is contained in:
@@ -223,8 +223,7 @@ export const TEAM_REQ_INVALID_TARGET_COLL_ID =
|
||||
* Tried to reorder team request but failed
|
||||
* (TeamRequestService)
|
||||
*/
|
||||
export const TEAM_REQ_REORDERING_FAILED =
|
||||
'team_req/reordering_failed' as const;
|
||||
export const TEAM_REQ_REORDERING_FAILED = 'team_req/reordering_failed' as const;
|
||||
|
||||
/**
|
||||
* No Postmark Sender Email defined
|
||||
@@ -487,7 +486,7 @@ export const USER_COLL_NOT_FOUND = 'user_coll/not_found' as const;
|
||||
* UserCollection is already a root collection
|
||||
* (UserCollectionService)
|
||||
*/
|
||||
export const USER_COL_ALREADY_ROOT =
|
||||
export const USER_COLL_ALREADY_ROOT =
|
||||
'user_coll/target_user_collection_is_already_root_user_collection' as const;
|
||||
|
||||
/**
|
||||
@@ -535,3 +534,9 @@ export const USER_COLL_SAME_NEXT_COLL =
|
||||
* (UserCollectionService)
|
||||
*/
|
||||
export const USER_NOT_OWNER = 'user_coll/user_not_owner' as const;
|
||||
|
||||
/**
|
||||
* The JSON used is not valid
|
||||
* (UserCollectionService)
|
||||
*/
|
||||
export const USER_COLL_INVALID_JSON = 'user_coll/invalid_json';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { Prisma } from '@prisma/client';
|
||||
|
||||
export interface CollectionFolder {
|
||||
id?: string;
|
||||
folders: CollectionFolder[];
|
||||
requests: any[];
|
||||
name: string;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Field, ID, ArgsType } from '@nestjs/graphql';
|
||||
import { ReqType } from '@prisma/client';
|
||||
import { PaginationArgs } from 'src/types/input-types.args';
|
||||
|
||||
@ArgsType()
|
||||
@@ -73,3 +74,24 @@ export class MoveUserCollectionArgs {
|
||||
})
|
||||
userCollectionID: string;
|
||||
}
|
||||
|
||||
@ArgsType()
|
||||
export class ImportUserCollectionsFromJSONArgs {
|
||||
@Field({
|
||||
name: 'jsonString',
|
||||
description: 'JSON string to import',
|
||||
})
|
||||
jsonString: string;
|
||||
@Field({
|
||||
name: 'reqType',
|
||||
description: 'Type of UserCollection',
|
||||
})
|
||||
reqType: ReqType;
|
||||
@Field(() => ID, {
|
||||
name: 'parentCollectionID',
|
||||
description:
|
||||
'ID to the collection to which to import into (null if to import into the root of the user)',
|
||||
nullable: true,
|
||||
})
|
||||
parentCollectionID?: string;
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ import { PaginationArgs } from 'src/types/input-types.args';
|
||||
import {
|
||||
CreateChildUserCollectionArgs,
|
||||
CreateRootUserCollectionArgs,
|
||||
ImportUserCollectionsFromJSONArgs,
|
||||
MoveUserCollectionArgs,
|
||||
RenameUserCollectionsArgs,
|
||||
UpdateUserCollectionArgs,
|
||||
@@ -139,6 +140,32 @@ export class UserCollectionResolver {
|
||||
return userCollection.right;
|
||||
}
|
||||
|
||||
@Query(() => String, {
|
||||
description:
|
||||
'Returns the JSON string giving the collections and their contents of a user',
|
||||
})
|
||||
@UseGuards(GqlAuthGuard)
|
||||
async exportUserCollectionsToJSON(
|
||||
@GqlUser() user: AuthUser,
|
||||
@Args({
|
||||
type: () => ID,
|
||||
name: 'collectionID',
|
||||
description: 'ID of the user collection',
|
||||
nullable: true,
|
||||
defaultValue: null,
|
||||
})
|
||||
collectionID: string,
|
||||
) {
|
||||
const jsonString =
|
||||
await this.userCollectionService.exportUserCollectionsToJSON(
|
||||
user.uid,
|
||||
collectionID,
|
||||
);
|
||||
|
||||
if (E.isLeft(jsonString)) throwErr(jsonString.left as string);
|
||||
return jsonString.right;
|
||||
}
|
||||
|
||||
// Mutations
|
||||
@Mutation(() => UserCollection, {
|
||||
description: 'Creates root REST user collection(no parent user collection)',
|
||||
@@ -300,6 +327,25 @@ export class UserCollectionResolver {
|
||||
return res.right;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean, {
|
||||
description: 'Import collections from JSON string to the specified Team',
|
||||
})
|
||||
@UseGuards(GqlAuthGuard)
|
||||
async importUserCollectionsFromJSON(
|
||||
@Args() args: ImportUserCollectionsFromJSONArgs,
|
||||
@GqlUser() user: AuthUser,
|
||||
) {
|
||||
const importedCollection =
|
||||
await this.userCollectionService.importCollectionsFromJSON(
|
||||
args.jsonString,
|
||||
user.uid,
|
||||
args.parentCollectionID,
|
||||
args.reqType,
|
||||
);
|
||||
if (E.isLeft(importedCollection)) throwErr(importedCollection.left);
|
||||
return importedCollection.right;
|
||||
}
|
||||
|
||||
// Subscriptions
|
||||
@Subscription(() => UserCollection, {
|
||||
description: 'Listen for User Collection Creation',
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
USER_COLL_REORDERING_FAILED,
|
||||
USER_COLL_SAME_NEXT_COLL,
|
||||
USER_COLL_SHORT_TITLE,
|
||||
USER_COL_ALREADY_ROOT,
|
||||
USER_COLL_ALREADY_ROOT,
|
||||
USER_NOT_OWNER,
|
||||
} from 'src/errors';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
@@ -997,7 +997,7 @@ describe('moveUserCollection', () => {
|
||||
null,
|
||||
user.uid,
|
||||
);
|
||||
expect(result).toEqualLeft(USER_COL_ALREADY_ROOT);
|
||||
expect(result).toEqualLeft(USER_COLL_ALREADY_ROOT);
|
||||
});
|
||||
test('should successfully move a child user-collection into root', async () => {
|
||||
// getUserCollection
|
||||
|
||||
@@ -8,19 +8,26 @@ import {
|
||||
USER_COLL_REORDERING_FAILED,
|
||||
USER_COLL_SAME_NEXT_COLL,
|
||||
USER_COLL_SHORT_TITLE,
|
||||
USER_COL_ALREADY_ROOT,
|
||||
USER_COLL_ALREADY_ROOT,
|
||||
USER_NOT_FOUND,
|
||||
USER_NOT_OWNER,
|
||||
USER_COLL_INVALID_JSON,
|
||||
} from 'src/errors';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { AuthUser } from 'src/types/AuthUser';
|
||||
import * as E from 'fp-ts/Either';
|
||||
import * as O from 'fp-ts/Option';
|
||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||
import { Prisma, User, UserCollection } from '@prisma/client';
|
||||
import {
|
||||
Prisma,
|
||||
User,
|
||||
UserCollection,
|
||||
ReqType as DBReqType,
|
||||
} from '@prisma/client';
|
||||
import { UserCollection as UserCollectionModel } from './user-collections.model';
|
||||
import { ReqType } from 'src/types/RequestTypes';
|
||||
import { isValidLength } from 'src/utils';
|
||||
import { isValidLength, stringToJson } from 'src/utils';
|
||||
import { CollectionFolder } from 'src/types/CollectionFolder';
|
||||
@Injectable()
|
||||
export class UserCollectionService {
|
||||
constructor(
|
||||
@@ -209,10 +216,20 @@ export class UserCollectionService {
|
||||
const isTitleValid = isValidLength(title, 3);
|
||||
if (!isTitleValid) return E.left(USER_COLL_SHORT_TITLE);
|
||||
|
||||
// Check to see if parentUserCollectionID belongs to this User
|
||||
// If creating a child collection
|
||||
if (parentUserCollectionID !== null) {
|
||||
const isOwner = await this.isOwnerCheck(parentUserCollectionID, user.uid);
|
||||
if (O.isNone(isOwner)) return E.left(USER_NOT_OWNER);
|
||||
const parentCollection = await this.getUserCollection(
|
||||
parentUserCollectionID,
|
||||
);
|
||||
if (E.isLeft(parentCollection)) return E.left(parentCollection.left);
|
||||
|
||||
// Check to see if parentUserCollectionID belongs to this User
|
||||
if (parentCollection.right.userUid !== user.uid)
|
||||
return E.left(USER_NOT_OWNER);
|
||||
|
||||
// Check to see if parent collection is of the same type of new collection being created
|
||||
if (parentCollection.right.type !== type)
|
||||
return E.left(USER_COLL_NOT_SAME_TYPE);
|
||||
}
|
||||
|
||||
const isParent = parentUserCollectionID
|
||||
@@ -555,7 +572,7 @@ export class UserCollectionService {
|
||||
if (!collection.right.parentID) {
|
||||
// collection is a root collection
|
||||
// Throw error if collection is already a root collection
|
||||
return E.left(USER_COL_ALREADY_ROOT);
|
||||
return E.left(USER_COLL_ALREADY_ROOT);
|
||||
}
|
||||
// Move child collection into root and update orderIndexes for root userCollections
|
||||
await this.updateOrderIndex(
|
||||
@@ -764,4 +781,253 @@ export class UserCollectionService {
|
||||
return E.left(USER_COLL_REORDERING_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a JSON containing all the contents of a collection
|
||||
*
|
||||
* @param userUID The User UID
|
||||
* @param collectionID The Collection ID
|
||||
* @returns A JSON string containing all the contents of a collection
|
||||
*/
|
||||
private async exportUserCollectionToJSONObject(
|
||||
userUID: string,
|
||||
collectionID: string,
|
||||
): Promise<E.Left<string> | E.Right<CollectionFolder>> {
|
||||
// Get Collection details
|
||||
const collection = await this.getUserCollection(collectionID);
|
||||
if (E.isLeft(collection)) return E.left(collection.left);
|
||||
|
||||
// Get all child collections whose parentID === collectionID
|
||||
const childCollectionList = await this.prisma.userCollection.findMany({
|
||||
where: {
|
||||
parentID: collectionID,
|
||||
userUid: userUID,
|
||||
},
|
||||
orderBy: {
|
||||
orderIndex: 'asc',
|
||||
},
|
||||
});
|
||||
|
||||
// Create a list of child collection and request data ready for export
|
||||
const childrenCollectionObjects: CollectionFolder[] = [];
|
||||
for (const coll of childCollectionList) {
|
||||
const result = await this.exportUserCollectionToJSONObject(
|
||||
userUID,
|
||||
coll.id,
|
||||
);
|
||||
if (E.isLeft(result)) return E.left(result.left);
|
||||
|
||||
childrenCollectionObjects.push(result.right);
|
||||
}
|
||||
|
||||
// Fetch all child requests that belong to collectionID
|
||||
const requests = await this.prisma.userRequest.findMany({
|
||||
where: {
|
||||
userUid: userUID,
|
||||
collectionID,
|
||||
},
|
||||
orderBy: {
|
||||
orderIndex: 'asc',
|
||||
},
|
||||
});
|
||||
|
||||
const result: CollectionFolder = {
|
||||
id: collection.right.id,
|
||||
name: collection.right.title,
|
||||
folders: childrenCollectionObjects,
|
||||
requests: requests.map((x) => {
|
||||
return {
|
||||
id: x.id,
|
||||
name: x.title,
|
||||
...(x.request as Record<string, unknown>), // type casting x.request of type Prisma.JSONValue to an object to enable spread
|
||||
};
|
||||
}),
|
||||
};
|
||||
|
||||
return E.right(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a JSON containing all the contents of collections and requests of a team
|
||||
*
|
||||
* @param userUID The User UID
|
||||
* @returns A JSON string containing all the contents of collections and requests of a team
|
||||
*/
|
||||
async exportUserCollectionsToJSON(
|
||||
userUID: string,
|
||||
collectionID: string | null,
|
||||
) {
|
||||
// Get all child collections details
|
||||
const childCollectionList = await this.prisma.userCollection.findMany({
|
||||
where: {
|
||||
userUid: userUID,
|
||||
parentID: collectionID,
|
||||
},
|
||||
});
|
||||
|
||||
// Create a list of child collection and request data ready for export
|
||||
const collectionListObjects: CollectionFolder[] = [];
|
||||
for (const coll of childCollectionList) {
|
||||
const result = await this.exportUserCollectionToJSONObject(
|
||||
userUID,
|
||||
coll.id,
|
||||
);
|
||||
if (E.isLeft(result)) return E.left(result.left);
|
||||
|
||||
collectionListObjects.push(result.right);
|
||||
}
|
||||
|
||||
// If collectionID is not null, return JSONified data for specific collection
|
||||
if (collectionID) {
|
||||
// Get Details of collection
|
||||
const parentCollection = await this.getUserCollection(collectionID);
|
||||
if (E.isLeft(parentCollection)) return E.left(parentCollection.left);
|
||||
|
||||
// Fetch all child requests that belong to collectionID
|
||||
const requests = await this.prisma.userRequest.findMany({
|
||||
where: {
|
||||
userUid: userUID,
|
||||
collectionID: parentCollection.right.id,
|
||||
},
|
||||
orderBy: {
|
||||
orderIndex: 'asc',
|
||||
},
|
||||
});
|
||||
|
||||
return E.right(
|
||||
JSON.stringify({
|
||||
id: parentCollection.right.id,
|
||||
name: parentCollection.right.title,
|
||||
folders: collectionListObjects,
|
||||
requests: requests.map((x) => {
|
||||
return {
|
||||
id: x.id,
|
||||
name: x.title,
|
||||
...(x.request as Record<string, unknown>), // type casting x.request of type Prisma.JSONValue to an object to enable spread
|
||||
};
|
||||
}),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return E.right(JSON.stringify(collectionListObjects));
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a Prisma query object representation of a collection and its child collections and requests
|
||||
*
|
||||
* @param folder CollectionFolder from client
|
||||
* @param userID The User ID
|
||||
* @param orderIndex Initial OrderIndex of
|
||||
* @param reqType The Type of Collection
|
||||
* @returns A Prisma query object to create a collection, its child collections and requests
|
||||
*/
|
||||
private generatePrismaQueryObj(
|
||||
folder: CollectionFolder,
|
||||
userID: string,
|
||||
orderIndex: number,
|
||||
reqType: DBReqType,
|
||||
): Prisma.UserCollectionCreateInput {
|
||||
return {
|
||||
title: folder.name,
|
||||
user: {
|
||||
connect: {
|
||||
uid: userID,
|
||||
},
|
||||
},
|
||||
requests: {
|
||||
create: folder.requests.map((r, index) => ({
|
||||
title: r.name,
|
||||
user: {
|
||||
connect: {
|
||||
uid: userID,
|
||||
},
|
||||
},
|
||||
type: reqType,
|
||||
request: r,
|
||||
orderIndex: index + 1,
|
||||
})),
|
||||
},
|
||||
orderIndex: orderIndex,
|
||||
type: reqType,
|
||||
children: {
|
||||
create: folder.folders.map((f, index) =>
|
||||
this.generatePrismaQueryObj(f, userID, index + 1, reqType),
|
||||
),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Create new UserCollections and UserRequests from JSON string
|
||||
*
|
||||
* @param jsonString The JSON string of the content
|
||||
* @param userID The User ID
|
||||
* @param destCollectionID The Collection ID
|
||||
* @param reqType The Type of Collection
|
||||
* @returns An Either of a Boolean if the creation operation was successful
|
||||
*/
|
||||
async importCollectionsFromJSON(
|
||||
jsonString: string,
|
||||
userID: string,
|
||||
destCollectionID: string | null,
|
||||
reqType: DBReqType,
|
||||
) {
|
||||
// Check to see if jsonString is valid
|
||||
const collectionsList = stringToJson<CollectionFolder[]>(jsonString);
|
||||
if (E.isLeft(collectionsList)) return E.left(USER_COLL_INVALID_JSON);
|
||||
|
||||
// Check to see if parsed jsonString is an array
|
||||
if (!Array.isArray(collectionsList.right))
|
||||
return E.left(USER_COLL_INVALID_JSON);
|
||||
|
||||
// Check to see if destCollectionID belongs to this User
|
||||
if (destCollectionID) {
|
||||
const parentCollection = await this.getUserCollection(destCollectionID);
|
||||
if (E.isLeft(parentCollection)) return E.left(parentCollection.left);
|
||||
|
||||
// Check to see if parentUserCollectionID belongs to this User
|
||||
if (parentCollection.right.userUid !== userID)
|
||||
return E.left(USER_NOT_OWNER);
|
||||
|
||||
// Check to see if parent collection is of the same type of new collection being created
|
||||
if (parentCollection.right.type !== reqType)
|
||||
return E.left(USER_COLL_NOT_SAME_TYPE);
|
||||
}
|
||||
|
||||
// Get number of root or child collections for destCollectionID(if destcollectionID != null) or destTeamID(if destcollectionID == null)
|
||||
const count = !destCollectionID
|
||||
? await this.getRootCollectionsCount(userID)
|
||||
: await this.getChildCollectionsCount(destCollectionID);
|
||||
|
||||
// Generate Prisma Query Object for all child collections in collectionsList
|
||||
const queryList = collectionsList.right.map((x) =>
|
||||
this.generatePrismaQueryObj(x, userID, count + 1, reqType),
|
||||
);
|
||||
|
||||
const parent = destCollectionID
|
||||
? {
|
||||
connect: {
|
||||
id: destCollectionID,
|
||||
},
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const userCollections = await this.prisma.$transaction(
|
||||
queryList.map((x) =>
|
||||
this.prisma.userCollection.create({
|
||||
data: {
|
||||
...x,
|
||||
parent,
|
||||
},
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
userCollections.forEach((x) =>
|
||||
this.pubsub.publish(`user_coll/${userID}/created`, x),
|
||||
);
|
||||
|
||||
return E.right(true);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user