From a08601a21054e430dc45b2aada7365abaaefbf28 Mon Sep 17 00:00:00 2001 From: Scott Wilson Date: Thu, 13 Nov 2025 14:20:24 -0500 Subject: [PATCH] feat: add transparent error message when attempting to delete a role that is used by a membership --- backend/src/server/routes/index.ts | 3 ++- backend/src/services/role/role-service.ts | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index fab7ebde3..569eeb98b 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -643,7 +643,8 @@ export const registerRoutes = async ( projectDAL, identityDAL, userDAL, - externalGroupOrgRoleMappingDAL + externalGroupOrgRoleMappingDAL, + membershipRoleDAL }); const additionalPrivilegeService = additionalPrivilegeServiceFactory({ additionalPrivilegeDAL, diff --git a/backend/src/services/role/role-service.ts b/backend/src/services/role/role-service.ts index 3387dc96b..653a00b5c 100644 --- a/backend/src/services/role/role-service.ts +++ b/backend/src/services/role/role-service.ts @@ -6,6 +6,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { BadRequestError, NotFoundError } from "@app/lib/errors"; import { validateHandlebarTemplate } from "@app/lib/template/validate-handlebars"; import { UnpackedPermissionSchema, unpackPermissions } from "@app/server/routes/sanitizedSchema/permission"; +import { TMembershipRoleDALFactory } from "@app/services/membership/membership-role-dal"; import { ActorType } from "../auth/auth-type"; import { TExternalGroupOrgRoleMappingDALFactory } from "../external-group-org-role-mapping/external-group-org-role-mapping-dal"; @@ -33,6 +34,7 @@ type TRoleServiceFactoryDep = { permissionService: Pick; projectDAL: Pick; externalGroupOrgRoleMappingDAL: Pick; + membershipRoleDAL: Pick; }; export type TRoleServiceFactory = ReturnType; @@ -43,7 +45,8 @@ export const roleServiceFactory = ({ projectDAL, identityDAL, userDAL, - externalGroupOrgRoleMappingDAL + externalGroupOrgRoleMappingDAL, + membershipRoleDAL }: TRoleServiceFactoryDep) => { const orgRoleFactory = newOrgRoleFactory({ permissionService, @@ -137,6 +140,23 @@ export const roleServiceFactory = ({ }); if (!existingRole) throw new NotFoundError({ message: `Role with ${dto.selector.id} not found` }); + const [roleUsageData] = await membershipRoleDAL.find( + { + customRoleId: dto.selector.id + }, + { count: true } + ); + + if (roleUsageData) { + const count = Number.parseInt(roleUsageData.count, 10); + if (count > 0) { + const plural = count > 1 ? "s" : ""; + throw new BadRequestError({ + message: `Role is assigned to ${count} identity membership${plural}. Re-assign membership role${plural} to delete this role.` + }); + } + } + const [role] = await roleDAL.delete({ id: existingRole.id, [scope.key]: scope.value