Improve userFriendlyMessage devX (#16815)
Two challenges with error messages - always provide a useful/meaningful error message for the end user instead of the generic one. eg: show "Wrong password" and not "An error occured" - avoid technical details unless error regards a technical feature. eg: show "An error occured" and not "Invalid post-hook payload."; but do show "Invalid issuer URL." as it occurs while configuring SSO What this PR does - Make userFriendlyMessage mandatory for widely used GraphqlQueryRunnerException and CommonQueryRunnerException, so that developers are forced to ask themselves what the error message should be, and as it contains very wide error codes (eg: "Bad request") which should not be mapped to just one default message - Keep userFriendlyMessage optional for service-specific exceptions (eg: workflowStepExecutorException), but convert the error code to userFriendlyMessage mapper to a switch case function with a typecheck ensuring that all codes are mapped to a message. These default messages are still overridable where they are thrown.
This commit is contained in:
+2
@@ -3,6 +3,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
import { type CreateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
@@ -31,6 +32,7 @@ export class CreatedByCreateManyPreQueryHook
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'Payload data is required',
|
||||
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -3,6 +3,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
import { type CreateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
@@ -31,6 +32,7 @@ export class CreatedByCreateOnePreQueryHook
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'Payload data is required',
|
||||
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -3,6 +3,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
import { type UpdateManyResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
@@ -31,6 +32,7 @@ export class UpdatedByUpdateManyPreQueryHook
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'Payload data is required',
|
||||
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+2
@@ -3,6 +3,7 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { type WorkspacePreQueryHookInstance } from 'src/engine/api/graphql/workspace-query-runner/workspace-query-hook/interfaces/workspace-query-hook.interface';
|
||||
import { type UpdateOneResolverArgs } from 'src/engine/api/graphql/workspace-resolver-builder/interfaces/workspace-resolvers-builder.interface';
|
||||
|
||||
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
|
||||
import {
|
||||
GraphqlQueryRunnerException,
|
||||
GraphqlQueryRunnerExceptionCode,
|
||||
@@ -31,6 +32,7 @@ export class UpdatedByUpdateOnePreQueryHook
|
||||
throw new GraphqlQueryRunnerException(
|
||||
'Payload data is required',
|
||||
GraphqlQueryRunnerExceptionCode.INVALID_QUERY_INPUT,
|
||||
{ userFriendlyMessage: STANDARD_ERROR_MESSAGE },
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+17
-10
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -11,15 +12,21 @@ export enum ApiKeyExceptionCode {
|
||||
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.`,
|
||||
const getApiKeyExceptionUserFriendlyMessage = (code: ApiKeyExceptionCode) => {
|
||||
switch (code) {
|
||||
case ApiKeyExceptionCode.API_KEY_NOT_FOUND:
|
||||
return msg`API key not found.`;
|
||||
case ApiKeyExceptionCode.API_KEY_REVOKED:
|
||||
return msg`This API key has been revoked.`;
|
||||
case ApiKeyExceptionCode.API_KEY_EXPIRED:
|
||||
return msg`This API key has expired.`;
|
||||
case ApiKeyExceptionCode.API_KEY_NO_ROLE_ASSIGNED:
|
||||
return msg`This API key has no role assigned.`;
|
||||
case ApiKeyExceptionCode.ROLE_CANNOT_BE_ASSIGNED_TO_API_KEYS:
|
||||
return msg`This role cannot be assigned to API keys.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class ApiKeyException extends CustomException<ApiKeyExceptionCode> {
|
||||
@@ -30,7 +37,7 @@ export class ApiKeyException extends CustomException<ApiKeyExceptionCode> {
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? apiKeyExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getApiKeyExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+21
-11
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -12,16 +13,25 @@ export enum ApplicationExceptionCode {
|
||||
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.`,
|
||||
const getApplicationExceptionUserFriendlyMessage = (
|
||||
code: ApplicationExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case ApplicationExceptionCode.OBJECT_NOT_FOUND:
|
||||
return msg`Object not found.`;
|
||||
case ApplicationExceptionCode.FIELD_NOT_FOUND:
|
||||
return msg`Field not found.`;
|
||||
case ApplicationExceptionCode.SERVERLESS_FUNCTION_NOT_FOUND:
|
||||
return msg`Serverless function not found.`;
|
||||
case ApplicationExceptionCode.ENTITY_NOT_FOUND:
|
||||
return msg`Entity not found.`;
|
||||
case ApplicationExceptionCode.APPLICATION_NOT_FOUND:
|
||||
return msg`Application not found.`;
|
||||
case ApplicationExceptionCode.FORBIDDEN:
|
||||
return msg`You do not have permission to perform this action.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class ApplicationException extends CustomException<ApplicationExceptionCode> {
|
||||
@@ -32,7 +42,7 @@ export class ApplicationException extends CustomException<ApplicationExceptionCo
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? applicationExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getApplicationExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+11
-6
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -7,11 +8,15 @@ 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.`,
|
||||
const getApplicationVariableEntityExceptionUserFriendlyMessage = (
|
||||
code: ApplicationVariableEntityExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case ApplicationVariableEntityExceptionCode.APPLICATION_VARIABLE_NOT_FOUND:
|
||||
return msg`Application variable not found.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class ApplicationVariableEntityException extends CustomException<ApplicationVariableEntityExceptionCode> {
|
||||
@@ -23,7 +28,7 @@ export class ApplicationVariableEntityException extends CustomException<Applicat
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ??
|
||||
applicationVariableEntityExceptionUserFriendlyMessages[code],
|
||||
getApplicationVariableEntityExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+23
-12
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -13,17 +14,27 @@ export enum ApprovedAccessDomainExceptionCode {
|
||||
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.`,
|
||||
const getApprovedAccessDomainExceptionUserFriendlyMessage = (
|
||||
code: ApprovedAccessDomainExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_NOT_FOUND:
|
||||
return msg`Approved access domain not found.`;
|
||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VERIFIED:
|
||||
return msg`This domain has already been verified.`;
|
||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_REGISTERED:
|
||||
return msg`This domain is already registered.`;
|
||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_DOES_NOT_MATCH_DOMAIN_EMAIL:
|
||||
return msg`The domain does not match your email domain.`;
|
||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_VALIDATION_TOKEN_INVALID:
|
||||
return msg`Invalid validation token.`;
|
||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_ALREADY_VALIDATED:
|
||||
return msg`This domain has already been validated.`;
|
||||
case ApprovedAccessDomainExceptionCode.APPROVED_ACCESS_DOMAIN_MUST_BE_A_COMPANY_DOMAIN:
|
||||
return msg`Please use a company email domain.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class ApprovedAccessDomainException extends CustomException<ApprovedAccessDomainExceptionCode> {
|
||||
@@ -35,7 +46,7 @@ export class ApprovedAccessDomainException extends CustomException<ApprovedAcces
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ??
|
||||
approvedAccessDomainExceptionUserFriendlyMessages[code],
|
||||
getApprovedAccessDomainExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -8,12 +9,15 @@ export enum AuditExceptionCode {
|
||||
INVALID_INPUT = 'INVALID_INPUT',
|
||||
}
|
||||
|
||||
const auditExceptionUserFriendlyMessages: Record<
|
||||
AuditExceptionCode,
|
||||
MessageDescriptor
|
||||
> = {
|
||||
[AuditExceptionCode.INVALID_TYPE]: msg`Invalid audit type.`,
|
||||
[AuditExceptionCode.INVALID_INPUT]: msg`Invalid audit input.`,
|
||||
const getAuditExceptionUserFriendlyMessage = (code: AuditExceptionCode) => {
|
||||
switch (code) {
|
||||
case AuditExceptionCode.INVALID_TYPE:
|
||||
return msg`Invalid audit type.`;
|
||||
case AuditExceptionCode.INVALID_INPUT:
|
||||
return msg`Invalid audit input.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class AuditException extends CustomException<AuditExceptionCode> {
|
||||
@@ -24,7 +28,7 @@ export class AuditException extends CustomException<AuditExceptionCode> {
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? auditExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getAuditExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,55 +1,13 @@
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
|
||||
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',
|
||||
USER_WORKSPACE_NOT_FOUND: 'USER_WORKSPACE_NOT_FOUND',
|
||||
@@ -76,3 +34,70 @@ export const AuthExceptionCode = appendCommonExceptionCode({
|
||||
'TWO_FACTOR_AUTHENTICATION_VERIFICATION_REQUIRED',
|
||||
USER_ALREADY_EXISTS: 'USER_ALREADY_EXISTS',
|
||||
} as const);
|
||||
|
||||
const getAuthExceptionUserFriendlyMessage = (
|
||||
code: keyof typeof AuthExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case AuthExceptionCode.USER_NOT_FOUND:
|
||||
return msg`User not found.`;
|
||||
case AuthExceptionCode.USER_WORKSPACE_NOT_FOUND:
|
||||
return msg`User workspace not found.`;
|
||||
case AuthExceptionCode.EMAIL_NOT_VERIFIED:
|
||||
return msg`Email is not verified.`;
|
||||
case AuthExceptionCode.WORKSPACE_NOT_FOUND:
|
||||
return msg`Workspace not found.`;
|
||||
case AuthExceptionCode.APPLICATION_NOT_FOUND:
|
||||
return msg`Application not found.`;
|
||||
case AuthExceptionCode.INVALID_INPUT:
|
||||
return msg`Invalid input provided.`;
|
||||
case AuthExceptionCode.FORBIDDEN_EXCEPTION:
|
||||
return msg`You do not have permission to perform this action.`;
|
||||
case AuthExceptionCode.INSUFFICIENT_SCOPES:
|
||||
return msg`Insufficient permissions.`;
|
||||
case AuthExceptionCode.UNAUTHENTICATED:
|
||||
return msg`You must be authenticated to perform this action.`;
|
||||
case AuthExceptionCode.OAUTH_ACCESS_DENIED:
|
||||
return msg`OAuth access was denied.`;
|
||||
case AuthExceptionCode.SSO_AUTH_FAILED:
|
||||
return msg`Single sign-on authentication failed.`;
|
||||
case AuthExceptionCode.USE_SSO_AUTH:
|
||||
return msg`Please use single sign-on to authenticate.`;
|
||||
case AuthExceptionCode.SIGNUP_DISABLED:
|
||||
return msg`Sign up is disabled.`;
|
||||
case AuthExceptionCode.GOOGLE_API_AUTH_DISABLED:
|
||||
return msg`Google API authentication is disabled.`;
|
||||
case AuthExceptionCode.MICROSOFT_API_AUTH_DISABLED:
|
||||
return msg`Microsoft API authentication is disabled.`;
|
||||
case AuthExceptionCode.MISSING_ENVIRONMENT_VARIABLE:
|
||||
return msg`A required configuration is missing.`;
|
||||
case AuthExceptionCode.TWO_FACTOR_AUTHENTICATION_PROVISION_REQUIRED:
|
||||
return msg`Two-factor authentication setup is required.`;
|
||||
case AuthExceptionCode.TWO_FACTOR_AUTHENTICATION_VERIFICATION_REQUIRED:
|
||||
return msg`Two-factor authentication verification is required.`;
|
||||
case AuthExceptionCode.USER_ALREADY_EXISTS:
|
||||
return msg`A user with this email already exists.`;
|
||||
case AuthExceptionCode.INTERNAL_SERVER_ERROR:
|
||||
case AuthExceptionCode.INVALID_DATA:
|
||||
case AuthExceptionCode.CLIENT_NOT_FOUND:
|
||||
case AuthExceptionCode.INVALID_JWT_TOKEN_TYPE:
|
||||
return STANDARD_ERROR_MESSAGE;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class AuthException extends CustomException<
|
||||
keyof typeof AuthExceptionCode
|
||||
> {
|
||||
constructor(
|
||||
message: string,
|
||||
code: keyof typeof AuthExceptionCode,
|
||||
{ userFriendlyMessage }: { userFriendlyMessage?: MessageDescriptor } = {},
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? getAuthExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,10 @@ import { render } from '@react-email/render';
|
||||
import { addMilliseconds } from 'date-fns';
|
||||
import ms from 'ms';
|
||||
import { PasswordUpdateNotifyEmail } from 'twenty-emails';
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
import { AppPath } from 'twenty-shared/types';
|
||||
import { assertIsDefinedOrThrow, isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
import { PermissionFlagType } from 'twenty-shared/constants';
|
||||
|
||||
import { NodeEnvironment } from 'src/engine/core-modules/twenty-config/interfaces/node-environment.interface';
|
||||
|
||||
@@ -191,7 +191,7 @@ export class AuthService {
|
||||
'Wrong password',
|
||||
AuthExceptionCode.FORBIDDEN_EXCEPTION,
|
||||
{
|
||||
userFriendlyMessage: msg`Wrong password`,
|
||||
userFriendlyMessage: msg`Wrong password.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -357,6 +357,9 @@ export class AuthService {
|
||||
throw new AuthException(
|
||||
'Email is required',
|
||||
AuthExceptionCode.INVALID_INPUT,
|
||||
{
|
||||
userFriendlyMessage: msg`Email is required.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -603,6 +606,9 @@ export class AuthService {
|
||||
throw new AuthException(
|
||||
'Password is too weak',
|
||||
AuthExceptionCode.INVALID_INPUT,
|
||||
{
|
||||
userFriendlyMessage: msg`Password is too weak.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
@@ -650,6 +656,9 @@ export class AuthService {
|
||||
throw new AuthException(
|
||||
'Workspace does not exist',
|
||||
AuthExceptionCode.INVALID_INPUT,
|
||||
{
|
||||
userFriendlyMessage: msg`Workspace does not exist.`,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+12
-12
@@ -1,5 +1,7 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
|
||||
import {
|
||||
AuthException,
|
||||
AuthExceptionCode,
|
||||
@@ -12,15 +14,6 @@ import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.ent
|
||||
|
||||
import { JwtAuthStrategy } from './jwt.auth.strategy';
|
||||
|
||||
jest.mock('twenty-shared/utils', () => ({
|
||||
...jest.requireActual('twenty-shared/utils'),
|
||||
assertIsDefinedOrThrow: jest.fn((value, error) => {
|
||||
if (value === null || value === undefined) {
|
||||
throw error;
|
||||
}
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('JwtAuthStrategy', () => {
|
||||
let strategy: JwtAuthStrategy;
|
||||
let workspaceRepository: any;
|
||||
@@ -230,7 +223,9 @@ describe('JwtAuthStrategy', () => {
|
||||
);
|
||||
|
||||
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
|
||||
new AuthException('UserWorkspaceEntity not found', expect.any(String)),
|
||||
new AuthException('UserWorkspaceEntity not found', expect.any(String), {
|
||||
userFriendlyMessage: msg`User does not have access to this workspace.`,
|
||||
}),
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -269,7 +264,9 @@ describe('JwtAuthStrategy', () => {
|
||||
);
|
||||
|
||||
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
|
||||
new AuthException('UserWorkspaceEntity not found', expect.any(String)),
|
||||
new AuthException('UserWorkspaceEntity not found', expect.any(String), {
|
||||
userFriendlyMessage: msg`User does not have access to this workspace.`,
|
||||
}),
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -345,7 +342,9 @@ describe('JwtAuthStrategy', () => {
|
||||
);
|
||||
|
||||
await expect(strategy.validate(payload as JwtPayload)).rejects.toThrow(
|
||||
new AuthException('Application not found', expect.any(String)),
|
||||
new AuthException('Application not found', expect.any(String), {
|
||||
userFriendlyMessage: msg`Application not found.`,
|
||||
}),
|
||||
);
|
||||
|
||||
try {
|
||||
@@ -552,6 +551,7 @@ describe('JwtAuthStrategy', () => {
|
||||
new AuthException(
|
||||
'Invalid impersonation token, cannot find impersonator or impersonated user workspace',
|
||||
AuthExceptionCode.USER_WORKSPACE_NOT_FOUND,
|
||||
{ userFriendlyMessage: msg`User workspace not found.` },
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
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';
|
||||
|
||||
@@ -33,35 +34,61 @@ export enum BillingExceptionCode {
|
||||
BILLING_CREDITS_EXHAUSTED = 'BILLING_CREDITS_EXHAUSTED',
|
||||
}
|
||||
|
||||
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.`,
|
||||
[BillingExceptionCode.BILLING_CREDITS_EXHAUSTED]: msg`You have exhausted your credits. Please upgrade your plan to continue.`,
|
||||
const getBillingExceptionUserFriendlyMessage = (code: BillingExceptionCode) => {
|
||||
switch (code) {
|
||||
case BillingExceptionCode.BILLING_CUSTOMER_NOT_FOUND:
|
||||
return msg`Billing customer not found.`;
|
||||
case BillingExceptionCode.BILLING_PLAN_NOT_FOUND:
|
||||
return msg`Billing plan not found.`;
|
||||
case BillingExceptionCode.BILLING_PRODUCT_NOT_FOUND:
|
||||
return msg`Billing product not found.`;
|
||||
case BillingExceptionCode.BILLING_PRICE_NOT_FOUND:
|
||||
return msg`Billing price not found.`;
|
||||
case BillingExceptionCode.BILLING_METER_NOT_FOUND:
|
||||
return msg`Billing meter not found.`;
|
||||
case BillingExceptionCode.BILLING_SUBSCRIPTION_NOT_FOUND:
|
||||
return msg`Subscription not found.`;
|
||||
case BillingExceptionCode.BILLING_SUBSCRIPTION_ITEM_NOT_FOUND:
|
||||
return msg`Subscription item not found.`;
|
||||
case BillingExceptionCode.BILLING_SUBSCRIPTION_INVALID:
|
||||
return msg`Invalid subscription.`;
|
||||
case BillingExceptionCode.BILLING_SUBSCRIPTION_EVENT_WORKSPACE_NOT_FOUND:
|
||||
return msg`Workspace not found for subscription event.`;
|
||||
case BillingExceptionCode.BILLING_CUSTOMER_EVENT_WORKSPACE_NOT_FOUND:
|
||||
return msg`Workspace not found for customer event.`;
|
||||
case BillingExceptionCode.BILLING_ACTIVE_SUBSCRIPTION_NOT_FOUND:
|
||||
return msg`No active subscription found.`;
|
||||
case BillingExceptionCode.BILLING_METER_EVENT_FAILED:
|
||||
return msg`Failed to record billing event.`;
|
||||
case BillingExceptionCode.BILLING_MISSING_REQUEST_BODY:
|
||||
return msg`Missing request body.`;
|
||||
case BillingExceptionCode.BILLING_UNHANDLED_ERROR:
|
||||
return msg`An unexpected billing error occurred.`;
|
||||
case BillingExceptionCode.BILLING_STRIPE_ERROR:
|
||||
return msg`A payment processing error occurred.`;
|
||||
case BillingExceptionCode.BILLING_SUBSCRIPTION_NOT_IN_TRIAL_PERIOD:
|
||||
return msg`Subscription is not in trial period.`;
|
||||
case BillingExceptionCode.BILLING_SUBSCRIPTION_INTERVAL_NOT_SWITCHABLE:
|
||||
return msg`Cannot switch subscription interval.`;
|
||||
case BillingExceptionCode.BILLING_SUBSCRIPTION_INTERVAL_INVALID:
|
||||
return msg`Invalid subscription interval.`;
|
||||
case BillingExceptionCode.BILLING_SUBSCRIPTION_PLAN_NOT_SWITCHABLE:
|
||||
return msg`Cannot switch subscription plan.`;
|
||||
case BillingExceptionCode.BILLING_SUBSCRIPTION_ITEM_INVALID:
|
||||
return msg`Invalid subscription item.`;
|
||||
case BillingExceptionCode.BILLING_PRICE_INVALID_TIERS:
|
||||
return msg`Invalid pricing tiers.`;
|
||||
case BillingExceptionCode.BILLING_PRICE_INVALID:
|
||||
return msg`Invalid price.`;
|
||||
case BillingExceptionCode.BILLING_SUBSCRIPTION_PHASE_NOT_FOUND:
|
||||
return msg`Subscription phase not found.`;
|
||||
case BillingExceptionCode.BILLING_TOO_MUCH_SUBSCRIPTIONS_FOUND:
|
||||
return msg`Multiple subscriptions found where one was expected.`;
|
||||
case BillingExceptionCode.BILLING_CREDITS_EXHAUSTED:
|
||||
return msg`You have exhausted your credits. Please upgrade your plan to continue.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class BillingException extends CustomException<BillingExceptionCode> {
|
||||
@@ -72,7 +99,7 @@ export class BillingException extends CustomException<BillingExceptionCode> {
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? billingExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getBillingExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -7,11 +8,13 @@ export enum CaptchaExceptionCode {
|
||||
INVALID_CAPTCHA = 'INVALID_CAPTCHA',
|
||||
}
|
||||
|
||||
const captchaExceptionUserFriendlyMessages: Record<
|
||||
CaptchaExceptionCode,
|
||||
MessageDescriptor
|
||||
> = {
|
||||
[CaptchaExceptionCode.INVALID_CAPTCHA]: msg`Invalid captcha. Please try again.`,
|
||||
const getCaptchaExceptionUserFriendlyMessage = (code: CaptchaExceptionCode) => {
|
||||
switch (code) {
|
||||
case CaptchaExceptionCode.INVALID_CAPTCHA:
|
||||
return msg`Invalid captcha. Please try again.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class CaptchaException extends CustomException<CaptchaExceptionCode> {
|
||||
@@ -22,7 +25,7 @@ export class CaptchaException extends CustomException<CaptchaExceptionCode> {
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? captchaExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getCaptchaExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+23
-13
@@ -1,5 +1,6 @@
|
||||
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,18 +15,27 @@ export enum EmailVerificationExceptionCode {
|
||||
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.`,
|
||||
const getEmailVerificationExceptionUserFriendlyMessage = (
|
||||
code: EmailVerificationExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case EmailVerificationExceptionCode.EMAIL_VERIFICATION_NOT_REQUIRED:
|
||||
return msg`Email verification is not required.`;
|
||||
case EmailVerificationExceptionCode.INVALID_TOKEN:
|
||||
case EmailVerificationExceptionCode.INVALID_APP_TOKEN_TYPE:
|
||||
case EmailVerificationExceptionCode.TOKEN_EXPIRED:
|
||||
return msg`There is an issue with your token. Please try again.`;
|
||||
case EmailVerificationExceptionCode.EMAIL_MISSING:
|
||||
return msg`Email is required.`;
|
||||
case EmailVerificationExceptionCode.EMAIL_ALREADY_VERIFIED:
|
||||
return msg`Email is already verified.`;
|
||||
case EmailVerificationExceptionCode.INVALID_EMAIL:
|
||||
return msg`Invalid email address.`;
|
||||
case EmailVerificationExceptionCode.RATE_LIMIT_EXCEEDED:
|
||||
return msg`Too many requests. Please try again later.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class EmailVerificationException extends CustomException<EmailVerificationExceptionCode> {
|
||||
@@ -37,7 +47,7 @@ export class EmailVerificationException extends CustomException<EmailVerificatio
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ??
|
||||
emailVerificationExceptionUserFriendlyMessages[code],
|
||||
getEmailVerificationExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+19
-10
@@ -1,6 +1,8 @@
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
|
||||
import { CustomException } from 'src/utils/custom-exception';
|
||||
|
||||
export enum EmailingDomainDriverExceptionCode {
|
||||
@@ -11,15 +13,22 @@ export enum EmailingDomainDriverExceptionCode {
|
||||
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.`,
|
||||
const getEmailingDomainDriverExceptionUserFriendlyMessage = (
|
||||
code: EmailingDomainDriverExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case EmailingDomainDriverExceptionCode.NOT_FOUND:
|
||||
return msg`Email domain not found.`;
|
||||
case EmailingDomainDriverExceptionCode.INSUFFICIENT_PERMISSIONS:
|
||||
return msg`Insufficient permissions for email domain.`;
|
||||
case EmailingDomainDriverExceptionCode.CONFIGURATION_ERROR:
|
||||
return msg`Email domain configuration error.`;
|
||||
case EmailingDomainDriverExceptionCode.TEMPORARY_ERROR:
|
||||
case EmailingDomainDriverExceptionCode.UNKNOWN:
|
||||
return STANDARD_ERROR_MESSAGE;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class EmailingDomainDriverException extends CustomException<EmailingDomainDriverExceptionCode> {
|
||||
@@ -31,7 +40,7 @@ export class EmailingDomainDriverException extends CustomException<EmailingDomai
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ??
|
||||
emailingDomainDriverExceptionUserFriendlyMessages[code],
|
||||
getEmailingDomainDriverExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+11
-6
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -7,11 +8,15 @@ 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.`,
|
||||
const getFeatureFlagExceptionUserFriendlyMessage = (
|
||||
code: FeatureFlagExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case FeatureFlagExceptionCode.INVALID_FEATURE_FLAG_KEY:
|
||||
return msg`Invalid feature flag key.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class FeatureFlagException extends CustomException<FeatureFlagExceptionCode> {
|
||||
@@ -22,7 +27,7 @@ export class FeatureFlagException extends CustomException<FeatureFlagExceptionCo
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? featureFlagExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getFeatureFlagExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+11
-6
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -7,11 +8,15 @@ export enum FileStorageExceptionCode {
|
||||
FILE_NOT_FOUND = 'FILE_NOT_FOUND',
|
||||
}
|
||||
|
||||
const fileStorageExceptionUserFriendlyMessages: Record<
|
||||
FileStorageExceptionCode,
|
||||
MessageDescriptor
|
||||
> = {
|
||||
[FileStorageExceptionCode.FILE_NOT_FOUND]: msg`File not found.`,
|
||||
const getFileStorageExceptionUserFriendlyMessage = (
|
||||
code: FileStorageExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case FileStorageExceptionCode.FILE_NOT_FOUND:
|
||||
return msg`File not found.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class FileStorageException extends CustomException<FileStorageExceptionCode> {
|
||||
@@ -22,7 +27,7 @@ export class FileStorageException extends CustomException<FileStorageExceptionCo
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? fileStorageExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getFileStorageExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { type MessageDescriptor } from '@lingui/core';
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
|
||||
import { STANDARD_ERROR_MESSAGE } from 'src/engine/api/common/common-query-runners/errors/standard-error-message.constant';
|
||||
import {
|
||||
appendCommonExceptionCode,
|
||||
CustomException,
|
||||
@@ -11,13 +13,19 @@ export const FileExceptionCode = appendCommonExceptionCode({
|
||||
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.`,
|
||||
const getFileExceptionUserFriendlyMessage = (
|
||||
code: keyof typeof FileExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case FileExceptionCode.UNAUTHENTICATED:
|
||||
return msg`Authentication is required.`;
|
||||
case FileExceptionCode.FILE_NOT_FOUND:
|
||||
return msg`File not found.`;
|
||||
case FileExceptionCode.INTERNAL_SERVER_ERROR:
|
||||
return STANDARD_ERROR_MESSAGE;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class FileException extends CustomException<
|
||||
@@ -30,7 +38,7 @@ export class FileException extends CustomException<
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? fileExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getFileExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+16
-8
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -9,13 +10,19 @@ export enum PublicDomainExceptionCode {
|
||||
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.`,
|
||||
const getPublicDomainExceptionUserFriendlyMessage = (
|
||||
code: PublicDomainExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case PublicDomainExceptionCode.PUBLIC_DOMAIN_ALREADY_REGISTERED:
|
||||
return msg`This public domain is already registered.`;
|
||||
case PublicDomainExceptionCode.DOMAIN_ALREADY_REGISTERED_AS_CUSTOM_DOMAIN:
|
||||
return msg`This domain is already registered as a custom domain.`;
|
||||
case PublicDomainExceptionCode.PUBLIC_DOMAIN_NOT_FOUND:
|
||||
return msg`Public domain not found.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class PublicDomainException extends CustomException<PublicDomainExceptionCode> {
|
||||
@@ -26,7 +33,8 @@ export class PublicDomainException extends CustomException<PublicDomainException
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? publicDomainExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ??
|
||||
getPublicDomainExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+27
-14
@@ -1,5 +1,6 @@
|
||||
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,19 +16,31 @@ export enum RecordCrudExceptionCode {
|
||||
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.`,
|
||||
const getRecordCrudExceptionUserFriendlyMessage = (
|
||||
code: RecordCrudExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case RecordCrudExceptionCode.INVALID_REQUEST:
|
||||
return msg`Invalid request.`;
|
||||
case RecordCrudExceptionCode.WORKSPACE_ID_NOT_FOUND:
|
||||
return msg`Workspace not found.`;
|
||||
case RecordCrudExceptionCode.OBJECT_NOT_FOUND:
|
||||
return msg`Object not found.`;
|
||||
case RecordCrudExceptionCode.RECORD_NOT_FOUND:
|
||||
return msg`Record not found.`;
|
||||
case RecordCrudExceptionCode.RECORD_CREATION_FAILED:
|
||||
return msg`Failed to create record.`;
|
||||
case RecordCrudExceptionCode.RECORD_UPDATE_FAILED:
|
||||
return msg`Failed to update record.`;
|
||||
case RecordCrudExceptionCode.RECORD_DELETION_FAILED:
|
||||
return msg`Failed to delete record.`;
|
||||
case RecordCrudExceptionCode.RECORD_UPSERT_FAILED:
|
||||
return msg`Failed to upsert record.`;
|
||||
case RecordCrudExceptionCode.QUERY_FAILED:
|
||||
return msg`Query failed.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class RecordCrudException extends CustomException<RecordCrudExceptionCode> {
|
||||
@@ -38,7 +51,7 @@ export class RecordCrudException extends CustomException<RecordCrudExceptionCode
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? recordCrudExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getRecordCrudExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+23
-12
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -13,17 +14,27 @@ export enum RecordTransformerExceptionCode {
|
||||
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.`,
|
||||
const getRecordTransformerExceptionUserFriendlyMessage = (
|
||||
code: RecordTransformerExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case RecordTransformerExceptionCode.INVALID_URL:
|
||||
return msg`Invalid URL format.`;
|
||||
case RecordTransformerExceptionCode.INVALID_PHONE_NUMBER:
|
||||
return msg`Invalid phone number.`;
|
||||
case RecordTransformerExceptionCode.INVALID_PHONE_COUNTRY_CODE:
|
||||
return msg`Invalid phone country code.`;
|
||||
case RecordTransformerExceptionCode.INVALID_PHONE_CALLING_CODE:
|
||||
return msg`Invalid phone calling code.`;
|
||||
case RecordTransformerExceptionCode.CONFLICTING_PHONE_COUNTRY_CODE:
|
||||
return msg`Conflicting phone country code.`;
|
||||
case RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE:
|
||||
return msg`Conflicting phone calling code.`;
|
||||
case RecordTransformerExceptionCode.CONFLICTING_PHONE_CALLING_CODE_AND_COUNTRY_CODE:
|
||||
return msg`Conflicting phone calling code and country code.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class RecordTransformerException extends CustomException<RecordTransformerExceptionCode> {
|
||||
@@ -35,7 +46,7 @@ export class RecordTransformerException extends CustomException<RecordTransforme
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ??
|
||||
recordTransformerExceptionUserFriendlyMessages[code],
|
||||
getRecordTransformerExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+11
-7
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -8,12 +9,15 @@ export enum SearchExceptionCode {
|
||||
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.`,
|
||||
const getSearchExceptionUserFriendlyMessage = (code: SearchExceptionCode) => {
|
||||
switch (code) {
|
||||
case SearchExceptionCode.LABEL_IDENTIFIER_FIELD_NOT_FOUND:
|
||||
return msg`No identifier to search by was found.`;
|
||||
case SearchExceptionCode.OBJECT_METADATA_NOT_FOUND:
|
||||
return msg`Object not found.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class SearchException extends CustomException<SearchExceptionCode> {
|
||||
@@ -24,7 +28,7 @@ export class SearchException extends CustomException<SearchExceptionCode> {
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? searchExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getSearchExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
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,16 +15,23 @@ export enum SSOExceptionCode {
|
||||
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.`,
|
||||
const getSSOExceptionUserFriendlyMessage = (code: SSOExceptionCode) => {
|
||||
switch (code) {
|
||||
case SSOExceptionCode.USER_NOT_FOUND:
|
||||
return msg`User not found.`;
|
||||
case SSOExceptionCode.IDENTITY_PROVIDER_NOT_FOUND:
|
||||
return msg`Identity provider not found.`;
|
||||
case SSOExceptionCode.INVALID_ISSUER_URL:
|
||||
return msg`Invalid issuer URL.`;
|
||||
case SSOExceptionCode.INVALID_IDP_TYPE:
|
||||
return msg`Invalid identity provider type.`;
|
||||
case SSOExceptionCode.UNKNOWN_SSO_CONFIGURATION_ERROR:
|
||||
return msg`SSO configuration error.`;
|
||||
case SSOExceptionCode.SSO_DISABLE:
|
||||
return msg`SSO is disabled.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class SSOException extends CustomException<SSOExceptionCode> {
|
||||
@@ -34,7 +42,7 @@ export class SSOException extends CustomException<SSOExceptionCode> {
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? ssoExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getSSOExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -7,11 +8,15 @@ export enum ThrottlerExceptionCode {
|
||||
LIMIT_REACHED = 'LIMIT_REACHED',
|
||||
}
|
||||
|
||||
const throttlerExceptionUserFriendlyMessages: Record<
|
||||
ThrottlerExceptionCode,
|
||||
MessageDescriptor
|
||||
> = {
|
||||
[ThrottlerExceptionCode.LIMIT_REACHED]: msg`Rate limit reached. Please try again later.`,
|
||||
const getThrottlerExceptionUserFriendlyMessage = (
|
||||
code: ThrottlerExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case ThrottlerExceptionCode.LIMIT_REACHED:
|
||||
return msg`Rate limit reached. Please try again later.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class ThrottlerException extends CustomException<ThrottlerExceptionCode> {
|
||||
@@ -22,7 +27,7 @@ export class ThrottlerException extends CustomException<ThrottlerExceptionCode>
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? throttlerExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getThrottlerExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+22
-11
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -12,16 +13,25 @@ export enum SendEmailToolExceptionCode {
|
||||
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.`,
|
||||
const getSendEmailToolExceptionUserFriendlyMessage = (
|
||||
code: SendEmailToolExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case SendEmailToolExceptionCode.INVALID_CONNECTED_ACCOUNT_ID:
|
||||
return msg`Invalid connected account ID.`;
|
||||
case SendEmailToolExceptionCode.CONNECTED_ACCOUNT_NOT_FOUND:
|
||||
return msg`Connected account not found.`;
|
||||
case SendEmailToolExceptionCode.INVALID_EMAIL:
|
||||
return msg`Invalid email address.`;
|
||||
case SendEmailToolExceptionCode.WORKSPACE_ID_NOT_FOUND:
|
||||
return msg`Workspace not found.`;
|
||||
case SendEmailToolExceptionCode.FILE_NOT_FOUND:
|
||||
return msg`File not found.`;
|
||||
case SendEmailToolExceptionCode.INVALID_FILE_ID:
|
||||
return msg`Invalid file ID.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class SendEmailToolException extends CustomException<SendEmailToolExceptionCode> {
|
||||
@@ -32,7 +42,8 @@ export class SendEmailToolException extends CustomException<SendEmailToolExcepti
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? sendEmailToolExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ??
|
||||
getSendEmailToolExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+21
-11
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -12,16 +13,25 @@ export enum ConfigVariableExceptionCode {
|
||||
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.`,
|
||||
const getConfigVariableExceptionUserFriendlyMessage = (
|
||||
code: ConfigVariableExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case ConfigVariableExceptionCode.DATABASE_CONFIG_DISABLED:
|
||||
return msg`Database configuration is disabled.`;
|
||||
case ConfigVariableExceptionCode.ENVIRONMENT_ONLY_VARIABLE:
|
||||
return msg`This variable can only be set via environment.`;
|
||||
case ConfigVariableExceptionCode.VARIABLE_NOT_FOUND:
|
||||
return msg`Configuration variable not found.`;
|
||||
case ConfigVariableExceptionCode.VALIDATION_FAILED:
|
||||
return msg`Configuration validation failed.`;
|
||||
case ConfigVariableExceptionCode.UNSUPPORTED_CONFIG_TYPE:
|
||||
return msg`Unsupported configuration type.`;
|
||||
case ConfigVariableExceptionCode.INTERNAL_ERROR:
|
||||
return msg`An unexpected configuration error occurred.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class ConfigVariableException extends CustomException<ConfigVariableExceptionCode> {
|
||||
@@ -33,7 +43,7 @@ export class ConfigVariableException extends CustomException<ConfigVariableExcep
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ??
|
||||
configVariableExceptionUserFriendlyMessages[code],
|
||||
getConfigVariableExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+19
-10
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -11,15 +12,23 @@ export enum TwoFactorAuthenticationExceptionCode {
|
||||
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.`,
|
||||
const getTwoFactorAuthenticationExceptionUserFriendlyMessage = (
|
||||
code: TwoFactorAuthenticationExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case TwoFactorAuthenticationExceptionCode.INVALID_CONFIGURATION:
|
||||
return msg`Invalid two-factor authentication configuration.`;
|
||||
case TwoFactorAuthenticationExceptionCode.TWO_FACTOR_AUTHENTICATION_METHOD_NOT_FOUND:
|
||||
return msg`Two-factor authentication method not found.`;
|
||||
case TwoFactorAuthenticationExceptionCode.INVALID_OTP:
|
||||
return msg`Invalid verification code.`;
|
||||
case TwoFactorAuthenticationExceptionCode.TWO_FACTOR_AUTHENTICATION_METHOD_ALREADY_PROVISIONED:
|
||||
return msg`Two-factor authentication is already set up.`;
|
||||
case TwoFactorAuthenticationExceptionCode.MALFORMED_DATABASE_OBJECT:
|
||||
return msg`An error occurred with two-factor authentication data.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class TwoFactorAuthenticationException extends CustomException<TwoFactorAuthenticationExceptionCode> {
|
||||
@@ -31,7 +40,7 @@ export class TwoFactorAuthenticationException extends CustomException<TwoFactorA
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ??
|
||||
twoFactorAuthenticationExceptionUserFriendlyMessages[code],
|
||||
getTwoFactorAuthenticationExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+12
-6
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -7,11 +8,15 @@ 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.`,
|
||||
const getUserWorkspaceExceptionUserFriendlyMessage = (
|
||||
code: UserWorkspaceExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case UserWorkspaceExceptionCode.USER_WORKSPACE_NOT_FOUND:
|
||||
return msg`User workspace not found.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class UserWorkspaceException extends CustomException<UserWorkspaceExceptionCode> {
|
||||
@@ -22,7 +27,8 @@ export class UserWorkspaceException extends CustomException<UserWorkspaceExcepti
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? userWorkspaceExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ??
|
||||
getUserWorkspaceExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -10,14 +11,19 @@ export enum UserExceptionCode {
|
||||
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.`,
|
||||
const getUserExceptionUserFriendlyMessage = (code: UserExceptionCode) => {
|
||||
switch (code) {
|
||||
case UserExceptionCode.USER_NOT_FOUND:
|
||||
return msg`User not found.`;
|
||||
case UserExceptionCode.EMAIL_ALREADY_IN_USE:
|
||||
return msg`This email is already in use.`;
|
||||
case UserExceptionCode.EMAIL_UNCHANGED:
|
||||
return msg`Email is unchanged.`;
|
||||
case UserExceptionCode.EMAIL_UPDATE_RESTRICTED_TO_SINGLE_WORKSPACE:
|
||||
return msg`Email update is restricted to single workspace users.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class UserException extends CustomException<UserExceptionCode> {
|
||||
@@ -28,7 +34,7 @@ export class UserException extends CustomException<UserExceptionCode> {
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? userExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getUserExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -8,12 +9,15 @@ export enum WebhookExceptionCode {
|
||||
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.`,
|
||||
const getWebhookExceptionUserFriendlyMessage = (code: WebhookExceptionCode) => {
|
||||
switch (code) {
|
||||
case WebhookExceptionCode.WEBHOOK_NOT_FOUND:
|
||||
return msg`Webhook not found.`;
|
||||
case WebhookExceptionCode.INVALID_TARGET_URL:
|
||||
return msg`Invalid target URL.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class WebhookException extends CustomException<WebhookExceptionCode> {
|
||||
@@ -24,7 +28,7 @@ export class WebhookException extends CustomException<WebhookExceptionCode> {
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? webhookExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getWebhookExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+19
-11
@@ -1,5 +1,6 @@
|
||||
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';
|
||||
|
||||
@@ -12,16 +13,23 @@ export enum WorkspaceInvitationExceptionCode {
|
||||
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.`,
|
||||
const getWorkspaceInvitationExceptionUserFriendlyMessage = (
|
||||
code: WorkspaceInvitationExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case WorkspaceInvitationExceptionCode.INVALID_APP_TOKEN_TYPE:
|
||||
case WorkspaceInvitationExceptionCode.INVITATION_CORRUPTED:
|
||||
case WorkspaceInvitationExceptionCode.INVALID_INVITATION:
|
||||
return msg`There is an issue with your invitation. Please try again.`;
|
||||
case WorkspaceInvitationExceptionCode.INVITATION_ALREADY_EXIST:
|
||||
return msg`An invitation has already been sent to this email.`;
|
||||
case WorkspaceInvitationExceptionCode.USER_ALREADY_EXIST:
|
||||
return msg`This user is already a member of the workspace.`;
|
||||
case WorkspaceInvitationExceptionCode.EMAIL_MISSING:
|
||||
return msg`Email is required.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class WorkspaceInvitationException extends CustomException<WorkspaceInvitationExceptionCode> {
|
||||
@@ -33,7 +41,7 @@ export class WorkspaceInvitationException extends CustomException<WorkspaceInvit
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ??
|
||||
workspaceInvitationExceptionUserFriendlyMessages[code],
|
||||
getWorkspaceInvitationExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
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,18 +15,29 @@ 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.`,
|
||||
const getWorkspaceExceptionUserFriendlyMessage = (
|
||||
code: WorkspaceExceptionCode,
|
||||
) => {
|
||||
switch (code) {
|
||||
case WorkspaceExceptionCode.SUBDOMAIN_NOT_FOUND:
|
||||
return msg`Subdomain not found.`;
|
||||
case WorkspaceExceptionCode.SUBDOMAIN_ALREADY_TAKEN:
|
||||
return msg`This subdomain is already taken.`;
|
||||
case WorkspaceExceptionCode.SUBDOMAIN_NOT_VALID:
|
||||
return msg`Invalid subdomain.`;
|
||||
case WorkspaceExceptionCode.DOMAIN_ALREADY_TAKEN:
|
||||
return msg`This domain is already taken.`;
|
||||
case WorkspaceExceptionCode.WORKSPACE_NOT_FOUND:
|
||||
return msg`Workspace not found.`;
|
||||
case WorkspaceExceptionCode.WORKSPACE_CUSTOM_DOMAIN_DISABLED:
|
||||
return msg`Custom domains are disabled for this workspace.`;
|
||||
case WorkspaceExceptionCode.ENVIRONMENT_VAR_NOT_ENABLED:
|
||||
return msg`This feature is not enabled.`;
|
||||
case WorkspaceExceptionCode.CUSTOM_DOMAIN_NOT_FOUND:
|
||||
return msg`Custom domain not found.`;
|
||||
default:
|
||||
assertUnreachable(code);
|
||||
}
|
||||
};
|
||||
|
||||
export class WorkspaceException extends CustomException<WorkspaceExceptionCode> {
|
||||
@@ -36,7 +48,7 @@ export class WorkspaceException extends CustomException<WorkspaceExceptionCode>
|
||||
) {
|
||||
super(message, code, {
|
||||
userFriendlyMessage:
|
||||
userFriendlyMessage ?? workspaceExceptionUserFriendlyMessages[code],
|
||||
userFriendlyMessage ?? getWorkspaceExceptionUserFriendlyMessage(code),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user