From 9e6f19d16ed5d5cbd733dd34d0ac991beca082df Mon Sep 17 00:00:00 2001 From: Weiko Date: Mon, 9 Feb 2026 19:19:23 +0100 Subject: [PATCH] Fix RLS creation logic (#17815) Fix after resolveEntityRelationUniversalIdentifiers introduction ``` FlatEntityMapsException [Error]: Could not find rowLevelPermissionPredicateGroup for given rowLevelPermissionPredicateGroupId at resolveEntityRelationUniversalIdentifiers ``` - Creating util for RLS flat entity creation to be aligned with other entities - Progressively update the flat entity maps as each new group is built, following the same optimistic pattern used by computeUniversalFlatEntityMapsFromTo in the validate build and run. The updated maps are now passed to computePredicateOperations so predicates can also resolve references to the newly created groups. - Adding more coverage Co-authored-by: Charles Bochet --- ...w-level-permission-predicate-group.util.ts | 71 ++++++ ...lat-row-level-permission-predicate.util.ts | 82 ++++++ ...w-level-permission-predicate-group.util.ts | 38 +++ ...lat-row-level-permission-predicate.util.ts | 56 ++++ .../row-level-permission-predicate.service.ts | 240 ++++++------------ ...ssion-predicate-upsert.integration-spec.ts | 107 ++++++++ 6 files changed, 438 insertions(+), 156 deletions(-) create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-input-to-flat-row-level-permission-predicate.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-update-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group.util.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-update-row-level-permission-predicate-input-to-flat-row-level-permission-predicate.util.ts diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group.util.ts new file mode 100644 index 0000000000..7c5232147a --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group.util.ts @@ -0,0 +1,71 @@ +import { v4 } from 'uuid'; + +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; +import { type RowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/upsert-row-level-permission-predicates.input'; +import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type'; + +export const fromCreateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroup = + ({ + input, + roleId, + workspaceId, + roleUniversalIdentifier, + flatApplication, + flatObjectMetadataMaps, + flatRowLevelPermissionPredicateGroupMaps, + }: { + input: RowLevelPermissionPredicateGroupInput; + roleId: string; + workspaceId: string; + roleUniversalIdentifier: string; + flatApplication: FlatApplication; + } & Pick & { + flatRowLevelPermissionPredicateGroupMaps: FlatEntityMaps; + }): FlatRowLevelPermissionPredicateGroup => { + const groupId = input.id ?? v4(); + const createdAt = new Date().toISOString(); + + const { + objectMetadataUniversalIdentifier, + parentRowLevelPermissionPredicateGroupUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'rowLevelPermissionPredicateGroup', + foreignKeyValues: { + objectMetadataId: input.objectMetadataId, + parentRowLevelPermissionPredicateGroupId: + input.parentRowLevelPermissionPredicateGroupId, + }, + flatEntityMaps: { + flatObjectMetadataMaps, + flatRowLevelPermissionPredicateGroupMaps, + }, + }); + + return { + id: groupId, + workspaceId, + roleId, + roleUniversalIdentifier, + objectMetadataId: input.objectMetadataId, + objectMetadataUniversalIdentifier, + logicalOperator: input.logicalOperator, + parentRowLevelPermissionPredicateGroupId: + input.parentRowLevelPermissionPredicateGroupId ?? null, + parentRowLevelPermissionPredicateGroupUniversalIdentifier, + positionInRowLevelPermissionPredicateGroup: + input.positionInRowLevelPermissionPredicateGroup ?? null, + childRowLevelPermissionPredicateGroupIds: [], + childRowLevelPermissionPredicateGroupUniversalIdentifiers: [], + rowLevelPermissionPredicateIds: [], + rowLevelPermissionPredicateUniversalIdentifiers: [], + createdAt, + updatedAt: createdAt, + deletedAt: null, + universalIdentifier: groupId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + }; + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-input-to-flat-row-level-permission-predicate.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-input-to-flat-row-level-permission-predicate.util.ts new file mode 100644 index 0000000000..85eceb66cd --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-input-to-flat-row-level-permission-predicate.util.ts @@ -0,0 +1,82 @@ +import { v4 } from 'uuid'; + +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; +import { type RowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/upsert-row-level-permission-predicates.input'; +import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type'; +import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type'; + +export const fromCreateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicate = + ({ + input, + roleId, + objectMetadataId, + workspaceId, + roleUniversalIdentifier, + objectMetadataUniversalIdentifier, + flatApplication, + flatFieldMetadataMaps, + flatRowLevelPermissionPredicateGroupMaps, + }: { + input: RowLevelPermissionPredicateInput; + roleId: string; + objectMetadataId: string; + workspaceId: string; + roleUniversalIdentifier: string; + objectMetadataUniversalIdentifier: string; + flatApplication: FlatApplication; + } & Pick & { + flatRowLevelPermissionPredicateGroupMaps: FlatEntityMaps; + }): FlatRowLevelPermissionPredicate => { + const predicateId = input.id ?? v4(); + const createdAt = new Date().toISOString(); + + const { + fieldMetadataUniversalIdentifier, + rowLevelPermissionPredicateGroupUniversalIdentifier, + workspaceMemberFieldMetadataUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'rowLevelPermissionPredicate', + foreignKeyValues: { + fieldMetadataId: input.fieldMetadataId, + rowLevelPermissionPredicateGroupId: + input.rowLevelPermissionPredicateGroupId, + workspaceMemberFieldMetadataId: input.workspaceMemberFieldMetadataId, + }, + flatEntityMaps: { + flatFieldMetadataMaps, + flatRowLevelPermissionPredicateGroupMaps, + }, + }); + + return { + id: predicateId, + workspaceId, + roleId, + roleUniversalIdentifier, + objectMetadataId, + objectMetadataUniversalIdentifier, + fieldMetadataId: input.fieldMetadataId, + fieldMetadataUniversalIdentifier, + operand: input.operand, + value: input.value ?? null, + subFieldName: input.subFieldName ?? null, + workspaceMemberFieldMetadataId: + input.workspaceMemberFieldMetadataId ?? null, + workspaceMemberFieldMetadataUniversalIdentifier, + workspaceMemberSubFieldName: input.workspaceMemberSubFieldName ?? null, + rowLevelPermissionPredicateGroupId: + input.rowLevelPermissionPredicateGroupId ?? null, + rowLevelPermissionPredicateGroupUniversalIdentifier, + positionInRowLevelPermissionPredicateGroup: + input.positionInRowLevelPermissionPredicateGroup ?? null, + createdAt, + updatedAt: createdAt, + deletedAt: null, + universalIdentifier: predicateId, + applicationId: flatApplication.id, + applicationUniversalIdentifier: flatApplication.universalIdentifier, + }; + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-update-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-update-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group.util.ts new file mode 100644 index 0000000000..c2e5e8ead4 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-update-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group.util.ts @@ -0,0 +1,38 @@ +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; +import { type RowLevelPermissionPredicateGroupInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/upsert-row-level-permission-predicates.input'; +import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type'; + +export const fromUpdateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroup = + ({ + input, + existingGroup, + flatRowLevelPermissionPredicateGroupMaps, + }: { + input: RowLevelPermissionPredicateGroupInput; + existingGroup: FlatRowLevelPermissionPredicateGroup; + flatRowLevelPermissionPredicateGroupMaps: FlatEntityMaps; + }): FlatRowLevelPermissionPredicateGroup => { + const { parentRowLevelPermissionPredicateGroupUniversalIdentifier } = + resolveEntityRelationUniversalIdentifiers({ + metadataName: 'rowLevelPermissionPredicateGroup', + foreignKeyValues: { + parentRowLevelPermissionPredicateGroupId: + input.parentRowLevelPermissionPredicateGroupId, + }, + flatEntityMaps: { + flatRowLevelPermissionPredicateGroupMaps, + }, + }); + + return { + ...existingGroup, + logicalOperator: input.logicalOperator, + parentRowLevelPermissionPredicateGroupId: + input.parentRowLevelPermissionPredicateGroupId ?? null, + parentRowLevelPermissionPredicateGroupUniversalIdentifier, + positionInRowLevelPermissionPredicateGroup: + input.positionInRowLevelPermissionPredicateGroup ?? null, + updatedAt: new Date().toISOString(), + }; + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-update-row-level-permission-predicate-input-to-flat-row-level-permission-predicate.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-update-row-level-permission-predicate-input-to-flat-row-level-permission-predicate.util.ts new file mode 100644 index 0000000000..ea5882d012 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-update-row-level-permission-predicate-input-to-flat-row-level-permission-predicate.util.ts @@ -0,0 +1,56 @@ +import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; +import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; +import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; +import { type RowLevelPermissionPredicateInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/upsert-row-level-permission-predicates.input'; +import { type FlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate-group.type'; +import { type FlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/row-level-permission-predicate/types/flat-row-level-permission-predicate.type'; + +export const fromUpdateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicate = + ({ + input, + existingPredicate, + flatFieldMetadataMaps, + flatRowLevelPermissionPredicateGroupMaps, + }: { + input: RowLevelPermissionPredicateInput; + existingPredicate: FlatRowLevelPermissionPredicate; + } & Pick & { + flatRowLevelPermissionPredicateGroupMaps: FlatEntityMaps; + }): FlatRowLevelPermissionPredicate => { + const { + fieldMetadataUniversalIdentifier, + rowLevelPermissionPredicateGroupUniversalIdentifier, + workspaceMemberFieldMetadataUniversalIdentifier, + } = resolveEntityRelationUniversalIdentifiers({ + metadataName: 'rowLevelPermissionPredicate', + foreignKeyValues: { + fieldMetadataId: input.fieldMetadataId, + rowLevelPermissionPredicateGroupId: + input.rowLevelPermissionPredicateGroupId, + workspaceMemberFieldMetadataId: input.workspaceMemberFieldMetadataId, + }, + flatEntityMaps: { + flatFieldMetadataMaps, + flatRowLevelPermissionPredicateGroupMaps, + }, + }); + + return { + ...existingPredicate, + fieldMetadataId: input.fieldMetadataId, + fieldMetadataUniversalIdentifier, + operand: input.operand, + value: input.value ?? null, + subFieldName: input.subFieldName ?? null, + workspaceMemberFieldMetadataId: + input.workspaceMemberFieldMetadataId ?? null, + workspaceMemberFieldMetadataUniversalIdentifier, + workspaceMemberSubFieldName: input.workspaceMemberSubFieldName ?? null, + rowLevelPermissionPredicateGroupId: + input.rowLevelPermissionPredicateGroupId ?? null, + rowLevelPermissionPredicateGroupUniversalIdentifier, + positionInRowLevelPermissionPredicateGroup: + input.positionInRowLevelPermissionPredicateGroup ?? null, + updatedAt: new Date().toISOString(), + }; + }; diff --git a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service.ts b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service.ts index fe78b765c1..c1edb80eb5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/row-level-permission-predicate/services/row-level-permission-predicate.service.ts @@ -6,16 +6,22 @@ import { isDefined } from 'twenty-shared/utils'; import { v4 } from 'uuid'; import { ApplicationService } from 'src/engine/core-modules/application/services/application.service'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; import { BillingEntitlementKey } from 'src/engine/core-modules/billing/enums/billing-entitlement-key.enum'; import { BillingService } from 'src/engine/core-modules/billing/services/billing.service'; import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; +import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { type AllFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/all-flat-entity-maps.type'; import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; -import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; +import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util'; import { findFlatEntityByIdInFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-flat-entity-maps.util'; import { resolveEntityRelationUniversalIdentifiers } from 'src/engine/metadata-modules/flat-entity/utils/resolve-entity-relation-universal-identifiers.util'; +import { fromCreateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group.util'; +import { fromCreateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-create-row-level-permission-predicate-input-to-flat-row-level-permission-predicate.util'; import { fromFlatRowLevelPermissionPredicateGroupToDto } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-flat-row-level-permission-predicate-group-to-dto.util'; import { fromFlatRowLevelPermissionPredicateToDto } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-flat-row-level-permission-predicate-to-dto.util'; +import { fromUpdateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroup } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-update-row-level-permission-predicate-group-input-to-flat-row-level-permission-predicate-group.util'; +import { fromUpdateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicate } from 'src/engine/metadata-modules/flat-row-level-permission-predicate/utils/from-update-row-level-permission-predicate-input-to-flat-row-level-permission-predicate.util'; import { type RowLevelPermissionPredicateGroupInput, type RowLevelPermissionPredicateInput, @@ -206,19 +212,22 @@ export class RowLevelPermissionPredicateService { group.objectMetadataId === objectMetadataId, ); - const { groupsToCreate, groupsToUpdate, groupsToDelete } = - this.computePredicateGroupOperations({ - existingGroups, - inputGroups: predicateGroups, - roleId, - workspaceId, - flatRowLevelPermissionPredicateGroupMaps, - flatRoleMaps, - flatObjectMetadataMaps, - workspaceCustomApplicationId: workspaceCustomFlatApplication.id, - workspaceCustomApplicationUniversalIdentifier: - workspaceCustomFlatApplication.universalIdentifier, - }); + const { + groupsToCreate, + groupsToUpdate, + groupsToDelete, + flatRowLevelPermissionPredicateGroupMaps: + flatRowLevelPermissionPredicateGroupMapsWithCreatedGroups, + } = this.computePredicateGroupOperations({ + existingGroups, + inputGroups: predicateGroups, + roleId, + workspaceId, + flatApplication: workspaceCustomFlatApplication, + flatRowLevelPermissionPredicateGroupMaps, + flatRoleMaps, + flatObjectMetadataMaps, + }); const { predicatesToCreate, predicatesToUpdate, predicatesToDelete } = this.computePredicateOperations({ @@ -227,14 +236,13 @@ export class RowLevelPermissionPredicateService { roleId, objectMetadataId, workspaceId, + flatApplication: workspaceCustomFlatApplication, flatRowLevelPermissionPredicateMaps, - flatRowLevelPermissionPredicateGroupMaps, + flatRowLevelPermissionPredicateGroupMaps: + flatRowLevelPermissionPredicateGroupMapsWithCreatedGroups, flatRoleMaps, flatObjectMetadataMaps, flatFieldMetadataMaps, - workspaceCustomApplicationId: workspaceCustomFlatApplication.id, - workspaceCustomApplicationUniversalIdentifier: - workspaceCustomFlatApplication.universalIdentifier, }); await this.runUpsertMigration({ @@ -294,31 +302,32 @@ export class RowLevelPermissionPredicateService { inputGroups, roleId, workspaceId, + flatApplication, flatRowLevelPermissionPredicateGroupMaps, flatRoleMaps, flatObjectMetadataMaps, - workspaceCustomApplicationId, - workspaceCustomApplicationUniversalIdentifier, }: { existingGroups: FlatRowLevelPermissionPredicateGroup[]; inputGroups: RowLevelPermissionPredicateGroupInput[]; roleId: string; workspaceId: string; + flatApplication: FlatApplication; flatRowLevelPermissionPredicateGroupMaps: FlatEntityMaps; flatRoleMaps: AllFlatEntityMaps['flatRoleMaps']; flatObjectMetadataMaps: AllFlatEntityMaps['flatObjectMetadataMaps']; - workspaceCustomApplicationId: string; - workspaceCustomApplicationUniversalIdentifier: string; }): { groupsToCreate: FlatRowLevelPermissionPredicateGroup[]; groupsToUpdate: FlatRowLevelPermissionPredicateGroup[]; groupsToDelete: FlatRowLevelPermissionPredicateGroup[]; + flatRowLevelPermissionPredicateGroupMaps: FlatEntityMaps; } { const groupsToCreate: FlatRowLevelPermissionPredicateGroup[] = []; const groupsToUpdate: FlatRowLevelPermissionPredicateGroup[] = []; const inputGroupIds = new Set(); + let currentGroupMaps = flatRowLevelPermissionPredicateGroupMaps; + const { roleUniversalIdentifier } = resolveEntityRelationUniversalIdentifiers({ metadataName: 'rowLevelPermissionPredicateGroup', @@ -328,7 +337,6 @@ export class RowLevelPermissionPredicateService { for (const inputGroup of inputGroups) { const groupId = inputGroup.id ?? v4(); - const createdAt = new Date().toISOString(); inputGroupIds.add(groupId); @@ -338,69 +346,34 @@ export class RowLevelPermissionPredicateService { }); if (isDefined(existingGroup) && existingGroup.deletedAt === null) { - const { parentRowLevelPermissionPredicateGroupUniversalIdentifier } = - resolveEntityRelationUniversalIdentifiers({ - metadataName: 'rowLevelPermissionPredicateGroup', - foreignKeyValues: { - parentRowLevelPermissionPredicateGroupId: - inputGroup.parentRowLevelPermissionPredicateGroupId, + groupsToUpdate.push( + fromUpdateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroup( + { + input: inputGroup, + existingGroup, + flatRowLevelPermissionPredicateGroupMaps: currentGroupMaps, }, - flatEntityMaps: { - flatRowLevelPermissionPredicateGroupMaps, - }, - }); - - groupsToUpdate.push({ - ...existingGroup, - logicalOperator: inputGroup.logicalOperator, - parentRowLevelPermissionPredicateGroupId: - inputGroup.parentRowLevelPermissionPredicateGroupId ?? null, - parentRowLevelPermissionPredicateGroupUniversalIdentifier, - positionInRowLevelPermissionPredicateGroup: - inputGroup.positionInRowLevelPermissionPredicateGroup ?? null, - updatedAt: createdAt, - }); + ), + ); } else { - const { - objectMetadataUniversalIdentifier, - parentRowLevelPermissionPredicateGroupUniversalIdentifier, - } = resolveEntityRelationUniversalIdentifiers({ - metadataName: 'rowLevelPermissionPredicateGroup', - foreignKeyValues: { - objectMetadataId: inputGroup.objectMetadataId, - parentRowLevelPermissionPredicateGroupId: - inputGroup.parentRowLevelPermissionPredicateGroupId, - }, - flatEntityMaps: { - flatObjectMetadataMaps, - flatRowLevelPermissionPredicateGroupMaps, - }, - }); + const flatGroupToCreate = + fromCreateRowLevelPermissionPredicateGroupInputToFlatRowLevelPermissionPredicateGroup( + { + input: { ...inputGroup, id: groupId }, + roleId, + workspaceId, + roleUniversalIdentifier, + flatApplication, + flatObjectMetadataMaps, + flatRowLevelPermissionPredicateGroupMaps: currentGroupMaps, + }, + ); - groupsToCreate.push({ - id: groupId, - workspaceId, - roleId, - roleUniversalIdentifier, - objectMetadataId: inputGroup.objectMetadataId, - objectMetadataUniversalIdentifier, - logicalOperator: inputGroup.logicalOperator, - parentRowLevelPermissionPredicateGroupId: - inputGroup.parentRowLevelPermissionPredicateGroupId ?? null, - parentRowLevelPermissionPredicateGroupUniversalIdentifier, - positionInRowLevelPermissionPredicateGroup: - inputGroup.positionInRowLevelPermissionPredicateGroup ?? null, - childRowLevelPermissionPredicateGroupIds: [], - childRowLevelPermissionPredicateGroupUniversalIdentifiers: [], - rowLevelPermissionPredicateIds: [], - rowLevelPermissionPredicateUniversalIdentifiers: [], - createdAt, - updatedAt: createdAt, - deletedAt: null, - universalIdentifier: groupId, - applicationId: workspaceCustomApplicationId, - applicationUniversalIdentifier: - workspaceCustomApplicationUniversalIdentifier, + groupsToCreate.push(flatGroupToCreate); + + currentGroupMaps = addFlatEntityToFlatEntityMapsOrThrow({ + flatEntity: flatGroupToCreate, + flatEntityMaps: currentGroupMaps, }); } } @@ -417,6 +390,7 @@ export class RowLevelPermissionPredicateService { groupsToCreate, groupsToUpdate, groupsToDelete, + flatRowLevelPermissionPredicateGroupMaps: currentGroupMaps, }; } @@ -426,26 +400,24 @@ export class RowLevelPermissionPredicateService { roleId, objectMetadataId, workspaceId, + flatApplication, flatRowLevelPermissionPredicateMaps, flatRowLevelPermissionPredicateGroupMaps, flatRoleMaps, flatObjectMetadataMaps, flatFieldMetadataMaps, - workspaceCustomApplicationId, - workspaceCustomApplicationUniversalIdentifier, }: { existingPredicates: FlatRowLevelPermissionPredicate[]; inputPredicates: RowLevelPermissionPredicateInput[]; roleId: string; objectMetadataId: string; workspaceId: string; + flatApplication: FlatApplication; flatRowLevelPermissionPredicateMaps: FlatEntityMaps; flatRowLevelPermissionPredicateGroupMaps: FlatEntityMaps; flatRoleMaps: AllFlatEntityMaps['flatRoleMaps']; flatObjectMetadataMaps: AllFlatEntityMaps['flatObjectMetadataMaps']; flatFieldMetadataMaps: AllFlatEntityMaps['flatFieldMetadataMaps']; - workspaceCustomApplicationId: string; - workspaceCustomApplicationUniversalIdentifier: string; }): { predicatesToCreate: FlatRowLevelPermissionPredicate[]; predicatesToUpdate: FlatRowLevelPermissionPredicate[]; @@ -465,7 +437,6 @@ export class RowLevelPermissionPredicateService { for (const inputPredicate of inputPredicates) { const predicateId = inputPredicate.id ?? v4(); - const createdAt = new Date().toISOString(); inputPredicateIds.add(predicateId); @@ -474,79 +445,36 @@ export class RowLevelPermissionPredicateService { flatEntityMaps: flatRowLevelPermissionPredicateMaps, }); - const { - fieldMetadataUniversalIdentifier, - rowLevelPermissionPredicateGroupUniversalIdentifier, - workspaceMemberFieldMetadataUniversalIdentifier, - } = resolveEntityRelationUniversalIdentifiers({ - metadataName: 'rowLevelPermissionPredicate', - foreignKeyValues: { - fieldMetadataId: inputPredicate.fieldMetadataId, - rowLevelPermissionPredicateGroupId: - inputPredicate.rowLevelPermissionPredicateGroupId, - workspaceMemberFieldMetadataId: - inputPredicate.workspaceMemberFieldMetadataId, - }, - flatEntityMaps: { - flatFieldMetadataMaps, - flatRowLevelPermissionPredicateGroupMaps, - }, - }); - if ( isDefined(existingPredicate) && existingPredicate.deletedAt === null ) { - predicatesToUpdate.push({ - ...existingPredicate, - fieldMetadataId: inputPredicate.fieldMetadataId, - fieldMetadataUniversalIdentifier, - operand: inputPredicate.operand, - value: inputPredicate.value ?? null, - subFieldName: inputPredicate.subFieldName ?? null, - workspaceMemberFieldMetadataId: - inputPredicate.workspaceMemberFieldMetadataId ?? null, - workspaceMemberFieldMetadataUniversalIdentifier, - workspaceMemberSubFieldName: - inputPredicate.workspaceMemberSubFieldName ?? null, - rowLevelPermissionPredicateGroupId: - inputPredicate.rowLevelPermissionPredicateGroupId ?? null, - rowLevelPermissionPredicateGroupUniversalIdentifier, - positionInRowLevelPermissionPredicateGroup: - inputPredicate.positionInRowLevelPermissionPredicateGroup ?? null, - updatedAt: createdAt, - }); + predicatesToUpdate.push( + fromUpdateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicate( + { + input: inputPredicate, + existingPredicate, + flatFieldMetadataMaps, + flatRowLevelPermissionPredicateGroupMaps, + }, + ), + ); } else { - predicatesToCreate.push({ - id: predicateId, - workspaceId, - roleId, - roleUniversalIdentifier, - objectMetadataId, - objectMetadataUniversalIdentifier, - fieldMetadataId: inputPredicate.fieldMetadataId, - fieldMetadataUniversalIdentifier, - operand: inputPredicate.operand, - value: inputPredicate.value ?? null, - subFieldName: inputPredicate.subFieldName ?? null, - workspaceMemberFieldMetadataId: - inputPredicate.workspaceMemberFieldMetadataId ?? null, - workspaceMemberFieldMetadataUniversalIdentifier, - workspaceMemberSubFieldName: - inputPredicate.workspaceMemberSubFieldName ?? null, - rowLevelPermissionPredicateGroupId: - inputPredicate.rowLevelPermissionPredicateGroupId ?? null, - rowLevelPermissionPredicateGroupUniversalIdentifier, - positionInRowLevelPermissionPredicateGroup: - inputPredicate.positionInRowLevelPermissionPredicateGroup ?? null, - createdAt, - updatedAt: createdAt, - deletedAt: null, - universalIdentifier: predicateId, - applicationId: workspaceCustomApplicationId, - applicationUniversalIdentifier: - workspaceCustomApplicationUniversalIdentifier, - }); + predicatesToCreate.push( + fromCreateRowLevelPermissionPredicateInputToFlatRowLevelPermissionPredicate( + { + input: { ...inputPredicate, id: predicateId }, + roleId, + objectMetadataId, + workspaceId, + roleUniversalIdentifier, + objectMetadataUniversalIdentifier, + flatApplication, + flatFieldMetadataMaps, + flatRowLevelPermissionPredicateGroupMaps, + }, + ), + ); } } diff --git a/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/successful-row-level-permission-predicate-upsert.integration-spec.ts b/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/successful-row-level-permission-predicate-upsert.integration-spec.ts index 237e42176e..d18b8aecb5 100644 --- a/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/successful-row-level-permission-predicate-upsert.integration-spec.ts +++ b/packages/twenty-server/test/integration/metadata/suites/row-level-permission-predicate/successful-row-level-permission-predicate-upsert.integration-spec.ts @@ -8,6 +8,7 @@ import { RowLevelPermissionPredicateGroupLogicalOperator, RowLevelPermissionPredicateOperand, } from 'twenty-shared/types'; +import { v4 } from 'uuid'; import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum'; import { type UpsertRowLevelPermissionPredicatesInput } from 'src/engine/metadata-modules/row-level-permission-predicate/dtos/inputs/upsert-row-level-permission-predicates.input'; @@ -444,4 +445,110 @@ describe('Row Level Permission Predicate upsert should succeed', () => { positionInRowLevelPermissionPredicateGroup: 0, }); }); + + it('should create groups and predicates referencing those groups in a single call', async () => { + const groupId = v4(); + + const input: UpsertRowLevelPermissionPredicatesInput = { + roleId: createdRoleId, + objectMetadataId: companyObjectMetadataId, + predicates: [ + { + fieldMetadataId: companyNameFieldMetadataId, + operand: RowLevelPermissionPredicateOperand.CONTAINS, + rowLevelPermissionPredicateGroupId: groupId, + positionInRowLevelPermissionPredicateGroup: 0, + }, + ], + predicateGroups: [ + { + id: groupId, + objectMetadataId: companyObjectMetadataId, + logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator.AND, + }, + ], + }; + + const { data } = await upsertRowLevelPermissionPredicates({ + expectToFail: false, + input, + }); + + expect(data.upsertRowLevelPermissionPredicates.predicates).toHaveLength(1); + expect( + data.upsertRowLevelPermissionPredicates.predicateGroups, + ).toHaveLength(1); + expect( + data.upsertRowLevelPermissionPredicates.predicateGroups[0], + ).toMatchObject({ + id: groupId, + objectMetadataId: companyObjectMetadataId, + logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator.AND, + }); + expect(data.upsertRowLevelPermissionPredicates.predicates[0]).toMatchObject( + { + fieldMetadataId: companyNameFieldMetadataId, + rowLevelPermissionPredicateGroupId: groupId, + positionInRowLevelPermissionPredicateGroup: 0, + }, + ); + }); + + it('should create nested parent-child groups in a single call', async () => { + const parentGroupId = v4(); + const childGroupId = v4(); + + const input: UpsertRowLevelPermissionPredicatesInput = { + roleId: createdRoleId, + objectMetadataId: companyObjectMetadataId, + predicates: [], + predicateGroups: [ + { + id: parentGroupId, + objectMetadataId: companyObjectMetadataId, + logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator.AND, + parentRowLevelPermissionPredicateGroupId: null, + }, + { + id: childGroupId, + objectMetadataId: companyObjectMetadataId, + logicalOperator: RowLevelPermissionPredicateGroupLogicalOperator.OR, + parentRowLevelPermissionPredicateGroupId: parentGroupId, + }, + ], + }; + + const { data } = await upsertRowLevelPermissionPredicates({ + expectToFail: false, + input, + }); + + expect( + data.upsertRowLevelPermissionPredicates.predicateGroups, + ).toHaveLength(2); + + const parentGroup = + data.upsertRowLevelPermissionPredicates.predicateGroups.find( + (group: { id: string }) => group.id === parentGroupId, + ); + + const childGroup = + data.upsertRowLevelPermissionPredicates.predicateGroups.find( + (group: { id: string }) => group.id === childGroupId, + ); + + expect(parentGroup).toBeDefined(); + expect(parentGroup?.parentRowLevelPermissionPredicateGroupId).toBeNull(); + expect(parentGroup?.logicalOperator).toBe( + RowLevelPermissionPredicateGroupLogicalOperator.AND, + ); + + expect(childGroup).toBeDefined(); + expect(childGroup?.parentRowLevelPermissionPredicateGroupId).toBe( + parentGroupId, + ); + expect(childGroup?.logicalOperator).toBe( + RowLevelPermissionPredicateGroupLogicalOperator.OR, + ); + }); });