From ef003fb929f2092bdaa8f2b19aa0d1ccd8ef0657 Mon Sep 17 00:00:00 2001 From: neo773 <62795688+neo773@users.noreply.github.com> Date: Fri, 6 Mar 2026 00:56:04 +0530 Subject: [PATCH] fix blocklist (#18332) - The schema generator marked both the FK scalar and connect relation input as required for non-nullable `MANY_TO_ONE` relations, but the resolver rejects when both are provided making create mutations impossible - Fixed by making the connect input always optional in create input types (the FK scalar still enforces the constraint) - Added `createOne` pre-query hook for blocklist with ownership validation https://github.com/user-attachments/assets/aaae83d4-4747-4d16-a87c-8d8cad79d25d --------- Co-authored-by: Charles Bochet --- .../args-type/args-type.generator.ts | 11 ++-- ...etadata-create-gql-input-type.generator.ts | 6 +- ...etadata-create-gql-input-type.generator.ts | 24 ++++---- ...tadata-filter-gql-input-types.generator.ts | 7 +-- ...etadata-filter-gql-input-type.generator.ts | 32 +++------- ...adata-group-by-gql-input-type.generator.ts | 2 +- ...adata-group-by-gql-input-type.generator.ts | 2 +- ...adata-order-by-gql-input-type.generator.ts | 7 +-- ...object-metadata-order-by-base.generator.ts | 1 - ...-with-group-by-gql-input-type.generator.ts | 1 - ...ation-field-metadata-gql-type.generator.ts | 45 ++++++-------- ...etadata-update-gql-input-type.generator.ts | 6 +- ...etadata-update-gql-input-type.generator.ts | 6 +- ...ield-metadata-gql-object-type.generator.ts | 6 +- .../connection-gql-object-type.generator.ts | 7 +-- .../edge-gql-object-type.generator.ts | 16 ++--- ...by-connection-gql-object-type.generator.ts | 9 +-- ...ject-metadata-gql-object-type.generator.ts | 3 +- ...ield-metadata-gql-object-type.generator.ts | 14 ++--- .../root-types/root-type.generator.ts | 12 ++-- .../services/type-mapper.service.ts | 55 ++--------------- ...pply-type-options-for-create-input.util.ts | 38 ++++++++++++ ...apply-type-options-for-output-type.util.ts | 38 ++++++++++++ ...pply-type-options-for-update-input.util.ts | 26 ++++++++ .../utils/wrap-type-in-graphql-list.util.ts | 21 +++++++ .../services/blocklist-validation.service.ts | 60 ++++++++++++++++--- .../blocklist-create-many.pre-query.hook.ts | 12 +++- .../blocklist-create-one.pre-query.hook.ts | 49 +++++++++++++++ .../blocklist-query-hook.module.ts | 2 + .../blocklist-update-many.pre-query.hook.ts | 14 ++++- .../blocklist-update-one.pre-query.hook.ts | 12 +++- 31 files changed, 340 insertions(+), 204 deletions(-) create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-create-input.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-update-input.util.ts create mode 100644 packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/wrap-type-in-graphql-list.util.ts create mode 100644 packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-create-one.pre-query.hook.ts diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator.ts index 42c30b6a703..9b359377921 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator.ts @@ -5,18 +5,15 @@ import { isDefined } from 'twenty-shared/utils'; import { type ArgsMetadata } from 'src/engine/api/graphql/workspace-schema-builder/interfaces/param-metadata.interface'; -import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; +import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util'; import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util'; @Injectable() export class ArgsTypeGenerator { private readonly logger = new Logger(ArgsTypeGenerator.name); - constructor( - private readonly typeMapperService: TypeMapperService, - private readonly gqlTypesStorage: GqlTypesStorage, - ) {} + constructor(private readonly gqlTypesStorage: GqlTypesStorage) {} public generate({ args, @@ -33,7 +30,7 @@ export class ArgsTypeGenerator { // Argument is a scalar type if (isDefined(arg.type)) { - const gqlType = this.typeMapperService.applyTypeOptions(arg.type, { + const gqlType = applyTypeOptionsForOutputType(arg.type, { defaultValue: arg.defaultValue, nullable: arg.isNullable, isArray: arg.isArray, @@ -66,7 +63,7 @@ export class ArgsTypeGenerator { ); } - const gqlType = this.typeMapperService.applyTypeOptions(inputType, { + const gqlType = applyTypeOptionsForOutputType(inputType, { nullable: arg.isNullable, isArray: arg.isArray, }); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/composite-field-metadata-create-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/composite-field-metadata-create-gql-input-type.generator.ts index 537ec686cc8..dd2b410c8bd 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/composite-field-metadata-create-gql-input-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/composite-field-metadata-create-gql-input-type.generator.ts @@ -11,6 +11,7 @@ import { isDefined, pascalCase } from 'twenty-shared/utils'; import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum'; import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; +import { applyTypeOptionsForCreateInput } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-create-input.util'; import { computeCompositeFieldTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-composite-field-type-options.util'; import { computeCompositeFieldEnumTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-enum-type-key.util'; import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util'; @@ -87,10 +88,7 @@ export class CompositeFieldMetadataCreateGqlInputTypeGenerator { throw new Error(message); } - const modifiedType = this.typeMapperService.applyTypeOptions( - type, - typeOptions, - ); + const modifiedType = applyTypeOptionsForCreateInput(type, typeOptions); fields[property.name] = { type: modifiedType, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/object-metadata-create-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/object-metadata-create-gql-input-type.generator.ts index 3a2a9d4f8f9..ae34aaaa0b5 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/object-metadata-create-gql-input-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/create-input/object-metadata-create-gql-input-type.generator.ts @@ -11,12 +11,13 @@ import { isDefined, pascalCase } from 'twenty-shared/utils'; import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum'; import { RelationFieldMetadataGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator'; -import { - TypeMapperService, - TypeOptions, -} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; +import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type'; +import { + type CreateInputTypeOptions, + applyTypeOptionsForCreateInput, +} from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-create-input.util'; import { computeFieldInputTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util'; import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util'; import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util'; @@ -116,7 +117,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator { private generateEnumFieldCreateInputType( objectNameSingular: string, fieldMetadata: FlatFieldMetadata, - typeOptions: TypeOptions, + typeOptions: CreateInputTypeOptions, ) { const key = computeEnumFieldGqlTypeKey( objectNameSingular, @@ -135,7 +136,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator { throw new Error(message); } - const modifiedEnumType = this.typeMapperService.applyTypeOptions( + const modifiedEnumType = applyTypeOptionsForCreateInput( enumType, typeOptions, ); @@ -150,7 +151,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator { private generateCompositeFieldCreateInputType( fieldMetadata: FlatFieldMetadata, - typeOptions: TypeOptions, + typeOptions: CreateInputTypeOptions, ) { const key = computeCompositeFieldInputTypeKey( fieldMetadata.type, @@ -169,7 +170,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator { throw new Error(message); } - const modifiedCompositeType = this.typeMapperService.applyTypeOptions( + const modifiedCompositeType = applyTypeOptionsForCreateInput( compositeType, typeOptions, ); @@ -184,7 +185,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator { private generateAtomicFieldCreateInputType( fieldMetadata: FlatFieldMetadata, - typeOptions: TypeOptions, + typeOptions: CreateInputTypeOptions, ) { const type = this.typeMapperService.mapToPreBuiltGraphQLInputType({ fieldMetadataType: fieldMetadata.type, @@ -201,10 +202,7 @@ export class ObjectMetadataCreateGqlInputTypeGenerator { throw new Error(message); } - const modifiedType = this.typeMapperService.applyTypeOptions( - type, - typeOptions, - ); + const modifiedType = applyTypeOptionsForCreateInput(type, typeOptions); return { [fieldMetadata.name]: { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/composite-field-metadata-filter-gql-input-types.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/composite-field-metadata-filter-gql-input-types.generator.ts index 6c8735eb21d..1591ac0a0bc 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/composite-field-metadata-filter-gql-input-types.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/composite-field-metadata-filter-gql-input-types.generator.ts @@ -101,13 +101,8 @@ export class CompositeFieldMetadataFilterGqlInputTypeGenerator { throw new Error(message); } - const modifiedType = this.typeMapperService.applyTypeOptions( - type, - typeOptions, - ); - fields[property.name] = { - type: modifiedType, + type, description: property.description, }; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/object-metadata-filter-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/object-metadata-filter-gql-input-type.generator.ts index f418ccf0dee..40c0bce53f0 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/object-metadata-filter-gql-input-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/filter-input/object-metadata-filter-gql-input-type.generator.ts @@ -3,6 +3,7 @@ import { Injectable, Logger } from '@nestjs/common'; import { GraphQLInputFieldConfigMap, GraphQLInputObjectType, + GraphQLList, isEnumType, isInputObjectType, isObjectType, @@ -11,10 +12,7 @@ import { isDefined, pascalCase } from 'twenty-shared/utils'; import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum'; import { RelationFieldMetadataGqlInputTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator'; -import { - TypeMapperService, - TypeOptions, -} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; +import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; import { computeFieldInputTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util'; import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util'; @@ -62,11 +60,6 @@ export class ObjectMetadataFilterGqlInputTypeGenerator { fields: FlatFieldMetadata[], inputType: GraphQLInputObjectType, ): GraphQLInputFieldConfigMap { - const andOrType = this.typeMapperService.applyTypeOptions(inputType, { - isArray: true, - arrayDepth: 1, - nullable: true, - }); const allGeneratedFields: GraphQLInputFieldConfigMap = {}; for (const fieldMetadata of fields) { @@ -94,13 +87,10 @@ export class ObjectMetadataFilterGqlInputTypeGenerator { generatedFields = this.generateEnumFieldFilterInputType( objectNameSingular, fieldMetadata, - typeOptions, ); } else if (isCompositeFieldMetadataType(fieldMetadata.type)) { - generatedFields = this.generateCompositeFieldFilterInputType( - fieldMetadata, - typeOptions, - ); + generatedFields = + this.generateCompositeFieldFilterInputType(fieldMetadata); } else { generatedFields = this.generateAtomicFieldFilterInputType( fieldMetadata, @@ -113,15 +103,13 @@ export class ObjectMetadataFilterGqlInputTypeGenerator { return { ...allGeneratedFields, and: { - type: andOrType, + type: new GraphQLList(inputType), }, or: { - type: andOrType, + type: new GraphQLList(inputType), }, not: { - type: this.typeMapperService.applyTypeOptions(inputType, { - nullable: true, - }), + type: inputType, }, }; } @@ -129,7 +117,6 @@ export class ObjectMetadataFilterGqlInputTypeGenerator { private generateEnumFieldFilterInputType( objectNameSingular: string, fieldMetadata: FlatFieldMetadata, - typeOptions: TypeOptions, ) { const key = computeEnumFieldGqlTypeKey( objectNameSingular, @@ -143,7 +130,6 @@ export class ObjectMetadataFilterGqlInputTypeGenerator { this.logger.error(message, { fieldMetadata, - typeOptions, }); throw new Error(message); } @@ -160,7 +146,6 @@ export class ObjectMetadataFilterGqlInputTypeGenerator { private generateCompositeFieldFilterInputType( fieldMetadata: FlatFieldMetadata, - typeOptions: TypeOptions, ) { const key = computeCompositeFieldInputTypeKey( fieldMetadata.type, @@ -174,7 +159,6 @@ export class ObjectMetadataFilterGqlInputTypeGenerator { this.logger.error(message, { fieldMetadata, - typeOptions, }); throw new Error(message); } @@ -189,7 +173,7 @@ export class ObjectMetadataFilterGqlInputTypeGenerator { private generateAtomicFieldFilterInputType( fieldMetadata: FlatFieldMetadata, - typeOptions: TypeOptions, + typeOptions: { settings?: FlatFieldMetadata['settings'] }, ) { const type = this.typeMapperService.mapToFilterType( fieldMetadata.type, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/group-by-input/composite-field-metadata-group-by-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/group-by-input/composite-field-metadata-group-by-gql-input-type.generator.ts index 2c15d65993f..4499a3c98ef 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/group-by-input/composite-field-metadata-group-by-gql-input-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/group-by-input/composite-field-metadata-group-by-gql-input-type.generator.ts @@ -59,7 +59,7 @@ export class CompositeFieldMetadataGroupByGqlInputTypeGenerator { continue; } - const type = this.typeMapperService.applyTypeOptions(GraphQLBoolean, {}); + const type = GraphQLBoolean; fields[property.name] = { type, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/group-by-input/object-metadata-group-by-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/group-by-input/object-metadata-group-by-gql-input-type.generator.ts index c5d1559afd8..01929b9d076 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/group-by-input/object-metadata-group-by-gql-input-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/group-by-input/object-metadata-group-by-gql-input-type.generator.ts @@ -101,7 +101,7 @@ export class ObjectMetadataGroupByGqlInputTypeGenerator { type = groupByDateGranularityInputType; } else { - type = this.typeMapperService.applyTypeOptions(GraphQLBoolean, {}); + type = GraphQLBoolean; } return { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/composite-field-metadata-order-by-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/composite-field-metadata-order-by-gql-input-type.generator.ts index 26167312eea..d48b4d100e5 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/composite-field-metadata-order-by-gql-input-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/composite-field-metadata-order-by-gql-input-type.generator.ts @@ -76,13 +76,8 @@ export class CompositeFieldMetadataOrderByGqlInputTypeGenerator { throw new Error(message); } - const modifiedType = this.typeMapperService.applyTypeOptions( - type, - typeOptions, - ); - fields[property.name] = { - type: modifiedType, + type, description: property.description, }; } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-base.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-base.generator.ts index ab9a4b49324..6ca0e9a4472 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-base.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-base.generator.ts @@ -58,7 +58,6 @@ export class ObjectMetadataOrderByBaseGenerator { this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldOrderByInputType( { fieldMetadata, - typeOptions, isForGroupBy, context, }, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-with-group-by-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-with-group-by-gql-input-type.generator.ts index 31329a20a5e..d4bc0117c5d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-with-group-by-gql-input-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/order-by-input/object-metadata-order-by-with-group-by-gql-input-type.generator.ts @@ -101,7 +101,6 @@ export class ObjectMetadataOrderByWithGroupByGqlInputTypeGenerator { this.relationFieldMetadataGqlInputTypeGenerator.generateSimpleRelationFieldOrderByInputType( { fieldMetadata, - typeOptions, context, }, ); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator.ts index d37e2d70a1c..51f7bc284f9 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/relation-field-metadata-gql-type.generator.ts @@ -9,12 +9,13 @@ import { FieldMetadataType, RelationType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum'; -import { - TypeMapperService, - TypeOptions, -} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; +import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type'; +import { + type CreateInputTypeOptions, + applyTypeOptionsForCreateInput, +} from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-create-input.util'; import { computeObjectMetadataInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-input-type.util'; import { computeRelationConnectInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-relation-connect-input-type-key.util'; import { extractGraphQLRelationFieldNames } from 'src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util'; @@ -39,7 +40,7 @@ export class RelationFieldMetadataGqlInputTypeGenerator { fieldMetadata: FlatFieldMetadata< FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION >; - typeOptions: TypeOptions; + typeOptions: CreateInputTypeOptions; }) { if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY) return {}; @@ -61,10 +62,10 @@ export class RelationFieldMetadataGqlInputTypeGenerator { throw new Error(message); } - const modifiedType = this.typeMapperService.applyTypeOptions( - type, - typeOptions, - ); + const modifiedType = applyTypeOptionsForCreateInput(type, { + ...typeOptions, + nullable: true, + }); return { [joinColumnName]: { @@ -81,7 +82,7 @@ export class RelationFieldMetadataGqlInputTypeGenerator { fieldMetadata: FlatFieldMetadata< FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION >; - typeOptions: TypeOptions; + typeOptions: { settings?: FlatFieldMetadata['settings'] }; }) { if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY) return {}; @@ -103,14 +104,9 @@ export class RelationFieldMetadataGqlInputTypeGenerator { throw new Error(message); } - const modifiedType = this.typeMapperService.applyTypeOptions( - type, - typeOptions, - ); - return { [joinColumnName]: { - type: modifiedType, + type, description: fieldMetadata.description, }, }; @@ -118,14 +114,12 @@ export class RelationFieldMetadataGqlInputTypeGenerator { public generateSimpleRelationFieldOrderByInputType({ fieldMetadata, - typeOptions, isForGroupBy, context, }: { fieldMetadata: FlatFieldMetadata< FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION >; - typeOptions: TypeOptions; isForGroupBy?: boolean; context?: SchemaGenerationContext; }) { @@ -142,19 +136,13 @@ export class RelationFieldMetadataGqlInputTypeGenerator { this.logger.error(message, { type, - typeOptions, }); throw new Error(message); } - const modifiedType = this.typeMapperService.applyTypeOptions( - type, - typeOptions, - ); - const fields: GraphQLInputFieldConfigMap = { [joinColumnName]: { - type: modifiedType, + type, description: fieldMetadata.description, }, }; @@ -250,7 +238,7 @@ export class RelationFieldMetadataGqlInputTypeGenerator { fieldMetadata: FlatFieldMetadata< FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION >; - typeOptions: TypeOptions; + typeOptions: CreateInputTypeOptions; }) { if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY) { return {}; @@ -277,7 +265,10 @@ export class RelationFieldMetadataGqlInputTypeGenerator { return { [fieldMetadataName]: { - type: this.typeMapperService.applyTypeOptions(type, typeOptions), + type: applyTypeOptionsForCreateInput(type, { + ...typeOptions, + nullable: true, + }), description: fieldMetadata.description, }, }; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/composite-field-metadata-update-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/composite-field-metadata-update-gql-input-type.generator.ts index 1ee4128a04d..d86800e2022 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/composite-field-metadata-update-gql-input-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/composite-field-metadata-update-gql-input-type.generator.ts @@ -11,6 +11,7 @@ import { isDefined, pascalCase } from 'twenty-shared/utils'; import { GqlInputTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-input-type-definition-kind.enum'; import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; +import { applyTypeOptionsForUpdateInput } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-update-input.util'; import { computeCompositeFieldTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-composite-field-type-options.util'; import { computeCompositeFieldEnumTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-enum-type-key.util'; import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util'; @@ -88,10 +89,7 @@ export class CompositeFieldMetadataUpdateGqlInputTypeGenerator { throw new Error(message); } - const modifiedType = this.typeMapperService.applyTypeOptions( - type, - typeOptions, - ); + const modifiedType = applyTypeOptionsForUpdateInput(type, typeOptions); fields[property.name] = { type: modifiedType, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/object-metadata-update-gql-input-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/object-metadata-update-gql-input-type.generator.ts index 3ee9ecd67cf..f36d96cdb7e 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/object-metadata-update-gql-input-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/input-types/update-input/object-metadata-update-gql-input-type.generator.ts @@ -18,6 +18,7 @@ import { RelationFieldMetadataGqlInputTypeGenerator } from 'src/engine/api/graph import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type'; +import { applyTypeOptionsForUpdateInput } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-update-input.util'; import { computeFieldInputTypeOptions } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-field-input-type-options.util'; import { computeCompositeFieldInputTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-input-type-key.util'; import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util'; @@ -156,10 +157,7 @@ export class ObjectMetadataUpdateGqlInputTypeGenerator { } } - const modifiedType = this.typeMapperService.applyTypeOptions( - type, - typeOptions, - ); + const modifiedType = applyTypeOptionsForUpdateInput(type, typeOptions); generatedFields = { [fieldMetadata.name]: { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/composite-field-metadata-gql-object-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/composite-field-metadata-gql-object-type.generator.ts index a1c5cf6d95a..0da4b84b469 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/composite-field-metadata-gql-object-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/composite-field-metadata-gql-object-type.generator.ts @@ -8,6 +8,7 @@ import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schem import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types'; +import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util'; import { computeCompositeFieldEnumTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-enum-type-key.util'; import { computeCompositeFieldObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-object-type-key.util'; import { isEnumFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-enum-field-metadata-type.util'; @@ -86,10 +87,7 @@ export class CompositeFieldMetadataGqlObjectTypeGenerator { throw new Error(message); } - const modifiedType = this.typeMapperService.applyTypeOptions( - type, - typeOptions, - ); + const modifiedType = applyTypeOptionsForOutputType(type, typeOptions); fields[property.name] = { type: modifiedType, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/connection-gql-object-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/connection-gql-object-type.generator.ts index d6e96d1a01a..436e8f64c2a 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/connection-gql-object-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/connection-gql-object-type.generator.ts @@ -6,8 +6,8 @@ import { isDefined, pascalCase } from 'twenty-shared/utils'; import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum'; import { AggregationObjectTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/aggregation-type.generator'; import { PageInfoType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/object'; -import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; +import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util'; import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types'; import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util'; import { FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; @@ -19,7 +19,6 @@ export class ConnectionGqlObjectTypeGenerator { constructor( private readonly aggregationObjectTypeGenerator: AggregationObjectTypeGenerator, - private readonly typeMapperService: TypeMapperService, private readonly gqlTypesStorage: GqlTypesStorage, ) {} @@ -73,7 +72,7 @@ export class ConnectionGqlObjectTypeGenerator { } fields.edges = { - type: this.typeMapperService.applyTypeOptions(edgeType, { + type: applyTypeOptionsForOutputType(edgeType, { isArray: true, arrayDepth: 1, nullable: false, @@ -81,7 +80,7 @@ export class ConnectionGqlObjectTypeGenerator { }; fields.pageInfo = { - type: this.typeMapperService.applyTypeOptions(PageInfoType, { + type: applyTypeOptionsForOutputType(PageInfoType, { nullable: false, }), }; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/edge-gql-object-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/edge-gql-object-type.generator.ts index f0b67736d13..d9b88eaa324 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/edge-gql-object-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/edge-gql-object-type.generator.ts @@ -5,8 +5,8 @@ import { isDefined, pascalCase } from 'twenty-shared/utils'; import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum'; import { CursorScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars'; -import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; +import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util'; import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types'; import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; @@ -15,10 +15,7 @@ import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object export class EdgeGqlObjectTypeGenerator { private readonly logger = new Logger(EdgeGqlObjectTypeGenerator.name); - constructor( - private readonly typeMapperService: TypeMapperService, - private readonly gqlTypesStorage: GqlTypesStorage, - ) {} + constructor(private readonly gqlTypesStorage: GqlTypesStorage) {} public buildAndStore(flatObjectMetadata: FlatObjectMetadata) { const kind = ObjectTypeDefinitionKind.Edge; @@ -60,18 +57,15 @@ export class EdgeGqlObjectTypeGenerator { } const typeOptions = { - nullable: false, + nullable: false as const, }; fields.node = { - type: this.typeMapperService.applyTypeOptions(objectType, typeOptions), + type: applyTypeOptionsForOutputType(objectType, typeOptions), }; fields.cursor = { - type: this.typeMapperService.applyTypeOptions( - CursorScalarType, - typeOptions, - ), + type: applyTypeOptionsForOutputType(CursorScalarType, typeOptions), }; return fields; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/group-by-connection-gql-object-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/group-by-connection-gql-object-type.generator.ts index 94bf2585695..1d895db6e6d 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/group-by-connection-gql-object-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/group-by-connection-gql-object-type.generator.ts @@ -5,8 +5,8 @@ import GraphQLJSON from 'graphql-type-json'; import { isDefined, pascalCase } from 'twenty-shared/utils'; import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum'; -import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; +import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util'; import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types'; import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util'; import { type FlatObjectMetadata } from 'src/engine/metadata-modules/flat-object-metadata/types/flat-object-metadata.type'; @@ -17,10 +17,7 @@ export class GroupByConnectionGqlObjectTypeGenerator { GroupByConnectionGqlObjectTypeGenerator.name, ); - constructor( - private readonly typeMapperService: TypeMapperService, - private readonly gqlTypesStorage: GqlTypesStorage, - ) {} + constructor(private readonly gqlTypesStorage: GqlTypesStorage) {} public buildAndStore(flatObjectMetadata: FlatObjectMetadata) { const kind = ObjectTypeDefinitionKind.GroupByConnection; @@ -65,7 +62,7 @@ export class GroupByConnectionGqlObjectTypeGenerator { Object.assign(fields, connection.toConfig().fields); fields.groupByDimensionValues = { - type: this.typeMapperService.applyTypeOptions(GraphQLJSON, { + type: applyTypeOptionsForOutputType(GraphQLJSON, { nullable: false, }), }; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-gql-object-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-gql-object-type.generator.ts index d32981f8724..fe0c9a6370b 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-gql-object-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/object-metadata-gql-object-type.generator.ts @@ -13,6 +13,7 @@ import { RelationFieldMetadataGqlObjectTypeGenerator } from 'src/engine/api/grap import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; import { GraphQLOutputTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types'; +import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util'; import { computeCompositeFieldObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-composite-field-object-type-key.util'; import { computeEnumFieldGqlTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-enum-field-gql-type-key.util'; import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util'; @@ -150,7 +151,7 @@ export class ObjectMetadataGqlObjectTypeGenerator { } } - const modifiedType = this.typeMapperService.applyTypeOptions( + const modifiedType = applyTypeOptionsForOutputType( type, typeFactoryOptions, ); diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/relation-field-metadata-gql-object-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/relation-field-metadata-gql-object-type.generator.ts index 3bbc5874741..aa3dd81722a 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/relation-field-metadata-gql-object-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/object-types/relation-field-metadata-gql-object-type.generator.ts @@ -3,10 +3,11 @@ import { Injectable, Logger } from '@nestjs/common'; import { FieldMetadataType, RelationType } from 'twenty-shared/types'; import { isDefined } from 'twenty-shared/utils'; +import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { - TypeMapperService, - TypeOptions, -} from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; + type OutputTypeOptions, + applyTypeOptionsForOutputType, +} from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util'; import { extractGraphQLRelationFieldNames } from 'src/engine/api/graphql/workspace-schema-builder/utils/extract-graphql-relation-field-names.util'; import { type FlatFieldMetadata } from 'src/engine/metadata-modules/flat-field-metadata/types/flat-field-metadata.type'; @@ -25,7 +26,7 @@ export class RelationFieldMetadataGqlObjectTypeGenerator { fieldMetadata: FlatFieldMetadata< FieldMetadataType.RELATION | FieldMetadataType.MORPH_RELATION >; - typeOptions: TypeOptions; + typeOptions: OutputTypeOptions; }) { if (fieldMetadata.settings?.relationType === RelationType.ONE_TO_MANY) return {}; @@ -47,10 +48,7 @@ export class RelationFieldMetadataGqlObjectTypeGenerator { throw new Error(message); } - const modifiedType = this.typeMapperService.applyTypeOptions( - type, - typeOptions, - ); + const modifiedType = applyTypeOptionsForOutputType(type, typeOptions); return { [joinColumnName]: { diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/root-type.generator.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/root-type.generator.ts index 0ac1354db02..3b97d67f3c8 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/root-type.generator.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/root-types/root-type.generator.ts @@ -9,8 +9,8 @@ import { WorkspaceResolverBuilderService } from 'src/engine/api/graphql/workspac import { GqlOperation } from 'src/engine/api/graphql/workspace-schema-builder/enums/gql-operation.enum'; import { ObjectTypeDefinitionKind } from 'src/engine/api/graphql/workspace-schema-builder/enums/object-type-definition-kind.enum'; import { ArgsTypeGenerator } from 'src/engine/api/graphql/workspace-schema-builder/graphql-type-generators/args-type/args-type.generator'; -import { TypeMapperService } from 'src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service'; import { GqlTypesStorage } from 'src/engine/api/graphql/workspace-schema-builder/storages/gql-types.storage'; +import { applyTypeOptionsForOutputType } from 'src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util'; import { type SchemaGenerationContext } from 'src/engine/api/graphql/workspace-schema-builder/types/schema-generation-context.type'; import { GraphQLRootTypeFieldConfigMap } from 'src/engine/api/graphql/workspace-schema-builder/types/graphql-field-config-map.types'; import { computeObjectMetadataObjectTypeKey } from 'src/engine/api/graphql/workspace-schema-builder/utils/compute-stored-gql-type-key-utils/compute-object-metadata-object-type-key.util'; @@ -24,7 +24,6 @@ export class RootTypeGenerator { constructor( private readonly gqlTypesStorage: GqlTypesStorage, - private readonly typeMapperService: TypeMapperService, private readonly argsTypeGenerator: ArgsTypeGenerator, private readonly workspaceResolverBuilderService: WorkspaceResolverBuilderService, ) {} @@ -115,12 +114,9 @@ export class RootTypeGenerator { 'groupBy', ]; - const outputType = this.typeMapperService.applyTypeOptions( - objectType, - { - isArray: isMethodReturningArrayObjectType.includes(methodName), - }, - ); + const outputType = applyTypeOptionsForOutputType(objectType, { + isArray: isMethodReturningArrayObjectType.includes(methodName), + }); fieldConfigMap[name] = { type: outputType, diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts index 42d624f716f..95e6abf04be 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/services/type-mapper.service.ts @@ -8,21 +8,18 @@ import { type GraphQLInputObjectType, type GraphQLInputType, GraphQLList, - GraphQLNonNull, type GraphQLOutputType, type GraphQLScalarType, GraphQLString, - type GraphQLType, } from 'graphql'; import GraphQLJSON from 'graphql-type-json'; import { AggregateOperations, + type FieldMetadataDefaultValue, type FieldMetadataSettings, FieldMetadataType, NumberDataType, - type FieldMetadataDefaultValue, } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; import { OrderByDirectionType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/enum'; import { @@ -52,13 +49,12 @@ import { getNumberFilterType } from 'src/engine/api/graphql/workspace-schema-bui import { getNumberScalarType } from 'src/engine/api/graphql/workspace-schema-builder/utils/get-number-scalar-type.util'; export interface TypeOptions { - nullable?: boolean; - isArray?: boolean; - arrayDepth?: number; - defaultValue?: FieldMetadataDefaultValue; settings?: FieldMetadataSettings; isIdField?: boolean; - isRelationConnectField?: boolean; + nullable?: boolean; + defaultValue?: FieldMetadataDefaultValue; + isArray?: boolean; + arrayDepth?: number; } const StringArrayScalarType = new GraphQLList(GraphQLString); @@ -241,45 +237,4 @@ export class TypeMapperService { return typeOrderByMapping.get(aggregationType); } - - applyTypeOptions( - typeRef: T, - options: TypeOptions, - ): T { - let graphqlType: T | GraphQLList | GraphQLNonNull = typeRef; - - if (options.isArray) { - graphqlType = this.mapToGqlList( - graphqlType, - options.arrayDepth ?? 1, - options.nullable ?? false, - ); - } - - if (options.nullable === false && !isDefined(options.defaultValue)) { - graphqlType = new GraphQLNonNull(graphqlType) as unknown as T; - } - - return graphqlType as T; - } - - private mapToGqlList( - targetType: T, - depth: number, - nullable: boolean, - ): GraphQLList { - const targetTypeNonNull = nullable - ? targetType - : new GraphQLNonNull(targetType); - - if (depth === 0) { - return targetType as GraphQLList; - } - - return this.mapToGqlList( - new GraphQLList(targetTypeNonNull) as unknown as T, - depth - 1, - nullable, - ); - } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-create-input.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-create-input.util.ts new file mode 100644 index 00000000000..303b049d606 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-create-input.util.ts @@ -0,0 +1,38 @@ +import { type GraphQLList, GraphQLNonNull, type GraphQLType } from 'graphql'; +import { + type FieldMetadataDefaultValue, + type FieldMetadataType, +} from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +import { wrapTypeInGraphQLList } from 'src/engine/api/graphql/workspace-schema-builder/utils/wrap-type-in-graphql-list.util'; + +export type CreateInputTypeOptions = { + nullable?: boolean; + defaultValue?: FieldMetadataDefaultValue; + isArray?: boolean; + arrayDepth?: number; +}; + +export const applyTypeOptionsForCreateInput = < + T extends GraphQLType = GraphQLType, +>( + typeRef: T, + options: CreateInputTypeOptions, +): T => { + let graphqlType: T | GraphQLList | GraphQLNonNull = typeRef; + + if (options.isArray) { + graphqlType = wrapTypeInGraphQLList( + graphqlType, + options.arrayDepth ?? 1, + options.nullable ?? false, + ); + } + + if (options.nullable === false && !isDefined(options.defaultValue)) { + graphqlType = new GraphQLNonNull(graphqlType) as unknown as T; + } + + return graphqlType as T; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util.ts new file mode 100644 index 00000000000..920eb483c1e --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-output-type.util.ts @@ -0,0 +1,38 @@ +import { type GraphQLList, GraphQLNonNull, type GraphQLType } from 'graphql'; +import { + type FieldMetadataDefaultValue, + type FieldMetadataType, +} from 'twenty-shared/types'; +import { isDefined } from 'twenty-shared/utils'; + +import { wrapTypeInGraphQLList } from 'src/engine/api/graphql/workspace-schema-builder/utils/wrap-type-in-graphql-list.util'; + +export type OutputTypeOptions = { + nullable?: boolean; + defaultValue?: FieldMetadataDefaultValue; + isArray?: boolean; + arrayDepth?: number; +}; + +export const applyTypeOptionsForOutputType = < + T extends GraphQLType = GraphQLType, +>( + typeRef: T, + options: OutputTypeOptions, +): T => { + let graphqlType: T | GraphQLList | GraphQLNonNull = typeRef; + + if (options.isArray) { + graphqlType = wrapTypeInGraphQLList( + graphqlType, + options.arrayDepth ?? 1, + options.nullable ?? false, + ); + } + + if (options.nullable === false && !isDefined(options.defaultValue)) { + graphqlType = new GraphQLNonNull(graphqlType) as unknown as T; + } + + return graphqlType as T; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-update-input.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-update-input.util.ts new file mode 100644 index 00000000000..0fa862dcb92 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/apply-type-options-for-update-input.util.ts @@ -0,0 +1,26 @@ +import { type GraphQLType } from 'graphql'; + +import { wrapTypeInGraphQLList } from 'src/engine/api/graphql/workspace-schema-builder/utils/wrap-type-in-graphql-list.util'; + +export type UpdateInputTypeOptions = { + isArray?: boolean; + arrayDepth?: number; +}; + +// Update inputs are always nullable — only array wrapping is needed +export const applyTypeOptionsForUpdateInput = < + T extends GraphQLType = GraphQLType, +>( + typeRef: T, + options: UpdateInputTypeOptions, +): T => { + if (options.isArray) { + return wrapTypeInGraphQLList( + typeRef, + options.arrayDepth ?? 1, + true, + ) as unknown as T; + } + + return typeRef; +}; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/wrap-type-in-graphql-list.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/wrap-type-in-graphql-list.util.ts new file mode 100644 index 00000000000..9a8f7261543 --- /dev/null +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema-builder/utils/wrap-type-in-graphql-list.util.ts @@ -0,0 +1,21 @@ +import { GraphQLList, GraphQLNonNull, type GraphQLType } from 'graphql'; + +export const wrapTypeInGraphQLList = ( + targetType: T, + depth: number, + nullable: boolean, +): GraphQLList => { + const targetTypeNonNull = nullable + ? targetType + : new GraphQLNonNull(targetType); + + if (depth === 0) { + return targetType as GraphQLList; + } + + return wrapTypeInGraphQLList( + new GraphQLList(targetTypeNonNull) as unknown as T, + depth - 1, + nullable, + ); +}; diff --git a/packages/twenty-server/src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service.ts b/packages/twenty-server/src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service.ts index efab0c63703..f7b8d718dd3 100644 --- a/packages/twenty-server/src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service.ts +++ b/packages/twenty-server/src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service.ts @@ -1,5 +1,7 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; +import { isDefined } from 'twenty-shared/utils'; import { z } from 'zod'; import { @@ -7,6 +9,10 @@ import { type UpdateOneResolverArgs, } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { + CommonQueryRunnerException, + CommonQueryRunnerExceptionCode, +} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception'; import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator'; import { GlobalWorkspaceOrmManager } from 'src/engine/twenty-orm/global-workspace-datasource/global-workspace-orm.manager'; import { buildSystemAuthContext } from 'src/engine/twenty-orm/utils/build-system-auth-context.util'; @@ -68,13 +74,21 @@ export class BlocklistValidationService { for (const handle of blocklist.map((item) => item.handle)) { if (!handle) { - throw new BadRequestException('Blocklist handle is required'); + throw new CommonQueryRunnerException( + 'Blocklist handle is required', + CommonQueryRunnerExceptionCode.BAD_REQUEST, + { userFriendlyMessage: msg`Blocklist handle is required.` }, + ); } const result = emailOrDomainSchema.safeParse(handle); if (!result.success) { - throw new BadRequestException(result.error.issues[0].message); + throw new CommonQueryRunnerException( + result.error.issues[0].message, + CommonQueryRunnerExceptionCode.BAD_REQUEST, + { userFriendlyMessage: msg`Invalid email or domain.` }, + ); } } } @@ -102,6 +116,22 @@ export class BlocklistValidationService { authContext, ); + if ( + payload.data.some( + (item) => + isDefined(item.workspaceMemberId) && + item.workspaceMemberId !== currentWorkspaceMember.id, + ) + ) { + throw new CommonQueryRunnerException( + 'Cannot create blocklist entry for another workspace member', + CommonQueryRunnerExceptionCode.BAD_REQUEST, + { + userFriendlyMessage: msg`Cannot create blocklist entry for another workspace member.`, + }, + ); + } + const currentBlocklist = await this.blocklistRepository.getByWorkspaceMemberId( currentWorkspaceMember.id, @@ -115,7 +145,11 @@ export class BlocklistValidationService { if ( payload.data.some((item) => currentBlocklistHandles.includes(item.handle)) ) { - throw new BadRequestException('Blocklist handle already exists'); + throw new CommonQueryRunnerException( + 'Blocklist handle already exists', + CommonQueryRunnerExceptionCode.BAD_REQUEST, + { userFriendlyMessage: msg`Blocklist handle already exists.` }, + ); } } @@ -130,11 +164,19 @@ export class BlocklistValidationService { ); if (!existingRecord) { - throw new BadRequestException('Blocklist item not found'); + throw new CommonQueryRunnerException( + 'Blocklist item not found', + CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND, + { userFriendlyMessage: msg`Blocklist item not found.` }, + ); } if (existingRecord.workspaceMemberId !== payload.data.workspaceMemberId) { - throw new BadRequestException('Workspace member cannot be updated'); + throw new CommonQueryRunnerException( + 'Workspace member cannot be updated', + CommonQueryRunnerExceptionCode.BAD_REQUEST, + { userFriendlyMessage: msg`Workspace member cannot be updated.` }, + ); } if (existingRecord.handle === payload.data.handle) { @@ -170,7 +212,11 @@ export class BlocklistValidationService { .map((blocklist) => blocklist.handle); if (currentBlocklistHandles.includes(payload.data.handle)) { - throw new BadRequestException('Blocklist handle already exists'); + throw new CommonQueryRunnerException( + 'Blocklist handle already exists', + CommonQueryRunnerExceptionCode.BAD_REQUEST, + { userFriendlyMessage: msg`Blocklist handle already exists.` }, + ); } } } diff --git a/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-create-many.pre-query.hook.ts b/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-create-many.pre-query.hook.ts index c7d746d1653..51f4b5705cf 100644 --- a/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-create-many.pre-query.hook.ts +++ b/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-create-many.pre-query.hook.ts @@ -1,8 +1,12 @@ -import { BadRequestException } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { type CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { + CommonQueryRunnerException, + CommonQueryRunnerExceptionCode, +} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard'; import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; @@ -25,7 +29,11 @@ export class BlocklistCreateManyPreQueryHook payload: CreateManyResolverArgs, ): Promise> { if (!isUserAuthContext(authContext)) { - throw new BadRequestException('User id is required'); + throw new CommonQueryRunnerException( + 'User id is required', + CommonQueryRunnerExceptionCode.INVALID_AUTH_CONTEXT, + { userFriendlyMessage: msg`User id is required.` }, + ); } await this.blocklistValidationService.validateBlocklistForCreateMany( diff --git a/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-create-one.pre-query.hook.ts b/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-create-one.pre-query.hook.ts new file mode 100644 index 00000000000..71d888ce188 --- /dev/null +++ b/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-create-one.pre-query.hook.ts @@ -0,0 +1,49 @@ +import { msg } from '@lingui/core/macro'; + +import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; +import { type CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; + +import { + CommonQueryRunnerException, + CommonQueryRunnerExceptionCode, +} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception'; +import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; +import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard'; +import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; +import { + type BlocklistItem, + BlocklistValidationService, +} from 'src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service'; + +@WorkspaceQueryHook(`blocklist.createOne`) +export class BlocklistCreateOnePreQueryHook + implements WorkspacePreQueryHookInstance +{ + constructor( + private readonly blocklistValidationService: BlocklistValidationService, + ) {} + + async execute( + authContext: WorkspaceAuthContext, + _objectName: string, + payload: CreateOneResolverArgs, + ): Promise> { + if (!isUserAuthContext(authContext)) { + throw new CommonQueryRunnerException( + 'User id is required', + CommonQueryRunnerExceptionCode.INVALID_AUTH_CONTEXT, + { + userFriendlyMessage: msg`You must be authenticated to manage blocklist.`, + }, + ); + } + + await this.blocklistValidationService.validateBlocklistForCreateMany( + { data: [payload.data] }, + authContext.user.id, + authContext.workspace.id, + ); + + return payload; + } +} diff --git a/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-query-hook.module.ts b/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-query-hook.module.ts index 3a03459c85e..d2a93bd7acd 100644 --- a/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-query-hook.module.ts +++ b/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-query-hook.module.ts @@ -2,6 +2,7 @@ import { Module } from '@nestjs/common'; import { BlocklistValidationManagerModule } from 'src/modules/blocklist/blocklist-validation-manager/blocklist-validation-manager.module'; import { BlocklistCreateManyPreQueryHook } from 'src/modules/blocklist/query-hooks/blocklist-create-many.pre-query.hook'; +import { BlocklistCreateOnePreQueryHook } from 'src/modules/blocklist/query-hooks/blocklist-create-one.pre-query.hook'; import { BlocklistUpdateManyPreQueryHook } from 'src/modules/blocklist/query-hooks/blocklist-update-many.pre-query.hook'; import { BlocklistUpdateOnePreQueryHook } from 'src/modules/blocklist/query-hooks/blocklist-update-one.pre-query.hook'; @@ -9,6 +10,7 @@ import { BlocklistUpdateOnePreQueryHook } from 'src/modules/blocklist/query-hook imports: [BlocklistValidationManagerModule], providers: [ BlocklistCreateManyPreQueryHook, + BlocklistCreateOnePreQueryHook, BlocklistUpdateManyPreQueryHook, BlocklistUpdateOnePreQueryHook, ], diff --git a/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-update-many.pre-query.hook.ts b/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-update-many.pre-query.hook.ts index 27fcbe2acce..420c4768503 100644 --- a/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-update-many.pre-query.hook.ts +++ b/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-update-many.pre-query.hook.ts @@ -1,8 +1,12 @@ -import { MethodNotAllowedException } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { type UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { + CommonQueryRunnerException, + CommonQueryRunnerExceptionCode, +} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { type BlocklistItem } from 'src/modules/blocklist/blocklist-validation-manager/services/blocklist-validation.service'; @@ -13,6 +17,12 @@ export class BlocklistUpdateManyPreQueryHook constructor() {} async execute(): Promise> { - throw new MethodNotAllowedException('Method not allowed.'); + throw new CommonQueryRunnerException( + 'Method not allowed.', + CommonQueryRunnerExceptionCode.BAD_REQUEST, + { + userFriendlyMessage: msg`Bulk update of blocklist entries is not allowed.`, + }, + ); } } diff --git a/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-update-one.pre-query.hook.ts b/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-update-one.pre-query.hook.ts index 616b3b64d9d..b86e312c15e 100644 --- a/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-update-one.pre-query.hook.ts +++ b/packages/twenty-server/src/modules/blocklist/query-hooks/blocklist-update-one.pre-query.hook.ts @@ -1,8 +1,12 @@ -import { BadRequestException } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface'; import { type UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface'; +import { + CommonQueryRunnerException, + CommonQueryRunnerExceptionCode, +} from 'src/engine/api/common/common-query-runners/errors/common-query-runner.exception'; import { WorkspaceQueryHook } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/decorators/workspace-query-hook.decorator'; import { isUserAuthContext } from 'src/engine/core-modules/auth/guards/is-user-auth-context.guard'; import { type WorkspaceAuthContext } from 'src/engine/core-modules/auth/types/workspace-auth-context.type'; @@ -25,7 +29,11 @@ export class BlocklistUpdateOnePreQueryHook payload: UpdateOneResolverArgs, ): Promise> { if (!isUserAuthContext(authContext)) { - throw new BadRequestException('User id is required'); + throw new CommonQueryRunnerException( + 'User id is required', + CommonQueryRunnerExceptionCode.INVALID_AUTH_CONTEXT, + { userFriendlyMessage: msg`User id is required.` }, + ); } const workspace = authContext.workspace;