[permissions V2] Custom role deletion (#11187)

Closes https://github.com/twentyhq/core-team-issues/issues/616
This commit is contained in:
Marie
2025-03-26 15:08:48 +01:00
committed by GitHub
parent 16cb768c5c
commit 7af90eb4c4
9 changed files with 365 additions and 100 deletions

View File

@ -3,6 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { isDefined } from 'twenty-shared/utils';
import { Repository } from 'typeorm';
import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity';
import { ADMIN_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/admin-role-label.constants';
import { MEMBER_ROLE_LABEL } from 'src/engine/metadata-modules/permissions/constants/member-role-label.constants';
import {
@ -16,12 +17,19 @@ import {
UpdateRolePayload,
} from 'src/engine/metadata-modules/role/dtos/update-role-input.dto';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { UserWorkspaceRoleEntity } from 'src/engine/metadata-modules/role/user-workspace-role.entity';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
import { isArgDefinedIfProvidedOrThrow } from 'src/engine/metadata-modules/utils/is-arg-defined-if-provided-or-throw.util';
export class RoleService {
constructor(
@InjectRepository(Workspace, 'core')
private readonly workspaceRepository: Repository<Workspace>,
@InjectRepository(RoleEntity, 'metadata')
private readonly roleRepository: Repository<RoleEntity>,
@InjectRepository(UserWorkspaceRoleEntity, 'metadata')
private readonly userWorkspaceRoleRepository: Repository<UserWorkspaceRoleEntity>,
private readonly userRoleService: UserRoleService,
) {}
public async getWorkspaceRoles(workspaceId: string): Promise<RoleEntity[]> {
@ -76,6 +84,11 @@ export class RoleService {
input: UpdateRoleInput;
workspaceId: string;
}): Promise<RoleEntity> {
await this.validateRoleIsEditableOrThrow({
roleId: input.id,
workspaceId,
});
const existingRole = await this.roleRepository.findOne({
where: {
id: input.id,
@ -123,6 +136,49 @@ export class RoleService {
});
}
public async deleteRole(
roleId: string,
workspaceId: string,
): Promise<string> {
await this.validateRoleIsEditableOrThrow({
roleId,
workspaceId,
});
const defaultRole = await this.workspaceRepository.findOne({
where: {
id: workspaceId,
},
});
const defaultRoleId = defaultRole?.defaultRoleId;
if (!isDefined(defaultRoleId)) {
throw new PermissionsException(
PermissionsExceptionMessage.DEFAULT_ROLE_NOT_FOUND,
PermissionsExceptionCode.DEFAULT_ROLE_NOT_FOUND,
);
}
await this.validateRoleIsNotDefaultRoleOrThrow({
roleId,
defaultRoleId,
});
await this.assignDefaultRoleToMembersWithRoleToDelete({
roleId,
workspaceId,
defaultRoleId,
});
await this.roleRepository.delete({
id: roleId,
workspaceId,
});
return roleId;
}
public async createMemberRole({
workspaceId,
}: {
@ -210,4 +266,91 @@ export class RoleService {
}
}
}
private async validateRoleIsNotDefaultRoleOrThrow({
roleId,
defaultRoleId,
}: {
roleId: string;
defaultRoleId: string;
}): Promise<void> {
if (defaultRoleId === roleId) {
throw new PermissionsException(
PermissionsExceptionMessage.DEFAULT_ROLE_CANNOT_BE_DELETED,
PermissionsExceptionCode.DEFAULT_ROLE_CANNOT_BE_DELETED,
);
}
}
private async assignDefaultRoleToMembersWithRoleToDelete({
roleId,
workspaceId,
defaultRoleId,
}: {
roleId: string;
workspaceId: string;
defaultRoleId: string;
}): Promise<void> {
const userWorkspaceIds = await this.getUserWorkspaceIdsForRole(
roleId,
workspaceId,
);
await Promise.all(
userWorkspaceIds.map((userWorkspaceId) =>
this.userRoleService.assignRoleToUserWorkspace({
userWorkspaceId,
roleId: defaultRoleId,
workspaceId,
}),
),
);
}
private async getUserWorkspaceIdsForRole(
roleId: string,
workspaceId: string,
): Promise<string[]> {
return this.userWorkspaceRoleRepository
.find({
where: {
roleId: roleId,
workspaceId,
},
})
.then((userWorkspaceRoles) =>
userWorkspaceRoles.map(
(userWorkspaceRole) => userWorkspaceRole.userWorkspaceId,
),
);
}
private async getRole(
roleId: string,
workspaceId: string,
): Promise<RoleEntity | null> {
return this.roleRepository.findOne({
where: {
id: roleId,
workspaceId,
},
});
}
private async validateRoleIsEditableOrThrow({
roleId,
workspaceId,
}: {
roleId: string;
workspaceId: string;
}) {
const role = await this.getRole(roleId, workspaceId);
if (!role?.isEditable) {
throw new PermissionsException(
PermissionsExceptionMessage.ROLE_NOT_EDITABLE,
PermissionsExceptionCode.ROLE_NOT_EDITABLE,
);
}
}
}