diff --git a/packages/twenty-server/src/command/command.ts b/packages/twenty-server/src/command/command.ts index 5e45fde2a..3a156cc97 100644 --- a/packages/twenty-server/src/command/command.ts +++ b/packages/twenty-server/src/command/command.ts @@ -2,7 +2,7 @@ import { CommandFactory } from 'nest-commander'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { LoggerService } from 'src/engine/core-modules/logger/logger.service'; -import { shouldFilterException } from 'src/engine/utils/global-exception-handler.util'; +import { shouldCaptureException } from 'src/engine/utils/global-exception-handler.util'; import { CommandModule } from './command.module'; @@ -10,11 +10,9 @@ async function bootstrap() { const errorHandler = (err: Error) => { loggerService.error(err?.message, err?.name); - if (shouldFilterException(err)) { - return; + if (shouldCaptureException(err)) { + exceptionHandlerService.captureExceptions([err]); } - - exceptionHandlerService.captureExceptions([err]); }; const app = await CommandFactory.createWithoutRunning(CommandModule, { 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 ab5869b52..7075173d9 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 @@ -22,6 +22,5 @@ export enum GraphqlQueryRunnerExceptionCode { RELATION_SETTINGS_NOT_FOUND = 'RELATION_SETTINGS_NOT_FOUND', RELATION_TARGET_OBJECT_METADATA_NOT_FOUND = 'RELATION_TARGET_OBJECT_METADATA_NOT_FOUND', NOT_IMPLEMENTED = 'NOT_IMPLEMENTED', - OBJECT_METADATA_COLLECTION_NOT_FOUND = 'OBJECT_METADATA_COLLECTION_NOT_FOUND', INVALID_POST_HOOK_PAYLOAD = 'INVALID_POST_HOOK_PAYLOAD', } 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 c861941b2..40ba98d20 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 @@ -27,7 +27,6 @@ export const graphqlQueryRunnerExceptionHandler = ( throw new NotFoundError(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: { 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 30054bdf8..4cd778086 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 @@ -5,10 +5,6 @@ import { GraphQLSchema, printSchema } from 'graphql'; import { gql } from 'graphql-tag'; import { isDefined } from 'twenty-shared/utils'; -import { - GraphqlQueryRunnerException, - GraphqlQueryRunnerExceptionCode, -} from 'src/engine/api/graphql/graphql-query-runner/errors/graphql-query-runner.exception'; import { ScalarsExplorerService } from 'src/engine/api/graphql/services/scalars-explorer.service'; import { workspaceResolverBuilderMethodNames } from 'src/engine/api/graphql/workspace-resolver-builder/factories/factories'; import { WorkspaceResolverFactory } from 'src/engine/api/graphql/workspace-resolver-builder/workspace-resolver.factory'; @@ -18,6 +14,10 @@ import { FeatureFlagService } from 'src/engine/core-modules/feature-flag/service import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; import { DataSourceService } from 'src/engine/metadata-modules/data-source/data-source.service'; import { ObjectMetadataMaps } from 'src/engine/metadata-modules/types/object-metadata-maps'; +import { + WorkspaceMetadataCacheException, + WorkspaceMetadataCacheExceptionCode, +} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; import { WorkspaceMetadataVersionException, @@ -86,9 +86,9 @@ export class WorkspaceSchemaFactory { } if (!objectMetadataMaps) { - throw new GraphqlQueryRunnerException( + throw new WorkspaceMetadataCacheException( 'Object metadata collection not found', - GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_COLLECTION_NOT_FOUND, + WorkspaceMetadataCacheExceptionCode.OBJECT_METADATA_COLLECTION_NOT_FOUND, ); } 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 c3af4bb73..b4af06383 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 @@ -20,18 +20,6 @@ export class AuthGraphqlApiExceptionFilter implements ExceptionFilter { case AuthExceptionCode.INVALID_INPUT: throw new UserInputError(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: - case AuthExceptionCode.USER_WORKSPACE_NOT_FOUND: case AuthExceptionCode.INSUFFICIENT_SCOPES: case AuthExceptionCode.OAUTH_ACCESS_DENIED: case AuthExceptionCode.SSO_AUTH_FAILED: @@ -40,6 +28,18 @@ export class AuthGraphqlApiExceptionFilter implements ExceptionFilter { case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED: case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED: case AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE: + throw new ForbiddenError(exception.message); + case AuthExceptionCode.EMAIL_NOT_VERIFIED: + case AuthExceptionCode.INVALID_DATA: + 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.INTERNAL_SERVER_ERROR: + case AuthExceptionCode.USER_WORKSPACE_NOT_FOUND: throw exception; default: { const _exhaustiveCheck: never = exception.code; diff --git a/packages/twenty-server/src/engine/core-modules/auth/utils/get-auth-exception-rest-status.util.ts b/packages/twenty-server/src/engine/core-modules/auth/utils/get-auth-exception-rest-status.util.ts new file mode 100644 index 000000000..f04824277 --- /dev/null +++ b/packages/twenty-server/src/engine/core-modules/auth/utils/get-auth-exception-rest-status.util.ts @@ -0,0 +1,37 @@ +import { + AuthException, + AuthExceptionCode, +} from 'src/engine/core-modules/auth/auth.exception'; + +export const getAuthExceptionRestStatus = (exception: AuthException) => { + switch (exception.code) { + case AuthExceptionCode.CLIENT_NOT_FOUND: + return 404; + case AuthExceptionCode.INVALID_INPUT: + return 400; + case AuthExceptionCode.FORBIDDEN_EXCEPTION: + 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: + case AuthExceptionCode.EMAIL_NOT_VERIFIED: + return 403; + case AuthExceptionCode.INVALID_DATA: + case AuthExceptionCode.UNAUTHENTICATED: + case AuthExceptionCode.USER_NOT_FOUND: + case AuthExceptionCode.WORKSPACE_NOT_FOUND: + return 401; + case AuthExceptionCode.INTERNAL_SERVER_ERROR: + case AuthExceptionCode.USER_WORKSPACE_NOT_FOUND: + return 500; + default: { + const _exhaustiveCheck: never = exception.code; + + return 500; + } + } +}; diff --git a/packages/twenty-server/src/engine/core-modules/domain-manager/controllers/cloudflare.controller.ts b/packages/twenty-server/src/engine/core-modules/domain-manager/controllers/cloudflare.controller.ts index 7d461455b..d0b97ece8 100644 --- a/packages/twenty-server/src/engine/core-modules/domain-manager/controllers/cloudflare.controller.ts +++ b/packages/twenty-server/src/engine/core-modules/domain-manager/controllers/cloudflare.controller.ts @@ -24,8 +24,8 @@ import { CloudflareSecretMatchGuard } from 'src/engine/core-modules/domain-manag import { CustomDomainService } from 'src/engine/core-modules/domain-manager/services/custom-domain.service'; import { DomainManagerService } from 'src/engine/core-modules/domain-manager/services/domain-manager.service'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; -import { handleException } from 'src/engine/core-modules/exception-handler/http-exception-handler.service'; import { Workspace } from 'src/engine/core-modules/workspace/workspace.entity'; +import { handleException } from 'src/engine/utils/global-exception-handler.util'; @Controller() @UseFilters(AuthRestApiExceptionFilter) @@ -43,13 +43,13 @@ export class CloudflareController { @UseGuards(CloudflareSecretMatchGuard) async customHostnameWebhooks(@Req() req: Request, @Res() res: Response) { if (!req.body?.data?.data?.hostname) { - handleException( - new DomainManagerException( + handleException({ + exception: new DomainManagerException( 'Hostname missing', DomainManagerExceptionCode.INVALID_INPUT_DATA, ), - this.exceptionHandlerService, - ); + exceptionHandlerService: this.exceptionHandlerService, + }); return res.status(200).send(); } 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 d07b12c8f..ddb1875e0 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 @@ -18,6 +18,7 @@ export class ExceptionHandlerSentryDriver Sentry.withScope((scope) => { if (options?.operation) { scope.setExtra('operation', options.operation.name); + scope.setExtra('operationType', options.operation.type); } if (options?.document) { @@ -57,6 +58,13 @@ export class ExceptionHandlerSentryDriver if (exception instanceof CustomException) { scope.setTag('customExceptionCode', exception.code); scope.setFingerprint([exception.code]); + exception.name = exception.code + .split('_') + .map( + (word) => + word.charAt(0)?.toUpperCase() + word.slice(1)?.toLowerCase(), + ) + .join(' '); } const eventId = Sentry.captureException(exception, { diff --git a/packages/twenty-server/src/engine/core-modules/exception-handler/http-exception-handler.service.ts b/packages/twenty-server/src/engine/core-modules/exception-handler/http-exception-handler.service.ts index 2f57f7a33..0ee479337 100644 --- a/packages/twenty-server/src/engine/core-modules/exception-handler/http-exception-handler.service.ts +++ b/packages/twenty-server/src/engine/core-modules/exception-handler/http-exception-handler.service.ts @@ -7,19 +7,9 @@ import { ExceptionHandlerUser } from 'src/engine/core-modules/exception-handler/ import { ExceptionHandlerWorkspace } from 'src/engine/core-modules/exception-handler/interfaces/exception-handler-workspace.interface'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; +import { handleException } from 'src/engine/utils/global-exception-handler.util'; import { CustomException } from 'src/utils/custom-exception'; -export const handleException = ( - exception: CustomException, - exceptionHandlerService: ExceptionHandlerService, - user?: ExceptionHandlerUser, - workspace?: ExceptionHandlerWorkspace, -): CustomException => { - exceptionHandlerService.captureExceptions([exception], { user, workspace }); - - return exception; -}; - interface RequestAndParams { request: Request | null; // eslint-disable-next-line @typescript-eslint/no-explicit-any @@ -51,9 +41,16 @@ export class HttpExceptionHandlerService { workspace = { ...workspace, id: params.workspaceId }; if (params?.userId) user = { ...user, id: params.userId }; - handleException(exception, this.exceptionHandlerService, user, workspace); const statusCode = errorCode || 500; + handleException({ + exception, + exceptionHandlerService: this.exceptionHandlerService, + user, + workspace, + statusCode, + }); + return response.status(statusCode).send({ statusCode, error: exception.name || 'Bad Request', diff --git a/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook.ts b/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook.ts index b87d08d48..3f181f418 100644 --- a/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook.ts +++ b/packages/twenty-server/src/engine/core-modules/graphql/hooks/use-graphql-error-handler.hook.ts @@ -11,7 +11,7 @@ import { GraphQLContext } from 'src/engine/api/graphql/graphql-config/interfaces import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { generateGraphQLErrorFromError } from 'src/engine/core-modules/graphql/utils/generate-graphql-error-from-error.util'; import { BaseGraphQLError } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; -import { shouldCaptureException } from 'src/engine/core-modules/graphql/utils/should-capture-exception.util'; +import { shouldCaptureException } from 'src/engine/utils/global-exception-handler.util'; const DEFAULT_EVENT_ID_KEY = 'exceptionEventId'; const SCHEMA_VERSION_HEADER = 'x-schema-version'; 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 deleted file mode 100644 index 2af14c8e7..000000000 --- a/packages/twenty-server/src/engine/core-modules/graphql/utils/should-capture-exception.util.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { HttpException } from '@nestjs/common'; - -import { - BaseGraphQLError, - ErrorCode, -} from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; - -export const graphQLErrorCodesToFilterOut = [ - ErrorCode.GRAPHQL_VALIDATION_FAILED, - ErrorCode.UNAUTHENTICATED, - ErrorCode.FORBIDDEN, - ErrorCode.NOT_FOUND, - ErrorCode.METHOD_NOT_ALLOWED, - ErrorCode.TIMEOUT, - ErrorCode.CONFLICT, - ErrorCode.BAD_USER_INPUT, -]; - -export const shouldCaptureException = (exception: Error): boolean => { - if ( - exception instanceof BaseGraphQLError && - graphQLErrorCodesToFilterOut.includes(exception?.extensions?.code) - ) { - 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/message-queue/message-queue.explorer.ts b/packages/twenty-server/src/engine/core-modules/message-queue/message-queue.explorer.ts index f90732297..82581e9d0 100644 --- a/packages/twenty-server/src/engine/core-modules/message-queue/message-queue.explorer.ts +++ b/packages/twenty-server/src/engine/core-modules/message-queue/message-queue.explorer.ts @@ -5,21 +5,21 @@ import { ModuleRef, createContextId, } from '@nestjs/core'; -import { Module } from '@nestjs/core/injector/module'; import { Injector } from '@nestjs/core/injector/injector'; import { InstanceWrapper } from '@nestjs/core/injector/instance-wrapper'; +import { Module } from '@nestjs/core/injector/module'; -import { MessageQueueWorkerOptions } from 'src/engine/core-modules/message-queue/interfaces/message-queue-worker-options.interface'; import { MessageQueueJob, MessageQueueJobData, } from 'src/engine/core-modules/message-queue/interfaces/message-queue-job.interface'; +import { MessageQueueWorkerOptions } from 'src/engine/core-modules/message-queue/interfaces/message-queue-worker-options.interface'; +import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { MessageQueueMetadataAccessor } from 'src/engine/core-modules/message-queue/message-queue-metadata.accessor'; import { MessageQueueService } from 'src/engine/core-modules/message-queue/services/message-queue.service'; import { getQueueToken } from 'src/engine/core-modules/message-queue/utils/get-queue-token.util'; -import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; -import { shouldFilterException } from 'src/engine/utils/global-exception-handler.util'; +import { shouldCaptureException } from 'src/engine/utils/global-exception-handler.util'; interface ProcessorGroup { instance: object; @@ -207,7 +207,7 @@ export class MessageQueueExplorer implements OnModuleInit { // @ts-expect-error legacy noImplicitAny await instance[processMethodName].call(instance, job.data); } catch (err) { - if (!shouldFilterException(err)) { + if (shouldCaptureException(err)) { this.exceptionHandlerService.captureExceptions([err]); } throw err; 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 43445bc27..323708b8b 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 @@ -8,4 +8,5 @@ export class WorkspaceMetadataCacheException extends CustomException { export enum WorkspaceMetadataCacheExceptionCode { OBJECT_METADATA_MAP_NOT_FOUND = 'Object Metadata map not found', + OBJECT_METADATA_COLLECTION_NOT_FOUND = 'Object Metadata collection not found', } diff --git a/packages/twenty-server/src/engine/middlewares/middleware.service.ts b/packages/twenty-server/src/engine/middlewares/middleware.service.ts index d2d4d1ef6..35961a645 100644 --- a/packages/twenty-server/src/engine/middlewares/middleware.service.ts +++ b/packages/twenty-server/src/engine/middlewares/middleware.service.ts @@ -3,9 +3,10 @@ import { Injectable } from '@nestjs/common'; import { Request, Response } from 'express'; import { isDefined } from 'twenty-shared/utils'; -import { AuthExceptionCode } from 'src/engine/core-modules/auth/auth.exception'; +import { AuthException } from 'src/engine/core-modules/auth/auth.exception'; import { AccessTokenService } from 'src/engine/core-modules/auth/token/services/access-token.service'; import { AuthContext } from 'src/engine/core-modules/auth/types/auth-context.type'; +import { getAuthExceptionRestStatus } from 'src/engine/core-modules/auth/utils/get-auth-exception-rest-status.util'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { ErrorCode } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; import { JwtWrapperService } from 'src/engine/core-modules/jwt/services/jwt-wrapper.service'; @@ -53,11 +54,15 @@ export class MiddlewareService { // eslint-disable-next-line @typescript-eslint/no-explicit-any public writeRestResponseOnExceptionCaught(res: Response, error: any) { - // capture and handle custom exceptions - handleException(error as CustomException, this.exceptionHandlerService); - const statusCode = this.getStatus(error); + // capture and handle custom exceptions + handleException({ + exception: error as CustomException, + exceptionHandlerService: this.exceptionHandlerService, + statusCode, + }); + res.writeHead(statusCode, { 'Content-Type': 'application/json' }); res.write( JSON.stringify({ @@ -158,13 +163,8 @@ export class MiddlewareService { return error.status; } - if (error instanceof CustomException) { - switch (error.code) { - case AuthExceptionCode.UNAUTHENTICATED: - return 401; - default: - return 400; - } + if (error instanceof AuthException) { + return getAuthExceptionRestStatus(error); } return 500; diff --git a/packages/twenty-server/src/engine/twenty-orm/exceptions/twenty-orm.exception.ts b/packages/twenty-server/src/engine/twenty-orm/exceptions/twenty-orm.exception.ts index f1412d894..099bd3587 100644 --- a/packages/twenty-server/src/engine/twenty-orm/exceptions/twenty-orm.exception.ts +++ b/packages/twenty-server/src/engine/twenty-orm/exceptions/twenty-orm.exception.ts @@ -7,9 +7,7 @@ export class TwentyORMException extends CustomException { } export enum TwentyORMExceptionCode { - METADATA_VERSION_NOT_FOUND = 'METADATA_VERSION_NOT_FOUND', METADATA_VERSION_MISMATCH = 'METADATA_VERSION_MISMATCH', - METADATA_COLLECTION_NOT_FOUND = 'METADATA_COLLECTION_NOT_FOUND', WORKSPACE_SCHEMA_NOT_FOUND = 'WORKSPACE_SCHEMA_NOT_FOUND', ROLES_PERMISSIONS_VERSION_NOT_FOUND = 'ROLES_PERMISSIONS_VERSION_NOT_FOUND', FEATURE_FLAG_MAP_VERSION_NOT_FOUND = 'FEATURE_FLAG_MAP_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 f58ef98de..32145ab54 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 @@ -10,6 +10,10 @@ import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interface import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service'; 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 { + WorkspaceMetadataCacheException, + WorkspaceMetadataCacheExceptionCode, +} from 'src/engine/metadata-modules/workspace-metadata-cache/exceptions/workspace-metadata-cache.exception'; import { WorkspaceMetadataCacheService } from 'src/engine/metadata-modules/workspace-metadata-cache/services/workspace-metadata-cache.service'; import { WorkspaceMetadataVersionException, @@ -120,9 +124,9 @@ export class WorkspaceDatasourceFactory { ); if (!cachedObjectMetadataMaps) { - throw new TwentyORMException( + throw new WorkspaceMetadataCacheException( `Object metadata collection not found for workspace ${workspaceId}`, - TwentyORMExceptionCode.METADATA_COLLECTION_NOT_FOUND, + WorkspaceMetadataCacheExceptionCode.OBJECT_METADATA_COLLECTION_NOT_FOUND, ); } @@ -329,7 +333,7 @@ export class WorkspaceDatasourceFactory { if (!isDefined(latestWorkspaceMetadataVersion)) { if (shouldFailIfMetadataNotFound) { throw new WorkspaceMetadataVersionException( - `Metadata version not found for workspace ${workspaceId}`, + `Metadata version not found while fetching datasource for workspace ${workspaceId}`, WorkspaceMetadataVersionExceptionCode.METADATA_VERSION_NOT_FOUND, ); } else { @@ -345,9 +349,9 @@ export class WorkspaceDatasourceFactory { } if (!isDefined(latestWorkspaceMetadataVersion)) { - throw new TwentyORMException( + throw new WorkspaceMetadataVersionException( `Metadata version not found after recompute`, - TwentyORMExceptionCode.METADATA_VERSION_NOT_FOUND, + WorkspaceMetadataVersionExceptionCode.METADATA_VERSION_NOT_FOUND, ); } diff --git a/packages/twenty-server/src/engine/utils/global-exception-handler.util.ts b/packages/twenty-server/src/engine/utils/global-exception-handler.util.ts index 3160eee10..41d73b8f6 100644 --- a/packages/twenty-server/src/engine/utils/global-exception-handler.util.ts +++ b/packages/twenty-server/src/engine/utils/global-exception-handler.util.ts @@ -18,6 +18,7 @@ import { TimeoutError, ValidationError, } from 'src/engine/core-modules/graphql/utils/graphql-errors.util'; +import { CustomException } from 'src/utils/custom-exception'; const graphQLPredefinedExceptions = { 400: ValidationError, @@ -46,44 +47,63 @@ export const handleExceptionAndConvertToGraphQLError = ( user?: ExceptionHandlerUser, workspace?: ExceptionHandlerWorkspace, ): BaseGraphQLError => { - handleException(exception, exceptionHandlerService, user, workspace); + handleException({ + exception, + exceptionHandlerService, + user, + workspace, + }); return convertExceptionToGraphQLError(exception); }; -export const shouldFilterException = (exception: Error): boolean => { +export const shouldCaptureException = ( + exception: Error, + statusCode?: number, +): boolean => { if ( exception instanceof GraphQLError && (exception?.extensions?.http?.status ?? 500) < 500 ) { - return true; + return false; } if ( exception instanceof BaseGraphQLError && graphQLErrorCodesToFilter.includes(exception?.extensions?.code) ) { - return true; + return false; } if (exception instanceof HttpException && exception.getStatus() < 500) { - return true; + return false; } - return false; + if (statusCode && statusCode < 500) { + return false; + } + + return true; }; -export const handleException = ( - exception: Error, - exceptionHandlerService: ExceptionHandlerService, - user?: ExceptionHandlerUser, - workspace?: ExceptionHandlerWorkspace, -): void => { - if (shouldFilterException(exception)) { - return; +export const handleException = ({ + exception, + exceptionHandlerService, + user, + workspace, + statusCode, +}: { + exception: T; + exceptionHandlerService: ExceptionHandlerService; + user?: ExceptionHandlerUser; + workspace?: ExceptionHandlerWorkspace; + statusCode?: number; +}): T => { + if (shouldCaptureException(exception, statusCode)) { + exceptionHandlerService.captureExceptions([exception], { user, workspace }); } - exceptionHandlerService.captureExceptions([exception], { user, workspace }); + return exception; }; export const convertExceptionToGraphQLError = ( diff --git a/packages/twenty-server/src/queue-worker/queue-worker.ts b/packages/twenty-server/src/queue-worker/queue-worker.ts index ec3396d70..61ebe2a6b 100644 --- a/packages/twenty-server/src/queue-worker/queue-worker.ts +++ b/packages/twenty-server/src/queue-worker/queue-worker.ts @@ -2,7 +2,7 @@ import { NestFactory } from '@nestjs/core'; import { ExceptionHandlerService } from 'src/engine/core-modules/exception-handler/exception-handler.service'; import { LoggerService } from 'src/engine/core-modules/logger/logger.service'; -import { shouldFilterException } from 'src/engine/utils/global-exception-handler.util'; +import { shouldCaptureException } from 'src/engine/utils/global-exception-handler.util'; import 'src/instrument'; import { QueueWorkerModule } from 'src/queue-worker/queue-worker.module'; @@ -23,7 +23,7 @@ async function bootstrap() { } catch (err) { loggerService?.error(err?.message, err?.name); - if (!shouldFilterException(err)) { + if (shouldCaptureException(err)) { exceptionHandlerService?.captureExceptions([err]); } diff --git a/packages/twenty-server/test/integration/rest/suites/rest-api-core-authentication.integration-spec.ts b/packages/twenty-server/test/integration/rest/suites/rest-api-core-authentication.integration-spec.ts index 098d16e9a..ce4c8d3a5 100644 --- a/packages/twenty-server/test/integration/rest/suites/rest-api-core-authentication.integration-spec.ts +++ b/packages/twenty-server/test/integration/rest/suites/rest-api-core-authentication.integration-spec.ts @@ -7,7 +7,7 @@ describe('Core REST API Authentication', () => { path: `/people`, bearer: '', }) - .expect(400) + .expect(403) .expect((res) => { expect(res.body.error).toBe('FORBIDDEN_EXCEPTION'); expect(res.body.messages[0]).toBe('Missing authentication token');