diff --git a/packages/twenty-server/src/engine/metadata-modules/data-source/data-source.exception.ts b/packages/twenty-server/src/engine/metadata-modules/data-source/data-source.exception.ts new file mode 100644 index 000000000..89672fa26 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/data-source/data-source.exception.ts @@ -0,0 +1,11 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class DataSourceException extends CustomException { + constructor(message: string, code: DataSourceExceptionCode) { + super(message, code); + } +} + +export enum DataSourceExceptionCode { + DATA_SOURCE_NOT_FOUND = 'DATA_SOURCE_NOT_FOUND', +} diff --git a/packages/twenty-server/src/engine/metadata-modules/data-source/data-source.service.ts b/packages/twenty-server/src/engine/metadata-modules/data-source/data-source.service.ts index 6e6f46a4c..6ca6eb83b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/data-source/data-source.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/data-source/data-source.service.ts @@ -3,6 +3,11 @@ import { InjectRepository } from '@nestjs/typeorm'; import { FindManyOptions, Repository } from 'typeorm'; +import { + DataSourceException, + DataSourceExceptionCode, +} from 'src/engine/metadata-modules/data-source/data-source.exception'; + import { DataSourceEntity } from './data-source.entity'; @Injectable() @@ -58,10 +63,17 @@ export class DataSourceService { async getLastDataSourceMetadataFromWorkspaceIdOrFail( workspaceId: string, ): Promise { - return this.dataSourceMetadataRepository.findOneOrFail({ - where: { workspaceId }, - order: { createdAt: 'DESC' }, - }); + try { + return this.dataSourceMetadataRepository.findOneOrFail({ + where: { workspaceId }, + order: { createdAt: 'DESC' }, + }); + } catch (error) { + throw new DataSourceException( + `Data source not found for workspace ${workspaceId}: ${error}`, + DataSourceExceptionCode.DATA_SOURCE_NOT_FOUND, + ); + } } async delete(workspaceId: string): Promise { diff --git a/packages/twenty-server/src/engine/metadata-modules/errors/InvalidStringException.ts b/packages/twenty-server/src/engine/metadata-modules/errors/InvalidStringException.ts deleted file mode 100644 index eabfb0140..000000000 --- a/packages/twenty-server/src/engine/metadata-modules/errors/InvalidStringException.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { BadRequestException } from '@nestjs/common'; - -export class InvalidStringException extends BadRequestException { - constructor(string: string) { - const message = `String "${string}" is not valid`; - - super(message); - } -} 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 new file mode 100644 index 000000000..e9390c099 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts @@ -0,0 +1,17 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class FieldMetadataException extends CustomException { + code: FieldMetadataExceptionCode; + constructor(message: string, code: FieldMetadataExceptionCode) { + super(message, code); + } +} + +export enum FieldMetadataExceptionCode { + FIELD_METADATA_NOT_FOUND = 'FIELD_METADATA_NOT_FOUND', + INVALID_FIELD_INPUT = 'INVALID_FIELD_INPUT', + FIELD_MUTATION_NOT_ALLOWED = 'FIELD_MUTATION_NOT_ALLOWED', + FIELD_ALREADY_EXISTS = 'FIELD_ALREADY_EXISTS', + OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND', + INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR', +} 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 b4bc92e00..8e3c63208 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 @@ -23,6 +23,7 @@ import { RelationDefinitionDTO } from 'src/engine/metadata-modules/field-metadat import { UpdateOneFieldMetadataInput } from 'src/engine/metadata-modules/field-metadata/dtos/update-field.input'; import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; 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'; @UseGuards(JwtAuthGuard) @Resolver(() => FieldMetadataDTO) @@ -34,10 +35,14 @@ export class FieldMetadataResolver { @Args('input') input: CreateOneFieldMetadataInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.fieldMetadataService.createOne({ - ...input.field, - workspaceId, - }); + try { + return this.fieldMetadataService.createOne({ + ...input.field, + workspaceId, + }); + } catch (error) { + fieldMetadataGraphqlApiExceptionHandler(error); + } } @Mutation(() => FieldMetadataDTO) @@ -45,10 +50,14 @@ export class FieldMetadataResolver { @Args('input') input: UpdateOneFieldMetadataInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.fieldMetadataService.updateOne(input.id, { - ...input.update, - workspaceId, - }); + try { + return this.fieldMetadataService.updateOne(input.id, { + ...input.update, + workspaceId, + }); + } catch (error) { + fieldMetadataGraphqlApiExceptionHandler(error); + } } @Mutation(() => FieldMetadataDTO) @@ -85,27 +94,32 @@ export class FieldMetadataResolver { ); } - return this.fieldMetadataService.deleteOneField(input, workspaceId); + try { + return this.fieldMetadataService.deleteOneField(input, workspaceId); + } catch (error) { + fieldMetadataGraphqlApiExceptionHandler(error); + } } @ResolveField(() => RelationDefinitionDTO, { nullable: true }) async relationDefinition( @Parent() fieldMetadata: FieldMetadataDTO, @Context() context: { loaders: IDataloaders }, - ): Promise { + ): Promise { if (fieldMetadata.type !== FieldMetadataType.RELATION) { return null; } - const relationMetadataItem = - await context.loaders.relationMetadataLoader.load(fieldMetadata.id); + try { + const relationMetadataItem = + await context.loaders.relationMetadataLoader.load(fieldMetadata.id); - const relationDefinition = - await this.fieldMetadataService.getRelationDefinitionFromRelationMetadata( + return this.fieldMetadataService.getRelationDefinitionFromRelationMetadata( fieldMetadata, relationMetadataItem, ); - - return relationDefinition; + } catch (error) { + fieldMetadataGraphqlApiExceptionHandler(error); + } } } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts index 8098b4f26..1c7a31588 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.service.ts @@ -1,9 +1,4 @@ -import { - BadRequestException, - ConflictException, - Injectable, - NotFoundException, -} from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import { v4 as uuidV4 } from 'uuid'; @@ -39,9 +34,15 @@ import { import { DeleteOneFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/delete-field.input'; import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { assertMutationNotOnRemoteObject } from 'src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util'; -import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; -import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; +import { + validateMetadataName, + InvalidStringException, +} from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; +import { + FieldMetadataException, + FieldMetadataExceptionCode, +} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { FieldMetadataEntity, @@ -94,7 +95,10 @@ export class FieldMetadataService extends TypeOrmQueryService { @@ -15,8 +14,10 @@ describe('serializeDefaultValue', () => { expect(serializeDefaultValue('now')).toBe('now()'); }); - it('should throw BadRequestException for invalid dynamic default value type', () => { - expect(() => serializeDefaultValue('invalid')).toThrow(BadRequestException); + it('should throw FieldMetadataException for invalid dynamic default value type', () => { + expect(() => serializeDefaultValue('invalid')).toThrow( + FieldMetadataException, + ); }); it('should handle string static default value', () => { diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-column-name.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-column-name.util.ts index c1f358ad4..116eb94f1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-column-name.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/compute-column-name.util.ts @@ -4,6 +4,10 @@ import { CompositeProperty } from 'src/engine/metadata-modules/field-metadata/in import { FieldMetadataType } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { pascalCase } from 'src/utils/pascal-case'; +import { + FieldMetadataException, + FieldMetadataExceptionCode, +} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; type ComputeColumnNameOptions = { isForeignKey?: boolean }; @@ -29,8 +33,9 @@ export function computeColumnName( } if (isCompositeFieldMetadataType(fieldMetadataOrFieldName.type)) { - throw new Error( - `Cannot compute column name for composite field metadata type: ${fieldMetadataOrFieldName.type}`, + throw new FieldMetadataException( + `Cannot compute composite column name for non-composite field metadata type: ${fieldMetadataOrFieldName.type}`, + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, ); } @@ -61,8 +66,9 @@ export function computeCompositeColumnName< } if (!isCompositeFieldMetadataType(fieldMetadataOrFieldName.type)) { - throw new Error( + throw new FieldMetadataException( `Cannot compute composite column name for non-composite field metadata type: ${fieldMetadataOrFieldName.type}`, + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, ); } 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 new file mode 100644 index 000000000..eeef6e280 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/field-metadata-graphql-api-exception-handler.util.ts @@ -0,0 +1,32 @@ +import { + FieldMetadataException, + FieldMetadataExceptionCode, +} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; +import { + UserInputError, + ForbiddenError, + ConflictError, + InternalServerError, + NotFoundError, +} from 'src/engine/utils/graphql-errors.util'; + +export const fieldMetadataGraphqlApiExceptionHandler = (error: Error) => { + if (error instanceof FieldMetadataException) { + switch (error.code) { + case FieldMetadataExceptionCode.FIELD_METADATA_NOT_FOUND: + throw new NotFoundError(error.message); + case FieldMetadataExceptionCode.INVALID_FIELD_INPUT: + throw new UserInputError(error.message); + case FieldMetadataExceptionCode.FIELD_MUTATION_NOT_ALLOWED: + throw new ForbiddenError(error.message); + case FieldMetadataExceptionCode.FIELD_ALREADY_EXISTS: + throw new ConflictError(error.message); + case FieldMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND: + case FieldMetadataExceptionCode.INTERNAL_SERVER_ERROR: + default: + throw new InternalServerError(error.message); + } + } + + throw error; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/serialize-default-value.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/serialize-default-value.ts index 2f93da1b8..7445e0fed 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/serialize-default-value.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/serialize-default-value.ts @@ -1,7 +1,9 @@ -import { BadRequestException } from '@nestjs/common'; - import { FieldMetadataDefaultSerializableValue } from 'src/engine/metadata-modules/field-metadata/interfaces/field-metadata-default-value.interface'; +import { + FieldMetadataException, + FieldMetadataExceptionCode, +} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { isFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/is-function-default-value.util'; import { serializeFunctionDefaultValue } from 'src/engine/metadata-modules/field-metadata/utils/serialize-function-default-value.util'; @@ -18,7 +20,10 @@ export const serializeDefaultValue = ( serializeFunctionDefaultValue(defaultValue); if (!serializedTypeDefaultValue) { - throw new BadRequestException('Invalid default value'); + throw new FieldMetadataException( + 'Invalid default value', + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + ); } return serializedTypeDefaultValue; @@ -51,5 +56,8 @@ export const serializeDefaultValue = ( return `'${JSON.stringify(defaultValue)}'`; } - throw new BadRequestException(`Invalid default value "${defaultValue}"`); + throw new FieldMetadataException( + `Invalid default value "${defaultValue}"`, + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + ); }; diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-options-for-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-options-for-type.util.ts index 7e119d594..aecb4eefe 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-options-for-type.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/utils/validate-options-for-type.util.ts @@ -8,6 +8,10 @@ import { FieldMetadataComplexOption, FieldMetadataDefaultOption, } from 'src/engine/metadata-modules/field-metadata/dtos/options.input'; +import { + FieldMetadataException, + FieldMetadataExceptionCode, +} from 'src/engine/metadata-modules/field-metadata/field-metadata.exception'; import { isEnumFieldMetadataType } from './is-enum-field-metadata-type.util'; @@ -24,7 +28,10 @@ export const validateOptionsForType = ( if (options === null) return true; if (!Array.isArray(options)) { - throw new Error('Options must be an array'); + throw new FieldMetadataException( + 'Options must be an array', + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + ); } if (!isEnumFieldMetadataType(type)) { @@ -39,7 +46,10 @@ export const validateOptionsForType = ( // Check if all options are unique if (new Set(values).size !== options.length) { - throw new Error('Options must be unique'); + throw new FieldMetadataException( + 'Options must be unique', + FieldMetadataExceptionCode.INVALID_FIELD_INPUT, + ); } const validators = optionsValidatorsMap[type]; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.exception.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.exception.ts new file mode 100644 index 000000000..2d489b132 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.exception.ts @@ -0,0 +1,15 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class ObjectMetadataException extends CustomException { + code: ObjectMetadataExceptionCode; + constructor(message: string, code: ObjectMetadataExceptionCode) { + super(message, code); + } +} + +export enum ObjectMetadataExceptionCode { + OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND', + INVALID_OBJECT_INPUT = 'INVALID_OBJECT_INPUT', + OBJECT_MUTATION_NOT_ALLOWED = 'OBJECT_MUTATION_NOT_ALLOWED', + OBJECT_ALREADY_EXISTS = 'OBJECT_ALREADY_EXISTS', +} diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts index ccb1b2a60..710e74d9d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.resolver.ts @@ -12,6 +12,7 @@ import { UpdateOneObjectInput, } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; import { BeforeUpdateOneObject } from 'src/engine/metadata-modules/object-metadata/hooks/before-update-one-object.hook'; +import { objectMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util'; @UseGuards(JwtAuthGuard) @Resolver(() => ObjectMetadataDTO) @@ -26,7 +27,11 @@ export class ObjectMetadataResolver { @Args('input') input: DeleteOneObjectInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.objectMetadataService.deleteOneObject(input, workspaceId); + try { + return this.objectMetadataService.deleteOneObject(input, workspaceId); + } catch (error) { + objectMetadataGraphqlApiExceptionHandler(error); + } } @Mutation(() => ObjectMetadataDTO) @@ -34,8 +39,12 @@ export class ObjectMetadataResolver { @Args('input') input: UpdateOneObjectInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - await this.beforeUpdateOneObject.run(input, workspaceId); + try { + await this.beforeUpdateOneObject.run(input, workspaceId); - return this.objectMetadataService.updateOneObject(input, workspaceId); + return this.objectMetadataService.updateOneObject(input, workspaceId); + } catch (error) { + objectMetadataGraphqlApiExceptionHandler(error); + } } } diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts index 878a12192..c1e1d8111 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/object-metadata.service.ts @@ -1,9 +1,4 @@ -import { - BadRequestException, - ConflictException, - Injectable, - NotFoundException, -} from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import console from 'console'; @@ -56,6 +51,10 @@ import { mapUdtNameToFieldType } from 'src/engine/metadata-modules/remote-server import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; import { UpdateOneObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; import { RemoteTableRelationsService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table-relations/remote-table-relations.service'; +import { + ObjectMetadataException, + ObjectMetadataExceptionCode, +} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; import { ObjectMetadataEntity } from './object-metadata.entity'; @@ -121,7 +120,10 @@ export class ObjectMetadataService extends TypeOrmQueryService, ): Promise { - return this.objectMetadataRepository.findOneOrFail({ - relations: [ - 'fields', - 'fields.fromRelationMetadata', - 'fields.toRelationMetadata', - ], - ...options, - where: { - ...options.where, - workspaceId, - }, - }); + try { + return this.objectMetadataRepository.findOneOrFail({ + relations: [ + 'fields', + 'fields.fromRelationMetadata', + 'fields.toRelationMetadata', + ], + ...options, + where: { + ...options.where, + workspaceId, + }, + }); + } catch (error) { + throw new ObjectMetadataException( + 'Object does not exist', + ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND, + ); + } } public async findManyWithinWorkspace( diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util.ts index 1bfcf6973..0db389d95 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/assert-mutation-not-on-remote-object.util.ts @@ -1,11 +1,17 @@ -import { BadRequestException } from '@nestjs/common'; - import { ObjectMetadataInterface } from 'src/engine/metadata-modules/field-metadata/interfaces/object-metadata.interface'; +import { + ObjectMetadataException, + ObjectMetadataExceptionCode, +} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; + export const assertMutationNotOnRemoteObject = ( objectMetadataItem: ObjectMetadataInterface, ) => { if (objectMetadataItem.isRemote) { - throw new BadRequestException('Remote objects are read-only'); + throw new ObjectMetadataException( + 'Remote objects are read-only', + ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED, + ); } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts new file mode 100644 index 000000000..6961b65bf --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/object-metadata-graphql-api-exception-handler.util.ts @@ -0,0 +1,30 @@ +import { + ObjectMetadataException, + ObjectMetadataExceptionCode, +} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; +import { + UserInputError, + ForbiddenError, + ConflictError, + InternalServerError, + NotFoundError, +} from 'src/engine/utils/graphql-errors.util'; + +export const objectMetadataGraphqlApiExceptionHandler = (error: Error) => { + if (error instanceof ObjectMetadataException) { + switch (error.code) { + case ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND: + throw new NotFoundError(error.message); + case ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT: + throw new UserInputError(error.message); + case ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED: + throw new ForbiddenError(error.message); + case ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS: + throw new ConflictError(error.message); + default: + throw new InternalServerError(error.message); + } + } + + throw error; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts index 42d1c1b00..23087298b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/object-metadata/utils/validate-object-metadata-input.util.ts @@ -1,9 +1,13 @@ -import { BadRequestException, ForbiddenException } from '@nestjs/common'; - -import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; import { CreateObjectInput } from 'src/engine/metadata-modules/object-metadata/dtos/create-object.input'; import { UpdateObjectPayload } from 'src/engine/metadata-modules/object-metadata/dtos/update-object.input'; -import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; +import { + ObjectMetadataException, + ObjectMetadataExceptionCode, +} from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; +import { + validateMetadataName, + InvalidStringException, +} from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; import { camelCase } from 'src/utils/camel-case'; const coreObjectNames = [ @@ -55,7 +59,10 @@ export const validateObjectMetadataInputOrThrow = < const validateNameIsNotReservedKeywordOrThrow = (name?: string) => { if (name) { if (reservedKeywords.includes(name)) { - throw new ForbiddenException(`The name "${name}" is not available`); + throw new ObjectMetadataException( + `The name "${name}" is not available`, + ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, + ); } } }; @@ -63,7 +70,10 @@ const validateNameIsNotReservedKeywordOrThrow = (name?: string) => { const validateNameCamelCasedOrThrow = (name?: string) => { if (name) { if (name !== camelCase(name)) { - throw new ForbiddenException(`Name should be in camelCase: ${name}`); + throw new ObjectMetadataException( + `Name should be in camelCase: ${name}`, + ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, + ); } } }; @@ -75,8 +85,9 @@ const validateNameCharactersOrThrow = (name?: string) => { } } catch (error) { if (error instanceof InvalidStringException) { - throw new BadRequestException( + throw new ObjectMetadataException( `Characters used in name "${name}" are not supported`, + ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, ); } else { throw error; diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.exception.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.exception.ts new file mode 100644 index 000000000..4ce48e8b8 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.exception.ts @@ -0,0 +1,15 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class RelationMetadataException extends CustomException { + code: RelationMetadataExceptionCode; + constructor(message: string, code: RelationMetadataExceptionCode) { + super(message, code); + } +} + +export enum RelationMetadataExceptionCode { + RELATION_METADATA_NOT_FOUND = 'RELATION_METADATA_NOT_FOUND', + INVALID_RELATION_INPUT = 'INVALID_RELATION_INPUT', + RELATION_ALREADY_EXISTS = 'RELATION_ALREADY_EXISTS', + FOREIGN_KEY_NOT_FOUND = 'FOREIGN_KEY_NOT_FOUND', +} diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.resolver.ts index c7eb41498..9fcd2b80b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.resolver.ts @@ -7,6 +7,7 @@ import { RelationMetadataService } from 'src/engine/metadata-modules/relation-me import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { RelationMetadataDTO } from 'src/engine/metadata-modules/relation-metadata/dtos/relation-metadata.dto'; import { DeleteOneRelationInput } from 'src/engine/metadata-modules/relation-metadata/dtos/delete-relation.input'; +import { relationMetadataGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/relation-metadata/utils/relation-metadata-graphql-api-exception-handler.util'; @UseGuards(JwtAuthGuard) @Resolver() @@ -20,9 +21,13 @@ export class RelationMetadataResolver { @Args('input') input: DeleteOneRelationInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.relationMetadataService.deleteOneRelation( - input.id, - workspaceId, - ); + try { + return this.relationMetadataService.deleteOneRelation( + input.id, + workspaceId, + ); + } catch (error) { + relationMetadataGraphqlApiExceptionHandler(error); + } } } diff --git a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts index 8915b60ab..8ba348dcd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/relation-metadata/relation-metadata.service.ts @@ -1,9 +1,4 @@ -import { - BadRequestException, - ConflictException, - Injectable, - NotFoundException, -} from '@nestjs/common'; +import { Injectable, NotFoundException } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { TypeOrmQueryService } from '@ptc-org/nestjs-query-typeorm'; @@ -28,9 +23,15 @@ import { import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { computeObjectTargetTable } from 'src/engine/utils/compute-object-target-table.util'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; -import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; -import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; +import { + validateMetadataName, + InvalidStringException, +} from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; +import { + RelationMetadataException, + RelationMetadataExceptionCode, +} from 'src/engine/metadata-modules/relation-metadata/relation-metadata.exception'; import { RelationMetadataEntity, @@ -66,8 +67,9 @@ export class RelationMetadataService extends TypeOrmQueryService { + if (error instanceof RelationMetadataException) { + switch (error.code) { + case RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND: + throw new NotFoundError(error.message); + case RelationMetadataExceptionCode.INVALID_RELATION_INPUT: + throw new UserInputError(error.message); + case RelationMetadataExceptionCode.RELATION_ALREADY_EXISTS: + throw new ConflictError(error.message); + case RelationMetadataExceptionCode.FOREIGN_KEY_NOT_FOUND: + default: + throw new InternalServerError(error.message); + } + } + + throw error; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.exception.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.exception.ts new file mode 100644 index 000000000..6aa035234 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.exception.ts @@ -0,0 +1,16 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class RemoteServerException extends CustomException { + code: RemoteServerExceptionCode; + constructor(message: string, code: RemoteServerExceptionCode) { + super(message, code); + } +} + +export enum RemoteServerExceptionCode { + REMOTE_SERVER_NOT_FOUND = 'REMOTE_SERVER_NOT_FOUND', + REMOTE_SERVER_ALREADY_EXISTS = 'REMOTE_SERVER_ALREADY_EXISTS', + REMOTE_SERVER_MUTATION_NOT_ALLOWED = 'REMOTE_SERVER_MUTATION_NOT_ALLOWED', + REMOTE_SERVER_CONNECTION_ERROR = 'REMOTE_SERVER_CONNECTION_ERROR', + INVALID_REMOTE_SERVER_INPUT = 'INVALID_REMOTE_SERVER_INPUT', +} diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.resolver.ts index 04d44a190..e5f72621a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.resolver.ts @@ -11,6 +11,7 @@ import { RemoteServerDTO } from 'src/engine/metadata-modules/remote-server/dtos/ import { UpdateRemoteServerInput } from 'src/engine/metadata-modules/remote-server/dtos/update-remote-server.input'; import { RemoteServerType } from 'src/engine/metadata-modules/remote-server/remote-server.entity'; import { RemoteServerService } from 'src/engine/metadata-modules/remote-server/remote-server.service'; +import { remoteServerGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/remote-server/utils/remote-server-graphql-api-exception-handler.util'; @UseGuards(JwtAuthGuard) @Resolver() @@ -24,7 +25,11 @@ export class RemoteServerResolver { @Args('input') input: CreateRemoteServerInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.remoteServerService.createOneRemoteServer(input, workspaceId); + try { + return this.remoteServerService.createOneRemoteServer(input, workspaceId); + } catch (error) { + remoteServerGraphqlApiExceptionHandler(error); + } } @Mutation(() => RemoteServerDTO) @@ -32,7 +37,11 @@ export class RemoteServerResolver { @Args('input') input: UpdateRemoteServerInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.remoteServerService.updateOneRemoteServer(input, workspaceId); + try { + return this.remoteServerService.updateOneRemoteServer(input, workspaceId); + } catch (error) { + remoteServerGraphqlApiExceptionHandler(error); + } } @Mutation(() => RemoteServerDTO) @@ -40,7 +49,11 @@ export class RemoteServerResolver { @Args('input') { id }: RemoteServerIdInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.remoteServerService.deleteOneRemoteServer(id, workspaceId); + try { + return this.remoteServerService.deleteOneRemoteServer(id, workspaceId); + } catch (error) { + remoteServerGraphqlApiExceptionHandler(error); + } } @Query(() => RemoteServerDTO) @@ -48,7 +61,14 @@ export class RemoteServerResolver { @Args('input') { id }: RemoteServerIdInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.remoteServerService.findOneByIdWithinWorkspace(id, workspaceId); + try { + return this.remoteServerService.findOneByIdWithinWorkspace( + id, + workspaceId, + ); + } catch (error) { + remoteServerGraphqlApiExceptionHandler(error); + } } @Query(() => [RemoteServerDTO]) @@ -57,9 +77,13 @@ export class RemoteServerResolver { { foreignDataWrapperType }: RemoteServerTypeInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.remoteServerService.findManyByTypeWithinWorkspace( - foreignDataWrapperType, - workspaceId, - ); + try { + return this.remoteServerService.findManyByTypeWithinWorkspace( + foreignDataWrapperType, + workspaceId, + ); + } catch (error) { + remoteServerGraphqlApiExceptionHandler(error); + } } } diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.service.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.service.ts index b7ad3a2e7..d1c241827 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-server.service.ts @@ -1,8 +1,4 @@ -import { - ForbiddenException, - Injectable, - NotFoundException, -} from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectDataSource, InjectRepository } from '@nestjs/typeorm'; import isEmpty from 'lodash.isempty'; @@ -27,6 +23,10 @@ import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/work import { buildUpdateRemoteServerRawQuery } from 'src/engine/metadata-modules/remote-server/utils/build-update-remote-server-raw-query.utils'; import { validateRemoteServerType } from 'src/engine/metadata-modules/remote-server/utils/validate-remote-server-type.util'; import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; +import { + RemoteServerException, + RemoteServerExceptionCode, +} from 'src/engine/metadata-modules/remote-server/remote-server.exception'; @Injectable() export class RemoteServerService { @@ -122,7 +122,10 @@ export class RemoteServerService { ); if (!remoteServer) { - throw new NotFoundException('Remote server does not exist'); + throw new RemoteServerException( + 'Remote server does not exist', + RemoteServerExceptionCode.REMOTE_SERVER_NOT_FOUND, + ); } const currentRemoteTablesForServer = @@ -132,8 +135,9 @@ export class RemoteServerService { }); if (currentRemoteTablesForServer.length > 0) { - throw new ForbiddenException( + throw new RemoteServerException( 'Cannot update remote server with synchronized tables', + RemoteServerExceptionCode.REMOTE_SERVER_MUTATION_NOT_ALLOWED, ); } @@ -207,7 +211,10 @@ export class RemoteServerService { }); if (!remoteServer) { - throw new NotFoundException('Remote server does not exist'); + throw new RemoteServerException( + 'Remote server does not exist', + RemoteServerExceptionCode.REMOTE_SERVER_NOT_FOUND, + ); } await this.remoteTableService.unsyncAll(workspaceId, remoteServer); diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.exception.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.exception.ts new file mode 100644 index 000000000..609da7a6f --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.exception.ts @@ -0,0 +1,13 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class DistantTableException extends CustomException { + code: DistantTableExceptionCode; + constructor(message: string, code: DistantTableExceptionCode) { + super(message, code); + } +} + +export enum DistantTableExceptionCode { + INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR', + TIMEOUT_ERROR = 'TIMEOUT_ERROR', +} diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.service.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.service.ts index d0e5fdade..337ab25be 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.service.ts @@ -1,8 +1,4 @@ -import { - BadRequestException, - Injectable, - RequestTimeoutException, -} from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { EntityManager, Repository } from 'typeorm'; @@ -17,6 +13,10 @@ import { DistantTables } from 'src/engine/metadata-modules/remote-server/remote- import { STRIPE_DISTANT_TABLES } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/utils/stripe-distant-tables.util'; import { PostgresTableSchemaColumn } from 'src/engine/metadata-modules/remote-server/types/postgres-table-schema-column'; import { isQueryTimeoutError } from 'src/engine/utils/query-timeout.util'; +import { + DistantTableException, + DistantTableExceptionCode, +} from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.exception'; @Injectable() export class DistantTableService { @@ -64,7 +64,10 @@ export class DistantTableService { tableName?: string, ): Promise { if (!remoteServer.schema) { - throw new BadRequestException('Remote server schema is not defined'); + throw new DistantTableException( + 'Remote server schema is not defined', + DistantTableExceptionCode.INTERNAL_SERVER_ERROR, + ); } const tmpSchemaId = v4(); @@ -116,8 +119,9 @@ export class DistantTableService { return distantTables; } catch (error) { if (isQueryTimeoutError(error)) { - throw new RequestTimeoutException( + throw new DistantTableException( `Could not find distant tables: ${error.message}`, + DistantTableExceptionCode.TIMEOUT_ERROR, ); } @@ -132,8 +136,9 @@ export class DistantTableService { case RemoteServerType.STRIPE_FDW: return STRIPE_DISTANT_TABLES; default: - throw new BadRequestException( + throw new DistantTableException( `Type ${remoteServer.foreignDataWrapperType} does not have a static schema.`, + DistantTableExceptionCode.INTERNAL_SERVER_ERROR, ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.exception.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.exception.ts new file mode 100644 index 000000000..3f3305823 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.exception.ts @@ -0,0 +1,13 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class ForeignTableException extends CustomException { + code: ForeignTableExceptionCode; + constructor(message: string, code: ForeignTableExceptionCode) { + super(message, code); + } +} + +export enum ForeignTableExceptionCode { + FOREIGN_TABLE_MUTATION_NOT_ALLOWED = 'FOREIGN_TABLE_MUTATION_NOT_ALLOWED', + INVALID_FOREIGN_TABLE_INPUT = 'INVALID_FOREIGN_TABLE_INPUT', +} diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.service.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.service.ts index c5951b69b..4f1f2d1cd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.service.ts @@ -1,10 +1,14 @@ -import { BadRequestException, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; import { RemoteServerEntity, RemoteServerType, } from 'src/engine/metadata-modules/remote-server/remote-server.entity'; import { RemoteTableStatus } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto'; +import { + ForeignTableException, + ForeignTableExceptionCode, +} from 'src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.exception'; import { getForeignTableColumnName } from 'src/engine/metadata-modules/remote-server/remote-table/foreign-table/utils/get-foreign-table-column-name.util'; import { PostgresTableSchemaColumn } from 'src/engine/metadata-modules/remote-server/types/postgres-table-schema-column'; import { WorkspaceCacheVersionService } from 'src/engine/metadata-modules/workspace-cache-version/workspace-cache-version.service'; @@ -90,8 +94,9 @@ export class ForeignTableService { } catch (exception) { this.workspaceMigrationService.deleteById(workspaceMigration.id); - throw new BadRequestException( + throw new ForeignTableException( 'Could not create foreign table. The table may already exists or a column type may not be supported.', + ForeignTableExceptionCode.INVALID_FOREIGN_TABLE_INPUT, ); } } @@ -130,7 +135,10 @@ export class ForeignTableService { } catch (exception) { this.workspaceMigrationService.deleteById(workspaceMigration.id); - throw new BadRequestException('Could not alter foreign table.'); + throw new ForeignTableException( + 'Could not alter foreign table.', + ForeignTableExceptionCode.FOREIGN_TABLE_MUTATION_NOT_ALLOWED, + ); } } @@ -167,7 +175,10 @@ export class ForeignTableService { case RemoteServerType.STRIPE_FDW: return { object: distantTableName }; default: - throw new BadRequestException('Foreign data wrapper not supported'); + throw new ForeignTableException( + 'Foreign data wrapper not supported', + ForeignTableExceptionCode.INVALID_FOREIGN_TABLE_INPUT, + ); } } } diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.exception.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.exception.ts new file mode 100644 index 000000000..32e99227c --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.exception.ts @@ -0,0 +1,17 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class RemoteTableException extends CustomException { + code: RemoteTableExceptionCode; + constructor(message: string, code: RemoteTableExceptionCode) { + super(message, code); + } +} + +export enum RemoteTableExceptionCode { + REMOTE_TABLE_NOT_FOUND = 'REMOTE_TABLE_NOT_FOUND', + INVALID_REMOTE_TABLE_INPUT = 'INVALID_REMOTE_TABLE_INPUT', + REMOTE_TABLE_ALREADY_EXISTS = 'REMOTE_TABLE_ALREADY_EXISTS', + NO_FOREIGN_TABLES_FOUND = 'NO_FOREIGN_TABLES_FOUND', + NO_OBJECT_METADATA_FOUND = 'NO_OBJECT_METADATA_FOUND', + NO_FIELD_METADATA_FOUND = 'NO_FIELD_METADATA_FOUND', +} diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.resolver.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.resolver.ts index 37abfc748..745df5eab 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.resolver.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.resolver.ts @@ -8,6 +8,7 @@ import { FindManyRemoteTablesInput } from 'src/engine/metadata-modules/remote-se import { RemoteTableInput } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table-input'; import { RemoteTableDTO } from 'src/engine/metadata-modules/remote-server/remote-table/dtos/remote-table.dto'; import { RemoteTableService } from 'src/engine/metadata-modules/remote-server/remote-table/remote-table.service'; +import { remoteTableGraphqlApiExceptionHandler } from 'src/engine/metadata-modules/remote-server/remote-table/utils/remote-table-graphql-api-exception-handler.util'; @UseGuards(JwtAuthGuard) @Resolver() @@ -19,11 +20,15 @@ export class RemoteTableResolver { @Args('input') input: FindManyRemoteTablesInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.remoteTableService.findDistantTablesWithStatus( - input.id, - workspaceId, - input.shouldFetchPendingSchemaUpdates, - ); + try { + return this.remoteTableService.findDistantTablesWithStatus( + input.id, + workspaceId, + input.shouldFetchPendingSchemaUpdates, + ); + } catch (error) { + remoteTableGraphqlApiExceptionHandler(error); + } } @Mutation(() => RemoteTableDTO) @@ -31,7 +36,11 @@ export class RemoteTableResolver { @Args('input') input: RemoteTableInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.remoteTableService.syncRemoteTable(input, workspaceId); + try { + return this.remoteTableService.syncRemoteTable(input, workspaceId); + } catch (error) { + remoteTableGraphqlApiExceptionHandler(error); + } } @Mutation(() => RemoteTableDTO) @@ -39,7 +48,11 @@ export class RemoteTableResolver { @Args('input') input: RemoteTableInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.remoteTableService.unsyncRemoteTable(input, workspaceId); + try { + return this.remoteTableService.unsyncRemoteTable(input, workspaceId); + } catch (error) { + remoteTableGraphqlApiExceptionHandler(error); + } } @Mutation(() => RemoteTableDTO) @@ -47,9 +60,13 @@ export class RemoteTableResolver { @Args('input') input: RemoteTableInput, @AuthWorkspace() { id: workspaceId }: Workspace, ) { - return this.remoteTableService.syncRemoteTableSchemaChanges( - input, - workspaceId, - ); + try { + return this.remoteTableService.syncRemoteTableSchemaChanges( + input, + workspaceId, + ); + } catch (error) { + remoteTableGraphqlApiExceptionHandler(error); + } } } diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.service.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.service.ts index 732108af8..9102839ed 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/remote-table.service.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Logger, NotFoundException } from '@nestjs/common'; +import { Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; @@ -40,6 +40,10 @@ import { } from 'src/engine/metadata-modules/workspace-migration/workspace-migration.entity'; import { CreateFieldInput } from 'src/engine/metadata-modules/field-metadata/dtos/create-field.input'; import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity'; +import { + RemoteTableException, + RemoteTableExceptionCode, +} from 'src/engine/metadata-modules/remote-server/remote-table/remote-table.exception'; export class RemoteTableService { private readonly logger = new Logger(RemoteTableService.name); @@ -74,7 +78,10 @@ export class RemoteTableService { }); if (!remoteServer) { - throw new NotFoundException('Remote server does not exist'); + throw new RemoteTableException( + 'Remote server does not exist', + RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT, + ); } const currentRemoteTables = await this.findRemoteTablesByServerId({ @@ -148,7 +155,10 @@ export class RemoteTableService { }); if (!remoteServer) { - throw new NotFoundException('Remote server does not exist'); + throw new RemoteTableException( + 'Remote server does not exist', + RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT, + ); } const currentRemoteTableWithSameDistantName = @@ -161,7 +171,10 @@ export class RemoteTableService { }); if (currentRemoteTableWithSameDistantName) { - throw new BadRequestException('Remote table already exists'); + throw new RemoteTableException( + 'Remote server does not exist', + RemoteTableExceptionCode.REMOTE_TABLE_ALREADY_EXISTS, + ); } const dataSourceMetatada = @@ -200,7 +213,10 @@ export class RemoteTableService { ); if (!distantTableColumns) { - throw new BadRequestException('Table not found'); + throw new RemoteTableException( + 'Remote server does not exist', + RemoteTableExceptionCode.REMOTE_TABLE_NOT_FOUND, + ); } // We only support remote tables with an id column for now. @@ -209,7 +225,10 @@ export class RemoteTableService { ); if (!distantTableIdColumn) { - throw new BadRequestException('Remote table must have an id column'); + throw new RemoteTableException( + 'Remote server does not exist', + RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT, + ); } await this.foreignTableService.createForeignTable( @@ -250,7 +269,10 @@ export class RemoteTableService { }); if (!remoteServer) { - throw new NotFoundException('Remote server does not exist'); + throw new RemoteTableException( + 'Remote server does not exist', + RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT, + ); } const remoteTable = await this.remoteTableRepository.findOne({ @@ -262,7 +284,10 @@ export class RemoteTableService { }); if (!remoteTable) { - throw new NotFoundException('Remote table does not exist'); + throw new RemoteTableException( + 'Remote table does not exist', + RemoteTableExceptionCode.REMOTE_TABLE_NOT_FOUND, + ); } await this.unsyncOne(workspaceId, remoteTable, remoteServer); @@ -302,7 +327,10 @@ export class RemoteTableService { }); if (!remoteServer) { - throw new NotFoundException('Remote server does not exist'); + throw new RemoteTableException( + 'Remote server does not exist', + RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT, + ); } const remoteTable = await this.remoteTableRepository.findOne({ @@ -314,7 +342,10 @@ export class RemoteTableService { }); if (!remoteTable) { - throw new NotFoundException('Remote table does not exist'); + throw new RemoteTableException( + 'Remote table does not exist', + RemoteTableExceptionCode.REMOTE_TABLE_NOT_FOUND, + ); } const distantTableColumns = @@ -379,7 +410,10 @@ export class RemoteTableService { ); if (!currentForeignTableNames.includes(remoteTable.localTableName)) { - throw new NotFoundException('Foreign table does not exist'); + throw new RemoteTableException( + 'Foreign table does not exist', + RemoteTableExceptionCode.NO_FOREIGN_TABLES_FOUND, + ); } const objectMetadata = @@ -507,8 +541,9 @@ export class RemoteTableService { }); if (!objectMetadata) { - throw new NotFoundException( + throw new RemoteTableException( `Cannot find associated object for table ${foreignTableName}`, + RemoteTableExceptionCode.NO_OBJECT_METADATA_FOUND, ); } for (const columnUpdate of columnsUpdates) { @@ -547,8 +582,9 @@ export class RemoteTableService { }); if (!fieldMetadataToDelete) { - throw new NotFoundException( + throw new RemoteTableException( `Cannot find associated field metadata for column ${columnName}`, + RemoteTableExceptionCode.NO_FIELD_METADATA_FOUND, ); } diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/utils/get-remote-table-local-name.util.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/utils/get-remote-table-local-name.util.ts index 92008e169..1df79156f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/utils/get-remote-table-local-name.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/utils/get-remote-table-local-name.util.ts @@ -1,9 +1,11 @@ -import { BadRequestException } from '@nestjs/common/exceptions'; - import { singular } from 'pluralize'; import { DataSource } from 'typeorm'; import { camelCase } from 'src/utils/camel-case'; +import { + RemoteTableException, + RemoteTableExceptionCode, +} from 'src/engine/metadata-modules/remote-server/remote-table/remote-table.exception'; const MAX_SUFFIX = 10; @@ -55,5 +57,8 @@ export const getRemoteTableLocalName = async ( } } - throw new BadRequestException('Table name is already taken.'); + throw new RemoteTableException( + 'Table name is already taken', + RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT, + ); }; diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/utils/remote-table-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/utils/remote-table-graphql-api-exception-handler.util.ts new file mode 100644 index 000000000..b7d65a9c1 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/utils/remote-table-graphql-api-exception-handler.util.ts @@ -0,0 +1,30 @@ +import { + RemoteTableException, + RemoteTableExceptionCode, +} from 'src/engine/metadata-modules/remote-server/remote-table/remote-table.exception'; +import { + UserInputError, + ConflictError, + InternalServerError, + NotFoundError, +} from 'src/engine/utils/graphql-errors.util'; + +export const remoteTableGraphqlApiExceptionHandler = (error: Error) => { + if (error instanceof RemoteTableException) { + switch (error.code) { + case RemoteTableExceptionCode.REMOTE_TABLE_NOT_FOUND: + case RemoteTableExceptionCode.NO_OBJECT_METADATA_FOUND: + case RemoteTableExceptionCode.NO_FOREIGN_TABLES_FOUND: + case RemoteTableExceptionCode.NO_FIELD_METADATA_FOUND: + throw new NotFoundError(error.message); + case RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT: + throw new UserInputError(error.message); + case RemoteTableExceptionCode.REMOTE_TABLE_ALREADY_EXISTS: + throw new ConflictError(error.message); + default: + throw new InternalServerError(error.message); + } + } + + throw error; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/build-update-remote-server-raw-query.utils.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/build-update-remote-server-raw-query.utils.ts index fff4778db..3e23e9b55 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/build-update-remote-server-raw-query.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/build-update-remote-server-raw-query.utils.ts @@ -1,10 +1,12 @@ -import { BadRequestException } from '@nestjs/common'; - import { ForeignDataWrapperOptions, RemoteServerEntity, RemoteServerType, } from 'src/engine/metadata-modules/remote-server/remote-server.entity'; +import { + RemoteServerException, + RemoteServerExceptionCode, +} from 'src/engine/metadata-modules/remote-server/remote-server.exception'; import { UserMappingOptions } from 'src/engine/metadata-modules/remote-server/types/user-mapping-options'; export type DeepPartial = { @@ -49,7 +51,10 @@ export const buildUpdateRemoteServerRawQuery = ( } if (options.length < 1) { - throw new BadRequestException('No fields to update'); + throw new RemoteServerException( + 'No fields to update', + RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT, + ); } const rawQuery = `UPDATE metadata."remoteServer" SET ${options.join( diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/remote-server-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/remote-server-graphql-api-exception-handler.util.ts new file mode 100644 index 000000000..394eb7330 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/remote-server-graphql-api-exception-handler.util.ts @@ -0,0 +1,30 @@ +import { + RemoteServerException, + RemoteServerExceptionCode, +} from 'src/engine/metadata-modules/remote-server/remote-server.exception'; +import { + UserInputError, + ForbiddenError, + ConflictError, + InternalServerError, + NotFoundError, +} from 'src/engine/utils/graphql-errors.util'; + +export const remoteServerGraphqlApiExceptionHandler = (error: any) => { + if (error instanceof RemoteServerException) { + switch (error.code) { + case RemoteServerExceptionCode.REMOTE_SERVER_NOT_FOUND: + throw new NotFoundError(error.message); + case RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT: + throw new UserInputError(error.message); + case RemoteServerExceptionCode.REMOTE_SERVER_MUTATION_NOT_ALLOWED: + throw new ForbiddenError(error.message); + case RemoteServerExceptionCode.REMOTE_SERVER_ALREADY_EXISTS: + throw new ConflictError(error.message); + default: + throw new InternalServerError(error.message); + } + } + + throw error; +}; diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/validate-remote-server-input.utils.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/validate-remote-server-input.utils.ts index 99de7fa95..38cfecdcf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/validate-remote-server-input.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/validate-remote-server-input.utils.ts @@ -1,7 +1,10 @@ -import { BadRequestException } from '@nestjs/common'; - import { isDefined } from 'class-validator'; +import { + RemoteServerException, + RemoteServerExceptionCode, +} from 'src/engine/metadata-modules/remote-server/remote-server.exception'; + const INPUT_REGEX = /^([A-Za-z0-9\-_.@]+)$/; export const validateObjectAgainstInjections = (input: object) => { @@ -21,6 +24,9 @@ export const validateObjectAgainstInjections = (input: object) => { export const validateStringAgainstInjections = (input: string) => { if (!INPUT_REGEX.test(input)) { - throw new BadRequestException('Invalid remote server input'); + throw new RemoteServerException( + 'Invalid remote server input', + RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT, + ); } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/validate-remote-server-type.util.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/validate-remote-server-type.util.ts index 7a158e6a7..333353970 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/validate-remote-server-type.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/utils/validate-remote-server-type.util.ts @@ -1,5 +1,3 @@ -import { BadRequestException } from '@nestjs/common'; - import { Repository } from 'typeorm'; import { @@ -7,6 +5,10 @@ import { FeatureFlagKeys, } from 'src/engine/core-modules/feature-flag/feature-flag.entity'; import { RemoteServerType } from 'src/engine/metadata-modules/remote-server/remote-server.entity'; +import { + RemoteServerException, + RemoteServerExceptionCode, +} from 'src/engine/metadata-modules/remote-server/remote-server.exception'; export const validateRemoteServerType = async ( remoteServerType: RemoteServerType, @@ -24,7 +26,10 @@ export const validateRemoteServerType = async ( const featureFlagEnabled = featureFlag && featureFlag.value; if (!featureFlagEnabled) { - throw new BadRequestException(`Type ${remoteServerType} is not supported.`); + throw new RemoteServerException( + `Type ${remoteServerType} is not supported.`, + RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT, + ); } }; @@ -35,8 +40,9 @@ const getFeatureFlagKey = (remoteServerType: RemoteServerType) => { case RemoteServerType.STRIPE_FDW: return FeatureFlagKeys.IsStripeIntegrationEnabled; default: - throw new BadRequestException( + throw new RemoteServerException( `Type ${remoteServerType} is not supported.`, + RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT, ); } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-metadata-name.spec.ts b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-metadata-name.spec.ts index d2e03eac6..1533d0ce6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-metadata-name.spec.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/__tests__/validate-metadata-name.spec.ts @@ -1,5 +1,7 @@ -import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; -import { validateMetadataName } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; +import { + validateMetadataName, + InvalidStringException, +} from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; describe('validateMetadataName', () => { it('does not throw if string is valid', () => { diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name.utils.ts index 16e8bfd56..0711863cd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name.utils.ts @@ -1,5 +1,3 @@ -import { InvalidStringException } from 'src/engine/metadata-modules/errors/InvalidStringException'; - const VALID_STRING_PATTERN = /^[a-z][a-zA-Z0-9]*$/; export const validateMetadataName = (string: string) => { @@ -7,3 +5,11 @@ export const validateMetadataName = (string: string) => { throw new InvalidStringException(string); } }; + +export class InvalidStringException extends Error { + constructor(string: string) { + const message = `String "${string}" is not valid`; + + super(message); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts index 093b3cba9..a22048718 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory.ts @@ -13,6 +13,10 @@ import { serializeDefaultValue } from 'src/engine/metadata-modules/field-metadat import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/workspace-migration/utils/field-metadata-type-to-column-type.util'; import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory'; import { computeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; +import { + WorkspaceMigrationException, + WorkspaceMigrationExceptionCode, +} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception'; export type BasicFieldMetadataType = | FieldMetadataType.UUID @@ -66,8 +70,9 @@ export class BasicColumnActionFactory extends ColumnActionAbstractFactory, _options?: WorkspaceColumnActionOptions, ): WorkspaceMigrationColumnCreate[] { - throw new Error('handleCreateAction method not implemented.'); + throw new WorkspaceMigrationException( + 'handleCreateAction method not implemented.', + WorkspaceMigrationExceptionCode.INVALID_ACTION, + ); } protected handleAlterAction( @@ -61,6 +73,9 @@ export class ColumnActionAbstractFactory< _alteredFieldMetadata: FieldMetadataInterface, _options?: WorkspaceColumnActionOptions, ): WorkspaceMigrationColumnAlter[] { - throw new Error('handleAlterAction method not implemented.'); + throw new WorkspaceMigrationException( + 'handleAlterAction method not implemented.', + WorkspaceMigrationExceptionCode.INVALID_ACTION, + ); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory.ts index 5947ba2c9..a74bdd4e1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory.ts @@ -13,6 +13,10 @@ import { fieldMetadataTypeToColumnType } from 'src/engine/metadata-modules/works import { ColumnActionAbstractFactory } from 'src/engine/metadata-modules/workspace-migration/factories/column-action-abstract.factory'; import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { compositeTypeDefintions } from 'src/engine/metadata-modules/field-metadata/composite-types'; +import { + WorkspaceMigrationException, + WorkspaceMigrationExceptionCode, +} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception'; export type CompositeFieldMetadataType = | FieldMetadataType.ADDRESS @@ -34,8 +38,9 @@ export class CompositeColumnActionFactory extends ColumnActionAbstractFactory( fieldMetadataType: Type, @@ -34,6 +38,9 @@ export const fieldMetadataTypeToColumnType = ( case FieldMetadataType.RAW_JSON: return 'jsonb'; default: - throw new Error(`Cannot convert ${fieldMetadataType} to column type.`); + throw new WorkspaceMigrationException( + `Cannot convert ${fieldMetadataType} to column type.`, + WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA, + ); } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.exception.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.exception.ts new file mode 100644 index 000000000..6340aeaa2 --- /dev/null +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.exception.ts @@ -0,0 +1,14 @@ +import { CustomException } from 'src/utils/custom-exception'; + +export class WorkspaceMigrationException extends CustomException { + code: WorkspaceMigrationExceptionCode; + constructor(message: string, code: WorkspaceMigrationExceptionCode) { + super(message, code); + } +} + +export enum WorkspaceMigrationExceptionCode { + NO_FACTORY_FOUND = 'NO_FACTORY_FOUND', + INVALID_ACTION = 'INVALID_ACTION', + INVALID_FIELD_METADATA = 'INVALID_FIELD_METADATA', +} diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts index 6b3697294..6a62f3b6b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.factory.ts @@ -12,6 +12,10 @@ import { import { BasicColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/basic-column-action.factory'; import { EnumColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/enum-column-action.factory'; import { CompositeColumnActionFactory } from 'src/engine/metadata-modules/workspace-migration/factories/composite-column-action.factory'; +import { + WorkspaceMigrationException, + WorkspaceMigrationExceptionCode, +} from 'src/engine/metadata-modules/workspace-migration/workspace-migration.exception'; @Injectable() export class WorkspaceMigrationFactory { @@ -131,7 +135,10 @@ export class WorkspaceMigrationFactory { undefinedOrAlteredFieldMetadata, ); - throw new Error(`No field metadata provided for action ${action}`); + throw new WorkspaceMigrationException( + `No field metadata provided for action ${action}`, + WorkspaceMigrationExceptionCode.INVALID_ACTION, + ); } const columnActions = this.createColumnAction( @@ -161,7 +168,10 @@ export class WorkspaceMigrationFactory { }, ); - throw new Error(`No factory found for type ${alteredFieldMetadata.type}`); + throw new WorkspaceMigrationException( + `No factory found for type ${alteredFieldMetadata.type}`, + WorkspaceMigrationExceptionCode.NO_FACTORY_FOUND, + ); } return factory.create( diff --git a/packages/twenty-server/src/engine/utils/graphql-errors.util.ts b/packages/twenty-server/src/engine/utils/graphql-errors.util.ts index a33cec928..613225796 100644 --- a/packages/twenty-server/src/engine/utils/graphql-errors.util.ts +++ b/packages/twenty-server/src/engine/utils/graphql-errors.util.ts @@ -183,3 +183,11 @@ export class TimeoutError extends BaseGraphQLError { Object.defineProperty(this, 'name', { value: 'TimeoutError' }); } } + +export class InternalServerError extends BaseGraphQLError { + constructor(message: string) { + super(message, ErrorCode.INTERNAL_SERVER_ERROR); + + Object.defineProperty(this, 'name', { value: 'InternalServerError' }); + } +} diff --git a/packages/twenty-server/src/utils/custom-exception.ts b/packages/twenty-server/src/utils/custom-exception.ts new file mode 100644 index 000000000..e12d8840c --- /dev/null +++ b/packages/twenty-server/src/utils/custom-exception.ts @@ -0,0 +1,8 @@ +export class CustomException extends Error { + code: string; + + constructor(message: string, code: string) { + super(message); + this.code = code; + } +}