From ae889bfa9937438d59b8df2e0059d9f11d51f78a Mon Sep 17 00:00:00 2001 From: ehconitin Date: Fri, 8 May 2026 03:12:30 +0530 Subject: [PATCH] Polish permission flag definition foundation --- .../src/metadata/generated/schema.graphql | 6 +- .../src/metadata/generated/schema.ts | 8 +- .../src/metadata/generated/types.ts | 6 +- .../src/generated-metadata/graphql.ts | 6 +- .../workspace.command-runner.ts | 4 +- ...mission-flag-definition-syncable-entity.ts | 14 +- ...configuration-by-metadata-name.constant.ts | 2 +- ...definition-editable-properties.constant.ts | 2 +- ...rmission-flag-definition-to-create.util.ts | 2 +- ...-to-permission-flag-definition-dto.util.ts | 2 +- ...lag-definition-permission-type.constant.ts | 7 + ...create-permission-flag-definition.input.ts | 10 +- .../dtos/permission-flag-definition.dto.ts | 10 +- ...update-permission-flag-definition.input.ts | 10 +- .../permission-flag-definition.entity.ts | 3 +- .../permission-flag-definition.exception.ts | 6 +- .../permission-flag-definition.module.ts | 7 - .../permission-flag-definition.service.ts | 72 +++------ ...tion-graphql-api-exception-handler.util.ts | 2 +- ...definition-graphql-api-exception.filter.ts | 5 +- ...ard-permission-flag-definition.constant.ts | 54 +++---- ...sion-flag-definition-flat-metadata.util.ts | 2 +- ...-flag-definition-validator.service.spec.ts | 142 +++++++++++++++++- ...ssion-flag-definition-validator.service.ts | 25 ++- 24 files changed, 267 insertions(+), 140 deletions(-) create mode 100644 packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/constants/permission-flag-definition-permission-type.constant.ts diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql index f1771b3c9b7..ee4abddadec 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.graphql +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.graphql @@ -2847,7 +2847,7 @@ type PermissionFlagDefinition { label: String! description: String iconKey: String - category: String! + permissionType: String! isRelevantForAgents: Boolean! isRelevantForUsers: Boolean! isRelevantForApiKeys: Boolean! @@ -4232,7 +4232,7 @@ input CreatePermissionFlagDefinitionInput { label: String! description: String iconKey: String - category: String! + permissionType: String! isRelevantForAgents: Boolean isRelevantForUsers: Boolean isRelevantForApiKeys: Boolean @@ -4250,7 +4250,7 @@ input UpdatePermissionFlagDefinitionInputUpdates { label: String description: String iconKey: String - category: String + permissionType: String isRelevantForAgents: Boolean isRelevantForUsers: Boolean isRelevantForApiKeys: Boolean diff --git a/packages/twenty-client-sdk/src/metadata/generated/schema.ts b/packages/twenty-client-sdk/src/metadata/generated/schema.ts index 6e34927d195..3144137f5c0 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/schema.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/schema.ts @@ -2497,7 +2497,7 @@ export interface PermissionFlagDefinition { label: Scalars['String'] description?: Scalars['String'] iconKey?: Scalars['String'] - category: Scalars['String'] + permissionType: Scalars['String'] isRelevantForAgents: Scalars['Boolean'] isRelevantForUsers: Scalars['Boolean'] isRelevantForApiKeys: Scalars['Boolean'] @@ -5532,7 +5532,7 @@ export interface PermissionFlagDefinitionGenqlSelection{ label?: boolean | number description?: boolean | number iconKey?: boolean | number - category?: boolean | number + permissionType?: boolean | number isRelevantForAgents?: boolean | number isRelevantForUsers?: boolean | number isRelevantForApiKeys?: boolean | number @@ -6263,7 +6263,7 @@ update: UpdateWebhookInputUpdates} export interface UpdateWebhookInputUpdates {targetUrl?: (Scalars['String'] | null),operations?: (Scalars['String'][] | null),description?: (Scalars['String'] | null),secret?: (Scalars['String'] | null)} -export interface CreatePermissionFlagDefinitionInput {id?: (Scalars['UUID'] | null),universalIdentifier?: (Scalars['UUID'] | null),key: Scalars['String'],label: Scalars['String'],description?: (Scalars['String'] | null),iconKey?: (Scalars['String'] | null),category: Scalars['String'],isRelevantForAgents?: (Scalars['Boolean'] | null),isRelevantForUsers?: (Scalars['Boolean'] | null),isRelevantForApiKeys?: (Scalars['Boolean'] | null)} +export interface CreatePermissionFlagDefinitionInput {id?: (Scalars['UUID'] | null),universalIdentifier?: (Scalars['UUID'] | null),key: Scalars['String'],label: Scalars['String'],description?: (Scalars['String'] | null),iconKey?: (Scalars['String'] | null),permissionType: Scalars['String'],isRelevantForAgents?: (Scalars['Boolean'] | null),isRelevantForUsers?: (Scalars['Boolean'] | null),isRelevantForApiKeys?: (Scalars['Boolean'] | null)} export interface UpdatePermissionFlagDefinitionInput { /** The id of the permission flag definition to update */ @@ -6271,7 +6271,7 @@ id: Scalars['UUID'], /** The fields to update */ update: UpdatePermissionFlagDefinitionInputUpdates} -export interface UpdatePermissionFlagDefinitionInputUpdates {label?: (Scalars['String'] | null),description?: (Scalars['String'] | null),iconKey?: (Scalars['String'] | null),category?: (Scalars['String'] | null),isRelevantForAgents?: (Scalars['Boolean'] | null),isRelevantForUsers?: (Scalars['Boolean'] | null),isRelevantForApiKeys?: (Scalars['Boolean'] | null)} +export interface UpdatePermissionFlagDefinitionInputUpdates {label?: (Scalars['String'] | null),description?: (Scalars['String'] | null),iconKey?: (Scalars['String'] | null),permissionType?: (Scalars['String'] | null),isRelevantForAgents?: (Scalars['Boolean'] | null),isRelevantForUsers?: (Scalars['Boolean'] | null),isRelevantForApiKeys?: (Scalars['Boolean'] | null)} export interface CreateSkillInput {id?: (Scalars['UUID'] | null),name: Scalars['String'],label: Scalars['String'],icon?: (Scalars['String'] | null),description?: (Scalars['String'] | null),content: Scalars['String']} diff --git a/packages/twenty-client-sdk/src/metadata/generated/types.ts b/packages/twenty-client-sdk/src/metadata/generated/types.ts index 6142adedc5c..35bf120de99 100644 --- a/packages/twenty-client-sdk/src/metadata/generated/types.ts +++ b/packages/twenty-client-sdk/src/metadata/generated/types.ts @@ -5659,7 +5659,7 @@ export default { "iconKey": [ 1 ], - "category": [ + "permissionType": [ 1 ], "isRelevantForAgents": [ @@ -10751,7 +10751,7 @@ export default { "iconKey": [ 1 ], - "category": [ + "permissionType": [ 1 ], "isRelevantForAgents": [ @@ -10788,7 +10788,7 @@ export default { "iconKey": [ 1 ], - "category": [ + "permissionType": [ 1 ], "isRelevantForAgents": [ diff --git a/packages/twenty-front/src/generated-metadata/graphql.ts b/packages/twenty-front/src/generated-metadata/graphql.ts index 4c2fe2117fe..1db031b85c5 100644 --- a/packages/twenty-front/src/generated-metadata/graphql.ts +++ b/packages/twenty-front/src/generated-metadata/graphql.ts @@ -1150,7 +1150,6 @@ export type CreatePageLayoutWidgetInput = { }; export type CreatePermissionFlagDefinitionInput = { - category: Scalars['String']; description?: InputMaybe; iconKey?: InputMaybe; id?: InputMaybe; @@ -1159,6 +1158,7 @@ export type CreatePermissionFlagDefinitionInput = { isRelevantForUsers?: InputMaybe; key: Scalars['String']; label: Scalars['String']; + permissionType: Scalars['String']; universalIdentifier?: InputMaybe; }; @@ -3938,7 +3938,6 @@ export type PermissionFlag = { export type PermissionFlagDefinition = { __typename?: 'PermissionFlagDefinition'; applicationId: Scalars['UUID']; - category: Scalars['String']; createdAt: Scalars['DateTime']; description?: Maybe; iconKey?: Maybe; @@ -3949,6 +3948,7 @@ export type PermissionFlagDefinition = { isRelevantForUsers: Scalars['Boolean']; key: Scalars['String']; label: Scalars['String']; + permissionType: Scalars['String']; universalIdentifier: Scalars['UUID']; updatedAt: Scalars['DateTime']; }; @@ -5227,13 +5227,13 @@ export type UpdatePermissionFlagDefinitionInput = { }; export type UpdatePermissionFlagDefinitionInputUpdates = { - category?: InputMaybe; description?: InputMaybe; iconKey?: InputMaybe; isRelevantForAgents?: InputMaybe; isRelevantForApiKeys?: InputMaybe; isRelevantForUsers?: InputMaybe; label?: InputMaybe; + permissionType?: InputMaybe; }; export type UpdateRoleInput = { diff --git a/packages/twenty-server/src/database/commands/command-runners/workspace.command-runner.ts b/packages/twenty-server/src/database/commands/command-runners/workspace.command-runner.ts index 2c49db86732..8d0d94a4af0 100644 --- a/packages/twenty-server/src/database/commands/command-runners/workspace.command-runner.ts +++ b/packages/twenty-server/src/database/commands/command-runners/workspace.command-runner.ts @@ -130,7 +130,9 @@ export abstract class WorkspaceCommandRunner< }); if (report.fail.length > 0) { - throw new Error(`Command failed for ${report.fail.length} workspace(s)`); + throw new Error( + `Command failed for ${report.fail.length} workspace(s)`, + ); } this.logger.log(chalk.blue('Command completed!')); diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1799000000000-permission-flag-definition-syncable-entity.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1799000000000-permission-flag-definition-syncable-entity.ts index fa735ecfa5a..c71c11631c0 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1799000000000-permission-flag-definition-syncable-entity.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/2-4/2-4-instance-command-fast-1799000000000-permission-flag-definition-syncable-entity.ts @@ -18,7 +18,7 @@ export class PermissionFlagDefinitionSyncableEntityFastInstanceCommand "label" varchar NOT NULL, "description" text, "iconKey" varchar, - "category" varchar NOT NULL, + "permissionType" varchar NOT NULL, "isRelevantForAgents" boolean NOT NULL DEFAULT false, "isRelevantForUsers" boolean NOT NULL DEFAULT false, "isRelevantForApiKeys" boolean NOT NULL DEFAULT false, @@ -55,17 +55,19 @@ export class PermissionFlagDefinitionSyncableEntityFastInstanceCommand public async down(queryRunner: QueryRunner): Promise { await queryRunner.query( - `ALTER TABLE "core"."permissionFlagDefinition" DROP CONSTRAINT "FK_PERMISSION_FLAG_DEFINITION_APPLICATION"`, + `ALTER TABLE "core"."permissionFlagDefinition" DROP CONSTRAINT IF EXISTS "FK_PERMISSION_FLAG_DEFINITION_APPLICATION"`, ); await queryRunner.query( - `ALTER TABLE "core"."permissionFlagDefinition" DROP CONSTRAINT "FK_PERMISSION_FLAG_DEFINITION_WORKSPACE"`, + `ALTER TABLE "core"."permissionFlagDefinition" DROP CONSTRAINT IF EXISTS "FK_PERMISSION_FLAG_DEFINITION_WORKSPACE"`, ); await queryRunner.query( - `DROP INDEX "core"."IDX_PERMISSION_FLAG_DEFINITION_WORKSPACE_UNIVERSAL_IDENTIFIER"`, + `DROP INDEX IF EXISTS "core"."IDX_PERMISSION_FLAG_DEFINITION_WORKSPACE_UNIVERSAL_IDENTIFIER"`, ); await queryRunner.query( - `DROP INDEX "core"."IDX_PERMISSION_FLAG_DEFINITION_APPLICATION_ID"`, + `DROP INDEX IF EXISTS "core"."IDX_PERMISSION_FLAG_DEFINITION_APPLICATION_ID"`, + ); + await queryRunner.query( + `DROP TABLE IF EXISTS "core"."permissionFlagDefinition"`, ); - await queryRunner.query(`DROP TABLE "core"."permissionFlagDefinition"`); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts index 5d88a294993..8882d6823aa 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/constant/all-entity-properties-configuration-by-metadata-name.constant.ts @@ -1224,7 +1224,7 @@ export const ALL_ENTITY_PROPERTIES_CONFIGURATION_BY_METADATA_NAME = { toStringify: false, universalProperty: undefined, }, - category: { + permissionType: { toCompare: true, toStringify: false, universalProperty: undefined, diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag-definition/constants/flat-permission-flag-definition-editable-properties.constant.ts b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag-definition/constants/flat-permission-flag-definition-editable-properties.constant.ts index fe7cd6a49af..5ced8b3929c 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag-definition/constants/flat-permission-flag-definition-editable-properties.constant.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag-definition/constants/flat-permission-flag-definition-editable-properties.constant.ts @@ -4,7 +4,7 @@ export const FLAT_PERMISSION_FLAG_DEFINITION_EDITABLE_PROPERTIES = [ 'label', 'description', 'iconKey', - 'category', + 'permissionType', 'isRelevantForAgents', 'isRelevantForUsers', 'isRelevantForApiKeys', diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag-definition/utils/from-create-permission-flag-definition-input-to-flat-permission-flag-definition-to-create.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag-definition/utils/from-create-permission-flag-definition-input-to-flat-permission-flag-definition-to-create.util.ts index 2590543133e..f10c23ea44d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag-definition/utils/from-create-permission-flag-definition-input-to-flat-permission-flag-definition-to-create.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag-definition/utils/from-create-permission-flag-definition-input-to-flat-permission-flag-definition-to-create.util.ts @@ -34,7 +34,7 @@ export const fromCreatePermissionFlagDefinitionInputToFlatPermissionFlagDefiniti label, description: description ?? null, iconKey: iconKey ?? null, - category: createPermissionFlagDefinitionInput.category, + permissionType: createPermissionFlagDefinitionInput.permissionType, isRelevantForAgents: createPermissionFlagDefinitionInput.isRelevantForAgents ?? false, isRelevantForUsers: diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag-definition/utils/from-flat-permission-flag-definition-to-permission-flag-definition-dto.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag-definition/utils/from-flat-permission-flag-definition-to-permission-flag-definition-dto.util.ts index e1ad4d293c0..0cd9255b6fc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag-definition/utils/from-flat-permission-flag-definition-to-permission-flag-definition-dto.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-permission-flag-definition/utils/from-flat-permission-flag-definition-to-permission-flag-definition-dto.util.ts @@ -10,7 +10,7 @@ export const fromFlatPermissionFlagDefinitionToPermissionFlagDefinitionDto = ( label: flatDefinition.label, description: flatDefinition.description, iconKey: flatDefinition.iconKey, - category: flatDefinition.category, + permissionType: flatDefinition.permissionType, isRelevantForAgents: flatDefinition.isRelevantForAgents, isRelevantForUsers: flatDefinition.isRelevantForUsers, isRelevantForApiKeys: flatDefinition.isRelevantForApiKeys, diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/constants/permission-flag-definition-permission-type.constant.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/constants/permission-flag-definition-permission-type.constant.ts new file mode 100644 index 00000000000..1922c66a1ba --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/constants/permission-flag-definition-permission-type.constant.ts @@ -0,0 +1,7 @@ +export const PERMISSION_FLAG_DEFINITION_PERMISSION_TYPES = [ + 'settings', + 'tool', +] as const; + +export type PermissionFlagDefinitionPermissionType = + (typeof PERMISSION_FLAG_DEFINITION_PERMISSION_TYPES)[number]; diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/dtos/create-permission-flag-definition.input.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/dtos/create-permission-flag-definition.input.ts index 59ed990fa77..df299786f37 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/dtos/create-permission-flag-definition.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/dtos/create-permission-flag-definition.input.ts @@ -10,6 +10,10 @@ import { } from 'class-validator'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { + PERMISSION_FLAG_DEFINITION_PERMISSION_TYPES, + type PermissionFlagDefinitionPermissionType, +} from 'src/engine/metadata-modules/permission-flag-definition/constants/permission-flag-definition-permission-type.constant'; @InputType() export class CreatePermissionFlagDefinitionInput { @@ -43,9 +47,9 @@ export class CreatePermissionFlagDefinitionInput { @Field({ nullable: true }) iconKey?: string; - @IsIn(['settings', 'tool']) - @Field() - category: 'settings' | 'tool'; + @IsIn(PERMISSION_FLAG_DEFINITION_PERMISSION_TYPES) + @Field(() => String) + permissionType: PermissionFlagDefinitionPermissionType; @IsBoolean() @IsOptional() diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/dtos/permission-flag-definition.dto.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/dtos/permission-flag-definition.dto.ts index 0ac3b7867bf..2553f4e0740 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/dtos/permission-flag-definition.dto.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/dtos/permission-flag-definition.dto.ts @@ -11,6 +11,10 @@ import { } from 'class-validator'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { + PERMISSION_FLAG_DEFINITION_PERMISSION_TYPES, + type PermissionFlagDefinitionPermissionType, +} from 'src/engine/metadata-modules/permission-flag-definition/constants/permission-flag-definition-permission-type.constant'; @ObjectType('PermissionFlagDefinition') export class PermissionFlagDefinitionDTO { @@ -44,9 +48,9 @@ export class PermissionFlagDefinitionDTO { @Field(() => String, { nullable: true }) iconKey: string | null; - @IsIn(['settings', 'tool']) - @Field() - category: 'settings' | 'tool'; + @IsIn(PERMISSION_FLAG_DEFINITION_PERMISSION_TYPES) + @Field(() => String) + permissionType: PermissionFlagDefinitionPermissionType; @IsBoolean() @Field() diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/dtos/update-permission-flag-definition.input.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/dtos/update-permission-flag-definition.input.ts index 8e112489922..485ccac96cd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/dtos/update-permission-flag-definition.input.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/dtos/update-permission-flag-definition.input.ts @@ -12,6 +12,10 @@ import { } from 'class-validator'; import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; +import { + PERMISSION_FLAG_DEFINITION_PERMISSION_TYPES, + type PermissionFlagDefinitionPermissionType, +} from 'src/engine/metadata-modules/permission-flag-definition/constants/permission-flag-definition-permission-type.constant'; @InputType() export class UpdatePermissionFlagDefinitionInputUpdates { @@ -31,9 +35,9 @@ export class UpdatePermissionFlagDefinitionInputUpdates { iconKey?: string; @IsOptional() - @IsIn(['settings', 'tool']) - @Field({ nullable: true }) - category?: 'settings' | 'tool'; + @IsIn(PERMISSION_FLAG_DEFINITION_PERMISSION_TYPES) + @Field(() => String, { nullable: true }) + permissionType?: PermissionFlagDefinitionPermissionType; @IsOptional() @IsBoolean() diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/entities/permission-flag-definition.entity.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/entities/permission-flag-definition.entity.ts index 52701c04bfd..65594340dcc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/entities/permission-flag-definition.entity.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/entities/permission-flag-definition.entity.ts @@ -8,6 +8,7 @@ import { UpdateDateColumn, } from 'typeorm'; +import { type PermissionFlagDefinitionPermissionType } from 'src/engine/metadata-modules/permission-flag-definition/constants/permission-flag-definition-permission-type.constant'; import { SyncableEntity } from 'src/engine/workspace-manager/types/syncable-entity.interface'; @Entity('permissionFlagDefinition') @@ -33,7 +34,7 @@ export class PermissionFlagDefinitionEntity extends SyncableEntity { iconKey: string | null; @Column({ nullable: false, type: 'varchar' }) - category: 'settings' | 'tool'; + permissionType: PermissionFlagDefinitionPermissionType; @Column({ nullable: false, type: 'boolean', default: false }) isRelevantForAgents: boolean; diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.exception.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.exception.ts index bd336b84299..9c54d7bc800 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.exception.ts @@ -9,7 +9,7 @@ export enum PermissionFlagDefinitionExceptionCode { PERMISSION_FLAG_DEFINITION_ALREADY_EXISTS = 'PERMISSION_FLAG_DEFINITION_ALREADY_EXISTS', INVALID_PERMISSION_FLAG_DEFINITION_INPUT = 'INVALID_PERMISSION_FLAG_DEFINITION_INPUT', INVALID_PERMISSION_FLAG_DEFINITION_KEY = 'INVALID_PERMISSION_FLAG_DEFINITION_KEY', - INVALID_PERMISSION_FLAG_DEFINITION_CATEGORY = 'INVALID_PERMISSION_FLAG_DEFINITION_CATEGORY', + INVALID_PERMISSION_FLAG_DEFINITION_PERMISSION_TYPE = 'INVALID_PERMISSION_FLAG_DEFINITION_PERMISSION_TYPE', PERMISSION_FLAG_DEFINITION_KEY_IMMUTABLE = 'PERMISSION_FLAG_DEFINITION_KEY_IMMUTABLE', PERMISSION_FLAG_DEFINITION_APPLICATION_IMMUTABLE = 'PERMISSION_FLAG_DEFINITION_APPLICATION_IMMUTABLE', PERMISSION_FLAG_DEFINITION_IS_STANDARD = 'PERMISSION_FLAG_DEFINITION_IS_STANDARD', @@ -28,8 +28,8 @@ const getPermissionFlagDefinitionExceptionUserFriendlyMessage = ( return msg`Invalid permission flag definition input.`; case PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_KEY: return msg`Invalid permission flag definition key.`; - case PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_CATEGORY: - return msg`Invalid permission flag definition category.`; + case PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_PERMISSION_TYPE: + return msg`Invalid permission flag definition permission type.`; case PermissionFlagDefinitionExceptionCode.PERMISSION_FLAG_DEFINITION_KEY_IMMUTABLE: return msg`Permission flag definition key cannot be changed.`; case PermissionFlagDefinitionExceptionCode.PERMISSION_FLAG_DEFINITION_APPLICATION_IMMUTABLE: diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.module.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.module.ts index aded3376d6b..1ea7d1d3ca3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.module.ts @@ -1,11 +1,8 @@ import { Module } from '@nestjs/common'; -import { TypeOrmModule } from '@nestjs/typeorm'; -import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; import { WorkspaceManyOrAllFlatEntityMapsCacheModule } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.module'; import { FlatPermissionFlagDefinitionModule } from 'src/engine/metadata-modules/flat-permission-flag-definition/flat-permission-flag-definition.module'; -import { PermissionFlagDefinitionEntity } from 'src/engine/metadata-modules/permission-flag-definition/entities/permission-flag-definition.entity'; import { PermissionFlagDefinitionResolver } from 'src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.resolver'; import { PermissionFlagDefinitionService } from 'src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.service'; import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module'; @@ -14,10 +11,6 @@ import { WorkspaceMigrationModule } from 'src/engine/workspace-manager/workspace @Module({ imports: [ - TypeOrmModule.forFeature([ - ApplicationEntity, - PermissionFlagDefinitionEntity, - ]), WorkspaceManyOrAllFlatEntityMapsCacheModule, WorkspaceMigrationModule, WorkspaceCacheStorageModule, diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.service.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.service.ts index 29ef2b0e455..f623ebe05c1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.service.ts @@ -1,60 +1,41 @@ import { Injectable } from '@nestjs/common'; -import { InjectRepository } from '@nestjs/typeorm'; import { isDefined } from 'twenty-shared/utils'; -import { Repository } from 'typeorm'; -import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity'; import { ApplicationService } from 'src/engine/core-modules/application/application.service'; import { WorkspaceManyOrAllFlatEntityMapsCacheService } from 'src/engine/metadata-modules/flat-entity/services/workspace-many-or-all-flat-entity-maps-cache.service'; import { findFlatEntityByIdInFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-id-in-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 { fromCreatePermissionFlagDefinitionInputToFlatPermissionFlagDefinitionToCreate } from 'src/engine/metadata-modules/flat-permission-flag-definition/utils/from-create-permission-flag-definition-input-to-flat-permission-flag-definition-to-create.util'; import { fromDeletePermissionFlagDefinitionInputToFlatPermissionFlagDefinitionOrThrow } from 'src/engine/metadata-modules/flat-permission-flag-definition/utils/from-delete-permission-flag-definition-input-to-flat-permission-flag-definition-or-throw.util'; import { fromFlatPermissionFlagDefinitionToPermissionFlagDefinitionDto } from 'src/engine/metadata-modules/flat-permission-flag-definition/utils/from-flat-permission-flag-definition-to-permission-flag-definition-dto.util'; -import { fromPermissionFlagDefinitionEntityToFlatPermissionFlagDefinition } from 'src/engine/metadata-modules/flat-permission-flag-definition/utils/from-permission-flag-definition-entity-to-flat-permission-flag-definition.util'; import { fromUpdatePermissionFlagDefinitionInputToFlatPermissionFlagDefinitionToUpdateOrThrow } from 'src/engine/metadata-modules/flat-permission-flag-definition/utils/from-update-permission-flag-definition-input-to-flat-permission-flag-definition-to-update-or-throw.util'; import { type CreatePermissionFlagDefinitionInput } from 'src/engine/metadata-modules/permission-flag-definition/dtos/create-permission-flag-definition.input'; import { type PermissionFlagDefinitionDTO } from 'src/engine/metadata-modules/permission-flag-definition/dtos/permission-flag-definition.dto'; import { type UpdatePermissionFlagDefinitionInput } from 'src/engine/metadata-modules/permission-flag-definition/dtos/update-permission-flag-definition.input'; -import { PermissionFlagDefinitionEntity } from 'src/engine/metadata-modules/permission-flag-definition/entities/permission-flag-definition.entity'; -import { createIdToUniversalIdentifierMap } from 'src/engine/workspace-cache/utils/create-id-to-universal-identifier-map.util'; import { WorkspaceMigrationBuilderException } from 'src/engine/workspace-manager/workspace-migration/exceptions/workspace-migration-builder-exception'; import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration/services/workspace-migration-validate-build-and-run-service'; @Injectable() export class PermissionFlagDefinitionService { constructor( - @InjectRepository(PermissionFlagDefinitionEntity) - private readonly permissionFlagDefinitionRepository: Repository, - @InjectRepository(ApplicationEntity) - private readonly applicationRepository: Repository, private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService, private readonly workspaceManyOrAllFlatEntityMapsCacheService: WorkspaceManyOrAllFlatEntityMapsCacheService, private readonly applicationService: ApplicationService, ) {} async findAll(workspaceId: string): Promise { - const [definitions, applications] = await Promise.all([ - this.permissionFlagDefinitionRepository.find({ - where: { workspaceId }, - order: { createdAt: 'ASC' }, - }), - this.applicationRepository.find({ - where: { workspaceId }, - select: ['id', 'universalIdentifier'], - }), - ]); + const { flatPermissionFlagDefinitionMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatPermissionFlagDefinitionMaps'], + }, + ); - const applicationIdToUniversalIdentifierMap = - createIdToUniversalIdentifierMap(applications); - - return definitions - .map((entity) => - fromPermissionFlagDefinitionEntityToFlatPermissionFlagDefinition({ - entity, - applicationIdToUniversalIdentifierMap, - }), - ) + return Object.values(flatPermissionFlagDefinitionMaps.byUniversalIdentifier) + .filter(isDefined) + .sort((a, b) => a.createdAt.localeCompare(b.createdAt)) .map(fromFlatPermissionFlagDefinitionToPermissionFlagDefinitionDto); } @@ -62,28 +43,25 @@ export class PermissionFlagDefinitionService { id: string, workspaceId: string, ): Promise { - const [definition, applications] = await Promise.all([ - this.permissionFlagDefinitionRepository.findOne({ - where: { id, workspaceId }, - }), - this.applicationRepository.find({ - where: { workspaceId }, - select: ['id', 'universalIdentifier'], - }), - ]); + const { flatPermissionFlagDefinitionMaps } = + await this.workspaceManyOrAllFlatEntityMapsCacheService.getOrRecomputeManyOrAllFlatEntityMaps( + { + workspaceId, + flatMapsKeys: ['flatPermissionFlagDefinitionMaps'], + }, + ); - if (!isDefined(definition)) { + const flatPermissionFlagDefinition = findFlatEntityByIdInFlatEntityMaps({ + flatEntityId: id, + flatEntityMaps: flatPermissionFlagDefinitionMaps, + }); + + if (!isDefined(flatPermissionFlagDefinition)) { return null; } - const applicationIdToUniversalIdentifierMap = - createIdToUniversalIdentifierMap(applications); - return fromFlatPermissionFlagDefinitionToPermissionFlagDefinitionDto( - fromPermissionFlagDefinitionEntityToFlatPermissionFlagDefinition({ - entity: definition, - applicationIdToUniversalIdentifierMap, - }), + flatPermissionFlagDefinition, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/utils/permission-flag-definition-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/utils/permission-flag-definition-graphql-api-exception-handler.util.ts index 275f505061e..b15aeb74edf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/utils/permission-flag-definition-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/utils/permission-flag-definition-graphql-api-exception-handler.util.ts @@ -24,7 +24,7 @@ export const permissionFlagDefinitionGraphqlApiExceptionHandler = ( throw new ForbiddenError(error); case PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_INPUT: case PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_KEY: - case PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_CATEGORY: + case PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_PERMISSION_TYPE: case PermissionFlagDefinitionExceptionCode.PERMISSION_FLAG_DEFINITION_KEY_IMMUTABLE: case PermissionFlagDefinitionExceptionCode.PERMISSION_FLAG_DEFINITION_APPLICATION_IMMUTABLE: case PermissionFlagDefinitionExceptionCode.PERMISSION_FLAG_DEFINITION_IN_USE: diff --git a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/utils/permission-flag-definition-graphql-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/utils/permission-flag-definition-graphql-api-exception.filter.ts index e9cb0696a97..386ecfb0088 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/utils/permission-flag-definition-graphql-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permission-flag-definition/utils/permission-flag-definition-graphql-api-exception.filter.ts @@ -13,10 +13,7 @@ import { permissionFlagDefinitionGraphqlApiExceptionHandler } from 'src/engine/m export class PermissionFlagDefinitionGraphqlApiExceptionFilter implements ExceptionFilter { - catch( - exception: PermissionFlagDefinitionException, - _host: ExecutionContext, - ) { + catch(exception: PermissionFlagDefinitionException, _host: ExecutionContext) { return permissionFlagDefinitionGraphqlApiExceptionHandler(exception); } } diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-permission-flag-definition.constant.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-permission-flag-definition.constant.ts index bd4ebd72cac..22a8f9972f1 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-permission-flag-definition.constant.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/constants/standard-permission-flag-definition.constant.ts @@ -1,5 +1,7 @@ import { type PermissionFlagType } from 'twenty-shared/constants'; +import { type PermissionFlagDefinitionPermissionType } from 'src/engine/metadata-modules/permission-flag-definition/constants/permission-flag-definition-permission-type.constant'; + // Each entry maps a PermissionFlagType enum key to a stable universalIdentifier // plus the metadata used to render the flag in the Roles UI. Adding a new // built-in flag requires: @@ -14,7 +16,7 @@ export type StandardPermissionFlagDefinitionMetadata = { label: string; description: string | null; iconKey: string | null; - category: 'settings' | 'tool'; + permissionType: PermissionFlagDefinitionPermissionType; isRelevantForAgents: boolean; isRelevantForUsers: boolean; isRelevantForApiKeys: boolean; @@ -26,7 +28,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'API Keys and Webhooks', description: 'Manage API keys and webhooks', iconKey: 'IconApi', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -36,7 +38,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Workspace', description: 'Manage workspace settings', iconKey: 'IconBuildingSkyscraper', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -46,7 +48,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Workspace Members', description: 'Manage workspace members', iconKey: 'IconUsers', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -56,7 +58,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Roles', description: 'Manage roles and permissions', iconKey: 'IconShieldLock', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -66,7 +68,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Data Model', description: 'Manage objects and fields', iconKey: 'IconHierarchy2', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -76,7 +78,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Security', description: 'Manage security settings', iconKey: 'IconLock', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -86,7 +88,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Workflows', description: 'Manage workflows and logic functions', iconKey: 'IconSettingsAutomation', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -96,7 +98,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Impersonate', description: 'Impersonate other workspace members', iconKey: 'IconUser', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -106,7 +108,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'SSO Bypass', description: 'Bypass single sign-on requirements', iconKey: 'IconKey', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -116,7 +118,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Applications', description: 'Manage installed applications', iconKey: 'IconApps', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -126,7 +128,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Marketplace Apps', description: 'Browse and install marketplace apps', iconKey: 'IconShoppingBag', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -136,7 +138,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Layouts', description: 'Manage page layouts', iconKey: 'IconLayout', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -146,7 +148,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Billing', description: 'Manage billing and subscriptions', iconKey: 'IconCreditCard', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -156,7 +158,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'AI Settings', description: 'Manage AI provider settings', iconKey: 'IconBrain', - category: 'settings', + permissionType: 'settings', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -166,7 +168,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'AI', description: 'Use AI features', iconKey: 'IconSparkles', - category: 'tool', + permissionType: 'tool', isRelevantForAgents: true, isRelevantForUsers: true, isRelevantForApiKeys: true, @@ -176,7 +178,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Views', description: 'Read and write views', iconKey: 'IconLayoutGrid', - category: 'tool', + permissionType: 'tool', isRelevantForAgents: true, isRelevantForUsers: true, isRelevantForApiKeys: true, @@ -186,7 +188,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Upload File', description: 'Upload files to the workspace', iconKey: 'IconUpload', - category: 'tool', + permissionType: 'tool', isRelevantForAgents: true, isRelevantForUsers: true, isRelevantForApiKeys: true, @@ -196,7 +198,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Download File', description: 'Download files from the workspace', iconKey: 'IconDownload', - category: 'tool', + permissionType: 'tool', isRelevantForAgents: true, isRelevantForUsers: true, isRelevantForApiKeys: true, @@ -206,7 +208,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Send Email', description: 'Send emails on behalf of users', iconKey: 'IconAt', - category: 'tool', + permissionType: 'tool', isRelevantForAgents: true, isRelevantForUsers: false, isRelevantForApiKeys: false, @@ -216,7 +218,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'HTTP Request', description: 'Make outbound HTTP requests', iconKey: 'IconApi', - category: 'tool', + permissionType: 'tool', isRelevantForAgents: true, isRelevantForUsers: false, isRelevantForApiKeys: false, @@ -226,7 +228,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Code Interpreter', description: 'Run code to analyze files and data', iconKey: 'IconCode', - category: 'tool', + permissionType: 'tool', isRelevantForAgents: true, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -236,7 +238,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Import CSV', description: 'Import records from CSV files', iconKey: 'IconFileImport', - category: 'tool', + permissionType: 'tool', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -246,7 +248,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Export CSV', description: 'Export records to CSV files', iconKey: 'IconFileExport', - category: 'tool', + permissionType: 'tool', isRelevantForAgents: false, isRelevantForUsers: true, isRelevantForApiKeys: false, @@ -256,7 +258,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Connected Accounts', description: 'Read connected email and calendar accounts', iconKey: 'IconMail', - category: 'tool', + permissionType: 'tool', isRelevantForAgents: true, isRelevantForUsers: false, isRelevantForApiKeys: false, @@ -266,7 +268,7 @@ export const STANDARD_PERMISSION_FLAG_DEFINITION = { label: 'Profile Information', description: 'Read profile information', iconKey: 'IconUser', - category: 'tool', + permissionType: 'tool', isRelevantForAgents: true, isRelevantForUsers: false, isRelevantForApiKeys: false, diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/permission-flag-definition-metadata/create-standard-permission-flag-definition-flat-metadata.util.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/permission-flag-definition-metadata/create-standard-permission-flag-definition-flat-metadata.util.ts index 038de537611..9aa1f6cff8e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/permission-flag-definition-metadata/create-standard-permission-flag-definition-flat-metadata.util.ts +++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/permission-flag-definition-metadata/create-standard-permission-flag-definition-flat-metadata.util.ts @@ -31,7 +31,7 @@ export const createStandardPermissionFlagDefinitionFlatMetadata = ({ label: metadata.label, description: metadata.description, iconKey: metadata.iconKey, - category: metadata.category, + permissionType: metadata.permissionType, isRelevantForAgents: metadata.isRelevantForAgents, isRelevantForUsers: metadata.isRelevantForUsers, isRelevantForApiKeys: metadata.isRelevantForApiKeys, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/__tests__/flat-permission-flag-definition-validator.service.spec.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/__tests__/flat-permission-flag-definition-validator.service.spec.ts index 1782bd554b0..33aadccb549 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/__tests__/flat-permission-flag-definition-validator.service.spec.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/__tests__/flat-permission-flag-definition-validator.service.spec.ts @@ -7,6 +7,7 @@ import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-enti import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type'; import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type'; import { type FlatPermissionFlagDefinition } from 'src/engine/metadata-modules/flat-permission-flag-definition/types/flat-permission-flag-definition.type'; +import { type PermissionFlagDefinitionPermissionType } from 'src/engine/metadata-modules/permission-flag-definition/constants/permission-flag-definition-permission-type.constant'; import { PermissionFlagDefinitionExceptionCode } from 'src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.exception'; import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications'; import { FlatPermissionFlagDefinitionValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-definition-validator.service'; @@ -21,7 +22,7 @@ const buildFlatDefinition = ( label: 'Test Flag', description: 'A flag for tests', iconKey: 'IconTest', - category: 'tool', + permissionType: 'tool', isRelevantForAgents: true, isRelevantForUsers: false, isRelevantForApiKeys: false, @@ -100,17 +101,17 @@ describe('FlatPermissionFlagDefinitionValidatorService', () => { ); }); - it('rejects an unknown category', () => { + it('rejects an unknown permission type', () => { const result = service.validateFlatPermissionFlagDefinitionCreation( buildArgs( buildFlatDefinition({ - category: 'invalid' as 'settings' | 'tool', + permissionType: 'invalid' as PermissionFlagDefinitionPermissionType, }), ), ); expect(result.errors.map((error) => error.code)).toContain( - PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_CATEGORY, + PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_PERMISSION_TYPE, ); }); @@ -131,9 +132,50 @@ describe('FlatPermissionFlagDefinitionValidatorService', () => { PermissionFlagDefinitionExceptionCode.PERMISSION_FLAG_DEFINITION_ALREADY_EXISTS, ); }); + + it('rejects a duplicate universal identifier', () => { + const existing = buildFlatDefinition(); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagDefinitionCreation( + buildArgs( + buildFlatDefinition({ + id: '00000000-0000-0000-0000-000000000002', + key: 'ANOTHER_TEST_FLAG', + }), + optimisticMaps, + ), + ); + + expect(result.errors.map((error) => error.code)).toContain( + PermissionFlagDefinitionExceptionCode.PERMISSION_FLAG_DEFINITION_ALREADY_EXISTS, + ); + }); }); describe('validateFlatPermissionFlagDefinitionUpdate', () => { + it('passes for a valid update', () => { + const existing = buildFlatDefinition(); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagDefinitionUpdate({ + universalIdentifier: existing.universalIdentifier, + flatEntityUpdate: { label: 'Updated Label' }, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagDefinitionMaps: optimisticMaps, + }, + buildOptions: {} as never, + } as unknown as Parameters< + FlatPermissionFlagDefinitionValidatorService['validateFlatPermissionFlagDefinitionUpdate'] + >[0]); + + expect(result.errors).toHaveLength(0); + }); + it('rejects updating the immutable key field', () => { const existing = buildFlatDefinition({ key: 'ORIGINAL_KEY' }); const optimisticMaps = buildEmptyMaps(); @@ -172,9 +214,101 @@ describe('FlatPermissionFlagDefinitionValidatorService', () => { PermissionFlagDefinitionExceptionCode.PERMISSION_FLAG_DEFINITION_NOT_FOUND, ); }); + + it('rejects updating a standard definition from a custom application', () => { + const existing = buildFlatDefinition({ + applicationUniversalIdentifier: + TWENTY_STANDARD_APPLICATION.universalIdentifier, + }); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagDefinitionUpdate({ + universalIdentifier: existing.universalIdentifier, + flatEntityUpdate: { label: 'Updated Label' }, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagDefinitionMaps: optimisticMaps, + }, + buildOptions: { + isSystemBuild: false, + applicationUniversalIdentifier: + '00000000-0000-0000-0000-000000000aaa', + }, + } as unknown as Parameters< + FlatPermissionFlagDefinitionValidatorService['validateFlatPermissionFlagDefinitionUpdate'] + >[0]); + + expect(result.errors.map((error) => error.code)).toContain( + PermissionFlagDefinitionExceptionCode.PERMISSION_FLAG_DEFINITION_IS_STANDARD, + ); + }); + + it('rejects updating to an unknown permission type', () => { + const existing = buildFlatDefinition(); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagDefinitionUpdate({ + universalIdentifier: existing.universalIdentifier, + flatEntityUpdate: { + permissionType: 'invalid' as PermissionFlagDefinitionPermissionType, + }, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagDefinitionMaps: optimisticMaps, + }, + buildOptions: {} as never, + } as unknown as Parameters< + FlatPermissionFlagDefinitionValidatorService['validateFlatPermissionFlagDefinitionUpdate'] + >[0]); + + expect(result.errors.map((error) => error.code)).toContain( + PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_PERMISSION_TYPE, + ); + }); }); describe('validateFlatPermissionFlagDefinitionDeletion', () => { + it('passes for a valid deletion', () => { + const existing = buildFlatDefinition(); + const optimisticMaps = buildEmptyMaps(); + optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] = + existing; + + const result = service.validateFlatPermissionFlagDefinitionDeletion({ + flatEntityToValidate: existing, + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagDefinitionMaps: optimisticMaps, + flatPermissionFlagMaps: buildEmptyPermissionFlagMaps(), + }, + buildOptions: {} as never, + } as unknown as Parameters< + FlatPermissionFlagDefinitionValidatorService['validateFlatPermissionFlagDefinitionDeletion'] + >[0]); + + expect(result.errors).toHaveLength(0); + }); + + it('returns not-found if no existing definition matches the universalIdentifier', () => { + const result = service.validateFlatPermissionFlagDefinitionDeletion({ + flatEntityToValidate: buildFlatDefinition({ + universalIdentifier: '00000000-0000-0000-0000-deadbeefdead', + }), + optimisticFlatEntityMapsAndRelatedFlatEntityMaps: { + flatPermissionFlagDefinitionMaps: buildEmptyMaps(), + flatPermissionFlagMaps: buildEmptyPermissionFlagMaps(), + }, + buildOptions: {} as never, + } as unknown as Parameters< + FlatPermissionFlagDefinitionValidatorService['validateFlatPermissionFlagDefinitionDeletion'] + >[0]); + + expect(result.errors.map((error) => error.code)).toContain( + PermissionFlagDefinitionExceptionCode.PERMISSION_FLAG_DEFINITION_NOT_FOUND, + ); + }); + it('rejects deleting a standard definition from a custom application', () => { const existing = buildFlatDefinition({ applicationUniversalIdentifier: diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-definition-validator.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-definition-validator.service.ts index 97d2fa46b2a..6c46d2bb27f 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-definition-validator.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-definition-validator.service.ts @@ -6,6 +6,7 @@ import { ALL_METADATA_NAME } from 'twenty-shared/metadata'; import { isDefined } from 'twenty-shared/utils'; import { findFlatEntityByUniversalIdentifier } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier.util'; +import { PERMISSION_FLAG_DEFINITION_PERMISSION_TYPES } from 'src/engine/metadata-modules/permission-flag-definition/constants/permission-flag-definition-permission-type.constant'; import { PermissionFlagDefinitionExceptionCode } from 'src/engine/metadata-modules/permission-flag-definition/permission-flag-definition.exception'; import { belongsToTwentyStandardApp } from 'src/engine/metadata-modules/utils/belongs-to-twenty-standard-app.util'; import { isCallerTwentyStandardApp } from 'src/engine/metadata-modules/utils/is-caller-twenty-standard-app.util'; @@ -14,8 +15,6 @@ import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/ import { type FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type'; import { type UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type'; -const VALID_CATEGORIES = ['settings', 'tool'] as const; - @Injectable() export class FlatPermissionFlagDefinitionValidatorService { validateFlatPermissionFlagDefinitionCreation({ @@ -78,14 +77,14 @@ export class FlatPermissionFlagDefinitionValidatorService { } if ( - !VALID_CATEGORIES.includes( - flatPermissionFlagDefinitionToValidate.category as (typeof VALID_CATEGORIES)[number], + !PERMISSION_FLAG_DEFINITION_PERMISSION_TYPES.includes( + flatPermissionFlagDefinitionToValidate.permissionType, ) ) { validationResult.errors.push({ - code: PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_CATEGORY, - message: t`Permission flag definition category must be 'settings' or 'tool'`, - userFriendlyMessage: msg`Invalid category`, + code: PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_PERMISSION_TYPE, + message: t`Permission flag definition permission type must be 'settings' or 'tool'`, + userFriendlyMessage: msg`Invalid permission type`, }); } @@ -152,15 +151,15 @@ export class FlatPermissionFlagDefinitionValidatorService { } if ( - isDefined(flatEntityUpdate.category) && - !VALID_CATEGORIES.includes( - flatEntityUpdate.category as (typeof VALID_CATEGORIES)[number], + isDefined(flatEntityUpdate.permissionType) && + !PERMISSION_FLAG_DEFINITION_PERMISSION_TYPES.includes( + flatEntityUpdate.permissionType, ) ) { validationResult.errors.push({ - code: PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_CATEGORY, - message: t`Permission flag definition category must be 'settings' or 'tool'`, - userFriendlyMessage: msg`Invalid category`, + code: PermissionFlagDefinitionExceptionCode.INVALID_PERMISSION_FLAG_DEFINITION_PERMISSION_TYPE, + message: t`Permission flag definition permission type must be 'settings' or 'tool'`, + userFriendlyMessage: msg`Invalid permission type`, }); }