From 4affb2bc5bb8e7995c4ed96fe66a410a3ace9be7 Mon Sep 17 00:00:00 2001 From: Mir Arif Hasan Date: Mon, 19 Dec 2022 17:38:46 +0600 Subject: [PATCH] feat: added user-settings schema and user-settings module --- .../hoppscotch-backend/prisma/schema.prisma | 9 +++++ packages/hoppscotch-backend/src/app.module.ts | 2 + packages/hoppscotch-backend/src/errors.ts | 6 +++ .../src/user-settings/user-settings.model.ts | 24 ++++++++++++ .../src/user-settings/user-settings.module.ts | 11 ++++++ .../user-settings/user-settings.resolver.ts | 39 +++++++++++++++++++ .../user-settings/user-settings.service.ts | 36 +++++++++++++++++ packages/hoppscotch-backend/src/utils.ts | 15 +++++++ 8 files changed, 142 insertions(+) create mode 100644 packages/hoppscotch-backend/src/user-settings/user-settings.model.ts create mode 100644 packages/hoppscotch-backend/src/user-settings/user-settings.module.ts create mode 100644 packages/hoppscotch-backend/src/user-settings/user-settings.resolver.ts create mode 100644 packages/hoppscotch-backend/src/user-settings/user-settings.service.ts diff --git a/packages/hoppscotch-backend/prisma/schema.prisma b/packages/hoppscotch-backend/prisma/schema.prisma index 947d158a8..650affa80 100644 --- a/packages/hoppscotch-backend/prisma/schema.prisma +++ b/packages/hoppscotch-backend/prisma/schema.prisma @@ -83,6 +83,15 @@ model User { displayName String? email String? photoURL String? + settings UserSettings? +} + +model UserSettings { + id String @id @default(uuid()) + userUid String @unique + user User @relation(fields: [userUid], references: [uid], onDelete: Cascade) + properties Json + updatedOn DateTime @updatedAt } enum TeamMemberRole { diff --git a/packages/hoppscotch-backend/src/app.module.ts b/packages/hoppscotch-backend/src/app.module.ts index 7614320b9..92c105f00 100644 --- a/packages/hoppscotch-backend/src/app.module.ts +++ b/packages/hoppscotch-backend/src/app.module.ts @@ -3,6 +3,7 @@ import { GraphQLModule } from '@nestjs/graphql'; import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo'; import { UserModule } from './user/user.module'; import { GQLComplexityPlugin } from './plugins/GQLComplexityPlugin'; +import { UserSettingsModule } from './user-settings/user-settings.module'; @Module({ imports: [ @@ -44,6 +45,7 @@ import { GQLComplexityPlugin } from './plugins/GQLComplexityPlugin'; driver: ApolloDriver, }), UserModule, + UserSettingsModule ], providers: [GQLComplexityPlugin], }) diff --git a/packages/hoppscotch-backend/src/errors.ts b/packages/hoppscotch-backend/src/errors.ts index 794a06db5..2e8641e50 100644 --- a/packages/hoppscotch-backend/src/errors.ts +++ b/packages/hoppscotch-backend/src/errors.ts @@ -8,6 +8,12 @@ export const EMAIL_FAILED = 'email/failed' as const; */ export const AUTH_FAIL = 'auth/fail'; +/** + * Invalid JSON + * (Utils) + */ +export const JSON_INVALID = 'json_invalid'; + /** * Tried to delete an user data document from fb firestore but failed. * (FirebaseService) diff --git a/packages/hoppscotch-backend/src/user-settings/user-settings.model.ts b/packages/hoppscotch-backend/src/user-settings/user-settings.model.ts new file mode 100644 index 000000000..f49824689 --- /dev/null +++ b/packages/hoppscotch-backend/src/user-settings/user-settings.model.ts @@ -0,0 +1,24 @@ +import { Field, ID, ObjectType } from '@nestjs/graphql'; + +@ObjectType() +export class UserSettings { + @Field(() => ID, { + description: 'ID of the User Settings', + }) + id: string; + + @Field(() => ID, { + description: 'ID of the user this settings belongs to', + }) + userUid: string; + + @Field({ + description: 'All properties present in the settings', + }) + properties: string; // JSON string of the properties object (format:[{ key: "background", value: "system" }, ...] ) which will be received from the client + + @Field({ + description: 'Last updated date-time of the settings', + }) + updatedOn: Date; +} diff --git a/packages/hoppscotch-backend/src/user-settings/user-settings.module.ts b/packages/hoppscotch-backend/src/user-settings/user-settings.module.ts new file mode 100644 index 000000000..f85f36150 --- /dev/null +++ b/packages/hoppscotch-backend/src/user-settings/user-settings.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { PrismaModule } from 'src/prisma/prisma.module'; +import { PubSubModule } from 'src/pubsub/pubsub.module'; +import { UserSettingsResolver } from './user-settings.resolver'; +import { UserSettingsService } from './user-settings.service'; + +@Module({ + imports: [PrismaModule, PubSubModule], + providers: [UserSettingsResolver, UserSettingsService], +}) +export class UserSettingsModule {} diff --git a/packages/hoppscotch-backend/src/user-settings/user-settings.resolver.ts b/packages/hoppscotch-backend/src/user-settings/user-settings.resolver.ts new file mode 100644 index 000000000..a686a9b69 --- /dev/null +++ b/packages/hoppscotch-backend/src/user-settings/user-settings.resolver.ts @@ -0,0 +1,39 @@ +import { UseGuards } from '@nestjs/common'; +import { Args, Mutation, Resolver } from '@nestjs/graphql'; +import { GqlUser } from 'src/decorators/gql-user.decorator'; +import { GqlAuthGuard } from 'src/guards/gql-auth.guard'; +import { User } from 'src/user/user.model'; +import * as E from 'fp-ts/Either'; +import { throwErr } from 'src/utils'; +import { UserSettings } from './user-settings.model'; +import { UserSettingsService } from './user-settings.service'; + +@Resolver() +export class UserSettingsResolver { + constructor(private readonly userSettingsService: UserSettingsService) {} + + /* Mutations */ + + @Mutation(() => UserSettings, { + description: 'Creates a new user settings for given user', + }) + @UseGuards(GqlAuthGuard) + async createUserSettings( + @GqlUser() user: User, + @Args({ + name: 'properties', + description: 'JSON string of properties object', + }) + properties: string, + ) { + const userSettings = await this.userSettingsService.createUserSettings( + user, + properties, + ); + + if (E.isLeft(userSettings)) throwErr(userSettings.left); + return userSettings.right; + } + + /* Subscriptions */ +} diff --git a/packages/hoppscotch-backend/src/user-settings/user-settings.service.ts b/packages/hoppscotch-backend/src/user-settings/user-settings.service.ts new file mode 100644 index 000000000..f69eb20e8 --- /dev/null +++ b/packages/hoppscotch-backend/src/user-settings/user-settings.service.ts @@ -0,0 +1,36 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from 'src/prisma/prisma.service'; +import { PubSubService } from 'src/pubsub/pubsub.service'; +import { User } from 'src/user/user.model'; +import * as E from 'fp-ts/Either'; +import { stringToJson } from 'src/utils'; +import { UserSettings } from './user-settings.model'; + +@Injectable() +export class UserSettingsService { + constructor( + private readonly prisma: PrismaService, + private readonly pubsub: PubSubService, + ) {} + + async createUserSettings(user: User, properties: string) { + const jsonProperties = stringToJson(properties); + if (E.isLeft(jsonProperties)) return E.left(jsonProperties.left); + + const dbUserSettings = await this.prisma.userSettings.create({ + data: { + properties: jsonProperties.right, + userUid: user.uid, + }, + }); + + const userSettings: UserSettings = { + id: dbUserSettings.id, + userUid: dbUserSettings.userUid, + properties, + updatedOn: dbUserSettings.updatedOn, + }; + + return E.right(userSettings); + } +} diff --git a/packages/hoppscotch-backend/src/utils.ts b/packages/hoppscotch-backend/src/utils.ts index a4756f5e6..35d2c8139 100644 --- a/packages/hoppscotch-backend/src/utils.ts +++ b/packages/hoppscotch-backend/src/utils.ts @@ -4,8 +4,10 @@ import { pipe } from 'fp-ts/lib/function'; import * as O from 'fp-ts/Option'; import * as TE from 'fp-ts/TaskEither'; import * as T from 'fp-ts/Task'; +import * as E from 'fp-ts/Either'; import { User } from './user/user.model'; import * as A from 'fp-ts/Array'; +import { JSON_INVALID } from './errors'; /** * A workaround to throw an exception in an expression. @@ -28,6 +30,19 @@ export const trace = (val: T) => { return val; }; +/** + * String to JSON parser + * @param {str} str The string to parse + * @returns {E.Right | E.Left<"json_invalid">} An Either of the parsed JSON + */ +export function stringToJson(str: string): E.Right | E.Left { + try { + return E.right(JSON.parse(str)); + } catch (err) { + return E.left(JSON_INVALID); + } +} + /** * Similar to `trace` but allows for labels and also an * optional transform function.