From 5eaabe95e75c1092977e84e771b5d08d566853ac Mon Sep 17 00:00:00 2001 From: martmull Date: Thu, 9 Apr 2026 11:36:16 +0200 Subject: [PATCH] Fix role synchronisation (#19469) As title solves https://discord.com/channels/1130383047699738754/1491167098398052503 --- .../src/logic-functions/seed-post-cards.ts | 59 +++++++ .../src/roles/default-function.role.ts | 2 +- .../SettingsApplicationPermissionsTab.tsx | 14 +- .../app-dev/expected-manifest.ts | 3 + .../app-dev/expected-manifest.ts | 12 +- .../build/manifest/manifest-build.ts | 7 +- .../from-role-config-to-role-manifest.ts | 41 +++++ .../sdk/common/types/define-entity.type.ts | 4 +- .../twenty-sdk/src/sdk/roles/define-role.ts | 4 +- .../twenty-sdk/src/sdk/roles/role-config.ts | 15 ++ .../application-manifest-migration.service.ts | 152 +----------------- .../application-manifest.module.ts | 4 - ...to-universal-flat-field-permission.util.ts | 28 ++++ ...o-universal-flat-object-permission.util.ts | 29 ++++ ...-to-universal-flat-permission-flag.util.ts | 23 +++ ...est-all-universal-flat-entity-maps.util.ts | 44 +++++ .../twenty-shared/src/application/index.ts | 1 + .../src/application/roleManifestType.ts | 10 +- 18 files changed, 282 insertions(+), 170 deletions(-) create mode 100644 packages/twenty-apps/examples/postcard/src/logic-functions/seed-post-cards.ts create mode 100644 packages/twenty-sdk/src/cli/utilities/build/manifest/utils/from-role-config-to-role-manifest.ts create mode 100644 packages/twenty-sdk/src/sdk/roles/role-config.ts create mode 100644 packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-field-permission-manifest-to-universal-flat-field-permission.util.ts create mode 100644 packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-object-permission-manifest-to-universal-flat-object-permission.util.ts create mode 100644 packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-permission-flag.util.ts diff --git a/packages/twenty-apps/examples/postcard/src/logic-functions/seed-post-cards.ts b/packages/twenty-apps/examples/postcard/src/logic-functions/seed-post-cards.ts new file mode 100644 index 0000000000..e0ebf422fd --- /dev/null +++ b/packages/twenty-apps/examples/postcard/src/logic-functions/seed-post-cards.ts @@ -0,0 +1,59 @@ +import { CoreApiClient } from 'twenty-client-sdk/core'; +import { definePostInstallLogicFunction } from 'twenty-sdk'; + +const POST_CARDS_TO_SEED = [ + { + name: 'Greetings from Paris', + content: + 'Wish you were here! The Eiffel Tower looks even better in person. - Alex', + }, + { + name: 'Hello from Tokyo', + content: + 'The cherry blossoms are amazing this time of year. See you soon! - Sam', + }, +]; + +const handler = async (): Promise<{ + message: string; + createdIds: string[]; +}> => { + console.log('Seeding 2 post cards...'); + const client = new CoreApiClient(); + + const createdIds: string[] = []; + + for (const postCard of POST_CARDS_TO_SEED) { + const { createPostCard } = await client.mutation({ + createPostCard: { + __args: { + data: { + name: postCard.name, + content: postCard.content, + }, + }, + id: true, + }, + }); + + if (!createPostCard?.id) { + throw new Error(`Failed to create post card "${postCard.name}"`); + } + + createdIds.push(createPostCard.id); + } + + console.log('Seeding complete!'); + return { + message: `Seeded ${createdIds.length} post cards`, + createdIds, + }; +}; + +export default definePostInstallLogicFunction({ + universalIdentifier: '9f3d8c21-b471-4a82-8e5c-6f3a7b8c9d01', + name: 'seed-post-cards', + description: 'Seeds the workspace with 2 sample post card records.', + timeoutSeconds: 10, + handler, +}); diff --git a/packages/twenty-apps/examples/postcard/src/roles/default-function.role.ts b/packages/twenty-apps/examples/postcard/src/roles/default-function.role.ts index dc68a1db49..c808fd58c8 100644 --- a/packages/twenty-apps/examples/postcard/src/roles/default-function.role.ts +++ b/packages/twenty-apps/examples/postcard/src/roles/default-function.role.ts @@ -33,7 +33,7 @@ export default defineRole({ objectUniversalIdentifier: POST_CARD_UNIVERSAL_IDENTIFIER, fieldUniversalIdentifier: CONTENT_FIELD_UNIVERSAL_IDENTIFIER, canReadFieldValue: false, - canUpdateFieldValue: false, + canUpdateFieldValue: true, }, ], permissionFlags: [PermissionFlag.APPLICATIONS], diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationPermissionsTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationPermissionsTab.tsx index 6b3c92cdd9..2abf34c9cb 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationPermissionsTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationPermissionsTab.tsx @@ -153,12 +153,14 @@ const buildSyntheticRole = ( canReadFieldValue: permission.canReadFieldValue, canUpdateFieldValue: permission.canUpdateFieldValue, })), - permissionFlags: (defaultRole.permissionFlags ?? []).map((flag) => ({ - __typename: 'PermissionFlag' as const, - id: uuidv4(), - roleId: defaultRole.universalIdentifier, - flag: flag as PermissionFlagType, - })), + permissionFlags: (defaultRole.permissionFlags ?? []).map( + (permissionFlag) => ({ + __typename: 'PermissionFlag' as const, + id: uuidv4(), + roleId: defaultRole.universalIdentifier, + flag: permissionFlag.flag, + }), + ), }); const buildFieldMetadataItemFromMarketplaceField = ( diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/minimal-app/__integration__/app-dev/expected-manifest.ts b/packages/twenty-sdk/src/cli/__tests__/apps/minimal-app/__integration__/app-dev/expected-manifest.ts index c16414e650..dbe62f8df4 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/minimal-app/__integration__/app-dev/expected-manifest.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/minimal-app/__integration__/app-dev/expected-manifest.ts @@ -363,6 +363,9 @@ export const EXPECTED_MANIFEST: Manifest = { canBeAssignedToAgents: false, canBeAssignedToUsers: true, canBeAssignedToApiKeys: false, + fieldPermissions: [], + objectPermissions: [], + permissionFlags: [], }, ], }; diff --git a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts index 304f72ecb8..d93cf83793 100644 --- a/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts +++ b/packages/twenty-sdk/src/cli/__tests__/apps/rich-app/__integration__/app-dev/expected-manifest.ts @@ -1499,6 +1499,9 @@ export const EXPECTED_MANIFEST: Manifest = { description: 'A simple root-level role', label: 'Root role', universalIdentifier: 'c0c1c2c3-c4c5-4000-8000-000000000001', + fieldPermissions: [], + objectPermissions: [], + permissionFlags: [], }, { canBeAssignedToAgents: false, @@ -1512,6 +1515,7 @@ export const EXPECTED_MANIFEST: Manifest = { description: 'Default role for function Twenty client', fieldPermissions: [ { + universalIdentifier: 'dbc86ced-bd2c-5874-93f1-1f72c5111991', canReadFieldValue: false, canUpdateFieldValue: false, fieldUniversalIdentifier: '58a0a314-d7ea-4865-9850-7fb84e72f30b', @@ -1521,6 +1525,7 @@ export const EXPECTED_MANIFEST: Manifest = { label: 'Default function role', objectPermissions: [ { + universalIdentifier: '99c7c326-04ca-5c8b-ad11-da6c5b819813', canDestroyObjectRecords: false, canReadObjectRecords: true, canSoftDeleteObjectRecords: false, @@ -1528,7 +1533,12 @@ export const EXPECTED_MANIFEST: Manifest = { objectUniversalIdentifier: '54b589ca-eeed-4950-a176-358418b85c05', }, ], - permissionFlags: [PermissionFlagType.APPLICATIONS], + permissionFlags: [ + { + universalIdentifier: '01d7865a-7700-5d49-b2aa-62623c2cbac7', + flag: PermissionFlagType.APPLICATIONS, + }, + ], universalIdentifier: 'b648f87b-1d26-4961-b974-0908fd991061', }, ], diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts index 88b76f06b9..6532a96baf 100644 --- a/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/manifest-build.ts @@ -38,6 +38,8 @@ import { import { getInputSchemaFromSourceCode } from 'twenty-shared/logic-function'; import { assertUnreachable } from 'twenty-shared/utils'; import { addMissingFieldOptionIds } from '@/cli/utilities/build/manifest/utils/add-missing-field-option-ids'; +import { fromRoleConfigToRoleManifest } from '@/cli/utilities/build/manifest/utils/from-role-config-to-role-manifest'; +import { type RoleConfig } from '@/sdk/roles/role-config'; const loadSources = async (appPath: string): Promise => { return await glob(['**/*.ts', '**/*.tsx'], { @@ -170,11 +172,12 @@ export const buildManifest = async ( break; } case ManifestEntityKey.Roles: { - const extract = await extractManifestFromFile({ + const extract = await extractManifestFromFile({ appPath, filePath, }); - roles.push(extract.config); + const roleConfig = fromRoleConfigToRoleManifest(extract.config); + roles.push(roleConfig); errors.push(...extract.errors); rolesFilePaths.push(relativePath); break; diff --git a/packages/twenty-sdk/src/cli/utilities/build/manifest/utils/from-role-config-to-role-manifest.ts b/packages/twenty-sdk/src/cli/utilities/build/manifest/utils/from-role-config-to-role-manifest.ts new file mode 100644 index 0000000000..90dfc41b3f --- /dev/null +++ b/packages/twenty-sdk/src/cli/utilities/build/manifest/utils/from-role-config-to-role-manifest.ts @@ -0,0 +1,41 @@ +import { type RoleConfig } from '@/sdk/roles/role-config'; +import { type RoleManifest } from 'twenty-shared/application'; +import { v5 as uuidv5 } from 'uuid'; + +const ROLE_UNIVERSAL_IDENTIFIER_NAMESPACE = + 'b403ec59-4d80-4f22-85e6-717a192dc9cb'; + +export const fromRoleConfigToRoleManifest = ( + roleConfig: RoleConfig, +): RoleManifest => { + return { + ...roleConfig, + objectPermissions: (roleConfig.objectPermissions ?? []).map( + (objectPermission) => ({ + ...objectPermission, + universalIdentifier: uuidv5( + `${roleConfig.universalIdentifier}:${objectPermission.objectUniversalIdentifier}`, + ROLE_UNIVERSAL_IDENTIFIER_NAMESPACE, + ), + }), + ), + fieldPermissions: (roleConfig.fieldPermissions ?? []).map( + (fieldPermission) => ({ + ...fieldPermission, + universalIdentifier: uuidv5( + `${roleConfig.universalIdentifier}:${fieldPermission.objectUniversalIdentifier}:${fieldPermission.fieldUniversalIdentifier}`, + ROLE_UNIVERSAL_IDENTIFIER_NAMESPACE, + ), + }), + ), + permissionFlags: (roleConfig.permissionFlags ?? []).map( + (permissionFlag) => ({ + universalIdentifier: uuidv5( + `${roleConfig.universalIdentifier}:${permissionFlag}`, + ROLE_UNIVERSAL_IDENTIFIER_NAMESPACE, + ), + flag: permissionFlag, + }), + ), + }; +}; diff --git a/packages/twenty-sdk/src/sdk/common/types/define-entity.type.ts b/packages/twenty-sdk/src/sdk/common/types/define-entity.type.ts index 03aa94a763..93fb3a70df 100644 --- a/packages/twenty-sdk/src/sdk/common/types/define-entity.type.ts +++ b/packages/twenty-sdk/src/sdk/common/types/define-entity.type.ts @@ -4,10 +4,10 @@ import { type LogicFunctionConfig } from '@/sdk/logic-functions/logic-function-c import { type ObjectConfig } from '@/sdk/objects/object-config'; import { type PageLayoutConfig } from '@/sdk/page-layouts/page-layout-config'; import { type ViewConfig } from '@/sdk/views/view-config'; +import { type RoleConfig } from '@/sdk/roles/role-config'; import { type FieldManifest, type NavigationMenuItemManifest, - type RoleManifest, type SkillManifest, } from 'twenty-shared/application'; @@ -23,7 +23,7 @@ export type DefinableEntity = | FieldManifest | FrontComponentConfig | LogicFunctionConfig - | RoleManifest + | RoleConfig | SkillManifest | ViewConfig | NavigationMenuItemManifest diff --git a/packages/twenty-sdk/src/sdk/roles/define-role.ts b/packages/twenty-sdk/src/sdk/roles/define-role.ts index e092c9faab..9d0b3b46d1 100644 --- a/packages/twenty-sdk/src/sdk/roles/define-role.ts +++ b/packages/twenty-sdk/src/sdk/roles/define-role.ts @@ -1,8 +1,8 @@ import { createValidationResult } from '@/sdk/common/utils/create-validation-result'; -import { type RoleManifest } from 'twenty-shared/application'; import { type DefineEntity } from '@/sdk/common/types/define-entity.type'; +import { type RoleConfig } from '@/sdk/roles/role-config'; -export const defineRole: DefineEntity = (config) => { +export const defineRole: DefineEntity = (config) => { const errors = []; if (!config.universalIdentifier) { diff --git a/packages/twenty-sdk/src/sdk/roles/role-config.ts b/packages/twenty-sdk/src/sdk/roles/role-config.ts new file mode 100644 index 0000000000..e99cdedaa0 --- /dev/null +++ b/packages/twenty-sdk/src/sdk/roles/role-config.ts @@ -0,0 +1,15 @@ +import type { + FieldPermissionManifest, + ObjectPermissionManifest, + RoleManifest, +} from 'twenty-shared/application'; +import { type PermissionFlagType } from 'twenty-shared/constants'; + +export type RoleConfig = Omit< + RoleManifest, + 'objectPermissions' | 'fieldPermissions' | 'permissionFlags' +> & { + objectPermissions?: Omit[]; + fieldPermissions?: Omit[]; + permissionFlags?: PermissionFlagType[]; +}; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest-migration.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest-migration.service.ts index 7d10513a19..29504c2fdf 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest-migration.service.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest-migration.service.ts @@ -1,6 +1,6 @@ import { Injectable, Logger } from '@nestjs/common'; -import { type Manifest, type RoleManifest } from 'twenty-shared/application'; +import { type Manifest } from 'twenty-shared/application'; import { ALL_METADATA_NAME } from 'twenty-shared/metadata'; import { isDefined } from 'twenty-shared/utils'; @@ -15,9 +15,6 @@ import { computeApplicationManifestAllUniversalFlatEntityMaps } from 'src/engine import { getApplicationSubAllFlatEntityMaps } from 'src/engine/core-modules/application/application-manifest/utils/get-application-sub-all-flat-entity-maps.util'; import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; import { getMetadataFlatEntityMapsKey } from 'src/engine/metadata-modules/flat-entity/utils/get-metadata-flat-entity-maps-key.util'; -import { FieldPermissionService } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.service'; -import { ObjectPermissionService } from 'src/engine/metadata-modules/object-permission/object-permission.service'; -import { PermissionFlagService } from 'src/engine/metadata-modules/permission-flag/permission-flag.service'; import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception'; @@ -34,9 +31,6 @@ export class ApplicationManifestMigrationService { private readonly workspaceCacheService: WorkspaceCacheService, private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, private readonly applicationService: ApplicationService, - private readonly objectPermissionService: ObjectPermissionService, - private readonly fieldPermissionService: FieldPermissionService, - private readonly permissionFlagService: PermissionFlagService, ) {} async syncMetadataFromManifest({ @@ -119,7 +113,7 @@ export class ApplicationManifestMigrationService { `Metadata migration completed for application ${ownerFlatApplication.universalIdentifier}`, ); - await this.syncRolePermissionsAndDefaultRole({ + await this.syncDefaultRoleAndSettingsCustomTab({ manifest, workspaceId, ownerFlatApplication, @@ -131,13 +125,7 @@ export class ApplicationManifestMigrationService { }; } - /** - * @deprecated should be remove once below issues are resolved: - * - [objectPermission](https://github.com/twentyhq/core-team-issues/issues/2223) - * - [fieldPermission](https://github.com/twentyhq/core-team-issues/issues/2224) - * - [permissionFlag](https://github.com/twentyhq/core-team-issues/issues/2225) - */ - private async syncRolePermissionsAndDefaultRole({ + private async syncDefaultRoleAndSettingsCustomTab({ manifest, workspaceId, ownerFlatApplication, @@ -148,13 +136,9 @@ export class ApplicationManifestMigrationService { }) { const { flatRoleMaps: refreshedFlatRoleMaps, - flatObjectMetadataMaps: refreshedFlatObjectMetadataMaps, - flatFieldMetadataMaps: refreshedFlatFieldMetadataMaps, flatFrontComponentMaps: refreshedFlatFrontComponentMaps, } = await this.workspaceCacheService.getOrRecompute(workspaceId, [ 'flatRoleMaps', - 'flatObjectMetadataMaps', - 'flatFieldMetadataMaps', 'flatFrontComponentMaps', ]); @@ -173,14 +157,6 @@ export class ApplicationManifestMigrationService { ); } - await this.syncApplicationRolePermissions({ - role, - workspaceId, - roleId: flatRole.id, - refreshedFlatObjectMetadataMaps, - refreshedFlatFieldMetadataMaps, - }); - if ( role.universalIdentifier === manifest.application.defaultRoleUniversalIdentifier @@ -216,126 +192,4 @@ export class ApplicationManifestMigrationService { ...(isDefined(defaultRoleId) ? { defaultRoleId } : {}), }); } - - private async syncApplicationRolePermissions({ - role, - workspaceId, - roleId, - refreshedFlatObjectMetadataMaps, - refreshedFlatFieldMetadataMaps, - }: { - role: RoleManifest; - workspaceId: string; - roleId: string; - refreshedFlatObjectMetadataMaps: Awaited< - ReturnType - >['flatObjectMetadataMaps']; - refreshedFlatFieldMetadataMaps: Awaited< - ReturnType - >['flatFieldMetadataMaps']; - }) { - if ( - (role.objectPermissions ?? []).length > 0 || - (role.fieldPermissions ?? []).length > 0 - ) { - const formattedObjectPermissions = role.objectPermissions - ?.map((permission) => { - const flatObjectMetadata = findFlatEntityByUniversalIdentifier({ - flatEntityMaps: refreshedFlatObjectMetadataMaps, - universalIdentifier: permission.objectUniversalIdentifier, - }); - - if (!isDefined(flatObjectMetadata)) { - throw new ApplicationException( - `Failed to find object with universalIdentifier ${permission.objectUniversalIdentifier}`, - ApplicationExceptionCode.OBJECT_NOT_FOUND, - ); - } - - return { - ...permission, - objectMetadataId: flatObjectMetadata.id, - }; - }) - .filter( - ( - permission, - ): permission is typeof permission & { objectMetadataId: string } => - isDefined(permission.objectMetadataId), - ); - - if (isDefined(formattedObjectPermissions)) { - await this.objectPermissionService.upsertObjectPermissions({ - workspaceId, - input: { - roleId, - objectPermissions: formattedObjectPermissions, - }, - }); - } - - const formattedFieldPermissions = role.fieldPermissions - ?.map((permission) => { - const flatObjectMetadata = findFlatEntityByUniversalIdentifier({ - flatEntityMaps: refreshedFlatObjectMetadataMaps, - universalIdentifier: permission.objectUniversalIdentifier, - }); - - if (!isDefined(flatObjectMetadata)) { - throw new ApplicationException( - `Failed to find object with universalIdentifier ${permission.objectUniversalIdentifier}`, - ApplicationExceptionCode.OBJECT_NOT_FOUND, - ); - } - - const flatFieldMetadata = findFlatEntityByUniversalIdentifier({ - flatEntityMaps: refreshedFlatFieldMetadataMaps, - universalIdentifier: permission.fieldUniversalIdentifier, - }); - - if (!isDefined(flatFieldMetadata)) { - throw new ApplicationException( - `Failed to find field with universalIdentifier ${permission.fieldUniversalIdentifier}`, - ApplicationExceptionCode.FIELD_NOT_FOUND, - ); - } - - return { - ...permission, - objectMetadataId: flatObjectMetadata.id, - fieldMetadataId: flatFieldMetadata.id, - }; - }) - .filter( - ( - permission, - ): permission is typeof permission & { - objectMetadataId: string; - fieldMetadataId: string; - } => - isDefined(permission.objectMetadataId) && - isDefined(permission.fieldMetadataId), - ); - - if (isDefined(formattedFieldPermissions)) { - await this.fieldPermissionService.upsertFieldPermissions({ - workspaceId, - input: { - roleId, - fieldPermissions: formattedFieldPermissions, - }, - }); - } - } - - if (isDefined(role.permissionFlags) && role.permissionFlags.length > 0) { - await this.permissionFlagService.upsertPermissionFlags({ - workspaceId, - input: { - roleId, - permissionFlagKeys: role.permissionFlags, - }, - }); - } - } } diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest.module.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest.module.ts index c70b229012..1d3d9752a1 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest.module.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/application-manifest.module.ts @@ -7,8 +7,6 @@ import { ApplicationSyncService } from 'src/engine/core-modules/application/appl import { ApplicationVariableEntityModule } from 'src/engine/core-modules/application/application-variable/application-variable.module'; import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module'; -import { ObjectPermissionModule } from 'src/engine/metadata-modules/object-permission/object-permission.module'; -import { PermissionFlagModule } from 'src/engine/metadata-modules/permission-flag/permission-flag.module'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor'; @@ -21,8 +19,6 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace ApplicationVariableEntityModule, FeatureFlagModule, FileStorageModule, - ObjectPermissionModule, - PermissionFlagModule, PermissionsModule, WorkspaceCacheModule, WorkspaceMigrationModule, diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-field-permission-manifest-to-universal-flat-field-permission.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-field-permission-manifest-to-universal-flat-field-permission.util.ts new file mode 100644 index 0000000000..1bc066732c --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-field-permission-manifest-to-universal-flat-field-permission.util.ts @@ -0,0 +1,28 @@ +import { type FieldPermissionManifest } from 'twenty-shared/application'; +import { type UniversalFlatFieldPermission } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-field-permission.type'; + +export const fromFieldPermissionManifestToUniversalFlatFieldPermission = ({ + fieldPermissionManifest, + roleUniversalIdentifier, + applicationUniversalIdentifier, + now, +}: { + fieldPermissionManifest: FieldPermissionManifest; + roleUniversalIdentifier: string; + applicationUniversalIdentifier: string; + now: string; +}): UniversalFlatFieldPermission => { + return { + universalIdentifier: fieldPermissionManifest.universalIdentifier, + applicationUniversalIdentifier, + roleUniversalIdentifier, + objectMetadataUniversalIdentifier: + fieldPermissionManifest.objectUniversalIdentifier, + fieldMetadataUniversalIdentifier: + fieldPermissionManifest.fieldUniversalIdentifier, + canReadFieldValue: fieldPermissionManifest.canReadFieldValue ?? null, + canUpdateFieldValue: fieldPermissionManifest.canUpdateFieldValue ?? null, + createdAt: now, + updatedAt: now, + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-object-permission-manifest-to-universal-flat-object-permission.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-object-permission-manifest-to-universal-flat-object-permission.util.ts new file mode 100644 index 0000000000..e556e3dfda --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-object-permission-manifest-to-universal-flat-object-permission.util.ts @@ -0,0 +1,29 @@ +import { type ObjectPermissionManifest } from 'twenty-shared/application'; +import { type UniversalFlatObjectPermission } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-object-permission.type'; + +export const fromObjectPermissionManifestToUniversalFlatObjectPermission = ({ + objectPermissionManifest, + roleUniversalIdentifier, + applicationUniversalIdentifier, + now, +}: { + objectPermissionManifest: ObjectPermissionManifest; + roleUniversalIdentifier: string; + applicationUniversalIdentifier: string; + now: string; +}): UniversalFlatObjectPermission => { + return { + universalIdentifier: objectPermissionManifest.universalIdentifier, + applicationUniversalIdentifier, + roleUniversalIdentifier, + objectMetadataUniversalIdentifier: + objectPermissionManifest.objectUniversalIdentifier, + canReadObjectRecords: objectPermissionManifest.canReadObjectRecords, + canUpdateObjectRecords: objectPermissionManifest.canUpdateObjectRecords, + canSoftDeleteObjectRecords: + objectPermissionManifest.canSoftDeleteObjectRecords, + canDestroyObjectRecords: objectPermissionManifest.canDestroyObjectRecords, + createdAt: now, + updatedAt: now, + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-permission-flag.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-permission-flag.util.ts new file mode 100644 index 0000000000..e8a8e65c70 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-permission-flag.util.ts @@ -0,0 +1,23 @@ +import { type UniversalFlatPermissionFlag } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-permission-flag.type'; +import { PermissionFlagManifest } from 'twenty-shared/application'; + +export const fromPermissionFlagToUniversalFlatPermissionFlag = ({ + permissionFlag, + roleUniversalIdentifier, + applicationUniversalIdentifier, + now, +}: { + permissionFlag: PermissionFlagManifest; + roleUniversalIdentifier: string; + applicationUniversalIdentifier: string; + now: string; +}): UniversalFlatPermissionFlag => { + return { + universalIdentifier: permissionFlag.universalIdentifier, + applicationUniversalIdentifier, + roleUniversalIdentifier, + flag: permissionFlag.flag, + createdAt: now, + updatedAt: now, + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util.ts b/packages/twenty-server/src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util.ts index 23c9fd1667..d17bc5cca4 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application-manifest/utils/compute-application-manifest-all-universal-flat-entity-maps.util.ts @@ -4,13 +4,16 @@ import { isDefined } from 'twenty-shared/utils'; import { fromCommandMenuItemManifestToUniversalFlatCommandMenuItem } from 'src/engine/core-modules/application/application-manifest/converters/from-command-menu-item-manifest-to-universal-flat-command-menu-item.util'; import { fromFieldManifestToUniversalFlatFieldMetadata } from 'src/engine/core-modules/application/application-manifest/converters/from-field-manifest-to-universal-flat-field-metadata.util'; +import { fromFieldPermissionManifestToUniversalFlatFieldPermission } from 'src/engine/core-modules/application/application-manifest/converters/from-field-permission-manifest-to-universal-flat-field-permission.util'; import { fromFrontComponentManifestToUniversalFlatFrontComponent } from 'src/engine/core-modules/application/application-manifest/converters/from-front-component-manifest-to-universal-flat-front-component.util'; import { fromLogicFunctionManifestToUniversalFlatLogicFunction } from 'src/engine/core-modules/application/application-manifest/converters/from-logic-function-manifest-to-universal-flat-logic-function.util'; import { fromNavigationMenuItemManifestToUniversalFlatNavigationMenuItem } from 'src/engine/core-modules/application/application-manifest/converters/from-navigation-menu-item-manifest-to-universal-flat-navigation-menu-item.util'; import { fromObjectManifestToUniversalFlatObjectMetadata } from 'src/engine/core-modules/application/application-manifest/converters/from-object-manifest-to-universal-flat-object-metadata.util'; +import { fromObjectPermissionManifestToUniversalFlatObjectPermission } from 'src/engine/core-modules/application/application-manifest/converters/from-object-permission-manifest-to-universal-flat-object-permission.util'; import { fromPageLayoutManifestToUniversalFlatPageLayout } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-manifest-to-universal-flat-page-layout.util'; import { fromPageLayoutTabManifestToUniversalFlatPageLayoutTab } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-tab-manifest-to-universal-flat-page-layout-tab.util'; import { fromPageLayoutWidgetManifestToUniversalFlatPageLayoutWidget } from 'src/engine/core-modules/application/application-manifest/converters/from-page-layout-widget-manifest-to-universal-flat-page-layout-widget.util'; +import { fromPermissionFlagToUniversalFlatPermissionFlag } from 'src/engine/core-modules/application/application-manifest/converters/from-permission-flag-to-universal-flat-permission-flag.util'; import { fromRoleManifestToUniversalFlatRole } from 'src/engine/core-modules/application/application-manifest/converters/from-role-manifest-to-universal-flat-role.util'; import { fromSkillManifestToUniversalFlatSkill } from 'src/engine/core-modules/application/application-manifest/converters/from-skill-manifest-to-universal-flat-skill.util'; import { computeSearchVectorUniversalSettingsFromObjectManifest } from 'src/engine/core-modules/application/application-manifest/utils/compute-search-vector-universal-settings-from-object-manifest.util'; @@ -144,6 +147,47 @@ export const computeApplicationManifestAllUniversalFlatEntityMaps = ({ }), universalFlatEntityMapsToMutate: allUniversalFlatEntityMaps.flatRoleMaps, }); + for (const objectPermissionManifest of roleManifest.objectPermissions ?? + []) { + addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({ + universalFlatEntity: + fromObjectPermissionManifestToUniversalFlatObjectPermission({ + objectPermissionManifest, + roleUniversalIdentifier: roleManifest.universalIdentifier, + applicationUniversalIdentifier, + now, + }), + universalFlatEntityMapsToMutate: + allUniversalFlatEntityMaps.flatObjectPermissionMaps, + }); + } + + for (const fieldPermissionManifest of roleManifest.fieldPermissions ?? []) { + addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({ + universalFlatEntity: + fromFieldPermissionManifestToUniversalFlatFieldPermission({ + fieldPermissionManifest, + roleUniversalIdentifier: roleManifest.universalIdentifier, + applicationUniversalIdentifier, + now, + }), + universalFlatEntityMapsToMutate: + allUniversalFlatEntityMaps.flatFieldPermissionMaps, + }); + } + + for (const permissionFlag of roleManifest.permissionFlags ?? []) { + addUniversalFlatEntityToUniversalFlatEntityMapsThroughMutationOrThrow({ + universalFlatEntity: fromPermissionFlagToUniversalFlatPermissionFlag({ + permissionFlag, + roleUniversalIdentifier: roleManifest.universalIdentifier, + applicationUniversalIdentifier, + now, + }), + universalFlatEntityMapsToMutate: + allUniversalFlatEntityMaps.flatPermissionFlagMaps, + }); + } } for (const skillManifest of manifest.skills ?? []) { diff --git a/packages/twenty-shared/src/application/index.ts b/packages/twenty-shared/src/application/index.ts index e8f2c026db..9c2f5353c3 100644 --- a/packages/twenty-shared/src/application/index.ts +++ b/packages/twenty-shared/src/application/index.ts @@ -47,6 +47,7 @@ export type { export type { ObjectPermissionManifest, FieldPermissionManifest, + PermissionFlagManifest, RoleManifest, } from './roleManifestType'; export type { ServerVariables } from './server-variables.type'; diff --git a/packages/twenty-shared/src/application/roleManifestType.ts b/packages/twenty-shared/src/application/roleManifestType.ts index 8c7737216a..3cc75129e3 100644 --- a/packages/twenty-shared/src/application/roleManifestType.ts +++ b/packages/twenty-shared/src/application/roleManifestType.ts @@ -1,7 +1,7 @@ import { type PermissionFlagType } from '@/constants'; import { type SyncableEntityOptions } from '@/application/syncableEntityOptionsType'; -export type ObjectPermissionManifest = { +export type ObjectPermissionManifest = SyncableEntityOptions & { objectUniversalIdentifier: string; canReadObjectRecords?: boolean; canUpdateObjectRecords?: boolean; @@ -9,13 +9,17 @@ export type ObjectPermissionManifest = { canDestroyObjectRecords?: boolean; }; -export type FieldPermissionManifest = { +export type FieldPermissionManifest = SyncableEntityOptions & { objectUniversalIdentifier: string; fieldUniversalIdentifier: string; canReadFieldValue?: boolean; canUpdateFieldValue?: boolean; }; +export type PermissionFlagManifest = SyncableEntityOptions & { + flag: PermissionFlagType; +}; + export type RoleManifest = SyncableEntityOptions & { label: string; description?: string; @@ -31,5 +35,5 @@ export type RoleManifest = SyncableEntityOptions & { canBeAssignedToApiKeys?: boolean; objectPermissions?: ObjectPermissionManifest[]; fieldPermissions?: FieldPermissionManifest[]; - permissionFlags?: PermissionFlagType[]; + permissionFlags?: PermissionFlagManifest[]; };