Migrate role and role target to v2 (#16009)

# Introduction
close https://github.com/twentyhq/core-team-issues/issues/1930
close https://github.com/twentyhq/core-team-issues/issues/1929
Migrating role and roleTarget entities to the v2 core engine, allowing
v2 caching leverage and allow migrating agent to v2 that needs role
target in prior
After agent we should be able to pass twenty standard app totally though
workspace migration

## Role target assignation
Please note that role target have 3 creation entrypoints:
- Agent
- User workspace
- ApiKey

Refactored all 3 of them to pass through a new role-target.service.ts
that consumes the v2 under the hood.

---------

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
Paul Rastoin
2025-11-28 18:06:11 +01:00
committed by GitHub
parent 23a7611aac
commit ea3c5d2d45
131 changed files with 6082 additions and 1319 deletions
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import { RoleTargetModule } from 'src/engine/metadata-modules/role-target/role-target.module';
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { UserRoleService } from 'src/engine/metadata-modules/user-role/user-role.service';
@@ -12,6 +13,7 @@ import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/wor
TypeOrmModule.forFeature([RoleEntity, RoleTargetsEntity]),
TypeOrmModule.forFeature([UserWorkspaceEntity]),
WorkspacePermissionsCacheModule,
RoleTargetModule,
],
providers: [UserRoleService],
exports: [UserRoleService],
@@ -2,7 +2,7 @@ import { InjectRepository } from '@nestjs/typeorm';
import { msg } from '@lingui/core/macro';
import { isDefined } from 'twenty-shared/utils';
import { type QueryRunner, In, Not, Repository } from 'typeorm';
import { In, Repository } from 'typeorm';
import { UserWorkspaceEntity } from 'src/engine/core-modules/user-workspace/user-workspace.entity';
import {
@@ -10,6 +10,7 @@ import {
PermissionsExceptionCode,
PermissionsExceptionMessage,
} from 'src/engine/metadata-modules/permissions/permissions.exception';
import { RoleTargetService } from 'src/engine/metadata-modules/role-target/services/role-target.service';
import { RoleTargetsEntity } from 'src/engine/metadata-modules/role/role-targets.entity';
import { RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
import { WorkspacePermissionsCacheService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache.service';
@@ -19,28 +20,24 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
export class UserRoleService {
constructor(
@InjectRepository(RoleEntity)
private readonly roleRepository: Repository<RoleEntity>,
@InjectRepository(RoleTargetsEntity)
private readonly roleTargetsRepository: Repository<RoleTargetsEntity>,
@InjectRepository(UserWorkspaceEntity)
private readonly userWorkspaceRepository: Repository<UserWorkspaceEntity>,
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
private readonly workspacePermissionsCacheService: WorkspacePermissionsCacheService,
private readonly roleTargetService: RoleTargetService,
) {}
public async assignRoleToUserWorkspace(
{
workspaceId,
userWorkspaceId,
roleId,
}: {
workspaceId: string;
userWorkspaceId: string;
roleId: string;
},
queryRunner?: QueryRunner,
): Promise<void> {
public async assignRoleToUserWorkspace({
workspaceId,
userWorkspaceId,
roleId,
}: {
workspaceId: string;
userWorkspaceId: string;
roleId: string;
}): Promise<void> {
const validationResult = await this.validateAssignRoleInput({
userWorkspaceId,
workspaceId,
@@ -51,27 +48,14 @@ export class UserRoleService {
return;
}
const roleTargetsRepo = queryRunner
? queryRunner.manager.getRepository(RoleTargetsEntity)
: this.roleTargetsRepository;
const newRoleTarget = await roleTargetsRepo.save({
roleId,
userWorkspaceId,
workspaceId,
});
await roleTargetsRepo.delete({
userWorkspaceId,
workspaceId,
id: Not(newRoleTarget.id),
});
await this.workspacePermissionsCacheService.recomputeUserWorkspaceRoleMapCache(
{
workspaceId,
await this.roleTargetService.create({
createRoleTargetInput: {
roleId,
targetId: userWorkspaceId,
targetMetadataForeignKey: 'userWorkspaceId',
},
);
workspaceId,
});
}
public async getRoleIdForUserWorkspace({
@@ -248,32 +232,6 @@ export class UserRoleService {
);
}
const role = await this.roleRepository.findOne({
where: {
id: roleId,
},
});
if (!isDefined(role)) {
throw new PermissionsException(
'Role not found',
PermissionsExceptionCode.ROLE_NOT_FOUND,
{
userFriendlyMessage: msg`The role you are trying to assign could not be found. It may have been deleted.`,
},
);
}
if (!role.canBeAssignedToUsers) {
throw new PermissionsException(
`Role "${role.label}" cannot be assigned to users`,
PermissionsExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_USERS,
{
userFriendlyMessage: msg`This role cannot be assigned to users. Please select a different role.`,
},
);
}
const roles = await this.getRolesByUserWorkspaces({
userWorkspaceIds: [userWorkspace.id],
workspaceId,