Refactor builder to embed Object validation create/delete/update (#13934)
# Introduction Moving validation directly in the builder that has the perfect granularity to do it. When importing we won't have to infer and dispatch on the operation nature ( update delete create ) and validate accordingly ## Objects Only migrated object validation for the moment even though create object involves a validate flat field metadata creation call too ## TODO - improve `otherFlatObjectMetadataMapsToValidate` naming too vague ## Next - handle fields validation within fields actions build - Unit test coverage validation issue on builder and validate - integration test plugging with new feature flag ## Manual tested - Update - Delete - Create TODO
This commit is contained in:
+76
-122
@@ -2,14 +2,13 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { MultipleMetadataValidationErrors } from 'src/engine/core-modules/error/multiple-metadata-validation-errors';
|
||||
import { EMPTY_FLAT_OBJECT_METADATA_MAPS } from 'src/engine/metadata-modules/flat-object-metadata-maps/constant/empty-flat-object-metadata-maps.constant';
|
||||
import { addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/add-flat-object-metadata-to-flat-object-metadata-maps-or-throw.util';
|
||||
import { deleteFieldFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-field-from-flat-object-metadata-maps-or-throw.util';
|
||||
import { deleteObjectFromFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/delete-object-from-flat-object-metadata-maps-or-throw.util';
|
||||
import { getSubFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps-or-throw.util';
|
||||
import { getSubFlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/get-sub-flat-object-metadata-maps.util';
|
||||
import { replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/replace-flat-object-metadata-in-flat-object-metadata-maps-or-throw.util';
|
||||
import { FlatObjectMetadataValidatorService } from 'src/engine/metadata-modules/flat-object-metadata/services/flat-object-metadata-validator.service';
|
||||
import { FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type';
|
||||
import { fromCreateObjectInputToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-create-object-input-to-flat-object-metadata.util';
|
||||
import { fromDeleteObjectInputToFlatFieldMetadatasToDelete } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-delete-object-input-to-flat-field-metadatas-to-delete.util';
|
||||
@@ -24,16 +23,13 @@ import {
|
||||
ObjectMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
|
||||
import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service';
|
||||
import { WorkspaceMigrationBuilderV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.service';
|
||||
import { WorkspaceMigrationRunnerV2Service } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.service';
|
||||
import { WorkspaceMigrationValidateBuildAndRunService } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-validate-build-and-run-service';
|
||||
|
||||
@Injectable()
|
||||
export class ObjectMetadataServiceV2 {
|
||||
constructor(
|
||||
private readonly workspaceMetadataCacheService: WorkspaceMetadataCacheService,
|
||||
private readonly workspaceMigrationBuilderV2: WorkspaceMigrationBuilderV2Service,
|
||||
private readonly workspaceMigrationRunnerV2Service: WorkspaceMigrationRunnerV2Service,
|
||||
private readonly flatObjectMetadataValidatorService: FlatObjectMetadataValidatorService,
|
||||
private readonly workspaceMigrationValidateBuildAndRunService: WorkspaceMigrationValidateBuildAndRunService,
|
||||
) {}
|
||||
|
||||
async updateOne({
|
||||
@@ -56,43 +52,29 @@ export class ObjectMetadataServiceV2 {
|
||||
updateObjectInput,
|
||||
});
|
||||
|
||||
const validationErrors =
|
||||
this.flatObjectMetadataValidatorService.validateFlatObjectMetadataUpdate({
|
||||
existingFlatObjectMetadataMaps,
|
||||
updatedFlatObjectMetadata: optimisticallyUpdatedFlatObjectMetadata,
|
||||
const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
objectMetadataIds: [optimisticallyUpdatedFlatObjectMetadata.id],
|
||||
});
|
||||
const toFlatObjectMetadataMaps =
|
||||
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadata: optimisticallyUpdatedFlatObjectMetadata,
|
||||
flatObjectMetadataMaps: fromFlatObjectMetadataMaps,
|
||||
});
|
||||
|
||||
if (validationErrors.length > 0) {
|
||||
throw new MultipleMetadataValidationErrors(
|
||||
validationErrors,
|
||||
'Multiple validation errors occurred while updating object',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
objectMetadataIds: [optimisticallyUpdatedFlatObjectMetadata.id],
|
||||
});
|
||||
const toFlatObjectMetadataMaps =
|
||||
replaceFlatObjectMetadataInFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadata: optimisticallyUpdatedFlatObjectMetadata,
|
||||
flatObjectMetadataMaps: fromFlatObjectMetadataMaps,
|
||||
});
|
||||
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromFlatObjectMetadataMaps,
|
||||
toFlatObjectMetadataMaps,
|
||||
inferDeletionFromMissingObjectFieldIndex: false,
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingObjectFieldIndex: false,
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
|
||||
} catch {
|
||||
throw new ObjectMetadataException(
|
||||
'Workspace migration failed to run',
|
||||
ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
errorMessage:
|
||||
'Multiple validation errors occurred while updating object',
|
||||
},
|
||||
);
|
||||
|
||||
const { flatObjectMetadataMaps: recomputedFlatObjectMetadataMaps } =
|
||||
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
|
||||
@@ -137,67 +119,51 @@ export class ObjectMetadataServiceV2 {
|
||||
});
|
||||
const { id: objectMetadataToDeleteId } = flatObjectMetadataToDelete;
|
||||
|
||||
const flatObjectDeleteValidationErrors =
|
||||
this.flatObjectMetadataValidatorService.validateFlatObjectMetadataDeletion(
|
||||
{
|
||||
existingFlatObjectMetadataMaps,
|
||||
objectMetadataToDeleteId,
|
||||
},
|
||||
);
|
||||
|
||||
if (flatObjectDeleteValidationErrors.length > 0) {
|
||||
throw new MultipleMetadataValidationErrors(
|
||||
flatObjectDeleteValidationErrors,
|
||||
'Multiple validation errors occurred while deleting object',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const impactedObjectMetadataIds = Array.from(
|
||||
new Set(
|
||||
flatFieldMetadatasToDelete.map(
|
||||
(flatFieldMetadata) => flatFieldMetadata.objectMetadataId,
|
||||
),
|
||||
const impactedObjectMetadataIds = Array.from(
|
||||
new Set(
|
||||
flatFieldMetadatasToDelete.map(
|
||||
(flatFieldMetadata) => flatFieldMetadata.objectMetadataId,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
objectMetadataIds: impactedObjectMetadataIds,
|
||||
});
|
||||
|
||||
const toFlatObjectMetadataMaps = flatFieldMetadatasToDelete
|
||||
.filter(
|
||||
(flatFieldMetadataToDelete) =>
|
||||
flatFieldMetadataToDelete.objectMetadataId !==
|
||||
objectMetadataToDeleteId,
|
||||
)
|
||||
.reduce(
|
||||
(flatObjectMetadataMaps, flatFieldMetadata) =>
|
||||
deleteFieldFromFlatObjectMetadataMapsOrThrow({
|
||||
fieldMetadataId: flatFieldMetadata.id,
|
||||
objectMetadataId: flatFieldMetadata.objectMetadataId,
|
||||
flatObjectMetadataMaps,
|
||||
}),
|
||||
deleteObjectFromFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: fromFlatObjectMetadataMaps,
|
||||
objectMetadataId: objectMetadataToDeleteId,
|
||||
}),
|
||||
);
|
||||
|
||||
const fromFlatObjectMetadataMaps = getSubFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
objectMetadataIds: impactedObjectMetadataIds,
|
||||
});
|
||||
|
||||
const toFlatObjectMetadataMaps = flatFieldMetadatasToDelete
|
||||
.filter(
|
||||
(flatFieldMetadataToDelete) =>
|
||||
flatFieldMetadataToDelete.objectMetadataId !==
|
||||
objectMetadataToDeleteId,
|
||||
)
|
||||
.reduce(
|
||||
(flatObjectMetadataMaps, flatFieldMetadata) =>
|
||||
deleteFieldFromFlatObjectMetadataMapsOrThrow({
|
||||
fieldMetadataId: flatFieldMetadata.id,
|
||||
objectMetadataId: flatFieldMetadata.objectMetadataId,
|
||||
flatObjectMetadataMaps,
|
||||
}),
|
||||
deleteObjectFromFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: fromFlatObjectMetadataMaps,
|
||||
objectMetadataId: objectMetadataToDeleteId,
|
||||
}),
|
||||
);
|
||||
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromFlatObjectMetadataMaps,
|
||||
toFlatObjectMetadataMaps,
|
||||
inferDeletionFromMissingObjectFieldIndex: true,
|
||||
buildOptions: {
|
||||
inferDeletionFromMissingObjectFieldIndex: true,
|
||||
isSystemBuild: false,
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
|
||||
} catch {
|
||||
throw new ObjectMetadataException(
|
||||
'Workspace migration failed to run',
|
||||
ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
errorMessage:
|
||||
'Multiple validation errors occurred while deleting object',
|
||||
},
|
||||
);
|
||||
|
||||
return fromFlatObjectMetadataToObjectMetadataDto(
|
||||
flatObjectMetadataToDelete,
|
||||
@@ -224,41 +190,29 @@ export class ObjectMetadataServiceV2 {
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
const validationErrors =
|
||||
await this.flatObjectMetadataValidatorService.validateFlatObjectMetadataCreation(
|
||||
{
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatObjectMetadataToValidate: flatObjectMetadataToCreate,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
const fromFlatObjectMetadataMaps =
|
||||
getSubFlatObjectMetadataMaps({
|
||||
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
|
||||
objectMetadataIds: [flatObjectMetadataToCreate.id],
|
||||
}) ?? EMPTY_FLAT_OBJECT_METADATA_MAPS;
|
||||
|
||||
if (validationErrors.length > 0) {
|
||||
throw new MultipleMetadataValidationErrors(
|
||||
validationErrors,
|
||||
'Multiple validation errors occurred while creating object',
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
const workspaceMigration = this.workspaceMigrationBuilderV2.build({
|
||||
fromFlatObjectMetadataMaps: EMPTY_FLAT_OBJECT_METADATA_MAPS,
|
||||
await this.workspaceMigrationValidateBuildAndRunService.validateBuildAndRunWorkspaceMigration(
|
||||
{
|
||||
fromFlatObjectMetadataMaps,
|
||||
toFlatObjectMetadataMaps:
|
||||
addFlatObjectMetadataToFlatObjectMetadataMapsOrThrow({
|
||||
flatObjectMetadataMaps: EMPTY_FLAT_OBJECT_METADATA_MAPS,
|
||||
flatObjectMetadata: flatObjectMetadataToCreate,
|
||||
}),
|
||||
inferDeletionFromMissingObjectFieldIndex: false,
|
||||
buildOptions: {
|
||||
isSystemBuild: false,
|
||||
inferDeletionFromMissingObjectFieldIndex: false,
|
||||
},
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
await this.workspaceMigrationRunnerV2Service.run(workspaceMigration);
|
||||
} catch {
|
||||
throw new ObjectMetadataException(
|
||||
'Workspace migration failed to run',
|
||||
ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR,
|
||||
);
|
||||
}
|
||||
errorMessage:
|
||||
'Multiple validation errors occurred while creating object',
|
||||
},
|
||||
);
|
||||
|
||||
const { flatObjectMetadataMaps: recomputedFlatObjectMetadataMaps } =
|
||||
await this.workspaceMetadataCacheService.getExistingOrRecomputeFlatObjectMetadataMaps(
|
||||
|
||||
+2
-4
@@ -43,8 +43,7 @@ import { WorkspacePermissionsCacheModule } from 'src/engine/metadata-modules/wor
|
||||
import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module';
|
||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||
import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module';
|
||||
import { WorkspaceMigrationBuilderV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/workspace-migration-builder-v2.module';
|
||||
import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/workspace-migration-runner-v2.module';
|
||||
import { WorkspaceMigrationV2Module } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-v2.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -69,8 +68,7 @@ import { WorkspaceMigrationRunnerV2Module } from 'src/engine/workspace-manager/w
|
||||
WorkspaceMetadataCacheModule,
|
||||
WorkspaceDataSourceModule,
|
||||
FeatureFlagModule,
|
||||
WorkspaceMigrationBuilderV2Module,
|
||||
WorkspaceMigrationRunnerV2Module,
|
||||
WorkspaceMigrationV2Module,
|
||||
],
|
||||
services: [
|
||||
ObjectMetadataService,
|
||||
|
||||
Reference in New Issue
Block a user