FieldMetadataServiceV2.updateOne (#13784)

# Introduction
Implementing the `updateOne` service handler following workspace
migration v2 style
This commit is contained in:
Paul Rastoin
2025-08-12 12:05:53 +02:00
committed by GitHub
parent 37a0f2726e
commit d4b5ed93a3
14 changed files with 565 additions and 93 deletions
@@ -3,11 +3,10 @@ import { FieldMetadataType, type FromTo } from 'twenty-shared/types';
import { isDefined } from 'twenty-shared/utils';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
import { type UpdateFieldAction } from 'src/engine/workspace-manager/workspace-migration-v2/workspace-migration-builder-v2/types/workspace-migration-field-action-v2';
import { transformMetadataForComparison } from 'src/engine/workspace-manager/workspace-sync-metadata/comparators/utils/transform-metadata-for-comparison.util';
const flatFieldMetadataPropertiesToCompare = [
export const flatFieldMetadataPropertiesToCompare = [
'defaultValue',
'description',
'icon',
@@ -24,11 +23,14 @@ const flatFieldMetadataPropertiesToCompare = [
export type FlatFieldMetadataPropertiesToCompare =
(typeof flatFieldMetadataPropertiesToCompare)[number];
const fieldMetadataPropertiesToStringify = [
export const flatFieldMetadataEntityJsonbProperties = [
'defaultValue',
'standardOverrides',
'options',
'settings',
] as const satisfies FlatFieldMetadataPropertiesToCompare[];
'standardOverrides',
] as const satisfies (keyof FlatFieldMetadata)[];
export type FlatFieldMetadataEntityJsonbProperties =
(typeof flatFieldMetadataEntityJsonbProperties)[number];
const shouldNotOverrideDefaultValue = (type: FieldMetadataType) => {
return [
@@ -73,7 +75,8 @@ export const compareTwoFlatFieldMetadata = ({
// Remove below assertion when we authorize relation edition, see https://github.com/twentyhq/twenty/commit/39f6f3c4bb101272a9014e142a842d0801a3c33b
if (
isDefined(fieldMetadata.type) &&
isRelationFieldMetadataType(fieldMetadata.type) &&
(fieldMetadata.type === FieldMetadataType.RELATION ||
fieldMetadata.type === FieldMetadataType.MORPH_RELATION) &&
!['label', 'description', 'isActive'].includes(property)
) {
return true;
@@ -81,7 +84,7 @@ export const compareTwoFlatFieldMetadata = ({
return false;
},
propertiesToStringify: fieldMetadataPropertiesToStringify,
propertiesToStringify: flatFieldMetadataEntityJsonbProperties,
};
const fromCompare = transformMetadataForComparison(
from,
@@ -100,11 +103,23 @@ export const compareTwoFlatFieldMetadata = ({
switch (difference.type) {
case 'CHANGE': {
const { oldValue, path, value } = difference;
const property = path[0] as FlatFieldMetadataPropertiesToCompare;
const isJsonb = flatFieldMetadataEntityJsonbProperties.includes(
property as FlatFieldMetadataEntityJsonbProperties,
);
if (isJsonb) {
return {
from: isDefined(oldValue) ? JSON.parse(oldValue) : oldValue,
to: isDefined(value) ? JSON.parse(value) : value,
property,
};
}
return {
from: oldValue,
to: value,
property: path[0] as FlatFieldMetadataPropertiesToCompare,
property,
};
}
case 'CREATE':
@@ -0,0 +1,158 @@
import {
extractAndSanitizeObjectStringFields,
isDefined,
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties,
} from 'twenty-shared/utils';
import {
fieldMetadataStandardOverridesProperties,
type FieldMetadataStandardOverridesProperties,
} from 'src/engine/metadata-modules/field-metadata/dtos/field-standard-overrides.dto';
import { type UpdateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input';
import {
FieldMetadataException,
FieldMetadataExceptionCode,
} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception';
import { type FieldInputTranspilationResult } from 'src/engine/metadata-modules/flat-field-metadata/types/field-input-transpilation-result.type';
import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type';
import {
flatFieldMetadataPropertiesToCompare,
type FlatFieldMetadataPropertiesToCompare,
} from 'src/engine/metadata-modules/flat-field-metadata/utils/compare-two-flat-field-metadata.util';
import { type FlatObjectMetadataMaps } from 'src/engine/metadata-modules/flat-object-metadata-maps/types/flat-object-metadata-maps.type';
import { findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId } from 'src/engine/metadata-modules/flat-object-metadata-maps/utils/find-flat-field-metadata-in-flat-object-metadata-maps-with-field-id-only.util';
import {
ObjectMetadataException,
ObjectMetadataExceptionCode,
} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception';
const fieldMetadataEditableProperties =
flatFieldMetadataPropertiesToCompare.filter(
(
property,
): property is Exclude<
FlatFieldMetadataPropertiesToCompare,
'standardOverrides'
> => property !== 'standardOverrides',
);
type FromUpdateFieldInputToFlatFieldMetadataToUpdateArgs = {
existingFlatObjectMetadataMaps: FlatObjectMetadataMaps;
updateFieldInput: UpdateFieldInput;
};
export const fromUpdateFieldInputToFlatFieldMetadataToUpdate = ({
existingFlatObjectMetadataMaps,
updateFieldInput: rawUpdateFieldInput,
}: FromUpdateFieldInputToFlatFieldMetadataToUpdateArgs): FieldInputTranspilationResult<FlatFieldMetadata> => {
const updateFieldInputInformalProperties =
trimAndRemoveDuplicatedWhitespacesFromObjectStringProperties(
rawUpdateFieldInput,
['objectMetadataId', 'id'],
);
const updatedEditableFields = extractAndSanitizeObjectStringFields(
rawUpdateFieldInput,
fieldMetadataEditableProperties,
);
const relatedFlatFieldMetadata =
findFlatFieldMetadataInFlatObjectMetadataMapsWithOnlyFieldId({
fieldMetadataId: updateFieldInputInformalProperties.id,
flatObjectMetadataMaps: existingFlatObjectMetadataMaps,
});
if (!isDefined(relatedFlatFieldMetadata)) {
return {
status: 'fail',
error: new FieldMetadataException(
'Field metadata to update not found',
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
),
};
}
const flatObjectMetadataWithFlatFieldMaps =
existingFlatObjectMetadataMaps.byId[
relatedFlatFieldMetadata.objectMetadataId
];
if (!isDefined(flatObjectMetadataWithFlatFieldMaps)) {
return {
status: 'fail',
error: new FieldMetadataException(
'Field metadata to update object metadata not found',
FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND,
),
};
}
if (flatObjectMetadataWithFlatFieldMaps.isRemote) {
return {
status: 'fail',
error: new ObjectMetadataException(
'Remote objects are read-only',
ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED,
),
};
}
const isStandardField =
relatedFlatFieldMetadata.standardId !== null &&
!relatedFlatFieldMetadata.isCustom;
if (isStandardField) {
const invalidUpdatedProperties = Object.keys(updatedEditableFields).filter(
(property) =>
fieldMetadataStandardOverridesProperties.includes(
property as FieldMetadataStandardOverridesProperties,
),
);
if (invalidUpdatedProperties.length > 0) {
return {
status: 'fail',
error: new FieldMetadataException(
`Cannot edit standard field metadata properties: ${invalidUpdatedProperties.join(', ')}`,
FieldMetadataExceptionCode.INVALID_FIELD_INPUT,
),
};
}
const updatedStandardFlatFieldMetadata =
fieldMetadataStandardOverridesProperties.reduce((acc, property) => {
const isPropertyUpdated = updatedEditableFields[property] !== undefined;
return {
...acc,
standardOverrides: {
...acc.standardOverrides,
...(isPropertyUpdated
? { [property]: updatedEditableFields[property] }
: {}),
},
};
}, relatedFlatFieldMetadata);
return {
status: 'success',
result: updatedStandardFlatFieldMetadata,
};
}
const updatedFlatFieldMetadata = fieldMetadataEditableProperties.reduce(
(acc, property) => {
const isPropertyUpdated = updatedEditableFields[property] !== undefined;
return {
...acc,
...(isPropertyUpdated
? { [property]: updatedEditableFields[property] }
: {}),
};
},
relatedFlatFieldMetadata,
);
return {
status: 'success',
result: updatedFlatFieldMetadata,
};
};