HBE-147 refactor: Introduce shortcodes into self-host refactored to pseudo-fp format (#22)

* refactor: refactor all queries,mutations and subscriptions for shortcode module

* test: rewrote test cases for shortcodes

* chore: modified shortcode error code

* chore: created helper function to do shortcode type conversion in service file

* chore: simplifed logic to fetch user shortcodes with cursor pagination

* chore: removed migrations file

* chore: removed unused imports in shortcodes module

* chore: modified generateUniqueShortCodeID function

* chore: modified generateUniqueShortCodeID function

* chore: changed jwtService to use verify instead of decode

* docs: added teacher comments to all shortcodes service methods

* chore: removed stale test cases from shortcode modules
This commit is contained in:
Balu Babu
2023-02-22 17:40:53 +05:30
committed by GitHub
parent 24dd535d9e
commit 1860057a25
7 changed files with 470 additions and 743 deletions

View File

@@ -1,18 +1,22 @@
import { Injectable, OnModuleInit } from '@nestjs/common';
import { flow, pipe } from 'fp-ts/function';
import * as T from 'fp-ts/Task';
import * as TE from 'fp-ts/TaskEither';
import * as O from 'fp-ts/Option';
import * as TO from 'fp-ts/TaskOption';
import * as A from 'fp-ts/Array';
import * as E from 'fp-ts/Either';
import { PrismaService } from 'src/prisma/prisma.service';
import { SHORTCODE_NOT_FOUND } from 'src/errors';
import {
SHORTCODE_ALREADY_EXISTS,
SHORTCODE_INVALID_JSON,
SHORTCODE_NOT_FOUND,
} from 'src/errors';
import { User } from 'src/user/user.model';
import { UserDataHandler } from 'src/user/user.data.handler';
import { Shortcode } from './shortcode.model';
import { Shortcode as DBShortCode } from '@prisma/client';
import { PubSubService } from 'src/pubsub/pubsub.service';
import { UserService } from 'src/user/user.service';
import { stringToJson } from 'src/utils';
import { PaginationArgs } from 'src/types/input-types.args';
const SHORT_CODE_LENGTH = 12;
const SHORT_CODE_CHARS =
@@ -39,7 +43,26 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
return undefined;
}
private generateShortcodeID(): string {
/**
* Converts a Prisma Shortcode type into the Shortcode model
*
* @param shortcodeInfo Prisma Shortcode type
* @returns GQL Shortcode
*/
private returnShortCode(shortcodeInfo: DBShortCode): Shortcode {
return <Shortcode>{
id: shortcodeInfo.id,
request: JSON.stringify(shortcodeInfo.request),
createdOn: shortcodeInfo.createdOn,
};
}
/**
* Generate a shortcode
*
* @returns generated shortcode
*/
private generateShortCodeID(): string {
let result = '';
for (let i = 0; i < SHORT_CODE_LENGTH; i++) {
result +=
@@ -48,189 +71,142 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
return result;
}
private async generateUniqueShortcodeID(): Promise<string> {
/**
* Check to see if ShortCode is already present in DB
*
* @returns Shortcode
*/
private async generateUniqueShortCodeID() {
while (true) {
const code = this.generateShortcodeID();
const code = this.generateShortCodeID();
const data = await this.resolveShortcode(code)();
const data = await this.getShortCode(code);
if (O.isNone(data)) return code;
if (E.isLeft(data)) return E.right(code);
}
}
resolveShortcode(shortcode: string): TO.TaskOption<Shortcode> {
return pipe(
// The task to perform
() => this.prisma.shortcode.findFirst({ where: { id: shortcode } }),
TO.fromTask, // Convert to Task to TaskOption
TO.chain(TO.fromNullable), // Remove nullability
TO.map((data) => {
return <Shortcode>{
id: data.id,
request: JSON.stringify(data.request),
createdOn: data.createdOn,
};
}),
);
/**
* Fetch details regarding a ShortCode
*
* @param shortcode ShortCode
* @returns Either of ShortCode details or error
*/
async getShortCode(shortcode: string) {
try {
const shortcodeInfo = await this.prisma.shortcode.findFirstOrThrow({
where: { id: shortcode },
});
return E.right(this.returnShortCode(shortcodeInfo));
} catch (error) {
return E.left(SHORTCODE_NOT_FOUND);
}
}
// TODO: Implement create shortcode and the user service method
// createShortcode(request: any, creator?: User): T.Task<Shortcode> {
// return pipe(
// T.Do,
//
// // Get shortcode
// T.bind('shortcode', () => () => this.generateUniqueShortcodeID()),
//
// // Create
// T.chain(
// ({ shortcode }) =>
// () =>
// this.prisma.shortcode.create({
// data: {
// id: shortcode,
// request: request,
// creatorUid: creator?.uid,
// },
// }),
// ),
//
// T.chainFirst((shortcode) => async () => {
// // Only publish event if creator is not null
// if (shortcode.creatorUid) {
// this.pubsub.publish(`shortcode/${shortcode.creatorUid}/created`, <
// Shortcode
// >{
// id: shortcode.id,
// request: JSON.stringify(shortcode.request),
// createdOn: shortcode.createdOn,
// });
// }
// }),
//
// // Map to valid return type
// T.map(
// (data) =>
// <Shortcode>{
// id: data.id,
// request: JSON.stringify(data.request),
// createdOn: data.createdOn,
// },
// ),
// );
// }
/**
* Create a new ShortCode
*
* @param request JSON string of request details
* @param userUID user UID, if present
* @returns Either of ShortCode or error
*/
async createShortcode(request: string, userUID: string | null) {
const shortcodeData = stringToJson(request);
if (E.isLeft(shortcodeData)) return E.left(SHORTCODE_INVALID_JSON);
fetchUserShortCodes(uid: string, cursor: string | null) {
return pipe(
cursor,
O.fromNullable,
O.fold(
() =>
pipe(
() =>
this.prisma.shortcode.findMany({
take: 10,
where: {
creatorUid: uid,
},
orderBy: {
createdOn: 'desc',
},
}),
T.map((codes) =>
codes.map(
(data) =>
<Shortcode>{
id: data.id,
request: JSON.stringify(data.request),
createdOn: data.createdOn,
},
),
),
),
(cursor) =>
pipe(
() =>
this.prisma.shortcode.findMany({
take: 10,
skip: 1,
cursor: {
id: cursor,
},
where: {
creatorUid: uid,
},
orderBy: {
createdOn: 'desc',
},
}),
T.map((codes) =>
codes.map(
(data) =>
<Shortcode>{
id: data.id,
request: JSON.stringify(data.request),
createdOn: data.createdOn,
},
),
),
),
),
);
const user = await this.userService.findUserById(userUID);
const generatedShortCode = await this.generateUniqueShortCodeID();
if (E.isLeft(generatedShortCode)) return E.left(generatedShortCode.left);
const createdShortCode = await this.prisma.shortcode.create({
data: {
id: generatedShortCode.right,
request: shortcodeData.right,
creatorUid: O.isNone(user) ? null : user.value.uid,
},
});
// Only publish event if creator is not null
if (createdShortCode.creatorUid) {
this.pubsub.publish(
`shortcode/${createdShortCode.creatorUid}/created`,
this.returnShortCode(createdShortCode),
);
}
return E.right(this.returnShortCode(createdShortCode));
}
// TODO: Implement revoke shortcode and user shortcode deletion feature
// revokeShortCode(shortcode: string, uid: string) {
// return pipe(
// TE.tryCatch(
// () =>
// this.prisma.shortcode.delete({
// where: {
// creator_uid_shortcode_unique: {
// creatorUid: uid,
// id: shortcode,
// },
// },
// }),
// () => SHORTCODE_NOT_FOUND,
// ),
// TE.chainFirst((shortcode) =>
// TE.fromTask(() =>
// this.pubsub.publish(`shortcode/${shortcode.creatorUid}/revoked`, <
// Shortcode
// >{
// id: shortcode.id,
// request: JSON.stringify(shortcode.request),
// createdOn: shortcode.createdOn,
// }),
// ),
// ),
// TE.map(
// (data) =>
// <Shortcode>{
// id: data.id,
// request: JSON.stringify(data.request),
// createdOn: data.createdOn,
// },
// ),
// );
// }
/**
* Fetch ShortCodes created by a User
*
* @param uid User Uid
* @param args Pagination arguments
* @returns Array of ShortCodes
*/
async fetchUserShortCodes(uid: string, args: PaginationArgs) {
const shortCodes = await this.prisma.shortcode.findMany({
where: {
creatorUid: uid,
},
orderBy: {
createdOn: 'desc',
},
skip: 1,
take: args.take,
cursor: args.cursor ? { id: args.cursor } : undefined,
});
// deleteUserShortcodes(uid: string) {
// return pipe(
// () =>
// this.prisma.shortcode.findMany({
// where: {
// creatorUid: uid,
// },
// }),
// T.chain(
// flow(
// A.map((shortcode) => this.revokeShortCode(shortcode.id, uid)),
// T.sequenceArray,
// ),
// ),
// T.map(() => undefined),
// );
// }
const fetchedShortCodes: Shortcode[] = shortCodes.map((code) =>
this.returnShortCode(code),
);
return fetchedShortCodes;
}
/**
* Delete a ShortCode
*
* @param shortcode ShortCode
* @param uid User Uid
* @returns Boolean on successful deletion
*/
async revokeShortCode(shortcode: string, uid: string) {
try {
const deletedShortCodes = await this.prisma.shortcode.delete({
where: {
creator_uid_shortcode_unique: {
creatorUid: uid,
id: shortcode,
},
},
});
this.pubsub.publish(
`shortcode/${deletedShortCodes.creatorUid}/revoked`,
this.returnShortCode(deletedShortCodes),
);
return E.right(true);
} catch (error) {
return E.left(SHORTCODE_NOT_FOUND);
}
}
/**
* Delete all of Users ShortCodes
*
* @param uid User Uid
* @returns number of all deleted user ShortCodes
*/
async deleteUserShortCodes(uid: string) {
const deletedShortCodes = await this.prisma.shortcode.deleteMany({
where: {
creatorUid: uid,
},
});
return deletedShortCodes.count;
}
}