From a28774c2c473c49e1d8831ce0992bd50de9cf51a Mon Sep 17 00:00:00 2001 From: ankitsridhar16 Date: Tue, 20 Dec 2022 14:35:25 +0530 Subject: [PATCH] feat: added user-history resolvers, service files and module --- .../src/user-history/user-history.module.ts | 14 ++ .../src/user-history/user-history.resolver.ts | 48 ++++ .../src/user-history/user-history.service.ts | 226 ++++++++++++++++++ 3 files changed, 288 insertions(+) create mode 100644 packages/hoppscotch-backend/src/user-history/user-history.module.ts create mode 100644 packages/hoppscotch-backend/src/user-history/user-history.resolver.ts create mode 100644 packages/hoppscotch-backend/src/user-history/user-history.service.ts diff --git a/packages/hoppscotch-backend/src/user-history/user-history.module.ts b/packages/hoppscotch-backend/src/user-history/user-history.module.ts new file mode 100644 index 000000000..aaa20bfec --- /dev/null +++ b/packages/hoppscotch-backend/src/user-history/user-history.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { PrismaModule } from '../prisma/prisma.module'; +import { PubSubModule } from '../pubsub/pubsub.module'; +import { UserModule } from '../user/user.module'; +import { UserHistoryUserResolver } from './user.resolver'; +import { UserHistoryResolver } from './user-history.resolver'; +import { UserHistoryService } from './user-history.service'; + +@Module({ + imports: [PrismaModule, PubSubModule, UserModule], + providers: [UserHistoryResolver, UserHistoryService, UserHistoryUserResolver], + exports: [UserHistoryService], +}) +export class UserHistoryModule {} diff --git a/packages/hoppscotch-backend/src/user-history/user-history.resolver.ts b/packages/hoppscotch-backend/src/user-history/user-history.resolver.ts new file mode 100644 index 000000000..a4ee893fb --- /dev/null +++ b/packages/hoppscotch-backend/src/user-history/user-history.resolver.ts @@ -0,0 +1,48 @@ +import { Args, Mutation, Resolver } from '@nestjs/graphql'; +import { UserHistoryService } from './user-history.service'; +import { PubSubService } from '../pubsub/pubsub.service'; +import { UserHistory } from './user-history.model'; +import { UseGuards } from '@nestjs/common'; +import { GqlAuthGuard } from '../guards/gql-auth.guard'; +import { GqlUser } from '../decorators/gql-user.decorator'; +import { User } from '../user/user.model'; + +@Resolver() +export class UserHistoryResolver { + constructor( + private readonly userHistoryService: UserHistoryService, + private readonly pubsub: PubSubService, + ) {} + + /* Mutations */ + + @Mutation(() => UserHistory, { + description: 'Adds a new REST/GQL request to user history', + }) + @UseGuards(GqlAuthGuard) + async addRequestToHistory( + @GqlUser() user: User, + @Args({ + name: 'reqData', + description: 'JSON string of the request data', + }) + reqData: string, + @Args({ + name: 'resMetadata', + description: 'JSON string of the response metadata', + }) + resMetadata: string, + @Args({ + name: 'reqType', + description: 'string that denotes type of request REST or GQL', + }) + reqType: string, + ): Promise { + return await this.userHistoryService.addRequestToHistory( + user.uid, + reqData, + resMetadata, + reqType, + ); + } +} diff --git a/packages/hoppscotch-backend/src/user-history/user-history.service.ts b/packages/hoppscotch-backend/src/user-history/user-history.service.ts new file mode 100644 index 000000000..8fcdfbc11 --- /dev/null +++ b/packages/hoppscotch-backend/src/user-history/user-history.service.ts @@ -0,0 +1,226 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { PubSubService } from '../pubsub/pubsub.service'; +import { ReqType, UserHistory } from './user-history.model'; +import * as E from 'fp-ts/Either'; + +// Contains constants for the subscription types we send to pubsub service +enum SubscriptionType { + Created = 'created', + Updated = 'updated', + Deleted = 'deleted', +} + +@Injectable() +export class UserHistoryService { + constructor( + private readonly prisma: PrismaService, + private readonly pubsub: PubSubService, + ) {} + + /** + * Fetch users REST or GraphQL history based on ReqType argument. + * @param uid Users uid + * @param reqType request Type to fetch i.e. GraphQL or REST + * @returns an array of user history + */ + async fetchUserHistory(uid: string, reqType: ReqType) { + const userHistory = await this.prisma.userHistory.findMany({ + where: { + userUid: uid, + type: reqType, + }, + }); + + const userHistoryColl: UserHistory[] = []; + userHistory.forEach((history) => { + userHistoryColl.push({ + id: history.id, + userUid: history.userUid, + reqType: history.type, + request: JSON.stringify(history.request), + responseMetadata: JSON.stringify(history.responseMetadata), + isStarred: history.isStarred, + }); + }); + + return userHistoryColl; + } + + /** + * Adds a request to users history. + * @param uid Users uid + * @param reqData the request data + * @param resMetadata the response metadata + * @param reqType request Type to fetch i.e. GraphQL or REST + * @returns an array of user history + */ + async addRequestToHistory( + uid: string, + reqData: string, + resMetadata: string, + reqType: string, + ) { + const requestType = this.validateReqType(reqType); + const history = await this.prisma.userHistory.create({ + data: { + userUid: uid, + request: JSON.parse(reqData), + responseMetadata: JSON.parse(resMetadata), + type: requestType, + isStarred: false, + }, + }); + + const userHistory = { + id: history.id, + userUid: history.userUid, + request: JSON.stringify(history.request), + responseMetadata: JSON.stringify(history.responseMetadata), + executedOn: history.executedOn, + isStarred: history.isStarred, + reqType: history.type, + }; + + await this.publishUserHistorySubscription( + userHistory, + SubscriptionType.Created, + ); + + return userHistory; + } + + /** + * Stars or unstars a request in the history + * @param uid Users uid + * @param id id of the request in the history + * @returns an Either of updated `UserHistory` or Error + */ + async starUnstarRequestInHistory(uid: string, id: string) { + const userHistory = await this.prisma.userHistory.findFirst({ + where: { + id: id, + }, + }); + + if (userHistory == null) { + return E.left('history doesnt exist'); + } + try { + const updatedHistory = await this.prisma.userHistory.update({ + where: { + id: id, + }, + data: { + isStarred: !userHistory.isStarred, + }, + }); + + const updatedUserHistory = { + id: updatedHistory.id, + userUid: updatedHistory.userUid, + request: JSON.stringify(updatedHistory.request), + responseMetadata: JSON.stringify(updatedHistory.responseMetadata), + executedOn: updatedHistory.executedOn, + isStarred: updatedHistory.isStarred, + reqType: updatedHistory.type, + }; + + await this.publishUserHistorySubscription( + updatedUserHistory, + SubscriptionType.Updated, + ); + + return E.right(updatedUserHistory); + } catch (e) { + E.left('error updating'); + } + } + + /** + * Removes a REST/GraphQL request from the history + * @param uid Users uid + * @param id id of the request + * @returns an Either of deleted `UserHistory` or Error + */ + async removeRequestFromHistory(uid: string, id: string) { + try { + const delUserHistory = await this.prisma.userHistory.delete({ + where: { + id: id, + }, + }); + + const deletedUserHistory = { + id: delUserHistory.id, + userUid: delUserHistory.userUid, + request: JSON.stringify(delUserHistory.request), + responseMetadata: JSON.stringify(delUserHistory.responseMetadata), + executedOn: delUserHistory.executedOn, + isStarred: delUserHistory.isStarred, + reqType: delUserHistory.type, + }; + + await this.publishUserHistorySubscription( + deletedUserHistory, + SubscriptionType.Deleted, + ); + return E.right(deletedUserHistory); + } catch (e) { + return E.left('error deleting history not found'); + } + } + + /** + * Delete all REST/GraphQl user history based on ReqType + * @param uid Users uid + * @param reqType request type to be deleted i.e. REST or GraphQL + * @returns a count of deleted history + */ + async deleteAllUserHistory(uid: string, reqType: string) { + const requestType = this.validateReqType(reqType); + return await this.prisma.userHistory.deleteMany({ + where: { + userUid: uid, + type: requestType, + }, + }); + } + + // Method that takes a request type argument as string and validates against `ReqType` + validateReqType(reqType: string) { + let requestType: ReqType; + return reqType == ReqType.REST + ? (requestType = ReqType.REST) + : (requestType = ReqType.GQL); + } + + // Method to publish subscriptions based on the subscription type of the history + async publishUserHistorySubscription( + userHistory: UserHistory, + subscriptionType: SubscriptionType, + ) { + switch (subscriptionType) { + case SubscriptionType.Created: + await this.pubsub.publish( + `user_history/${userHistory.id}/created`, + userHistory, + ); + break; + case SubscriptionType.Updated: + await this.pubsub.publish( + `user_history/${userHistory.id}/updated`, + userHistory, + ); + break; + case SubscriptionType.Deleted: + await this.pubsub.publish( + `user_history/${userHistory.id}/deleted`, + userHistory, + ); + break; + default: + break; + } + } +}