[1/3] Rename permissionFlag to rolePermissionFlag + add permissionFlag catalog/backfill (#20481)

Split of #20377.

## Summary

This PR separates available permission flags from per-role permission
flag grants.

Previously, `core.permissionFlag` stored the role assignment directly:
`roleId + flag`. This PR renames that legacy grant table to
`core.rolePermissionFlag`, then recreates `core.permissionFlag` as the
catalog of available permission flags.

## What changed

- Rename the existing `core.permissionFlag` grant table to
`core.rolePermissionFlag`.
- Add the new syncable `core.permissionFlag` catalog entity with key,
label, description, icon, permission type, relevance flags, and
custom/standard metadata.
- Add stable `SystemPermissionFlag` universal identifiers for the
built-in `PermissionFlagType` values.
- Seed the standard permission flags for every workspace under the
Twenty standard application.
- Backfill existing role grants:
  - create missing catalog rows for existing grant keys,
  - add `rolePermissionFlag.permissionFlagId`,
- migrate grants from the old string `flag` column to the new catalog
FK,
- replace the old `(flag, roleId)` uniqueness with `(permissionFlagId,
roleId)`.
- Rewire role permission flag caches, permission checks, role DTO
mapping, and `upsertPermissionFlags` to resolve through the catalog.
- Keep the existing public role permission API shape: product/app
surfaces still talk about `permissionFlags` and return `{ id, roleId,
flag }`.
- Update metadata flat-entity machinery, migration builders, validators,
action handlers, snapshots, generated schemas, docs, and app fixtures
for the new `permissionFlag` / `rolePermissionFlag` split.

## Behavior after this PR

- Existing permission flag grants keep working.
- Existing GraphQL role permission flows keep the same public naming.
- Standard permission flags are represented as catalog rows.
- Permission checks now compare grants through catalog universal
identifiers instead of the legacy `flag` column.
- Workspace deletion cleanup now verifies both `permissionFlag` and
`rolePermissionFlag`.

## What is not in this PR

- Public GraphQL CRUD for custom permission flags.
- App manifest support for declaring new custom permission flags.
- Frontend UI for creating or assigning custom permission flags beyond
the existing role permission flow.

---------

Co-authored-by: Weiko <corentin@twenty.com>
This commit is contained in:
nitin
2026-05-18 17:27:47 +05:30
committed by GitHub
parent 01535a3b3e
commit db0547f503
132 changed files with 3663 additions and 719 deletions
@@ -10,6 +10,7 @@ export const TWENTY_STANDARD_ALL_METADATA_NAME = [
'viewGroup',
'view',
'navigationMenuItem',
'permissionFlag',
'role',
'agent',
'skill',
@@ -0,0 +1,41 @@
import { v4 } from 'uuid';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { addFlatEntityToFlatEntityMapsOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/add-flat-entity-to-flat-entity-maps-or-throw.util';
import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type';
import { STANDARD_PERMISSION_FLAG_DEFINITIONS } from 'src/engine/metadata-modules/permission-flag/constants/standard-permission-flag-definitions.constant';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
export const buildStandardFlatPermissionFlagMetadataMaps = ({
now,
workspaceId,
twentyStandardApplicationId,
}: {
now: string;
workspaceId: string;
twentyStandardApplicationId: string;
}): FlatEntityMaps<FlatPermissionFlag> => {
let flatPermissionFlagMaps = createEmptyFlatEntityMaps();
for (const permissionFlagDefinition of STANDARD_PERMISSION_FLAG_DEFINITIONS) {
flatPermissionFlagMaps = addFlatEntityToFlatEntityMapsOrThrow({
flatEntity: {
id: v4(),
...permissionFlagDefinition,
workspaceId,
applicationId: twentyStandardApplicationId,
universalIdentifier: permissionFlagDefinition.universalIdentifier,
applicationUniversalIdentifier:
TWENTY_STANDARD_APPLICATION.universalIdentifier,
rolePermissionFlagIds: [],
rolePermissionFlagUniversalIdentifiers: [],
createdAt: now,
updatedAt: now,
},
flatEntityMaps: flatPermissionFlagMaps,
});
}
return flatPermissionFlagMaps;
};
@@ -72,8 +72,8 @@ export const createStandardRoleFlatMetadata = ({
TWENTY_STANDARD_APPLICATION.universalIdentifier,
createdAt: now,
updatedAt: now,
permissionFlagIds: [],
permissionFlagUniversalIdentifiers: [],
rolePermissionFlagIds: [],
rolePermissionFlagUniversalIdentifiers: [],
objectPermissionUniversalIdentifiers: [],
fieldPermissionIds: [],
fieldPermissionUniversalIdentifiers: [],
@@ -15,6 +15,7 @@ import { buildStandardFlatObjectMetadataMaps } from 'src/engine/workspace-manage
import { buildStandardFlatPageLayoutTabMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-tab/build-standard-flat-page-layout-tab-metadata-maps.util';
import { buildStandardFlatPageLayoutWidgetMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout-widget/build-standard-flat-page-layout-widget-metadata-maps.util';
import { buildStandardFlatPageLayoutMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/page-layout/build-standard-flat-page-layout-metadata-maps.util';
import { buildStandardFlatPermissionFlagMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/permission-flag/build-standard-flat-permission-flag-metadata-maps.util';
import { buildStandardFlatRoleMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/role-metadata/build-standard-flat-role-metadata-maps.util';
import { buildStandardFlatSkillMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/skill-metadata/build-standard-flat-skill-metadata-maps.util';
import { buildStandardFlatViewFieldMetadataMaps } from 'src/engine/workspace-manager/twenty-standard-application/utils/view-field/build-standard-flat-view-field-metadata-maps.util';
@@ -140,6 +141,12 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({
dependencyFlatEntityMaps: undefined,
});
const flatPermissionFlagMaps = buildStandardFlatPermissionFlagMetadataMaps({
now,
workspaceId,
twentyStandardApplicationId,
});
const flatAgentMaps = buildStandardFlatAgentMetadataMaps({
now,
workspaceId,
@@ -213,6 +220,7 @@ export const computeTwentyStandardApplicationAllFlatEntityMaps = ({
flatFieldMetadataMaps,
flatObjectMetadataMaps,
flatNavigationMenuItemMaps,
flatPermissionFlagMaps,
flatRoleMaps,
flatAgentMaps,
flatSkillMaps,
@@ -167,6 +167,7 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({
case 'navigationMenuItem':
case 'frontComponent':
case 'viewSort':
case 'rolePermissionFlag':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
@@ -29,6 +29,7 @@ import { WorkspaceMigrationPageLayoutTabActionsBuilderService } from 'src/engine
import { WorkspaceMigrationPageLayoutWidgetActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout-widget/workspace-migration-page-layout-widget-actions-builder.service';
import { WorkspaceMigrationPageLayoutActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout/workspace-migration-page-layout-actions-builder.service';
import { WorkspaceMigrationPermissionFlagActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/workspace-migration-permission-flag-actions-builder.service';
import { WorkspaceMigrationRolePermissionFlagActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/workspace-migration-role-permission-flag-actions-builder.service';
import { WorkspaceMigrationRoleTargetActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-target/workspace-migration-role-target-actions-builder.service';
import { WorkspaceMigrationRoleActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role/workspace-migration-role-actions-builder.service';
import { WorkspaceMigrationRowLevelPermissionPredicateGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/row-level-permission-predicate-group/workspace-migration-row-level-permission-predicate-group-actions-builder.service';
@@ -58,6 +59,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
private readonly workspaceMigrationViewSortActionsBuilderService: WorkspaceMigrationViewSortActionsBuilderService,
private readonly workspaceMigrationFieldPermissionActionsBuilderService: WorkspaceMigrationFieldPermissionActionsBuilderService,
private readonly workspaceMigrationObjectPermissionActionsBuilderService: WorkspaceMigrationObjectPermissionActionsBuilderService,
private readonly workspaceMigrationRolePermissionFlagActionsBuilderService: WorkspaceMigrationRolePermissionFlagActionsBuilderService,
private readonly workspaceMigrationPermissionFlagActionsBuilderService: WorkspaceMigrationPermissionFlagActionsBuilderService,
private readonly workspaceMigrationLogicFunctionActionsBuilderService: WorkspaceMigrationLogicFunctionActionsBuilderService,
private readonly workspaceMigrationRoleTargetActionsBuilderService: WorkspaceMigrationRoleTargetActionsBuilderService,
@@ -158,6 +160,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
flatRoleMaps,
flatObjectPermissionMaps,
flatFieldPermissionMaps,
flatRolePermissionFlagMaps,
flatPermissionFlagMaps,
flatRoleTargetMaps,
flatAgentMaps,
@@ -558,6 +561,34 @@ export class WorkspaceMigrationBuildOrchestratorService {
}
}
if (isDefined(flatRolePermissionFlagMaps)) {
const {
from: fromFlatRolePermissionFlagMaps,
to: toFlatRolePermissionFlagMaps,
} = flatRolePermissionFlagMaps;
const rolePermissionFlagResult =
await this.workspaceMigrationRolePermissionFlagActionsBuilderService.validateAndBuild(
{
additionalCacheDataMaps,
from: fromFlatRolePermissionFlagMaps,
to: toFlatRolePermissionFlagMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: optimisticAllFlatEntityMaps,
workspaceId,
},
);
if (rolePermissionFlagResult.status === 'fail') {
orchestratorFailureReport.rolePermissionFlag.push(
...rolePermissionFlagResult.errors,
);
} else {
orchestratorActionsReport.rolePermissionFlag =
rolePermissionFlagResult.actions;
}
}
if (isDefined(flatPermissionFlagMaps)) {
const { from: fromFlatPermissionFlagMaps, to: toFlatPermissionFlagMaps } =
flatPermissionFlagMaps;
@@ -986,6 +1017,12 @@ export class WorkspaceMigrationBuildOrchestratorService {
///
// Permission flags
...aggregatedOrchestratorActionsReport.rolePermissionFlag.delete,
...aggregatedOrchestratorActionsReport.rolePermissionFlag.create,
...aggregatedOrchestratorActionsReport.rolePermissionFlag.update,
///
// Permission flag definitions
...aggregatedOrchestratorActionsReport.permissionFlag.delete,
...aggregatedOrchestratorActionsReport.permissionFlag.create,
...aggregatedOrchestratorActionsReport.permissionFlag.update,
@@ -35,15 +35,18 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should matc
"widgetUniversalIdentifiers",
],
"pageLayoutWidget": [],
"permissionFlag": [],
"permissionFlag": [
"rolePermissionFlagUniversalIdentifiers",
],
"role": [
"roleTargetUniversalIdentifiers",
"objectPermissionUniversalIdentifiers",
"permissionFlagUniversalIdentifiers",
"rolePermissionFlagUniversalIdentifiers",
"fieldPermissionUniversalIdentifiers",
"rowLevelPermissionPredicateUniversalIdentifiers",
"rowLevelPermissionPredicateGroupUniversalIdentifiers",
],
"rolePermissionFlag": [],
"roleTarget": [],
"rowLevelPermissionPredicate": [],
"rowLevelPermissionPredicateGroup": [
@@ -38,6 +38,7 @@ export const ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME = {
},
commandMenuItem: {},
navigationMenuItem: {},
rolePermissionFlag: {},
permissionFlag: {},
objectPermission: {},
fieldPermission: {},
@@ -0,0 +1,7 @@
import { type RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity';
import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
export type UniversalFlatRolePermissionFlag = UniversalFlatEntityFrom<
RolePermissionFlagEntity,
'rolePermissionFlag'
>;
@@ -2,12 +2,12 @@ import { Injectable } from '@nestjs/common';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { type UniversalUpdatePermissionFlagAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/types/workspace-migration-permission-flag-action.type';
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';
import { type UniversalFlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-result.type';
import { WorkspaceEntityMigrationBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service';
import { UniversalUpdatePermissionFlagAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/types/workspace-migration-permission-flag-action.type';
import { FlatPermissionFlagValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service';
import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
import { UniversalFlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-result.type';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
@Injectable()
export class WorkspaceMigrationPermissionFlagActionsBuilderService extends WorkspaceEntityMigrationBuilderService<
@@ -105,7 +105,7 @@ export class WorkspaceMigrationPermissionFlagActionsBuilderService extends Works
const { universalIdentifier, flatEntityUpdate } = args;
const updatePermissionFlagAction: UniversalUpdatePermissionFlagAction = {
const updateAction: UniversalUpdatePermissionFlagAction = {
type: 'update',
metadataName: 'permissionFlag',
universalIdentifier,
@@ -114,7 +114,7 @@ export class WorkspaceMigrationPermissionFlagActionsBuilderService extends Works
return {
status: 'success',
action: updatePermissionFlagAction,
action: updateAction,
};
}
}
@@ -0,0 +1,24 @@
import { type BaseFlatCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-create-workspace-migration-action.type';
import { type BaseFlatDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-delete-workspace-migration-action.type';
import { type BaseFlatUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-flat-update-workspace-migration-action.type';
import { type BaseUniversalCreateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-create-workspace-migration-action.type';
import { type BaseUniversalDeleteWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-delete-workspace-migration-action.type';
import { type BaseUniversalUpdateWorkspaceMigrationAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/base-universal-update-workspace-migration-action.type';
export type FlatCreateRolePermissionFlagAction =
BaseFlatCreateWorkspaceMigrationAction<'rolePermissionFlag'>;
export type FlatUpdateRolePermissionFlagAction =
BaseFlatUpdateWorkspaceMigrationAction<'rolePermissionFlag'>;
export type FlatDeleteRolePermissionFlagAction =
BaseFlatDeleteWorkspaceMigrationAction<'rolePermissionFlag'>;
export type UniversalCreateRolePermissionFlagAction =
BaseUniversalCreateWorkspaceMigrationAction<'rolePermissionFlag'>;
export type UniversalUpdateRolePermissionFlagAction =
BaseUniversalUpdateWorkspaceMigrationAction<'rolePermissionFlag'>;
export type UniversalDeleteRolePermissionFlagAction =
BaseUniversalDeleteWorkspaceMigrationAction<'rolePermissionFlag'>;
@@ -0,0 +1,122 @@
import { Injectable } from '@nestjs/common';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { WorkspaceEntityMigrationBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/services/workspace-entity-migration-builder.service';
import { UniversalUpdateRolePermissionFlagAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/types/workspace-migration-role-permission-flag-action.type';
import { FlatRolePermissionFlagValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-role-permission-flag-validator.service';
import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
import { UniversalFlatEntityValidationReturnType } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-result.type';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
@Injectable()
export class WorkspaceMigrationRolePermissionFlagActionsBuilderService extends WorkspaceEntityMigrationBuilderService<
typeof ALL_METADATA_NAME.rolePermissionFlag
> {
constructor(
private readonly flatRolePermissionFlagValidatorService: FlatRolePermissionFlagValidatorService,
) {
super(ALL_METADATA_NAME.rolePermissionFlag);
}
protected validateFlatEntityCreation(
args: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.rolePermissionFlag
>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.rolePermissionFlag,
'create'
> {
const validationResult =
this.flatRolePermissionFlagValidatorService.validateFlatRolePermissionFlagCreation(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate: flatRolePermissionFlagToValidate } = args;
return {
status: 'success',
action: {
type: 'create',
metadataName: 'rolePermissionFlag',
flatEntity: flatRolePermissionFlagToValidate,
},
};
}
protected validateFlatEntityDeletion(
args: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.rolePermissionFlag
>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.rolePermissionFlag,
'delete'
> {
const validationResult =
this.flatRolePermissionFlagValidatorService.validateFlatRolePermissionFlagDeletion(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate: flatRolePermissionFlagToValidate } = args;
return {
status: 'success',
action: {
type: 'delete',
metadataName: 'rolePermissionFlag',
universalIdentifier:
flatRolePermissionFlagToValidate.universalIdentifier,
},
};
}
protected validateFlatEntityUpdate(
args: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.rolePermissionFlag
>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.rolePermissionFlag,
'update'
> {
const validationResult =
this.flatRolePermissionFlagValidatorService.validateFlatRolePermissionFlagUpdate(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { universalIdentifier, flatEntityUpdate } = args;
const updateRolePermissionFlagAction: UniversalUpdateRolePermissionFlagAction =
{
type: 'update',
metadataName: 'rolePermissionFlag',
universalIdentifier,
update: flatEntityUpdate,
};
return {
status: 'success',
action: updateRolePermissionFlagAction,
};
}
}
@@ -0,0 +1,381 @@
import { Test, type TestingModule } from '@nestjs/testing';
import { PermissionFlagType } from 'twenty-shared/constants';
import { ALL_METADATA_NAME } from 'twenty-shared/metadata';
import { createEmptyFlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/constant/create-empty-flat-entity-maps.constant';
import { type FlatEntityMaps } from 'src/engine/metadata-modules/flat-entity/types/flat-entity-maps.type';
import { type FlatRolePermissionFlag } from 'src/engine/metadata-modules/flat-role-permission-flag/types/flat-role-permission-flag.type';
import { type FlatPermissionFlag } from 'src/engine/metadata-modules/flat-permission-flag/types/flat-permission-flag.type';
import { type PermissionFlagPermissionType } from 'src/engine/metadata-modules/permission-flag/constants/permission-flag-permission-type.constant';
import { PermissionFlagExceptionCode } from 'src/engine/metadata-modules/permission-flag/permission-flag.exception';
import { TWENTY_STANDARD_APPLICATION } from 'src/engine/workspace-manager/twenty-standard-application/constants/twenty-standard-applications';
import { FlatPermissionFlagValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service';
const buildFlatDefinition = (
overrides: Partial<FlatPermissionFlag> = {},
): FlatPermissionFlag =>
({
id: '00000000-0000-0000-0000-000000000001',
universalIdentifier: '00000000-0000-0000-0000-000000000001',
key: 'TEST_FLAG',
label: 'Test Flag',
description: 'A flag for tests',
icon: 'IconTest',
permissionType: 'tool',
workspaceId: 'workspace-id',
applicationId: '00000000-0000-0000-0000-000000000aaa',
applicationUniversalIdentifier: '00000000-0000-0000-0000-000000000aaa',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
...overrides,
}) as unknown as FlatPermissionFlag;
const buildEmptyMaps = (): FlatEntityMaps<FlatPermissionFlag> =>
createEmptyFlatEntityMaps() as unknown as FlatEntityMaps<FlatPermissionFlag>;
const buildEmptyRolePermissionFlagMaps =
(): FlatEntityMaps<FlatRolePermissionFlag> =>
createEmptyFlatEntityMaps() as unknown as FlatEntityMaps<FlatRolePermissionFlag>;
const buildFlatRolePermissionFlag = (
overrides: Partial<FlatRolePermissionFlag> = {},
): FlatRolePermissionFlag =>
({
id: '00000000-0000-0000-0000-000000000101',
universalIdentifier: '00000000-0000-0000-0000-000000000101',
permissionFlagId: '00000000-0000-0000-0000-000000000001',
permissionFlagUniversalIdentifier: '00000000-0000-0000-0000-000000000001',
roleUniversalIdentifier: '00000000-0000-0000-0000-000000000201',
workspaceId: 'workspace-id',
applicationId: '00000000-0000-0000-0000-000000000aaa',
applicationUniversalIdentifier: '00000000-0000-0000-0000-000000000aaa',
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString(),
...overrides,
}) as unknown as FlatRolePermissionFlag;
const buildArgs = (
flatEntityToValidate: FlatPermissionFlag,
optimisticMaps: FlatEntityMaps<FlatPermissionFlag> = buildEmptyMaps(),
) =>
({
flatEntityToValidate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: optimisticMaps,
},
buildOptions: {} as never,
}) as unknown as Parameters<
FlatPermissionFlagValidatorService['validateFlatPermissionFlagCreation']
>[0];
describe('FlatPermissionFlagValidatorService', () => {
let service: FlatPermissionFlagValidatorService;
beforeEach(async () => {
const moduleRef: TestingModule = await Test.createTestingModule({
providers: [FlatPermissionFlagValidatorService],
}).compile();
service = moduleRef.get(FlatPermissionFlagValidatorService);
});
describe('validateFlatPermissionFlagCreation', () => {
it('passes for a valid definition', () => {
const result = service.validateFlatPermissionFlagCreation(
buildArgs(buildFlatDefinition()),
);
expect(result.errors).toHaveLength(0);
});
it('rejects an empty key', () => {
const result = service.validateFlatPermissionFlagCreation(
buildArgs(buildFlatDefinition({ key: '' })),
);
expect(result.errors.map((error) => error.code)).toEqual([
PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_KEY,
]);
});
it('rejects an unknown permission type', () => {
const result = service.validateFlatPermissionFlagCreation(
buildArgs(
buildFlatDefinition({
permissionType: 'invalid' as PermissionFlagPermissionType,
}),
),
);
expect(result.errors.map((error) => error.code)).toEqual([
PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_PERMISSION_TYPE,
]);
});
it('rejects a duplicate key in the same workspace', () => {
const existing = buildFlatDefinition({
universalIdentifier: '00000000-0000-0000-0000-000000000999',
key: 'EXISTING_KEY',
});
const optimisticMaps = buildEmptyMaps();
optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] =
existing;
const result = service.validateFlatPermissionFlagCreation(
buildArgs(buildFlatDefinition({ key: 'EXISTING_KEY' }), optimisticMaps),
);
expect(result.errors.map((error) => error.code)).toEqual([
PermissionFlagExceptionCode.PERMISSION_FLAG_ALREADY_EXISTS,
]);
});
it('rejects a duplicate universal identifier', () => {
const existing = buildFlatDefinition();
const optimisticMaps = buildEmptyMaps();
optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] =
existing;
const result = service.validateFlatPermissionFlagCreation(
buildArgs(
buildFlatDefinition({
id: '00000000-0000-0000-0000-000000000002',
key: 'ANOTHER_TEST_FLAG',
}),
optimisticMaps,
),
);
expect(result.errors.map((error) => error.code)).toEqual([
PermissionFlagExceptionCode.PERMISSION_FLAG_ALREADY_EXISTS,
]);
});
});
describe('validateFlatPermissionFlagUpdate', () => {
it('passes for a valid update', () => {
const existing = buildFlatDefinition();
const optimisticMaps = buildEmptyMaps();
optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] =
existing;
const result = service.validateFlatPermissionFlagUpdate({
universalIdentifier: existing.universalIdentifier,
flatEntityUpdate: { label: 'Updated Label' },
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: optimisticMaps,
},
buildOptions: {} as never,
} as unknown as Parameters<
FlatPermissionFlagValidatorService['validateFlatPermissionFlagUpdate']
>[0]);
expect(result.errors).toHaveLength(0);
});
it('rejects updating the immutable key field', () => {
const existing = buildFlatDefinition({ key: 'ORIGINAL_KEY' });
const optimisticMaps = buildEmptyMaps();
optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] =
existing;
const result = service.validateFlatPermissionFlagUpdate({
universalIdentifier: existing.universalIdentifier,
flatEntityUpdate: { key: 'NEW_KEY' },
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: optimisticMaps,
},
buildOptions: {} as never,
} as unknown as Parameters<
FlatPermissionFlagValidatorService['validateFlatPermissionFlagUpdate']
>[0]);
expect(result.errors.map((error) => error.code)).toEqual([
PermissionFlagExceptionCode.PERMISSION_FLAG_KEY_IMMUTABLE,
]);
});
it('returns not-found if no existing definition matches the universalIdentifier', () => {
const result = service.validateFlatPermissionFlagUpdate({
universalIdentifier: '00000000-0000-0000-0000-deadbeefdead',
flatEntityUpdate: { label: 'New Label' },
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: buildEmptyMaps(),
},
buildOptions: {} as never,
} as unknown as Parameters<
FlatPermissionFlagValidatorService['validateFlatPermissionFlagUpdate']
>[0]);
expect(result.errors.map((error) => error.code)).toEqual([
PermissionFlagExceptionCode.PERMISSION_FLAG_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.validateFlatPermissionFlagUpdate({
universalIdentifier: existing.universalIdentifier,
flatEntityUpdate: { label: 'Updated Label' },
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: optimisticMaps,
},
buildOptions: {
isSystemBuild: false,
applicationUniversalIdentifier:
'00000000-0000-0000-0000-000000000aaa',
},
} as unknown as Parameters<
FlatPermissionFlagValidatorService['validateFlatPermissionFlagUpdate']
>[0]);
expect(result.errors.map((error) => error.code)).toEqual([
PermissionFlagExceptionCode.PERMISSION_FLAG_IS_STANDARD,
]);
});
it('rejects updating to an unknown permission type', () => {
const existing = buildFlatDefinition();
const optimisticMaps = buildEmptyMaps();
optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] =
existing;
const result = service.validateFlatPermissionFlagUpdate({
universalIdentifier: existing.universalIdentifier,
flatEntityUpdate: {
permissionType: 'invalid' as PermissionFlagPermissionType,
},
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: optimisticMaps,
},
buildOptions: {} as never,
} as unknown as Parameters<
FlatPermissionFlagValidatorService['validateFlatPermissionFlagUpdate']
>[0]);
expect(result.errors.map((error) => error.code)).toEqual([
PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_PERMISSION_TYPE,
]);
});
});
describe('validateFlatPermissionFlagDeletion', () => {
it('passes for a valid deletion', () => {
const existing = buildFlatDefinition();
const optimisticMaps = buildEmptyMaps();
optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] =
existing;
const result = service.validateFlatPermissionFlagDeletion({
flatEntityToValidate: existing,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: optimisticMaps,
flatRolePermissionFlagMaps: buildEmptyRolePermissionFlagMaps(),
},
buildOptions: {} as never,
} as unknown as Parameters<
FlatPermissionFlagValidatorService['validateFlatPermissionFlagDeletion']
>[0]);
expect(result.errors).toHaveLength(0);
});
it('returns not-found if no existing definition matches the universalIdentifier', () => {
const result = service.validateFlatPermissionFlagDeletion({
flatEntityToValidate: buildFlatDefinition({
universalIdentifier: '00000000-0000-0000-0000-deadbeefdead',
}),
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: buildEmptyMaps(),
flatRolePermissionFlagMaps: buildEmptyRolePermissionFlagMaps(),
},
buildOptions: {} as never,
} as unknown as Parameters<
FlatPermissionFlagValidatorService['validateFlatPermissionFlagDeletion']
>[0]);
expect(result.errors.map((error) => error.code)).toEqual([
PermissionFlagExceptionCode.PERMISSION_FLAG_NOT_FOUND,
]);
});
it('rejects deleting 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.validateFlatPermissionFlagDeletion({
flatEntityToValidate: existing,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: optimisticMaps,
flatRolePermissionFlagMaps: buildEmptyRolePermissionFlagMaps(),
},
buildOptions: {
isSystemBuild: false,
applicationUniversalIdentifier:
'00000000-0000-0000-0000-000000000aaa',
},
} as unknown as Parameters<
FlatPermissionFlagValidatorService['validateFlatPermissionFlagDeletion']
>[0]);
expect(result.errors.map((error) => error.code)).toEqual([
PermissionFlagExceptionCode.PERMISSION_FLAG_IS_STANDARD,
]);
});
it('rejects deleting a definition while roles still grant its key', () => {
const existing = buildFlatDefinition({
key: PermissionFlagType.WORKSPACE,
});
const optimisticMaps = buildEmptyMaps();
optimisticMaps.byUniversalIdentifier[existing.universalIdentifier] =
existing;
const rolePermissionFlagMaps = buildEmptyRolePermissionFlagMaps();
const rolePermissionFlag = buildFlatRolePermissionFlag({
permissionFlagId: existing.id,
permissionFlagUniversalIdentifier: existing.universalIdentifier,
});
rolePermissionFlagMaps.byUniversalIdentifier[
rolePermissionFlag.universalIdentifier
] = rolePermissionFlag;
const result = service.validateFlatPermissionFlagDeletion({
flatEntityToValidate: existing,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: optimisticMaps,
flatRolePermissionFlagMaps: rolePermissionFlagMaps,
},
buildOptions: {
isSystemBuild: false,
applicationUniversalIdentifier:
'00000000-0000-0000-0000-000000000aaa',
},
} as unknown as Parameters<
FlatPermissionFlagValidatorService['validateFlatPermissionFlagDeletion']
>[0]);
expect(result.errors.map((error) => error.code)).toEqual([
PermissionFlagExceptionCode.PERMISSION_FLAG_IN_USE,
]);
});
});
// ALL_METADATA_NAME is referenced as a sanity check the metadata name exists
it('uses the registered ALL_METADATA_NAME entry', () => {
expect(ALL_METADATA_NAME.permissionFlag).toBe('permissionFlag');
});
});
@@ -1,17 +1,19 @@
import { Injectable } from '@nestjs/common';
import { msg, t } from '@lingui/core/macro';
import { PermissionFlagType } from 'twenty-shared/constants';
import { isNonEmptyString } from '@sniptt/guards';
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 { PermissionsExceptionCode } from 'src/engine/metadata-modules/permissions/permissions.exception';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { PERMISSION_FLAG_PERMISSION_TYPES } from 'src/engine/metadata-modules/permission-flag/constants/permission-flag-permission-type.constant';
import { PermissionFlagExceptionCode } from 'src/engine/metadata-modules/permission-flag/permission-flag.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';
import { type FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
import { validateRoleBelongsToCallerApplication } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/utils/validate-role-belongs-to-caller-application.util';
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';
@Injectable()
export class FlatPermissionFlagValidatorService {
@@ -19,17 +21,14 @@ export class FlatPermissionFlagValidatorService {
flatEntityToValidate: flatPermissionFlagToValidate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: optimisticFlatPermissionFlagMaps,
flatRoleMaps,
},
buildOptions,
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.permissionFlag
>): FailedFlatEntityValidation<'permissionFlag', 'create'> {
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier: flatPermissionFlagToValidate.universalIdentifier,
roleUniversalIdentifier:
flatPermissionFlagToValidate.roleUniversalIdentifier,
key: flatPermissionFlagToValidate.key,
},
metadataName: 'permissionFlag',
type: 'create',
@@ -42,71 +41,47 @@ export class FlatPermissionFlagValidatorService {
if (isDefined(existingByUniversalId)) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Permission flag with universal identifier ${flatPermissionFlagToValidate.universalIdentifier} already exists`,
userFriendlyMessage: msg`Permission flag already exists`,
code: PermissionFlagExceptionCode.PERMISSION_FLAG_ALREADY_EXISTS,
message: t`Permission flag definition with universal identifier ${flatPermissionFlagToValidate.universalIdentifier} already exists`,
userFriendlyMessage: msg`Permission flag definition already exists`,
});
}
const referencedRole = findFlatEntityByUniversalIdentifier({
universalIdentifier: flatPermissionFlagToValidate.roleUniversalIdentifier,
flatEntityMaps: flatRoleMaps,
});
if (!isDefined(referencedRole)) {
if (!isNonEmptyString(flatPermissionFlagToValidate.key)) {
validationResult.errors.push({
code: PermissionsExceptionCode.ROLE_NOT_FOUND,
message: t`Role not found`,
userFriendlyMessage: msg`Role not found`,
});
} else {
validationResult.errors.push(
...validateRoleBelongsToCallerApplication({
referencedRole,
buildOptions,
}),
);
if (!referencedRole.isEditable) {
validationResult.errors.push({
code: PermissionsExceptionCode.ROLE_NOT_EDITABLE,
message: t`Role is not editable`,
userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`,
});
}
}
const isValidFlag =
isDefined(flatPermissionFlagToValidate.flag) &&
Object.values(PermissionFlagType).includes(
flatPermissionFlagToValidate.flag,
);
if (!isValidFlag) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Invalid permission flag value`,
userFriendlyMessage: msg`Invalid permission setting`,
code: PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_KEY,
message: t`Permission flag definition key is required`,
userFriendlyMessage: msg`Key is required`,
});
}
const duplicateForSameRole = Object.values(
const duplicateKey = Object.values(
optimisticFlatPermissionFlagMaps.byUniversalIdentifier,
).filter(
(pf) =>
isDefined(pf) &&
pf.roleUniversalIdentifier ===
flatPermissionFlagToValidate.roleUniversalIdentifier &&
pf.flag === flatPermissionFlagToValidate.flag &&
pf.universalIdentifier !==
(definition) =>
isDefined(definition) &&
definition.key === flatPermissionFlagToValidate.key &&
definition.universalIdentifier !==
flatPermissionFlagToValidate.universalIdentifier,
);
if (duplicateForSameRole.length > 0) {
if (duplicateKey.length > 0) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Permission flag for this role and setting already exists`,
userFriendlyMessage: msg`This permission is already set for the role`,
code: PermissionFlagExceptionCode.PERMISSION_FLAG_ALREADY_EXISTS,
message: t`Permission flag definition with key ${flatPermissionFlagToValidate.key} already exists in this workspace`,
userFriendlyMessage: msg`A permission flag with this key already exists`,
});
}
if (
!PERMISSION_FLAG_PERMISSION_TYPES.includes(
flatPermissionFlagToValidate.permissionType,
)
) {
validationResult.errors.push({
code: PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_PERMISSION_TYPE,
message: t`Permission flag definition permission type must be 'settings' or 'tool'`,
userFriendlyMessage: msg`Invalid permission type`,
});
}
@@ -118,17 +93,11 @@ export class FlatPermissionFlagValidatorService {
flatEntityUpdate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: optimisticFlatPermissionFlagMaps,
flatRoleMaps,
},
buildOptions,
}: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.permissionFlag
>): FailedFlatEntityValidation<'permissionFlag', 'update'> {
const existingFlatPermissionFlag = findFlatEntityByUniversalIdentifier({
universalIdentifier,
flatEntityMaps: optimisticFlatPermissionFlagMaps,
});
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier,
@@ -137,79 +106,56 @@ export class FlatPermissionFlagValidatorService {
type: 'update',
});
if (!isDefined(existingFlatPermissionFlag)) {
const existing = findFlatEntityByUniversalIdentifier({
universalIdentifier,
flatEntityMaps: optimisticFlatPermissionFlagMaps,
});
if (!isDefined(existing)) {
validationResult.errors.push({
code: PermissionsExceptionCode.PERMISSION_NOT_FOUND,
message: t`Permission flag to update not found`,
userFriendlyMessage: msg`Permission flag not found`,
code: PermissionFlagExceptionCode.PERMISSION_FLAG_NOT_FOUND,
message: t`Permission flag definition to update not found`,
userFriendlyMessage: msg`Permission flag definition not found`,
});
return validationResult;
}
const updatedFlatPermissionFlag = {
...existingFlatPermissionFlag,
...flatEntityUpdate,
};
const referencedRole = findFlatEntityByUniversalIdentifier({
universalIdentifier: updatedFlatPermissionFlag.roleUniversalIdentifier,
flatEntityMaps: flatRoleMaps,
});
if (!isDefined(referencedRole)) {
if (
!isCallerTwentyStandardApp(buildOptions) &&
belongsToTwentyStandardApp({
universalIdentifier: existing.universalIdentifier,
applicationUniversalIdentifier: existing.applicationUniversalIdentifier,
})
) {
validationResult.errors.push({
code: PermissionsExceptionCode.ROLE_NOT_FOUND,
message: t`Role not found`,
userFriendlyMessage: msg`Role not found`,
code: PermissionFlagExceptionCode.PERMISSION_FLAG_IS_STANDARD,
message: t`Cannot update standard permission flag definition`,
userFriendlyMessage: msg`Cannot update standard permission flag definition`,
});
} else {
validationResult.errors.push(
...validateRoleBelongsToCallerApplication({
referencedRole,
buildOptions,
}),
);
if (!referencedRole.isEditable) {
validationResult.errors.push({
code: PermissionsExceptionCode.ROLE_NOT_EDITABLE,
message: t`Role is not editable`,
userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`,
});
}
}
if (isDefined(flatEntityUpdate.flag)) {
const isValidFlag = Object.values(PermissionFlagType).includes(
flatEntityUpdate.flag as PermissionFlagType,
);
if (!isValidFlag) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Invalid permission flag value`,
userFriendlyMessage: msg`Invalid permission setting`,
});
}
}
const duplicateForSameRole = Object.values(
optimisticFlatPermissionFlagMaps.byUniversalIdentifier,
).filter(
(pf) =>
isDefined(pf) &&
pf.roleUniversalIdentifier ===
updatedFlatPermissionFlag.roleUniversalIdentifier &&
pf.flag === updatedFlatPermissionFlag.flag &&
pf.universalIdentifier !== universalIdentifier,
);
if (duplicateForSameRole.length > 0) {
if (
isDefined(flatEntityUpdate.key) &&
flatEntityUpdate.key !== existing.key
) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Permission flag for this role and setting already exists`,
userFriendlyMessage: msg`This permission is already set for the role`,
code: PermissionFlagExceptionCode.PERMISSION_FLAG_KEY_IMMUTABLE,
message: t`Permission flag definition key cannot be changed after creation`,
userFriendlyMessage: msg`Key cannot be changed`,
});
}
if (
isDefined(flatEntityUpdate.permissionType) &&
!PERMISSION_FLAG_PERMISSION_TYPES.includes(
flatEntityUpdate.permissionType,
)
) {
validationResult.errors.push({
code: PermissionFlagExceptionCode.INVALID_PERMISSION_FLAG_PERMISSION_TYPE,
message: t`Permission flag definition permission type must be 'settings' or 'tool'`,
userFriendlyMessage: msg`Invalid permission type`,
});
}
@@ -220,8 +166,9 @@ export class FlatPermissionFlagValidatorService {
flatEntityToValidate: { universalIdentifier },
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps: optimisticFlatPermissionFlagMaps,
flatRoleMaps,
flatRolePermissionFlagMaps: optimisticFlatRolePermissionFlagMaps,
},
buildOptions,
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.permissionFlag
>): FailedFlatEntityValidation<'permissionFlag', 'delete'> {
@@ -233,30 +180,50 @@ export class FlatPermissionFlagValidatorService {
type: 'delete',
});
const existingFlatPermissionFlag = findFlatEntityByUniversalIdentifier({
const existing = findFlatEntityByUniversalIdentifier({
universalIdentifier,
flatEntityMaps: optimisticFlatPermissionFlagMaps,
});
if (!isDefined(existingFlatPermissionFlag)) {
if (!isDefined(existing)) {
validationResult.errors.push({
code: PermissionsExceptionCode.PERMISSION_NOT_FOUND,
message: t`Permission flag to delete not found`,
userFriendlyMessage: msg`Permission flag not found`,
});
} else {
const referencedRole = findFlatEntityByUniversalIdentifier({
universalIdentifier: existingFlatPermissionFlag.roleUniversalIdentifier,
flatEntityMaps: flatRoleMaps,
code: PermissionFlagExceptionCode.PERMISSION_FLAG_NOT_FOUND,
message: t`Permission flag definition to delete not found`,
userFriendlyMessage: msg`Permission flag definition not found`,
});
if (isDefined(referencedRole) && !referencedRole.isEditable) {
validationResult.errors.push({
code: PermissionsExceptionCode.ROLE_NOT_EDITABLE,
message: t`Role is not editable`,
userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`,
});
}
return validationResult;
}
if (
!isCallerTwentyStandardApp(buildOptions) &&
belongsToTwentyStandardApp({
universalIdentifier: existing.universalIdentifier,
applicationUniversalIdentifier: existing.applicationUniversalIdentifier,
})
) {
validationResult.errors.push({
code: PermissionFlagExceptionCode.PERMISSION_FLAG_IS_STANDARD,
message: t`Cannot delete standard permission flag definition`,
userFriendlyMessage: msg`Cannot delete standard permission flag definition`,
});
}
const isPermissionFlagInUse = Object.values(
optimisticFlatRolePermissionFlagMaps.byUniversalIdentifier,
).some(
(rolePermissionFlag) =>
isDefined(rolePermissionFlag) &&
rolePermissionFlag.permissionFlagUniversalIdentifier ===
existing.universalIdentifier,
);
if (isPermissionFlagInUse) {
validationResult.errors.push({
code: PermissionFlagExceptionCode.PERMISSION_FLAG_IN_USE,
message: t`Permission flag definition with key ${existing.key} is still assigned to a role`,
userFriendlyMessage: msg`Remove this permission from all roles before deleting it`,
});
}
return validationResult;
@@ -0,0 +1,281 @@
import { Injectable } from '@nestjs/common';
import { msg, t } from '@lingui/core/macro';
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 { PermissionsExceptionCode } from 'src/engine/metadata-modules/permissions/permissions.exception';
import { FailedFlatEntityValidation } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/types/failed-flat-entity-validation.type';
import { getEmptyFlatEntityValidationError } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/utils/get-flat-entity-validation-error.util';
import { FlatEntityUpdateValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-update-validation-args.type';
import { UniversalFlatEntityValidationArgs } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/types/universal-flat-entity-validation-args.type';
import { validateRoleBelongsToCallerApplication } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/utils/validate-role-belongs-to-caller-application.util';
@Injectable()
export class FlatRolePermissionFlagValidatorService {
validateFlatRolePermissionFlagCreation({
flatEntityToValidate: flatRolePermissionFlagToValidate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps,
flatRolePermissionFlagMaps: optimisticFlatRolePermissionFlagMaps,
flatRoleMaps,
},
buildOptions,
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.rolePermissionFlag
>): FailedFlatEntityValidation<'rolePermissionFlag', 'create'> {
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier:
flatRolePermissionFlagToValidate.universalIdentifier,
permissionFlagUniversalIdentifier:
flatRolePermissionFlagToValidate.permissionFlagUniversalIdentifier,
roleUniversalIdentifier:
flatRolePermissionFlagToValidate.roleUniversalIdentifier,
},
metadataName: 'rolePermissionFlag',
type: 'create',
});
const existingByUniversalId = findFlatEntityByUniversalIdentifier({
universalIdentifier: flatRolePermissionFlagToValidate.universalIdentifier,
flatEntityMaps: optimisticFlatRolePermissionFlagMaps,
});
if (isDefined(existingByUniversalId)) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Role permission flag with universal identifier ${flatRolePermissionFlagToValidate.universalIdentifier} already exists`,
userFriendlyMessage: msg`Role permission flag already exists`,
});
}
const referencedRole = findFlatEntityByUniversalIdentifier({
universalIdentifier:
flatRolePermissionFlagToValidate.roleUniversalIdentifier,
flatEntityMaps: flatRoleMaps,
});
if (!isDefined(referencedRole)) {
validationResult.errors.push({
code: PermissionsExceptionCode.ROLE_NOT_FOUND,
message: t`Role not found`,
userFriendlyMessage: msg`Role not found`,
});
} else {
validationResult.errors.push(
...validateRoleBelongsToCallerApplication({
referencedRole,
buildOptions,
}),
);
if (!referencedRole.isEditable) {
validationResult.errors.push({
code: PermissionsExceptionCode.ROLE_NOT_EDITABLE,
message: t`Role is not editable`,
userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`,
});
}
}
if (
isDefined(
flatRolePermissionFlagToValidate.permissionFlagUniversalIdentifier,
)
) {
const referencedPermissionFlag = findFlatEntityByUniversalIdentifier({
universalIdentifier:
flatRolePermissionFlagToValidate.permissionFlagUniversalIdentifier,
flatEntityMaps: flatPermissionFlagMaps,
});
if (!isDefined(referencedPermissionFlag)) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Permission flag not found`,
userFriendlyMessage: msg`Invalid permission setting`,
});
}
}
const duplicateForSameRole = Object.values(
optimisticFlatRolePermissionFlagMaps.byUniversalIdentifier,
).filter(
(pf) =>
isDefined(pf) &&
pf.roleUniversalIdentifier ===
flatRolePermissionFlagToValidate.roleUniversalIdentifier &&
pf.permissionFlagUniversalIdentifier ===
flatRolePermissionFlagToValidate.permissionFlagUniversalIdentifier &&
pf.universalIdentifier !==
flatRolePermissionFlagToValidate.universalIdentifier,
);
if (duplicateForSameRole.length > 0) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Permission flag for this role and setting already exists`,
userFriendlyMessage: msg`This permission is already set for the role`,
});
}
return validationResult;
}
validateFlatRolePermissionFlagUpdate({
universalIdentifier,
flatEntityUpdate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatPermissionFlagMaps,
flatRolePermissionFlagMaps: optimisticFlatRolePermissionFlagMaps,
flatRoleMaps,
},
buildOptions,
}: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.rolePermissionFlag
>): FailedFlatEntityValidation<'rolePermissionFlag', 'update'> {
const existingFlatRolePermissionFlag = findFlatEntityByUniversalIdentifier({
universalIdentifier,
flatEntityMaps: optimisticFlatRolePermissionFlagMaps,
});
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier,
},
metadataName: 'rolePermissionFlag',
type: 'update',
});
if (!isDefined(existingFlatRolePermissionFlag)) {
validationResult.errors.push({
code: PermissionsExceptionCode.PERMISSION_NOT_FOUND,
message: t`Permission flag to update not found`,
userFriendlyMessage: msg`Permission flag not found`,
});
return validationResult;
}
const updatedFlatRolePermissionFlag = {
...existingFlatRolePermissionFlag,
...flatEntityUpdate,
};
const referencedRole = findFlatEntityByUniversalIdentifier({
universalIdentifier:
updatedFlatRolePermissionFlag.roleUniversalIdentifier,
flatEntityMaps: flatRoleMaps,
});
if (!isDefined(referencedRole)) {
validationResult.errors.push({
code: PermissionsExceptionCode.ROLE_NOT_FOUND,
message: t`Role not found`,
userFriendlyMessage: msg`Role not found`,
});
} else {
validationResult.errors.push(
...validateRoleBelongsToCallerApplication({
referencedRole,
buildOptions,
}),
);
if (!referencedRole.isEditable) {
validationResult.errors.push({
code: PermissionsExceptionCode.ROLE_NOT_EDITABLE,
message: t`Role is not editable`,
userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`,
});
}
}
if (isDefined(flatEntityUpdate.permissionFlagUniversalIdentifier)) {
const referencedPermissionFlag = findFlatEntityByUniversalIdentifier({
universalIdentifier:
flatEntityUpdate.permissionFlagUniversalIdentifier as string,
flatEntityMaps: flatPermissionFlagMaps,
});
if (!isDefined(referencedPermissionFlag)) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Permission flag not found`,
userFriendlyMessage: msg`Invalid permission setting`,
});
}
}
const duplicateForSameRole = Object.values(
optimisticFlatRolePermissionFlagMaps.byUniversalIdentifier,
).filter(
(pf) =>
isDefined(pf) &&
pf.roleUniversalIdentifier ===
updatedFlatRolePermissionFlag.roleUniversalIdentifier &&
pf.permissionFlagUniversalIdentifier ===
updatedFlatRolePermissionFlag.permissionFlagUniversalIdentifier &&
pf.universalIdentifier !== universalIdentifier,
);
if (duplicateForSameRole.length > 0) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Permission flag for this role and setting already exists`,
userFriendlyMessage: msg`This permission is already set for the role`,
});
}
return validationResult;
}
validateFlatRolePermissionFlagDeletion({
flatEntityToValidate: { universalIdentifier },
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatRolePermissionFlagMaps: optimisticFlatRolePermissionFlagMaps,
flatRoleMaps,
},
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.rolePermissionFlag
>): FailedFlatEntityValidation<'rolePermissionFlag', 'delete'> {
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier,
},
metadataName: 'rolePermissionFlag',
type: 'delete',
});
const existingFlatRolePermissionFlag = findFlatEntityByUniversalIdentifier({
universalIdentifier,
flatEntityMaps: optimisticFlatRolePermissionFlagMaps,
});
if (!isDefined(existingFlatRolePermissionFlag)) {
validationResult.errors.push({
code: PermissionsExceptionCode.PERMISSION_NOT_FOUND,
message: t`Permission flag to delete not found`,
userFriendlyMessage: msg`Permission flag not found`,
});
} else {
const referencedRole = findFlatEntityByUniversalIdentifier({
universalIdentifier:
existingFlatRolePermissionFlag.roleUniversalIdentifier,
flatEntityMaps: flatRoleMaps,
});
if (isDefined(referencedRole) && !referencedRole.isEditable) {
validationResult.errors.push({
code: PermissionsExceptionCode.ROLE_NOT_EDITABLE,
message: t`Role is not editable`,
userFriendlyMessage: msg`This role cannot be modified because it is a system role. Only custom roles can be edited.`,
});
}
}
return validationResult;
}
}
@@ -17,6 +17,7 @@ import { FlatPageLayoutTabValidatorService } from 'src/engine/workspace-manager/
import { FlatPageLayoutValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-validator.service';
import { FlatPageLayoutWidgetValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-page-layout-widget-validator.service';
import { FlatPermissionFlagValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-permission-flag-validator.service';
import { FlatRolePermissionFlagValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-role-permission-flag-validator.service';
import { FlatRoleTargetValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-role-target-validator.service';
import { FlatRoleValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-role-validator.service';
import { FlatRowLevelPermissionPredicateGroupValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-row-level-permission-predicate-group-validator.service';
@@ -45,6 +46,7 @@ import { FlatConnectionProviderValidatorService } from 'src/engine/workspace-man
FlatViewSortValidatorService,
FlatFieldPermissionValidatorService,
FlatObjectPermissionValidatorService,
FlatRolePermissionFlagValidatorService,
FlatPermissionFlagValidatorService,
FlatIndexValidatorService,
FlatFieldMetadataValidatorService,
@@ -78,6 +80,7 @@ import { FlatConnectionProviderValidatorService } from 'src/engine/workspace-man
FlatViewSortValidatorService,
FlatFieldPermissionValidatorService,
FlatObjectPermissionValidatorService,
FlatRolePermissionFlagValidatorService,
FlatPermissionFlagValidatorService,
FlatIndexValidatorService,
FlatFieldMetadataValidatorService,
@@ -16,6 +16,7 @@ import { WorkspaceMigrationPageLayoutTabActionsBuilderService } from 'src/engine
import { WorkspaceMigrationPageLayoutWidgetActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout-widget/workspace-migration-page-layout-widget-actions-builder.service';
import { WorkspaceMigrationPageLayoutActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/page-layout/workspace-migration-page-layout-actions-builder.service';
import { WorkspaceMigrationPermissionFlagActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/workspace-migration-permission-flag-actions-builder.service';
import { WorkspaceMigrationRolePermissionFlagActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/workspace-migration-role-permission-flag-actions-builder.service';
import { WorkspaceMigrationRoleTargetActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-target/workspace-migration-role-target-actions-builder.service';
import { WorkspaceMigrationRoleActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role/workspace-migration-role-actions-builder.service';
import { WorkspaceMigrationRowLevelPermissionPredicateGroupActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/row-level-permission-predicate-group/workspace-migration-row-level-permission-predicate-group-actions-builder.service';
@@ -47,6 +48,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationViewSortActionsBuilderService,
WorkspaceMigrationFieldPermissionActionsBuilderService,
WorkspaceMigrationObjectPermissionActionsBuilderService,
WorkspaceMigrationRolePermissionFlagActionsBuilderService,
WorkspaceMigrationPermissionFlagActionsBuilderService,
WorkspaceMigrationIndexActionsBuilderService,
WorkspaceMigrationLogicFunctionActionsBuilderService,
@@ -79,6 +81,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationViewSortActionsBuilderService,
WorkspaceMigrationFieldPermissionActionsBuilderService,
WorkspaceMigrationObjectPermissionActionsBuilderService,
WorkspaceMigrationRolePermissionFlagActionsBuilderService,
WorkspaceMigrationPermissionFlagActionsBuilderService,
WorkspaceMigrationFieldActionsBuilderService,
WorkspaceMigrationLogicFunctionActionsBuilderService,
@@ -2,15 +2,15 @@ import { Injectable } from '@nestjs/common';
import { v4 } from 'uuid';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util';
import { getUniversalFlatEntityEmptyForeignKeyAggregators } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/reset-universal-flat-entity-foreign-key-aggregators.util';
import {
FlatCreatePermissionFlagAction,
UniversalCreatePermissionFlagAction,
type FlatCreatePermissionFlagAction,
type UniversalCreatePermissionFlagAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/types/workspace-migration-permission-flag-action.type';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
type WorkspaceMigrationActionRunnerArgs,
type WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
@@ -18,26 +18,29 @@ export class CreatePermissionFlagActionHandlerService extends WorkspaceMigration
'create',
'permissionFlag',
) {
constructor() {
super();
}
override async transpileUniversalActionToFlatAction({
action,
allFlatEntityMaps,
flatApplication,
workspaceId,
}: WorkspaceMigrationActionRunnerArgs<UniversalCreatePermissionFlagAction>): Promise<FlatCreatePermissionFlagAction> {
const { roleId } = resolveUniversalRelationIdentifiersToIds({
flatEntityMaps: allFlatEntityMaps,
metadataName: action.metadataName,
universalForeignKeyValues: action.flatEntity,
});
const emptyUniversalForeignKeyAggregators =
getUniversalFlatEntityEmptyForeignKeyAggregators({
metadataName: 'permissionFlag',
});
return {
...action,
flatEntity: {
...action.flatEntity,
roleId,
applicationId: flatApplication.id,
id: action.id ?? v4(),
workspaceId,
rolePermissionFlagIds: [],
...emptyUniversalForeignKeyAggregators,
},
};
}
@@ -1,14 +1,14 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity';
import {
FlatDeletePermissionFlagAction,
UniversalDeletePermissionFlagAction,
type FlatDeletePermissionFlagAction,
type UniversalDeletePermissionFlagAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/types/workspace-migration-permission-flag-action.type';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
type WorkspaceMigrationActionRunnerArgs,
type WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
@@ -31,12 +31,12 @@ export class DeletePermissionFlagActionHandlerService extends WorkspaceMigration
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const permissionFlagRepository =
const repository =
queryRunner.manager.getRepository<PermissionFlagEntity>(
PermissionFlagEntity,
);
await permissionFlagRepository.delete({
await repository.delete({
id: flatAction.entityId,
workspaceId,
});
@@ -1,16 +1,16 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { PermissionFlagEntity } from 'src/engine/metadata-modules/permission-flag/permission-flag.entity';
import { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util';
import {
FlatUpdatePermissionFlagAction,
UniversalUpdatePermissionFlagAction,
type FlatUpdatePermissionFlagAction,
type UniversalUpdatePermissionFlagAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/permission-flag/types/workspace-migration-permission-flag-action.type';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
type WorkspaceMigrationActionRunnerArgs,
type WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
@@ -18,16 +18,12 @@ export class UpdatePermissionFlagActionHandlerService extends WorkspaceMigration
'update',
'permissionFlag',
) {
constructor() {
super();
}
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalUpdatePermissionFlagAction>,
): Promise<FlatUpdatePermissionFlagAction> {
const { action, allFlatEntityMaps } = context;
const flatPermissionFlag = findFlatEntityByUniversalIdentifierOrThrow({
const flatDefinition = findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: allFlatEntityMaps.flatPermissionFlagMaps,
universalIdentifier: action.universalIdentifier,
});
@@ -41,7 +37,7 @@ export class UpdatePermissionFlagActionHandlerService extends WorkspaceMigration
return {
type: 'update',
metadataName: 'permissionFlag',
entityId: flatPermissionFlag.id,
entityId: flatDefinition.id,
update,
};
}
@@ -52,15 +48,12 @@ export class UpdatePermissionFlagActionHandlerService extends WorkspaceMigration
const { flatAction, queryRunner, workspaceId } = context;
const { entityId, update } = flatAction;
const permissionFlagRepository =
const repository =
queryRunner.manager.getRepository<PermissionFlagEntity>(
PermissionFlagEntity,
);
await permissionFlagRepository.update(
{ id: entityId, workspaceId },
update,
);
await repository.update({ id: entityId, workspaceId }, update);
}
async executeForWorkspaceSchema(
@@ -0,0 +1,62 @@
import { Injectable } from '@nestjs/common';
import { v4 } from 'uuid';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util';
import {
FlatCreateRolePermissionFlagAction,
UniversalCreateRolePermissionFlagAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/types/workspace-migration-role-permission-flag-action.type';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
export class CreateRolePermissionFlagActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create',
'rolePermissionFlag',
) {
override async transpileUniversalActionToFlatAction({
action,
allFlatEntityMaps,
flatApplication,
workspaceId,
}: WorkspaceMigrationActionRunnerArgs<UniversalCreateRolePermissionFlagAction>): Promise<FlatCreateRolePermissionFlagAction> {
const relationIds = resolveUniversalRelationIdentifiersToIds({
flatEntityMaps: allFlatEntityMaps,
metadataName: action.metadataName,
universalForeignKeyValues: action.flatEntity,
});
return {
...action,
flatEntity: {
...action.flatEntity,
...relationIds,
applicationId: flatApplication.id,
id: action.id ?? v4(),
workspaceId,
},
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatCreateRolePermissionFlagAction>,
): Promise<void> {
const { flatAction, queryRunner } = context;
const { flatEntity } = flatAction;
await this.insertFlatEntitiesInRepository({
queryRunner,
flatEntities: [flatEntity],
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatCreateRolePermissionFlagAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,50 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity';
import {
FlatDeleteRolePermissionFlagAction,
UniversalDeleteRolePermissionFlagAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/types/workspace-migration-role-permission-flag-action.type';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteRolePermissionFlagActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete',
'rolePermissionFlag',
) {
constructor() {
super();
}
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalDeleteRolePermissionFlagAction>,
): Promise<FlatDeleteRolePermissionFlagAction> {
return this.transpileUniversalDeleteActionToFlatDeleteAction(context);
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatDeleteRolePermissionFlagAction>,
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const rolePermissionFlagRepository =
queryRunner.manager.getRepository<RolePermissionFlagEntity>(
RolePermissionFlagEntity,
);
await rolePermissionFlagRepository.delete({
id: flatAction.entityId,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatDeleteRolePermissionFlagAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,71 @@
import { Injectable } from '@nestjs/common';
import { WorkspaceMigrationRunnerActionHandler } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/interfaces/workspace-migration-runner-action-handler-service.interface';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { RolePermissionFlagEntity } from 'src/engine/metadata-modules/role-permission-flag/role-permission-flag.entity';
import { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util';
import {
FlatUpdateRolePermissionFlagAction,
UniversalUpdateRolePermissionFlagAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/role-permission-flag/types/workspace-migration-role-permission-flag-action.type';
import {
WorkspaceMigrationActionRunnerArgs,
WorkspaceMigrationActionRunnerContext,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
export class UpdateRolePermissionFlagActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update',
'rolePermissionFlag',
) {
constructor() {
super();
}
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalUpdateRolePermissionFlagAction>,
): Promise<FlatUpdateRolePermissionFlagAction> {
const { action, allFlatEntityMaps } = context;
const flatRolePermissionFlag = findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: allFlatEntityMaps.flatRolePermissionFlagMaps,
universalIdentifier: action.universalIdentifier,
});
const update = resolveUniversalUpdateRelationIdentifiersToIds({
metadataName: 'rolePermissionFlag',
universalUpdate: action.update,
allFlatEntityMaps,
});
return {
type: 'update',
metadataName: 'rolePermissionFlag',
entityId: flatRolePermissionFlag.id,
update,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatUpdateRolePermissionFlagAction>,
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const { entityId, update } = flatAction;
const rolePermissionFlagRepository =
queryRunner.manager.getRepository<RolePermissionFlagEntity>(
RolePermissionFlagEntity,
);
await rolePermissionFlagRepository.update(
{ id: entityId, workspaceId },
update,
);
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatUpdateRolePermissionFlagAction>,
): Promise<void> {
return;
}
}
@@ -44,7 +44,7 @@ export class CreateRoleActionHandlerService extends WorkspaceMigrationRunnerActi
rowLevelPermissionPredicateIds: [],
rowLevelPermissionPredicateGroupIds: [],
objectPermissionIds: [],
permissionFlagIds: [],
rolePermissionFlagIds: [],
fieldPermissionIds: [],
...emptyUniversalForeignKeyAggregators,
},
@@ -46,6 +46,9 @@ import { UpdatePageLayoutActionHandlerService } from 'src/engine/workspace-manag
import { CreatePermissionFlagActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/create-permission-flag-action-handler.service';
import { DeletePermissionFlagActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/delete-permission-flag-action-handler.service';
import { UpdatePermissionFlagActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/permission-flag/services/update-permission-flag-action-handler.service';
import { CreateRolePermissionFlagActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/create-role-permission-flag-action-handler.service';
import { DeleteRolePermissionFlagActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/delete-role-permission-flag-action-handler.service';
import { UpdateRolePermissionFlagActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-permission-flag/services/update-role-permission-flag-action-handler.service';
import { CreateRoleTargetActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-target/services/create-role-target-action-handler.service';
import { DeleteRoleTargetActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-target/services/delete-role-target-action-handler.service';
import { UpdateRoleTargetActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/role-target/services/update-role-target-action-handler.service';
@@ -151,6 +154,10 @@ import { UpdateConnectionProviderActionHandlerService } from 'src/engine/workspa
DeleteRoleTargetActionHandlerService,
UpdateRoleTargetActionHandlerService,
CreateRolePermissionFlagActionHandlerService,
UpdateRolePermissionFlagActionHandlerService,
DeleteRolePermissionFlagActionHandlerService,
CreatePermissionFlagActionHandlerService,
UpdatePermissionFlagActionHandlerService,
DeletePermissionFlagActionHandlerService,
@@ -19,6 +19,7 @@ export const METADATA_EVENTS_TO_EMIT = {
pageLayoutTab: true,
commandMenuItem: true,
navigationMenuItem: true,
rolePermissionFlag: true,
permissionFlag: true,
objectPermission: true,
fieldPermission: true,
@@ -90,7 +90,7 @@ export class WorkspaceMigrationRunnerService {
const shouldInvalidateRolesPermissionsCache =
flatMapsKeysSet.has('flatObjectPermissionMaps') ||
flatMapsKeysSet.has('flatFieldPermissionMaps') ||
flatMapsKeysSet.has('flatPermissionFlagMaps');
flatMapsKeysSet.has('flatRolePermissionFlagMaps');
if (
shouldIncrementMetadataGraphqlSchemaVersion ||
@@ -92,6 +92,7 @@ const deriveAllMetadataEventsFromCreateAction = (
case 'commandMenuItem':
case 'frontComponent':
case 'navigationMenuItem':
case 'rolePermissionFlag':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
@@ -56,6 +56,7 @@ const deriveAllMetadataEventsFromDeleteAction = ({
case 'commandMenuItem':
case 'frontComponent':
case 'navigationMenuItem':
case 'rolePermissionFlag':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
@@ -134,6 +134,7 @@ const deriveAllMetadataEventsFromUpdateAction = ({
case 'commandMenuItem':
case 'frontComponent':
case 'navigationMenuItem':
case 'rolePermissionFlag':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
@@ -68,6 +68,7 @@ export const optimisticallyApplyCreateActionOnAllFlatEntityMaps = ({
case 'commandMenuItem':
case 'frontComponent':
case 'navigationMenuItem':
case 'rolePermissionFlag':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
@@ -40,6 +40,7 @@ export const optimisticallyApplyDeleteActionOnAllFlatEntityMaps = ({
case 'commandMenuItem':
case 'frontComponent':
case 'navigationMenuItem':
case 'rolePermissionFlag':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
@@ -61,6 +61,7 @@ export const optimisticallyApplyUpdateActionOnAllFlatEntityMaps = ({
case 'commandMenuItem':
case 'frontComponent':
case 'navigationMenuItem':
case 'rolePermissionFlag':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':