FieldMetadataServiceV2.updateOne (#13784)
# Introduction Implementing the `updateOne` service handler following workspace migration v2 style
This commit is contained in:
+38
-1
@@ -1,11 +1,16 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'class-validator';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
|
||||
import { FeatureFlagKey } from 'src/engine/core-modules/feature-flag/enums/feature-flag-key.enum';
|
||||
import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/services/feature-flag.service';
|
||||
import {
|
||||
FieldMetadataException,
|
||||
FieldMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
|
||||
import { ValidateOneFieldMetadataArgs } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-validator.service';
|
||||
import { FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
|
||||
import { type FlatFieldMetadataTypeValidator } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata-type-validator.type';
|
||||
import { isEnumValidateOneFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-enum-validate-one-field-metadata-args.util';
|
||||
import { validateEnumSelectFlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/validators/validate-enum-flat-field-metadata.validator';
|
||||
@@ -14,7 +19,7 @@ import { validateRelationFlatFieldMetadata } from 'src/engine/metadata-modules/f
|
||||
export class FlatFieldMetadataTypeValidatorService {
|
||||
constructor(private readonly featureFlagService: FeatureFlagService) {}
|
||||
|
||||
public readonly FIELD_METADATA_TYPE_VALIDATOR_HASHMAP: FlatFieldMetadataTypeValidator =
|
||||
private readonly FIELD_METADATA_TYPE_VALIDATOR_HASHMAP: FlatFieldMetadataTypeValidator =
|
||||
{
|
||||
ACTOR: async (_args) => {
|
||||
return [];
|
||||
@@ -126,4 +131,36 @@ export class FlatFieldMetadataTypeValidatorService {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
|
||||
public async validateFlatFieldMetadataTypeSpecificities<
|
||||
T extends FieldMetadataType = FieldMetadataType,
|
||||
>({
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToValidate,
|
||||
workspaceId,
|
||||
otherFlatObjectMetadataMapsToValidate,
|
||||
}: ValidateOneFieldMetadataArgs<T>): Promise<
|
||||
FailedFlatFieldMetadataValidationExceptions[]
|
||||
> {
|
||||
const fieldMetadataTypeValidator =
|
||||
this.FIELD_METADATA_TYPE_VALIDATOR_HASHMAP[
|
||||
flatFieldMetadataToValidate.type
|
||||
];
|
||||
|
||||
if (!isDefined(fieldMetadataTypeValidator)) {
|
||||
return [
|
||||
new FieldMetadataException(
|
||||
'Unsupported field metadata type',
|
||||
FieldMetadataExceptionCode.UNCOVERED_FIELD_METADATA_TYPE_VALIDATION,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
return await fieldMetadataTypeValidator({
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToValidate,
|
||||
workspaceId,
|
||||
otherFlatObjectMetadataMapsToValidate,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+160
-34
@@ -1,7 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { type FieldMetadataType } from 'twenty-shared/types';
|
||||
import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
@@ -11,8 +11,14 @@ import {
|
||||
import { FlatFieldMetadataTypeValidatorService } from 'src/engine/metadata-modules/flat-field-metadata/services/flat-field-metadata-type-validator.service';
|
||||
import { type FailedFlatFieldMetadataValidationExceptions } from 'src/engine/metadata-modules/flat-field-metadata/types/failed-flat-field-metadata-validation.type';
|
||||
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
|
||||
import {
|
||||
compareTwoFlatFieldMetadata,
|
||||
type FlatFieldMetadataPropertiesToCompare,
|
||||
} from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util';
|
||||
import { isFlatFieldMetadataEntityOfType } from 'src/engine/metadata-modules/flat-field-metadata/utils/is-flat-field-metadata-of-type.util';
|
||||
import { validateFlatFieldMetadataNameAvailability } from 'src/engine/metadata-modules/flat-field-metadata/validators/validate-flat-field-metadata-name-availability.validator';
|
||||
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
|
||||
import { fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/utils/from-flat-object-metadata-with-flat-field-maps-to-flat-object-metadatas.util';
|
||||
import {
|
||||
ObjectMetadataException,
|
||||
ObjectMetadataExceptionCode,
|
||||
@@ -21,7 +27,7 @@ import {
|
||||
InvalidMetadataException,
|
||||
InvalidMetadataExceptionCode,
|
||||
} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception';
|
||||
import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||
import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils';
|
||||
import { computeMetadataNameFromLabel } from 'src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util';
|
||||
|
||||
export type ValidateOneFieldMetadataArgs<
|
||||
@@ -39,6 +45,143 @@ export class FlatFieldMetadataValidatorService {
|
||||
private readonly flatFieldMetadataTypeValidatorService: FlatFieldMetadataTypeValidatorService,
|
||||
) {}
|
||||
|
||||
async validateFlatFieldMetadataUpdate<T extends FieldMetadataType>({
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToValidate: updatedFlatFieldMetadata,
|
||||
workspaceId,
|
||||
}: ValidateOneFieldMetadataArgs<T>): Promise<
|
||||
FailedFlatFieldMetadataValidationExceptions[]
|
||||
> {
|
||||
const errors: FailedFlatFieldMetadataValidationExceptions[] = [];
|
||||
const flatObjectMetadataWithFlatFieldMaps =
|
||||
existingFlatObjectMetadataMaps.byId[
|
||||
updatedFlatFieldMetadata.objectMetadataId
|
||||
];
|
||||
|
||||
if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) {
|
||||
return [
|
||||
new FieldMetadataException(
|
||||
'field metadata to update object metadata not found',
|
||||
FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND,
|
||||
),
|
||||
];
|
||||
}
|
||||
if (
|
||||
!isDefined(
|
||||
flatObjectMetadataWithFlatFieldMaps.labelIdentifierFieldMetadataId,
|
||||
)
|
||||
) {
|
||||
errors.push(
|
||||
new FieldMetadataException(
|
||||
'Label identifier field metadata id does not exist',
|
||||
FieldMetadataExceptionCode.LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
const existingFlatFieldMetadataToUpdate =
|
||||
flatObjectMetadataWithFlatFieldMaps.fieldsById[
|
||||
updatedFlatFieldMetadata.id
|
||||
];
|
||||
|
||||
if (!isDefined(existingFlatFieldMetadataToUpdate)) {
|
||||
errors.push(
|
||||
new FieldMetadataException(
|
||||
'field metadata to update not found',
|
||||
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
|
||||
),
|
||||
);
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
const updates = compareTwoFlatFieldMetadata({
|
||||
from: existingFlatFieldMetadataToUpdate,
|
||||
to: updatedFlatFieldMetadata,
|
||||
});
|
||||
|
||||
if (
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
updatedFlatFieldMetadata,
|
||||
FieldMetadataType.RELATION,
|
||||
) ||
|
||||
isFlatFieldMetadataEntityOfType(
|
||||
updatedFlatFieldMetadata,
|
||||
FieldMetadataType.MORPH_RELATION,
|
||||
)
|
||||
) {
|
||||
const relationEditableFields = [
|
||||
'label',
|
||||
'description',
|
||||
'isActive',
|
||||
] as const satisfies FlatFieldMetadataPropertiesToCompare[];
|
||||
const relationNonEditableUpdatedProperties = updates.flatMap(
|
||||
({ property }) =>
|
||||
!relationEditableFields.includes(
|
||||
property as (typeof relationEditableFields)[number],
|
||||
)
|
||||
? property
|
||||
: [],
|
||||
);
|
||||
|
||||
if (relationNonEditableUpdatedProperties.length > 0) {
|
||||
errors.push(
|
||||
new FieldMetadataException(
|
||||
`Forbidden updated properties for relation field metadata: ${relationNonEditableUpdatedProperties.join(', ')}`,
|
||||
FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const flatObjectMetadata =
|
||||
fromFlatObjectMetadataWithFlatFieldMapsToFlatObjectMetadata(
|
||||
flatObjectMetadataWithFlatFieldMaps,
|
||||
);
|
||||
|
||||
if (updates.some((update) => update.property === 'name')) {
|
||||
errors.push(
|
||||
...[
|
||||
validateMetadataName(updatedFlatFieldMetadata.name),
|
||||
validateFlatFieldMetadataNameAvailability({
|
||||
name: updatedFlatFieldMetadata.name,
|
||||
flatObjectMetadata: flatObjectMetadata,
|
||||
}),
|
||||
].filter(isDefined),
|
||||
);
|
||||
}
|
||||
|
||||
if (updatedFlatFieldMetadata.isLabelSyncedWithName) {
|
||||
const computedName = computeMetadataNameFromLabel(
|
||||
updatedFlatFieldMetadata.label,
|
||||
);
|
||||
|
||||
if (updatedFlatFieldMetadata.name !== computedName) {
|
||||
errors.push(
|
||||
new InvalidMetadataException(
|
||||
`Name is not synced with label. Expected name: "${computedName}", got ${updatedFlatFieldMetadata.name}`,
|
||||
InvalidMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const fieldMetadataTypeValidationErrors =
|
||||
await this.flatFieldMetadataTypeValidatorService.validateFlatFieldMetadataTypeSpecificities(
|
||||
{
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToValidate: updatedFlatFieldMetadata,
|
||||
workspaceId,
|
||||
},
|
||||
);
|
||||
|
||||
if (fieldMetadataTypeValidationErrors.length > 0) {
|
||||
errors.push(...fieldMetadataTypeValidationErrors);
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
validateFlatFieldMetadataDeletion({
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToDelete,
|
||||
@@ -98,7 +241,7 @@ export class FlatFieldMetadataValidatorService {
|
||||
return errors;
|
||||
}
|
||||
|
||||
async validateOneFlatFieldMetadata<
|
||||
async validateOneFlatFieldMetadataCreation<
|
||||
T extends FieldMetadataType = FieldMetadataType,
|
||||
>({
|
||||
existingFlatObjectMetadataMaps,
|
||||
@@ -140,7 +283,7 @@ export class FlatFieldMetadataValidatorService {
|
||||
const failedNameAvailabilityValidation =
|
||||
validateFlatFieldMetadataNameAvailability({
|
||||
name: flatFieldMetadataToValidate.name,
|
||||
objectMetadata: parentFlatObjectMetadata,
|
||||
flatObjectMetadata: parentFlatObjectMetadata,
|
||||
});
|
||||
|
||||
if (isDefined(failedNameAvailabilityValidation)) {
|
||||
@@ -163,43 +306,26 @@ export class FlatFieldMetadataValidatorService {
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
validateMetadataNameOrThrow(flatFieldMetadataToValidate.name);
|
||||
} catch (error) {
|
||||
errors.push(
|
||||
new FieldMetadataException(
|
||||
error.message,
|
||||
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
|
||||
{
|
||||
userFriendlyMessage: error.userFriendlyMessage,
|
||||
},
|
||||
),
|
||||
);
|
||||
const nameValidationResult = validateMetadataName(
|
||||
flatFieldMetadataToValidate.name,
|
||||
);
|
||||
|
||||
if (isDefined(nameValidationResult)) {
|
||||
errors.push(nameValidationResult);
|
||||
}
|
||||
|
||||
const fieldMetadataTypeValidator =
|
||||
this.flatFieldMetadataTypeValidatorService
|
||||
.FIELD_METADATA_TYPE_VALIDATOR_HASHMAP[
|
||||
flatFieldMetadataToValidate.type
|
||||
];
|
||||
|
||||
if (!isDefined(fieldMetadataTypeValidator)) {
|
||||
errors.push(
|
||||
new FieldMetadataException(
|
||||
'Unsupported field metadata type',
|
||||
FieldMetadataExceptionCode.UNCOVERED_FIELD_METADATA_TYPE_VALIDATION,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
const fieldMetadataTypeValidatorExceptions =
|
||||
await fieldMetadataTypeValidator({
|
||||
const fieldMetadataTypeValidationErrors =
|
||||
await this.flatFieldMetadataTypeValidatorService.validateFlatFieldMetadataTypeSpecificities(
|
||||
{
|
||||
existingFlatObjectMetadataMaps,
|
||||
flatFieldMetadataToValidate,
|
||||
workspaceId,
|
||||
otherFlatObjectMetadataMapsToValidate,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
errors.push(...fieldMetadataTypeValidatorExceptions);
|
||||
if (fieldMetadataTypeValidationErrors.length > 0) {
|
||||
errors.push(...fieldMetadataTypeValidationErrors);
|
||||
}
|
||||
|
||||
return errors;
|
||||
|
||||
Reference in New Issue
Block a user