Fix user deletion flows (#15614)
**Before** - any user with workpace_members permission was able to remove a user from their workspace. This triggered the deletion of workspaceMember + of userWorkspace, but did not delete the user (even if they had no workspace left) nor the roleTarget (acts as junction between role and userWorkspace) which was left with a userWorkspaceId pointing to nothing. This is because roleTarget points to userWorkspaceId but the foreign key constraint was not implemented - any user could delete their own account. This triggered the deletion of all their workspaceMembers, but not of their userWorkspace nor their user nor the roleTarget --> we have orphaned userWorkspace, not technically but product wise - a userWorkspace without a workspaceMember does not make sense So the problems are - we have some roleTargets pointing to non-existing userWorkspaceId (which caused https://github.com/twentyhq/twenty/issues/14608 ) - we have userWorkspaces that should not exist and that have no workspaceMember counterpart - it is not possible for a user to leave a workspace by themselves, they can only leave all workspaces at once, except if they are being removed from the workspace by another user **Now** - if a user has multiple workspaces, they are given the possibility to leave one workspace while remaining in the others (we show two buttons: Leave workspace and Delete account buttons). if a user has just one workspace, they only see Delete account - when a user leaves a workspace, we delete their workspaceMember, userWorkspace and roleTarget. If they don't belong to any other workspace we also soft-delete their user - soft-deleted users get hard deleted after 30 days thanks to a cron - we have two commands to clean the orphans roleTarget and userWorkspace (TODO: query db to see how many must be run) **Next** - once the commands have been run, we can implement and introduce the foreign key constraint on roleTarget Fixes https://github.com/twentyhq/twenty/issues/14608
This commit is contained in:
+7
-2
@@ -1,8 +1,12 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { FieldMetadataType, ObjectsPermissions } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
CommonQueryRunnerException,
|
||||
CommonQueryRunnerExceptionCode,
|
||||
} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception';
|
||||
import { CommonSelectedFieldsResult } from 'src/engine/api/common/types/common-selected-fields-result.type';
|
||||
import { getAllSelectableFields } from 'src/engine/api/rest/core/rest-to-common-args-handlers/utils/get-all-selectable-fields.util';
|
||||
import { MAX_DEPTH } from 'src/engine/api/rest/input-request-parsers/constants/max-depth.constant';
|
||||
@@ -86,8 +90,9 @@ export class CommonSelectedFieldsHandler {
|
||||
objectMetadataMaps.byId[field.relationTargetObjectMetadataId];
|
||||
|
||||
if (!isDefined(relationTargetObjectMetadata)) {
|
||||
throw new BadRequestException(
|
||||
throw new CommonQueryRunnerException(
|
||||
`Object metadata relation target not found for relation creation payload`,
|
||||
CommonQueryRunnerExceptionCode.BAD_REQUEST,
|
||||
);
|
||||
}
|
||||
const relationFieldSelectFields = getAllSelectableFields({
|
||||
|
||||
+1
@@ -13,4 +13,5 @@ export enum CommonQueryRunnerExceptionCode {
|
||||
MISSING_SYSTEM_FIELD = 'MISSING_SYSTEM_FIELD',
|
||||
INVALID_CURSOR = 'INVALID_CURSOR',
|
||||
UPSERT_MAX_RECORDS_EXCEEDED = 'UPSERT_MAX_RECORDS_EXCEEDED',
|
||||
BAD_REQUEST = 'BAD_REQUEST',
|
||||
}
|
||||
|
||||
+1
@@ -23,6 +23,7 @@ export const commonQueryRunnerToGraphqlApiExceptionHandler = (
|
||||
case CommonQueryRunnerExceptionCode.UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT:
|
||||
case CommonQueryRunnerExceptionCode.INVALID_CURSOR:
|
||||
case CommonQueryRunnerExceptionCode.UPSERT_MAX_RECORDS_EXCEEDED:
|
||||
case CommonQueryRunnerExceptionCode.BAD_REQUEST:
|
||||
throw new UserInputError(error);
|
||||
case CommonQueryRunnerExceptionCode.INVALID_AUTH_CONTEXT:
|
||||
throw new AuthenticationError(error);
|
||||
|
||||
+1
@@ -22,6 +22,7 @@ export const commonQueryRunnerToRestApiExceptionHandler = (
|
||||
case CommonQueryRunnerExceptionCode.UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT:
|
||||
case CommonQueryRunnerExceptionCode.INVALID_CURSOR:
|
||||
case CommonQueryRunnerExceptionCode.UPSERT_MAX_RECORDS_EXCEEDED:
|
||||
case CommonQueryRunnerExceptionCode.BAD_REQUEST:
|
||||
throw new BadRequestException(error.message);
|
||||
case CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND:
|
||||
throw new NotFoundException('Record not found');
|
||||
|
||||
Reference in New Issue
Block a user