diff --git a/packages/twenty-server/src/engine/api/common/common-query-runners/errors/common-query-runner.exception.ts b/packages/twenty-server/src/engine/api/common/common-query-runners/errors/common-query-runner.exception.ts index 337dd6f980..9d8ded9cc0 100644 --- a/packages/twenty-server/src/engine/api/common/common-query-runners/errors/common-query-runner.exception.ts +++ b/packages/twenty-server/src/engine/api/common/common-query-runners/errors/common-query-runner.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class CommonQueryRunnerException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum CommonQueryRunnerExceptionCode { RECORD_NOT_FOUND = 'RECORD_NOT_FOUND', @@ -18,3 +19,37 @@ export enum CommonQueryRunnerExceptionCode { INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR', TOO_COMPLEX_QUERY = 'TOO_COMPLEX_QUERY', } + +const commonQueryRunnerExceptionUserFriendlyMessages: Record< + CommonQueryRunnerExceptionCode, + MessageDescriptor +> = { + [CommonQueryRunnerExceptionCode.RECORD_NOT_FOUND]: msg`Record not found.`, + [CommonQueryRunnerExceptionCode.INVALID_QUERY_INPUT]: msg`Invalid query input.`, + [CommonQueryRunnerExceptionCode.INVALID_AUTH_CONTEXT]: msg`Invalid authentication context.`, + [CommonQueryRunnerExceptionCode.ARGS_CONFLICT]: msg`Conflicting arguments provided.`, + [CommonQueryRunnerExceptionCode.INVALID_ARGS_DATA]: msg`Invalid data provided.`, + [CommonQueryRunnerExceptionCode.INVALID_ARGS_FIRST]: msg`Invalid 'first' argument.`, + [CommonQueryRunnerExceptionCode.INVALID_ARGS_LAST]: msg`Invalid 'last' argument.`, + [CommonQueryRunnerExceptionCode.UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT]: msg`Multiple matching records found during upsert.`, + [CommonQueryRunnerExceptionCode.MISSING_SYSTEM_FIELD]: msg`Missing required system field.`, + [CommonQueryRunnerExceptionCode.INVALID_CURSOR]: msg`Invalid cursor provided.`, + [CommonQueryRunnerExceptionCode.TOO_MANY_RECORDS_TO_UPDATE]: msg`Too many records to update at once.`, + [CommonQueryRunnerExceptionCode.BAD_REQUEST]: msg`Bad request.`, + [CommonQueryRunnerExceptionCode.INTERNAL_SERVER_ERROR]: msg`An unexpected error occurred.`, + [CommonQueryRunnerExceptionCode.TOO_COMPLEX_QUERY]: msg`Query is too complex.`, +}; + +export class CommonQueryRunnerException extends CustomException { + constructor( + message: string, + code: CommonQueryRunnerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + commonQueryRunnerExceptionUserFriendlyMessages[code], + }); + } +} 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 5104ac6d74..89b233e2ad 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'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class GraphqlQueryRunnerException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum GraphqlQueryRunnerExceptionCode { INVALID_QUERY_INPUT = 'INVALID_QUERY_INPUT', @@ -22,3 +23,41 @@ export enum GraphqlQueryRunnerExceptionCode { UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT = 'UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT', UPSERT_MAX_RECORDS_EXCEEDED = 'UPSERT_MAX_RECORDS_EXCEEDED', } + +const graphqlQueryRunnerExceptionUserFriendlyMessages: Record< + GraphqlQueryRunnerExceptionCode, + MessageDescriptor +> = { + [GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT]: msg`Invalid query input.`, + [GraphqlQueryRunnerExceptionCode.MAX_DEPTH_REACHED]: msg`Maximum query depth reached.`, + [GraphqlQueryRunnerExceptionCode.INVALID_CURSOR]: msg`Invalid cursor provided.`, + [GraphqlQueryRunnerExceptionCode.INVALID_DIRECTION]: msg`Invalid direction provided.`, + [GraphqlQueryRunnerExceptionCode.UNSUPPORTED_OPERATOR]: msg`Unsupported operator.`, + [GraphqlQueryRunnerExceptionCode.ARGS_CONFLICT]: msg`Conflicting arguments provided.`, + [GraphqlQueryRunnerExceptionCode.FIELD_NOT_FOUND]: msg`Field not found.`, + [GraphqlQueryRunnerExceptionCode.MISSING_SYSTEM_FIELD]: msg`Missing required system field.`, + [GraphqlQueryRunnerExceptionCode.OBJECT_METADATA_NOT_FOUND]: msg`Object not found.`, + [GraphqlQueryRunnerExceptionCode.RECORD_NOT_FOUND]: msg`Record not found.`, + [GraphqlQueryRunnerExceptionCode.INVALID_ARGS_FIRST]: msg`Invalid 'first' argument.`, + [GraphqlQueryRunnerExceptionCode.INVALID_ARGS_LAST]: msg`Invalid 'last' argument.`, + [GraphqlQueryRunnerExceptionCode.RELATION_SETTINGS_NOT_FOUND]: msg`Relation settings not found.`, + [GraphqlQueryRunnerExceptionCode.RELATION_TARGET_OBJECT_METADATA_NOT_FOUND]: msg`Relation target not found.`, + [GraphqlQueryRunnerExceptionCode.NOT_IMPLEMENTED]: msg`This feature is not implemented.`, + [GraphqlQueryRunnerExceptionCode.INVALID_POST_HOOK_PAYLOAD]: msg`Invalid post-hook payload.`, + [GraphqlQueryRunnerExceptionCode.UPSERT_MULTIPLE_MATCHING_RECORDS_CONFLICT]: msg`Multiple matching records found during upsert.`, + [GraphqlQueryRunnerExceptionCode.UPSERT_MAX_RECORDS_EXCEEDED]: msg`Maximum records exceeded for upsert.`, +}; + +export class GraphqlQueryRunnerException extends CustomException { + constructor( + message: string, + code: GraphqlQueryRunnerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + graphqlQueryRunnerExceptionUserFriendlyMessages[code], + }); + } +} 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 f6ae911f37..4e14f8ccb7 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,12 +1,11 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { appendCommonExceptionCode, CustomException, } from 'src/utils/custom-exception'; -export class WorkspaceQueryRunnerException extends CustomException< - keyof typeof WorkspaceQueryRunnerExceptionCode -> {} - export const WorkspaceQueryRunnerExceptionCode = appendCommonExceptionCode({ INVALID_QUERY_INPUT: 'INVALID_QUERY_INPUT', DATA_NOT_FOUND: 'DATA_NOT_FOUND', @@ -17,3 +16,33 @@ export const WorkspaceQueryRunnerExceptionCode = appendCommonExceptionCode({ TOO_MANY_ROWS_AFFECTED: 'TOO_MANY_ROWS_AFFECTED', NO_ROWS_AFFECTED: 'NO_ROWS_AFFECTED', } as const); + +const workspaceQueryRunnerExceptionUserFriendlyMessages: Record< + keyof typeof WorkspaceQueryRunnerExceptionCode, + MessageDescriptor +> = { + INVALID_QUERY_INPUT: msg`Invalid query input.`, + DATA_NOT_FOUND: msg`Data not found.`, + QUERY_TIMEOUT: msg`Query timed out.`, + QUERY_VIOLATES_UNIQUE_CONSTRAINT: msg`A record with this value already exists.`, + QUERY_VIOLATES_FOREIGN_KEY_CONSTRAINT: msg`Cannot complete operation due to related records.`, + TOO_MANY_ROWS_AFFECTED: msg`Too many records affected.`, + NO_ROWS_AFFECTED: msg`No records were affected.`, + INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`, +}; + +export class WorkspaceQueryRunnerException extends CustomException< + keyof typeof WorkspaceQueryRunnerExceptionCode +> { + constructor( + message: string, + code: keyof typeof WorkspaceQueryRunnerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workspaceQueryRunnerExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/api/rest/input-request-parsers/rest-input-request-parser.exception.ts b/packages/twenty-server/src/engine/api/rest/input-request-parsers/rest-input-request-parser.exception.ts index c607f37a78..b25bbe5780 100644 --- a/packages/twenty-server/src/engine/api/rest/input-request-parsers/rest-input-request-parser.exception.ts +++ b/packages/twenty-server/src/engine/api/rest/input-request-parsers/rest-input-request-parser.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class RestInputRequestParserException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum RestInputRequestParserExceptionCode { INVALID_AGGREGATE_FIELDS_QUERY_PARAM = 'INVALID_AGGREGATE_FIELDS_QUERY_PARAM', @@ -11,3 +12,30 @@ export enum RestInputRequestParserExceptionCode { INVALID_LIMIT_QUERY_PARAM = 'INVALID_LIMIT_QUERY_PARAM', INVALID_FILTER_QUERY_PARAM = 'INVALID_FILTER_QUERY_PARAM', } + +const restInputRequestParserExceptionUserFriendlyMessages: Record< + RestInputRequestParserExceptionCode, + MessageDescriptor +> = { + [RestInputRequestParserExceptionCode.INVALID_AGGREGATE_FIELDS_QUERY_PARAM]: msg`Invalid aggregate fields parameter.`, + [RestInputRequestParserExceptionCode.INVALID_GROUP_BY_QUERY_PARAM]: msg`Invalid group by parameter.`, + [RestInputRequestParserExceptionCode.INVALID_ORDER_BY_WITH_GROUP_BY_QUERY_PARAM]: msg`Invalid order by with group by parameter.`, + [RestInputRequestParserExceptionCode.INVALID_ORDER_BY_QUERY_PARAM]: msg`Invalid order by parameter.`, + [RestInputRequestParserExceptionCode.INVALID_DEPTH_QUERY_PARAM]: msg`Invalid depth parameter.`, + [RestInputRequestParserExceptionCode.INVALID_LIMIT_QUERY_PARAM]: msg`Invalid limit parameter.`, + [RestInputRequestParserExceptionCode.INVALID_FILTER_QUERY_PARAM]: msg`Invalid filter parameter.`, +}; + +export class RestInputRequestParserException extends CustomException { + constructor( + message: string, + code: RestInputRequestParserExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + restInputRequestParserExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/api-key/exceptions/api-key.exception.ts b/packages/twenty-server/src/engine/core-modules/api-key/exceptions/api-key.exception.ts index e381d6d5a4..1169f6cc24 100644 --- a/packages/twenty-server/src/engine/core-modules/api-key/exceptions/api-key.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/api-key/exceptions/api-key.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class ApiKeyException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum ApiKeyExceptionCode { API_KEY_NOT_FOUND = 'API_KEY_NOT_FOUND', @@ -9,3 +10,27 @@ export enum ApiKeyExceptionCode { API_KEY_NO_ROLE_ASSIGNED = 'API_KEY_NO_ROLE_ASSIGNED', ROLE_CANNOT_BE_ASSIGNED_TO_API_KEYS = 'ROLE_CANNOT_BE_ASSIGNED_TO_API_KEYS', } + +const apiKeyExceptionUserFriendlyMessages: Record< + ApiKeyExceptionCode, + MessageDescriptor +> = { + [ApiKeyExceptionCode.API_KEY_NOT_FOUND]: msg`API key not found.`, + [ApiKeyExceptionCode.API_KEY_REVOKED]: msg`This API key has been revoked.`, + [ApiKeyExceptionCode.API_KEY_EXPIRED]: msg`This API key has expired.`, + [ApiKeyExceptionCode.API_KEY_NO_ROLE_ASSIGNED]: msg`This API key has no role assigned.`, + [ApiKeyExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_API_KEYS]: msg`This role cannot be assigned to API keys.`, +}; + +export class ApiKeyException extends CustomException { + constructor( + message: string, + code: ApiKeyExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? apiKeyExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/application/application.exception.ts b/packages/twenty-server/src/engine/core-modules/application/application.exception.ts index 63e35df844..30d3d57050 100644 --- a/packages/twenty-server/src/engine/core-modules/application/application.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/application/application.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class ApplicationException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum ApplicationExceptionCode { OBJECT_NOT_FOUND = 'OBJECT_NOT_FOUND', @@ -10,3 +11,28 @@ export enum ApplicationExceptionCode { APPLICATION_NOT_FOUND = 'APPLICATION_NOT_FOUND', FORBIDDEN = 'FORBIDDEN', } + +const applicationExceptionUserFriendlyMessages: Record< + ApplicationExceptionCode, + MessageDescriptor +> = { + [ApplicationExceptionCode.OBJECT_NOT_FOUND]: msg`Object not found.`, + [ApplicationExceptionCode.FIELD_NOT_FOUND]: msg`Field not found.`, + [ApplicationExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND]: msg`Serverless function not found.`, + [ApplicationExceptionCode.ENTITY_NOT_FOUND]: msg`Entity not found.`, + [ApplicationExceptionCode.APPLICATION_NOT_FOUND]: msg`Application not found.`, + [ApplicationExceptionCode.FORBIDDEN]: msg`You do not have permission to perform this action.`, +}; + +export class ApplicationException extends CustomException { + constructor( + message: string, + code: ApplicationExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? applicationExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/applicationVariable/application-variable.exception.ts b/packages/twenty-server/src/engine/core-modules/applicationVariable/application-variable.exception.ts index eee6d2d539..49833531eb 100644 --- a/packages/twenty-server/src/engine/core-modules/applicationVariable/application-variable.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/applicationVariable/application-variable.exception.ts @@ -1,7 +1,29 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class ApplicationVariableEntityException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum ApplicationVariableEntityExceptionCode { APPLICATION_VARIABLE_NOT_FOUND = 'APPLICATION_VARIABLE_NOT_FOUND', } + +const applicationVariableEntityExceptionUserFriendlyMessages: Record< + ApplicationVariableEntityExceptionCode, + MessageDescriptor +> = { + [ApplicationVariableEntityExceptionCode.APPLICATION_VARIABLE_NOT_FOUND]: msg`Application variable not found.`, +}; + +export class ApplicationVariableEntityException extends CustomException { + constructor( + message: string, + code: ApplicationVariableEntityExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + applicationVariableEntityExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/approved-access-domain/approved-access-domain.exception.ts b/packages/twenty-server/src/engine/core-modules/approved-access-domain/approved-access-domain.exception.ts index de009387dc..e0b6da0114 100644 --- a/packages/twenty-server/src/engine/core-modules/approved-access-domain/approved-access-domain.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/approved-access-domain/approved-access-domain.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class ApprovedAccessDomainException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum ApprovedAccessDomainExceptionCode { APPROVED_ACCESS_DOMAIN_NOT_FOUND = 'APPROVED_ACCESS_DOMAIN_NOT_FOUND', @@ -11,3 +12,30 @@ export enum ApprovedAccessDomainExceptionCode { APPROVED_ACCESS_DOMAIN_ALREADY_VALIDATED = 'APPROVED_ACCESS_DOMAIN_ALREADY_VALIDATED', APPROVED_ACCESS_DOMAIN_MUST_BE_A_COMPANY_DOMAIN = 'APPROVED_ACCESS_DOMAIN_MUST_BE_A_COMPANY_DOMAIN', } + +const approvedAccessDomainExceptionUserFriendlyMessages: Record< + ApprovedAccessDomainExceptionCode, + MessageDescriptor +> = { + [ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_NOT_FOUND]: msg`Approved access domain not found.`, + [ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VERIFIED]: msg`This domain has already been verified.`, + [ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_REGISTERED]: msg`This domain is already registered.`, + [ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_DOES_NOT_MATCH_DOMAIN_EMAIL]: msg`The domain does not match your email domain.`, + [ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_VALIDATION_TOKEN_INVALID]: msg`Invalid validation token.`, + [ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VALIDATED]: msg`This domain has already been validated.`, + [ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_MUST_BE_A_COMPANY_DOMAIN]: msg`Please use a company email domain.`, +}; + +export class ApprovedAccessDomainException extends CustomException { + constructor( + message: string, + code: ApprovedAccessDomainExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + approvedAccessDomainExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/audit/audit.exception.ts b/packages/twenty-server/src/engine/core-modules/audit/audit.exception.ts index b87410d8ab..d148395122 100644 --- a/packages/twenty-server/src/engine/core-modules/audit/audit.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/audit/audit.exception.ts @@ -1,8 +1,30 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class AuditException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum AuditExceptionCode { INVALID_TYPE = 'INVALID_TYPE', INVALID_INPUT = 'INVALID_INPUT', } + +const auditExceptionUserFriendlyMessages: Record< + AuditExceptionCode, + MessageDescriptor +> = { + [AuditExceptionCode.INVALID_TYPE]: msg`Invalid audit type.`, + [AuditExceptionCode.INVALID_INPUT]: msg`Invalid audit input.`, +}; + +export class AuditException extends CustomException { + constructor( + message: string, + code: AuditExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? auditExceptionUserFriendlyMessages[code], + }); + } +} 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 9280f6e36a..c6b60cc25f 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,11 +1,54 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { appendCommonExceptionCode, CustomException, } from 'src/utils/custom-exception'; +const authExceptionUserFriendlyMessages: Record< + keyof typeof AuthExceptionCode, + MessageDescriptor +> = { + USER_NOT_FOUND: msg`User not found.`, + USER_WORKSPACE_NOT_FOUND: msg`User workspace not found.`, + EMAIL_NOT_VERIFIED: msg`Email is not verified.`, + CLIENT_NOT_FOUND: msg`Client not found.`, + WORKSPACE_NOT_FOUND: msg`Workspace not found.`, + APPLICATION_NOT_FOUND: msg`Application not found.`, + INVALID_INPUT: msg`Invalid input provided.`, + FORBIDDEN_EXCEPTION: msg`You do not have permission to perform this action.`, + INSUFFICIENT_SCOPES: msg`Insufficient permissions.`, + UNAUTHENTICATED: msg`You must be authenticated to perform this action.`, + INVALID_DATA: msg`Invalid data provided.`, + OAUTH_ACCESS_DENIED: msg`OAuth access was denied.`, + SSO_AUTH_FAILED: msg`Single sign-on authentication failed.`, + USE_SSO_AUTH: msg`Please use single sign-on to authenticate.`, + SIGNUP_DISABLED: msg`Sign up is disabled.`, + GOOGLE_API_AUTH_DISABLED: msg`Google API authentication is disabled.`, + MICROSOFT_API_AUTH_DISABLED: msg`Microsoft API authentication is disabled.`, + MISSING_ENVIRONMENT_VARIABLE: msg`A required configuration is missing.`, + INVALID_JWT_TOKEN_TYPE: msg`Invalid authentication token.`, + TWO_FACTOR_AUTHENTICATION_PROVISION_REQUIRED: msg`Two-factor authentication setup is required.`, + TWO_FACTOR_AUTHENTICATION_VERIFICATION_REQUIRED: msg`Two-factor authentication verification is required.`, + USER_ALREADY_EXISTS: msg`A user with this email already exists.`, + INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`, +}; + export class AuthException extends CustomException< keyof typeof AuthExceptionCode -> {} +> { + constructor( + message: string, + code: keyof typeof AuthExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? authExceptionUserFriendlyMessages[code], + }); + } +} export const AuthExceptionCode = appendCommonExceptionCode({ USER_NOT_FOUND: 'USER_NOT_FOUND', diff --git a/packages/twenty-server/src/engine/core-modules/billing/billing.exception.ts b/packages/twenty-server/src/engine/core-modules/billing/billing.exception.ts index d0ce482207..939f8bbc15 100644 --- a/packages/twenty-server/src/engine/core-modules/billing/billing.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/billing/billing.exception.ts @@ -1,8 +1,9 @@ /* @license Enterprise */ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class BillingException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum BillingExceptionCode { BILLING_CUSTOMER_NOT_FOUND = 'BILLING_CUSTOMER_NOT_FOUND', @@ -30,3 +31,46 @@ export enum BillingExceptionCode { BILLING_SUBSCRIPTION_PHASE_NOT_FOUND = 'BILLING_SUBSCRIPTION_PHASE_NOT_FOUND', BILLING_TOO_MUCH_SUBSCRIPTIONS_FOUND = 'BILLING_TOO_MUCH_SUBSCRIPTIONS_FOUND', } + +const billingExceptionUserFriendlyMessages: Record< + BillingExceptionCode, + MessageDescriptor +> = { + [BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND]: msg`Billing customer not found.`, + [BillingExceptionCode.BILLING_PLAN_NOT_FOUND]: msg`Billing plan not found.`, + [BillingExceptionCode.BILLING_PRODUCT_NOT_FOUND]: msg`Billing product not found.`, + [BillingExceptionCode.BILLING_PRICE_NOT_FOUND]: msg`Billing price not found.`, + [BillingExceptionCode.BILLING_METER_NOT_FOUND]: msg`Billing meter not found.`, + [BillingExceptionCode.BILLING_SUBSCRIPTION_NOT_FOUND]: msg`Subscription not found.`, + [BillingExceptionCode.BILLING_SUBSCRIPTION_ITEM_NOT_FOUND]: msg`Subscription item not found.`, + [BillingExceptionCode.BILLING_SUBSCRIPTION_INVALID]: msg`Invalid subscription.`, + [BillingExceptionCode.BILLING_SUBSCRIPTION_EVENT_WORKSPACE_NOT_FOUND]: msg`Workspace not found for subscription event.`, + [BillingExceptionCode.BILLING_CUSTOMER_EVENT_WORKSPACE_NOT_FOUND]: msg`Workspace not found for customer event.`, + [BillingExceptionCode.BILLING_ACTIVE_SUBSCRIPTION_NOT_FOUND]: msg`No active subscription found.`, + [BillingExceptionCode.BILLING_METER_EVENT_FAILED]: msg`Failed to record billing event.`, + [BillingExceptionCode.BILLING_MISSING_REQUEST_BODY]: msg`Missing request body.`, + [BillingExceptionCode.BILLING_UNHANDLED_ERROR]: msg`An unexpected billing error occurred.`, + [BillingExceptionCode.BILLING_STRIPE_ERROR]: msg`A payment processing error occurred.`, + [BillingExceptionCode.BILLING_SUBSCRIPTION_NOT_IN_TRIAL_PERIOD]: msg`Subscription is not in trial period.`, + [BillingExceptionCode.BILLING_SUBSCRIPTION_INTERVAL_NOT_SWITCHABLE]: msg`Cannot switch subscription interval.`, + [BillingExceptionCode.BILLING_SUBSCRIPTION_INTERVAL_INVALID]: msg`Invalid subscription interval.`, + [BillingExceptionCode.BILLING_SUBSCRIPTION_PLAN_NOT_SWITCHABLE]: msg`Cannot switch subscription plan.`, + [BillingExceptionCode.BILLING_SUBSCRIPTION_ITEM_INVALID]: msg`Invalid subscription item.`, + [BillingExceptionCode.BILLING_PRICE_INVALID_TIERS]: msg`Invalid pricing tiers.`, + [BillingExceptionCode.BILLING_PRICE_INVALID]: msg`Invalid price.`, + [BillingExceptionCode.BILLING_SUBSCRIPTION_PHASE_NOT_FOUND]: msg`Subscription phase not found.`, + [BillingExceptionCode.BILLING_TOO_MUCH_SUBSCRIPTIONS_FOUND]: msg`Multiple subscriptions found where one was expected.`, +}; + +export class BillingException extends CustomException { + constructor( + message: string, + code: BillingExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? billingExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/captcha/captcha.exception.ts b/packages/twenty-server/src/engine/core-modules/captcha/captcha.exception.ts index 602a14d5b6..f7578b7d29 100644 --- a/packages/twenty-server/src/engine/core-modules/captcha/captcha.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/captcha/captcha.exception.ts @@ -1,7 +1,28 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class CaptchaException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum CaptchaExceptionCode { INVALID_CAPTCHA = 'INVALID_CAPTCHA', } + +const captchaExceptionUserFriendlyMessages: Record< + CaptchaExceptionCode, + MessageDescriptor +> = { + [CaptchaExceptionCode.INVALID_CAPTCHA]: msg`Invalid captcha. Please try again.`, +}; + +export class CaptchaException extends CustomException { + constructor( + message: string, + code: CaptchaExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? captchaExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/dns-manager/exceptions/dns-manager.exception.ts b/packages/twenty-server/src/engine/core-modules/dns-manager/exceptions/dns-manager.exception.ts index 1a572d712d..47fc6e0ad5 100644 --- a/packages/twenty-server/src/engine/core-modules/dns-manager/exceptions/dns-manager.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/dns-manager/exceptions/dns-manager.exception.ts @@ -1,12 +1,25 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { appendCommonExceptionCode, CustomException, } from 'src/utils/custom-exception'; export class DnsManagerException extends CustomException< - keyof typeof DnsManagerExceptionCode, - true -> {} + keyof typeof DnsManagerExceptionCode +> { + constructor( + message: string, + code: keyof typeof DnsManagerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? msg`A DNS manager error occurred.`, + }); + } +} export const DnsManagerExceptionCode = appendCommonExceptionCode({ HOSTNAME_ALREADY_REGISTERED: 'HOSTNAME_ALREADY_REGISTERED', 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 fef78ac39a..ad0c2c4977 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'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class EmailVerificationException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum EmailVerificationExceptionCode { EMAIL_VERIFICATION_NOT_REQUIRED = 'EMAIL_VERIFICATION_NOT_REQUIRED', @@ -12,3 +13,31 @@ export enum EmailVerificationExceptionCode { INVALID_EMAIL = 'INVALID_EMAIL', RATE_LIMIT_EXCEEDED = 'RATE_LIMIT_EXCEEDED', } + +const emailVerificationExceptionUserFriendlyMessages: Record< + EmailVerificationExceptionCode, + MessageDescriptor +> = { + [EmailVerificationExceptionCode.EMAIL_VERIFICATION_NOT_REQUIRED]: msg`Email verification is not required.`, + [EmailVerificationExceptionCode.INVALID_TOKEN]: msg`Invalid verification token.`, + [EmailVerificationExceptionCode.INVALID_APP_TOKEN_TYPE]: msg`Invalid token type.`, + [EmailVerificationExceptionCode.TOKEN_EXPIRED]: msg`Verification token has expired.`, + [EmailVerificationExceptionCode.EMAIL_MISSING]: msg`Email is required.`, + [EmailVerificationExceptionCode.EMAIL_ALREADY_VERIFIED]: msg`Email is already verified.`, + [EmailVerificationExceptionCode.INVALID_EMAIL]: msg`Invalid email address.`, + [EmailVerificationExceptionCode.RATE_LIMIT_EXCEEDED]: msg`Too many requests. Please try again later.`, +}; + +export class EmailVerificationException extends CustomException { + constructor( + message: string, + code: EmailVerificationExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + emailVerificationExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/emailing-domain/drivers/exceptions/emailing-domain-driver.exception.ts b/packages/twenty-server/src/engine/core-modules/emailing-domain/drivers/exceptions/emailing-domain-driver.exception.ts index 3fa2b2316b..b2fbc5e0c3 100644 --- a/packages/twenty-server/src/engine/core-modules/emailing-domain/drivers/exceptions/emailing-domain-driver.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/emailing-domain/drivers/exceptions/emailing-domain-driver.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class EmailingDomainDriverException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum EmailingDomainDriverExceptionCode { NOT_FOUND = 'NOT_FOUND', @@ -9,3 +10,28 @@ export enum EmailingDomainDriverExceptionCode { CONFIGURATION_ERROR = 'CONFIGURATION_ERROR', UNKNOWN = 'UNKNOWN', } + +const emailingDomainDriverExceptionUserFriendlyMessages: Record< + EmailingDomainDriverExceptionCode, + MessageDescriptor +> = { + [EmailingDomainDriverExceptionCode.NOT_FOUND]: msg`Email domain not found.`, + [EmailingDomainDriverExceptionCode.TEMPORARY_ERROR]: msg`A temporary error occurred. Please try again.`, + [EmailingDomainDriverExceptionCode.INSUFFICIENT_PERMISSIONS]: msg`Insufficient permissions for email domain.`, + [EmailingDomainDriverExceptionCode.CONFIGURATION_ERROR]: msg`Email domain configuration error.`, + [EmailingDomainDriverExceptionCode.UNKNOWN]: msg`An unknown email error occurred.`, +}; + +export class EmailingDomainDriverException extends CustomException { + constructor( + message: string, + code: EmailingDomainDriverExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + emailingDomainDriverExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.exception.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.exception.ts index 5c32d9a222..ae4af5e727 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/feature-flag.exception.ts @@ -1,7 +1,28 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class FeatureFlagException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum FeatureFlagExceptionCode { INVALID_FEATURE_FLAG_KEY = 'INVALID_FEATURE_FLAG_KEY', } + +const featureFlagExceptionUserFriendlyMessages: Record< + FeatureFlagExceptionCode, + MessageDescriptor +> = { + [FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY]: msg`Invalid feature flag key.`, +}; + +export class FeatureFlagException extends CustomException { + constructor( + message: string, + code: FeatureFlagExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? featureFlagExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/feature-flag/validates/feature-flag.validate.spec.ts b/packages/twenty-server/src/engine/core-modules/feature-flag/validates/feature-flag.validate.spec.ts index d51d593519..38740b1505 100644 --- a/packages/twenty-server/src/engine/core-modules/feature-flag/validates/feature-flag.validate.spec.ts +++ b/packages/twenty-server/src/engine/core-modules/feature-flag/validates/feature-flag.validate.spec.ts @@ -1,3 +1,5 @@ +import { msg } from '@lingui/core/macro'; + import { featureFlagValidator } from 'src/engine/core-modules/feature-flag/validates/feature-flag.validate'; import { UnknownException } from 'src/utils/custom-exception'; @@ -7,14 +9,18 @@ describe('featureFlagValidator', () => { expect(() => featureFlagValidator.assertIsFeatureFlagKey( 'IS_AI_ENABLED', - new UnknownException('Error', 'Error'), + new UnknownException('Error', 'Error', { + userFriendlyMessage: msg`Error`, + }), ), ).not.toThrow(); }); it('should throw error if featureFlagKey is invalid', () => { const invalidKey = 'InvalidKey'; - const exception = new UnknownException('Error', 'Error'); + const exception = new UnknownException('Error', 'Error', { + userFriendlyMessage: msg`Error`, + }); expect(() => featureFlagValidator.assertIsFeatureFlagKey(invalidKey, exception), diff --git a/packages/twenty-server/src/engine/core-modules/file-storage/interfaces/file-storage-exception.ts b/packages/twenty-server/src/engine/core-modules/file-storage/interfaces/file-storage-exception.ts index abace83059..5190312795 100644 --- a/packages/twenty-server/src/engine/core-modules/file-storage/interfaces/file-storage-exception.ts +++ b/packages/twenty-server/src/engine/core-modules/file-storage/interfaces/file-storage-exception.ts @@ -1,11 +1,28 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class FileStorageException extends CustomException { - constructor(message: string, code: FileStorageExceptionCode) { - super(message, code); - } -} +import { CustomException } from 'src/utils/custom-exception'; export enum FileStorageExceptionCode { FILE_NOT_FOUND = 'FILE_NOT_FOUND', } + +const fileStorageExceptionUserFriendlyMessages: Record< + FileStorageExceptionCode, + MessageDescriptor +> = { + [FileStorageExceptionCode.FILE_NOT_FOUND]: msg`File not found.`, +}; + +export class FileStorageException extends CustomException { + constructor( + message: string, + code: FileStorageExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? fileStorageExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/file/file.exception.ts b/packages/twenty-server/src/engine/core-modules/file/file.exception.ts index 782eda9519..cfe3588a0f 100644 --- a/packages/twenty-server/src/engine/core-modules/file/file.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/file/file.exception.ts @@ -1,13 +1,36 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { appendCommonExceptionCode, CustomException, } from 'src/utils/custom-exception'; -export class FileException extends CustomException< - keyof typeof FileExceptionCode -> {} - export const FileExceptionCode = appendCommonExceptionCode({ UNAUTHENTICATED: 'UNAUTHENTICATED', FILE_NOT_FOUND: 'FILE_NOT_FOUND', } as const); + +const fileExceptionUserFriendlyMessages: Record< + keyof typeof FileExceptionCode, + MessageDescriptor +> = { + UNAUTHENTICATED: msg`Authentication is required.`, + FILE_NOT_FOUND: msg`File not found.`, + INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`, +}; + +export class FileException extends CustomException< + keyof typeof FileExceptionCode +> { + constructor( + message: string, + code: keyof typeof FileExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? fileExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/public-domain/public-domain.exception.ts b/packages/twenty-server/src/engine/core-modules/public-domain/public-domain.exception.ts index 7f15aa9fcf..a42bf01a3d 100644 --- a/packages/twenty-server/src/engine/core-modules/public-domain/public-domain.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/public-domain/public-domain.exception.ts @@ -1,9 +1,32 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class PublicDomainException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum PublicDomainExceptionCode { PUBLIC_DOMAIN_ALREADY_REGISTERED = 'PUBLIC_DOMAIN_ALREADY_REGISTERED', DOMAIN_ALREADY_REGISTERED_AS_CUSTOM_DOMAIN = 'DOMAIN_ALREADY_REGISTERED_AS_CUSTOM_DOMAIN', PUBLIC_DOMAIN_NOT_FOUND = 'PUBLIC_DOMAIN_NOT_FOUND', } + +const publicDomainExceptionUserFriendlyMessages: Record< + PublicDomainExceptionCode, + MessageDescriptor +> = { + [PublicDomainExceptionCode.PUBLIC_DOMAIN_ALREADY_REGISTERED]: msg`This public domain is already registered.`, + [PublicDomainExceptionCode.DOMAIN_ALREADY_REGISTERED_AS_CUSTOM_DOMAIN]: msg`This domain is already registered as a custom domain.`, + [PublicDomainExceptionCode.PUBLIC_DOMAIN_NOT_FOUND]: msg`Public domain not found.`, +}; + +export class PublicDomainException extends CustomException { + constructor( + message: string, + code: PublicDomainExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? publicDomainExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/record-crud/exceptions/record-crud.exception.ts b/packages/twenty-server/src/engine/core-modules/record-crud/exceptions/record-crud.exception.ts index 0c5b122bf3..f3d47d54ee 100644 --- a/packages/twenty-server/src/engine/core-modules/record-crud/exceptions/record-crud.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/record-crud/exceptions/record-crud.exception.ts @@ -1,11 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class RecordCrudException extends CustomException { - code: RecordCrudExceptionCode; - constructor(message: string, code: RecordCrudExceptionCode) { - super(message, code); - } -} +import { CustomException } from 'src/utils/custom-exception'; export enum RecordCrudExceptionCode { INVALID_REQUEST = 'INVALID_REQUEST', @@ -18,3 +14,31 @@ export enum RecordCrudExceptionCode { RECORD_UPSERT_FAILED = 'RECORD_UPSERT_FAILED', QUERY_FAILED = 'QUERY_FAILED', } + +const recordCrudExceptionUserFriendlyMessages: Record< + RecordCrudExceptionCode, + MessageDescriptor +> = { + [RecordCrudExceptionCode.INVALID_REQUEST]: msg`Invalid request.`, + [RecordCrudExceptionCode.WORKSPACE_ID_NOT_FOUND]: msg`Workspace not found.`, + [RecordCrudExceptionCode.OBJECT_NOT_FOUND]: msg`Object not found.`, + [RecordCrudExceptionCode.RECORD_NOT_FOUND]: msg`Record not found.`, + [RecordCrudExceptionCode.RECORD_CREATION_FAILED]: msg`Failed to create record.`, + [RecordCrudExceptionCode.RECORD_UPDATE_FAILED]: msg`Failed to update record.`, + [RecordCrudExceptionCode.RECORD_DELETION_FAILED]: msg`Failed to delete record.`, + [RecordCrudExceptionCode.RECORD_UPSERT_FAILED]: msg`Failed to upsert record.`, + [RecordCrudExceptionCode.QUERY_FAILED]: msg`Query failed.`, +}; + +export class RecordCrudException extends CustomException { + constructor( + message: string, + code: RecordCrudExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? recordCrudExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/record-transformer/record-transformer.exception.ts b/packages/twenty-server/src/engine/core-modules/record-transformer/record-transformer.exception.ts index 531c697f41..bc6872aacc 100644 --- a/packages/twenty-server/src/engine/core-modules/record-transformer/record-transformer.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/record-transformer/record-transformer.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class RecordTransformerException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum RecordTransformerExceptionCode { INVALID_URL = 'INVALID_URL', @@ -11,3 +12,30 @@ export enum RecordTransformerExceptionCode { CONFLICTING_PHONE_CALLING_CODE = 'CONFLICTING_PHONE_CALLING_CODE', CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE = 'CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE', } + +const recordTransformerExceptionUserFriendlyMessages: Record< + RecordTransformerExceptionCode, + MessageDescriptor +> = { + [RecordTransformerExceptionCode.INVALID_URL]: msg`Invalid URL format.`, + [RecordTransformerExceptionCode.INVALID_PHONE_NUMBER]: msg`Invalid phone number.`, + [RecordTransformerExceptionCode.INVALID_PHONE_COUNTRY_CODE]: msg`Invalid phone country code.`, + [RecordTransformerExceptionCode.INVALID_PHONE_CALLING_CODE]: msg`Invalid phone calling code.`, + [RecordTransformerExceptionCode.CONFLICTING_PHONE_COUNTRY_CODE]: msg`Conflicting phone country code.`, + [RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE]: msg`Conflicting phone calling code.`, + [RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE]: msg`Conflicting phone calling code and country code.`, +}; + +export class RecordTransformerException extends CustomException { + constructor( + message: string, + code: RecordTransformerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + recordTransformerExceptionUserFriendlyMessages[code], + }); + } +} 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 66fcd12a76..94982d6f20 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,8 +1,30 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class SearchException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum SearchExceptionCode { LABEL_IDENTIFIER_FIELD_NOT_FOUND = 'LABEL_IDENTIFIER_FIELD_NOT_FOUND', OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND', } + +const searchExceptionUserFriendlyMessages: Record< + SearchExceptionCode, + MessageDescriptor +> = { + [SearchExceptionCode.LABEL_IDENTIFIER_FIELD_NOT_FOUND]: msg`Label identifier field not found.`, + [SearchExceptionCode.OBJECT_METADATA_NOT_FOUND]: msg`Object not found.`, +}; + +export class SearchException extends CustomException { + constructor( + message: string, + code: SearchExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? searchExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/sso/sso.exception.ts b/packages/twenty-server/src/engine/core-modules/sso/sso.exception.ts index c9b94f6a4f..4f7dd1f636 100644 --- a/packages/twenty-server/src/engine/core-modules/sso/sso.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/sso/sso.exception.ts @@ -1,8 +1,9 @@ /* @license Enterprise */ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class SSOException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum SSOExceptionCode { USER_NOT_FOUND = 'USER_NOT_FOUND', @@ -12,3 +13,28 @@ export enum SSOExceptionCode { UNKNOWN_SSO_CONFIGURATION_ERROR = 'UNKNOWN_SSO_CONFIGURATION_ERROR', SSO_DISABLE = 'SSO_DISABLE', } + +const ssoExceptionUserFriendlyMessages: Record< + SSOExceptionCode, + MessageDescriptor +> = { + [SSOExceptionCode.USER_NOT_FOUND]: msg`User not found.`, + [SSOExceptionCode.IDENTITY_PROVIDER_NOT_FOUND]: msg`Identity provider not found.`, + [SSOExceptionCode.INVALID_ISSUER_URL]: msg`Invalid issuer URL.`, + [SSOExceptionCode.INVALID_IDP_TYPE]: msg`Invalid identity provider type.`, + [SSOExceptionCode.UNKNOWN_SSO_CONFIGURATION_ERROR]: msg`SSO configuration error.`, + [SSOExceptionCode.SSO_DISABLE]: msg`SSO is disabled.`, +}; + +export class SSOException extends CustomException { + constructor( + message: string, + code: SSOExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? ssoExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/throttler/throttler.exception.ts b/packages/twenty-server/src/engine/core-modules/throttler/throttler.exception.ts index 753a075b7a..ac5ef8fb36 100644 --- a/packages/twenty-server/src/engine/core-modules/throttler/throttler.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/throttler/throttler.exception.ts @@ -1,7 +1,28 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class ThrottlerException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum ThrottlerExceptionCode { LIMIT_REACHED = 'LIMIT_REACHED', } + +const throttlerExceptionUserFriendlyMessages: Record< + ThrottlerExceptionCode, + MessageDescriptor +> = { + [ThrottlerExceptionCode.LIMIT_REACHED]: msg`Rate limit reached. Please try again later.`, +}; + +export class ThrottlerException extends CustomException { + constructor( + message: string, + code: ThrottlerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? throttlerExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/exceptions/send-email-tool.exception.ts b/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/exceptions/send-email-tool.exception.ts index 1c24737efa..05070e9634 100644 --- a/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/exceptions/send-email-tool.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/tool/tools/send-email-tool/exceptions/send-email-tool.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class SendEmailToolException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum SendEmailToolExceptionCode { INVALID_CONNECTED_ACCOUNT_ID = 'INVALID_CONNECTED_ACCOUNT_ID', @@ -10,3 +11,28 @@ export enum SendEmailToolExceptionCode { FILE_NOT_FOUND = 'FILE_NOT_FOUND', INVALID_FILE_ID = 'INVALID_FILE_ID', } + +const sendEmailToolExceptionUserFriendlyMessages: Record< + SendEmailToolExceptionCode, + MessageDescriptor +> = { + [SendEmailToolExceptionCode.INVALID_CONNECTED_ACCOUNT_ID]: msg`Invalid connected account ID.`, + [SendEmailToolExceptionCode.CONNECTED_ACCOUNT_NOT_FOUND]: msg`Connected account not found.`, + [SendEmailToolExceptionCode.INVALID_EMAIL]: msg`Invalid email address.`, + [SendEmailToolExceptionCode.WORKSPACE_ID_NOT_FOUND]: msg`Workspace not found.`, + [SendEmailToolExceptionCode.FILE_NOT_FOUND]: msg`File not found.`, + [SendEmailToolExceptionCode.INVALID_FILE_ID]: msg`Invalid file ID.`, +}; + +export class SendEmailToolException extends CustomException { + constructor( + message: string, + code: SendEmailToolExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? sendEmailToolExceptionUserFriendlyMessages[code], + }); + } +} 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 4c0d09b2f9..e72d92919a 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'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class ConfigVariableException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum ConfigVariableExceptionCode { DATABASE_CONFIG_DISABLED = 'DATABASE_CONFIG_DISABLED', @@ -10,3 +11,29 @@ export enum ConfigVariableExceptionCode { UNSUPPORTED_CONFIG_TYPE = 'UNSUPPORTED_CONFIG_TYPE', INTERNAL_ERROR = 'INTERNAL_ERROR', } + +const configVariableExceptionUserFriendlyMessages: Record< + ConfigVariableExceptionCode, + MessageDescriptor +> = { + [ConfigVariableExceptionCode.DATABASE_CONFIG_DISABLED]: msg`Database configuration is disabled.`, + [ConfigVariableExceptionCode.ENVIRONMENT_ONLY_VARIABLE]: msg`This variable can only be set via environment.`, + [ConfigVariableExceptionCode.VARIABLE_NOT_FOUND]: msg`Configuration variable not found.`, + [ConfigVariableExceptionCode.VALIDATION_FAILED]: msg`Configuration validation failed.`, + [ConfigVariableExceptionCode.UNSUPPORTED_CONFIG_TYPE]: msg`Unsupported configuration type.`, + [ConfigVariableExceptionCode.INTERNAL_ERROR]: msg`An unexpected configuration error occurred.`, +}; + +export class ConfigVariableException extends CustomException { + constructor( + message: string, + code: ConfigVariableExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + configVariableExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.exception.ts b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.exception.ts index 059040b75f..b50f7cdbfc 100644 --- a/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/two-factor-authentication/two-factor-authentication.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class TwoFactorAuthenticationException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum TwoFactorAuthenticationExceptionCode { INVALID_CONFIGURATION = 'INVALID_CONFIGURATION', @@ -9,3 +10,28 @@ export enum TwoFactorAuthenticationExceptionCode { TWO_FACTOR_AUTHENTICATION_METHOD_ALREADY_PROVISIONED = 'TWO_FACTOR_AUTHENTICATION_METHOD_ALREADY_PROVISIONED', MALFORMED_DATABASE_OBJECT = 'MALFORMED_DATABASE_OBJECT', } + +const twoFactorAuthenticationExceptionUserFriendlyMessages: Record< + TwoFactorAuthenticationExceptionCode, + MessageDescriptor +> = { + [TwoFactorAuthenticationExceptionCode.INVALID_CONFIGURATION]: msg`Invalid two-factor authentication configuration.`, + [TwoFactorAuthenticationExceptionCode.TWO_FACTOR_AUTHENTICATION_METHOD_NOT_FOUND]: msg`Two-factor authentication method not found.`, + [TwoFactorAuthenticationExceptionCode.INVALID_OTP]: msg`Invalid verification code.`, + [TwoFactorAuthenticationExceptionCode.TWO_FACTOR_AUTHENTICATION_METHOD_ALREADY_PROVISIONED]: msg`Two-factor authentication is already set up.`, + [TwoFactorAuthenticationExceptionCode.MALFORMED_DATABASE_OBJECT]: msg`An error occurred with two-factor authentication data.`, +}; + +export class TwoFactorAuthenticationException extends CustomException { + constructor( + message: string, + code: TwoFactorAuthenticationExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + twoFactorAuthenticationExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.exception.ts b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.exception.ts index 121c32e9be..4ae687bc71 100644 --- a/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/user-workspace/user-workspace.exception.ts @@ -1,11 +1,32 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class UserWorkspaceException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum UserWorkspaceExceptionCode { USER_WORKSPACE_NOT_FOUND = 'WORKSPACE_NOT_FOUND', } +const userWorkspaceExceptionUserFriendlyMessages: Record< + UserWorkspaceExceptionCode, + MessageDescriptor +> = { + [UserWorkspaceExceptionCode.USER_WORKSPACE_NOT_FOUND]: msg`User workspace not found.`, +}; + +export class UserWorkspaceException extends CustomException { + constructor( + message: string, + code: UserWorkspaceExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? userWorkspaceExceptionUserFriendlyMessages[code], + }); + } +} + export const UserWorkspaceNotFoundDefaultError = new UserWorkspaceException( 'User Workspace not found', UserWorkspaceExceptionCode.USER_WORKSPACE_NOT_FOUND, diff --git a/packages/twenty-server/src/engine/core-modules/user/user.exception.ts b/packages/twenty-server/src/engine/core-modules/user/user.exception.ts index f88873020e..e350b4deeb 100644 --- a/packages/twenty-server/src/engine/core-modules/user/user.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/user/user.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class UserException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum UserExceptionCode { USER_NOT_FOUND = 'USER_NOT_FOUND', @@ -8,3 +9,26 @@ export enum UserExceptionCode { EMAIL_UNCHANGED = 'EMAIL_UNCHANGED', EMAIL_UPDATE_RESTRICTED_TO_SINGLE_WORKSPACE = 'EMAIL_UPDATE_RESTRICTED_TO_SINGLE_WORKSPACE', } + +const userExceptionUserFriendlyMessages: Record< + UserExceptionCode, + MessageDescriptor +> = { + [UserExceptionCode.USER_NOT_FOUND]: msg`User not found.`, + [UserExceptionCode.EMAIL_ALREADY_IN_USE]: msg`This email is already in use.`, + [UserExceptionCode.EMAIL_UNCHANGED]: msg`Email is unchanged.`, + [UserExceptionCode.EMAIL_UPDATE_RESTRICTED_TO_SINGLE_WORKSPACE]: msg`Email update is restricted to single workspace users.`, +}; + +export class UserException extends CustomException { + constructor( + message: string, + code: UserExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? userExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/webhook/webhook.exception.ts b/packages/twenty-server/src/engine/core-modules/webhook/webhook.exception.ts index d3526ecce5..2a40cd705c 100644 --- a/packages/twenty-server/src/engine/core-modules/webhook/webhook.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/webhook/webhook.exception.ts @@ -1,8 +1,30 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WebhookException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WebhookExceptionCode { WEBHOOK_NOT_FOUND = 'WEBHOOK_NOT_FOUND', INVALID_TARGET_URL = 'INVALID_TARGET_URL', } + +const webhookExceptionUserFriendlyMessages: Record< + WebhookExceptionCode, + MessageDescriptor +> = { + [WebhookExceptionCode.WEBHOOK_NOT_FOUND]: msg`Webhook not found.`, + [WebhookExceptionCode.INVALID_TARGET_URL]: msg`Invalid target URL.`, +}; + +export class WebhookException extends CustomException { + constructor( + message: string, + code: WebhookExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? webhookExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.exception.ts b/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.exception.ts index 1f33743bbb..ae7d06efbd 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.exception.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace-invitation/workspace-invitation.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WorkspaceInvitationException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WorkspaceInvitationExceptionCode { INVALID_APP_TOKEN_TYPE = 'INVALID_APP_TOKEN_TYPE', @@ -10,3 +11,29 @@ export enum WorkspaceInvitationExceptionCode { INVALID_INVITATION = 'INVALID_INVITATION', EMAIL_MISSING = 'EMAIL_MISSING', } + +const workspaceInvitationExceptionUserFriendlyMessages: Record< + WorkspaceInvitationExceptionCode, + MessageDescriptor +> = { + [WorkspaceInvitationExceptionCode.INVALID_APP_TOKEN_TYPE]: msg`Invalid token type.`, + [WorkspaceInvitationExceptionCode.INVITATION_CORRUPTED]: msg`Invitation is corrupted.`, + [WorkspaceInvitationExceptionCode.INVITATION_ALREADY_EXIST]: msg`An invitation has already been sent to this email.`, + [WorkspaceInvitationExceptionCode.USER_ALREADY_EXIST]: msg`This user is already a member of the workspace.`, + [WorkspaceInvitationExceptionCode.INVALID_INVITATION]: msg`Invalid invitation.`, + [WorkspaceInvitationExceptionCode.EMAIL_MISSING]: msg`Email is required.`, +}; + +export class WorkspaceInvitationException extends CustomException { + constructor( + message: string, + code: WorkspaceInvitationExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workspaceInvitationExceptionUserFriendlyMessages[code], + }); + } +} 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 cff8ca50ba..edacff50ca 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'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WorkspaceException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WorkspaceExceptionCode { SUBDOMAIN_NOT_FOUND = 'SUBDOMAIN_NOT_FOUND', @@ -13,6 +14,33 @@ export enum WorkspaceExceptionCode { CUSTOM_DOMAIN_NOT_FOUND = 'CUSTOM_DOMAIN_NOT_FOUND', } +const workspaceExceptionUserFriendlyMessages: Record< + WorkspaceExceptionCode, + MessageDescriptor +> = { + [WorkspaceExceptionCode.SUBDOMAIN_NOT_FOUND]: msg`Subdomain not found.`, + [WorkspaceExceptionCode.SUBDOMAIN_ALREADY_TAKEN]: msg`This subdomain is already taken.`, + [WorkspaceExceptionCode.SUBDOMAIN_NOT_VALID]: msg`Invalid subdomain.`, + [WorkspaceExceptionCode.DOMAIN_ALREADY_TAKEN]: msg`This domain is already taken.`, + [WorkspaceExceptionCode.WORKSPACE_NOT_FOUND]: msg`Workspace not found.`, + [WorkspaceExceptionCode.WORKSPACE_CUSTOM_DOMAIN_DISABLED]: msg`Custom domains are disabled for this workspace.`, + [WorkspaceExceptionCode.ENVIRONMENT_VAR_NOT_ENABLED]: msg`This feature is not enabled.`, + [WorkspaceExceptionCode.CUSTOM_DOMAIN_NOT_FOUND]: msg`Custom domain not found.`, +}; + +export class WorkspaceException extends CustomException { + constructor( + message: string, + code: WorkspaceExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? workspaceExceptionUserFriendlyMessages[code], + }); + } +} + export const WorkspaceNotFoundDefaultError = new WorkspaceException( 'Workspace not found', WorkspaceExceptionCode.WORKSPACE_NOT_FOUND, diff --git a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.exception.ts b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.exception.ts index a554cbad68..b3dd060cad 100644 --- a/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/ai/ai-agent/agent.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class AgentException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum AgentExceptionCode { AGENT_NOT_FOUND = 'AGENT_NOT_FOUND', @@ -13,3 +14,31 @@ export enum AgentExceptionCode { AGENT_ALREADY_EXISTS = 'AGENT_ALREADY_EXISTS', AGENT_IS_STANDARD = 'AGENT_IS_STANDARD', } + +const agentExceptionUserFriendlyMessages: Record< + AgentExceptionCode, + MessageDescriptor +> = { + [AgentExceptionCode.AGENT_NOT_FOUND]: msg`Agent not found.`, + [AgentExceptionCode.AGENT_EXECUTION_FAILED]: msg`Agent execution failed.`, + [AgentExceptionCode.API_KEY_NOT_CONFIGURED]: msg`API key is not configured.`, + [AgentExceptionCode.USER_WORKSPACE_ID_NOT_FOUND]: msg`User workspace not found.`, + [AgentExceptionCode.ROLE_NOT_FOUND]: msg`Role not found.`, + [AgentExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_AGENTS]: msg`This role cannot be assigned to agents.`, + [AgentExceptionCode.INVALID_AGENT_INPUT]: msg`Invalid agent input.`, + [AgentExceptionCode.AGENT_ALREADY_EXISTS]: msg`An agent with this name already exists.`, + [AgentExceptionCode.AGENT_IS_STANDARD]: msg`Standard agents cannot be modified.`, +}; + +export class AgentException extends CustomException { + constructor( + message: string, + code: AgentExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? agentExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/exceptions/cron-trigger.exception.ts b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/exceptions/cron-trigger.exception.ts index 06dae41062..414f656136 100644 --- a/packages/twenty-server/src/engine/metadata-modules/cron-trigger/exceptions/cron-trigger.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/cron-trigger/exceptions/cron-trigger.exception.ts @@ -1,13 +1,32 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class CronTriggerException extends CustomException { - constructor(message: string, code: CronTriggerExceptionCode) { - super(message, code); - } -} +import { CustomException } from 'src/utils/custom-exception'; export enum CronTriggerExceptionCode { CRON_TRIGGER_NOT_FOUND = 'CRON_TRIGGER_NOT_FOUND', CRON_TRIGGER_ALREADY_EXIST = 'CRON_TRIGGER_ALREADY_EXIST', SERVERLESS_FUNCTION_NOT_FOUND = 'SERVERLESS_FUNCTION_NOT_FOUND', } + +const cronTriggerExceptionUserFriendlyMessages: Record< + CronTriggerExceptionCode, + MessageDescriptor +> = { + [CronTriggerExceptionCode.CRON_TRIGGER_NOT_FOUND]: msg`Cron trigger not found.`, + [CronTriggerExceptionCode.CRON_TRIGGER_ALREADY_EXIST]: msg`Cron trigger already exists.`, + [CronTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND]: msg`Serverless function not found.`, +}; + +export class CronTriggerException extends CustomException { + constructor( + message: string, + code: CronTriggerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? cronTriggerExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/data-source/data-source.exception.ts b/packages/twenty-server/src/engine/metadata-modules/data-source/data-source.exception.ts index 33bbb01e5d..33bf5e1dbf 100644 --- a/packages/twenty-server/src/engine/metadata-modules/data-source/data-source.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/data-source/data-source.exception.ts @@ -1,7 +1,28 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class DataSourceException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum DataSourceExceptionCode { DATA_SOURCE_NOT_FOUND = 'DATA_SOURCE_NOT_FOUND', } + +const dataSourceExceptionUserFriendlyMessages: Record< + DataSourceExceptionCode, + MessageDescriptor +> = { + [DataSourceExceptionCode.DATA_SOURCE_NOT_FOUND]: msg`Data source not found.`, +}; + +export class DataSourceException extends CustomException { + constructor( + message: string, + code: DataSourceExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? dataSourceExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/exceptions/database-event-trigger.exception.ts b/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/exceptions/database-event-trigger.exception.ts index 87f3d8b0f7..bc9a61f2fc 100644 --- a/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/exceptions/database-event-trigger.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/database-event-trigger/exceptions/database-event-trigger.exception.ts @@ -1,10 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class DatabaseEventTriggerException extends CustomException { - constructor(message: string, code: DatabaseEventTriggerExceptionCode) { - super(message, code); - } -} +import { CustomException } from 'src/utils/custom-exception'; export enum DatabaseEventTriggerExceptionCode { DATABASE_EVENT_TRIGGER_NOT_FOUND = 'DATABASE_EVENT_TRIGGER_NOT_FOUND', @@ -12,3 +9,27 @@ export enum DatabaseEventTriggerExceptionCode { DATABASE_EVENT_TRIGGER_INVALID = 'DATABASE_EVENT_TRIGGER_INVALID', SERVERLESS_FUNCTION_NOT_FOUND = 'SERVERLESS_FUNCTION_NOT_FOUND', } + +const databaseEventTriggerExceptionUserFriendlyMessages: Record< + DatabaseEventTriggerExceptionCode, + MessageDescriptor +> = { + [DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_NOT_FOUND]: msg`Database event trigger not found.`, + [DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_ALREADY_EXIST]: msg`Database event trigger already exists.`, + [DatabaseEventTriggerExceptionCode.DATABASE_EVENT_TRIGGER_INVALID]: msg`Invalid database event trigger.`, + [DatabaseEventTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND]: msg`Serverless function not found.`, +}; + +export class DatabaseEventTriggerException extends CustomException { + constructor( + message: string, + code: DatabaseEventTriggerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + databaseEventTriggerExceptionUserFriendlyMessages[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 314bc65ac6..f446b49b69 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,12 +1,11 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { appendCommonExceptionCode, CustomException, } from 'src/utils/custom-exception'; -export class FieldMetadataException extends CustomException< - keyof typeof FieldMetadataExceptionCode -> {} - export const FieldMetadataExceptionCode = appendCommonExceptionCode({ FIELD_METADATA_NOT_FOUND: 'FIELD_METADATA_NOT_FOUND', INVALID_FIELD_INPUT: 'INVALID_FIELD_INPUT', @@ -27,3 +26,37 @@ export const FieldMetadataExceptionCode = appendCommonExceptionCode({ // eslint-disable-next-line no-redeclare export type FieldMetadataExceptionCode = (typeof FieldMetadataExceptionCode)[keyof typeof FieldMetadataExceptionCode]; + +const fieldMetadataExceptionUserFriendlyMessages: Record< + keyof typeof FieldMetadataExceptionCode, + MessageDescriptor +> = { + FIELD_METADATA_NOT_FOUND: msg`Field not found.`, + INVALID_FIELD_INPUT: msg`Invalid field input.`, + FIELD_MUTATION_NOT_ALLOWED: msg`This field cannot be modified.`, + FIELD_ALREADY_EXISTS: msg`A field with this name already exists.`, + OBJECT_METADATA_NOT_FOUND: msg`Object not found.`, + FIELD_METADATA_RELATION_NOT_ENABLED: msg`Relation is not enabled for this field.`, + FIELD_METADATA_RELATION_MALFORMED: msg`Relation configuration is invalid.`, + LABEL_IDENTIFIER_FIELD_METADATA_ID_NOT_FOUND: msg`Label identifier field not found.`, + UNCOVERED_FIELD_METADATA_TYPE_VALIDATION: msg`Field type validation error.`, + RESERVED_KEYWORD: msg`This name is a reserved keyword.`, + NOT_AVAILABLE: msg`This field name is not available.`, + NAME_NOT_SYNCED_WITH_LABEL: msg`Field name is not synced with label.`, + INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`, +}; + +export class FieldMetadataException extends CustomException< + keyof typeof FieldMetadataExceptionCode +> { + constructor( + message: string, + code: keyof typeof FieldMetadataExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? fieldMetadataExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception.ts b/packages/twenty-server/src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception.ts index 6256c9aa74..e437ad7e45 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-entity/exceptions/flat-entity-maps.exception.ts @@ -1,14 +1,39 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { appendCommonExceptionCode, CustomException, } from 'src/utils/custom-exception'; -export class FlatEntityMapsException extends CustomException< - keyof typeof FlatEntityMapsExceptionCode -> {} - export const FlatEntityMapsExceptionCode = appendCommonExceptionCode({ ENTITY_ALREADY_EXISTS: 'ENTITY_ALREADY_EXISTS', ENTITY_NOT_FOUND: 'ENTITY_NOT_FOUND', ENTITY_MALFORMED: 'ENTITY_MALFORMED', } as const); + +const flatEntityMapsExceptionUserFriendlyMessages: Record< + keyof typeof FlatEntityMapsExceptionCode, + MessageDescriptor +> = { + ENTITY_ALREADY_EXISTS: msg`Entity already exists.`, + ENTITY_NOT_FOUND: msg`Entity not found.`, + ENTITY_MALFORMED: msg`Entity data is malformed.`, + INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`, +}; + +export class FlatEntityMapsException extends CustomException< + keyof typeof FlatEntityMapsExceptionCode +> { + constructor( + message: string, + code: keyof typeof FlatEntityMapsExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + flatEntityMapsExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-field-metadata.exception.ts b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-field-metadata.exception.ts index a1464f1f94..d1f6b3a243 100644 --- a/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-field-metadata.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/index-metadata/index-field-metadata.exception.ts @@ -1,15 +1,18 @@ import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; import { CustomException } from 'src/utils/custom-exception'; -export class IndexMetadataException extends CustomException { - declare code: IndexMetadataExceptionCode; +export class IndexMetadataException extends CustomException { constructor( message: string, code: IndexMetadataExceptionCode, { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { - super(message, code, { userFriendlyMessage }); + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? msg`An index metadata error occurred.`, + }); } } 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 e24e6b3af0..813a8671f6 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'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class ObjectMetadataException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum ObjectMetadataExceptionCode { OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND', @@ -12,3 +13,31 @@ export enum ObjectMetadataExceptionCode { INTERNAL_SERVER_ERROR = 'INTERNAL_SERVER_ERROR', NAME_CONFLICT = 'NAME_CONFLICT', } + +const objectMetadataExceptionUserFriendlyMessages: Record< + ObjectMetadataExceptionCode, + MessageDescriptor +> = { + [ObjectMetadataExceptionCode.OBJECT_METADATA_NOT_FOUND]: msg`Object not found.`, + [ObjectMetadataExceptionCode.INVALID_OBJECT_INPUT]: msg`Invalid object input.`, + [ObjectMetadataExceptionCode.OBJECT_MUTATION_NOT_ALLOWED]: msg`This object cannot be modified.`, + [ObjectMetadataExceptionCode.OBJECT_ALREADY_EXISTS]: msg`An object with this name already exists.`, + [ObjectMetadataExceptionCode.MISSING_CUSTOM_OBJECT_DEFAULT_LABEL_IDENTIFIER_FIELD]: msg`Custom object is missing a label identifier field.`, + [ObjectMetadataExceptionCode.INVALID_ORM_OUTPUT]: msg`Invalid data format.`, + [ObjectMetadataExceptionCode.INTERNAL_SERVER_ERROR]: msg`An unexpected error occurred.`, + [ObjectMetadataExceptionCode.NAME_CONFLICT]: msg`A name conflict occurred.`, +}; + +export class ObjectMetadataException extends CustomException { + constructor( + message: string, + code: ObjectMetadataExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + objectMetadataExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception.ts index a92f28cb6e..fd651a47aa 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/exceptions/page-layout-tab.exception.ts @@ -1,3 +1,5 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; @@ -15,7 +17,26 @@ export enum PageLayoutTabExceptionMessageKey { PAGE_LAYOUT_TAB_NOT_DELETED = 'PAGE_LAYOUT_TAB_NOT_DELETED', } -export class PageLayoutTabException extends CustomException {} +const pageLayoutTabExceptionUserFriendlyMessages: Record< + PageLayoutTabExceptionCode, + MessageDescriptor +> = { + [PageLayoutTabExceptionCode.PAGE_LAYOUT_TAB_NOT_FOUND]: msg`Page layout tab not found.`, + [PageLayoutTabExceptionCode.INVALID_PAGE_LAYOUT_TAB_DATA]: msg`Invalid page layout tab data.`, +}; + +export class PageLayoutTabException extends CustomException { + constructor( + message: string, + code: PageLayoutTabExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? pageLayoutTabExceptionUserFriendlyMessages[code], + }); + } +} export const generatePageLayoutTabExceptionMessage = ( key: PageLayoutTabExceptionMessageKey, diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception.ts index 4d861426ed..f8d2e82000 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/exceptions/page-layout-widget.exception.ts @@ -1,3 +1,5 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; @@ -18,7 +20,27 @@ export enum PageLayoutWidgetExceptionMessageKey { INVALID_WIDGET_CONFIGURATION = 'INVALID_WIDGET_CONFIGURATION', } -export class PageLayoutWidgetException extends CustomException {} +const pageLayoutWidgetExceptionUserFriendlyMessages: Record< + PageLayoutWidgetExceptionCode, + MessageDescriptor +> = { + [PageLayoutWidgetExceptionCode.PAGE_LAYOUT_WIDGET_NOT_FOUND]: msg`Page layout widget not found.`, + [PageLayoutWidgetExceptionCode.INVALID_PAGE_LAYOUT_WIDGET_DATA]: msg`Invalid page layout widget data.`, +}; + +export class PageLayoutWidgetException extends CustomException { + constructor( + message: string, + code: PageLayoutWidgetExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + pageLayoutWidgetExceptionUserFriendlyMessages[code], + }); + } +} export const generatePageLayoutWidgetExceptionMessage = ( key: PageLayoutWidgetExceptionMessageKey, diff --git a/packages/twenty-server/src/engine/metadata-modules/page-layout/exceptions/page-layout.exception.ts b/packages/twenty-server/src/engine/metadata-modules/page-layout/exceptions/page-layout.exception.ts index 96a5940dc1..28d7422ebd 100644 --- a/packages/twenty-server/src/engine/metadata-modules/page-layout/exceptions/page-layout.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/page-layout/exceptions/page-layout.exception.ts @@ -1,3 +1,5 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; @@ -14,7 +16,27 @@ export enum PageLayoutExceptionMessageKey { TAB_NOT_FOUND_FOR_WIDGET_DUPLICATION = 'TAB_NOT_FOUND_FOR_WIDGET_DUPLICATION', } -export class PageLayoutException extends CustomException {} +const pageLayoutExceptionUserFriendlyMessages: Record< + PageLayoutExceptionCode, + MessageDescriptor +> = { + [PageLayoutExceptionCode.PAGE_LAYOUT_NOT_FOUND]: msg`Page layout not found.`, + [PageLayoutExceptionCode.INVALID_PAGE_LAYOUT_DATA]: msg`Invalid page layout data.`, + [PageLayoutExceptionCode.TAB_NOT_FOUND_FOR_WIDGET_DUPLICATION]: msg`Tab not found for widget duplication.`, +}; + +export class PageLayoutException extends CustomException { + constructor( + message: string, + code: PageLayoutExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? pageLayoutExceptionUserFriendlyMessages[code], + }); + } +} export const generatePageLayoutExceptionMessage = ( key: PageLayoutExceptionMessageKey, 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 15d1f4db69..7f46e2eda9 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,12 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -// TODO: It would be usefull to enable typed message like below. More refactorisation is necessary to use it. -// export class PermissionsException extends CustomException< -// PermissionsExceptionCode, -// false, -// PermissionsExceptionMessage -// > {} -export class PermissionsException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum PermissionsExceptionCode { PERMISSION_DENIED = 'PERMISSION_DENIED', @@ -53,6 +48,67 @@ export enum PermissionsExceptionCode { ROLE_CANNOT_BE_ASSIGNED_TO_USERS = 'ROLE_CANNOT_BE_ASSIGNED_TO_USERS', } +const permissionsExceptionUserFriendlyMessages: Record< + PermissionsExceptionCode, + MessageDescriptor +> = { + [PermissionsExceptionCode.PERMISSION_DENIED]: msg`You do not have permission to perform this action.`, + [PermissionsExceptionCode.ADMIN_ROLE_NOT_FOUND]: msg`Admin role not found.`, + [PermissionsExceptionCode.USER_WORKSPACE_NOT_FOUND]: msg`User workspace not found.`, + [PermissionsExceptionCode.WORKSPACE_ID_ROLE_USER_WORKSPACE_MISMATCH]: msg`Workspace ID and role mismatch.`, + [PermissionsExceptionCode.TOO_MANY_ADMIN_CANDIDATES]: msg`Too many admin candidates found.`, + [PermissionsExceptionCode.USER_WORKSPACE_ALREADY_HAS_ROLE]: msg`User already has a role assigned.`, + [PermissionsExceptionCode.WORKSPACE_MEMBER_NOT_FOUND]: msg`Workspace member not found.`, + [PermissionsExceptionCode.ROLE_NOT_FOUND]: msg`Role not found.`, + [PermissionsExceptionCode.CANNOT_UNASSIGN_LAST_ADMIN]: msg`Cannot remove the last admin from the workspace.`, + [PermissionsExceptionCode.CANNOT_DELETE_LAST_ADMIN_USER]: msg`Cannot delete the last admin user.`, + [PermissionsExceptionCode.UNKNOWN_OPERATION_NAME]: msg`Unknown operation.`, + [PermissionsExceptionCode.UNKNOWN_REQUIRED_PERMISSION]: msg`Unknown permission required.`, + [PermissionsExceptionCode.CANNOT_UPDATE_SELF_ROLE]: msg`You cannot update your own role.`, + [PermissionsExceptionCode.NO_ROLE_FOUND_FOR_USER_WORKSPACE]: msg`No role found for this user in the workspace.`, + [PermissionsExceptionCode.API_KEY_ROLE_NOT_FOUND]: msg`API key role not found.`, + [PermissionsExceptionCode.NO_AUTHENTICATION_CONTEXT]: msg`Authentication is required.`, + [PermissionsExceptionCode.INVALID_ARG]: msg`Invalid argument provided.`, + [PermissionsExceptionCode.ROLE_LABEL_ALREADY_EXISTS]: msg`A role with this label already exists.`, + [PermissionsExceptionCode.DEFAULT_ROLE_NOT_FOUND]: msg`Default role not found.`, + [PermissionsExceptionCode.OBJECT_METADATA_NOT_FOUND]: msg`Object metadata not found.`, + [PermissionsExceptionCode.INVALID_SETTING]: msg`Invalid permission setting.`, + [PermissionsExceptionCode.ROLE_NOT_EDITABLE]: msg`This role cannot be edited.`, + [PermissionsExceptionCode.DEFAULT_ROLE_CANNOT_BE_DELETED]: msg`The default role cannot be deleted.`, + [PermissionsExceptionCode.NO_PERMISSIONS_FOUND_IN_DATASOURCE]: msg`No permissions found in datasource.`, + [PermissionsExceptionCode.CANNOT_ADD_OBJECT_PERMISSION_ON_SYSTEM_OBJECT]: msg`Cannot add permissions on system objects.`, + [PermissionsExceptionCode.CANNOT_ADD_FIELD_PERMISSION_ON_SYSTEM_OBJECT]: msg`Cannot add field permissions on system objects.`, + [PermissionsExceptionCode.METHOD_NOT_ALLOWED]: msg`This method is not allowed.`, + [PermissionsExceptionCode.RAW_SQL_NOT_ALLOWED]: msg`Raw SQL queries are not allowed.`, + [PermissionsExceptionCode.CANNOT_GIVE_WRITING_PERMISSION_ON_NON_READABLE_OBJECT]: msg`Cannot give write permission on non-readable objects.`, + [PermissionsExceptionCode.CANNOT_GIVE_WRITING_PERMISSION_WITHOUT_READING_PERMISSION]: msg`Cannot give write permission without read permission.`, + [PermissionsExceptionCode.FIELD_METADATA_NOT_FOUND]: msg`Field metadata not found.`, + [PermissionsExceptionCode.ONLY_FIELD_RESTRICTION_ALLOWED]: msg`Only field restrictions are allowed.`, + [PermissionsExceptionCode.FIELD_RESTRICTION_ONLY_ALLOWED_ON_READABLE_OBJECT]: msg`Field restrictions only apply to readable objects.`, + [PermissionsExceptionCode.FIELD_RESTRICTION_ON_UPDATE_ONLY_ALLOWED_ON_UPDATABLE_OBJECT]: msg`Update field restrictions only apply to updatable objects.`, + [PermissionsExceptionCode.UPSERT_FIELD_PERMISSION_FAILED]: msg`Failed to update field permission.`, + [PermissionsExceptionCode.PERMISSION_NOT_FOUND]: msg`Permission not found.`, + [PermissionsExceptionCode.OBJECT_PERMISSION_NOT_FOUND]: msg`Object permission not found.`, + [PermissionsExceptionCode.EMPTY_FIELD_PERMISSION_NOT_ALLOWED]: msg`Empty field permissions are not allowed.`, + [PermissionsExceptionCode.JOIN_COLUMN_NAME_REQUIRED]: msg`Join column name is required.`, + [PermissionsExceptionCode.COMPOSITE_TYPE_NOT_FOUND]: msg`Composite type not found.`, + [PermissionsExceptionCode.ROLE_MUST_HAVE_AT_LEAST_ONE_TARGET]: msg`Role must have at least one target.`, + [PermissionsExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_USERS]: msg`This role cannot be assigned to users.`, +}; + +export class PermissionsException extends CustomException { + constructor( + message: string, + code: PermissionsExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? permissionsExceptionUserFriendlyMessages[code], + }); + } +} + export enum PermissionsExceptionMessage { PERMISSION_DENIED = 'Entity performing the request does not have permission', USER_WORKSPACE_NOT_FOUND = 'User workspace not found', 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 ada14e709b..dfb61eb1fb 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'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class RemoteServerException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum RemoteServerExceptionCode { REMOTE_SERVER_NOT_FOUND = 'REMOTE_SERVER_NOT_FOUND', @@ -9,3 +10,27 @@ export enum RemoteServerExceptionCode { REMOTE_SERVER_CONNECTION_ERROR = 'REMOTE_SERVER_CONNECTION_ERROR', INVALID_REMOTE_SERVER_INPUT = 'INVALID_REMOTE_SERVER_INPUT', } + +const remoteServerExceptionUserFriendlyMessages: Record< + RemoteServerExceptionCode, + MessageDescriptor +> = { + [RemoteServerExceptionCode.REMOTE_SERVER_NOT_FOUND]: msg`Remote server not found.`, + [RemoteServerExceptionCode.REMOTE_SERVER_ALREADY_EXISTS]: msg`Remote server already exists.`, + [RemoteServerExceptionCode.REMOTE_SERVER_MUTATION_NOT_ALLOWED]: msg`This remote server cannot be modified.`, + [RemoteServerExceptionCode.REMOTE_SERVER_CONNECTION_ERROR]: msg`Failed to connect to remote server.`, + [RemoteServerExceptionCode.INVALID_REMOTE_SERVER_INPUT]: msg`Invalid remote server input.`, +}; + +export class RemoteServerException extends CustomException { + constructor( + message: string, + code: RemoteServerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? remoteServerExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.exception.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.exception.ts index 821a3f4f99..4aa6629284 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/distant-table/distant-table.exception.ts @@ -1,12 +1,34 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { appendCommonExceptionCode, CustomException, } from 'src/utils/custom-exception'; -export class DistantTableException extends CustomException< - keyof typeof DistantTableExceptionCode -> {} - export const DistantTableExceptionCode = appendCommonExceptionCode({ TIMEOUT_ERROR: 'TIMEOUT_ERROR', } as const); + +const distantTableExceptionUserFriendlyMessages: Record< + keyof typeof DistantTableExceptionCode, + MessageDescriptor +> = { + TIMEOUT_ERROR: msg`Request timed out.`, + INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`, +}; + +export class DistantTableException extends CustomException< + keyof typeof DistantTableExceptionCode +> { + constructor( + message: string, + code: keyof typeof DistantTableExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? distantTableExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.exception.ts b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.exception.ts index 8cad5b875d..22a8212de3 100644 --- a/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/remote-server/remote-table/foreign-table/foreign-table.exception.ts @@ -1,8 +1,30 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class ForeignTableException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum ForeignTableExceptionCode { FOREIGN_TABLE_MUTATION_NOT_ALLOWED = 'FOREIGN_TABLE_MUTATION_NOT_ALLOWED', INVALID_FOREIGN_TABLE_INPUT = 'INVALID_FOREIGN_TABLE_INPUT', } + +const foreignTableExceptionUserFriendlyMessages: Record< + ForeignTableExceptionCode, + MessageDescriptor +> = { + [ForeignTableExceptionCode.FOREIGN_TABLE_MUTATION_NOT_ALLOWED]: msg`This foreign table cannot be modified.`, + [ForeignTableExceptionCode.INVALID_FOREIGN_TABLE_INPUT]: msg`Invalid foreign table input.`, +}; + +export class ForeignTableException extends CustomException { + constructor( + message: string, + code: ForeignTableExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? foreignTableExceptionUserFriendlyMessages[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 6004a6855d..94e146bb6d 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'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class RemoteTableException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum RemoteTableExceptionCode { REMOTE_TABLE_NOT_FOUND = 'REMOTE_TABLE_NOT_FOUND', @@ -10,3 +11,28 @@ export enum RemoteTableExceptionCode { NO_OBJECT_METADATA_FOUND = 'NO_OBJECT_METADATA_FOUND', NO_FIELD_METADATA_FOUND = 'NO_FIELD_METADATA_FOUND', } + +const remoteTableExceptionUserFriendlyMessages: Record< + RemoteTableExceptionCode, + MessageDescriptor +> = { + [RemoteTableExceptionCode.REMOTE_TABLE_NOT_FOUND]: msg`Remote table not found.`, + [RemoteTableExceptionCode.INVALID_REMOTE_TABLE_INPUT]: msg`Invalid remote table input.`, + [RemoteTableExceptionCode.REMOTE_TABLE_ALREADY_EXISTS]: msg`Remote table already exists.`, + [RemoteTableExceptionCode.NO_FOREIGN_TABLES_FOUND]: msg`No foreign tables found.`, + [RemoteTableExceptionCode.NO_OBJECT_METADATA_FOUND]: msg`Object metadata not found.`, + [RemoteTableExceptionCode.NO_FIELD_METADATA_FOUND]: msg`Field metadata not found.`, +}; + +export class RemoteTableException extends CustomException { + constructor( + message: string, + code: RemoteTableExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? remoteTableExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/role/exceptions/role-target.exception.ts b/packages/twenty-server/src/engine/metadata-modules/role/exceptions/role-target.exception.ts index d750512afc..5ec4856cc1 100644 --- a/packages/twenty-server/src/engine/metadata-modules/role/exceptions/role-target.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/role/exceptions/role-target.exception.ts @@ -1,11 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class RoleTargetException extends CustomException { - code: RoleTargetExceptionCode; - constructor(message: string, code: RoleTargetExceptionCode) { - super(message, code); - } -} +import { CustomException } from 'src/utils/custom-exception'; export enum RoleTargetExceptionCode { ROLE_TARGET_NOT_FOUND = 'ROLE_TARGET_NOT_FOUND', @@ -14,3 +10,27 @@ export enum RoleTargetExceptionCode { ROLE_CANNOT_BE_ASSIGNED_TO_ENTITY = 'ROLE_CANNOT_BE_ASSIGNED_TO_ENTITY', ROLE_NOT_FOUND = 'ROLE_NOT_FOUND', } + +const roleTargetExceptionUserFriendlyMessages: Record< + RoleTargetExceptionCode, + MessageDescriptor +> = { + [RoleTargetExceptionCode.ROLE_TARGET_NOT_FOUND]: msg`Role target not found.`, + [RoleTargetExceptionCode.INVALID_ROLE_TARGET_DATA]: msg`Invalid role target data.`, + [RoleTargetExceptionCode.ROLE_TARGET_MISSING_IDENTIFIER]: msg`Role target is missing identifier.`, + [RoleTargetExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_ENTITY]: msg`Role cannot be assigned to this entity.`, + [RoleTargetExceptionCode.ROLE_NOT_FOUND]: msg`Role not found.`, +}; + +export class RoleTargetException extends CustomException { + constructor( + message: string, + code: RoleTargetExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? roleTargetExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/route-trigger/exceptions/route-trigger.exception.ts b/packages/twenty-server/src/engine/metadata-modules/route-trigger/exceptions/route-trigger.exception.ts index 560f60bfb9..e67b29e38d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/route-trigger/exceptions/route-trigger.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/route-trigger/exceptions/route-trigger.exception.ts @@ -1,11 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class RouteTriggerException extends CustomException { - code: RouteTriggerExceptionCode; - constructor(message: string, code: RouteTriggerExceptionCode) { - super(message, code); - } -} +import { CustomException } from 'src/utils/custom-exception'; export enum RouteTriggerExceptionCode { WORKSPACE_NOT_FOUND = 'WORKSPACE_NOT_FOUND', @@ -17,3 +13,30 @@ export enum RouteTriggerExceptionCode { FORBIDDEN_EXCEPTION = 'FORBIDDEN_EXCEPTION', SERVERLESS_FUNCTION_EXECUTION_ERROR = 'SERVERLESS_FUNCTION_EXECUTION_ERROR', } + +const routeTriggerExceptionUserFriendlyMessages: Record< + RouteTriggerExceptionCode, + MessageDescriptor +> = { + [RouteTriggerExceptionCode.WORKSPACE_NOT_FOUND]: msg`Workspace not found.`, + [RouteTriggerExceptionCode.ROUTE_NOT_FOUND]: msg`Route not found.`, + [RouteTriggerExceptionCode.TRIGGER_NOT_FOUND]: msg`Trigger not found.`, + [RouteTriggerExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND]: msg`Serverless function not found.`, + [RouteTriggerExceptionCode.ROUTE_ALREADY_EXIST]: msg`Route already exists.`, + [RouteTriggerExceptionCode.ROUTE_PATH_ALREADY_EXIST]: msg`Route path already exists.`, + [RouteTriggerExceptionCode.FORBIDDEN_EXCEPTION]: msg`You do not have permission to perform this action.`, + [RouteTriggerExceptionCode.SERVERLESS_FUNCTION_EXECUTION_ERROR]: msg`Serverless function execution failed.`, +}; + +export class RouteTriggerException extends CustomException { + constructor( + message: string, + code: RouteTriggerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? routeTriggerExceptionUserFriendlyMessages[code], + }); + } +} 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 01286903c0..b151df2786 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'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class ServerlessFunctionException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum ServerlessFunctionExceptionCode { SERVERLESS_FUNCTION_NOT_FOUND = 'SERVERLESS_FUNCTION_NOT_FOUND', @@ -13,3 +14,32 @@ export enum ServerlessFunctionExceptionCode { SERVERLESS_FUNCTION_CREATE_FAILED = 'SERVERLESS_FUNCTION_CREATE_FAILED', SERVERLESS_FUNCTION_EXECUTION_TIMEOUT = 'SERVERLESS_FUNCTION_EXECUTION_TIMEOUT', } + +const serverlessFunctionExceptionUserFriendlyMessages: Record< + ServerlessFunctionExceptionCode, + MessageDescriptor +> = { + [ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND]: msg`Function not found.`, + [ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_VERSION_NOT_FOUND]: msg`Function version not found.`, + [ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_ALREADY_EXIST]: msg`A function with this name already exists.`, + [ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_NOT_READY]: msg`Function is not ready.`, + [ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_BUILDING]: msg`Function is currently building.`, + [ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_CODE_UNCHANGED]: msg`Function code is unchanged.`, + [ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_LIMIT_REACHED]: msg`Function execution limit reached.`, + [ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_CREATE_FAILED]: msg`Failed to create function.`, + [ServerlessFunctionExceptionCode.SERVERLESS_FUNCTION_EXECUTION_TIMEOUT]: msg`Function execution timed out.`, +}; + +export class ServerlessFunctionException extends CustomException { + constructor( + message: string, + code: ServerlessFunctionExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + serverlessFunctionExceptionUserFriendlyMessages[code], + }); + } +} 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 8b0c512fc7..13cc1d1e5e 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,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class InvalidMetadataException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum InvalidMetadataExceptionCode { LABEL_REQUIRED = 'Label required', @@ -13,3 +14,32 @@ export enum InvalidMetadataExceptionCode { INVALID_STRING = 'Invalid string', NOT_AVAILABLE = 'Name not available', } + +const invalidMetadataExceptionUserFriendlyMessages: Record< + InvalidMetadataExceptionCode, + MessageDescriptor +> = { + [InvalidMetadataExceptionCode.LABEL_REQUIRED]: msg`Label is required.`, + [InvalidMetadataExceptionCode.INPUT_TOO_SHORT]: msg`Input is too short.`, + [InvalidMetadataExceptionCode.EXCEEDS_MAX_LENGTH]: msg`Input exceeds maximum length.`, + [InvalidMetadataExceptionCode.RESERVED_KEYWORD]: msg`This name is a reserved keyword.`, + [InvalidMetadataExceptionCode.NOT_CAMEL_CASE]: msg`Name must be in camelCase format.`, + [InvalidMetadataExceptionCode.INVALID_LABEL]: msg`Invalid label format.`, + [InvalidMetadataExceptionCode.NAME_NOT_SYNCED_WITH_LABEL]: msg`Name is not synced with label.`, + [InvalidMetadataExceptionCode.INVALID_STRING]: msg`Invalid string format.`, + [InvalidMetadataExceptionCode.NOT_AVAILABLE]: msg`This name is not available.`, +}; + +export class InvalidMetadataException extends CustomException { + constructor( + message: string, + code: InvalidMetadataExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + invalidMetadataExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field/exceptions/view-field.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view-field/exceptions/view-field.exception.ts index 23515b7e57..e51d3e86d2 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field/exceptions/view-field.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field/exceptions/view-field.exception.ts @@ -4,14 +4,16 @@ import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; -export class ViewFieldException extends CustomException { - declare code: ViewFieldExceptionCode; +export class ViewFieldException extends CustomException { constructor( message: string, code: ViewFieldExceptionCode, { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { - super(message, code, { userFriendlyMessage }); + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? msg`A view field error occurred.`, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-field/filters/view-field-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view-field/filters/view-field-rest-api-exception.filter.ts index 57b82b31b9..7ee6912017 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-field/filters/view-field-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-field/filters/view-field-rest-api-exception.filter.ts @@ -5,6 +5,7 @@ import { Injectable, } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; import { type Response } from 'express'; import { SOURCE_LOCALE } from 'twenty-shared/translations'; @@ -81,6 +82,7 @@ export class ViewFieldRestApiExceptionFilter implements ExceptionFilter { const unknownException = new UnknownException( 'Internal server error', 'INTERNAL_ERROR', + { userFriendlyMessage: msg`An unexpected error occurred.` }, ); return this.httpExceptionHandlerService.handleError( diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception.ts index 7dccff63ed..1238924fcb 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/exceptions/view-filter-group.exception.ts @@ -4,14 +4,16 @@ import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; -export class ViewFilterGroupException extends CustomException { - declare code: ViewFilterGroupExceptionCode; +export class ViewFilterGroupException extends CustomException { constructor( message: string, code: ViewFilterGroupExceptionCode, { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { - super(message, code, { userFriendlyMessage }); + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? msg`A view filter group error occurred.`, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/filters/view-filter-group-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/filters/view-filter-group-rest-api-exception.filter.ts index 31848bb388..190ea08d5d 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter-group/filters/view-filter-group-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter-group/filters/view-filter-group-rest-api-exception.filter.ts @@ -5,6 +5,7 @@ import { Injectable, } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; import { type Response } from 'express'; import { SOURCE_LOCALE } from 'twenty-shared/translations'; @@ -81,6 +82,7 @@ export class ViewFilterGroupRestApiExceptionFilter implements ExceptionFilter { const unknownException = new UnknownException( 'Internal server error', 'INTERNAL_ERROR', + { userFriendlyMessage: msg`An unexpected error occurred.` }, ); return this.httpExceptionHandlerService.handleError( diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter/exceptions/view-filter.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter/exceptions/view-filter.exception.ts index 752b9d3aa0..3d36aacbfe 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter/exceptions/view-filter.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter/exceptions/view-filter.exception.ts @@ -4,14 +4,16 @@ import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; -export class ViewFilterException extends CustomException { - declare code: ViewFilterExceptionCode; +export class ViewFilterException extends CustomException { constructor( message: string, code: ViewFilterExceptionCode, { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { - super(message, code, { userFriendlyMessage }); + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? msg`A view filter error occurred.`, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-filter/filters/view-filter-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view-filter/filters/view-filter-rest-api-exception.filter.ts index 8b4ae2a6eb..7ab770246b 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-filter/filters/view-filter-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-filter/filters/view-filter-rest-api-exception.filter.ts @@ -5,6 +5,7 @@ import { Injectable, } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; import { type Response } from 'express'; import { SOURCE_LOCALE } from 'twenty-shared/translations'; @@ -81,6 +82,7 @@ export class ViewFilterRestApiExceptionFilter implements ExceptionFilter { const unknownException = new UnknownException( 'Internal server error', 'INTERNAL_ERROR', + { userFriendlyMessage: msg`An unexpected error occurred.` }, ); return this.httpExceptionHandlerService.handleError( diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/exceptions/view-group.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/exceptions/view-group.exception.ts index 9a60a3684f..9a2b83ed44 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/exceptions/view-group.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/exceptions/view-group.exception.ts @@ -4,14 +4,16 @@ import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; -export class ViewGroupException extends CustomException { - declare code: ViewGroupExceptionCode; +export class ViewGroupException extends CustomException { constructor( message: string, code: ViewGroupExceptionCode, { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { - super(message, code, { userFriendlyMessage }); + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? msg`A view group error occurred.`, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-group/filters/view-group-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view-group/filters/view-group-rest-api-exception.filter.ts index 14c1eefcd7..5901e2c423 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-group/filters/view-group-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-group/filters/view-group-rest-api-exception.filter.ts @@ -5,6 +5,7 @@ import { Injectable, } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; import { type Response } from 'express'; import { SOURCE_LOCALE } from 'twenty-shared/translations'; @@ -80,6 +81,7 @@ export class ViewGroupRestApiExceptionFilter implements ExceptionFilter { const unknownException = new UnknownException( 'Internal server error', 'INTERNAL_ERROR', + { userFriendlyMessage: msg`An unexpected error occurred.` }, ); return this.httpExceptionHandlerService.handleError( diff --git a/packages/twenty-server/src/engine/metadata-modules/view-sort/exceptions/view-sort.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view-sort/exceptions/view-sort.exception.ts index c57eb0c5a1..4f6136221a 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-sort/exceptions/view-sort.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-sort/exceptions/view-sort.exception.ts @@ -4,14 +4,16 @@ import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; -export class ViewSortException extends CustomException { - declare code: ViewSortExceptionCode; +export class ViewSortException extends CustomException { constructor( message: string, code: ViewSortExceptionCode, { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { - super(message, code, { userFriendlyMessage }); + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? msg`A view sort error occurred.`, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view-sort/filters/view-sort-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view-sort/filters/view-sort-rest-api-exception.filter.ts index 7a4fdfa640..d073ecd4ae 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view-sort/filters/view-sort-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view-sort/filters/view-sort-rest-api-exception.filter.ts @@ -5,6 +5,7 @@ import { Injectable, } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; import { type Response } from 'express'; import { SOURCE_LOCALE } from 'twenty-shared/translations'; @@ -81,6 +82,7 @@ export class ViewSortRestApiExceptionFilter implements ExceptionFilter { const unknownException = new UnknownException( 'Internal server error', 'INTERNAL_ERROR', + { userFriendlyMessage: msg`An unexpected error occurred.` }, ); return this.httpExceptionHandlerService.handleError( diff --git a/packages/twenty-server/src/engine/metadata-modules/view/exceptions/flat-view.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view/exceptions/flat-view.exception.ts index e28c13e61d..efc543d55e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/exceptions/flat-view.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/exceptions/flat-view.exception.ts @@ -1,13 +1,36 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { appendCommonExceptionCode, CustomException, } from 'src/utils/custom-exception'; -export class FlatViewException extends CustomException< - keyof typeof FlatViewExceptionCode -> {} - export const FlatViewExceptionCode = appendCommonExceptionCode({ VIEW_NOT_FOUND: 'VIEW_NOT_FOUND', VIEW_ALREADY_EXISTS: 'VIEW_ALREADY_EXISTS', } as const); + +const flatViewExceptionUserFriendlyMessages: Record< + keyof typeof FlatViewExceptionCode, + MessageDescriptor +> = { + VIEW_NOT_FOUND: msg`View not found.`, + VIEW_ALREADY_EXISTS: msg`View already exists.`, + INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`, +}; + +export class FlatViewException extends CustomException< + keyof typeof FlatViewExceptionCode +> { + constructor( + message: string, + code: keyof typeof FlatViewExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? flatViewExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/view/exceptions/view.exception.ts b/packages/twenty-server/src/engine/metadata-modules/view/exceptions/view.exception.ts index ce05b4d3ba..3a7b92e503 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/exceptions/view.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/exceptions/view.exception.ts @@ -4,14 +4,15 @@ import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; -export class ViewException extends CustomException { - declare code: ViewExceptionCode; +export class ViewException extends CustomException { constructor( message: string, code: ViewExceptionCode, { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, ) { - super(message, code, { userFriendlyMessage }); + super(message, code, { + userFriendlyMessage: userFriendlyMessage ?? msg`A view error occurred.`, + }); } } diff --git a/packages/twenty-server/src/engine/metadata-modules/view/filters/view-rest-api-exception.filter.ts b/packages/twenty-server/src/engine/metadata-modules/view/filters/view-rest-api-exception.filter.ts index 9ed2a96d67..79ded70881 100644 --- a/packages/twenty-server/src/engine/metadata-modules/view/filters/view-rest-api-exception.filter.ts +++ b/packages/twenty-server/src/engine/metadata-modules/view/filters/view-rest-api-exception.filter.ts @@ -5,6 +5,7 @@ import { Injectable, } from '@nestjs/common'; +import { msg } from '@lingui/core/macro'; import { type Response } from 'express'; import { SOURCE_LOCALE } from 'twenty-shared/translations'; @@ -87,6 +88,7 @@ export class ViewRestApiExceptionFilter implements ExceptionFilter { const unknownException = new UnknownException( 'Internal server error', 'INTERNAL_ERROR', + { userFriendlyMessage: msg`An unexpected error occurred.` }, ); return this.httpExceptionHandlerService.handleError( diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception.ts index 2c9ce4347f..b83f9d6ea6 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-metadata-version/exceptions/workspace-metadata-version.exception.ts @@ -1,7 +1,29 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WorkspaceMetadataVersionException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WorkspaceMetadataVersionExceptionCode { METADATA_VERSION_NOT_FOUND = 'METADATA_VERSION_NOT_FOUND', } + +const workspaceMetadataVersionExceptionUserFriendlyMessages: Record< + WorkspaceMetadataVersionExceptionCode, + MessageDescriptor +> = { + [WorkspaceMetadataVersionExceptionCode.METADATA_VERSION_NOT_FOUND]: msg`Metadata version not found.`, +}; + +export class WorkspaceMetadataVersionException extends CustomException { + constructor( + message: string, + code: WorkspaceMetadataVersionExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workspaceMetadataVersionExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.exception.ts b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.exception.ts index 84c2abd1f5..3e3df07f8e 100644 --- a/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.exception.ts +++ b/packages/twenty-server/src/engine/metadata-modules/workspace-migration/workspace-migration.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WorkspaceMigrationException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WorkspaceMigrationExceptionCode { NO_FACTORY_FOUND = 'NO_FACTORY_FOUND', @@ -9,3 +10,28 @@ export enum WorkspaceMigrationExceptionCode { INVALID_COMPOSITE_TYPE = 'INVALID_COMPOSITE_TYPE', ENUM_TYPE_NAME_NOT_FOUND = 'ENUM_TYPE_NAME_NOT_FOUND', } + +const workspaceMigrationExceptionUserFriendlyMessages: Record< + WorkspaceMigrationExceptionCode, + MessageDescriptor +> = { + [WorkspaceMigrationExceptionCode.NO_FACTORY_FOUND]: msg`Migration factory not found.`, + [WorkspaceMigrationExceptionCode.INVALID_ACTION]: msg`Invalid migration action.`, + [WorkspaceMigrationExceptionCode.INVALID_FIELD_METADATA]: msg`Invalid field metadata.`, + [WorkspaceMigrationExceptionCode.INVALID_COMPOSITE_TYPE]: msg`Invalid composite type.`, + [WorkspaceMigrationExceptionCode.ENUM_TYPE_NAME_NOT_FOUND]: msg`Enum type not found.`, +}; + +export class WorkspaceMigrationException extends CustomException { + constructor( + message: string, + code: WorkspaceMigrationExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workspaceMigrationExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/twenty-orm/exceptions/relation.exception.ts b/packages/twenty-server/src/engine/twenty-orm/exceptions/relation.exception.ts index 0cdca3e196..1f9a2a8460 100644 --- a/packages/twenty-server/src/engine/twenty-orm/exceptions/relation.exception.ts +++ b/packages/twenty-server/src/engine/twenty-orm/exceptions/relation.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class RelationException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum RelationExceptionCode { RELATION_OBJECT_METADATA_NOT_FOUND = 'RELATION_OBJECT_METADATA_NOT_FOUND', @@ -9,3 +10,27 @@ export enum RelationExceptionCode { MISSING_RELATION_JOIN_COLUMN = 'MISSING_RELATION_JOIN_COLUMN', MULTIPLE_JOIN_COLUMNS_FOUND = 'MULTIPLE_JOIN_COLUMNS_FOUND', } + +const relationExceptionUserFriendlyMessages: Record< + RelationExceptionCode, + MessageDescriptor +> = { + [RelationExceptionCode.RELATION_OBJECT_METADATA_NOT_FOUND]: msg`Relation object not found.`, + [RelationExceptionCode.RELATION_TARGET_FIELD_METADATA_ID_NOT_FOUND]: msg`Relation target field not found.`, + [RelationExceptionCode.RELATION_JOIN_COLUMN_ON_BOTH_SIDES]: msg`Relation has join column on both sides.`, + [RelationExceptionCode.MISSING_RELATION_JOIN_COLUMN]: msg`Missing relation join column.`, + [RelationExceptionCode.MULTIPLE_JOIN_COLUMNS_FOUND]: msg`Multiple join columns found.`, +}; + +export class RelationException extends CustomException { + constructor( + message: string, + code: RelationExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? relationExceptionUserFriendlyMessages[code], + }); + } +} 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 17e62a23ff..5f0b71ac65 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 @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class TwentyORMException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum TwentyORMExceptionCode { METADATA_VERSION_MISMATCH = 'METADATA_VERSION_MISMATCH', @@ -23,3 +24,41 @@ export enum TwentyORMExceptionCode { INVALID_INPUT = 'INVALID_INPUT', ORM_EVENT_DATA_CORRUPTED = 'ORM_EVENT_DATA_CORRUPTED', } + +const twentyORMExceptionUserFriendlyMessages: Record< + TwentyORMExceptionCode, + MessageDescriptor +> = { + [TwentyORMExceptionCode.METADATA_VERSION_MISMATCH]: msg`Data version mismatch. Please refresh and try again.`, + [TwentyORMExceptionCode.WORKSPACE_SCHEMA_NOT_FOUND]: msg`Workspace schema not found.`, + [TwentyORMExceptionCode.ROLES_PERMISSIONS_VERSION_NOT_FOUND]: msg`Roles and permissions configuration not found.`, + [TwentyORMExceptionCode.FEATURE_FLAG_MAP_VERSION_NOT_FOUND]: msg`Feature configuration not found.`, + [TwentyORMExceptionCode.USER_WORKSPACE_ROLE_MAP_VERSION_NOT_FOUND]: msg`User workspace role configuration not found.`, + [TwentyORMExceptionCode.API_KEY_ROLE_MAP_VERSION_NOT_FOUND]: msg`API key role configuration not found.`, + [TwentyORMExceptionCode.MALFORMED_METADATA]: msg`Data structure is invalid.`, + [TwentyORMExceptionCode.WORKSPACE_NOT_FOUND]: msg`Workspace not found.`, + [TwentyORMExceptionCode.CONNECT_RECORD_NOT_FOUND]: msg`Related record not found.`, + [TwentyORMExceptionCode.CONNECT_NOT_ALLOWED]: msg`This connection is not allowed.`, + [TwentyORMExceptionCode.CONNECT_UNIQUE_CONSTRAINT_ERROR]: msg`A record with this relationship already exists.`, + [TwentyORMExceptionCode.MISSING_MAIN_ALIAS_TARGET]: msg`Missing main alias target.`, + [TwentyORMExceptionCode.METHOD_NOT_ALLOWED]: msg`This operation is not allowed.`, + [TwentyORMExceptionCode.ENUM_TYPE_NAME_NOT_FOUND]: msg`Enum type not found.`, + [TwentyORMExceptionCode.QUERY_READ_TIMEOUT]: msg`Query timed out. Please try again.`, + [TwentyORMExceptionCode.DUPLICATE_ENTRY_DETECTED]: msg`A duplicate entry was detected.`, + [TwentyORMExceptionCode.TOO_MANY_RECORDS_TO_UPDATE]: msg`Too many records to update at once.`, + [TwentyORMExceptionCode.INVALID_INPUT]: msg`Invalid input provided.`, + [TwentyORMExceptionCode.ORM_EVENT_DATA_CORRUPTED]: msg`Event data is corrupted.`, +}; + +export class TwentyORMException extends CustomException { + constructor( + message: string, + code: TwentyORMExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? twentyORMExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts b/packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts index ca0c78bb95..8b5a35a3c7 100644 --- a/packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts +++ b/packages/twenty-server/src/engine/twenty-orm/workspace-schema-manager/exceptions/workspace-schema-manager.exception.ts @@ -1,12 +1,35 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { appendCommonExceptionCode, CustomException, } from 'src/utils/custom-exception'; -export class WorkspaceSchemaManagerException extends CustomException< - keyof typeof WorkspaceSchemaManagerExceptionCode -> {} - export const WorkspaceSchemaManagerExceptionCode = appendCommonExceptionCode({ ENUM_OPERATION_FAILED: 'ENUM_OPERATION_FAILED', } as const); + +const workspaceSchemaManagerExceptionUserFriendlyMessages: Record< + keyof typeof WorkspaceSchemaManagerExceptionCode, + MessageDescriptor +> = { + ENUM_OPERATION_FAILED: msg`Schema enum operation failed.`, + INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`, +}; + +export class WorkspaceSchemaManagerException extends CustomException< + keyof typeof WorkspaceSchemaManagerExceptionCode +> { + constructor( + message: string, + code: keyof typeof WorkspaceSchemaManagerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workspaceSchemaManagerExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/workspace-cache/exceptions/workspace-cache.exception.ts b/packages/twenty-server/src/engine/workspace-cache/exceptions/workspace-cache.exception.ts index c8b39601e6..d11a03845c 100644 --- a/packages/twenty-server/src/engine/workspace-cache/exceptions/workspace-cache.exception.ts +++ b/packages/twenty-server/src/engine/workspace-cache/exceptions/workspace-cache.exception.ts @@ -1,13 +1,37 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { appendCommonExceptionCode, CustomException, } from 'src/utils/custom-exception'; -export class WorkspaceCacheException extends CustomException< - keyof typeof WorkspaceCacheExceptionCode -> {} - export const WorkspaceCacheExceptionCode = appendCommonExceptionCode({ MISSING_DECORATOR: 'MISSING_DECORATOR', INVALID_PARAMETERS: 'INVALID_PARAMETERS', } as const); + +const workspaceCacheExceptionUserFriendlyMessages: Record< + keyof typeof WorkspaceCacheExceptionCode, + MessageDescriptor +> = { + MISSING_DECORATOR: msg`Missing decorator configuration.`, + INVALID_PARAMETERS: msg`Invalid parameters provided.`, + INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`, +}; + +export class WorkspaceCacheException extends CustomException< + keyof typeof WorkspaceCacheExceptionCode +> { + constructor( + message: string, + code: keyof typeof WorkspaceCacheExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workspaceCacheExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/exceptions/workspace-cleaner.exception.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/exceptions/workspace-cleaner.exception.ts index 36c2aa78a8..ca3a513616 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/exceptions/workspace-cleaner.exception.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/exceptions/workspace-cleaner.exception.ts @@ -1,7 +1,29 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WorkspaceCleanerException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WorkspaceCleanerExceptionCode { BILLING_SUBSCRIPTION_NOT_FOUND = 'BILLING_SUBSCRIPTION_NOT_FOUND', } + +const workspaceCleanerExceptionUserFriendlyMessages: Record< + WorkspaceCleanerExceptionCode, + MessageDescriptor +> = { + [WorkspaceCleanerExceptionCode.BILLING_SUBSCRIPTION_NOT_FOUND]: msg`Billing subscription not found.`, +}; + +export class WorkspaceCleanerException extends CustomException { + constructor( + message: string, + code: WorkspaceCleanerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workspaceCleanerExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception.ts index 2d7849f379..b53506c906 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration-v2/workspace-migration-runner-v2/exceptions/workspace-migration-runner.exception.ts @@ -1,12 +1,11 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { appendCommonExceptionCode, CustomException, } from 'src/utils/custom-exception'; -export class WorkspaceMigrationRunnerException extends CustomException< - keyof typeof WorkspaceMigrationRunnerExceptionCode -> {} - export const WorkspaceMigrationRunnerExceptionCode = appendCommonExceptionCode({ FIELD_METADATA_NOT_FOUND: 'FIELD_METADATA_NOT_FOUND', OBJECT_METADATA_NOT_FOUND: 'OBJECT_METADATA_NOT_FOUND', @@ -16,3 +15,33 @@ export const WorkspaceMigrationRunnerExceptionCode = appendCommonExceptionCode({ INVALID_ACTION_TYPE: 'INVALID_ACTION_TYPE', FLAT_ENTITY_NOT_FOUND: 'FLAT_ENTITY_NOT_FOUND', } as const); + +const workspaceMigrationRunnerExceptionUserFriendlyMessages: Record< + keyof typeof WorkspaceMigrationRunnerExceptionCode, + MessageDescriptor +> = { + FIELD_METADATA_NOT_FOUND: msg`Field metadata not found.`, + OBJECT_METADATA_NOT_FOUND: msg`Object metadata not found.`, + ENUM_OPERATION_FAILED: msg`Enum operation failed.`, + UNSUPPORTED_COMPOSITE_COLUMN_TYPE: msg`Unsupported composite column type.`, + NOT_SUPPORTED: msg`This operation is not supported.`, + INVALID_ACTION_TYPE: msg`Invalid action type.`, + FLAT_ENTITY_NOT_FOUND: msg`Entity not found.`, + INTERNAL_SERVER_ERROR: msg`An unexpected error occurred.`, +}; + +export class WorkspaceMigrationRunnerException extends CustomException< + keyof typeof WorkspaceMigrationRunnerExceptionCode +> { + constructor( + message: string, + code: keyof typeof WorkspaceMigrationRunnerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workspaceMigrationRunnerExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-migration.exception.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-migration.exception.ts index 12f68cd193..0af78b57cd 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-migration.exception.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-migration.exception.ts @@ -1,5 +1,26 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; import { type WorkspaceMigrationV2ExceptionCode } from 'twenty-shared/metadata'; import { CustomException } from 'src/utils/custom-exception'; -export class WorkspaceMigrationV2Exception extends CustomException {} +const workspaceMigrationV2ExceptionUserFriendlyMessages: Partial< + Record +> = {}; + +const defaultUserFriendlyMessage = msg`An error occurred during workspace migration.`; + +export class WorkspaceMigrationV2Exception extends CustomException { + constructor( + message: string, + code: WorkspaceMigrationV2ExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workspaceMigrationV2ExceptionUserFriendlyMessages[code] ?? + defaultUserFriendlyMessage, + }); + } +} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception.ts index a7b3b3c742..643122d83b 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class CalendarEventImportDriverException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum CalendarEventImportDriverExceptionCode { NOT_FOUND = 'NOT_FOUND', @@ -12,3 +13,31 @@ export enum CalendarEventImportDriverExceptionCode { HANDLE_ALIASES_REQUIRED = 'HANDLE_ALIASES_REQUIRED', CHANNEL_MISCONFIGURED = 'CHANNEL_MISCONFIGURED', } + +const calendarEventImportDriverExceptionUserFriendlyMessages: Record< + CalendarEventImportDriverExceptionCode, + MessageDescriptor +> = { + [CalendarEventImportDriverExceptionCode.NOT_FOUND]: msg`Calendar event not found.`, + [CalendarEventImportDriverExceptionCode.TEMPORARY_ERROR]: msg`A temporary error occurred. Please try again.`, + [CalendarEventImportDriverExceptionCode.INSUFFICIENT_PERMISSIONS]: msg`Insufficient permissions to access calendar.`, + [CalendarEventImportDriverExceptionCode.SYNC_CURSOR_ERROR]: msg`Calendar sync error.`, + [CalendarEventImportDriverExceptionCode.UNKNOWN]: msg`An unknown calendar error occurred.`, + [CalendarEventImportDriverExceptionCode.UNKNOWN_NETWORK_ERROR]: msg`A network error occurred while accessing calendar.`, + [CalendarEventImportDriverExceptionCode.HANDLE_ALIASES_REQUIRED]: msg`Handle aliases are required.`, + [CalendarEventImportDriverExceptionCode.CHANNEL_MISCONFIGURED]: msg`Calendar channel is misconfigured.`, +}; + +export class CalendarEventImportDriverException extends CustomException { + constructor( + message: string, + code: CalendarEventImportDriverExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + calendarEventImportDriverExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/exceptions/calendar-event-import.exception.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/exceptions/calendar-event-import.exception.ts index 07c02993ee..70f62d3697 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/exceptions/calendar-event-import.exception.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/exceptions/calendar-event-import.exception.ts @@ -1,8 +1,31 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class CalendarEventImportException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum CalendarEventImportExceptionCode { PROVIDER_NOT_SUPPORTED = 'PROVIDER_NOT_SUPPORTED', UNKNOWN = 'UNKNOWN', } + +const calendarEventImportExceptionUserFriendlyMessages: Record< + CalendarEventImportExceptionCode, + MessageDescriptor +> = { + [CalendarEventImportExceptionCode.PROVIDER_NOT_SUPPORTED]: msg`Calendar provider is not supported.`, + [CalendarEventImportExceptionCode.UNKNOWN]: msg`An unknown calendar error occurred.`, +}; + +export class CalendarEventImportException extends CustomException { + constructor( + message: string, + code: CalendarEventImportExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + calendarEventImportExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception.ts index 25f7c73213..f4fc0f7b60 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class ConnectedAccountRefreshAccessTokenException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum ConnectedAccountRefreshAccessTokenExceptionCode { REFRESH_TOKEN_NOT_FOUND = 'REFRESH_TOKEN_NOT_FOUND', @@ -9,3 +10,28 @@ export enum ConnectedAccountRefreshAccessTokenExceptionCode { TEMPORARY_NETWORK_ERROR = 'TEMPORARY_NETWORK_ERROR', ACCESS_TOKEN_NOT_FOUND = 'ACCESS_TOKEN_NOT_FOUND', } + +const connectedAccountRefreshAccessTokenExceptionUserFriendlyMessages: Record< + ConnectedAccountRefreshAccessTokenExceptionCode, + MessageDescriptor +> = { + [ConnectedAccountRefreshAccessTokenExceptionCode.REFRESH_TOKEN_NOT_FOUND]: msg`Refresh token not found.`, + [ConnectedAccountRefreshAccessTokenExceptionCode.INVALID_REFRESH_TOKEN]: msg`Invalid refresh token.`, + [ConnectedAccountRefreshAccessTokenExceptionCode.PROVIDER_NOT_SUPPORTED]: msg`This provider is not supported.`, + [ConnectedAccountRefreshAccessTokenExceptionCode.TEMPORARY_NETWORK_ERROR]: msg`A temporary network error occurred.`, + [ConnectedAccountRefreshAccessTokenExceptionCode.ACCESS_TOKEN_NOT_FOUND]: msg`Access token not found.`, +}; + +export class ConnectedAccountRefreshAccessTokenException extends CustomException { + constructor( + message: string, + code: ConnectedAccountRefreshAccessTokenExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + connectedAccountRefreshAccessTokenExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/modules/dashboard/exceptions/dashboard.exception.ts b/packages/twenty-server/src/modules/dashboard/exceptions/dashboard.exception.ts index d64f353a2f..837395baf9 100644 --- a/packages/twenty-server/src/modules/dashboard/exceptions/dashboard.exception.ts +++ b/packages/twenty-server/src/modules/dashboard/exceptions/dashboard.exception.ts @@ -1,3 +1,5 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; import { assertUnreachable } from 'twenty-shared/utils'; import { CustomException } from 'src/utils/custom-exception'; @@ -14,7 +16,27 @@ export enum DashboardExceptionMessageKey { PAGE_LAYOUT_NOT_FOUND = 'PAGE_LAYOUT_NOT_FOUND', } -export class DashboardException extends CustomException {} +const dashboardExceptionUserFriendlyMessages: Record< + DashboardExceptionCode, + MessageDescriptor +> = { + [DashboardExceptionCode.DASHBOARD_NOT_FOUND]: msg`Dashboard not found.`, + [DashboardExceptionCode.DASHBOARD_DUPLICATION_FAILED]: msg`Failed to duplicate dashboard.`, + [DashboardExceptionCode.PAGE_LAYOUT_NOT_FOUND]: msg`Page layout not found.`, +}; + +export class DashboardException extends CustomException { + constructor( + message: string, + code: DashboardExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? dashboardExceptionUserFriendlyMessages[code], + }); + } +} export const generateDashboardExceptionMessage = ( key: DashboardExceptionMessageKey, diff --git a/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-common.exception.ts b/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-common.exception.ts index 5d718314b4..fe1b5285f7 100644 --- a/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-common.exception.ts +++ b/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-common.exception.ts @@ -1,7 +1,29 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WorkflowCommonException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WorkflowCommonExceptionCode { OBJECT_METADATA_NOT_FOUND = 'OBJECT_METADATA_NOT_FOUND', } + +const workflowCommonExceptionUserFriendlyMessages: Record< + WorkflowCommonExceptionCode, + MessageDescriptor +> = { + [WorkflowCommonExceptionCode.OBJECT_METADATA_NOT_FOUND]: msg`Object metadata not found.`, +}; + +export class WorkflowCommonException extends CustomException { + constructor( + message: string, + code: WorkflowCommonExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workflowCommonExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-query-validation.exception.ts b/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-query-validation.exception.ts index 851a32b7f3..42d11618a8 100644 --- a/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-query-validation.exception.ts +++ b/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-query-validation.exception.ts @@ -1,7 +1,29 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WorkflowQueryValidationException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WorkflowQueryValidationExceptionCode { FORBIDDEN = 'FORBIDDEN', } + +const workflowQueryValidationExceptionUserFriendlyMessages: Record< + WorkflowQueryValidationExceptionCode, + MessageDescriptor +> = { + [WorkflowQueryValidationExceptionCode.FORBIDDEN]: msg`You do not have permission to perform this workflow action.`, +}; + +export class WorkflowQueryValidationException extends CustomException { + constructor( + message: string, + code: WorkflowQueryValidationExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workflowQueryValidationExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-version-edge.exception.ts b/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-version-edge.exception.ts index 4f7954d37c..4c34d54833 100644 --- a/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-version-edge.exception.ts +++ b/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-version-edge.exception.ts @@ -1,8 +1,31 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WorkflowVersionEdgeException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WorkflowVersionEdgeExceptionCode { NOT_FOUND = 'NOT_FOUND', INVALID_REQUEST = 'INVALID_REQUEST', } + +const workflowVersionEdgeExceptionUserFriendlyMessages: Record< + WorkflowVersionEdgeExceptionCode, + MessageDescriptor +> = { + [WorkflowVersionEdgeExceptionCode.NOT_FOUND]: msg`Workflow edge not found.`, + [WorkflowVersionEdgeExceptionCode.INVALID_REQUEST]: msg`Invalid workflow edge request.`, +}; + +export class WorkflowVersionEdgeException extends CustomException { + constructor( + message: string, + code: WorkflowVersionEdgeExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workflowVersionEdgeExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-version-step.exception.ts b/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-version-step.exception.ts index e19447ecf9..233ac5eca3 100644 --- a/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-version-step.exception.ts +++ b/packages/twenty-server/src/modules/workflow/common/exceptions/workflow-version-step.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WorkflowVersionStepException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WorkflowVersionStepExceptionCode { INVALID_REQUEST = 'INVALID_REQUEST', @@ -8,3 +9,27 @@ export enum WorkflowVersionStepExceptionCode { CODE_STEP_FAILURE = 'CODE_STEP_FAILURE', AI_AGENT_STEP_FAILURE = 'AI_AGENT_STEP_FAILURE', } + +const workflowVersionStepExceptionUserFriendlyMessages: Record< + WorkflowVersionStepExceptionCode, + MessageDescriptor +> = { + [WorkflowVersionStepExceptionCode.INVALID_REQUEST]: msg`Invalid workflow step request.`, + [WorkflowVersionStepExceptionCode.NOT_FOUND]: msg`Workflow step not found.`, + [WorkflowVersionStepExceptionCode.CODE_STEP_FAILURE]: msg`Code step execution failed.`, + [WorkflowVersionStepExceptionCode.AI_AGENT_STEP_FAILURE]: msg`AI agent step execution failed.`, +}; + +export class WorkflowVersionStepException extends CustomException { + constructor( + message: string, + code: WorkflowVersionStepExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workflowVersionStepExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception.ts b/packages/twenty-server/src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception.ts index d0d4038feb..c9760252b1 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-executor/exceptions/workflow-step-executor.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WorkflowStepExecutorException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WorkflowStepExecutorExceptionCode { SCOPED_WORKSPACE_NOT_FOUND = 'SCOPED_WORKSPACE_NOT_FOUND', @@ -8,3 +9,27 @@ export enum WorkflowStepExecutorExceptionCode { STEP_NOT_FOUND = 'STEP_NOT_FOUND', INTERNAL_ERROR = 'INTERNAL_ERROR', } + +const workflowStepExecutorExceptionUserFriendlyMessages: Record< + WorkflowStepExecutorExceptionCode, + MessageDescriptor +> = { + [WorkflowStepExecutorExceptionCode.SCOPED_WORKSPACE_NOT_FOUND]: msg`Workspace not found.`, + [WorkflowStepExecutorExceptionCode.INVALID_STEP_TYPE]: msg`Invalid workflow step type.`, + [WorkflowStepExecutorExceptionCode.STEP_NOT_FOUND]: msg`Workflow step not found.`, + [WorkflowStepExecutorExceptionCode.INTERNAL_ERROR]: msg`An unexpected workflow error occurred.`, +}; + +export class WorkflowStepExecutorException extends CustomException { + constructor( + message: string, + code: WorkflowStepExecutorExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workflowStepExecutorExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/exceptions/workflow-run.exception.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/exceptions/workflow-run.exception.ts index 5b26bf661f..e88369ef17 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/exceptions/workflow-run.exception.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/exceptions/workflow-run.exception.ts @@ -1,6 +1,7 @@ -import { CustomException } from 'src/utils/custom-exception'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WorkflowRunException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WorkflowRunExceptionCode { WORKFLOW_RUN_NOT_FOUND = 'WORKFLOW_RUN_NOT_FOUND', @@ -10,3 +11,28 @@ export enum WorkflowRunExceptionCode { WORKFLOW_RUN_LIMIT_REACHED = 'WORKFLOW_RUN_LIMIT_REACHED', WORKFLOW_RUN_INVALID = 'WORKFLOW_RUN_INVALID', } + +const workflowRunExceptionUserFriendlyMessages: Record< + WorkflowRunExceptionCode, + MessageDescriptor +> = { + [WorkflowRunExceptionCode.WORKFLOW_RUN_NOT_FOUND]: msg`Workflow run not found.`, + [WorkflowRunExceptionCode.WORKFLOW_ROOT_STEP_NOT_FOUND]: msg`Workflow root step not found.`, + [WorkflowRunExceptionCode.INVALID_OPERATION]: msg`Invalid workflow operation.`, + [WorkflowRunExceptionCode.INVALID_INPUT]: msg`Invalid workflow input.`, + [WorkflowRunExceptionCode.WORKFLOW_RUN_LIMIT_REACHED]: msg`Workflow run limit reached.`, + [WorkflowRunExceptionCode.WORKFLOW_RUN_INVALID]: msg`Invalid workflow run.`, +}; + +export class WorkflowRunException extends CustomException { + constructor( + message: string, + code: WorkflowRunExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? workflowRunExceptionUserFriendlyMessages[code], + }); + } +} 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 c84059b27b..38d6954ca1 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'; +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; -export class WorkflowTriggerException extends CustomException {} +import { CustomException } from 'src/utils/custom-exception'; export enum WorkflowTriggerExceptionCode { INVALID_INPUT = 'INVALID_INPUT', @@ -12,3 +13,31 @@ export enum WorkflowTriggerExceptionCode { FORBIDDEN = 'FORBIDDEN', INTERNAL_ERROR = 'INTERNAL_ERROR', } + +const workflowTriggerExceptionUserFriendlyMessages: Record< + WorkflowTriggerExceptionCode, + MessageDescriptor +> = { + [WorkflowTriggerExceptionCode.INVALID_INPUT]: msg`Invalid workflow trigger input.`, + [WorkflowTriggerExceptionCode.INVALID_WORKFLOW_TRIGGER]: msg`Invalid workflow trigger configuration.`, + [WorkflowTriggerExceptionCode.INVALID_WORKFLOW_VERSION]: msg`Invalid workflow version.`, + [WorkflowTriggerExceptionCode.INVALID_WORKFLOW_STATUS]: msg`Invalid workflow status.`, + [WorkflowTriggerExceptionCode.INVALID_ACTION_TYPE]: msg`Invalid action type.`, + [WorkflowTriggerExceptionCode.NOT_FOUND]: msg`Workflow trigger not found.`, + [WorkflowTriggerExceptionCode.FORBIDDEN]: msg`You do not have permission to access this workflow.`, + [WorkflowTriggerExceptionCode.INTERNAL_ERROR]: msg`An unexpected workflow error occurred.`, +}; + +export class WorkflowTriggerException extends CustomException { + constructor( + message: string, + code: WorkflowTriggerExceptionCode, + { userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {}, + ) { + super(message, code, { + userFriendlyMessage: + userFriendlyMessage ?? + workflowTriggerExceptionUserFriendlyMessages[code], + }); + } +} diff --git a/packages/twenty-server/src/utils/__test__/custom-exception.spec.ts b/packages/twenty-server/src/utils/__test__/custom-exception.spec.ts index 71d0f9bf89..39e7da7d9e 100644 --- a/packages/twenty-server/src/utils/__test__/custom-exception.spec.ts +++ b/packages/twenty-server/src/utils/__test__/custom-exception.spec.ts @@ -1,3 +1,6 @@ +import { type MessageDescriptor } from '@lingui/core'; +import { msg } from '@lingui/core/macro'; + import { appendCommonExceptionCode, CustomException, @@ -28,26 +31,31 @@ describe('appendCommonExceptionCode', () => { }); describe('CustomException', () => { - // Create a concrete implementation of the abstract class for testing - class TestException extends CustomException {} + class TestException extends CustomException { + constructor( + message: string, + code: string, + { userFriendlyMessage }: { userFriendlyMessage: MessageDescriptor }, + ) { + super(message, code, { userFriendlyMessage }); + } + } it('should set message and code correctly', () => { const message = 'Test error message'; const code = 'TEST_ERROR'; - const exception = new TestException(message, code); + const userFriendlyMessage = msg`Test user friendly message`; + const exception = new TestException(message, code, { userFriendlyMessage }); expect(exception.message).toBe(message); expect(exception.code).toBe(code); - expect(exception.userFriendlyMessage).toBeUndefined(); + expect(exception.userFriendlyMessage).toBe(userFriendlyMessage); }); it('should set userFriendlyMessage when provided', () => { const message = 'Test error message'; const code = 'TEST_ERROR'; - const userFriendlyMessage = { - id: 'test.message', - message: 'User friendly error message', - }; + const userFriendlyMessage = msg`User friendly error message`; const exception = new TestException(message, code, { userFriendlyMessage, }); @@ -58,7 +66,9 @@ describe('CustomException', () => { }); it('should extend Error', () => { - const exception = new TestException('Test error', 'TEST_ERROR'); + const exception = new TestException('Test error', 'TEST_ERROR', { + userFriendlyMessage: msg`Test error`, + }); expect(exception).toBeInstanceOf(Error); }); @@ -66,7 +76,9 @@ describe('CustomException', () => { describe('UnknownException', () => { it('should extend CustomException', () => { - const exception = new UnknownException('Test error', 'TEST_ERROR'); + const exception = new UnknownException('Test error', 'TEST_ERROR', { + userFriendlyMessage: msg`Test error`, + }); expect(exception).toBeInstanceOf(CustomException); }); @@ -74,20 +86,18 @@ describe('UnknownException', () => { it('should set message and code correctly', () => { const message = 'Test error message'; const code = 'TEST_ERROR'; - const exception = new UnknownException(message, code); + const exception = new UnknownException(message, code, { + userFriendlyMessage: msg`Test error`, + }); expect(exception.message).toBe(message); expect(exception.code).toBe(code); - expect(exception.userFriendlyMessage).toBeUndefined(); }); it('should set userFriendlyMessage when provided', () => { const message = 'Test error message'; const code = 'TEST_ERROR'; - const userFriendlyMessage = { - id: 'test.message', - message: 'User friendly error message', - }; + const userFriendlyMessage = msg`User friendly error message`; const exception = new UnknownException(message, code, { userFriendlyMessage, }); diff --git a/packages/twenty-server/src/utils/custom-exception.ts b/packages/twenty-server/src/utils/custom-exception.ts index f84634b1e9..321dd5b7f8 100644 --- a/packages/twenty-server/src/utils/custom-exception.ts +++ b/packages/twenty-server/src/utils/custom-exception.ts @@ -18,24 +18,19 @@ export const appendCommonExceptionCode = < export abstract class CustomException< ExceptionCode extends string = string, - ForceFriendlyMessage = false, ExceptionMessage extends string = string, > extends CustomError { code: ExceptionCode; - userFriendlyMessage?: MessageDescriptor; + userFriendlyMessage: MessageDescriptor; constructor( message: ExceptionMessage, code: ExceptionCode, - ...userFriendlyMessage: ForceFriendlyMessage extends true - ? [{ userFriendlyMessage: MessageDescriptor }] - : [{ userFriendlyMessage?: MessageDescriptor }?] + { userFriendlyMessage }: { userFriendlyMessage: MessageDescriptor }, ) { super(message); this.code = code; - this.userFriendlyMessage = userFriendlyMessage - ? userFriendlyMessage?.[0]?.userFriendlyMessage - : undefined; + this.userFriendlyMessage = userFriendlyMessage; } } diff --git a/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/__snapshots__/successful-sign-up.integration-spec.ts.snap b/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/__snapshots__/successful-sign-up.integration-spec.ts.snap index b214633424..cd4b14e6f8 100644 --- a/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/__snapshots__/successful-sign-up.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/graphql/suites/auth/sign-up/__snapshots__/successful-sign-up.integration-spec.ts.snap @@ -25,6 +25,10 @@ exports[`Successful User Sign Up (integration) should sign up, delete and signup "extensions": { "code": "UNAUTHENTICATED", "subCode": "USER_NOT_FOUND", + "userFriendlyMessage": { + "id": Any, + "message": "User not found.", + }, }, "message": "User not found", } diff --git a/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-deletion.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-deletion.integration-spec.ts.snap index 47c111d3ce..ef14792ff7 100644 --- a/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-deletion.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/agent/__snapshots__/failing-agent-deletion.integration-spec.ts.snap @@ -72,7 +72,7 @@ exports[`Agent deletion should fail when deleting a non-existent agent 1`] = ` "extensions": { "code": "NOT_FOUND", "subCode": "AGENT_NOT_FOUND", - "userFriendlyMessage": "An error occurred.", + "userFriendlyMessage": "Agent not found.", }, "message": "Could not retrieve deleted agent", "name": "NotFoundError", diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/failing-update-one-standard-field-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/failing-update-one-standard-field-metadata.integration-spec.ts.snap index a7534c3faf..1374203618 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/failing-update-one-standard-field-metadata.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/failing-update-one-standard-field-metadata.integration-spec.ts.snap @@ -5,7 +5,7 @@ exports[`Standard field metadata update should be ignored when trying to update "extensions": { "code": "FORBIDDEN", "subCode": "FIELD_MUTATION_NOT_ALLOWED", - "userFriendlyMessage": "An error occurred.", + "userFriendlyMessage": "This field cannot be modified.", }, "message": "Cannot edit standard field metadata properties: isLabelSyncedWithName", "name": "ForbiddenError", @@ -17,7 +17,7 @@ exports[`Standard field metadata update should be ignored when trying to update "extensions": { "code": "FORBIDDEN", "subCode": "FIELD_MUTATION_NOT_ALLOWED", - "userFriendlyMessage": "An error occurred.", + "userFriendlyMessage": "This field cannot be modified.", }, "message": "Cannot edit standard field metadata properties: name", "name": "ForbiddenError", @@ -29,7 +29,7 @@ exports[`Standard field metadata update should be ignored when trying to update "extensions": { "code": "FORBIDDEN", "subCode": "FIELD_MUTATION_NOT_ALLOWED", - "userFriendlyMessage": "An error occurred.", + "userFriendlyMessage": "This field cannot be modified.", }, "message": "Cannot edit standard field metadata properties: isLabelSyncedWithName, name", "name": "ForbiddenError", diff --git a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata-view-filters-side-effect-v2.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata-view-filters-side-effect-v2.integration-spec.ts.snap index 94e0399f9e..a5598e2694 100644 --- a/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata-view-filters-side-effect-v2.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/field-metadata/__snapshots__/update-one-field-metadata-view-filters-side-effect-v2.integration-spec.ts.snap @@ -70,7 +70,7 @@ exports[`update-one-field-metadata-view-filters-side-effect-v2 MULTI_SELECT shou "extensions": { "code": "INTERNAL_SERVER_ERROR", "exceptionEventId": "mocked-exception-id", - "userFriendlyMessage": "An error occurred.", + "userFriendlyMessage": "An unexpected error occurred.", }, "message": "Unexpected invalid view filter value for filter 20202020-e3b5-4fa7-85aa-9b1950fc7bf5", }, @@ -180,7 +180,7 @@ exports[`update-one-field-metadata-view-filters-side-effect-v2 SELECT should thr "extensions": { "code": "INTERNAL_SERVER_ERROR", "exceptionEventId": "mocked-exception-id", - "userFriendlyMessage": "An error occurred.", + "userFriendlyMessage": "An unexpected error occurred.", }, "message": "Unexpected invalid view filter value for filter 20202020-e3b5-4fa7-85aa-9b1950fc7bf5", }, diff --git a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-standard-object-metadata.integration-spec.ts.snap b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-standard-object-metadata.integration-spec.ts.snap index fb6ce3ceac..8f8f8b9f22 100644 --- a/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-standard-object-metadata.integration-spec.ts.snap +++ b/packages/twenty-server/test/integration/metadata/suites/object-metadata/__snapshots__/failing-update-one-standard-object-metadata.integration-spec.ts.snap @@ -5,7 +5,7 @@ exports[`Standard object metadata update should fail when trying to update label "extensions": { "code": "BAD_USER_INPUT", "subCode": "INVALID_OBJECT_INPUT", - "userFriendlyMessage": "An error occurred.", + "userFriendlyMessage": "Invalid object input.", }, "message": "Cannot edit standard object metadata properties: labelIdentifierFieldMetadataId", "name": "UserInputError", @@ -17,7 +17,7 @@ exports[`Standard object metadata update should fail when trying to update nameP "extensions": { "code": "BAD_USER_INPUT", "subCode": "INVALID_OBJECT_INPUT", - "userFriendlyMessage": "An error occurred.", + "userFriendlyMessage": "Invalid object input.", }, "message": "Cannot edit standard object metadata properties: namePlural", "name": "UserInputError", @@ -29,7 +29,7 @@ exports[`Standard object metadata update should fail when trying to update nameS "extensions": { "code": "BAD_USER_INPUT", "subCode": "INVALID_OBJECT_INPUT", - "userFriendlyMessage": "An error occurred.", + "userFriendlyMessage": "Invalid object input.", }, "message": "Cannot edit standard object metadata properties: nameSingular", "name": "UserInputError", @@ -41,7 +41,7 @@ exports[`Standard object metadata update should fail when trying to update sever "extensions": { "code": "BAD_USER_INPUT", "subCode": "INVALID_OBJECT_INPUT", - "userFriendlyMessage": "An error occurred.", + "userFriendlyMessage": "Invalid object input.", }, "message": "Cannot edit standard object metadata properties: isLabelSyncedWithName, namePlural", "name": "UserInputError",