feat: user session resolver added for sessions updates
This commit is contained in:
@@ -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',
|
||||||
|
});
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user