diff --git a/packages/backend/server/package.json b/packages/backend/server/package.json index 90e9e216c6..2560bbcfaf 100644 --- a/packages/backend/server/package.json +++ b/packages/backend/server/package.json @@ -80,6 +80,7 @@ "graphql-upload": "^17.0.0", "html-validate": "^9.0.0", "htmlrewriter": "^0.0.12", + "http-errors": "^2.0.0", "ioredis": "^5.4.1", "is-mobile": "^5.0.0", "keyv": "^5.2.2", @@ -120,6 +121,7 @@ "@types/cookie-parser": "^1.4.8", "@types/express": "^4.17.21", "@types/graphql-upload": "^17.0.0", + "@types/http-errors": "^2.0.4", "@types/lodash-es": "^4.17.12", "@types/mixpanel": "^2.14.9", "@types/mustache": "^4.2.5", diff --git a/packages/backend/server/src/__tests__/utils/blobs.ts b/packages/backend/server/src/__tests__/utils/blobs.ts index 6aa2f34922..a73426b165 100644 --- a/packages/backend/server/src/__tests__/utils/blobs.ts +++ b/packages/backend/server/src/__tests__/utils/blobs.ts @@ -1,6 +1,7 @@ import { type Blob } from '@prisma/client'; import { TestingApp } from './testing-app'; +import { TEST_LOG_LEVEL } from './utils'; export async function listBlobs( app: TestingApp, @@ -73,5 +74,13 @@ export async function setBlob( `blob-${Math.random().toString(16).substring(2, 10)}.data` ) .expect(200); + + if (res.body.errors?.length) { + if (TEST_LOG_LEVEL !== 'fatal') { + // print the error stack when log level is not fatal, for better debugging + console.error('%o', res.body); + } + throw new Error(res.body.errors[0].message); + } return res.body.data.setBlob; } diff --git a/packages/backend/server/src/__tests__/workspace/blobs.e2e.ts b/packages/backend/server/src/__tests__/workspace/blobs.e2e.ts index f3e35b0677..a94b9da516 100644 --- a/packages/backend/server/src/__tests__/workspace/blobs.e2e.ts +++ b/packages/backend/server/src/__tests__/workspace/blobs.e2e.ts @@ -169,3 +169,15 @@ test('should accept blob even storage out of quota if workspace has unlimited fe await t.notThrowsAsync(setBlob(app, workspace.id, buffer)); await t.notThrowsAsync(setBlob(app, workspace.id, buffer)); }); + +test('should throw error when blob size large than max file size', async t => { + await app.signup(); + + const workspace = await createWorkspace(app); + + const buffer = Buffer.from(new Uint8Array(1024 * 1024 * 11)); + await t.throwsAsync(setBlob(app, workspace.id, buffer), { + message: + 'HTTP request error, message: File truncated as it exceeds the 10485760 byte size limit.', + }); +}); diff --git a/packages/backend/server/src/base/error/def.ts b/packages/backend/server/src/base/error/def.ts index effd1fb9c5..60aab8dcc6 100644 --- a/packages/backend/server/src/base/error/def.ts +++ b/packages/backend/server/src/base/error/def.ts @@ -264,6 +264,11 @@ export const USER_FRIENDLY_ERRORS = { message: ({ code, message }) => `GraphQL bad request, code: ${code}, ${message}`, }, + http_request_error: { + type: 'bad_request', + args: { message: 'string' }, + message: ({ message }) => `HTTP request error, message: ${message}`, + }, // Input errors query_too_long: { diff --git a/packages/backend/server/src/base/error/errors.gen.ts b/packages/backend/server/src/base/error/errors.gen.ts index 97aff45eb8..bdc640aa57 100644 --- a/packages/backend/server/src/base/error/errors.gen.ts +++ b/packages/backend/server/src/base/error/errors.gen.ts @@ -45,6 +45,16 @@ export class GraphqlBadRequest extends UserFriendlyError { } } @ObjectType() +class HttpRequestErrorDataType { + @Field() message!: string +} + +export class HttpRequestError extends UserFriendlyError { + constructor(args: HttpRequestErrorDataType, message?: string | ((args: HttpRequestErrorDataType) => string)) { + super('bad_request', 'http_request_error', message, args); + } +} +@ObjectType() class QueryTooLongDataType { @Field() max!: number } @@ -896,6 +906,7 @@ export enum ErrorNames { NOT_FOUND, BAD_REQUEST, GRAPHQL_BAD_REQUEST, + HTTP_REQUEST_ERROR, QUERY_TOO_LONG, VALIDATION_ERROR, USER_NOT_FOUND, @@ -1011,5 +1022,5 @@ registerEnumType(ErrorNames, { export const ErrorDataUnionType = createUnionType({ name: 'ErrorDataUnion', types: () => - [GraphqlBadRequestDataType, QueryTooLongDataType, ValidationErrorDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, InvalidOauthCallbackCodeDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, WorkspacePermissionNotFoundDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, SpaceShouldHaveOnlyOneOwnerDataType, DocNotFoundDataType, DocActionDeniedDataType, DocUpdateBlockedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, ExpectToGrantDocUserRolesDataType, ExpectToRevokeDocUserRolesDataType, ExpectToUpdateDocUserRoleDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotDocNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, CopilotInvalidContextDataType, CopilotContextFileNotSupportedDataType, CopilotFailedToModifyContextDataType, CopilotFailedToMatchContextDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseUpdateParamsDataType, WorkspaceMembersExceedLimitToDowngradeDataType, UnsupportedClientVersionDataType, MentionUserDocAccessDeniedDataType] as const, + [GraphqlBadRequestDataType, HttpRequestErrorDataType, QueryTooLongDataType, ValidationErrorDataType, WrongSignInCredentialsDataType, UnknownOauthProviderDataType, InvalidOauthCallbackCodeDataType, MissingOauthQueryParameterDataType, InvalidEmailDataType, InvalidPasswordLengthDataType, WorkspacePermissionNotFoundDataType, SpaceNotFoundDataType, MemberNotFoundInSpaceDataType, NotInSpaceDataType, AlreadyInSpaceDataType, SpaceAccessDeniedDataType, SpaceOwnerNotFoundDataType, SpaceShouldHaveOnlyOneOwnerDataType, DocNotFoundDataType, DocActionDeniedDataType, DocUpdateBlockedDataType, VersionRejectedDataType, InvalidHistoryTimestampDataType, DocHistoryNotFoundDataType, BlobNotFoundDataType, ExpectToGrantDocUserRolesDataType, ExpectToRevokeDocUserRolesDataType, ExpectToUpdateDocUserRoleDataType, UnsupportedSubscriptionPlanDataType, SubscriptionAlreadyExistsDataType, SubscriptionNotExistsDataType, SameSubscriptionRecurringDataType, SubscriptionPlanNotFoundDataType, CopilotDocNotFoundDataType, CopilotMessageNotFoundDataType, CopilotPromptNotFoundDataType, CopilotProviderSideErrorDataType, CopilotInvalidContextDataType, CopilotContextFileNotSupportedDataType, CopilotFailedToModifyContextDataType, CopilotFailedToMatchContextDataType, RuntimeConfigNotFoundDataType, InvalidRuntimeConfigTypeDataType, InvalidLicenseUpdateParamsDataType, WorkspaceMembersExceedLimitToDowngradeDataType, UnsupportedClientVersionDataType, MentionUserDocAccessDeniedDataType] as const, }); diff --git a/packages/backend/server/src/base/nestjs/exception.ts b/packages/backend/server/src/base/nestjs/exception.ts index 488c669565..32d0596ea3 100644 --- a/packages/backend/server/src/base/nestjs/exception.ts +++ b/packages/backend/server/src/base/nestjs/exception.ts @@ -11,12 +11,14 @@ import { ThrottlerException } from '@nestjs/throttler'; import { BaseWsExceptionFilter } from '@nestjs/websockets'; import { Response } from 'express'; import { GraphQLError } from 'graphql'; +import { HttpError } from 'http-errors'; import { of } from 'rxjs'; import { Socket } from 'socket.io'; import { ZodError } from 'zod'; import { GraphqlBadRequest, + HttpRequestError, InternalServerError, NotFound, TooManyRequest, @@ -57,6 +59,16 @@ export function mapAnyError(error: any): UserFriendlyError { return new ValidationError({ errors: error.message, }); + } else if ( + error instanceof HttpError && + error.status >= 400 && + error.status < 500 + ) { + const e = new HttpRequestError({ + message: error.message, + }); + e.status = error.status; + return e; } else { const e = new InternalServerError(); e.cause = error; diff --git a/packages/backend/server/src/schema.gql b/packages/backend/server/src/schema.gql index c5d82eeb0d..3d9749fe15 100644 --- a/packages/backend/server/src/schema.gql +++ b/packages/backend/server/src/schema.gql @@ -376,7 +376,7 @@ type EditorType { name: String! } -union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotContextFileNotSupportedDataType | CopilotDocNotFoundDataType | CopilotFailedToMatchContextDataType | CopilotFailedToModifyContextDataType | CopilotInvalidContextDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocActionDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | DocUpdateBlockedDataType | ExpectToGrantDocUserRolesDataType | ExpectToRevokeDocUserRolesDataType | ExpectToUpdateDocUserRoleDataType | GraphqlBadRequestDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseUpdateParamsDataType | InvalidOauthCallbackCodeDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MemberNotFoundInSpaceDataType | MentionUserDocAccessDeniedDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SpaceShouldHaveOnlyOneOwnerDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedClientVersionDataType | UnsupportedSubscriptionPlanDataType | ValidationErrorDataType | VersionRejectedDataType | WorkspaceMembersExceedLimitToDowngradeDataType | WorkspacePermissionNotFoundDataType | WrongSignInCredentialsDataType +union ErrorDataUnion = AlreadyInSpaceDataType | BlobNotFoundDataType | CopilotContextFileNotSupportedDataType | CopilotDocNotFoundDataType | CopilotFailedToMatchContextDataType | CopilotFailedToModifyContextDataType | CopilotInvalidContextDataType | CopilotMessageNotFoundDataType | CopilotPromptNotFoundDataType | CopilotProviderSideErrorDataType | DocActionDeniedDataType | DocHistoryNotFoundDataType | DocNotFoundDataType | DocUpdateBlockedDataType | ExpectToGrantDocUserRolesDataType | ExpectToRevokeDocUserRolesDataType | ExpectToUpdateDocUserRoleDataType | GraphqlBadRequestDataType | HttpRequestErrorDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseUpdateParamsDataType | InvalidOauthCallbackCodeDataType | InvalidPasswordLengthDataType | InvalidRuntimeConfigTypeDataType | MemberNotFoundInSpaceDataType | MentionUserDocAccessDeniedDataType | MissingOauthQueryParameterDataType | NotInSpaceDataType | QueryTooLongDataType | RuntimeConfigNotFoundDataType | SameSubscriptionRecurringDataType | SpaceAccessDeniedDataType | SpaceNotFoundDataType | SpaceOwnerNotFoundDataType | SpaceShouldHaveOnlyOneOwnerDataType | SubscriptionAlreadyExistsDataType | SubscriptionNotExistsDataType | SubscriptionPlanNotFoundDataType | UnknownOauthProviderDataType | UnsupportedClientVersionDataType | UnsupportedSubscriptionPlanDataType | ValidationErrorDataType | VersionRejectedDataType | WorkspaceMembersExceedLimitToDowngradeDataType | WorkspacePermissionNotFoundDataType | WrongSignInCredentialsDataType enum ErrorNames { ACCESS_DENIED @@ -429,6 +429,7 @@ enum ErrorNames { FAILED_TO_SAVE_UPDATES FAILED_TO_UPSERT_SNAPSHOT GRAPHQL_BAD_REQUEST + HTTP_REQUEST_ERROR INTERNAL_SERVER_ERROR INVALID_AUTH_STATE INVALID_CHECKOUT_PARAMETERS @@ -554,6 +555,10 @@ type GraphqlBadRequestDataType { message: String! } +type HttpRequestErrorDataType { + message: String! +} + input ImportUsersInput { users: [CreateUserInput!]! } diff --git a/packages/frontend/graphql/src/schema.ts b/packages/frontend/graphql/src/schema.ts index 879950d84c..3231e82cfb 100644 --- a/packages/frontend/graphql/src/schema.ts +++ b/packages/frontend/graphql/src/schema.ts @@ -489,6 +489,7 @@ export type ErrorDataUnion = | ExpectToRevokeDocUserRolesDataType | ExpectToUpdateDocUserRoleDataType | GraphqlBadRequestDataType + | HttpRequestErrorDataType | InvalidEmailDataType | InvalidHistoryTimestampDataType | InvalidLicenseUpdateParamsDataType @@ -569,6 +570,7 @@ export enum ErrorNames { FAILED_TO_SAVE_UPDATES = 'FAILED_TO_SAVE_UPDATES', FAILED_TO_UPSERT_SNAPSHOT = 'FAILED_TO_UPSERT_SNAPSHOT', GRAPHQL_BAD_REQUEST = 'GRAPHQL_BAD_REQUEST', + HTTP_REQUEST_ERROR = 'HTTP_REQUEST_ERROR', INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR', INVALID_AUTH_STATE = 'INVALID_AUTH_STATE', INVALID_CHECKOUT_PARAMETERS = 'INVALID_CHECKOUT_PARAMETERS', @@ -697,6 +699,11 @@ export interface GraphqlBadRequestDataType { message: Scalars['String']['output']; } +export interface HttpRequestErrorDataType { + __typename?: 'HttpRequestErrorDataType'; + message: Scalars['String']['output']; +} + export interface ImportUsersInput { users: Array; } diff --git a/packages/frontend/i18n/src/i18n-completenesses.json b/packages/frontend/i18n/src/i18n-completenesses.json index 5546e8cf8c..61ca20f799 100644 --- a/packages/frontend/i18n/src/i18n-completenesses.json +++ b/packages/frontend/i18n/src/i18n-completenesses.json @@ -2,16 +2,16 @@ "ar": 96, "ca": 4, "da": 5, - "de": 97, + "de": 96, "el-GR": 96, "en": 100, - "es-AR": 97, + "es-AR": 96, "es-CL": 98, "es": 96, "fa": 96, "fr": 96, "hi": 2, - "it-IT": 97, + "it-IT": 96, "it": 1, "ja": 96, "ko": 61, diff --git a/packages/frontend/i18n/src/i18n.gen.ts b/packages/frontend/i18n/src/i18n.gen.ts index 7969d0e67f..d93f085649 100644 --- a/packages/frontend/i18n/src/i18n.gen.ts +++ b/packages/frontend/i18n/src/i18n.gen.ts @@ -7282,6 +7282,12 @@ export function useAFFiNEI18N(): { code: string; message: string; }>): string; + /** + * `HTTP request error, message: {{message}}` + */ + ["error.HTTP_REQUEST_ERROR"](options: { + readonly message: string; + }): string; /** * `Query is too long, max length is {{max}}.` */ diff --git a/packages/frontend/i18n/src/resources/en.json b/packages/frontend/i18n/src/resources/en.json index 77ecd5ab9f..43d1e2ba64 100644 --- a/packages/frontend/i18n/src/resources/en.json +++ b/packages/frontend/i18n/src/resources/en.json @@ -1813,6 +1813,7 @@ "error.NOT_FOUND": "Resource not found.", "error.BAD_REQUEST": "Bad request.", "error.GRAPHQL_BAD_REQUEST": "GraphQL bad request, code: {{code}}, {{message}}", + "error.HTTP_REQUEST_ERROR": "HTTP request error, message: {{message}}", "error.QUERY_TOO_LONG": "Query is too long, max length is {{max}}.", "error.VALIDATION_ERROR": "Validation error, errors: {{errors}}", "error.USER_NOT_FOUND": "User not found.", diff --git a/yarn.lock b/yarn.lock index a10697591f..3ceb22dbee 100644 --- a/yarn.lock +++ b/yarn.lock @@ -892,6 +892,7 @@ __metadata: "@types/cookie-parser": "npm:^1.4.8" "@types/express": "npm:^4.17.21" "@types/graphql-upload": "npm:^17.0.0" + "@types/http-errors": "npm:^2.0.4" "@types/lodash-es": "npm:^4.17.12" "@types/mixpanel": "npm:^2.14.9" "@types/mustache": "npm:^4.2.5" @@ -921,6 +922,7 @@ __metadata: graphql-upload: "npm:^17.0.0" html-validate: "npm:^9.0.0" htmlrewriter: "npm:^0.0.12" + http-errors: "npm:^2.0.0" ioredis: "npm:^5.4.1" is-mobile: "npm:^5.0.0" keyv: "npm:^5.2.2" @@ -14347,7 +14349,7 @@ __metadata: languageName: node linkType: hard -"@types/http-errors@npm:*": +"@types/http-errors@npm:*, @types/http-errors@npm:^2.0.4": version: 2.0.4 resolution: "@types/http-errors@npm:2.0.4" checksum: 10/1f3d7c3b32c7524811a45690881736b3ef741bf9849ae03d32ad1ab7062608454b150a4e7f1351f83d26a418b2d65af9bdc06198f1c079d75578282884c4e8e3