feat: I can delete my account easily (#977)
* Add support for account deletion Co-authored-by: v1b3m <vibenjamin6@gmail.com> * Add more fixes Co-authored-by: Benjamin Mayanja <vibenjamin6@gmail.com> * Add more fixes Co-authored-by: v1b3m <vibenjamin6@gmail.com> --------- Co-authored-by: v1b3m <vibenjamin6@gmail.com>
This commit is contained in:
@ -59,7 +59,7 @@ export class AbilityFactory {
|
||||
},
|
||||
});
|
||||
can(AbilityAction.Update, 'User', { id: user.id });
|
||||
cannot(AbilityAction.Delete, 'User');
|
||||
can(AbilityAction.Delete, 'User', { id: user.id });
|
||||
|
||||
// Workspace
|
||||
can(AbilityAction.Read, 'Workspace');
|
||||
|
||||
@ -65,6 +65,7 @@ export class UpdateUserAbilityHandler implements IAbilityHandler {
|
||||
async handle(ability: AppAbility, context: ExecutionContext) {
|
||||
const gqlContext = GqlExecutionContext.create(context);
|
||||
const args = gqlContext.getArgs<UserArgs>();
|
||||
// TODO: Confirm if this is correct
|
||||
const user = await this.prismaService.client.user.findFirst({
|
||||
where: args.where,
|
||||
});
|
||||
@ -92,8 +93,14 @@ export class DeleteUserAbilityHandler implements IAbilityHandler {
|
||||
async handle(ability: AppAbility, context: ExecutionContext) {
|
||||
const gqlContext = GqlExecutionContext.create(context);
|
||||
const args = gqlContext.getArgs<UserArgs>();
|
||||
|
||||
// obtain the auth user from the context
|
||||
const reqUser = gqlContext.getContext().req.user;
|
||||
|
||||
// FIXME: When `args.where` is undefined(which it is in almost all the cases I've tested),
|
||||
// this query will return the first user entry in the DB, which is most likely not the current user
|
||||
const user = await this.prismaService.client.user.findFirst({
|
||||
where: args.where,
|
||||
where: { ...args.where, id: reqUser.user.id },
|
||||
});
|
||||
assert(user, '', NotFoundException);
|
||||
|
||||
|
||||
@ -9,7 +9,7 @@ import {
|
||||
import { UseFilters, UseGuards } from '@nestjs/common';
|
||||
|
||||
import { accessibleBy } from '@casl/prisma';
|
||||
import { Prisma } from '@prisma/client';
|
||||
import { Prisma, Workspace } from '@prisma/client';
|
||||
import { FileUpload, GraphQLUpload } from 'graphql-upload';
|
||||
|
||||
import { FileFolder } from 'src/core/file/interfaces/file-folder.interface';
|
||||
@ -25,6 +25,7 @@ import {
|
||||
import { AbilityGuard } from 'src/guards/ability.guard';
|
||||
import { CheckAbilities } from 'src/decorators/check-abilities.decorator';
|
||||
import {
|
||||
DeleteUserAbilityHandler,
|
||||
ReadUserAbilityHandler,
|
||||
UpdateUserAbilityHandler,
|
||||
} from 'src/ability/handlers/user.ability-handler';
|
||||
@ -35,6 +36,7 @@ import { assert } from 'src/utils/assert';
|
||||
import { UpdateOneUserArgs } from 'src/core/@generated/user/update-one-user.args';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
import { FileUploadService } from 'src/core/file/services/file-upload.service';
|
||||
import { AuthWorkspace } from 'src/decorators/auth-workspace.decorator';
|
||||
|
||||
import { UserService } from './user.service';
|
||||
|
||||
@ -147,4 +149,14 @@ export class UserResolver {
|
||||
|
||||
return paths[0];
|
||||
}
|
||||
|
||||
@Mutation(() => User)
|
||||
@UseGuards(AbilityGuard)
|
||||
@CheckAbilities(DeleteUserAbilityHandler)
|
||||
async deleteUserAccount(
|
||||
@AuthUser() { id: userId }: User,
|
||||
@AuthWorkspace() { id: workspaceId }: Workspace,
|
||||
) {
|
||||
return this.userService.deleteUser({ userId, workspaceId });
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,4 +91,89 @@ export class UserService {
|
||||
|
||||
return user as Prisma.UserGetPayload<T>;
|
||||
}
|
||||
|
||||
async deleteUser({
|
||||
workspaceId,
|
||||
userId,
|
||||
}: {
|
||||
workspaceId: string;
|
||||
userId: string;
|
||||
}) {
|
||||
const {
|
||||
workspaceMember,
|
||||
company,
|
||||
comment,
|
||||
attachment,
|
||||
refreshToken,
|
||||
activity,
|
||||
activityTarget,
|
||||
} = this.prismaService.client;
|
||||
const user = await this.findUnique({
|
||||
where: {
|
||||
id: userId,
|
||||
},
|
||||
select: {
|
||||
id: true,
|
||||
},
|
||||
});
|
||||
assert(user, 'User not found');
|
||||
|
||||
const workspace = await this.workspaceService.findUnique({
|
||||
where: { id: workspaceId },
|
||||
select: { id: true },
|
||||
});
|
||||
assert(workspace, 'Workspace not found');
|
||||
|
||||
const workSpaceMembers = await workspaceMember.findMany({
|
||||
where: {
|
||||
workspaceId,
|
||||
},
|
||||
});
|
||||
|
||||
const isLastMember =
|
||||
workSpaceMembers.length === 1 && workSpaceMembers[0].userId === userId;
|
||||
|
||||
if (isLastMember) {
|
||||
// Delete entire workspace
|
||||
await this.workspaceService.deleteWorkspace({
|
||||
workspaceId,
|
||||
userId,
|
||||
select: { id: true },
|
||||
});
|
||||
} else {
|
||||
const where = { authorId: userId };
|
||||
const activities = await activity.findMany({
|
||||
where,
|
||||
});
|
||||
|
||||
await this.prismaService.client.$transaction([
|
||||
workspaceMember.deleteMany({
|
||||
where: { userId },
|
||||
}),
|
||||
company.deleteMany({
|
||||
where: { accountOwnerId: userId },
|
||||
}),
|
||||
comment.deleteMany({
|
||||
where,
|
||||
}),
|
||||
attachment.deleteMany({
|
||||
where,
|
||||
}),
|
||||
refreshToken.deleteMany({
|
||||
where: { userId },
|
||||
}),
|
||||
...activities.map(({ id: activityId }) =>
|
||||
activityTarget.deleteMany({
|
||||
where: { activityId },
|
||||
}),
|
||||
),
|
||||
activity.deleteMany({
|
||||
where,
|
||||
}),
|
||||
this.delete({ where: { id: userId } }),
|
||||
]);
|
||||
}
|
||||
|
||||
return user;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user