Fixes https://github.com/twentyhq/twenty/issues/8300 ## Context API events were created too late and were already formatted as Gql responses (including nesting with edges/node/type + formatting that should not exist in an event payload). This PR moves the emit logic to the resolver where we actually do the DB query Note: Also added RESTORED events
163 lines
5.1 KiB
TypeScript
163 lines
5.1 KiB
TypeScript
import { isPlainObject } from '@nestjs/common/utils/shared.utils';
|
|
|
|
import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface';
|
|
|
|
import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-metadata/composite-types';
|
|
import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
|
import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util';
|
|
import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity';
|
|
import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps';
|
|
import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps';
|
|
import { computeRelationType } from 'src/engine/twenty-orm/utils/compute-relation-type.util';
|
|
import { getCompositeFieldMetadataCollection } from 'src/engine/twenty-orm/utils/get-composite-field-metadata-collection';
|
|
import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util';
|
|
|
|
export function formatResult<T>(
|
|
data: any,
|
|
ObjectMetadataItemWithFieldMaps: ObjectMetadataItemWithFieldMaps,
|
|
objectMetadataMaps: ObjectMetadataMaps,
|
|
): T {
|
|
if (!data) {
|
|
return data;
|
|
}
|
|
|
|
if (Array.isArray(data)) {
|
|
return data.map((item) =>
|
|
formatResult(item, ObjectMetadataItemWithFieldMaps, objectMetadataMaps),
|
|
) as T;
|
|
}
|
|
|
|
if (!isPlainObject(data)) {
|
|
return data;
|
|
}
|
|
|
|
if (!ObjectMetadataItemWithFieldMaps) {
|
|
throw new Error('Object metadata is missing');
|
|
}
|
|
|
|
const compositeFieldMetadataCollection = getCompositeFieldMetadataCollection(
|
|
ObjectMetadataItemWithFieldMaps,
|
|
);
|
|
|
|
const compositeFieldMetadataMap = new Map(
|
|
compositeFieldMetadataCollection.flatMap((fieldMetadata) => {
|
|
const compositeType = compositeTypeDefinitions.get(fieldMetadata.type);
|
|
|
|
if (!compositeType) return [];
|
|
|
|
// Map each composite property to a [key, value] pair
|
|
return compositeType.properties.map((compositeProperty) => [
|
|
computeCompositeColumnName(fieldMetadata.name, compositeProperty),
|
|
{
|
|
parentField: fieldMetadata.name,
|
|
...compositeProperty,
|
|
},
|
|
]);
|
|
}),
|
|
);
|
|
|
|
const relationMetadataMap = new Map(
|
|
Object.values(ObjectMetadataItemWithFieldMaps.fieldsById)
|
|
.filter(({ type }) => isRelationFieldMetadataType(type))
|
|
.map((fieldMetadata) => [
|
|
fieldMetadata.name,
|
|
{
|
|
relationMetadata:
|
|
fieldMetadata.fromRelationMetadata ??
|
|
fieldMetadata.toRelationMetadata,
|
|
relationType: computeRelationType(
|
|
fieldMetadata,
|
|
fieldMetadata.fromRelationMetadata ??
|
|
(fieldMetadata.toRelationMetadata as RelationMetadataEntity),
|
|
),
|
|
},
|
|
]),
|
|
);
|
|
const newData: object = {};
|
|
const objectMetadaItemFieldsByName =
|
|
objectMetadataMaps.byId[ObjectMetadataItemWithFieldMaps.id]?.fieldsByName;
|
|
|
|
for (const [key, value] of Object.entries(data)) {
|
|
const compositePropertyArgs = compositeFieldMetadataMap.get(key);
|
|
const { relationMetadata, relationType } =
|
|
relationMetadataMap.get(key) ?? {};
|
|
|
|
if (!compositePropertyArgs && !relationMetadata) {
|
|
if (isPlainObject(value)) {
|
|
newData[key] = formatResult(
|
|
value,
|
|
ObjectMetadataItemWithFieldMaps,
|
|
objectMetadataMaps,
|
|
);
|
|
} else if (objectMetadaItemFieldsByName[key]) {
|
|
newData[key] = formatFieldMetadataValue(
|
|
value,
|
|
objectMetadaItemFieldsByName[key],
|
|
);
|
|
} else {
|
|
newData[key] = value;
|
|
}
|
|
|
|
continue;
|
|
}
|
|
|
|
if (relationMetadata) {
|
|
const toObjectMetadata =
|
|
objectMetadataMaps.byId[relationMetadata.toObjectMetadataId];
|
|
|
|
const fromObjectMetadata =
|
|
objectMetadataMaps.byId[relationMetadata.fromObjectMetadataId];
|
|
|
|
if (!toObjectMetadata) {
|
|
throw new Error(
|
|
`Object metadata for object metadataId "${relationMetadata.toObjectMetadataId}" is missing`,
|
|
);
|
|
}
|
|
|
|
if (!fromObjectMetadata) {
|
|
throw new Error(
|
|
`Object metadata for object metadataId "${relationMetadata.fromObjectMetadataId}" is missing`,
|
|
);
|
|
}
|
|
|
|
newData[key] = formatResult(
|
|
value,
|
|
relationType === 'one-to-many' ? toObjectMetadata : fromObjectMetadata,
|
|
objectMetadataMaps,
|
|
);
|
|
continue;
|
|
}
|
|
|
|
if (!compositePropertyArgs) {
|
|
continue;
|
|
}
|
|
|
|
const { parentField, ...compositeProperty } = compositePropertyArgs;
|
|
|
|
if (!newData[parentField]) {
|
|
newData[parentField] = {};
|
|
}
|
|
|
|
newData[parentField][compositeProperty.name] = value;
|
|
}
|
|
|
|
return newData as T;
|
|
}
|
|
|
|
function formatFieldMetadataValue(
|
|
value: any,
|
|
fieldMetadata: FieldMetadataInterface,
|
|
) {
|
|
if (
|
|
typeof value === 'string' &&
|
|
(fieldMetadata.type === FieldMetadataType.MULTI_SELECT ||
|
|
fieldMetadata.type === FieldMetadataType.ARRAY)
|
|
) {
|
|
const cleanedValue = value.replace(/{|}/g, '').trim();
|
|
|
|
return cleanedValue ? cleanedValue.split(',') : [];
|
|
}
|
|
|
|
return value;
|
|
}
|