From 8d794374f107836e55f216922dd0d41298f0fc54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=A9my=20M?= Date: Fri, 24 Jan 2025 10:38:50 +0100 Subject: [PATCH] feat: new relation resolver (#9794) Fix [#240](https://github.com/twentyhq/core-team-issues/issues/240) --- .../typeorm-seeds/core/feature-flags.ts | 5 + .../enums/feature-flag-key.enum.ts | 1 + .../dataloaders/dataloader.interface.ts | 17 ++- .../engine/dataloaders/dataloader.module.ts | 3 +- .../engine/dataloaders/dataloader.service.ts | 55 ++++++++- .../field-metadata/dtos/relation.dto.ts | 38 ++++++ .../field-metadata.exception.ts | 2 + .../field-metadata/field-metadata.module.ts | 12 +- .../field-metadata/field-metadata.resolver.ts | 66 ++++++++++- .../interfaces/field-metadata.interface.ts | 6 + .../field-metadata-relation.service.ts | 109 ++++++++++++++++++ ...data-graphql-api-exception-handler.util.ts | 2 + .../utils/clean-object-metadata.util.ts | 10 ++ 13 files changed, 318 insertions(+), 8 deletions(-) create mode 100644 packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/relation.dto.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/field-metadata/relation/field-metadata-relation.service.ts create mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/clean-object-metadata.util.ts diff --git a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts index e567c7e08..de20550d1 100644 --- a/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts +++ b/packages/twenty-server/src/database/typeorm-seeds/core/feature-flags.ts @@ -75,6 +75,11 @@ export const seedFeatureFlags = async ( workspaceId: workspaceId, value: true, }, + { + key: FeatureFlagKey.IsNewRelationEnabled, + workspaceId: workspaceId, + value: false, + }, ]) .execute(); }; diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts index c08b90e5e..a3c266ba4 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/enums/feature-flag-key.enum.ts @@ -14,4 +14,5 @@ export enum FeatureFlagKey { IsJsonFilterEnabled = 'IS_JSON_FILTER_ENABLED', IsLocalizationEnabled = 'IS_LOCALIZATION_ENABLED', IsBillingPlansEnabled = 'IS_BILLING_PLANS_ENABLED', + IsNewRelationEnabled = 'IS_NEW_RELATION_ENABLED', } diff --git a/packages/twenty-server/src/engine/dataloaders/dataloader.interface.ts b/packages/twenty-server/src/engine/dataloaders/dataloader.interface.ts index 90b4c9ef4..c43234f75 100644 --- a/packages/twenty-server/src/engine/dataloaders/dataloader.interface.ts +++ b/packages/twenty-server/src/engine/dataloaders/dataloader.interface.ts @@ -1,6 +1,11 @@ import DataLoader from 'dataloader'; -import { RelationMetadataLoaderPayload } from 'src/engine/dataloaders/dataloader.service'; +import { + RelationLoaderPayload, + RelationMetadataLoaderPayload, +} from 'src/engine/dataloaders/dataloader.service'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; export interface IDataloaders { @@ -8,4 +13,14 @@ export interface IDataloaders { RelationMetadataLoaderPayload, RelationMetadataEntity >; + + relationLoader: DataLoader< + RelationLoaderPayload, + { + sourceObjectMetadata: ObjectMetadataEntity; + targetObjectMetadata: ObjectMetadataEntity; + sourceFieldMetadata: FieldMetadataEntity; + targetFieldMetadata: FieldMetadataEntity; + } + >; } diff --git a/packages/twenty-server/src/engine/dataloaders/dataloader.module.ts b/packages/twenty-server/src/engine/dataloaders/dataloader.module.ts index a669e0e05..3e5c71a03 100644 --- a/packages/twenty-server/src/engine/dataloaders/dataloader.module.ts +++ b/packages/twenty-server/src/engine/dataloaders/dataloader.module.ts @@ -1,11 +1,12 @@ import { Module } from '@nestjs/common'; import { DataloaderService } from 'src/engine/dataloaders/dataloader.service'; +import { FieldMetadataModule } from 'src/engine/metadata-modules/field-metadata/field-metadata.module'; import { RelationMetadataModule } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.module'; @Module({ + imports: [RelationMetadataModule, FieldMetadataModule], providers: [DataloaderService], - imports: [RelationMetadataModule], exports: [DataloaderService], }) export class DataloaderModule {} diff --git a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts index e87f236db..75507aaef 100644 --- a/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts +++ b/packages/twenty-server/src/engine/dataloaders/dataloader.service.ts @@ -5,6 +5,9 @@ import DataLoader from 'dataloader'; import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/relation/field-metadata-relation.service'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; import { RelationMetadataService } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.service'; @@ -16,14 +19,37 @@ export type RelationMetadataLoaderPayload = { >; }; +export type RelationLoaderPayload = { + workspaceId: string; + fieldMetadata: Pick< + FieldMetadataInterface, + | 'type' + | 'id' + | 'objectMetadataId' + | 'relationTargetFieldMetadataId' + | 'relationTargetObjectMetadataId' + >; +}; + @Injectable() export class DataloaderService { constructor( private readonly relationMetadataService: RelationMetadataService, + private readonly fieldMetadataRelationService: FieldMetadataRelationService, ) {} createLoaders(): IDataloaders { - const relationMetadataLoader = new DataLoader< + const relationMetadataLoader = this.createRelationMetadataLoader(); + const relationLoader = this.createRelationLoader(); + + return { + relationMetadataLoader, + relationLoader, + }; + } + + private createRelationMetadataLoader() { + return new DataLoader< RelationMetadataLoaderPayload, RelationMetadataEntity >(async (dataLoaderParams: RelationMetadataLoaderPayload[]) => { @@ -40,9 +66,30 @@ export class DataloaderService { return relationsMetadataCollection; }); + } - return { - relationMetadataLoader, - }; + private createRelationLoader() { + return new DataLoader< + RelationLoaderPayload, + { + sourceObjectMetadata: ObjectMetadataEntity; + targetObjectMetadata: ObjectMetadataEntity; + sourceFieldMetadata: FieldMetadataEntity; + targetFieldMetadata: FieldMetadataEntity; + } + >(async (dataLoaderParams: RelationLoaderPayload[]) => { + const workspaceId = dataLoaderParams[0].workspaceId; + const fieldMetadataItems = dataLoaderParams.map( + (dataLoaderParam) => dataLoaderParam.fieldMetadata, + ); + + const fieldMetadataRelationCollection = + await this.fieldMetadataRelationService.findCachedFieldMetadataRelation( + fieldMetadataItems, + workspaceId, + ); + + return fieldMetadataRelationCollection; + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/relation.dto.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/relation.dto.ts new file mode 100644 index 000000000..8d65bcdfb --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/dtos/relation.dto.ts @@ -0,0 +1,38 @@ +import { Field, ObjectType, registerEnumType } from '@nestjs/graphql'; + +import { IsEnum, IsNotEmpty } from 'class-validator'; +import { Relation } from 'typeorm'; + +import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface'; + +import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; +import { ObjectMetadataDTO } from 'src/engine/metadata-modules/object-metadata/dtos/object-metadata.dto'; + +registerEnumType(RelationType, { + name: 'RelationType', + description: 'Relation type', +}); + +@ObjectType('Relation') +export class RelationDTO { + @IsEnum(RelationType) + @IsNotEmpty() + @Field(() => RelationType) + type: RelationType; + + @IsNotEmpty() + @Field(() => ObjectMetadataDTO) + sourceObjectMetadata: Relation; + + @IsNotEmpty() + @Field(() => ObjectMetadataDTO) + targetObjectMetadata: Relation; + + @IsNotEmpty() + @Field(() => FieldMetadataDTO) + sourceFieldMetadata: Relation; + + @IsNotEmpty() + @Field(() => FieldMetadataDTO) + targetFieldMetadata: Relation; +} diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts index e9390c099..a1989b275 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts @@ -14,4 +14,6 @@ export enum FieldMetadataExceptionCode { FIELD_ALREADY_EXISTS = 'FIELD_ALREADY_EXISTS', OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND', INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR', + FIELD_METADATA_RELATION_NOT_ENABLED = 'FIELD_METADATA_RELATION_NOT_ENABLED', + FIELD_METADATA_RELATION_MALFORMED = 'FIELD_METADATA_RELATION_MALFORMED', } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts index 9c6c2cfee..c7d06a321 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.module.ts @@ -9,12 +9,14 @@ import { NestjsQueryTypeOrmModule } from '@ptc-org/nestjs-query-typeorm'; import { TypeORMModule } from 'src/database/typeorm/typeorm.module'; import { ActorModule } from 'src/engine/core-modules/actor/actor.module'; +import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module'; import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard'; import { DataSourceModule } from 'src/engine/metadata-modules/data-source/data-source.module'; import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; import { FieldMetadataValidationService } from 'src/engine/metadata-modules/field-metadata/field-metadata-validation.service'; import { FieldMetadataResolver } from 'src/engine/metadata-modules/field-metadata/field-metadata.resolver'; import { FieldMetadataGraphqlApiExceptionInterceptor } from 'src/engine/metadata-modules/field-metadata/interceptors/field-metadata-graphql-api-exception.interceptor'; +import { FieldMetadataRelationService } from 'src/engine/metadata-modules/field-metadata/relation/field-metadata-relation.service'; import { FieldMetadataRelatedRecordsService } from 'src/engine/metadata-modules/field-metadata/services/field-metadata-related-records.service'; import { IsFieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-default-value.validator'; import { IsFieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/validators/is-field-metadata-options.validator'; @@ -22,6 +24,7 @@ import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadat import { ObjectMetadataModule } from 'src/engine/metadata-modules/object-metadata/object-metadata.module'; import { WorkspaceMetadataVersionModule } from 'src/engine/metadata-modules/workspace-metadata-version/workspace-metadata-version.module'; import { WorkspaceMigrationModule } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.module'; +import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/workspace-cache-storage.module'; import { WorkspaceMigrationRunnerModule } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.module'; import { ViewModule } from 'src/modules/view/view.module'; @@ -42,6 +45,8 @@ import { UpdateFieldInput } from './dtos/update-field.input'; WorkspaceMigrationModule, WorkspaceMigrationRunnerModule, WorkspaceMetadataVersionModule, + WorkspaceCacheStorageModule, + FeatureFlagModule, ObjectMetadataModule, DataSourceModule, TypeORMModule, @@ -86,9 +91,14 @@ import { UpdateFieldInput } from './dtos/update-field.input'; IsFieldMetadataDefaultValue, IsFieldMetadataOptions, FieldMetadataService, + FieldMetadataRelationService, FieldMetadataRelatedRecordsService, FieldMetadataResolver, ], - exports: [FieldMetadataService, FieldMetadataRelatedRecordsService], + exports: [ + FieldMetadataService, + FieldMetadataRelationService, + FieldMetadataRelatedRecordsService, + ], }) export class FieldMetadataModule {} diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts index e2fd5576d..678f027e4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.resolver.ts @@ -14,6 +14,8 @@ import { import { FieldMetadataType } from 'twenty-shared'; +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 { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { IDataloaders } from 'src/engine/dataloaders/dataloader.interface'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; @@ -22,14 +24,24 @@ import { CreateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-m import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input'; import { FieldMetadataDTO } from 'src/engine/metadata-modules/field-metadata/dtos/field-metadata.dto'; import { RelationDefinitionDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation-definition.dto'; +import { RelationDTO } from 'src/engine/metadata-modules/field-metadata/dtos/relation.dto'; import { UpdateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + FieldMetadataException, + FieldMetadataExceptionCode, +} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { FieldMetadataService } from 'src/engine/metadata-modules/field-metadata/field-metadata.service'; import { fieldMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util'; +import { isRelationFieldMetadataType } from 'src/engine/utils/is-relation-field-metadata-type.util'; @UseGuards(WorkspaceAuthGuard) @Resolver(() => FieldMetadataDTO) export class FieldMetadataResolver { - constructor(private readonly fieldMetadataService: FieldMetadataService) {} + constructor( + private readonly fieldMetadataService: FieldMetadataService, + private readonly featureFlagService: FeatureFlagService, + ) {} @Mutation(() => FieldMetadataDTO) async createOneField( @@ -127,4 +139,56 @@ export class FieldMetadataResolver { fieldMetadataGraphqlApiExceptionHandler(error); } } + + @ResolveField(() => RelationDTO, { nullable: true }) + async relation( + @AuthWorkspace() workspace: Workspace, + @Parent() fieldMetadata: FieldMetadataEntity, + @Context() context: { loaders: IDataloaders }, + ): Promise { + if (!isRelationFieldMetadataType(fieldMetadata.type)) { + return null; + } + + try { + const isNewRelationEnabled = + await this.featureFlagService.isFeatureEnabled( + FeatureFlagKey.IsNewRelationEnabled, + workspace.id, + ); + + if (!isNewRelationEnabled) { + throw new FieldMetadataException( + 'New relation feature is not enabled for this workspace', + FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED, + ); + } + const { + sourceObjectMetadata, + targetObjectMetadata, + sourceFieldMetadata, + targetFieldMetadata, + } = await context.loaders.relationLoader.load({ + fieldMetadata, + workspaceId: workspace.id, + }); + + if (!fieldMetadata.settings) { + throw new FieldMetadataException( + 'Relation settings are required', + FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, + ); + } + + return { + type: fieldMetadata.settings.relationType, + sourceObjectMetadata, + targetObjectMetadata, + sourceFieldMetadata, + targetFieldMetadata, + }; + } catch (error) { + fieldMetadataGraphqlApiExceptionHandler(error); + } + } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface.ts index f5ba634d3..055d8de81 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface.ts @@ -4,6 +4,8 @@ import { FieldMetadataDefaultValue } from 'src/engine/metadata-modules/field-met import { FieldMetadataOptions } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-options.interface'; import { FieldMetadataSettings } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-settings.interface'; +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { RelationMetadataEntity } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; export interface FieldMetadataInterface< @@ -23,6 +25,10 @@ export interface FieldMetadataInterface< isUnique?: boolean; fromRelationMetadata?: RelationMetadataEntity; toRelationMetadata?: RelationMetadataEntity; + relationTargetFieldMetadataId?: string; + relationTargetFieldMetadata?: FieldMetadataEntity; + relationTargetObjectMetadataId?: string; + relationTargetObjectMetadata?: ObjectMetadataEntity; isCustom?: boolean; generatedType?: 'STORED' | 'VIRTUAL'; asExpression?: string; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/relation/field-metadata-relation.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/relation/field-metadata-relation.service.ts new file mode 100644 index 000000000..8d58fff2c --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/relation/field-metadata-relation.service.ts @@ -0,0 +1,109 @@ +import { Injectable } from '@nestjs/common'; + +import { FieldMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata.interface'; + +import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + FieldMetadataException, + FieldMetadataExceptionCode, +} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; +import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; +import { cleanObjectMetadata } from 'src/engine/metadata-modules/utils/clean-object-metadata.util'; +import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; + +@Injectable() +export class FieldMetadataRelationService { + constructor( + private readonly workspaceCacheStorageService: WorkspaceCacheStorageService, + ) {} + + async findCachedFieldMetadataRelation( + fieldMetadataItems: Array< + Pick< + FieldMetadataInterface, + | 'id' + | 'type' + | 'objectMetadataId' + | 'relationTargetFieldMetadataId' + | 'relationTargetObjectMetadataId' + > + >, + workspaceId: string, + ): Promise< + Array<{ + sourceObjectMetadata: ObjectMetadataEntity; + sourceFieldMetadata: FieldMetadataEntity; + targetObjectMetadata: ObjectMetadataEntity; + targetFieldMetadata: FieldMetadataEntity; + }> + > { + const metadataVersion = + await this.workspaceCacheStorageService.getMetadataVersion(workspaceId); + + if (!metadataVersion) { + throw new FieldMetadataException( + `Metadata version not found for workspace ${workspaceId}`, + FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR, + ); + } + + const objectMetadataMaps = + await this.workspaceCacheStorageService.getObjectMetadataMaps( + workspaceId, + metadataVersion, + ); + + if (!objectMetadataMaps) { + throw new FieldMetadataException( + `Object metadata map not found for workspace ${workspaceId} and metadata version ${metadataVersion}`, + FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR, + ); + } + + return fieldMetadataItems.map((fieldMetadataItem) => { + const { + id, + objectMetadataId, + relationTargetFieldMetadataId, + relationTargetObjectMetadataId, + } = fieldMetadataItem; + + if (!relationTargetObjectMetadataId || !relationTargetFieldMetadataId) { + throw new FieldMetadataException( + `Relation target object metadata id or relation target field metadata id not found for field metadata ${id}`, + FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, + ); + } + + const sourceObjectMetadata = objectMetadataMaps.byId[objectMetadataId]; + const targetObjectMetadata = + objectMetadataMaps.byId[relationTargetObjectMetadataId]; + const sourceFieldMetadata = sourceObjectMetadata.fieldsById[id]; + const targetFieldMetadata = + targetObjectMetadata.fieldsById[relationTargetFieldMetadataId]; + + if ( + !sourceObjectMetadata || + !targetObjectMetadata || + !sourceFieldMetadata || + !targetFieldMetadata + ) { + throw new FieldMetadataException( + `Field relation metadata not found for field metadata ${id}`, + FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED, + ); + } + + return { + sourceObjectMetadata: cleanObjectMetadata( + sourceObjectMetadata, + ) as ObjectMetadataEntity, + sourceFieldMetadata: sourceFieldMetadata as FieldMetadataEntity, + targetObjectMetadata: cleanObjectMetadata( + targetObjectMetadata, + ) as ObjectMetadataEntity, + targetFieldMetadata: targetFieldMetadata as FieldMetadataEntity, + }; + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util.ts index 5ff6bf161..af8c3cd87 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util.ts @@ -23,6 +23,8 @@ export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => { throw new ConflictError(error.message); case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND: case FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR: + case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED: + case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED: default: throw new InternalServerError(error.message); } diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/clean-object-metadata.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/clean-object-metadata.util.ts new file mode 100644 index 000000000..1ea6c33a6 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/utils/clean-object-metadata.util.ts @@ -0,0 +1,10 @@ +import omit from 'lodash.omit'; + +import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; + +import { ObjectMetadataItemWithFieldMaps } from 'src/engine/metadata-modules/types/object-metadata-item-with-field-maps'; + +export const cleanObjectMetadata = ( + objectMetadata: ObjectMetadataItemWithFieldMaps, +): ObjectMetadataInterface => + omit(objectMetadata, ['fieldsById', 'fieldsByName']);