diff --git a/packages/hoppscotch-backend/src/user/user.model.ts b/packages/hoppscotch-backend/src/user/user.model.ts index da83a99c5..d06319358 100644 --- a/packages/hoppscotch-backend/src/user/user.model.ts +++ b/packages/hoppscotch-backend/src/user/user.model.ts @@ -1,4 +1,10 @@ -import { ObjectType, ID, Field, InputType } from '@nestjs/graphql'; +import { + ObjectType, + ID, + Field, + InputType, + registerEnumType, +} from '@nestjs/graphql'; @ObjectType() export class User { @@ -38,33 +44,11 @@ export class User { currentGQLSession?: string; } -@InputType() -export class UpdateUserInput { - @Field({ - nullable: true, - name: 'displayName', - description: 'Displayed name of the user (if given)', - }) - displayName?: string; - - @Field({ - nullable: true, - name: 'photoURL', - description: 'URL to the profile photo of the user (if given)', - }) - photoURL?: string; - - @Field({ - nullable: true, - name: 'currentRESTSession', - description: 'JSON string of the saved REST session', - }) - currentRESTSession?: string; - - @Field({ - nullable: true, - name: 'currentGQLSession', - description: 'JSON string of the saved GQL session', - }) - currentGQLSession?: string; +export enum SessionType { + REST = 'REST', + GQL = 'GQL', } + +registerEnumType(SessionType, { + name: 'SessionType', +}); diff --git a/packages/hoppscotch-backend/src/user/user.resolver.ts b/packages/hoppscotch-backend/src/user/user.resolver.ts index 0120a499f..a5e288934 100644 --- a/packages/hoppscotch-backend/src/user/user.resolver.ts +++ b/packages/hoppscotch-backend/src/user/user.resolver.ts @@ -1,5 +1,5 @@ import { Resolver, Query, Mutation, Args, Subscription } from '@nestjs/graphql'; -import { UpdateUserInput, User } from './user.model'; +import { SessionType, User } from './user.model'; import { UseGuards } from '@nestjs/common'; import { GqlAuthGuard } from '../guards/gql-auth.guard'; import { GqlUser } from '../decorators/gql-user.decorator'; @@ -38,14 +38,27 @@ export class UserResolver { /* Mutations */ @Mutation(() => User, { - description: 'Update user information', + description: 'Update user sessions', }) @UseGuards(GqlAuthGuard) - async updateUser( + async updateUserSessions( @GqlUser() user: User, - @Args('args') args: UpdateUserInput, + @Args({ + name: 'currentSession', + description: 'JSON string of the saved REST/GQL session', + }) + currentSession: string, + @Args({ + name: 'sessionType', + description: 'Type of the session', + }) + sessionType: SessionType, ): Promise { - const updatedUser = await this.userService.updateUser(user, args); + const updatedUser = await this.userService.updateUserSessions( + user, + currentSession, + sessionType, + ); if (E.isLeft(updatedUser)) throwErr(updatedUser.left); return updatedUser.right; } diff --git a/packages/hoppscotch-backend/src/user/user.service.spec.ts b/packages/hoppscotch-backend/src/user/user.service.spec.ts index b9d4849ba..4561a6f61 100644 --- a/packages/hoppscotch-backend/src/user/user.service.spec.ts +++ b/packages/hoppscotch-backend/src/user/user.service.spec.ts @@ -25,77 +25,78 @@ beforeEach(() => { }); describe('UserService', () => { - describe('updateUser', () => { - test('Should resolve and update user both GQL and REST session', async () => { + describe('updateUserSessions', () => { + test('Should resolve right and update users GQL session', async () => { + const sessionData = user.currentGQLSession; mockPrisma.user.update.mockResolvedValue({ ...user, - currentGQLSession: JSON.parse(user.currentGQLSession), - currentRESTSession: JSON.parse(user.currentRESTSession), + currentGQLSession: JSON.parse(sessionData), + currentRESTSession: null, }); - const result = await userService.updateUser(user, { - currentGQLSession: user.currentGQLSession, - currentRESTSession: user.currentRESTSession, - }); - - expect(result).toEqualRight(user); - }); - test('Should resolve and update user only with GQL session', async () => { - mockPrisma.user.update.mockResolvedValue({ - ...user, - currentGQLSession: JSON.parse(user.currentGQLSession), - currentRESTSession: undefined, - }); - - const result = await userService.updateUser(user, { - currentGQLSession: user.currentGQLSession, - }); - - expect(result).toEqualRight({ ...user, currentRESTSession: null }); - }); - test('Should reject update user for invalid GQL session', async () => { - const newGqlSession = null; - mockPrisma.user.update.mockResolvedValue({ - ...user, - currentGQLSession: newGqlSession, - currentRESTSession: undefined, - }); - - const result = await userService.updateUser(user, { - currentGQLSession: newGqlSession, - }); + const result = await userService.updateUserSessions( + user, + sessionData, + 'GQL', + ); expect(result).toEqualRight({ ...user, - currentGQLSession: newGqlSession, + currentGQLSession: sessionData, currentRESTSession: null, }); }); - test('Should reject update user for invalid GQL session', async () => { - const newGqlSession = 'invalid json'; + test('Should resolve right and update users REST session', async () => { + const sessionData = user.currentGQLSession; mockPrisma.user.update.mockResolvedValue({ ...user, - currentGQLSession: newGqlSession, - currentRESTSession: undefined, + currentGQLSession: null, + currentRESTSession: JSON.parse(sessionData), }); - const result = await userService.updateUser(user, { - currentGQLSession: newGqlSession, + const result = await userService.updateUserSessions( + user, + sessionData, + 'REST', + ); + + expect(result).toEqualRight({ + ...user, + currentGQLSession: null, + currentRESTSession: sessionData, }); + }); + test('Should reject left and update user for invalid GQL session', async () => { + const sessionData = 'invalid json'; + + const result = await userService.updateUserSessions( + user, + sessionData, + 'GQL', + ); expect(result).toEqualLeft(JSON_INVALID); }); - test('Should publish pubsub message on user update', async () => { + test('Should reject left and update user for invalid REST session', async () => { + const sessionData = 'invalid json'; + + const result = await userService.updateUserSessions( + user, + sessionData, + 'REST', + ); + + expect(result).toEqualLeft(JSON_INVALID); + }); + + test('Should publish pubsub message on user update sessions', async () => { mockPrisma.user.update.mockResolvedValue({ ...user, currentGQLSession: JSON.parse(user.currentGQLSession), currentRESTSession: JSON.parse(user.currentRESTSession), }); - await userService.updateUser(user, { - currentGQLSession: user.currentGQLSession, - currentRESTSession: user.currentRESTSession, - }); + await userService.updateUserSessions(user, user.currentGQLSession, 'GQL'); expect(mockPubSub.publish).toHaveBeenCalledTimes(1); expect(mockPubSub.publish).toHaveBeenCalledWith( diff --git a/packages/hoppscotch-backend/src/user/user.service.ts b/packages/hoppscotch-backend/src/user/user.service.ts index a2ad712b4..9b0d6be1c 100644 --- a/packages/hoppscotch-backend/src/user/user.service.ts +++ b/packages/hoppscotch-backend/src/user/user.service.ts @@ -1,7 +1,6 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; -import { UpdateUserInput, User } from './user.model'; -import { User as DbUser, Prisma } from '@prisma/client'; +import { SessionType, User } from './user.model'; import * as E from 'fp-ts/lib/Either'; import { USER_UPDATE_FAILED } from 'src/errors'; import { PubSubService } from 'src/pubsub/pubsub.service'; @@ -15,37 +14,36 @@ export class UserService { ) {} /** - * Update a user's information + * Update a user's sessions * @param user User object - * @param updateData Properties to update + * @param currentRESTSession user's current REST session + * @param currentGQLSession user's current GQL session * @returns a Either of User or error */ - async updateUser( + async updateUserSessions( user: User, - updateData: UpdateUserInput, - ): Promise> { - let { currentGQLSession, currentRESTSession, ...rest } = updateData; - let updateUserObj: Partial = rest; + currentSession: string, + sessionType: string, + ): Promise | E.Left> { + const validatedSession = await this.validateSession(currentSession); + if (E.isLeft(validatedSession)) return E.left(validatedSession.left); - // Convert stringified JSON to JSON - if (updateData?.currentGQLSession !== undefined) { - const jsonGql = stringToJson(updateData.currentGQLSession); - if (E.isLeft(jsonGql)) return jsonGql; - - updateUserObj.currentGQLSession = jsonGql?.right ?? Prisma.DbNull; - } - if (updateData?.currentRESTSession !== undefined) { - const jsonRest = stringToJson(updateData.currentRESTSession); - if (E.isLeft(jsonRest)) return jsonRest; - - updateUserObj.currentRESTSession = jsonRest?.right ?? Prisma.DbNull; - } - - // Update user try { + const sessionObj = {}; + switch (sessionType) { + case SessionType.GQL: + sessionObj['currentGQLSession'] = validatedSession.right; + break; + case SessionType.REST: + sessionObj['currentRESTSession'] = validatedSession.right; + break; + default: + return E.left(USER_UPDATE_FAILED); + } + const dbUpdatedUser = await this.prisma.user.update({ where: { uid: user.uid }, - data: updateUserObj, + data: sessionObj, }); const updatedUser: User = { @@ -66,4 +64,16 @@ export class UserService { return E.left(USER_UPDATE_FAILED); } } + + /** + * Validate and parse currentRESTSession and currentGQLSession + * @param sessionData string of the session + * @returns a Either of JSON object or error + */ + async validateSession(sessionData: string) { + const jsonSession = stringToJson(sessionData); + if (E.isLeft(jsonSession)) return E.left(jsonSession.left); + + return E.right(jsonSession.right); + } }