From 9c2b88870fe9c2f1341a60a7c9e66d02f3ac6126 Mon Sep 17 00:00:00 2001 From: Marie <51697796+ijreilly@users.noreply.github.com> Date: Wed, 14 May 2025 11:00:06 +0200 Subject: [PATCH] Improve sentry grouping (#12007) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This PR attemps at improving sentry grouping and filtering by - Using the exceptionCode as the fingerprint when the error is a customException. For this to work in this PR we are now throwing customExceptions instead of internalServerError deprived of their code. They will still be converted to Internal server errors when sent back as response - Filtering 4xx issues where it was missing (for emailVerification because errors were not handled, for invalid captcha and billing errors because they are httpErrors and not graphqlErrors) --------- Co-authored-by: Félix Malfait --- .../src/modules/auth/hooks/useAuth.ts | 2 +- .../errors/graphql-query-runner.exception.ts | 3 +- ...hql-query-runner-exception-handler.util.ts | 13 +++++++-- .../utils/workspace-exception-handler.util.ts | 9 ++++-- .../workspace-query-runner.exception.ts | 2 ++ .../api/graphql/workspace-schema.factory.ts | 8 +++-- .../core-modules/auth/auth.exception.ts | 1 + .../auth-graphql-api-exception.filter.ts | 25 ++++++++++++---- ...ail-verification-exception-handler.util.ts | 29 +++++++++++++++++++ .../email-verification.exception.ts | 1 + .../email-verification.resolver.ts | 27 +++++++++++------ .../drivers/sentry.driver.ts | 9 ++++-- .../graphql/utils/graphql-errors.util.ts | 17 +++-------- .../utils/should-capture-exception.util.ts | 11 ++++++- .../search/exceptions/search.exception.ts | 3 +- .../filters/search-api-exception.filter.ts | 15 +++++++--- .../core-modules/search/search.resolver.ts | 22 ++++++++------ ...g-variable-graphql-api-exception.filter.ts | 10 +++++-- .../twenty-config/twenty-config.exception.ts | 2 +- ...ow-trigger-graphql-api-exception.filter.ts | 10 +++++-- ...graphql-api-exception-handler.util.spec.ts | 4 +-- ...pace-graphql-api-exception-handler.util.ts | 10 +++++-- .../workspace/workspace.exception.ts | 1 + .../field-metadata.exception.ts | 1 + .../field-metadata/field-metadata.service.ts | 6 ++-- .../field-metadata-relation.service.ts | 16 +++++++--- ...data-graphql-api-exception-handler.util.ts | 9 ++++-- .../object-metadata.exception.ts | 1 + ...data-graphql-api-exception-handler.util.ts | 10 +++++-- .../validate-object-metadata-input.util.ts | 6 ++-- .../permissions/permissions.exception.ts | 1 + ...sion-graphql-api-exception-handler.util.ts | 18 ++++++++++-- .../relation-metadata.exception.ts | 1 + .../relation-metadata.service.ts | 18 +++++++++--- ...data-graphql-api-exception-handler.util.ts | 13 +++++---- .../remote-server/remote-server.exception.ts | 1 + .../remote-table/remote-table.exception.ts | 1 + ...able-graphql-api-exception-handler.util.ts | 8 +++-- ...rver-graphql-api-exception-handler.util.ts | 10 +++++-- .../serverless-function.exception.ts | 1 + ...ion-graphql-api-exception-handler.utils.ts | 11 +++++-- .../compute-metadata-name-from-label.util.ts | 15 ++++++++-- .../invalid-metadata-name.exception.ts | 5 ---- .../exceptions/invalid-metadata.exception.ts | 20 +++++++++++-- .../validate-field-name-availability.utils.ts | 15 ++++++++-- ...idate-metadata-name-is-camel-case.utils.ts | 8 +++-- ...e-metadata-name-is-not-reserved-keyword.ts | 8 +++-- ...ate-metadata-name-is-not-too-long.utils.ts | 8 +++-- ...te-metadata-name-is-not-too-short.utils.ts | 10 +++++-- ...er-and-contain-digits-nor-letters.utils.ts | 8 +++-- ...e-name-and-label-are-sync-or-throw.util.ts | 16 ++++++++-- .../workspace-metadata-cache.exception.ts | 2 +- .../workspace-metadata-cache.service.ts | 12 ++++---- .../factories/workspace-datasource.factory.ts | 10 +++++-- ...-event-import-exception-handler.service.ts | 19 +++++++----- ...saging-import-exception-handler.service.ts | 5 +++- .../exceptions/workflow-trigger.exception.ts | 1 + 57 files changed, 376 insertions(+), 152 deletions(-) create mode 100644 packages/twenty-server/src/engine/core-modules/email-verification/email-verification-exception-handler.util.ts delete mode 100644 packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception.ts diff --git a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts index 438e0aa9206..b841b01c079 100644 --- a/packages/twenty-front/src/modules/auth/hooks/useAuth.ts +++ b/packages/twenty-front/src/modules/auth/hooks/useAuth.ts @@ -194,7 +194,7 @@ export const useAuth = () => { // TODO: Get intellisense for graphql error extensions code (codegen?) if ( error instanceof ApolloError && - error.graphQLErrors[0]?.extensions?.code === 'EMAIL_NOT_VERIFIED' + error.graphQLErrors[0]?.extensions?.subCode === 'EMAIL_NOT_VERIFIED' ) { setSearchParams({ email }); setSignInUpStep(SignInUpStep.EmailVerification); diff --git a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception.ts b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception.ts index 24e736c73c7..ab5869b52e0 100644 --- a/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception.ts +++ b/packages/twenty-server/src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception.ts @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class GraphqlQueryRunnerException extends CustomException { + declare code: GraphqlQueryRunnerExceptionCode; constructor(message: string, code: GraphqlQueryRunnerExceptionCode) { super(message, code); } @@ -18,8 +19,6 @@ export enum GraphqlQueryRunnerExceptionCode { RECORD_NOT_FOUND = 'RECORD_NOT_FOUND', INVALID_ARGS_FIRST = 'INVALID_ARGS_FIRST', INVALID_ARGS_LAST = 'INVALID_ARGS_LAST', - METADATA_CACHE_VERSION_NOT_FOUND = 'METADATA_CACHE_VERSION_NOT_FOUND', - METADATA_CACHE_FEATURE_FLAG_RECOMPUTATION_REQUIRED = 'METADATA_CACHE_FEATURE_FLAG_RECOMPUTATION_REQUIRED', RELATION_SETTINGS_NOT_FOUND = 'RELATION_SETTINGS_NOT_FOUND', RELATION_TARGET_OBJECT_METADATA_NOT_FOUND = 'RELATION_TARGET_OBJECT_METADATA_NOT_FOUND', NOT_IMPLEMENTED = 'NOT_IMPLEMENTED', diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/graphql-query-runner-exception-handler.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/graphql-query-runner-exception-handler.util.ts index 8a279908433..c861941b22b 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/graphql-query-runner-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/graphql-query-runner-exception-handler.util.ts @@ -3,7 +3,6 @@ import { GraphqlQueryRunnerExceptionCode, } from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { - InternalServerError, NotFoundError, UserInputError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; @@ -26,7 +25,15 @@ export const graphqlQueryRunnerExceptionHandler = ( throw new UserInputError(error.message); case GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND: throw new NotFoundError(error.message); - default: - throw new InternalServerError(error.message); + case GraphqlQueryRunnerExceptionCode.RELATION_SETTINGS_NOT_FOUND: + case GraphqlQueryRunnerExceptionCode.RELATION_TARGET_OBJECT_METADATA_NOT_FOUND: + case GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_COLLECTION_NOT_FOUND: + case GraphqlQueryRunnerExceptionCode.INVALID_POST_HOOK_PAYLOAD: + throw error; + default: { + const _exhaustiveCheck: never = error.code; + + throw error; + } } }; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-exception-handler.util.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-exception-handler.util.ts index 69b9b3fa134..04eab266f99 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/utils/workspace-exception-handler.util.ts @@ -4,7 +4,6 @@ import { } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception'; import { ForbiddenError, - InternalServerError, NotFoundError, TimeoutError, UserInputError, @@ -26,7 +25,11 @@ export const workspaceExceptionHandler = ( case WorkspaceQueryRunnerExceptionCode.QUERY_TIMEOUT: throw new TimeoutError(error.message); case WorkspaceQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR: - default: - throw new InternalServerError(error.message); + throw error; + default: { + const _exhaustiveCheck: never = error.code; + + throw error; + } } }; diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception.ts b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception.ts index 0f33bdd6537..9a7ca365f04 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-query-runner/workspace-query-runner.exception.ts @@ -1,8 +1,10 @@ import { CustomException } from 'src/utils/custom-exception'; export class WorkspaceQueryRunnerException extends CustomException { + code: WorkspaceQueryRunnerExceptionCode; constructor(message: string, code: WorkspaceQueryRunnerExceptionCode) { super(message, code); + this.code = code; } } diff --git a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts index 4a9afd30849..30054bdf87e 100644 --- a/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts +++ b/packages/twenty-server/src/engine/api/graphql/workspace-schema.factory.ts @@ -19,6 +19,10 @@ import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twent import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; +import { + WorkspaceMetadataVersionException, + WorkspaceMetadataVersionExceptionCode, +} from 'src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; @Injectable() @@ -89,9 +93,9 @@ export class WorkspaceSchemaFactory { } if (!currentCacheVersion) { - throw new GraphqlQueryRunnerException( + throw new WorkspaceMetadataVersionException( 'Metadata cache version not found', - GraphqlQueryRunnerExceptionCode.METADATA_CACHE_VERSION_NOT_FOUND, + WorkspaceMetadataVersionExceptionCode.METADATA_VERSION_NOT_FOUND, ); } diff --git a/packages/twenty-server/src/engine/core-modules/auth/auth.exception.ts b/packages/twenty-server/src/engine/core-modules/auth/auth.exception.ts index ef6d2f3e9d5..777259d48ee 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/auth.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/auth.exception.ts @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class AuthException extends CustomException { + declare code: AuthExceptionCode; constructor(message: string, code: AuthExceptionCode) { super(message, code); } diff --git a/packages/twenty-server/src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter.ts b/packages/twenty-server/src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter.ts index 818fc399f0d..c3af4bb73cc 100644 --- a/packages/twenty-server/src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter.ts @@ -6,9 +6,7 @@ import { } from 'src/engine/core-modules/auth/auth.exception'; import { AuthenticationError, - EmailNotVerifiedError, ForbiddenError, - InternalServerError, NotFoundError, UserInputError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; @@ -21,18 +19,33 @@ export class AuthGraphqlApiExceptionFilter implements ExceptionFilter { throw new NotFoundError(exception.message); case AuthExceptionCode.INVALID_INPUT: throw new UserInputError(exception.message); - case AuthExceptionCode.EMAIL_NOT_VERIFIED: - throw new EmailNotVerifiedError(exception.message); case AuthExceptionCode.FORBIDDEN_EXCEPTION: throw new ForbiddenError(exception.message); + case AuthExceptionCode.EMAIL_NOT_VERIFIED: + throw new ForbiddenError(exception.message, { + subCode: AuthExceptionCode.EMAIL_NOT_VERIFIED, + }); case AuthExceptionCode.UNAUTHENTICATED: case AuthExceptionCode.USER_NOT_FOUND: case AuthExceptionCode.WORKSPACE_NOT_FOUND: throw new AuthenticationError(exception.message); case AuthExceptionCode.INVALID_DATA: case AuthExceptionCode.INTERNAL_SERVER_ERROR: - default: - throw new InternalServerError(exception.message); + case AuthExceptionCode.USER_WORKSPACE_NOT_FOUND: + case AuthExceptionCode.INSUFFICIENT_SCOPES: + case AuthExceptionCode.OAUTH_ACCESS_DENIED: + case AuthExceptionCode.SSO_AUTH_FAILED: + case AuthExceptionCode.USE_SSO_AUTH: + case AuthExceptionCode.SIGNUP_DISABLED: + case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED: + case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED: + case AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE: + throw exception; + default: { + const _exhaustiveCheck: never = exception.code; + + throw exception; + } } } } diff --git a/packages/twenty-server/src/engine/core-modules/email-verification/email-verification-exception-handler.util.ts b/packages/twenty-server/src/engine/core-modules/email-verification/email-verification-exception-handler.util.ts new file mode 100644 index 00000000000..367071dbe06 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/email-verification/email-verification-exception-handler.util.ts @@ -0,0 +1,29 @@ +import { + EmailVerificationException, + EmailVerificationExceptionCode, +} from 'src/engine/core-modules/email-verification/email-verification.exception'; +import { + ForbiddenError, + UserInputError, +} from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; + +export const emailVerificationGraphqlApiExceptionHandler = ( + error: EmailVerificationException, +) => { + switch (error.code) { + case EmailVerificationExceptionCode.INVALID_TOKEN: + case EmailVerificationExceptionCode.INVALID_APP_TOKEN_TYPE: + case EmailVerificationExceptionCode.TOKEN_EXPIRED: + case EmailVerificationExceptionCode.RATE_LIMIT_EXCEEDED: + throw new ForbiddenError(error.message); + case EmailVerificationExceptionCode.EMAIL_MISSING: + case EmailVerificationExceptionCode.EMAIL_ALREADY_VERIFIED: + case EmailVerificationExceptionCode.EMAIL_VERIFICATION_NOT_REQUIRED: + throw new UserInputError(error.message); + default: { + const _exhaustiveCheck: never = error.code; + + throw error; + } + } +}; diff --git a/packages/twenty-server/src/engine/core-modules/email-verification/email-verification.exception.ts b/packages/twenty-server/src/engine/core-modules/email-verification/email-verification.exception.ts index cd3c49d961e..8cc641e8d37 100644 --- a/packages/twenty-server/src/engine/core-modules/email-verification/email-verification.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/email-verification/email-verification.exception.ts @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class EmailVerificationException extends CustomException { + declare code: EmailVerificationExceptionCode; constructor(message: string, code: EmailVerificationExceptionCode) { super(message, code); } diff --git a/packages/twenty-server/src/engine/core-modules/email-verification/email-verification.resolver.ts b/packages/twenty-server/src/engine/core-modules/email-verification/email-verification.resolver.ts index 4018e1892a4..db286c95cc7 100644 --- a/packages/twenty-server/src/engine/core-modules/email-verification/email-verification.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/email-verification/email-verification.resolver.ts @@ -5,6 +5,8 @@ import { SOURCE_LOCALE } from 'twenty-shared/translations'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service'; import { ResendEmailVerificationTokenInput } from 'src/engine/core-modules/email-verification/dtos/resend-email-verification-token.input'; import { ResendEmailVerificationTokenOutput } from 'src/engine/core-modules/email-verification/dtos/resend-email-verification-token.output'; +import { emailVerificationGraphqlApiExceptionHandler } from 'src/engine/core-modules/email-verification/email-verification-exception-handler.util'; +import { EmailVerificationException } from 'src/engine/core-modules/email-verification/email-verification.exception'; import { EmailVerificationService } from 'src/engine/core-modules/email-verification/services/email-verification.service'; import { I18nContext } from 'src/engine/core-modules/i18n/types/i18n-context.type'; import { OriginHeader } from 'src/engine/decorators/auth/origin-header.decorator'; @@ -23,15 +25,22 @@ export class EmailVerificationResolver { @OriginHeader() origin: string, @Context() context: I18nContext, ): Promise { - const workspace = - await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace( - origin, - ); + try { + const workspace = + await this.domainManagerService.getWorkspaceByOriginOrDefaultWorkspace( + origin, + ); - return await this.emailVerificationService.resendEmailVerificationToken( - resendEmailVerificationTokenInput.email, - workspace, - context.req.headers['x-locale'] ?? SOURCE_LOCALE, - ); + return await this.emailVerificationService.resendEmailVerificationToken( + resendEmailVerificationTokenInput.email, + workspace, + context.req.headers['x-locale'] ?? SOURCE_LOCALE, + ); + } catch (error) { + if (error instanceof EmailVerificationException) { + return emailVerificationGraphqlApiExceptionHandler(error); + } + throw error; + } } } diff --git a/packages/twenty-server/src/engine/core-modules/exception-handler/drivers/sentry.driver.ts b/packages/twenty-server/src/engine/core-modules/exception-handler/drivers/sentry.driver.ts index 28d6ab0471c..95adbb1267e 100644 --- a/packages/twenty-server/src/engine/core-modules/exception-handler/drivers/sentry.driver.ts +++ b/packages/twenty-server/src/engine/core-modules/exception-handler/drivers/sentry.driver.ts @@ -3,6 +3,7 @@ import * as Sentry from '@sentry/node'; import { ExceptionHandlerOptions } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-options.interface'; import { ExceptionHandlerDriverInterface } from 'src/engine/core-modules/exception-handler/interfaces'; +import { CustomException } from 'src/utils/custom-exception'; export class ExceptionHandlerSentryDriver implements ExceptionHandlerDriverInterface @@ -15,8 +16,7 @@ export class ExceptionHandlerSentryDriver Sentry.withScope((scope) => { if (options?.operation) { - scope.setTag('operation', options.operation.name); - scope.setTag('operationName', options.operation.name); + scope.setExtra('operation', options.operation.name); } if (options?.document) { @@ -49,6 +49,11 @@ export class ExceptionHandlerSentryDriver }); } + if (exception instanceof CustomException) { + scope.setTag('customExceptionCode', exception.code); + scope.setFingerprint([exception.code]); + } + const eventId = Sentry.captureException(exception, { contexts: { GraphQL: { diff --git a/packages/twenty-server/src/engine/core-modules/graphql/utils/graphql-errors.util.ts b/packages/twenty-server/src/engine/core-modules/graphql/utils/graphql-errors.util.ts index d91ef60ade1..701159c0fb7 100644 --- a/packages/twenty-server/src/engine/core-modules/graphql/utils/graphql-errors.util.ts +++ b/packages/twenty-server/src/engine/core-modules/graphql/utils/graphql-errors.util.ts @@ -24,7 +24,6 @@ export enum ErrorCode { PERSISTED_QUERY_NOT_SUPPORTED = 'PERSISTED_QUERY_NOT_SUPPORTED', BAD_USER_INPUT = 'BAD_USER_INPUT', NOT_FOUND = 'NOT_FOUND', - EMAIL_NOT_VERIFIED = 'EMAIL_NOT_VERIFIED', METHOD_NOT_ALLOWED = 'METHOD_NOT_ALLOWED', CONFLICT = 'CONFLICT', TIMEOUT = 'TIMEOUT', @@ -107,16 +106,16 @@ export class ValidationError extends BaseGraphQLError { } export class AuthenticationError extends BaseGraphQLError { - constructor(message: string) { - super(message, ErrorCode.UNAUTHENTICATED); + constructor(message: string, extensions?: Record) { + super(message, ErrorCode.UNAUTHENTICATED, extensions); Object.defineProperty(this, 'name', { value: 'AuthenticationError' }); } } export class ForbiddenError extends BaseGraphQLError { - constructor(message: string) { - super(message, ErrorCode.FORBIDDEN); + constructor(message: string, extensions?: Record) { + super(message, ErrorCode.FORBIDDEN, extensions); Object.defineProperty(this, 'name', { value: 'ForbiddenError' }); } @@ -161,14 +160,6 @@ export class NotFoundError extends BaseGraphQLError { } } -export class EmailNotVerifiedError extends BaseGraphQLError { - constructor(message: string) { - super(message, ErrorCode.EMAIL_NOT_VERIFIED); - - Object.defineProperty(this, 'name', { value: 'EmailNotVerifiedError' }); - } -} - export class MethodNotAllowedError extends BaseGraphQLError { constructor(message: string) { super(message, ErrorCode.METHOD_NOT_ALLOWED); diff --git a/packages/twenty-server/src/engine/core-modules/graphql/utils/should-capture-exception.util.ts b/packages/twenty-server/src/engine/core-modules/graphql/utils/should-capture-exception.util.ts index 817f6d76ee9..2af14c8e727 100644 --- a/packages/twenty-server/src/engine/core-modules/graphql/utils/should-capture-exception.util.ts +++ b/packages/twenty-server/src/engine/core-modules/graphql/utils/should-capture-exception.util.ts @@ -1,3 +1,5 @@ +import { HttpException } from '@nestjs/common'; + import { BaseGraphQLError, ErrorCode, @@ -12,7 +14,6 @@ export const graphQLErrorCodesToFilterOut = [ ErrorCode.TIMEOUT, ErrorCode.CONFLICT, ErrorCode.BAD_USER_INPUT, - ErrorCode.EMAIL_NOT_VERIFIED, ]; export const shouldCaptureException = (exception: Error): boolean => { @@ -23,5 +24,13 @@ export const shouldCaptureException = (exception: Error): boolean => { return false; } + if ( + exception instanceof HttpException && + exception.getStatus() >= 400 && + exception.getStatus() < 500 + ) { + return false; + } + return true; }; diff --git a/packages/twenty-server/src/engine/core-modules/search/exceptions/search.exception.ts b/packages/twenty-server/src/engine/core-modules/search/exceptions/search.exception.ts index 72b88d200c2..97603519b16 100644 --- a/packages/twenty-server/src/engine/core-modules/search/exceptions/search.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/search/exceptions/search.exception.ts @@ -1,13 +1,12 @@ import { CustomException } from 'src/utils/custom-exception'; export class SearchException extends CustomException { + declare code: SearchExceptionCode; constructor(message: string, code: SearchExceptionCode) { super(message, code); } } export enum SearchExceptionCode { - METADATA_CACHE_VERSION_NOT_FOUND = 'METADATA_CACHE_VERSION_NOT_FOUND', LABEL_IDENTIFIER_FIELD_NOT_FOUND = 'LABEL_IDENTIFIER_FIELD_NOT_FOUND', - OBJECT_METADATA_MAP_NOT_FOUND = 'OBJECT_METADATA_MAP_NOT_FOUND', } diff --git a/packages/twenty-server/src/engine/core-modules/search/filters/search-api-exception.filter.ts b/packages/twenty-server/src/engine/core-modules/search/filters/search-api-exception.filter.ts index d8f0c13c8c7..43f901cf600 100644 --- a/packages/twenty-server/src/engine/core-modules/search/filters/search-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/core-modules/search/filters/search-api-exception.filter.ts @@ -1,7 +1,9 @@ import { Catch, ExceptionFilter } from '@nestjs/common'; -import { InternalServerError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; -import { SearchException } from 'src/engine/core-modules/search/exceptions/search.exception'; +import { + SearchException, + SearchExceptionCode, +} from 'src/engine/core-modules/search/exceptions/search.exception'; @Catch(SearchException) export class SearchApiExceptionFilter implements ExceptionFilter { @@ -9,8 +11,13 @@ export class SearchApiExceptionFilter implements ExceptionFilter { catch(exception: SearchException) { switch (exception.code) { - default: - throw new InternalServerError(exception.message); + case SearchExceptionCode.LABEL_IDENTIFIER_FIELD_NOT_FOUND: + throw exception; + default: { + const _exhaustiveCheck: never = exception.code; + + throw exception; + } } } } diff --git a/packages/twenty-server/src/engine/core-modules/search/search.resolver.ts b/packages/twenty-server/src/engine/core-modules/search/search.resolver.ts index 88a19dd506b..7bce44e97c2 100644 --- a/packages/twenty-server/src/engine/core-modules/search/search.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/search/search.resolver.ts @@ -7,16 +7,20 @@ import { ObjectRecordFilter } from 'src/engine/api/graphql/workspace-query-build import { SearchArgs } from 'src/engine/core-modules/search/dtos/search-args'; import { SearchRecordDTO } from 'src/engine/core-modules/search/dtos/search-record-dto'; -import { - SearchException, - SearchExceptionCode, -} from 'src/engine/core-modules/search/exceptions/search.exception'; import { SearchApiExceptionFilter } from 'src/engine/core-modules/search/filters/search-api-exception.filter'; import { SearchService } from 'src/engine/core-modules/search/services/search.service'; import { RecordsWithObjectMetadataItem } from 'src/engine/core-modules/search/types/records-with-object-metadata-item'; import { formatSearchTerms } from 'src/engine/core-modules/search/utils/format-search-terms'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator'; +import { + WorkspaceMetadataCacheException, + WorkspaceMetadataCacheExceptionCode, +} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception'; +import { + WorkspaceMetadataVersionException, + WorkspaceMetadataVersionExceptionCode, +} from 'src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception'; import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; @@ -47,9 +51,9 @@ export class SearchResolver { await this.workspaceCacheStorageService.getMetadataVersion(workspace.id); if (currentCacheVersion === undefined) { - throw new SearchException( - 'Metadata cache version not found', - SearchExceptionCode.METADATA_CACHE_VERSION_NOT_FOUND, + throw new WorkspaceMetadataVersionException( + `Metadata version not found for workspace ${workspace.id}`, + WorkspaceMetadataVersionExceptionCode.METADATA_VERSION_NOT_FOUND, ); } @@ -60,9 +64,9 @@ export class SearchResolver { ); if (!objectMetadataMaps) { - throw new SearchException( + throw new WorkspaceMetadataCacheException( `Object metadata map not found for workspace ${workspace.id} and metadata version ${currentCacheVersion}`, - SearchExceptionCode.OBJECT_METADATA_MAP_NOT_FOUND, + WorkspaceMetadataCacheExceptionCode.OBJECT_METADATA_MAP_NOT_FOUND, ); } diff --git a/packages/twenty-server/src/engine/core-modules/twenty-config/filters/config-variable-graphql-api-exception.filter.ts b/packages/twenty-server/src/engine/core-modules/twenty-config/filters/config-variable-graphql-api-exception.filter.ts index 636541d570e..1387a30e71d 100644 --- a/packages/twenty-server/src/engine/core-modules/twenty-config/filters/config-variable-graphql-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/core-modules/twenty-config/filters/config-variable-graphql-api-exception.filter.ts @@ -2,7 +2,6 @@ import { Catch, ExceptionFilter } from '@nestjs/common'; import { ForbiddenError, - InternalServerError, NotFoundError, UserInputError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; @@ -25,8 +24,13 @@ export class ConfigVariableGraphqlApiExceptionFilter case ConfigVariableExceptionCode.VALIDATION_FAILED: throw new UserInputError(exception.message); case ConfigVariableExceptionCode.INTERNAL_ERROR: - default: - throw new InternalServerError(exception.message); + case ConfigVariableExceptionCode.UNSUPPORTED_CONFIG_TYPE: + throw exception; + default: { + const _exhaustiveCheck: never = exception.code; + + throw exception; + } } } } diff --git a/packages/twenty-server/src/engine/core-modules/twenty-config/twenty-config.exception.ts b/packages/twenty-server/src/engine/core-modules/twenty-config/twenty-config.exception.ts index 9d7c17c53a1..dd24b7da711 100644 --- a/packages/twenty-server/src/engine/core-modules/twenty-config/twenty-config.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/twenty-config/twenty-config.exception.ts @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class ConfigVariableException extends CustomException { + declare code: ConfigVariableExceptionCode; constructor(message: string, code: ConfigVariableExceptionCode) { super(message, code); } @@ -12,6 +13,5 @@ export enum ConfigVariableExceptionCode { VARIABLE_NOT_FOUND = 'VARIABLE_NOT_FOUND', VALIDATION_FAILED = 'VALIDATION_FAILED', UNSUPPORTED_CONFIG_TYPE = 'UNSUPPORTED_CONFIG_TYPE', - METADATA_NOT_FOUND = 'METADATA_NOT_FOUND', INTERNAL_ERROR = 'INTERNAL_ERROR', } diff --git a/packages/twenty-server/src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter.ts b/packages/twenty-server/src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter.ts index df46ad401c6..508aff9fecc 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/filters/workflow-trigger-graphql-api-exception.filter.ts @@ -1,7 +1,6 @@ import { Catch, ExceptionFilter } from '@nestjs/common'; import { - InternalServerError, NotFoundError, UserInputError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; @@ -25,8 +24,13 @@ export class WorkflowTriggerGraphqlApiExceptionFilter throw new UserInputError(exception.message); case WorkflowTriggerExceptionCode.NOT_FOUND: throw new NotFoundError(exception.message); - default: - throw new InternalServerError(exception.message); + case WorkflowTriggerExceptionCode.INTERNAL_ERROR: + throw exception; + default: { + const _exhaustiveCheck: never = exception.code; + + throw exception; + } } } } diff --git a/packages/twenty-server/src/engine/core-modules/workspace/utils/__tests__/workspace-graphql-api-exception-handler.util.spec.ts b/packages/twenty-server/src/engine/core-modules/workspace/utils/__tests__/workspace-graphql-api-exception-handler.util.spec.ts index f5ae36bf028..26e94f054ea 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/utils/__tests__/workspace-graphql-api-exception-handler.util.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/utils/__tests__/workspace-graphql-api-exception-handler.util.spec.ts @@ -1,6 +1,5 @@ import { ConflictError, - InternalServerError, NotFoundError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { workspaceGraphqlApiExceptionHandler } from 'src/engine/core-modules/workspace/utils/workspace-graphql-api-exception-handler.util'; @@ -8,6 +7,7 @@ import { WorkspaceException, WorkspaceExceptionCode, } from 'src/engine/core-modules/workspace/workspace.exception'; +import { CustomException } from 'src/utils/custom-exception'; describe('workspaceGraphqlApiExceptionHandler', () => { it('should throw NotFoundError when WorkspaceExceptionCode is SUBDOMAIN_NOT_FOUND', () => { @@ -48,7 +48,7 @@ describe('workspaceGraphqlApiExceptionHandler', () => { const error = new WorkspaceException('Unknown error', 'UNKNOWN_CODE'); expect(() => workspaceGraphqlApiExceptionHandler(error)).toThrow( - InternalServerError, + CustomException, ); }); diff --git a/packages/twenty-server/src/engine/core-modules/workspace/utils/workspace-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/core-modules/workspace/utils/workspace-graphql-api-exception-handler.util.ts index 10f349b4042..2f424dfdf1b 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/utils/workspace-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/utils/workspace-graphql-api-exception-handler.util.ts @@ -1,7 +1,6 @@ import { ConflictError, ForbiddenError, - InternalServerError, NotFoundError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { @@ -15,12 +14,17 @@ export const workspaceGraphqlApiExceptionHandler = (error: Error) => { case WorkspaceExceptionCode.SUBDOMAIN_NOT_FOUND: case WorkspaceExceptionCode.WORKSPACE_NOT_FOUND: throw new NotFoundError(error.message); + case WorkspaceExceptionCode.DOMAIN_ALREADY_TAKEN: case WorkspaceExceptionCode.SUBDOMAIN_ALREADY_TAKEN: throw new ConflictError(error.message); case WorkspaceExceptionCode.ENVIRONMENT_VAR_NOT_ENABLED: + case WorkspaceExceptionCode.WORKSPACE_CUSTOM_DOMAIN_DISABLED: throw new ForbiddenError(error.message); - default: - throw new InternalServerError(error.message); + default: { + const _exhaustiveCheck: never = error.code; + + throw error; + } } } diff --git a/packages/twenty-server/src/engine/core-modules/workspace/workspace.exception.ts b/packages/twenty-server/src/engine/core-modules/workspace/workspace.exception.ts index 7e301dd4f19..29f6c421b72 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace/workspace.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace/workspace.exception.ts @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class WorkspaceException extends CustomException { + declare code: WorkspaceExceptionCode; constructor(message: string, code: WorkspaceExceptionCode) { super(message, code); } diff --git a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts index f0ac60c8c69..a544b183b7f 100644 --- a/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/field-metadata/field-metadata.exception.ts @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class FieldMetadataException extends CustomException { + declare code: FieldMetadataExceptionCode; constructor(message: string, code: FieldMetadataExceptionCode) { super(message, code); } 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 5f34cc4077f..0402ac7df12 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 @@ -46,7 +46,7 @@ import { RelationMetadataEntity, RelationMetadataType, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.entity'; -import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception'; +import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; import { exceedsDatabaseIdentifierMaximumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils'; import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils'; import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; @@ -614,7 +614,7 @@ export class FieldMetadataService extends TypeOrmQueryService { case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_NOT_ENABLED: case FieldMetadataExceptionCode.FIELD_METADATA_RELATION_MALFORMED: case FieldMetadataExceptionCode.LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND: - default: - throw new InternalServerError(error.message); + throw error; + default: { + const _exhaustiveCheck: never = error.code; + + throw error; + } } } 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 index 7bef5777362..4a843ac23b9 100644 --- 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 @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class ObjectMetadataException extends CustomException { + declare code: ObjectMetadataExceptionCode; constructor(message: string, code: ObjectMetadataExceptionCode) { super(message, code); } 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 index c4eca8e8cfb..f130b1ee05f 100644 --- 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 @@ -1,7 +1,6 @@ import { ConflictError, ForbiddenError, - InternalServerError, NotFoundError, UserInputError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; @@ -26,8 +25,13 @@ export const objectMetadataGraphqlApiExceptionHandler = (error: Error) => { throw new ForbiddenError(error.message); case ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS: throw new ConflictError(error.message); - default: - throw new InternalServerError(error.message); + case ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD: + throw error; + default: { + const _exhaustiveCheck: never = error.code; + + 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 eba464d060a..6cda29ee160 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 @@ -6,7 +6,7 @@ import { ObjectMetadataException, ObjectMetadataExceptionCode, } from 'src/engine/metadata-modules/object-metadata/object-metadata.exception'; -import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception'; +import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; import { validateMetadataNameIsNotTooLongOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-long.utils'; import { validateMetadataNameIsNotTooShortOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-short.utils'; import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; @@ -28,7 +28,7 @@ export const validateObjectMetadataInputNameOrThrow = (name: string): void => { try { validateMetadataNameOrThrow(name); } catch (error) { - if (error instanceof InvalidMetadataNameException) { + if (error instanceof InvalidMetadataException) { throw new ObjectMetadataException( error.message, ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, @@ -58,7 +58,7 @@ const validateObjectMetadataInputLabelOrThrow = (name: string): void => { try { validators.forEach((validator) => validator(name.trim())); } catch (error) { - if (error instanceof InvalidMetadataNameException) { + if (error instanceof InvalidMetadataException) { throw new ObjectMetadataException( error.message, ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT, diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.exception.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.exception.ts index 3b84e765ad0..10fc01352d2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/permissions.exception.ts @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class PermissionsException extends CustomException { + declare code: PermissionsExceptionCode; constructor(message: string, code: PermissionsExceptionCode) { super(message, code); } diff --git a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts index a04436025db..ae8c00ed4e9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/permissions/utils/permission-graphql-api-exception-handler.util.ts @@ -1,6 +1,5 @@ import { ForbiddenError, - InternalServerError, NotFoundError, UserInputError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; @@ -29,7 +28,20 @@ export const permissionGraphqlApiExceptionHandler = ( case PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND: throw new NotFoundError(error.message); case PermissionsExceptionCode.DEFAULT_ROLE_NOT_FOUND: - default: - throw new InternalServerError(error.message); + case PermissionsExceptionCode.WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH: + case PermissionsExceptionCode.TOO_MANY_ADMIN_CANDIDATES: + case PermissionsExceptionCode.USER_WORKSPACE_ALREADY_HAS_ROLE: + case PermissionsExceptionCode.ADMIN_ROLE_NOT_FOUND: + case PermissionsExceptionCode.DEFAULT_ROLE_CANNOT_BE_DELETED: + case PermissionsExceptionCode.WORKSPACE_MEMBER_NOT_FOUND: + case PermissionsExceptionCode.UNKNOWN_OPERATION_NAME: + case PermissionsExceptionCode.UNKNOWN_REQUIRED_PERMISSION: + case PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE: + throw error; + default: { + const _exhaustiveCheck: never = error.code; + + 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 index 69ae12902aa..abe61f3f7cb 100644 --- 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 @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class RelationMetadataException extends CustomException { + declare code: RelationMetadataExceptionCode; constructor(message: string, code: RelationMetadataExceptionCode) { super(message, code); } 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 26bb87d3d91..49e05d44ed5 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 @@ -22,9 +22,17 @@ import { RelationMetadataException, RelationMetadataExceptionCode, } from 'src/engine/metadata-modules/relation-metadata/relation-metadata.exception'; -import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception'; +import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; import { validateFieldNameAvailabilityOrThrow } from 'src/engine/metadata-modules/utils/validate-field-name-availability.utils'; import { validateMetadataNameOrThrow } from 'src/engine/metadata-modules/utils/validate-metadata-name.utils'; +import { + WorkspaceMetadataCacheException, + WorkspaceMetadataCacheExceptionCode, +} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception'; +import { + WorkspaceMetadataVersionException, + WorkspaceMetadataVersionExceptionCode, +} from 'src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception'; import { WorkspaceMetadataVersionService } from 'src/engine/metadata-modules/workspace-metadata-version/services/workspace-metadata-version.service'; import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util'; import { @@ -73,7 +81,7 @@ 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); + case RelationMetadataExceptionCode.RELATION_METADATA_NOT_FOUND: + throw error; + default: { + const _exhaustiveCheck: never = error.code; + + 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 index 7e9e41b9a59..ab95b6e4dfa 100644 --- 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 @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class RemoteServerException extends CustomException { + declare code: RemoteServerExceptionCode; constructor(message: string, code: RemoteServerExceptionCode) { super(message, code); } 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 index e1bb768c9b0..37fd452c838 100644 --- 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 @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class RemoteTableException extends CustomException { + declare code: RemoteTableExceptionCode; constructor(message: string, code: RemoteTableExceptionCode) { super(message, code); } 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 index 13e2c976a62..1ea1f6fc844 100644 --- 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 @@ -1,6 +1,5 @@ import { ConflictError, - InternalServerError, NotFoundError, UserInputError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; @@ -21,8 +20,11 @@ export const remoteTableGraphqlApiExceptionHandler = (error: Error) => { throw new UserInputError(error.message); case RemoteTableExceptionCode.REMOTE_TABLE_ALREADY_EXISTS: throw new ConflictError(error.message); - default: - throw new InternalServerError(error.message); + default: { + const _exhaustiveCheck: never = error.code; + + throw error; + } } } 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 index e289d02af49..7cb6e331d55 100644 --- 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 @@ -1,7 +1,6 @@ import { ConflictError, ForbiddenError, - InternalServerError, NotFoundError, UserInputError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; @@ -21,8 +20,13 @@ export const remoteServerGraphqlApiExceptionHandler = (error: any) => { throw new ForbiddenError(error.message); case RemoteServerExceptionCode.REMOTE_SERVER_ALREADY_EXISTS: throw new ConflictError(error.message); - default: - throw new InternalServerError(error.message); + case RemoteServerExceptionCode.REMOTE_SERVER_CONNECTION_ERROR: + throw error; + default: { + const _exhaustiveCheck: never = error.code; + + throw error; + } } } diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.exception.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.exception.ts index 0a0137dd5b3..191fa2c9238 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/serverless-function.exception.ts @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class ServerlessFunctionException extends CustomException { + declare code: ServerlessFunctionExceptionCode; constructor(message: string, code: ServerlessFunctionExceptionCode) { super(message, code); } diff --git a/packages/twenty-server/src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils.ts b/packages/twenty-server/src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils.ts index 5518e7dac66..68ccc4dadcc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/serverless-function/utils/serverless-function-graphql-api-exception-handler.utils.ts @@ -1,7 +1,6 @@ import { ConflictError, ForbiddenError, - InternalServerError, NotFoundError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { @@ -20,9 +19,15 @@ export const serverlessFunctionGraphQLApiExceptionHandler = (error: any) => { case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_READY: case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_BUILDING: case ServerlessFunctionExceptionCode.FEATURE_FLAG_INVALID: + case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_LIMIT_REACHED: throw new ForbiddenError(error.message); - default: - throw new InternalServerError(error.message); + case ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_CODE_UNCHANGED: + throw error; + default: { + const _exhaustiveCheck: never = error.code; + + throw error; + } } } throw error; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label.util.ts index 9b5f8133dbf..c301092a9e6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/compute-metadata-name-from-label.util.ts @@ -2,11 +2,17 @@ import camelCase from 'lodash.camelcase'; import { slugify } from 'transliteration'; import { isDefined } from 'twenty-shared/utils'; -import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; +import { + InvalidMetadataException, + InvalidMetadataExceptionCode, +} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; export const computeMetadataNameFromLabel = (label: string): string => { if (!isDefined(label)) { - throw new InvalidMetadataException('Label is required'); + throw new InvalidMetadataException( + 'Label is required', + InvalidMetadataExceptionCode.LABEL_REQUIRED, + ); } const prefixedLabel = /^\d/.test(label) ? `n${label}` : label; @@ -22,7 +28,10 @@ export const computeMetadataNameFromLabel = (label: string): string => { }); if (formattedString === '') { - throw new InvalidMetadataException(`Invalid label: "${label}"`); + throw new InvalidMetadataException( + `Invalid label: "${label}"`, + InvalidMetadataExceptionCode.INVALID_LABEL, + ); } return camelCase(formattedString); diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception.ts b/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception.ts deleted file mode 100644 index dd985672d18..00000000000 --- a/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception.ts +++ /dev/null @@ -1,5 +0,0 @@ -export class InvalidMetadataNameException extends Error { - constructor(message: string) { - super(message); - } -} diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception.ts b/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception.ts index 8f3e596442f..49b451eedbb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception.ts @@ -1,5 +1,19 @@ -export class InvalidMetadataException extends Error { - constructor(message: string) { - super(message); +import { CustomException } from 'src/utils/custom-exception'; + +export class InvalidMetadataException extends CustomException { + constructor(message: string, code: InvalidMetadataExceptionCode) { + super(message, code); } } + +export enum InvalidMetadataExceptionCode { + LABEL_REQUIRED = 'Label required', + INPUT_TOO_SHORT = 'Input too short', + EXCEEDS_MAX_LENGTH = 'Exceeds max length', + RESERVED_KEYWORD = 'Reserved keyword', + NOT_CAMEL_CASE = 'Not camel case', + INVALID_LABEL = 'Invalid label', + NAME_NOT_SYNCED_WITH_LABEL = 'Name not synced with label', + INVALID_STRING = 'Invalid string', + NOT_AVAILABLE = 'Name not available', +} diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts index adbd0948fc0..c728eef3be5 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-field-name-availability.utils.ts @@ -2,7 +2,10 @@ import { compositeTypeDefinitions } from 'src/engine/metadata-modules/field-meta import { computeCompositeColumnName } from 'src/engine/metadata-modules/field-metadata/utils/compute-column-name.util'; import { isCompositeFieldMetadataType } from 'src/engine/metadata-modules/field-metadata/utils/is-composite-field-metadata-type.util'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; -import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception'; +import { + InvalidMetadataException, + InvalidMetadataExceptionCode, +} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; const getReservedCompositeFieldNames = ( objectMetadata: ObjectMetadataEntity, @@ -33,10 +36,16 @@ export const validateFieldNameAvailabilityOrThrow = ( getReservedCompositeFieldNames(objectMetadata); if (objectMetadata.fields.some((field) => field.name === name)) { - throw new InvalidMetadataNameException(`Name "${name}" is not available`); + throw new InvalidMetadataException( + `Name "${name}" is not available`, + InvalidMetadataExceptionCode.NOT_AVAILABLE, + ); } if (reservedCompositeFieldsNames.includes(name)) { - throw new InvalidMetadataNameException(`Name "${name}" is not available`); + throw new InvalidMetadataException( + `Name "${name}" is not available`, + InvalidMetadataExceptionCode.RESERVED_KEYWORD, + ); } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-camel-case.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-camel-case.utils.ts index ef89e0d76af..bdfb99b43c9 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-camel-case.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-camel-case.utils.ts @@ -1,11 +1,15 @@ import camelCase from 'lodash.camelcase'; -import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception'; +import { + InvalidMetadataException, + InvalidMetadataExceptionCode, +} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; export const validateMetadataNameIsCamelCaseOrThrow = (name: string) => { if (name !== camelCase(name)) { - throw new InvalidMetadataNameException( + throw new InvalidMetadataException( `Name should be in camelCase: ${name}`, + InvalidMetadataExceptionCode.NOT_CAMEL_CASE, ); } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-reserved-keyword.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-reserved-keyword.ts index c8073ae7ebd..1d0da0a19cf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-reserved-keyword.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-reserved-keyword.ts @@ -1,4 +1,7 @@ -import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception'; +import { + InvalidMetadataException, + InvalidMetadataExceptionCode, +} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; const coreObjectNames = [ 'approvedAccessDomain', @@ -64,8 +67,9 @@ export const validateMetadataNameIsNotReservedKeywordOrThrow = ( name: string, ) => { if (reservedKeywords.includes(name)) { - throw new InvalidMetadataNameException( + throw new InvalidMetadataException( `The name "${name}" is not available`, + InvalidMetadataExceptionCode.RESERVED_KEYWORD, ); } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-long.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-long.utils.ts index ae4cd293df1..afd9a20e77e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-long.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-long.utils.ts @@ -1,10 +1,14 @@ -import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception'; +import { + InvalidMetadataException, + InvalidMetadataExceptionCode, +} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; import { exceedsDatabaseIdentifierMaximumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils'; export const validateMetadataNameIsNotTooLongOrThrow = (name: string) => { if (exceedsDatabaseIdentifierMaximumLength(name)) { - throw new InvalidMetadataNameException( + throw new InvalidMetadataException( `String "${name}" exceeds 63 characters limit`, + InvalidMetadataExceptionCode.EXCEEDS_MAX_LENGTH, ); } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-short.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-short.utils.ts index 52077b57716..db59fe06377 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-short.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-is-not-too-short.utils.ts @@ -1,8 +1,14 @@ -import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception'; +import { + InvalidMetadataException, + InvalidMetadataExceptionCode, +} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; import { beneathDatabaseIdentifierMinimumLength } from 'src/engine/metadata-modules/utils/validate-database-identifier-length.utils'; export const validateMetadataNameIsNotTooShortOrThrow = (name: string) => { if (beneathDatabaseIdentifierMinimumLength(name)) { - throw new InvalidMetadataNameException(`Input is too short: "${name}"`); + throw new InvalidMetadataException( + `Input is too short: "${name}"`, + InvalidMetadataExceptionCode.INPUT_TOO_SHORT, + ); } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-start-with-lowercase-letter-and-contain-digits-nor-letters.utils.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-start-with-lowercase-letter-and-contain-digits-nor-letters.utils.ts index fd80f678e28..cc6d93eab41 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-start-with-lowercase-letter-and-contain-digits-nor-letters.utils.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-metadata-name-start-with-lowercase-letter-and-contain-digits-nor-letters.utils.ts @@ -1,4 +1,7 @@ -import { InvalidMetadataNameException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata-name.exception'; +import { + InvalidMetadataException, + InvalidMetadataExceptionCode, +} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; const STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX = /^[a-z][a-zA-Z0-9]*$/; @@ -10,8 +13,9 @@ export const validateMetadataNameStartWithLowercaseLetterAndContainDigitsNorLett STARTS_WITH_LOWER_CASE_AND_CONTAINS_ONLY_CAPS_AND_LOWER_LETTERS_AND_NUMBER_STRING_REGEX, ) ) { - throw new InvalidMetadataNameException( + throw new InvalidMetadataException( `String "${name}" is not valid: must start with lowercase letter and contain only alphanumeric letters`, + InvalidMetadataExceptionCode.INVALID_STRING, ); } }; diff --git a/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util.ts b/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util.ts index d619b4b6d9c..6f7968d89e8 100644 --- a/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/utils/validate-name-and-label-are-sync-or-throw.util.ts @@ -2,7 +2,10 @@ import camelCase from 'lodash.camelcase'; import { slugify } from 'transliteration'; import { isDefined } from 'twenty-shared/utils'; -import { InvalidMetadataException } from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; +import { + InvalidMetadataException, + InvalidMetadataExceptionCode, +} from 'src/engine/metadata-modules/utils/exceptions/invalid-metadata.exception'; export const validateNameAndLabelAreSyncOrThrow = ( label: string, @@ -13,13 +16,17 @@ export const validateNameAndLabelAreSyncOrThrow = ( if (name !== computedName) { throw new InvalidMetadataException( `Name is not synced with label. Expected name: "${computedName}", got ${name}`, + InvalidMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL, ); } }; export const computeMetadataNameFromLabel = (label: string): string => { if (!isDefined(label)) { - throw new InvalidMetadataException('Label is required'); + throw new InvalidMetadataException( + 'Label is required', + InvalidMetadataExceptionCode.LABEL_REQUIRED, + ); } const prefixedLabel = /^\d/.test(label) ? `n${label}` : label; @@ -35,7 +42,10 @@ export const computeMetadataNameFromLabel = (label: string): string => { }); if (formattedString === '') { - throw new InvalidMetadataException(`Invalid label: "${label}"`); + throw new InvalidMetadataException( + `Invalid label: "${label}"`, + InvalidMetadataExceptionCode.INVALID_LABEL, + ); } return camelCase(formattedString); diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception.ts index b8b34458347..43445bc27ce 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception.ts @@ -7,5 +7,5 @@ export class WorkspaceMetadataCacheException extends CustomException { } export enum WorkspaceMetadataCacheExceptionCode { - METADATA_VERSION_NOT_FOUND = 'METADATA_VERSION_NOT_FOUND', + OBJECT_METADATA_MAP_NOT_FOUND = 'Object Metadata map not found', } diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts index acc620c7b8c..ff373aa5b0d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service.ts @@ -1,17 +1,17 @@ import { Injectable, Logger } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { Repository } from 'typeorm'; import { isDefined } from 'twenty-shared/utils'; +import { Repository } from 'typeorm'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; import { generateObjectMetadataMaps } from 'src/engine/metadata-modules/utils/generate-object-metadata-maps.util'; import { - WorkspaceMetadataCacheException, - WorkspaceMetadataCacheExceptionCode, -} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception'; + WorkspaceMetadataVersionException, + WorkspaceMetadataVersionExceptionCode, +} from 'src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception'; import { WorkspaceCacheStorageService } from 'src/engine/workspace-cache-storage/workspace-cache-storage.service'; @Injectable() @@ -46,9 +46,9 @@ export class WorkspaceMetadataCacheService { await this.getMetadataVersionFromDatabase(workspaceId); if (!isDefined(currentDatabaseVersion)) { - throw new WorkspaceMetadataCacheException( + throw new WorkspaceMetadataVersionException( 'Metadata version not found in the database', - WorkspaceMetadataCacheExceptionCode.METADATA_VERSION_NOT_FOUND, + WorkspaceMetadataVersionExceptionCode.METADATA_VERSION_NOT_FOUND, ); } diff --git a/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts b/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts index fb639b4339e..9a75a5f5b0c 100644 --- a/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts +++ b/packages/twenty-server/src/engine/twenty-orm/factories/workspace-datasource.factory.ts @@ -11,6 +11,10 @@ import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twent import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { WorkspaceFeatureFlagsMapCacheService } from 'src/engine/metadata-modules/workspace-feature-flags-map-cache/workspace-feature-flags-map-cache.service'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; +import { + WorkspaceMetadataVersionException, + WorkspaceMetadataVersionExceptionCode, +} from 'src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception'; import { WorkspacePermissionsCacheStorageService } from 'src/engine/metadata-modules/workspace-permissions-cache/workspace-permissions-cache-storage.service'; import { ROLES_PERMISSIONS, @@ -312,9 +316,9 @@ export class WorkspaceDatasourceFactory { if (!isDefined(latestWorkspaceMetadataVersion)) { if (shouldFailIfMetadataNotFound) { - throw new TwentyORMException( + throw new WorkspaceMetadataVersionException( `Metadata version not found for workspace ${workspaceId}`, - TwentyORMExceptionCode.METADATA_VERSION_NOT_FOUND, + WorkspaceMetadataVersionExceptionCode.METADATA_VERSION_NOT_FOUND, ); } else { await this.workspaceMetadataCacheService.recomputeMetadataCache({ @@ -330,7 +334,7 @@ export class WorkspaceDatasourceFactory { if (!isDefined(latestWorkspaceMetadataVersion)) { throw new TwentyORMException( - `Metadata version not found after recompute for workspace ${workspaceId}`, + `Metadata version not found after recompute`, TwentyORMExceptionCode.METADATA_VERSION_NOT_FOUND, ); } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts index 5baa320389f..7a5f915c80b 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts @@ -148,16 +148,21 @@ export class CalendarEventImportErrorHandlerService { workspaceId, ); - this.exceptionHandlerService.captureExceptions([exception], { - workspace: { - id: workspaceId, - }, - }); - - throw new CalendarEventImportException( + const calendarEventImportException = new CalendarEventImportException( `Unknown error importing calendar events for calendar channel ${calendarChannel.id} in workspace ${workspaceId}: ${exception.message}`, CalendarEventImportExceptionCode.UNKNOWN, ); + + this.exceptionHandlerService.captureExceptions( + [calendarEventImportException], + { + workspace: { + id: workspaceId, + }, + }, + ); + + throw calendarEventImportException; } private async handleNotFoundException( diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts index 497896808d7..0870da04f44 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts @@ -162,7 +162,10 @@ export class MessageImportExceptionHandlerService { ); this.exceptionHandlerService.captureExceptions([ - `Unknown error importing messages for message channel ${messageChannel.id} in workspace ${workspaceId}: ${exception.message}`, + new MessageImportException( + `Unknown error importing messages for message channel ${messageChannel.id} in workspace ${workspaceId}: ${exception.message}`, + MessageImportExceptionCode.UNKNOWN, + ), ]); throw new MessageImportException( diff --git a/packages/twenty-server/src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception.ts b/packages/twenty-server/src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception.ts index 0d9f2dc21ab..549d25b75ea 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-trigger/exceptions/workflow-trigger.exception.ts @@ -1,6 +1,7 @@ import { CustomException } from 'src/utils/custom-exception'; export class WorkflowTriggerException extends CustomException { + declare code: WorkflowTriggerExceptionCode; constructor(message: string, code: WorkflowTriggerExceptionCode) { super(message, code); }