feat: user session resolver added for sessions updates

This commit is contained in:
Mir Arif Hasan
2023-01-24 17:19:24 +06:00
parent e6fcb1272a
commit 08ca57cba2
4 changed files with 115 additions and 107 deletions

View File

@@ -1,4 +1,10 @@
import { ObjectType, ID, Field, InputType } from '@nestjs/graphql'; import {
ObjectType,
ID,
Field,
InputType,
registerEnumType,
} from '@nestjs/graphql';
@ObjectType() @ObjectType()
export class User { export class User {
@@ -38,33 +44,11 @@ export class User {
currentGQLSession?: string; currentGQLSession?: string;
} }
@InputType() export enum SessionType {
export class UpdateUserInput { REST = 'REST',
@Field({ GQL = 'GQL',
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;
} }
registerEnumType(SessionType, {
name: 'SessionType',
});

View File

@@ -1,5 +1,5 @@
import { Resolver, Query, Mutation, Args, Subscription } from '@nestjs/graphql'; 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 { UseGuards } from '@nestjs/common';
import { GqlAuthGuard } from '../guards/gql-auth.guard'; import { GqlAuthGuard } from '../guards/gql-auth.guard';
import { GqlUser } from '../decorators/gql-user.decorator'; import { GqlUser } from '../decorators/gql-user.decorator';
@@ -38,14 +38,27 @@ export class UserResolver {
/* Mutations */ /* Mutations */
@Mutation(() => User, { @Mutation(() => User, {
description: 'Update user information', description: 'Update user sessions',
}) })
@UseGuards(GqlAuthGuard) @UseGuards(GqlAuthGuard)
async updateUser( async updateUserSessions(
@GqlUser() user: User, @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<User> { ): Promise<User> {
const updatedUser = await this.userService.updateUser(user, args); const updatedUser = await this.userService.updateUserSessions(
user,
currentSession,
sessionType,
);
if (E.isLeft(updatedUser)) throwErr(updatedUser.left); if (E.isLeft(updatedUser)) throwErr(updatedUser.left);
return updatedUser.right; return updatedUser.right;
} }

View File

@@ -25,77 +25,78 @@ beforeEach(() => {
}); });
describe('UserService', () => { describe('UserService', () => {
describe('updateUser', () => { describe('updateUserSessions', () => {
test('Should resolve and update user both GQL and REST session', async () => { test('Should resolve right and update users GQL session', async () => {
const sessionData = user.currentGQLSession;
mockPrisma.user.update.mockResolvedValue({ mockPrisma.user.update.mockResolvedValue({
...user, ...user,
currentGQLSession: JSON.parse(user.currentGQLSession), currentGQLSession: JSON.parse(sessionData),
currentRESTSession: JSON.parse(user.currentRESTSession), currentRESTSession: null,
}); });
const result = await userService.updateUser(user, { const result = await userService.updateUserSessions(
currentGQLSession: user.currentGQLSession, user,
currentRESTSession: user.currentRESTSession, sessionData,
}); 'GQL',
);
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,
});
expect(result).toEqualRight({ expect(result).toEqualRight({
...user, ...user,
currentGQLSession: newGqlSession, currentGQLSession: sessionData,
currentRESTSession: null, currentRESTSession: null,
}); });
}); });
test('Should reject update user for invalid GQL session', async () => { test('Should resolve right and update users REST session', async () => {
const newGqlSession = 'invalid json'; const sessionData = user.currentGQLSession;
mockPrisma.user.update.mockResolvedValue({ mockPrisma.user.update.mockResolvedValue({
...user, ...user,
currentGQLSession: newGqlSession, currentGQLSession: null,
currentRESTSession: undefined, currentRESTSession: JSON.parse(sessionData),
}); });
const result = await userService.updateUser(user, { const result = await userService.updateUserSessions(
currentGQLSession: newGqlSession, 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); 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({ mockPrisma.user.update.mockResolvedValue({
...user, ...user,
currentGQLSession: JSON.parse(user.currentGQLSession), currentGQLSession: JSON.parse(user.currentGQLSession),
currentRESTSession: JSON.parse(user.currentRESTSession), currentRESTSession: JSON.parse(user.currentRESTSession),
}); });
await userService.updateUser(user, { await userService.updateUserSessions(user, user.currentGQLSession, 'GQL');
currentGQLSession: user.currentGQLSession,
currentRESTSession: user.currentRESTSession,
});
expect(mockPubSub.publish).toHaveBeenCalledTimes(1); expect(mockPubSub.publish).toHaveBeenCalledTimes(1);
expect(mockPubSub.publish).toHaveBeenCalledWith( expect(mockPubSub.publish).toHaveBeenCalledWith(

View File

@@ -1,7 +1,6 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service'; import { PrismaService } from '../prisma/prisma.service';
import { UpdateUserInput, User } from './user.model'; import { SessionType, User } from './user.model';
import { User as DbUser, Prisma } from '@prisma/client';
import * as E from 'fp-ts/lib/Either'; import * as E from 'fp-ts/lib/Either';
import { USER_UPDATE_FAILED } from 'src/errors'; import { USER_UPDATE_FAILED } from 'src/errors';
import { PubSubService } from 'src/pubsub/pubsub.service'; 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 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 * @returns a Either of User or error
*/ */
async updateUser( async updateUserSessions(
user: User, user: User,
updateData: UpdateUserInput, currentSession: string,
): Promise<E.Either<string, User>> { sessionType: string,
let { currentGQLSession, currentRESTSession, ...rest } = updateData; ): Promise<E.Right<User> | E.Left<string>> {
let updateUserObj: Partial<DbUser> = rest; 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<string>(jsonGql)) return jsonGql;
updateUserObj.currentGQLSession = jsonGql?.right ?? Prisma.DbNull;
}
if (updateData?.currentRESTSession !== undefined) {
const jsonRest = stringToJson(updateData.currentRESTSession);
if (E.isLeft<string>(jsonRest)) return jsonRest;
updateUserObj.currentRESTSession = jsonRest?.right ?? Prisma.DbNull;
}
// Update user
try { 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({ const dbUpdatedUser = await this.prisma.user.update({
where: { uid: user.uid }, where: { uid: user.uid },
data: updateUserObj, data: sessionObj,
}); });
const updatedUser: User = { const updatedUser: User = {
@@ -66,4 +64,16 @@ export class UserService {
return E.left(USER_UPDATE_FAILED); 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);
}
} }