feat: Introducing Admin Module to Backend (HBE-83) (#21)
* feat: introducing admin module, resolvers and service files as a module * feat: adding admin module in the app module * feat: introducing admin guard and decorator for allowing admin operations * feat: invited user model * chore: added user invitation mail description to mailer service * chore: added admin and user related error * feat: added invited users as a new model in prisma * chore: added admin related topics to pubsub * chore: added service method to fetch all users from user table * chore: added user deletion base implementation * Revert "chore: added user deletion base implementation" This reverts commit d1615ad83db2bae946e2d366a903d2f95051dabb. * feat: adding team related operations to admin * chore: adding admin related service methods to teams module service * chore: adding admin related service methods to team coll invitations requests envs * chore: added more module error messages * chore: added admin check service method * chore: added find individual user by UID in admin * HBE-106 feat: introduced code to handle first time admin login setup (#23) * test: wrote test cases for verifyAdmin route service method * chore: added comments to verifyAdmin service method * chore: deleted the prisma migration file * chore: added find admin users * feat: added user deletion into admin module * chore: admin user related errors * chore: fixed registry pattern in the shortcodes and teams to handle user deletion * chore: add subscription topic for user deletion * chore: updated user type in data handler * feat: implement and fix user deletion * feat: added make user admin mutation * chore: added unit tests for admin specific service methods in admin module * chore: added invitation not found error * chore: added admin specific operation test cases in specific modules * chore: added tests related to user deletion and admin related operation in user module * chore: updated to error constant when invitations not found * chore: fix rebase overwritten methods * feat: implement remove user as admin * chore: add new line * feat: introducing basic metrics into the self-hosted admin module (HBE-104) (#43) * feat: introducing admin module, resolvers and service files as a module * feat: adding admin module in the app module * feat: introducing admin guard and decorator for allowing admin operations * feat: invited user model * chore: added user invitation mail description to mailer service * chore: added admin and user related error * feat: added invited users as a new model in prisma * chore: added admin related topics to pubsub * chore: added service method to fetch all users from user table * chore: added user deletion base implementation * Revert "chore: added user deletion base implementation" This reverts commit d1615ad83db2bae946e2d366a903d2f95051dabb. * feat: adding team related operations to admin * chore: adding admin related service methods to teams module service * chore: adding admin related service methods to team coll invitations requests envs * chore: added more module error messages * chore: added admin check service method * chore: added find individual user by UID in admin * HBE-106 feat: introduced code to handle first time admin login setup (#23) * test: wrote test cases for verifyAdmin route service method * chore: added comments to verifyAdmin service method * chore: deleted the prisma migration file * chore: added find admin users * feat: added user deletion into admin module * chore: admin user related errors * chore: fixed registry pattern in the shortcodes and teams to handle user deletion * chore: add subscription topic for user deletion * chore: updated user type in data handler * feat: implement and fix user deletion * feat: added make user admin mutation * chore: added unit tests for admin specific service methods in admin module * chore: added invitation not found error * chore: added admin specific operation test cases in specific modules * chore: added tests related to user deletion and admin related operation in user module * chore: updated to error constant when invitations not found * chore: fix rebase overwritten methods * feat: implement remove user as admin * chore: add new line * chore: created new GQL return type for admin module * chore: created resolver and service method for method to fetch org metrics * chore: removed all entities relevant to seperate query for fetching admin metrics * chore: created all resolvers for metrics * feat: completed adding field resolves to query org metrics * test: wrote tests for all metrics related methods in admin module * test: added test cases for get count functions in multiple modules * chore: removed prisma migration folder * Delete backend-schema.gql * chore: resolved merge conflicts in team test file --------- Co-authored-by: ankitsridhar16 <ankit.sridhar16@gmail.com> * refactor: update mailer service to stop using postmark (#38) * refactor: update mailer service to stop using postmark * chore: remove postmark as a dep and move out postmark code * chore: remove postmark variables from .env.example * chore: add formal errors for mailer initialization errors * chore: add and update jsdoc comments in mailer service methods * chore: added user invitation mail description to mailer service * chore: updated with review changes requested for admin module * feat: adding admin resolver to gql schema * feat: adding input args for admin resolvers * chore: invited user renamed * chore: updated mailer service to be compatible with new mailer * chore: updated team service with review changes * chore: updated team collection service with review changes * chore: updated team environments service with review changes * chore: updated team requests service with review changes * chore: updated user service with review changes * refactor: invited user model * chore: review changes implemented * chore: implemented the review changes for admin, user and teams module * chore: removed error handling and implemented review changes * refactor: naming change for IsAdmin --------- Co-authored-by: Balu Babu <balub997@gmail.com> Co-authored-by: Andrew Bastin <andrewbastin.k@gmail.com>
This commit is contained in:
@@ -1,11 +1,11 @@
|
||||
import * as T from "fp-ts/Task"
|
||||
import * as TO from "fp-ts/TaskOption"
|
||||
import { User } from "src/user/user.model"
|
||||
import * as T from 'fp-ts/Task';
|
||||
import * as TO from 'fp-ts/TaskOption';
|
||||
import { AuthUser } from '../types/AuthUser';
|
||||
|
||||
/**
|
||||
* Defines how external services should handle User Data and User data related operations and actions.
|
||||
*/
|
||||
export interface UserDataHandler {
|
||||
canAllowUserDeletion: (user: User) => TO.TaskOption<string>
|
||||
onUserDelete: (user: User) => T.Task<void>
|
||||
}
|
||||
canAllowUserDeletion: (user: AuthUser) => TO.TaskOption<string>;
|
||||
onUserDelete: (user: AuthUser) => T.Task<void>;
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ 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 * as TE from 'fp-ts/TaskEither';
|
||||
import { pipe } from 'fp-ts/function';
|
||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||
import { AuthUser } from 'src/types/AuthUser';
|
||||
|
||||
@@ -52,6 +54,18 @@ export class UserResolver {
|
||||
if (E.isLeft(updatedUser)) throwErr(updatedUser.left);
|
||||
return updatedUser.right;
|
||||
}
|
||||
@Mutation(() => Boolean, {
|
||||
description: 'Delete an user account',
|
||||
})
|
||||
@UseGuards(GqlAuthGuard)
|
||||
deleteUser(@GqlUser() user: AuthUser): Promise<boolean> {
|
||||
return pipe(
|
||||
this.userService.deleteUserByUID(user),
|
||||
TE.map(() => true),
|
||||
TE.mapLeft((message) => message.toString()),
|
||||
TE.getOrElse(throwErr),
|
||||
)();
|
||||
}
|
||||
|
||||
/* Subscriptions */
|
||||
|
||||
@@ -63,4 +77,13 @@ export class UserResolver {
|
||||
userUpdated(@GqlUser() user: User) {
|
||||
return this.pubsub.asyncIterator(`user/${user.uid}/updated`);
|
||||
}
|
||||
|
||||
@Subscription(() => User, {
|
||||
description: 'Listen for user deletion',
|
||||
resolve: (value) => value,
|
||||
})
|
||||
@UseGuards(GqlAuthGuard)
|
||||
userDeleted(@GqlUser() user: User): AsyncIterator<User> {
|
||||
return this.pubsub.asyncIterator(`user/${user.uid}/deleted`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,26 @@
|
||||
import { JSON_INVALID } from 'src/errors';
|
||||
import { JSON_INVALID, USER_NOT_FOUND } from 'src/errors';
|
||||
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import { AuthUser } from 'src/types/AuthUser';
|
||||
import { User } from './user.model';
|
||||
import { UserService } from './user.service';
|
||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||
import * as TO from 'fp-ts/TaskOption';
|
||||
import * as T from 'fp-ts/Task';
|
||||
|
||||
const mockPrisma = mockDeep<PrismaService>();
|
||||
const mockPubSub = mockDeep<PubSubService>();
|
||||
let service: UserService;
|
||||
|
||||
const handler1 = {
|
||||
canAllowUserDeletion: jest.fn(),
|
||||
onUserDelete: jest.fn(),
|
||||
};
|
||||
|
||||
const handler2 = {
|
||||
canAllowUserDeletion: jest.fn(),
|
||||
onUserDelete: jest.fn(),
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||
// @ts-ignore
|
||||
@@ -27,6 +40,90 @@ const user: AuthUser = {
|
||||
createdOn: currentTime,
|
||||
};
|
||||
|
||||
const adminUser: AuthUser = {
|
||||
uid: '123344',
|
||||
email: 'dwight@dundermifflin.com',
|
||||
displayName: 'Dwight Schrute',
|
||||
photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute',
|
||||
isAdmin: true,
|
||||
currentRESTSession: {},
|
||||
currentGQLSession: {},
|
||||
refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb',
|
||||
createdOn: currentTime,
|
||||
};
|
||||
|
||||
const users: AuthUser[] = [
|
||||
{
|
||||
uid: '123344',
|
||||
email: 'dwight@dundermifflin.com',
|
||||
displayName: 'Dwight Schrute',
|
||||
photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute',
|
||||
isAdmin: false,
|
||||
currentRESTSession: {},
|
||||
currentGQLSession: {},
|
||||
refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb',
|
||||
createdOn: currentTime,
|
||||
},
|
||||
{
|
||||
uid: '5555',
|
||||
email: 'abc@dundermifflin.com',
|
||||
displayName: 'abc Schrute',
|
||||
photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute',
|
||||
isAdmin: false,
|
||||
currentRESTSession: {},
|
||||
currentGQLSession: {},
|
||||
refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb',
|
||||
createdOn: currentTime,
|
||||
},
|
||||
{
|
||||
uid: '6666',
|
||||
email: 'def@dundermifflin.com',
|
||||
displayName: 'def Schrute',
|
||||
photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute',
|
||||
isAdmin: false,
|
||||
currentRESTSession: {},
|
||||
currentGQLSession: {},
|
||||
refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb',
|
||||
createdOn: currentTime,
|
||||
},
|
||||
];
|
||||
|
||||
const adminUsers: AuthUser[] = [
|
||||
{
|
||||
uid: '123344',
|
||||
email: 'dwight@dundermifflin.com',
|
||||
displayName: 'Dwight Schrute',
|
||||
photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute',
|
||||
isAdmin: true,
|
||||
currentRESTSession: {},
|
||||
currentGQLSession: {},
|
||||
refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb',
|
||||
createdOn: currentTime,
|
||||
},
|
||||
{
|
||||
uid: '5555',
|
||||
email: 'abc@dundermifflin.com',
|
||||
displayName: 'abc Schrute',
|
||||
photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute',
|
||||
isAdmin: true,
|
||||
currentRESTSession: {},
|
||||
currentGQLSession: {},
|
||||
refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb',
|
||||
createdOn: currentTime,
|
||||
},
|
||||
{
|
||||
uid: '6666',
|
||||
email: 'def@dundermifflin.com',
|
||||
displayName: 'def Schrute',
|
||||
photoURL: 'https://en.wikipedia.org/wiki/Dwight_Schrute',
|
||||
isAdmin: true,
|
||||
currentRESTSession: {},
|
||||
currentGQLSession: {},
|
||||
refreshToken: 'hbfvdkhjbvkdvdfjvbnkhjb',
|
||||
createdOn: currentTime,
|
||||
},
|
||||
];
|
||||
|
||||
const exampleSSOProfileData = {
|
||||
id: '123rfedvd',
|
||||
emails: [{ value: 'dwight@dundermifflin.com' }],
|
||||
@@ -38,6 +135,10 @@ const exampleSSOProfileData = {
|
||||
beforeEach(() => {
|
||||
mockReset(mockPrisma);
|
||||
mockPubSub.publish.mockClear();
|
||||
service = new UserService(mockPrisma, mockPubSub as any);
|
||||
|
||||
service.registerUserDataHandler(handler1);
|
||||
service.registerUserDataHandler(handler2);
|
||||
});
|
||||
|
||||
describe('UserService', () => {
|
||||
@@ -312,4 +413,147 @@ describe('UserService', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchAllUsers', () => {
|
||||
test('should resolve right and return 20 users when cursor is null', async () => {
|
||||
mockPrisma.user.findMany.mockResolvedValueOnce(users);
|
||||
|
||||
const result = await userService.fetchAllUsers(null, 20);
|
||||
expect(result).toEqual(users);
|
||||
});
|
||||
test('should resolve right and return next 20 users when cursor is provided', async () => {
|
||||
mockPrisma.user.findMany.mockResolvedValueOnce(users);
|
||||
|
||||
const result = await userService.fetchAllUsers('123344', 20);
|
||||
expect(result).toEqual(users);
|
||||
});
|
||||
test('should resolve left and return an error when users not found', async () => {
|
||||
mockPrisma.user.findMany.mockResolvedValueOnce([]);
|
||||
|
||||
const result = await userService.fetchAllUsers(null, 20);
|
||||
expect(result).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchAdminUsers', () => {
|
||||
test('should return a list of admin users', async () => {
|
||||
mockPrisma.user.findMany.mockResolvedValueOnce(adminUsers);
|
||||
const result = await userService.fetchAdminUsers();
|
||||
expect(mockPrisma.user.findMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
expect(result).toEqual(adminUsers);
|
||||
});
|
||||
test('should return null when no admin users found', async () => {
|
||||
mockPrisma.user.findMany.mockResolvedValueOnce(null);
|
||||
const result = await userService.fetchAdminUsers();
|
||||
expect(mockPrisma.user.findMany).toHaveBeenCalledWith({
|
||||
where: {
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
expect(result).toEqual(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('makeAdmin', () => {
|
||||
test('should resolve right and return a user object after making a user admin', async () => {
|
||||
mockPrisma.user.update.mockResolvedValueOnce(adminUser);
|
||||
const result = await userService.makeAdmin(user.uid);
|
||||
expect(mockPrisma.user.update).toHaveBeenCalledWith({
|
||||
where: {
|
||||
uid: user.uid,
|
||||
},
|
||||
data: {
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
expect(result).toEqualRight(adminUser);
|
||||
});
|
||||
test('should resolve left and error when invalid user uid is passed', async () => {
|
||||
mockPrisma.user.update.mockRejectedValueOnce('NotFoundError');
|
||||
const result = await userService.makeAdmin(user.uid);
|
||||
expect(mockPrisma.user.update).toHaveBeenCalledWith({
|
||||
where: {
|
||||
uid: user.uid,
|
||||
},
|
||||
data: {
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
expect(result).toEqualLeft(USER_NOT_FOUND);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteUserByID', () => {
|
||||
test('should resolve right for valid user uid and perform successful user deletion', () => {
|
||||
// For a successful deletion, the handlers should allow user deletion
|
||||
handler1.canAllowUserDeletion.mockImplementation(() => TO.none);
|
||||
handler2.canAllowUserDeletion.mockImplementation(() => TO.none);
|
||||
handler1.onUserDelete.mockImplementation(() => T.of(undefined));
|
||||
handler2.onUserDelete.mockImplementation(() => T.of(undefined));
|
||||
mockPrisma.user.delete.mockResolvedValueOnce(user);
|
||||
|
||||
const result = service.deleteUserByUID(user)();
|
||||
return expect(result).resolves.toBeRight();
|
||||
});
|
||||
test('should resolve right for successful deletion and publish user deleted subscription', async () => {
|
||||
// For a successful deletion, the handlers should allow user deletion
|
||||
handler1.canAllowUserDeletion.mockImplementation(() => TO.none);
|
||||
handler2.canAllowUserDeletion.mockImplementation(() => TO.none);
|
||||
handler1.onUserDelete.mockImplementation(() => T.of(undefined));
|
||||
handler2.onUserDelete.mockImplementation(() => T.of(undefined));
|
||||
|
||||
mockPrisma.user.delete.mockResolvedValueOnce(user);
|
||||
const result = service.deleteUserByUID(user)();
|
||||
await expect(result).resolves.toBeRight();
|
||||
|
||||
// fire the subscription for user deletion
|
||||
expect(mockPubSub.publish).toHaveBeenCalledWith(
|
||||
`user/${user.uid}/deleted`,
|
||||
<User>{
|
||||
uid: user.uid,
|
||||
displayName: user.displayName,
|
||||
email: user.email,
|
||||
photoURL: user.photoURL,
|
||||
isAdmin: user.isAdmin,
|
||||
currentRESTSession: user.currentRESTSession,
|
||||
currentGQLSession: user.currentGQLSession,
|
||||
createdOn: user.createdOn,
|
||||
},
|
||||
);
|
||||
});
|
||||
test("should resolve left when one or both the handlers don't allow userDeletion", () => {
|
||||
// Handlers don't allow user deletion
|
||||
handler1.canAllowUserDeletion.mockImplementation(() => TO.some);
|
||||
handler2.canAllowUserDeletion.mockImplementation(() => TO.some);
|
||||
|
||||
const result = service.deleteUserByUID(user)();
|
||||
return expect(result).resolves.toBeLeft();
|
||||
});
|
||||
test('should resolve left when ther is an unsuccessful deletion of userdata from firestore', () => {
|
||||
// Handlers allow deletion to proceed
|
||||
handler1.canAllowUserDeletion.mockImplementation(() => TO.none);
|
||||
handler2.canAllowUserDeletion.mockImplementation(() => TO.none);
|
||||
handler1.onUserDelete.mockImplementation(() => T.of(undefined));
|
||||
handler2.onUserDelete.mockImplementation(() => T.of(undefined));
|
||||
|
||||
// Deleting users errors out
|
||||
mockPrisma.user.delete.mockRejectedValueOnce('NotFoundError');
|
||||
|
||||
const result = service.deleteUserByUID(user)();
|
||||
return expect(result).resolves.toBeLeft();
|
||||
});
|
||||
});
|
||||
|
||||
describe('getUsersCount', () => {
|
||||
test('should return count of all users in the organization', async () => {
|
||||
mockPrisma.user.count.mockResolvedValueOnce(10);
|
||||
|
||||
const result = await userService.getUsersCount();
|
||||
expect(result).toEqual(10);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,12 +2,17 @@ import { Injectable } from '@nestjs/common';
|
||||
import { PrismaService } from 'src/prisma/prisma.service';
|
||||
import * as O from 'fp-ts/Option';
|
||||
import * as E from 'fp-ts/Either';
|
||||
import * as TO from 'fp-ts/TaskOption';
|
||||
import * as TE from 'fp-ts/TaskEither';
|
||||
import * as T from 'fp-ts/Task';
|
||||
import * as A from 'fp-ts/Array';
|
||||
import { pipe, constVoid } from 'fp-ts/function';
|
||||
import { AuthUser } from 'src/types/AuthUser';
|
||||
import { USER_NOT_FOUND } from 'src/errors';
|
||||
import { USER_NOT_FOUND, USERS_NOT_FOUND } from 'src/errors';
|
||||
import { SessionType, User } from './user.model';
|
||||
import { USER_UPDATE_FAILED } from 'src/errors';
|
||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||
import { stringToJson } from 'src/utils';
|
||||
import { stringToJson, taskEitherValidateArraySeq } from 'src/utils';
|
||||
import { UserDataHandler } from './user.data.handler';
|
||||
|
||||
@Injectable()
|
||||
@@ -261,4 +266,168 @@ export class UserService {
|
||||
|
||||
return E.right(jsonSession.right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all the users in the `User` table based on cursor
|
||||
* @param cursorID string of userUID or null
|
||||
* @param take number of users to query
|
||||
* @returns an array of `User` object
|
||||
*/
|
||||
async fetchAllUsers(cursorID: string, take: number) {
|
||||
const fetchedUsers = await this.prisma.user.findMany({
|
||||
skip: cursorID ? 1 : 0,
|
||||
take: take,
|
||||
cursor: cursorID ? { uid: cursorID } : undefined,
|
||||
});
|
||||
return fetchedUsers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch the number of users in db
|
||||
* @returns a count (Int) of user records in DB
|
||||
*/
|
||||
async getUsersCount() {
|
||||
const usersCount = await this.prisma.user.count();
|
||||
return usersCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Change a user to an admin by toggling isAdmin param to true
|
||||
* @param userUID user UID
|
||||
* @returns a Either of `User` object or error
|
||||
*/
|
||||
async makeAdmin(userUID: string) {
|
||||
try {
|
||||
const elevatedUser = await this.prisma.user.update({
|
||||
where: {
|
||||
uid: userUID,
|
||||
},
|
||||
data: {
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
return E.right(elevatedUser);
|
||||
} catch (error) {
|
||||
return E.left(USER_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all the admin users
|
||||
* @returns an array of admin users
|
||||
*/
|
||||
async fetchAdminUsers() {
|
||||
const admins = this.prisma.user.findMany({
|
||||
where: {
|
||||
isAdmin: true,
|
||||
},
|
||||
});
|
||||
|
||||
return admins;
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a user account by UID
|
||||
* @param uid User UID
|
||||
* @returns an Either of string or boolean
|
||||
*/
|
||||
async deleteUserAccount(uid: string) {
|
||||
try {
|
||||
await this.prisma.user.delete({
|
||||
where: {
|
||||
uid: uid,
|
||||
},
|
||||
});
|
||||
return E.right(true);
|
||||
} catch (e) {
|
||||
return E.left(USER_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user deletion error messages when the data handlers are initialised in respective modules
|
||||
* @param user User Object
|
||||
* @returns an TaskOption of string array
|
||||
*/
|
||||
getUserDeletionErrors(user: AuthUser): TO.TaskOption<string[]> {
|
||||
return pipe(
|
||||
this.userDataHandlers,
|
||||
A.map((handler) =>
|
||||
pipe(
|
||||
handler.canAllowUserDeletion(user),
|
||||
TO.matchE(
|
||||
() => TE.right(undefined),
|
||||
(error) => TE.left(error),
|
||||
),
|
||||
),
|
||||
),
|
||||
taskEitherValidateArraySeq,
|
||||
TE.matchE(
|
||||
(e) => TO.some(e),
|
||||
() => TO.none,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes a user by UID
|
||||
* @param user User Object
|
||||
* @returns an TaskEither of string or boolean
|
||||
*/
|
||||
deleteUserByUID(user: AuthUser) {
|
||||
return pipe(
|
||||
this.getUserDeletionErrors(user),
|
||||
TO.matchEW(
|
||||
() =>
|
||||
pipe(
|
||||
this.userDataHandlers,
|
||||
A.map((handler) => handler.onUserDelete(user)),
|
||||
T.sequenceArray,
|
||||
T.map(constVoid),
|
||||
TE.fromTask,
|
||||
) as TE.TaskEither<never, void>,
|
||||
(errors): TE.TaskEither<string[], void> => TE.left(errors),
|
||||
),
|
||||
|
||||
TE.chainW(() => () => this.deleteUserAccount(user.uid)),
|
||||
|
||||
TE.chainFirst(() =>
|
||||
TE.fromTask(() =>
|
||||
this.pubsub.publish(`user/${user.uid}/deleted`, <User>{
|
||||
uid: user.uid,
|
||||
displayName: user.displayName,
|
||||
email: user.email,
|
||||
photoURL: user.photoURL,
|
||||
isAdmin: user.isAdmin,
|
||||
createdOn: user.createdOn,
|
||||
currentGQLSession: user.currentGQLSession,
|
||||
currentRESTSession: user.currentRESTSession,
|
||||
}),
|
||||
),
|
||||
),
|
||||
|
||||
TE.mapLeft((errors) => errors.toString()),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Change the user from an admin by toggling isAdmin param to false
|
||||
* @param userUID user UID
|
||||
* @returns a Either of `User` object or error
|
||||
*/
|
||||
async removeUserAsAdmin(userUID: string) {
|
||||
try {
|
||||
const user = await this.prisma.user.update({
|
||||
where: {
|
||||
uid: userUID,
|
||||
},
|
||||
data: {
|
||||
isAdmin: false,
|
||||
},
|
||||
});
|
||||
return E.right(user);
|
||||
} catch (error) {
|
||||
return E.left(USER_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user