Migrate field permission to syncable entity (#18751)

Co-authored-by: Copilot Autofix powered by AI <223894421+github-code-quality[bot]@users.noreply.github.com>
Co-authored-by: Charles Bochet <charles@twenty.com>
This commit is contained in:
Abdul Rahman
2026-03-27 19:17:27 +05:30
committed by GitHub
parent c2b058a6a7
commit 5efe69f8d3
105 changed files with 2956 additions and 490 deletions
@@ -95,6 +95,7 @@ export const createStandardFieldFlatMetadata = <
morphId: null,
viewFieldIds: [],
viewFilterIds: [],
fieldPermissionIds: [],
kanbanAggregateOperationViewIds: [],
calendarViewIds: [],
mainGroupByFieldMetadataViewIds: [],
@@ -107,6 +108,7 @@ export const createStandardFieldFlatMetadata = <
relationTargetFieldMetadataUniversalIdentifier: null,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
fieldPermissionUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
@@ -113,6 +113,7 @@ export const createStandardRelationFieldFlatMetadata = <
morphId,
viewFieldIds: [],
viewFilterIds: [],
fieldPermissionIds: [],
kanbanAggregateOperationViewIds: [],
calendarViewIds: [],
mainGroupByFieldMetadataViewIds: [],
@@ -127,6 +128,7 @@ export const createStandardRelationFieldFlatMetadata = <
targetFieldDefinition.universalIdentifier,
viewFilterUniversalIdentifiers: [],
viewFieldUniversalIdentifiers: [],
fieldPermissionUniversalIdentifiers: [],
kanbanAggregateOperationViewUniversalIdentifiers: [],
calendarViewUniversalIdentifiers: [],
mainGroupByFieldMetadataViewUniversalIdentifiers: [],
@@ -96,6 +96,7 @@ export const createStandardObjectFlatMetadata = <
fieldIds: [],
indexMetadataIds: [],
objectPermissionIds: [],
fieldPermissionIds: [],
viewIds: [],
createdAt: now,
updatedAt: now,
@@ -103,6 +104,7 @@ export const createStandardObjectFlatMetadata = <
applicationUniversalIdentifier: twentyStandardApplicationId,
fieldUniversalIdentifiers: [],
objectPermissionUniversalIdentifiers: [],
fieldPermissionUniversalIdentifiers: [],
viewUniversalIdentifiers: [],
indexMetadataUniversalIdentifiers: [],
labelIdentifierFieldMetadataUniversalIdentifier,
@@ -76,6 +76,7 @@ export const createStandardRoleFlatMetadata = ({
permissionFlagUniversalIdentifiers: [],
objectPermissionUniversalIdentifiers: [],
fieldPermissionIds: [],
fieldPermissionUniversalIdentifiers: [],
objectPermissionIds: [],
roleTargetIds: [],
roleTargetUniversalIdentifiers: [],
@@ -169,6 +169,7 @@ export const enrichCreateWorkspaceMigrationActionsWithIds = ({
case 'viewSort':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
case 'webhook': {
if (!isDefined(idByUniversalIdentifier)) {
return action;
@@ -15,6 +15,7 @@ import { aggregateOrchestratorActionsReport } from 'src/engine/workspace-manager
import { crossEntityTransversalValidation } from 'src/engine/workspace-manager/workspace-migration/utils/cross-entity-transversal-validation.util';
import { WorkspaceMigrationAgentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/agent/workspace-migration-agent-actions-builder.service';
import { WorkspaceMigrationCommandMenuItemActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/command-menu-item/workspace-migration-command-menu-item-actions-builder.service';
import { WorkspaceMigrationFieldPermissionActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field-permission/workspace-migration-field-permission-actions-builder.service';
import { WorkspaceMigrationFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/workspace-migration-field-actions-builder.service';
import { WorkspaceMigrationFrontComponentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/front-component/workspace-migration-front-component-actions-builder.service';
import { WorkspaceMigrationIndexActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/index/workspace-migration-index-actions-builder.service';
@@ -52,6 +53,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
private readonly workspaceMigrationViewGroupActionsBuilderService: WorkspaceMigrationViewGroupActionsBuilderService,
private readonly workspaceMigrationViewFieldGroupActionsBuilderService: WorkspaceMigrationViewFieldGroupActionsBuilderService,
private readonly workspaceMigrationViewSortActionsBuilderService: WorkspaceMigrationViewSortActionsBuilderService,
private readonly workspaceMigrationFieldPermissionActionsBuilderService: WorkspaceMigrationFieldPermissionActionsBuilderService,
private readonly workspaceMigrationObjectPermissionActionsBuilderService: WorkspaceMigrationObjectPermissionActionsBuilderService,
private readonly workspaceMigrationPermissionFlagActionsBuilderService: WorkspaceMigrationPermissionFlagActionsBuilderService,
private readonly workspaceMigrationLogicFunctionActionsBuilderService: WorkspaceMigrationLogicFunctionActionsBuilderService,
@@ -145,6 +147,7 @@ export class WorkspaceMigrationBuildOrchestratorService {
flatRowLevelPermissionPredicateGroupMaps,
flatRoleMaps,
flatObjectPermissionMaps,
flatFieldPermissionMaps,
flatPermissionFlagMaps,
flatRoleTargetMaps,
flatAgentMaps,
@@ -515,6 +518,34 @@ export class WorkspaceMigrationBuildOrchestratorService {
}
}
if (isDefined(flatFieldPermissionMaps)) {
const {
from: fromFlatFieldPermissionMaps,
to: toFlatFieldPermissionMaps,
} = flatFieldPermissionMaps;
const fieldPermissionResult =
await this.workspaceMigrationFieldPermissionActionsBuilderService.validateAndBuild(
{
additionalCacheDataMaps,
from: fromFlatFieldPermissionMaps,
to: toFlatFieldPermissionMaps,
buildOptions,
dependencyOptimisticFlatEntityMaps: optimisticAllFlatEntityMaps,
workspaceId,
},
);
if (fieldPermissionResult.status === 'fail') {
orchestratorFailureReport.fieldPermission.push(
...fieldPermissionResult.errors,
);
} else {
orchestratorActionsReport.fieldPermission =
fieldPermissionResult.actions;
}
}
if (isDefined(flatPermissionFlagMaps)) {
const { from: fromFlatPermissionFlagMaps, to: toFlatPermissionFlagMaps } =
flatPermissionFlagMaps;
@@ -876,6 +907,12 @@ export class WorkspaceMigrationBuildOrchestratorService {
...aggregatedOrchestratorActionsReport.objectPermission.update,
///
// Field permissions
...aggregatedOrchestratorActionsReport.fieldPermission.delete,
...aggregatedOrchestratorActionsReport.fieldPermission.create,
...aggregatedOrchestratorActionsReport.fieldPermission.update,
///
// Permission flags
...aggregatedOrchestratorActionsReport.permissionFlag.delete,
...aggregatedOrchestratorActionsReport.permissionFlag.create,
@@ -1,4 +1,4 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should match snapshot 1`] = `
{
@@ -6,12 +6,14 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should matc
"commandMenuItem": [],
"fieldMetadata": [
"viewSortUniversalIdentifiers",
"fieldPermissionUniversalIdentifiers",
"viewFieldUniversalIdentifiers",
"viewFilterUniversalIdentifiers",
"kanbanAggregateOperationViewUniversalIdentifiers",
"calendarViewUniversalIdentifiers",
"mainGroupByFieldMetadataViewUniversalIdentifiers",
],
"fieldPermission": [],
"frontComponent": [],
"index": [],
"logicFunction": [],
@@ -20,6 +22,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should matc
"fieldUniversalIdentifiers",
"indexMetadataUniversalIdentifiers",
"objectPermissionUniversalIdentifiers",
"fieldPermissionUniversalIdentifiers",
"viewUniversalIdentifiers",
],
"objectPermission": [],
@@ -35,6 +38,7 @@ exports[`ALL_UNIVERSAL_FLAT_ENTITY_FOREIGN_KEY_AGGREGATOR_PROPERTIES should matc
"roleTargetUniversalIdentifiers",
"objectPermissionUniversalIdentifiers",
"permissionFlagUniversalIdentifiers",
"fieldPermissionUniversalIdentifiers",
"rowLevelPermissionPredicateUniversalIdentifiers",
"rowLevelPermissionPredicateGroupUniversalIdentifiers",
],
@@ -39,6 +39,7 @@ export const ALL_JSONB_PROPERTIES_WITH_SERIALIZED_RELATION_BY_METADATA_NAME = {
navigationMenuItem: {},
permissionFlag: {},
objectPermission: {},
fieldPermission: {},
frontComponent: {},
} as const satisfies {
[P in AllMetadataName]: Partial<{
@@ -0,0 +1,7 @@
import { type FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
import { type UniversalFlatEntityFrom } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/universal-flat-entity-from.type';
export type UniversalFlatFieldPermission = UniversalFlatEntityFrom<
FieldPermissionEntity,
'fieldPermission'
>;
@@ -12,6 +12,8 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a crea
"createdAt": "2024-01-01T00:00:00.000Z",
"defaultValue": null,
"description": "default flat field metadata description",
"fieldPermissionIds": [],
"fieldPermissionUniversalIdentifiers": [],
"icon": "icon",
"id": "field-id-1",
"isActive": true,
@@ -75,6 +77,8 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect a dele
"createdAt": "2024-01-01T00:00:00.000Z",
"defaultValue": null,
"description": "default flat field metadata description",
"fieldPermissionIds": [],
"fieldPermissionUniversalIdentifiers": [],
"icon": "icon",
"id": "field-id-1",
"isActive": true,
@@ -152,6 +156,8 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create
"createdAt": "2024-01-01T00:00:00.000Z",
"defaultValue": null,
"description": "default flat field metadata description",
"fieldPermissionIds": [],
"fieldPermissionUniversalIdentifiers": [],
"icon": "icon",
"id": "field-id-3",
"isActive": true,
@@ -201,6 +207,8 @@ exports[`flatEntityDeletedCreatedUpdatedMatrixDispatcher It should detect create
"createdAt": "2024-01-01T00:00:00.000Z",
"defaultValue": null,
"description": "default flat field metadata description",
"fieldPermissionIds": [],
"fieldPermissionUniversalIdentifiers": [],
"icon": "icon",
"id": "field-id-2",
"isActive": true,
@@ -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 FlatCreateFieldPermissionAction =
BaseFlatCreateWorkspaceMigrationAction<'fieldPermission'>;
export type FlatUpdateFieldPermissionAction =
BaseFlatUpdateWorkspaceMigrationAction<'fieldPermission'>;
export type FlatDeleteFieldPermissionAction =
BaseFlatDeleteWorkspaceMigrationAction<'fieldPermission'>;
export type UniversalCreateFieldPermissionAction =
BaseUniversalCreateWorkspaceMigrationAction<'fieldPermission'>;
export type UniversalUpdateFieldPermissionAction =
BaseUniversalUpdateWorkspaceMigrationAction<'fieldPermission'>;
export type UniversalDeleteFieldPermissionAction =
BaseUniversalDeleteWorkspaceMigrationAction<'fieldPermission'>;
@@ -0,0 +1,120 @@
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 { UniversalUpdateFieldPermissionAction } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field-permission/types/workspace-migration-field-permission-action.type';
import { FlatFieldPermissionValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-field-permission-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 WorkspaceMigrationFieldPermissionActionsBuilderService extends WorkspaceEntityMigrationBuilderService<
typeof ALL_METADATA_NAME.fieldPermission
> {
constructor(
private readonly flatFieldPermissionValidatorService: FlatFieldPermissionValidatorService,
) {
super(ALL_METADATA_NAME.fieldPermission);
}
protected validateFlatEntityCreation(
args: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.fieldPermission
>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.fieldPermission,
'create'
> {
const validationResult =
this.flatFieldPermissionValidatorService.validateFlatFieldPermissionCreation(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate: flatFieldPermissionToValidate } = args;
return {
status: 'success',
action: {
type: 'create',
metadataName: 'fieldPermission',
flatEntity: flatFieldPermissionToValidate,
},
};
}
protected validateFlatEntityDeletion(
args: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.fieldPermission
>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.fieldPermission,
'delete'
> {
const validationResult =
this.flatFieldPermissionValidatorService.validateFlatFieldPermissionDeletion(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { flatEntityToValidate: flatFieldPermissionToValidate } = args;
return {
status: 'success',
action: {
type: 'delete',
metadataName: 'fieldPermission',
universalIdentifier: flatFieldPermissionToValidate.universalIdentifier,
},
};
}
protected validateFlatEntityUpdate(
args: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.fieldPermission
>,
): UniversalFlatEntityValidationReturnType<
typeof ALL_METADATA_NAME.fieldPermission,
'update'
> {
const validationResult =
this.flatFieldPermissionValidatorService.validateFlatFieldPermissionUpdate(
args,
);
if (validationResult.errors.length > 0) {
return {
status: 'fail',
...validationResult,
};
}
const { universalIdentifier, flatEntityUpdate } = args;
const updateFieldPermissionAction: UniversalUpdateFieldPermissionAction = {
type: 'update',
metadataName: 'fieldPermission',
universalIdentifier,
update: flatEntityUpdate,
};
return {
status: 'success',
action: updateFieldPermissionAction,
};
}
}
@@ -0,0 +1,290 @@
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';
@Injectable()
export class FlatFieldPermissionValidatorService {
validateFlatFieldPermissionCreation({
flatEntityToValidate: flatFieldPermissionToValidate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatFieldPermissionMaps: optimisticFlatFieldPermissionMaps,
flatRoleMaps,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
},
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.fieldPermission
>): FailedFlatEntityValidation<'fieldPermission', 'create'> {
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier: flatFieldPermissionToValidate.universalIdentifier,
roleUniversalIdentifier:
flatFieldPermissionToValidate.roleUniversalIdentifier,
objectMetadataUniversalIdentifier:
flatFieldPermissionToValidate.objectMetadataUniversalIdentifier,
fieldMetadataUniversalIdentifier:
flatFieldPermissionToValidate.fieldMetadataUniversalIdentifier,
},
metadataName: 'fieldPermission',
type: 'create',
});
const existingByUniversalId = findFlatEntityByUniversalIdentifier({
universalIdentifier: flatFieldPermissionToValidate.universalIdentifier,
flatEntityMaps: optimisticFlatFieldPermissionMaps,
});
if (isDefined(existingByUniversalId)) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Field permission with universal identifier ${flatFieldPermissionToValidate.universalIdentifier} already exists`,
userFriendlyMessage: msg`Field permission already exists`,
});
}
const referencedRole = findFlatEntityByUniversalIdentifier({
universalIdentifier:
flatFieldPermissionToValidate.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 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 referencedObjectMetadata = findFlatEntityByUniversalIdentifier({
universalIdentifier:
flatFieldPermissionToValidate.objectMetadataUniversalIdentifier,
flatEntityMaps: flatObjectMetadataMaps,
});
if (!isDefined(referencedObjectMetadata)) {
validationResult.errors.push({
code: PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND,
message: t`Object metadata not found`,
userFriendlyMessage: msg`Object metadata not found`,
});
}
const referencedFieldMetadata = findFlatEntityByUniversalIdentifier({
universalIdentifier:
flatFieldPermissionToValidate.fieldMetadataUniversalIdentifier,
flatEntityMaps: flatFieldMetadataMaps,
});
if (!isDefined(referencedFieldMetadata)) {
validationResult.errors.push({
code: PermissionsExceptionCode.FIELD_METADATA_NOT_FOUND,
message: t`Field metadata not found`,
userFriendlyMessage: msg`Field metadata not found`,
});
}
const duplicateForSameRoleObjectField = Object.values(
optimisticFlatFieldPermissionMaps.byUniversalIdentifier,
).filter(
(fp) =>
isDefined(fp) &&
fp.roleUniversalIdentifier ===
flatFieldPermissionToValidate.roleUniversalIdentifier &&
fp.objectMetadataUniversalIdentifier ===
flatFieldPermissionToValidate.objectMetadataUniversalIdentifier &&
fp.fieldMetadataUniversalIdentifier ===
flatFieldPermissionToValidate.fieldMetadataUniversalIdentifier &&
fp.universalIdentifier !==
flatFieldPermissionToValidate.universalIdentifier,
);
if (duplicateForSameRoleObjectField.length > 0) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Field permission for this role, object and field already exists`,
userFriendlyMessage: msg`This field permission is already set for the role`,
});
}
return validationResult;
}
validateFlatFieldPermissionUpdate({
universalIdentifier,
flatEntityUpdate,
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatFieldPermissionMaps: optimisticFlatFieldPermissionMaps,
flatRoleMaps,
flatObjectMetadataMaps,
flatFieldMetadataMaps,
},
}: FlatEntityUpdateValidationArgs<
typeof ALL_METADATA_NAME.fieldPermission
>): FailedFlatEntityValidation<'fieldPermission', 'update'> {
const existingFlatFieldPermission = findFlatEntityByUniversalIdentifier({
universalIdentifier,
flatEntityMaps: optimisticFlatFieldPermissionMaps,
});
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier,
},
metadataName: 'fieldPermission',
type: 'update',
});
if (!isDefined(existingFlatFieldPermission)) {
validationResult.errors.push({
code: PermissionsExceptionCode.FIELD_PERMISSION_NOT_FOUND,
message: t`Field permission to update not found`,
userFriendlyMessage: msg`Field permission not found`,
});
return validationResult;
}
const updatedFlatFieldPermission = {
...existingFlatFieldPermission,
...flatEntityUpdate,
};
const referencedRole = findFlatEntityByUniversalIdentifier({
universalIdentifier: updatedFlatFieldPermission.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 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.objectMetadataUniversalIdentifier)) {
const referencedObjectMetadata = findFlatEntityByUniversalIdentifier({
universalIdentifier:
updatedFlatFieldPermission.objectMetadataUniversalIdentifier,
flatEntityMaps: flatObjectMetadataMaps,
});
if (!isDefined(referencedObjectMetadata)) {
validationResult.errors.push({
code: PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND,
message: t`Object metadata not found`,
userFriendlyMessage: msg`Object metadata not found`,
});
}
}
if (isDefined(flatEntityUpdate.fieldMetadataUniversalIdentifier)) {
const referencedFieldMetadata = findFlatEntityByUniversalIdentifier({
universalIdentifier:
updatedFlatFieldPermission.fieldMetadataUniversalIdentifier,
flatEntityMaps: flatFieldMetadataMaps,
});
if (!isDefined(referencedFieldMetadata)) {
validationResult.errors.push({
code: PermissionsExceptionCode.FIELD_METADATA_NOT_FOUND,
message: t`Field metadata not found`,
userFriendlyMessage: msg`Field metadata not found`,
});
}
}
const duplicateForSameRoleObjectField = Object.values(
optimisticFlatFieldPermissionMaps.byUniversalIdentifier,
).filter(
(fp) =>
isDefined(fp) &&
fp.roleUniversalIdentifier ===
updatedFlatFieldPermission.roleUniversalIdentifier &&
fp.objectMetadataUniversalIdentifier ===
updatedFlatFieldPermission.objectMetadataUniversalIdentifier &&
fp.fieldMetadataUniversalIdentifier ===
updatedFlatFieldPermission.fieldMetadataUniversalIdentifier &&
fp.universalIdentifier !== universalIdentifier,
);
if (duplicateForSameRoleObjectField.length > 0) {
validationResult.errors.push({
code: PermissionsExceptionCode.INVALID_SETTING,
message: t`Field permission for this role, object and field already exists`,
userFriendlyMessage: msg`This field permission is already set for the role`,
});
}
return validationResult;
}
validateFlatFieldPermissionDeletion({
flatEntityToValidate: { universalIdentifier },
optimisticFlatEntityMapsAndRelatedFlatEntityMaps: {
flatFieldPermissionMaps: optimisticFlatFieldPermissionMaps,
flatRoleMaps,
},
}: UniversalFlatEntityValidationArgs<
typeof ALL_METADATA_NAME.fieldPermission
>): FailedFlatEntityValidation<'fieldPermission', 'delete'> {
const validationResult = getEmptyFlatEntityValidationError({
flatEntityMinimalInformation: {
universalIdentifier,
},
metadataName: 'fieldPermission',
type: 'delete',
});
const existingFlatFieldPermission = findFlatEntityByUniversalIdentifier({
universalIdentifier,
flatEntityMaps: optimisticFlatFieldPermissionMaps,
});
if (!isDefined(existingFlatFieldPermission)) {
validationResult.errors.push({
code: PermissionsExceptionCode.FIELD_PERMISSION_NOT_FOUND,
message: t`Field permission to delete not found`,
userFriendlyMessage: msg`Field permission not found`,
});
} else {
const referencedRole = findFlatEntityByUniversalIdentifier({
universalIdentifier:
existingFlatFieldPermission.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;
}
}
@@ -6,6 +6,7 @@ import { FlatPageLayoutWidgetTypeValidatorService } from 'src/engine/metadata-mo
import { FlatAgentValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-agent-validator.service';
import { FlatCommandMenuItemValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-command-menu-item-validator.service';
import { FlatFieldMetadataValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-field-metadata-validator.service';
import { FlatFieldPermissionValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-field-permission-validator.service';
import { FlatFrontComponentValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-front-component-validator.service';
import { FlatIndexValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-index-metadata-validator.service';
import { FlatLogicFunctionValidatorService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/validators/services/flat-logic-function-validator.service';
@@ -40,6 +41,7 @@ import { FlatWebhookValidatorService } from 'src/engine/workspace-manager/worksp
FlatViewGroupValidatorService,
FlatViewFieldGroupValidatorService,
FlatViewSortValidatorService,
FlatFieldPermissionValidatorService,
FlatObjectPermissionValidatorService,
FlatPermissionFlagValidatorService,
FlatIndexValidatorService,
@@ -70,6 +72,7 @@ import { FlatWebhookValidatorService } from 'src/engine/workspace-manager/worksp
FlatViewGroupValidatorService,
FlatViewFieldGroupValidatorService,
FlatViewSortValidatorService,
FlatFieldPermissionValidatorService,
FlatObjectPermissionValidatorService,
FlatPermissionFlagValidatorService,
FlatIndexValidatorService,
@@ -4,6 +4,7 @@ import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
import { WorkspaceMigrationAgentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/agent/workspace-migration-agent-actions-builder.service';
import { WorkspaceMigrationCommandMenuItemActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/command-menu-item/workspace-migration-command-menu-item-actions-builder.service';
import { WorkspaceMigrationFieldPermissionActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field-permission/workspace-migration-field-permission-actions-builder.service';
import { WorkspaceMigrationFieldActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field/workspace-migration-field-actions-builder.service';
import { WorkspaceMigrationFrontComponentActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/front-component/workspace-migration-front-component-actions-builder.service';
import { WorkspaceMigrationIndexActionsBuilderService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/index/workspace-migration-index-actions-builder.service';
@@ -42,6 +43,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationViewGroupActionsBuilderService,
WorkspaceMigrationViewFieldGroupActionsBuilderService,
WorkspaceMigrationViewSortActionsBuilderService,
WorkspaceMigrationFieldPermissionActionsBuilderService,
WorkspaceMigrationObjectPermissionActionsBuilderService,
WorkspaceMigrationPermissionFlagActionsBuilderService,
WorkspaceMigrationIndexActionsBuilderService,
@@ -71,6 +73,7 @@ import { WorkspaceMigrationBuilderValidatorsModule } from 'src/engine/workspace-
WorkspaceMigrationViewGroupActionsBuilderService,
WorkspaceMigrationViewFieldGroupActionsBuilderService,
WorkspaceMigrationViewSortActionsBuilderService,
WorkspaceMigrationFieldPermissionActionsBuilderService,
WorkspaceMigrationObjectPermissionActionsBuilderService,
WorkspaceMigrationPermissionFlagActionsBuilderService,
WorkspaceMigrationFieldActionsBuilderService,
@@ -0,0 +1,65 @@
import { Injectable } from '@nestjs/common';
import { v4 } from 'uuid';
import {
FlatCreateFieldPermissionAction,
UniversalCreateFieldPermissionAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field-permission/types/workspace-migration-field-permission-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,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
import { resolveUniversalRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-relation-identifiers-to-ids.util';
@Injectable()
export class CreateFieldPermissionActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'create',
'fieldPermission',
) {
override async transpileUniversalActionToFlatAction({
action,
allFlatEntityMaps,
flatApplication,
workspaceId,
}: WorkspaceMigrationActionRunnerArgs<UniversalCreateFieldPermissionAction>): Promise<FlatCreateFieldPermissionAction> {
const { roleId, objectMetadataId, fieldMetadataId } =
resolveUniversalRelationIdentifiersToIds({
flatEntityMaps: allFlatEntityMaps,
metadataName: action.metadataName,
universalForeignKeyValues: action.flatEntity,
});
return {
...action,
flatEntity: {
...action.flatEntity,
roleId,
objectMetadataId,
fieldMetadataId,
applicationId: flatApplication.id,
id: action.id ?? v4(),
workspaceId,
},
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatCreateFieldPermissionAction>,
): Promise<void> {
const { flatAction, queryRunner } = context;
const { flatEntity } = flatAction;
await this.insertFlatEntitiesInRepository({
queryRunner,
flatEntities: [flatEntity],
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatCreateFieldPermissionAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,46 @@
import { Injectable } from '@nestjs/common';
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
import {
FlatDeleteFieldPermissionAction,
UniversalDeleteFieldPermissionAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field-permission/types/workspace-migration-field-permission-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,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
@Injectable()
export class DeleteFieldPermissionActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'delete',
'fieldPermission',
) {
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalDeleteFieldPermissionAction>,
): Promise<FlatDeleteFieldPermissionAction> {
return this.transpileUniversalDeleteActionToFlatDeleteAction(context);
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatDeleteFieldPermissionAction>,
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const fieldPermissionRepository =
queryRunner.manager.getRepository<FieldPermissionEntity>(
FieldPermissionEntity,
);
await fieldPermissionRepository.delete({
id: flatAction.entityId,
workspaceId,
});
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatDeleteFieldPermissionAction>,
): Promise<void> {
return;
}
}
@@ -0,0 +1,67 @@
import { Injectable } from '@nestjs/common';
import { findFlatEntityByUniversalIdentifierOrThrow } from 'src/engine/metadata-modules/flat-entity/utils/find-flat-entity-by-universal-identifier-or-throw.util';
import { FieldPermissionEntity } from 'src/engine/metadata-modules/object-permission/field-permission/field-permission.entity';
import {
FlatUpdateFieldPermissionAction,
UniversalUpdateFieldPermissionAction,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-builder/builders/field-permission/types/workspace-migration-field-permission-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,
} from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/types/workspace-migration-action-runner-args.type';
import { resolveUniversalUpdateRelationIdentifiersToIds } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/utils/resolve-universal-update-relation-identifiers-to-ids.util';
@Injectable()
export class UpdateFieldPermissionActionHandlerService extends WorkspaceMigrationRunnerActionHandler(
'update',
'fieldPermission',
) {
override async transpileUniversalActionToFlatAction(
context: WorkspaceMigrationActionRunnerArgs<UniversalUpdateFieldPermissionAction>,
): Promise<FlatUpdateFieldPermissionAction> {
const { action, allFlatEntityMaps } = context;
const flatFieldPermission = findFlatEntityByUniversalIdentifierOrThrow({
flatEntityMaps: allFlatEntityMaps.flatFieldPermissionMaps,
universalIdentifier: action.universalIdentifier,
});
const update = resolveUniversalUpdateRelationIdentifiersToIds({
metadataName: 'fieldPermission',
universalUpdate: action.update,
allFlatEntityMaps,
});
return {
type: 'update',
metadataName: 'fieldPermission',
entityId: flatFieldPermission.id,
update,
};
}
async executeForMetadata(
context: WorkspaceMigrationActionRunnerContext<FlatUpdateFieldPermissionAction>,
): Promise<void> {
const { flatAction, queryRunner, workspaceId } = context;
const { entityId, update } = flatAction;
const fieldPermissionRepository =
queryRunner.manager.getRepository<FieldPermissionEntity>(
FieldPermissionEntity,
);
await fieldPermissionRepository.update(
{ id: entityId, workspaceId },
update,
);
}
async executeForWorkspaceSchema(
_context: WorkspaceMigrationActionRunnerContext<FlatUpdateFieldPermissionAction>,
): Promise<void> {
return;
}
}
@@ -130,6 +130,7 @@ export const fromUniversalFlatFieldMetadataToFlatFieldMetadata = ({
// Empty aggregator arrays for newly created entities
viewFieldIds: [],
viewFilterIds: [],
fieldPermissionIds: [],
calendarViewIds: [],
mainGroupByFieldMetadataViewIds: [],
kanbanAggregateOperationViewIds: [],
@@ -123,6 +123,7 @@ export const fromUniversalFlatObjectMetadataToFlatObjectMetadata = ({
viewIds: [],
indexMetadataIds: [],
objectPermissionIds: [],
fieldPermissionIds: [],
...emptyUniversalForeignKeyAggregators,
};
};
@@ -9,6 +9,9 @@ import { UpdateAgentActionHandlerService } from 'src/engine/workspace-manager/wo
import { CreateCommandMenuItemActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/command-menu-item/services/create-command-menu-item-action-handler.service';
import { DeleteCommandMenuItemActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/command-menu-item/services/delete-command-menu-item-action-handler.service';
import { UpdateCommandMenuItemActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/command-menu-item/services/update-command-menu-item-action-handler.service';
import { CreateFieldPermissionActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field-permission/services/create-field-permission-action-handler.service';
import { DeleteFieldPermissionActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field-permission/services/delete-field-permission-action-handler.service';
import { UpdateFieldPermissionActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field-permission/services/update-field-permission-action-handler.service';
import { CreateFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/create-field-action-handler.service';
import { DeleteFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/delete-field-action-handler.service';
import { UpdateFieldActionHandlerService } from 'src/engine/workspace-manager/workspace-migration/workspace-migration-runner/action-handlers/field/services/update-field-action-handler.service';
@@ -144,6 +147,10 @@ import { UpdateWebhookActionHandlerService } from 'src/engine/workspace-manager/
UpdatePermissionFlagActionHandlerService,
DeletePermissionFlagActionHandlerService,
CreateFieldPermissionActionHandlerService,
UpdateFieldPermissionActionHandlerService,
DeleteFieldPermissionActionHandlerService,
CreateObjectPermissionActionHandlerService,
UpdateObjectPermissionActionHandlerService,
DeleteObjectPermissionActionHandlerService,
@@ -21,6 +21,7 @@ export const METADATA_EVENTS_TO_EMIT = {
navigationMenuItem: true,
permissionFlag: true,
objectPermission: true,
fieldPermission: true,
rowLevelPermissionPredicate: true,
rowLevelPermissionPredicateGroup: true,
index: true,
@@ -91,6 +91,7 @@ const deriveAllMetadataEventsFromCreateAction = (
case 'navigationMenuItem':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
case 'viewSort':
case 'webhook': {
return [
@@ -55,6 +55,7 @@ const deriveAllMetadataEventsFromDeleteAction = ({
case 'navigationMenuItem':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
case 'viewSort':
case 'webhook': {
const flatEntityToDelete = findFlatEntityByIdInFlatEntityMapsOrThrow<
@@ -133,6 +133,7 @@ const deriveAllMetadataEventsFromUpdateAction = ({
case 'navigationMenuItem':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
case 'viewSort':
case 'webhook': {
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(
@@ -70,6 +70,7 @@ export const optimisticallyApplyCreateActionOnAllFlatEntityMaps = ({
case 'navigationMenuItem':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
case 'webhook': {
addFlatEntityToFlatEntityAndRelatedEntityMapsThroughMutationOrThrow({
flatEntity: flatAction.flatEntity,
@@ -42,6 +42,7 @@ export const optimisticallyApplyDeleteActionOnAllFlatEntityMaps = ({
case 'navigationMenuItem':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
case 'webhook': {
const flatEntityToDelete = findFlatEntityByIdInFlatEntityMapsOrThrow<
MetadataFlatEntity<typeof flatAction.metadataName>
@@ -63,6 +63,7 @@ export const optimisticallyApplyUpdateActionOnAllFlatEntityMaps = ({
case 'navigationMenuItem':
case 'permissionFlag':
case 'objectPermission':
case 'fieldPermission':
case 'webhook': {
const flatEntityMapsKey = getMetadataFlatEntityMapsKey(
flatAction.metadataName,