Fix seeding perf + batch role targets creation (#16337)

This commit is contained in:
Weiko
2025-12-04 20:29:48 +01:00
committed by GitHub
parent e653385485
commit 1991ee850e
11 changed files with 156 additions and 110 deletions
@@ -37,6 +37,25 @@ export class RoleTargetService {
createRoleTargetInput: CreateRoleTargetInput;
workspaceId: string;
}): Promise<FlatRoleTarget> {
const [flatRoleTarget] = await this.createMany({
createRoleTargetInputs: [createRoleTargetInput],
workspaceId,
});
return flatRoleTarget;
}
async createMany({
createRoleTargetInputs,
workspaceId,
}: {
createRoleTargetInputs: CreateRoleTargetInput[];
workspaceId: string;
}): Promise<FlatRoleTarget[]> {
if (createRoleTargetInputs.length === 0) {
return [];
}
const { flatRoleTargetMaps, flatRoleMaps } =
await this.flatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps(
{
@@ -44,6 +63,7 @@ export class RoleTargetService {
flatMapsKeys: ['flatRoleTargetMaps', 'flatRoleMaps'],
},
);
const { workspaceCustomFlatApplication } =
await this.applicationService.findWorkspaceTwentyStandardAndCustomApplicationOrThrow(
{
@@ -51,17 +71,25 @@ export class RoleTargetService {
},
);
const { flatRoleTargetToCreate, flatRoleTargetsToDelete } =
fromCreateRoleTargetInputToFlatRoleTargetToCreate({
createRoleTargetInput: {
...createRoleTargetInput,
applicationId:
createRoleTargetInput.applicationId ??
workspaceCustomFlatApplication.id,
},
flatRoleTargetMaps,
workspaceId,
});
const allFlatRoleTargetsToCreate: FlatRoleTarget[] = [];
const allFlatRoleTargetsToDelete: FlatRoleTarget[] = [];
for (const createRoleTargetInput of createRoleTargetInputs) {
const { flatRoleTargetToCreate, flatRoleTargetsToDelete } =
fromCreateRoleTargetInputToFlatRoleTargetToCreate({
createRoleTargetInput: {
...createRoleTargetInput,
applicationId:
createRoleTargetInput.applicationId ??
workspaceCustomFlatApplication.id,
},
flatRoleTargetMaps,
workspaceId,
});
allFlatRoleTargetsToCreate.push(flatRoleTargetToCreate);
allFlatRoleTargetsToDelete.push(...flatRoleTargetsToDelete);
}
const validateAndBuildResult =
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
@@ -69,8 +97,8 @@ export class RoleTargetService {
fromToAllFlatEntityMaps: {
flatRoleTargetMaps: computeFlatEntityMapsFromTo({
flatEntityMaps: flatRoleTargetMaps,
flatEntityToCreate: [flatRoleTargetToCreate],
flatEntityToDelete: flatRoleTargetsToDelete,
flatEntityToCreate: allFlatRoleTargetsToCreate,
flatEntityToDelete: allFlatRoleTargetsToDelete,
flatEntityToUpdate: [],
}),
},
@@ -90,7 +118,7 @@ export class RoleTargetService {
if (isDefined(validateAndBuildResult)) {
throw new WorkspaceMigrationBuilderExceptionV2(
validateAndBuildResult,
'Multiple validation errors occurred while creating role target',
'Multiple validation errors occurred while creating role targets',
);
}
@@ -102,10 +130,12 @@ export class RoleTargetService {
},
);
return findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatRoleTargetToCreate.id,
flatEntityMaps: recomputedFlatRoleTargetMaps,
});
return allFlatRoleTargetsToCreate.map((flatRoleTargetToCreate) =>
findFlatEntityByIdInFlatEntityMapsOrThrow({
flatEntityId: flatRoleTargetToCreate.id,
flatEntityMaps: recomputedFlatRoleTargetMaps,
}),
);
}
async delete({ id, workspaceId }: DeleteRoleTargetInput): Promise<void> {
@@ -126,8 +126,8 @@ export class RoleResolver {
workspaceId: workspace.id,
});
await this.userRoleService.assignRoleToUserWorkspace({
userWorkspaceId: userWorkspace.id,
await this.userRoleService.assignRoleToManyUserWorkspace({
userWorkspaceIds: [userWorkspace.id],
workspaceId: workspace.id,
roleId,
});
@@ -354,14 +354,10 @@ export class RoleService {
workspaceId,
);
await Promise.all(
userWorkspaceIds.map((userWorkspaceId) =>
this.userRoleService.assignRoleToUserWorkspace({
userWorkspaceId,
roleId: defaultRoleId,
workspaceId,
}),
),
);
await this.userRoleService.assignRoleToManyUserWorkspace({
userWorkspaceIds,
roleId: defaultRoleId,
workspaceId,
});
}
}
@@ -29,31 +29,38 @@ export class UserRoleService {
private readonly workspaceCacheService: WorkspaceCacheService,
) {}
public async assignRoleToUserWorkspace({
public async assignRoleToManyUserWorkspace({
workspaceId,
userWorkspaceId,
userWorkspaceIds,
roleId,
}: {
workspaceId: string;
userWorkspaceId: string;
userWorkspaceIds: string[];
roleId: string;
}): Promise<void> {
const validationResult = await this.validateAssignRoleInput({
userWorkspaceId,
workspaceId,
roleId,
});
if (validationResult?.roleToAssignIsSameAsCurrentRole) {
if (userWorkspaceIds.length === 0) {
return;
}
await this.roleTargetService.create({
createRoleTargetInput: {
const userWorkspaceIdsToAssign =
await this.validateAssignRoleInputsAndGetUserWorkspaceIdsToAssign({
userWorkspaceIds,
workspaceId,
roleId,
targetId: userWorkspaceId,
targetMetadataForeignKey: 'userWorkspaceId',
},
});
if (userWorkspaceIdsToAssign.length === 0) {
return;
}
await this.roleTargetService.createMany({
createRoleTargetInputs: userWorkspaceIdsToAssign.map(
(userWorkspaceId) => ({
roleId,
targetId: userWorkspaceId,
targetMetadataForeignKey: 'userWorkspaceId' as const,
}),
),
workspaceId,
});
}
@@ -208,57 +215,72 @@ export class UserRoleService {
}
}
private async validateAssignRoleInput({
userWorkspaceId,
private async validateAssignRoleInputsAndGetUserWorkspaceIdsToAssign({
userWorkspaceIds,
workspaceId,
roleId,
}: {
userWorkspaceId: string;
userWorkspaceIds: string[];
workspaceId: string;
roleId: string;
}) {
const userWorkspace = await this.userWorkspaceRepository.findOne({
}): Promise<string[]> {
const userWorkspaces = await this.userWorkspaceRepository.find({
where: {
id: userWorkspaceId,
id: In(userWorkspaceIds),
},
});
if (!isDefined(userWorkspace)) {
const foundUserWorkspaceIds = new Set(
userWorkspaces.map((userWorkspace) => userWorkspace.id),
);
const missingUserWorkspaceIds = userWorkspaceIds.filter(
(id) => !foundUserWorkspaceIds.has(id),
);
if (missingUserWorkspaceIds.length > 0) {
throw new PermissionsException(
'User workspace not found',
`User workspaces not found: ${missingUserWorkspaceIds.join(', ')}`,
PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND,
{
userFriendlyMessage: msg`Your workspace membership could not be found. You may no longer have access to this workspace.`,
userFriendlyMessage: msg`Some workspace memberships could not be found. They may no longer have access to this workspace.`,
},
);
}
const roles = await this.getRolesByUserWorkspaces({
userWorkspaceIds: [userWorkspace.id],
const rolesByUserWorkspaces = await this.getRolesByUserWorkspaces({
userWorkspaceIds,
workspaceId,
});
const currentRole = roles.get(userWorkspace.id)?.[0];
const userWorkspaceIdsToAssign: string[] = [];
let adminRoleIdToValidate: string | undefined;
if (currentRole?.id === roleId) {
return {
roleToAssignIsSameAsCurrentRole: true,
};
}
for (const userWorkspaceId of userWorkspaceIds) {
const currentRole = rolesByUserWorkspaces.get(userWorkspaceId)?.[0];
if (
!(
if (currentRole?.id === roleId) {
continue;
}
if (
isDefined(currentRole) &&
currentRole.standardId === ADMIN_ROLE.standardId
)
) {
return;
) {
adminRoleIdToValidate = currentRole.id;
}
userWorkspaceIdsToAssign.push(userWorkspaceId);
}
await this.validateMoreThanOneWorkspaceMemberHasAdminRoleOrThrow({
workspaceId,
adminRoleId: currentRole.id,
});
if (isDefined(adminRoleIdToValidate)) {
await this.validateMoreThanOneWorkspaceMemberHasAdminRoleOrThrow({
workspaceId,
adminRoleId: adminRoleIdToValidate,
});
}
return userWorkspaceIdsToAssign;
}
private async validateMoreThanOneWorkspaceMemberHasAdminRoleOrThrow({