diff --git a/packages/hoppscotch-backend/prisma/schema.prisma b/packages/hoppscotch-backend/prisma/schema.prisma index 255781ec4..37247d244 100644 --- a/packages/hoppscotch-backend/prisma/schema.prisma +++ b/packages/hoppscotch-backend/prisma/schema.prisma @@ -83,8 +83,8 @@ model User { displayName String? email String? photoURL String? - currentRESTSession String? - currentGQLSession String? + currentRESTSession Json? + currentGQLSession Json? UserEnvironments UserEnvironment[] } diff --git a/packages/hoppscotch-backend/src/errors.ts b/packages/hoppscotch-backend/src/errors.ts index a03e437c2..fe2d6dd45 100644 --- a/packages/hoppscotch-backend/src/errors.ts +++ b/packages/hoppscotch-backend/src/errors.ts @@ -26,6 +26,12 @@ export const USER_FB_DOCUMENT_DELETION_FAILED = */ export const USER_NOT_FOUND = 'user/not_found' as const; +/** + * User update failure + * (UserService) + */ +export const USER_UPDATE_FAILED = 'user/update_failed' as const; + /** * User deletion failure * (UserService) diff --git a/packages/hoppscotch-backend/src/pubsub/topicsDefs.ts b/packages/hoppscotch-backend/src/pubsub/topicsDefs.ts index d7bfa10d0..4d0cd959e 100644 --- a/packages/hoppscotch-backend/src/pubsub/topicsDefs.ts +++ b/packages/hoppscotch-backend/src/pubsub/topicsDefs.ts @@ -7,8 +7,6 @@ export type TopicDef = { [ topic: `user_environment/${string}/${'created' | 'updated' | 'deleted'}` ]: UserEnvironment; - [ - topic: `user/${string}/${'created' | 'updated' | 'deleted'}` - ]: User; + [topic: `user/${string}/${'updated'}`]: User; [topic: `user_environment/${string}/deleted_many`]: number; }; diff --git a/packages/hoppscotch-backend/src/user/dtos/update-user-input.dto.ts b/packages/hoppscotch-backend/src/user/dtos/update-user-input.dto.ts deleted file mode 100644 index b5fd7d0a6..000000000 --- a/packages/hoppscotch-backend/src/user/dtos/update-user-input.dto.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { Field, InputType } from '@nestjs/graphql'; - -@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; -} diff --git a/packages/hoppscotch-backend/src/user/user.model.ts b/packages/hoppscotch-backend/src/user/user.model.ts index 6db48b3e1..da83a99c5 100644 --- a/packages/hoppscotch-backend/src/user/user.model.ts +++ b/packages/hoppscotch-backend/src/user/user.model.ts @@ -1,4 +1,4 @@ -import { ObjectType, ID, Field } from '@nestjs/graphql'; +import { ObjectType, ID, Field, InputType } from '@nestjs/graphql'; @ObjectType() export class User { @@ -37,3 +37,34 @@ 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; +} diff --git a/packages/hoppscotch-backend/src/user/user.resolver.ts b/packages/hoppscotch-backend/src/user/user.resolver.ts index 99475d0d5..3d2abd9dc 100644 --- a/packages/hoppscotch-backend/src/user/user.resolver.ts +++ b/packages/hoppscotch-backend/src/user/user.resolver.ts @@ -1,12 +1,11 @@ import { Resolver, Query, Mutation, Args, Subscription } from '@nestjs/graphql'; -import { User } from './user.model'; +import { UpdateUserInput, User } from './user.model'; import { UseGuards } from '@nestjs/common'; import { GqlAuthGuard } from '../guards/gql-auth.guard'; import { GqlUser } from '../decorators/gql-user.decorator'; import { UserService } from './user.service'; import { throwErr } from 'src/utils'; import * as E from 'fp-ts/lib/Either'; -import { UpdateUserInput } from './dtos/update-user-input.dto'; import { PubSubService } from 'src/pubsub/pubsub.service'; @Resolver(() => User) @@ -58,7 +57,7 @@ export class UserResolver { resolve: (value) => value, }) @UseGuards(GqlAuthGuard) - userSettingsUpdated(@GqlUser() user: User) { + userUpdated(@GqlUser() user: User) { return this.pubsub.asyncIterator(`user/${user.uid}/updated`); } } diff --git a/packages/hoppscotch-backend/src/user/user.service.ts b/packages/hoppscotch-backend/src/user/user.service.ts index cdb1f64e2..a2ad712b4 100644 --- a/packages/hoppscotch-backend/src/user/user.service.ts +++ b/packages/hoppscotch-backend/src/user/user.service.ts @@ -1,10 +1,11 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; -import { User } from './user.model'; +import { UpdateUserInput, User } from './user.model'; +import { User as DbUser, Prisma } from '@prisma/client'; import * as E from 'fp-ts/lib/Either'; -import { USER_NOT_FOUND } from 'src/errors'; -import { UpdateUserInput } from './dtos/update-user-input.dto'; +import { USER_UPDATE_FAILED } from 'src/errors'; import { PubSubService } from 'src/pubsub/pubsub.service'; +import { stringToJson } from 'src/utils'; @Injectable() export class UserService { @@ -16,22 +17,53 @@ export class UserService { /** * Update a user's information * @param user User object - * @param updateUserData Properties to update + * @param updateData Properties to update * @returns a Either of User or error */ - async updateUser(user: User, updateUserData: UpdateUserInput) { + async updateUser( + user: User, + updateData: UpdateUserInput, + ): Promise> { + let { currentGQLSession, currentRESTSession, ...rest } = updateData; + let updateUserObj: Partial = rest; + + // 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 updatedUser = await this.prisma.user.update({ + const dbUpdatedUser = await this.prisma.user.update({ where: { uid: user.uid }, - data: updateUserData, + data: updateUserObj, }); + const updatedUser: User = { + ...dbUpdatedUser, + currentGQLSession: dbUpdatedUser.currentGQLSession + ? JSON.stringify(dbUpdatedUser.currentGQLSession) + : null, + currentRESTSession: dbUpdatedUser.currentRESTSession + ? JSON.stringify(dbUpdatedUser.currentRESTSession) + : null, + }; + // Publish subscription for user updates - await this.pubsub.publish(`user/${user.uid}/updated`, updatedUser); + await this.pubsub.publish(`user/${updatedUser.uid}/updated`, updatedUser); return E.right(updatedUser); } catch (e) { - return E.left(USER_NOT_FOUND); + return E.left(USER_UPDATE_FAILED); } } }