diff --git a/backend/src/authorization/auth-with-api.middleware.ts b/backend/src/authorization/auth-with-api.middleware.ts index 47154333b..23aca7158 100644 --- a/backend/src/authorization/auth-with-api.middleware.ts +++ b/backend/src/authorization/auth-with-api.middleware.ts @@ -1,5 +1,4 @@ import { - BadRequestException, HttpException, Injectable, InternalServerErrorException, @@ -15,6 +14,7 @@ import { Repository } from 'typeorm'; import { JwtScopesEnum } from '../entities/user/enums/jwt-scopes.enum.js'; import { UserEntity } from '../entities/user/user.entity.js'; import { EncryptionAlgorithmEnum } from '../enums/encryption-algorithm.enum.js'; +import { TwoFaRequiredException } from '../exceptions/custom-exceptions/two-fa-required-exception.js'; import { Messages } from '../exceptions/text/messages.js'; import { Constants } from '../helpers/constants/constants.js'; import { Encryptor } from '../helpers/encryption/encryptor.js'; @@ -84,7 +84,7 @@ export class AuthWithApiMiddleware implements NestMiddleware { const addedScope: Array = data.scope; if (addedScope && addedScope.length > 0) { if (addedScope.includes(JwtScopesEnum.TWO_FA_ENABLE)) { - throw new BadRequestException(Messages.TWO_FA_REQUIRED); + throw new TwoFaRequiredException(); } } diff --git a/backend/src/authorization/auth.middleware.ts b/backend/src/authorization/auth.middleware.ts index cc06fe5c6..e164d1f9b 100644 --- a/backend/src/authorization/auth.middleware.ts +++ b/backend/src/authorization/auth.middleware.ts @@ -1,5 +1,4 @@ import { - BadRequestException, HttpException, Injectable, InternalServerErrorException, @@ -14,6 +13,7 @@ import { Repository } from 'typeorm'; import { LogOutEntity } from '../entities/log-out/log-out.entity.js'; import { JwtScopesEnum } from '../entities/user/enums/jwt-scopes.enum.js'; import { UserEntity } from '../entities/user/user.entity.js'; +import { TwoFaRequiredException } from '../exceptions/custom-exceptions/two-fa-required-exception.js'; import { Messages } from '../exceptions/text/messages.js'; import { isTest } from '../helpers/app/is-test.js'; import { Constants } from '../helpers/constants/constants.js'; @@ -72,7 +72,7 @@ export class AuthMiddleware implements NestMiddleware { const addedScope: Array = data.scope; if (addedScope && addedScope.length > 0) { if (addedScope.includes(JwtScopesEnum.TWO_FA_ENABLE)) { - throw new BadRequestException(Messages.TWO_FA_REQUIRED); + throw new TwoFaRequiredException(); } } diff --git a/backend/src/entities/ai/use-cases/request-ai-settings-and-widgets-creation.use.case.ts b/backend/src/entities/ai/use-cases/request-ai-settings-and-widgets-creation.use.case.ts index c75618ded..2165efa38 100644 --- a/backend/src/entities/ai/use-cases/request-ai-settings-and-widgets-creation.use.case.ts +++ b/backend/src/entities/ai/use-cases/request-ai-settings-and-widgets-creation.use.case.ts @@ -1,10 +1,10 @@ -import { BadRequestException, Inject, Injectable, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import Sentry from '@sentry/minimal'; import { Response } from 'express'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; import { SharedJobsService } from '../../shared-jobs/shared-jobs.service.js'; import { IAISettingsAndWidgetsCreation } from '../ai-use-cases.interface.js'; @@ -28,7 +28,7 @@ export class RequestAISettingsAndWidgetsCreationUseCase const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); if (!connection) { - throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } this.setupResponseHeaders(response); diff --git a/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts b/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts index d1bc6106e..7722752b9 100644 --- a/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts +++ b/backend/src/entities/ai/use-cases/request-info-from-table-with-ai-v7.use.case.ts @@ -1,13 +1,5 @@ import { BaseMessage } from '@langchain/core/messages'; -import { - BadRequestException, - ForbiddenException, - Inject, - Injectable, - Logger, - NotFoundException, - Scope, -} from '@nestjs/common'; +import { BadRequestException, ForbiddenException, HttpStatus, Inject, Injectable, Logger, Scope } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js'; import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object.interface.js'; @@ -32,6 +24,7 @@ import { encodeError, encodeToToon } from '../../../ai-core/utils/toon-encoder.j import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; import { isConnectionTypeAgent } from '../../../helpers/is-connection-entity-agent.js'; @@ -479,7 +472,7 @@ export class RequestInfoFromTableWithAIUseCaseV7 ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } let userEmail = ''; diff --git a/backend/src/entities/cedar-authorization/cedar-permissions.service.ts b/backend/src/entities/cedar-authorization/cedar-permissions.service.ts index 544bd8903..ab4bd2388 100644 --- a/backend/src/entities/cedar-authorization/cedar-permissions.service.ts +++ b/backend/src/entities/cedar-authorization/cedar-permissions.service.ts @@ -1,9 +1,9 @@ import * as cedarWasm from '@cedar-policy/cedar-wasm/nodejs'; -import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; import { IGlobalDatabaseContext } from '../../common/application/global-database-context.interface.js'; import { BaseType } from '../../common/data-injection.tokens.js'; import { AccessLevelEnum } from '../../enums/access-level.enum.js'; -import { Messages } from '../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Cacher } from '../../helpers/cache/cacher.js'; import { GroupEntity } from '../group/group.entity.js'; import { ITablePermissionData } from '../permission/permission.interface.js'; @@ -368,7 +368,7 @@ export class CedarPermissionsService implements IUserAccessRepository { async getConnectionId(groupId: string): Promise { const group = await this.globalDbContext.groupRepository.findGroupByIdWithConnectionAndUsers(groupId); if (!group?.connection?.id) { - throw new HttpException({ message: Messages.CONNECTION_NOT_FOUND }, HttpStatus.BAD_REQUEST); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } return group.connection.id; } diff --git a/backend/src/entities/connection-properties/use-cases/create-connection-properties.use.case.ts b/backend/src/entities/connection-properties/use-cases/create-connection-properties.use.case.ts index 75b1022c3..a3150b834 100644 --- a/backend/src/entities/connection-properties/use-cases/create-connection-properties.use.case.ts +++ b/backend/src/entities/connection-properties/use-cases/create-connection-properties.use.case.ts @@ -1,9 +1,8 @@ import { HttpStatus, Inject, Injectable } from '@nestjs/common'; -import { HttpException } from '@nestjs/common/exceptions/http.exception.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; import { CreateConnectionPropertiesDs } from '../application/data-structures/create-connection-properties.ds.js'; import { FoundConnectionPropertiesDs } from '../application/data-structures/found-connection-properties.ds.js'; @@ -31,12 +30,7 @@ export class CreateConnectionPropertiesUseCase master_password ?? '', ); if (!foundConnection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.NOT_FOUND, - ); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } await validateCreateConnectionPropertiesDs(inputData, foundConnection); const newConnectionProperties = buildConnectionPropertiesEntity(inputData, foundConnection); diff --git a/backend/src/entities/connection-properties/use-cases/update-connection-properties.use.case.ts b/backend/src/entities/connection-properties/use-cases/update-connection-properties.use.case.ts index 49a32b9d4..798394d47 100644 --- a/backend/src/entities/connection-properties/use-cases/update-connection-properties.use.case.ts +++ b/backend/src/entities/connection-properties/use-cases/update-connection-properties.use.case.ts @@ -3,6 +3,7 @@ import { HttpException } from '@nestjs/common/exceptions/http.exception.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { CreateConnectionPropertiesDs } from '../application/data-structures/create-connection-properties.ds.js'; import { FoundConnectionPropertiesDs } from '../application/data-structures/found-connection-properties.ds.js'; @@ -34,12 +35,7 @@ export class UpdateConnectionPropertiesUseCase master_password ?? '', ); if (!foundConnection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.NOT_FOUND, - ); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } await validateCreateConnectionPropertiesDs(inputData, foundConnection); const connectionPropertiesToUpdate = diff --git a/backend/src/entities/connection/repository/custom-connection-repository-extension.ts b/backend/src/entities/connection/repository/custom-connection-repository-extension.ts index 51fcc3eb6..de98e4b2f 100644 --- a/backend/src/entities/connection/repository/custom-connection-repository-extension.ts +++ b/backend/src/entities/connection/repository/custom-connection-repository-extension.ts @@ -1,5 +1,8 @@ import { Repository } from 'typeorm'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { + MasterPasswordIncorrectError, + MasterPasswordMissingError, +} from '../../../exceptions/domain-errors/master-password.errors.js'; import { Constants } from '../../../helpers/constants/constants.js'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; import { isConnectionTypeAgent } from '../../../helpers/is-connection-entity-agent.js'; @@ -111,14 +114,14 @@ export const customConnectionRepositoryExtension: IConnectionRepository & await decryptConnectionCredentialsAsync(connection); if (connection.masterEncryption && !masterPwd) { - throw new Error(Messages.MASTER_PASSWORD_MISSING); + throw new MasterPasswordMissingError(); } if (connection.masterEncryption && masterPwd) { if (connection.master_hash) { const isMasterPwdCorrect = await Encryptor.verifyUserPassword(masterPwd, connection.master_hash); if (!isMasterPwdCorrect) { - throw new Error(Messages.MASTER_PASSWORD_INCORRECT); + throw new MasterPasswordIncorrectError(); } } connection = Encryptor.decryptConnectionCredentials(connection, masterPwd); diff --git a/backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts b/backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts index 563fa13d5..d07a1807e 100644 --- a/backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/create-group-in-connection.use.case.ts @@ -1,8 +1,9 @@ -import { BadRequestException, Inject, Injectable, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { AccessLevelEnum } from '../../../enums/access-level.enum.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { Cacher } from '../../../helpers/cache/cacher.js'; import { generateCedarPolicyForGroup } from '../../cedar-authorization/cedar-policy-generator.js'; @@ -31,7 +32,7 @@ export class CreateGroupInConnectionUseCase } = inputData; const connectionToUpdate = await this._dbContext.connectionRepository.findConnectionWithGroups(connectionId); if (!connectionToUpdate) { - throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } if (connectionToUpdate.groups.find((group) => group.title === title)) { throw new BadRequestException(Messages.GROUP_NAME_UNIQUE); diff --git a/backend/src/entities/connection/use-cases/delete-connection.use.case.ts b/backend/src/entities/connection/use-cases/delete-connection.use.case.ts index 8488f9e4c..a3cf4348a 100644 --- a/backend/src/entities/connection/use-cases/delete-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/delete-connection.use.case.ts @@ -1,7 +1,8 @@ -import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { DeleteConnectionDs } from '../application/data-structures/delete-connection.ds.js'; import { CreatedConnectionDTO } from '../application/dto/created-connection.dto.js'; @@ -26,7 +27,7 @@ export class DeleteConnectionUseCase inputData.masterPwd, ); if (!connectionToDelete) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const userNonTestConnections = await this._dbContext.connectionRepository.findAllUserNonTestsConnections( inputData.cognitoUserName, diff --git a/backend/src/entities/connection/use-cases/find-one-connection.use.case.ts b/backend/src/entities/connection/use-cases/find-one-connection.use.case.ts index b75de8976..198079a8e 100644 --- a/backend/src/entities/connection/use-cases/find-one-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/find-one-connection.use.case.ts @@ -1,9 +1,12 @@ -import { BadRequestException, HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; import { HttpException } from '@nestjs/common/exceptions/http.exception.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { AccessLevelEnum } from '../../../enums/access-level.enum.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; +import { MasterPasswordIncorrectException } from '../../../exceptions/custom-exceptions/master-password-incorrect-exception.js'; +import { MasterPasswordMissingException } from '../../../exceptions/custom-exceptions/master-password-missing-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { Constants } from '../../../helpers/constants/constants.js'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; @@ -33,7 +36,7 @@ export class FindOneConnectionUseCase protected async implementation(inputData: FindOneConnectionDs): Promise { const connection = await this._dbContext.connectionRepository.findOneConnection(inputData.connectionId); if (!connection) { - throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } const accessLevel: AccessLevelEnum = await this.cedarPermissions.getUserConnectionAccessLevel( inputData.cognitoUserName, @@ -41,25 +44,13 @@ export class FindOneConnectionUseCase ); if (connection.masterEncryption && !inputData.masterPwd) { - throw new HttpException( - { - message: Messages.MASTER_PASSWORD_MISSING, - type: 'no_master_key', - }, - HttpStatus.BAD_REQUEST, - ); + throw new MasterPasswordMissingException(); } if (connection.masterEncryption && inputData.masterPwd) { const isMaterPwdValid = await Encryptor.verifyUserPassword(inputData.masterPwd, connection.master_hash ?? ''); if (!isMaterPwdValid) { - throw new HttpException( - { - message: Messages.MASTER_PASSWORD_INCORRECT, - type: 'invalid_master_key', - }, - HttpStatus.BAD_REQUEST, - ); + throw new MasterPasswordIncorrectException(); } } diff --git a/backend/src/entities/connection/use-cases/get-connection-diagram.use.case.ts b/backend/src/entities/connection/use-cases/get-connection-diagram.use.case.ts index 922256f58..984b0c75f 100644 --- a/backend/src/entities/connection/use-cases/get-connection-diagram.use.case.ts +++ b/backend/src/entities/connection/use-cases/get-connection-diagram.use.case.ts @@ -1,4 +1,4 @@ -import { BadRequestException, HttpException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import { validateSchemaCache } from '@rocketadmin/shared-code/dist/src/caching/schema-cache-validator.js'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; @@ -8,6 +8,7 @@ import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/en import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; @@ -35,7 +36,7 @@ export class GetConnectionDiagramUseCase const { connectionId, masterPwd, userId } = inputData; const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); if (!connection) { - throw new HttpException({ message: Messages.CONNECTION_NOT_FOUND }, HttpStatus.BAD_REQUEST); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } if (!isSqlConnectionType(connection.type)) { throw new BadRequestException(Messages.DIAGRAM_NOT_SUPPORTED_FOR_CONNECTION_TYPE); diff --git a/backend/src/entities/connection/use-cases/get-permissions-for-group-in-connection.use.case.ts b/backend/src/entities/connection/use-cases/get-permissions-for-group-in-connection.use.case.ts index 74f7cf552..7958458ae 100644 --- a/backend/src/entities/connection/use-cases/get-permissions-for-group-in-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/get-permissions-for-group-in-connection.use.case.ts @@ -1,10 +1,10 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { AccessLevelEnum } from '../../../enums/access-level.enum.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { parseCedarPolicyToClassicalPermissions } from '../../cedar-authorization/cedar-policy-parser.js'; import { TablePermissionDs } from '../../permission/application/data-structures/create-permissions.ds.js'; import { FoundPermissionsInConnectionDs } from '../application/data-structures/found-permissions-in-connection.ds.js'; @@ -48,7 +48,7 @@ export class GetPermissionsForGroupInConnectionUseCase inputData.masterPwd, ); if (!connection) { - throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } const dao = getDataAccessObject(connection); const tables: Array = (await dao.getTablesFromDB()).map((table) => table.tableName); diff --git a/backend/src/entities/connection/use-cases/get-user-permissions-for-group-in-connection.use.case.ts b/backend/src/entities/connection/use-cases/get-user-permissions-for-group-in-connection.use.case.ts index c905846ee..5b6c9a847 100644 --- a/backend/src/entities/connection/use-cases/get-user-permissions-for-group-in-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/get-user-permissions-for-group-in-connection.use.case.ts @@ -1,9 +1,9 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { TablePermissionDs } from '../../permission/application/data-structures/create-permissions.ds.js'; import { FoundPermissionsInConnectionDs } from '../application/data-structures/found-permissions-in-connection.ds.js'; @@ -33,7 +33,7 @@ export class GetUserPermissionsForGroupInConnectionUseCase const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); if (!connection) { - throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } const dao = getDataAccessObject(connection); const tables: Array = (await dao.getTablesFromDB()).map((table) => table.tableName); diff --git a/backend/src/entities/connection/use-cases/preview-connection-diagram.use.case.ts b/backend/src/entities/connection/use-cases/preview-connection-diagram.use.case.ts index 2a47e3cb1..ed64c0e03 100644 --- a/backend/src/entities/connection/use-cases/preview-connection-diagram.use.case.ts +++ b/backend/src/entities/connection/use-cases/preview-connection-diagram.use.case.ts @@ -1,4 +1,4 @@ -import { BadRequestException, HttpException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import { validateSchemaCache } from '@rocketadmin/shared-code/dist/src/caching/schema-cache-validator.js'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; @@ -8,6 +8,7 @@ import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/en import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; @@ -36,7 +37,7 @@ export class PreviewConnectionDiagramUseCase const { connectionId, masterPwd, userId, sqlCommands } = inputData; const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); if (!connection) { - throw new HttpException({ message: Messages.CONNECTION_NOT_FOUND }, HttpStatus.BAD_REQUEST); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } if (!isSqlConnectionType(connection.type)) { throw new BadRequestException(Messages.DIAGRAM_NOT_SUPPORTED_FOR_CONNECTION_TYPE); diff --git a/backend/src/entities/connection/use-cases/restore-connection-use.case.ts b/backend/src/entities/connection/use-cases/restore-connection-use.case.ts index cfa84cff8..f0bfd5f13 100644 --- a/backend/src/entities/connection/use-cases/restore-connection-use.case.ts +++ b/backend/src/entities/connection/use-cases/restore-connection-use.case.ts @@ -3,6 +3,7 @@ import { HttpException } from '@nestjs/common/exceptions/http.exception.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { isConnectionEntityAgent } from '../../../helpers/is-connection-entity-agent.js'; import { RestoredConnectionDs } from '../application/data-structures/restored-connection.ds.js'; @@ -44,12 +45,7 @@ export class RestoreConnectionUseCase const foundConnection = await this._dbContext.connectionRepository.findOneById(connectionId); if (!foundConnection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } const hostCheckingResult = await isHostAllowed(connection_parameters); if (!hostCheckingResult) { @@ -75,12 +71,7 @@ export class RestoreConnectionUseCase where: { id: savedConnection.id }, }); if (!foundConnectionAfterSave) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } await decryptConnectionCredentialsAsync(foundConnectionAfterSave); const token = updatedConnection.agent?.token || null; diff --git a/backend/src/entities/connection/use-cases/unfreeze-connection.use.case.ts b/backend/src/entities/connection/use-cases/unfreeze-connection.use.case.ts index ccacb4297..4d6848162 100644 --- a/backend/src/entities/connection/use-cases/unfreeze-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/unfreeze-connection.use.case.ts @@ -1,8 +1,8 @@ -import { BadRequestException, Inject, Injectable, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { SuccessResponse } from '../../../microservices/saas-microservice/data-structures/common-responce.ds.js'; import { UnfreezeConnectionDs } from '../application/data-structures/unfreeze-connection.ds.js'; import { IUnfreezeConnection } from './use-cases.interfaces.js'; @@ -25,7 +25,7 @@ export class UnfreezeConnectionUseCase const connection = await this._dbContext.connectionRepository.findOne({ where: { id: connectionId } }); if (!connection) { - throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } // if (isSaaS()) { diff --git a/backend/src/entities/connection/use-cases/update-connection-master-password.use.case.ts b/backend/src/entities/connection/use-cases/update-connection-master-password.use.case.ts index bb85d38cc..b645df9b4 100644 --- a/backend/src/entities/connection/use-cases/update-connection-master-password.use.case.ts +++ b/backend/src/entities/connection/use-cases/update-connection-master-password.use.case.ts @@ -1,8 +1,8 @@ -import { BadRequestException, Inject, Injectable } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; import { SuccessResponse } from '../../../microservices/saas-microservice/data-structures/common-responce.ds.js'; import { UpdateMasterPasswordDs } from '../application/data-structures/update-master-password.ds.js'; @@ -24,7 +24,7 @@ export class UpdateConnectionMasterPasswordUseCase const { connectionId, newMasterPwd, oldMasterPwd } = inputData; let connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, oldMasterPwd); if (!connection) { - throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } connection = Encryptor.encryptConnectionCredentials(connection, newMasterPwd); connection.master_hash = await Encryptor.hashUserPassword(newMasterPwd); diff --git a/backend/src/entities/connection/use-cases/update-connection-title.use.case.ts b/backend/src/entities/connection/use-cases/update-connection-title.use.case.ts index 7cb37f6ee..2f83d45ba 100644 --- a/backend/src/entities/connection/use-cases/update-connection-title.use.case.ts +++ b/backend/src/entities/connection/use-cases/update-connection-title.use.case.ts @@ -1,8 +1,8 @@ -import { BadRequestException, Inject, Injectable, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { SuccessResponse } from '../../../microservices/saas-microservice/data-structures/common-responce.ds.js'; import { UpdateConnectionTitleDs } from '../application/data-structures/update-connection-title.ds.js'; import { IUpdateConnectionTitle } from './use-cases.interfaces.js'; @@ -24,7 +24,7 @@ export class UpdateConnectionTitleUseCase const connection = await this._dbContext.connectionRepository.findOne({ where: { id: connectionId } }); if (!connection) { - throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } connection.title = title; await this._dbContext.connectionRepository.save(connection); diff --git a/backend/src/entities/connection/use-cases/update-connection.use.case.ts b/backend/src/entities/connection/use-cases/update-connection.use.case.ts index fb0a73d7f..cf289345a 100644 --- a/backend/src/entities/connection/use-cases/update-connection.use.case.ts +++ b/backend/src/entities/connection/use-cases/update-connection.use.case.ts @@ -4,6 +4,8 @@ import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { AmplitudeEventTypeEnum } from '../../../enums/amplitude-event-type.enum.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; +import { MasterPasswordIncorrectException } from '../../../exceptions/custom-exceptions/master-password-incorrect-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { Constants } from '../../../helpers/constants/constants.js'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; @@ -44,12 +46,7 @@ export class UpdateConnectionUseCase masterPwd, ); if (!foundConnectionToUpdate) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.NOT_FOUND, - ); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const testConnectionsHosts = Constants.getTestConnectionsHostNamesArr(); @@ -93,7 +90,7 @@ export class UpdateConnectionUseCase if (updatedConnection.master_hash) { const isMasterPwdValid = await Encryptor.verifyUserPassword(masterPwd, updatedConnection.master_hash); if (!isMasterPwdValid) { - throw new HttpException({ message: Messages.MASTER_PASSWORD_INCORRECT }, HttpStatus.BAD_REQUEST); + throw new MasterPasswordIncorrectException(); } } updatedConnection = Encryptor.encryptConnectionCredentials(updatedConnection, masterPwd); diff --git a/backend/src/entities/connection/use-cases/validate-connection-master-password.use.case.ts b/backend/src/entities/connection/use-cases/validate-connection-master-password.use.case.ts index 07781bf30..220b1d7a3 100644 --- a/backend/src/entities/connection/use-cases/validate-connection-master-password.use.case.ts +++ b/backend/src/entities/connection/use-cases/validate-connection-master-password.use.case.ts @@ -1,7 +1,8 @@ -import { BadRequestException, Inject, Injectable, InternalServerErrorException, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; import { ValidateConnectionMasterPasswordDs } from '../application/data-structures/validate-connection-master-password.ds.js'; @@ -29,7 +30,7 @@ export class ValidateConnectionMasterPasswordUseCase }); if (!connection) { - throw new InternalServerErrorException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.INTERNAL_SERVER_ERROR); } if (!connection.masterEncryption) { diff --git a/backend/src/entities/custom-field/use-cases/create-custom-fields.use.case.ts b/backend/src/entities/custom-field/use-cases/create-custom-fields.use.case.ts index 9444e7fe5..a85957b30 100644 --- a/backend/src/entities/custom-field/use-cases/create-custom-fields.use.case.ts +++ b/backend/src/entities/custom-field/use-cases/create-custom-fields.use.case.ts @@ -1,8 +1,8 @@ -import { Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { CreateTableSettingsDs } from '../../table-settings/application/data-structures/create-table-settings.ds.js'; import { FoundTableSettingsDs } from '../../table-settings/application/data-structures/found-table-settings.ds.js'; import { TableSettingsEntity } from '../../table-settings/common-table-settings/table-settings.entity.js'; @@ -32,7 +32,7 @@ export class CreateCustomFieldsUseCase masterPwd, ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } await validateCreateCustomFieldDto(createFieldDto, foundConnection, tableName); const foundTableSettingToUpdate: TableSettingsEntity = diff --git a/backend/src/entities/custom-field/use-cases/update-custom-field.use.case.ts b/backend/src/entities/custom-field/use-cases/update-custom-field.use.case.ts index 769255421..8fd8942f5 100644 --- a/backend/src/entities/custom-field/use-cases/update-custom-field.use.case.ts +++ b/backend/src/entities/custom-field/use-cases/update-custom-field.use.case.ts @@ -3,6 +3,7 @@ import { HttpException } from '@nestjs/common/exceptions/http.exception.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { FoundCustomFieldsDs } from '../application/data-structures/found-custom-fields.ds.js'; import { UpdateCustomFieldsDs } from '../application/data-structures/update-custom-fields.ds.js'; @@ -30,12 +31,7 @@ export class UpdateCustomFieldUseCase masterPwd, ); if (!foundConnection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.NOT_FOUND, - ); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } await validateCreateCustomFieldDto(updateFieldDto, foundConnection, tableName); const fieldToUpdate = await this._dbContext.customFieldsRepository.findCustomFieldById(updateFieldDto.id); diff --git a/backend/src/entities/group/use-cases/saas-add-user-in-group-v2.use.case.ts b/backend/src/entities/group/use-cases/saas-add-user-in-group-v2.use.case.ts index 6fdb04660..a3329f208 100644 --- a/backend/src/entities/group/use-cases/saas-add-user-in-group-v2.use.case.ts +++ b/backend/src/entities/group/use-cases/saas-add-user-in-group-v2.use.case.ts @@ -2,6 +2,7 @@ import { HttpException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/co import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { EmailService } from '../../email/email/email.service.js'; import { AddUserInGroupWithSaaSDs } from '../application/data-sctructures/add-user-in-group.ds.js'; @@ -39,12 +40,7 @@ export class AddUserInGroupUseCase await this._dbContext.connectionRepository.getConnectionByGroupIdWithCompanyAndUsersInCompany(groupId); if (!foundConnection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.NOT_FOUND, - ); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } if (!foundConnection.company || !foundConnection.company.id) { diff --git a/backend/src/entities/group/use-cases/update-group-title.use.case.ts b/backend/src/entities/group/use-cases/update-group-title.use.case.ts index 10925f558..8595b810d 100644 --- a/backend/src/entities/group/use-cases/update-group-title.use.case.ts +++ b/backend/src/entities/group/use-cases/update-group-title.use.case.ts @@ -2,6 +2,7 @@ import { BadRequestException, HttpException, HttpStatus, Inject, Injectable, Sco import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { FoundGroupDataInfoDs } from '../application/data-sctructures/found-user-groups.ds.js'; import { UpdateGroupTitleDto } from '../dto/update-group-title.dto.js'; @@ -34,12 +35,7 @@ export class UpdateGroupTitleUseCase groupToUpdate.connection.id, ); if (!connectionWithGroups) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.NOT_FOUND, - ); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } if (connectionWithGroups.groups.find((group) => group.title === title)) { diff --git a/backend/src/entities/s3-widget/use-cases/get-s3-file-url.use.case.ts b/backend/src/entities/s3-widget/use-cases/get-s3-file-url.use.case.ts index 37eab0ea0..4713e5f92 100644 --- a/backend/src/entities/s3-widget/use-cases/get-s3-file-url.use.case.ts +++ b/backend/src/entities/s3-widget/use-cases/get-s3-file-url.use.case.ts @@ -7,6 +7,7 @@ import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { WidgetTypeEnum } from '../../../enums/widget-type.enum.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; import { buildCommonTableSettingsInput } from '../../table/utils/build-common-table-settings-input.util.js'; @@ -39,7 +40,7 @@ export class GetS3FileUrlUseCase const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); if (!connection) { - throw new HttpException({ message: Messages.CONNECTION_NOT_FOUND }, HttpStatus.BAD_REQUEST); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } const foundTableWidgets = await this._dbContext.tableWidgetsRepository.findTableWidgets(connectionId, tableName); diff --git a/backend/src/entities/table-actions/table-action-rules-module/use-cases/activate-actions-in-rule.use.case.ts b/backend/src/entities/table-actions/table-action-rules-module/use-cases/activate-actions-in-rule.use.case.ts index 37ec2f330..ecd36a30c 100644 --- a/backend/src/entities/table-actions/table-action-rules-module/use-cases/activate-actions-in-rule.use.case.ts +++ b/backend/src/entities/table-actions/table-action-rules-module/use-cases/activate-actions-in-rule.use.case.ts @@ -5,6 +5,7 @@ import { BaseType } from '../../../../common/data-injection.tokens.js'; import { LogOperationTypeEnum } from '../../../../enums/log-operation-type.enum.js'; import { OperationResultStatusEnum } from '../../../../enums/operation-result-status.enum.js'; import { TableActionEventEnum } from '../../../../enums/table-action-event-enum.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../../exceptions/text/messages.js'; import { TableLogsService } from '../../../table-logs/table-logs.service.js'; import { TableActionActivationService } from '../../table-actions-module/table-action-activation.service.js'; @@ -51,12 +52,7 @@ export class ActivateActionsInEventUseCase masterPwd, ); if (!foundConnection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.NOT_FOUND, - ); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } let locationFromResult: string | null = null; diff --git a/backend/src/entities/table-actions/table-action-rules-module/use-cases/create-action-rule.use.case.ts b/backend/src/entities/table-actions/table-action-rules-module/use-cases/create-action-rule.use.case.ts index 7fd8cd5ec..bc5d72ba0 100644 --- a/backend/src/entities/table-actions/table-action-rules-module/use-cases/create-action-rule.use.case.ts +++ b/backend/src/entities/table-actions/table-action-rules-module/use-cases/create-action-rule.use.case.ts @@ -1,9 +1,10 @@ -import { BadRequestException, Inject, Injectable, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; import { TableActionMethodEnum } from '../../../../enums/table-action-method-enum.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../../exceptions/text/messages.js'; import { isTest } from '../../../../helpers/app/is-test.js'; import { isActionUrlHostAllowed } from '../../../../helpers/validators/is-action-url-host-allowed.js'; @@ -40,7 +41,7 @@ export class CreateActionRuleUseCase const foundConnection = await this._dbContext.connectionRepository.findOne({ where: { id: connectionId } }); if (!foundConnection) { - throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } const newActionRule = buildEmptyActionRule(rule_data, foundConnection); const savedActionRule = await this._dbContext.actionRulesRepository.saveNewOrUpdatedActionRule(newActionRule); @@ -101,7 +102,7 @@ export class CreateActionRuleUseCase masterPwd, ); if (!foundConnection) { - throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } const dao = getDataAccessObject(foundConnection); const tablesInConnection = await dao.getTablesFromDB(); diff --git a/backend/src/entities/table-actions/table-action-rules-module/use-cases/update-action-rule-with-actions-and-events.use.case.ts b/backend/src/entities/table-actions/table-action-rules-module/use-cases/update-action-rule-with-actions-and-events.use.case.ts index 0be911651..105934c06 100644 --- a/backend/src/entities/table-actions/table-action-rules-module/use-cases/update-action-rule-with-actions-and-events.use.case.ts +++ b/backend/src/entities/table-actions/table-action-rules-module/use-cases/update-action-rule-with-actions-and-events.use.case.ts @@ -1,9 +1,10 @@ -import { BadRequestException, Inject, Injectable, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; import { TableActionMethodEnum } from '../../../../enums/table-action-method-enum.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../../exceptions/text/messages.js'; import { isTest } from '../../../../helpers/app/is-test.js'; import { isActionUrlHostAllowed } from '../../../../helpers/validators/is-action-url-host-allowed.js'; @@ -168,7 +169,7 @@ export class UpdateRuleUseCase masterPwd, ); if (!foundConnection) { - throw new BadRequestException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } const dao = getDataAccessObject(foundConnection); const tablesInConnection = await dao.getTablesFromDB(); diff --git a/backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts b/backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts index 1139f567e..632587225 100644 --- a/backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts +++ b/backend/src/entities/table-actions/table-actions-module/table-action-activation.service.ts @@ -7,6 +7,7 @@ import { Repository } from 'typeorm'; import { OperationResultStatusEnum } from '../../../enums/operation-result-status.enum.js'; import { TableActionEventEnum } from '../../../enums/table-action-event-enum.js'; import { TableActionMethodEnum } from '../../../enums/table-action-method-enum.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { isSaaS } from '../../../helpers/app/is-saas.js'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; @@ -195,12 +196,7 @@ export class TableActionActivationService { $$_tableName: tableName, }); if (!foundConnection.signing_key) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } const autoadminSignatureHeader = Encryptor.hashDataHMACexternalKey(foundConnection.signing_key, actionRequestBody); diff --git a/backend/src/entities/table-categories/use-cases/create-or-update-table-categories.use.case.ts b/backend/src/entities/table-categories/use-cases/create-or-update-table-categories.use.case.ts index 4b65cfed3..a13b8369f 100644 --- a/backend/src/entities/table-categories/use-cases/create-or-update-table-categories.use.case.ts +++ b/backend/src/entities/table-categories/use-cases/create-or-update-table-categories.use.case.ts @@ -1,8 +1,8 @@ -import { Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { CreateOrUpdateTableCategoriesDS } from '../data-sctructures/create-or-update-table-categories.ds.js'; import { FoundTableCategoryRo } from '../dto/found-table-category.ro.js'; import { buildFoundTableCategoryRo } from '../utils/build-found-table-category.ro.js'; @@ -32,7 +32,7 @@ export class CreateOrUpdateTableCategoriesUseCase master_password, ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } await validateTableCategories(filteredCategories, foundConnection); diff --git a/backend/src/entities/table-categories/use-cases/find-table-categories-with-tables.use.case.ts b/backend/src/entities/table-categories/use-cases/find-table-categories-with-tables.use.case.ts index a8da3d74f..e04437e34 100644 --- a/backend/src/entities/table-categories/use-cases/find-table-categories-with-tables.use.case.ts +++ b/backend/src/entities/table-categories/use-cases/find-table-categories-with-tables.use.case.ts @@ -1,11 +1,14 @@ -import { HttpException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { TableDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table.ds.js'; import * as Sentry from '@sentry/node'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; +import { MasterPasswordIncorrectException } from '../../../exceptions/custom-exceptions/master-password-incorrect-exception.js'; +import { MasterPasswordMissingException } from '../../../exceptions/custom-exceptions/master-password-missing-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; @@ -40,31 +43,14 @@ export class FindTableCategoriesWithTablesUseCase } catch (error) { const errMessage = getErrorMessage(error); if (errMessage === Messages.MASTER_PASSWORD_MISSING) { - throw new HttpException( - { - message: Messages.MASTER_PASSWORD_MISSING, - type: 'no_master_key', - }, - HttpStatus.BAD_REQUEST, - ); + throw new MasterPasswordMissingException(); } if (errMessage === Messages.MASTER_PASSWORD_INCORRECT) { - throw new HttpException( - { - message: Messages.MASTER_PASSWORD_INCORRECT, - type: 'invalid_master_key', - }, - HttpStatus.BAD_REQUEST, - ); + throw new MasterPasswordIncorrectException(); } } if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } const dao = getDataAccessObject(connection); const userEmail = await getUserEmailForAgent(connection, userId, this._dbContext.userRepository); diff --git a/backend/src/entities/table-filters/use-cases/create-table-filters.use.case.ts b/backend/src/entities/table-filters/use-cases/create-table-filters.use.case.ts index d7788a379..889c2753f 100644 --- a/backend/src/entities/table-filters/use-cases/create-table-filters.use.case.ts +++ b/backend/src/entities/table-filters/use-cases/create-table-filters.use.case.ts @@ -1,7 +1,8 @@ -import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { CreateTableFilterDs } from '../application/data-structures/create-table-filters.ds.js'; @@ -31,7 +32,7 @@ export class CreateTableFiltersUseCase masterPwd ?? '', ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } if (foundConnection.is_frozen) { throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); diff --git a/backend/src/entities/table-filters/use-cases/update-table-filters.use.case.ts b/backend/src/entities/table-filters/use-cases/update-table-filters.use.case.ts index e977420d0..4239d198e 100644 --- a/backend/src/entities/table-filters/use-cases/update-table-filters.use.case.ts +++ b/backend/src/entities/table-filters/use-cases/update-table-filters.use.case.ts @@ -1,7 +1,8 @@ -import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { UpdateTableFilterDs } from '../application/data-structures/update-table-filters.ds.js'; @@ -30,7 +31,7 @@ export class UpdateTableFiltersUseCase masterPwd, ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } if (foundConnection.is_frozen) { throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); diff --git a/backend/src/entities/table-schema/use-cases/approve-and-apply-schema-change.use-case.ts b/backend/src/entities/table-schema/use-cases/approve-and-apply-schema-change.use-case.ts index 421ed2a63..691753019 100644 --- a/backend/src/entities/table-schema/use-cases/approve-and-apply-schema-change.use-case.ts +++ b/backend/src/entities/table-schema/use-cases/approve-and-apply-schema-change.use-case.ts @@ -1,6 +1,7 @@ import { BadRequestException, ConflictException, + HttpStatus, Inject, Injectable, Logger, @@ -13,7 +14,7 @@ import { AICoreService } from '../../../ai-core/services/ai-core.service.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; import { ConnectionEntity } from '../../connection/connection.entity.js'; import { runSchemaChangeFixAiLoop } from '../ai/run-schema-change-fix-ai-loop.js'; @@ -80,7 +81,7 @@ export class ApproveAndApplySchemaChangeUseCase masterPassword ?? '', ); if (!connection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const isMongo = isMongoSchemaChangeType(change.changeType); diff --git a/backend/src/entities/table-schema/use-cases/approve-batch-schema-changes.use-case.ts b/backend/src/entities/table-schema/use-cases/approve-batch-schema-changes.use-case.ts index 76848198d..4b889acf2 100644 --- a/backend/src/entities/table-schema/use-cases/approve-batch-schema-changes.use-case.ts +++ b/backend/src/entities/table-schema/use-cases/approve-batch-schema-changes.use-case.ts @@ -1,6 +1,7 @@ import { BadRequestException, ConflictException, + HttpStatus, Inject, Injectable, Logger, @@ -13,7 +14,7 @@ import { AICoreService } from '../../../ai-core/services/ai-core.service.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; import { ConnectionEntity } from '../../connection/connection.entity.js'; import { runSchemaChangeFixAiLoop } from '../ai/run-schema-change-fix-ai-loop.js'; @@ -84,7 +85,7 @@ export class ApproveBatchSchemaChangesUseCase masterPassword ?? '', ); if (!connection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const applied: TableSchemaChangeEntity[] = []; diff --git a/backend/src/entities/table-schema/use-cases/generate-schema-change.use-case.ts b/backend/src/entities/table-schema/use-cases/generate-schema-change.use-case.ts index 30d365d6d..f7aeddae1 100644 --- a/backend/src/entities/table-schema/use-cases/generate-schema-change.use-case.ts +++ b/backend/src/entities/table-schema/use-cases/generate-schema-change.use-case.ts @@ -1,5 +1,5 @@ import { BaseMessage } from '@langchain/core/messages'; -import { BadRequestException, Inject, Injectable, Logger, NotFoundException, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, Logger, Scope } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js'; import Sentry from '@sentry/minimal'; @@ -10,6 +10,7 @@ import { MessageBuilder } from '../../../ai-core/utils/message-builder.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; import { MessageRole } from '../../ai/ai-conversation-history/ai-chat-messages/message-role.enum.js'; @@ -70,7 +71,7 @@ export class GenerateSchemaChangeUseCase masterPassword ?? '', ); if (!connection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const connectionType = connection.type as ConnectionTypesEnum; diff --git a/backend/src/entities/table-schema/use-cases/rollback-batch-schema-changes.use-case.ts b/backend/src/entities/table-schema/use-cases/rollback-batch-schema-changes.use-case.ts index c0124ee19..8e7187002 100644 --- a/backend/src/entities/table-schema/use-cases/rollback-batch-schema-changes.use-case.ts +++ b/backend/src/entities/table-schema/use-cases/rollback-batch-schema-changes.use-case.ts @@ -1,6 +1,7 @@ import { BadRequestException, ConflictException, + HttpStatus, Inject, Injectable, Logger, @@ -11,7 +12,7 @@ import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/en import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; import { RollbackBatchSchemaChangeDs } from '../application/data-structures/rollback-batch-schema-change.ds.js'; import { SchemaChangeBatchResponseDto } from '../application/data-transfer-objects/schema-change-batch-response.dto.js'; @@ -64,7 +65,7 @@ export class RollbackBatchSchemaChangesUseCase masterPassword ?? '', ); if (!connection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const failures: Array<{ changeId: string; error: string }> = []; diff --git a/backend/src/entities/table-schema/use-cases/rollback-schema-change.use-case.ts b/backend/src/entities/table-schema/use-cases/rollback-schema-change.use-case.ts index 62cc88f5c..534521b29 100644 --- a/backend/src/entities/table-schema/use-cases/rollback-schema-change.use-case.ts +++ b/backend/src/entities/table-schema/use-cases/rollback-schema-change.use-case.ts @@ -1,6 +1,7 @@ import { BadRequestException, ConflictException, + HttpStatus, Inject, Injectable, Logger, @@ -11,7 +12,7 @@ import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/en import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { RollbackSchemaChangeDs } from '../application/data-structures/rollback-schema-change.ds.js'; import { SchemaChangeResponseDto } from '../application/data-transfer-objects/schema-change-response.dto.js'; import { SchemaChangeStatusEnum, SchemaChangeTypeEnum } from '../table-schema-change-enums.js'; @@ -58,7 +59,7 @@ export class RollbackSchemaChangeUseCase masterPassword ?? '', ); if (!connection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } try { diff --git a/backend/src/entities/table-settings/common-table-settings/use-cases/create-table-settings.use.case.ts b/backend/src/entities/table-settings/common-table-settings/use-cases/create-table-settings.use.case.ts index 8271c4683..3fb0217b0 100644 --- a/backend/src/entities/table-settings/common-table-settings/use-cases/create-table-settings.use.case.ts +++ b/backend/src/entities/table-settings/common-table-settings/use-cases/create-table-settings.use.case.ts @@ -6,7 +6,7 @@ import { buildValidateTableSettingsDS } from '@rocketadmin/shared-code/dist/src/ import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; -import { Messages } from '../../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { toPrettyErrorsMsg } from '../../../../helpers/to-pretty-errors-msg.js'; import { CreateTableSettingsDs } from '../../application/data-structures/create-table-settings.ds.js'; import { FoundTableSettingsDs } from '../../application/data-structures/found-table-settings.ds.js'; @@ -33,12 +33,7 @@ export class CreateTableSettingsUseCase masterPwd ?? '', ); if (!foundConnection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.NOT_FOUND, - ); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const dao = getDataAccessObject(foundConnection); const tableSettingsDs: ValidateTableSettingsDS = buildValidateTableSettingsDS(inputData); diff --git a/backend/src/entities/table-settings/common-table-settings/use-cases/find-table-settings.use.case.ts b/backend/src/entities/table-settings/common-table-settings/use-cases/find-table-settings.use.case.ts index c1c423830..c471953ca 100644 --- a/backend/src/entities/table-settings/common-table-settings/use-cases/find-table-settings.use.case.ts +++ b/backend/src/entities/table-settings/common-table-settings/use-cases/find-table-settings.use.case.ts @@ -1,8 +1,8 @@ -import { Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; -import { Messages } from '../../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { FindTableSettingsDs } from '../../application/data-structures/find-table-settings.ds.js'; import { FoundTableSettingsDs } from '../../application/data-structures/found-table-settings.ds.js'; import { buildFoundTableSettingsDs } from '../utils/build-found-table-settings-ds.js'; @@ -31,7 +31,7 @@ export class FindTableSettingsUseCase masterPassword, ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } return buildFoundTableSettingsDs(tableSettings); diff --git a/backend/src/entities/table-settings/common-table-settings/use-cases/update-table-settings.use.case.ts b/backend/src/entities/table-settings/common-table-settings/use-cases/update-table-settings.use.case.ts index 34388bc9e..1997b0f9c 100644 --- a/backend/src/entities/table-settings/common-table-settings/use-cases/update-table-settings.use.case.ts +++ b/backend/src/entities/table-settings/common-table-settings/use-cases/update-table-settings.use.case.ts @@ -6,6 +6,7 @@ import { buildValidateTableSettingsDS } from '@rocketadmin/shared-code/dist/src/ import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../../exceptions/text/messages.js'; import { toPrettyErrorsMsg } from '../../../../helpers/to-pretty-errors-msg.js'; import { CreateTableSettingsDs } from '../../application/data-structures/create-table-settings.ds.js'; @@ -33,12 +34,7 @@ export class UpdateTableSettingsUseCase masterPwd ?? '', ); if (!foundConnection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.NOT_FOUND, - ); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const dao = getDataAccessObject(foundConnection); const tableSettingsDs: ValidateTableSettingsDS = buildValidateTableSettingsDS(inputData); diff --git a/backend/src/entities/table-settings/personal-table-settings/use-cases/create-update-personal-table-settings.use.case.ts b/backend/src/entities/table-settings/personal-table-settings/use-cases/create-update-personal-table-settings.use.case.ts index 966d08c19..e22e2ad4f 100644 --- a/backend/src/entities/table-settings/personal-table-settings/use-cases/create-update-personal-table-settings.use.case.ts +++ b/backend/src/entities/table-settings/personal-table-settings/use-cases/create-update-personal-table-settings.use.case.ts @@ -1,9 +1,9 @@ -import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; -import { Messages } from '../../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { ConnectionEntity } from '../../../connection/connection.entity.js'; import { CreatePersonalTableSettingsDs, @@ -38,7 +38,7 @@ export class CreateUpdatePersonalTableSettingsUseCase master_password, ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } await this.validatePersonalTableSettingsData(table_settings_data, foundConnection, table_name); diff --git a/backend/src/entities/table-settings/personal-table-settings/use-cases/find-personal-table-settings.use.case.ts b/backend/src/entities/table-settings/personal-table-settings/use-cases/find-personal-table-settings.use.case.ts index 4ecf29a0a..b6caf06bf 100644 --- a/backend/src/entities/table-settings/personal-table-settings/use-cases/find-personal-table-settings.use.case.ts +++ b/backend/src/entities/table-settings/personal-table-settings/use-cases/find-personal-table-settings.use.case.ts @@ -1,8 +1,8 @@ -import { Inject, Injectable, NotFoundException } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable } from '@nestjs/common'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; -import { Messages } from '../../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { FindPersonalTableSettingsDs } from '../data-structures/find-personal-table-settings.ds.js'; import { FoundPersonalTableSettingsDto } from '../dto/found-personal-table-settings.dto.js'; import { buildFoundTableSettingsDto } from '../utils/build-found-table-settings-dto.js'; @@ -29,7 +29,7 @@ export class FindPersonalTableSettingsUseCase ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const foundPersonalTableSettings = await this._dbContext.personalTableSettingsRepository.findUserTableSettings( diff --git a/backend/src/entities/table/table.controller.ts b/backend/src/entities/table/table.controller.ts index 03c1e5bbd..2e3bcb280 100644 --- a/backend/src/entities/table/table.controller.ts +++ b/backend/src/entities/table/table.controller.ts @@ -30,6 +30,7 @@ import { Timeout, TimeoutDefaults } from '../../decorators/timeout.decorator.js' import { UserId } from '../../decorators/user-id.decorator.js'; import { AmplitudeEventTypeEnum } from '../../enums/amplitude-event-type.enum.js'; import { InTransactionEnum } from '../../enums/in-transaction.enum.js'; +import { ConnectionNotFoundException } from '../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../exceptions/text/messages.js'; import { TableAddGuard } from '../../guards/table-add.guard.js'; import { TableDeleteGuard } from '../../guards/table-delete.guard.js'; @@ -835,12 +836,7 @@ export class TableController { const primaryKeys = []; const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } let userEmail = ''; if (isConnectionTypeAgent(connection.type)) { diff --git a/backend/src/entities/table/use-cases/add-row-in-table.use.case.ts b/backend/src/entities/table/use-cases/add-row-in-table.use.case.ts index f2d291987..7f1409fac 100644 --- a/backend/src/entities/table/use-cases/add-row-in-table.use.case.ts +++ b/backend/src/entities/table/use-cases/add-row-in-table.use.case.ts @@ -9,6 +9,7 @@ import { AmplitudeEventTypeEnum } from '../../../enums/amplitude-event-type.enum import { LogOperationTypeEnum } from '../../../enums/log-operation-type.enum.js'; import { OperationResultStatusEnum } from '../../../enums/operation-result-status.enum.js'; import { TableActionEventEnum } from '../../../enums/table-action-event-enum.js'; +import { TableNotFoundException } from '../../../exceptions/custom-exceptions/table-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; import { isObjectEmpty } from '../../../helpers/is-object-empty.js'; @@ -19,7 +20,7 @@ import { isTestConnectionUtil } from '../../connection/utils/is-test-connection- import { TableActionActivationService } from '../../table-actions/table-actions-module/table-action-activation.service.js'; import { TableLogsService } from '../../table-logs/table-logs.service.js'; import { AddRowInTableDs } from '../application/data-structures/add-row-in-table.ds.js'; -import { ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js'; +import { TableRowRODs } from '../table-datastructures.js'; import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js'; import { buildCommonTableSettingsInput } from '../utils/build-common-table-settings-input.util.js'; import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js'; @@ -65,12 +66,7 @@ export class AddRowInTableUseCase extends AbstractUseCase el.tableName === tableName); if (!isTableInConnection) { - throw new HttpException( - { - message: Messages.TABLE_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); + throw new TableNotFoundException(); } const isView = await dao.isView(tableName, userEmail); diff --git a/backend/src/entities/table/use-cases/bulk-update-rows-in-table.use.case.ts b/backend/src/entities/table/use-cases/bulk-update-rows-in-table.use.case.ts index 6cce40294..6ad6abe2c 100644 --- a/backend/src/entities/table/use-cases/bulk-update-rows-in-table.use.case.ts +++ b/backend/src/entities/table/use-cases/bulk-update-rows-in-table.use.case.ts @@ -7,6 +7,7 @@ import { BaseType } from '../../../common/data-injection.tokens.js'; import { LogOperationTypeEnum } from '../../../enums/log-operation-type.enum.js'; import { OperationResultStatusEnum } from '../../../enums/operation-result-status.enum.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; +import { PrimaryKeyMissingException } from '../../../exceptions/custom-exceptions/primary-key-missing-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; @@ -39,12 +40,7 @@ export class BulkUpdateRowsInTableUseCase let operationResult = OperationResultStatusEnum.unknown; if (!primaryKeys?.length) { - throw new HttpException( - { - message: Messages.PRIMARY_KEY_MISSING, - }, - HttpStatus.BAD_REQUEST, - ); + throw new PrimaryKeyMissingException(); } const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); diff --git a/backend/src/entities/table/use-cases/delete-row-from-table.use.case.ts b/backend/src/entities/table/use-cases/delete-row-from-table.use.case.ts index e9c5beddf..b73aef65d 100644 --- a/backend/src/entities/table/use-cases/delete-row-from-table.use.case.ts +++ b/backend/src/entities/table/use-cases/delete-row-from-table.use.case.ts @@ -10,6 +10,7 @@ import { OperationResultStatusEnum } from '../../../enums/operation-result-statu import { TableActionEventEnum } from '../../../enums/table-action-event-enum.js'; import { DeleteRowException } from '../../../exceptions/custom-exceptions/delete-row-exception.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; +import { PrimaryKeyMissingException } from '../../../exceptions/custom-exceptions/primary-key-missing-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { compareArrayElements } from '../../../helpers/compare-array-elements.js'; @@ -47,12 +48,7 @@ export class DeleteRowFromTableUseCase let operationResult = OperationResultStatusEnum.unknown; if (!primaryKey) { - throw new HttpException( - { - message: Messages.PRIMARY_KEY_MISSING, - }, - HttpStatus.BAD_REQUEST, - ); + throw new PrimaryKeyMissingException(); } const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); diff --git a/backend/src/entities/table/use-cases/delete-rows-from-table.use.case.ts b/backend/src/entities/table/use-cases/delete-rows-from-table.use.case.ts index 80d3a3803..1049677fc 100644 --- a/backend/src/entities/table/use-cases/delete-rows-from-table.use.case.ts +++ b/backend/src/entities/table/use-cases/delete-rows-from-table.use.case.ts @@ -7,6 +7,7 @@ import { BaseType } from '../../../common/data-injection.tokens.js'; import { AmplitudeEventTypeEnum } from '../../../enums/amplitude-event-type.enum.js'; import { LogOperationTypeEnum } from '../../../enums/log-operation-type.enum.js'; import { OperationResultStatusEnum } from '../../../enums/operation-result-status.enum.js'; +import { PrimaryKeyMissingException } from '../../../exceptions/custom-exceptions/primary-key-missing-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { compareArrayElements } from '../../../helpers/compare-array-elements.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; @@ -46,12 +47,7 @@ export class DeleteRowsFromTableUseCase // eslint-disable-next-line prefer-const let { connectionId, masterPwd, primaryKeys, tableName, userId } = inputData; if (!primaryKeys) { - throw new HttpException( - { - message: Messages.PRIMARY_KEY_MISSING, - }, - HttpStatus.BAD_REQUEST, - ); + throw new PrimaryKeyMissingException(); } const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); validateConnection(connection); diff --git a/backend/src/entities/table/use-cases/find-tables-in-connection-v2.use.case.ts b/backend/src/entities/table/use-cases/find-tables-in-connection-v2.use.case.ts index 79f98cd2c..9c792a671 100644 --- a/backend/src/entities/table/use-cases/find-tables-in-connection-v2.use.case.ts +++ b/backend/src/entities/table/use-cases/find-tables-in-connection-v2.use.case.ts @@ -6,7 +6,10 @@ import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { AmplitudeEventTypeEnum } from '../../../enums/amplitude-event-type.enum.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; +import { MasterPasswordIncorrectException } from '../../../exceptions/custom-exceptions/master-password-incorrect-exception.js'; +import { MasterPasswordMissingException } from '../../../exceptions/custom-exceptions/master-password-missing-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { isTest as isTestEnv } from '../../../helpers/app/is-test.js'; @@ -19,7 +22,7 @@ import { isTestConnectionUtil } from '../../connection/utils/is-test-connection- import { WinstonLogger } from '../../logging/winston-logger.js'; import { ITableAndViewPermissionData } from '../../permission/permission.interface.js'; import { FindTablesDs } from '../application/data-structures/find-tables.ds.js'; -import { FoundTableDs, FoundTablesWithCategoriesDS } from '../application/data-structures/found-table.ds.js'; +import { FoundTablesWithCategoriesDS } from '../application/data-structures/found-table.ds.js'; import { addDisplayNamesForTables } from '../utils/add-display-names-for-tables.util.js'; import { saveTableInfoInDatabase } from '../utils/save-table-info-in-database-orchestrator.util.js'; import { IFindTablesInConnectionV2 } from './table-use-cases.interface.js'; @@ -47,31 +50,14 @@ export class FindTablesInConnectionV2UseCase } catch (error) { const errMessage = getErrorMessage(error); if (errMessage === Messages.MASTER_PASSWORD_MISSING) { - throw new HttpException( - { - message: Messages.MASTER_PASSWORD_MISSING, - type: 'no_master_key', - }, - HttpStatus.BAD_REQUEST, - ); + throw new MasterPasswordMissingException(); } if (errMessage === Messages.MASTER_PASSWORD_INCORRECT) { - throw new HttpException( - { - message: Messages.MASTER_PASSWORD_INCORRECT, - type: 'invalid_master_key', - }, - HttpStatus.BAD_REQUEST, - ); + throw new MasterPasswordIncorrectException(); } } if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } const dao = getDataAccessObject(connection); let userEmail = ''; diff --git a/backend/src/entities/table/use-cases/find-tables-in-connection.use.case.ts b/backend/src/entities/table/use-cases/find-tables-in-connection.use.case.ts index 25634cc15..3da40da20 100644 --- a/backend/src/entities/table/use-cases/find-tables-in-connection.use.case.ts +++ b/backend/src/entities/table/use-cases/find-tables-in-connection.use.case.ts @@ -7,7 +7,10 @@ import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { AmplitudeEventTypeEnum } from '../../../enums/amplitude-event-type.enum.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; +import { MasterPasswordIncorrectException } from '../../../exceptions/custom-exceptions/master-password-incorrect-exception.js'; +import { MasterPasswordMissingException } from '../../../exceptions/custom-exceptions/master-password-missing-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { isTest as isTestEnv } from '../../../helpers/app/is-test.js'; @@ -48,31 +51,14 @@ export class FindTablesInConnectionUseCase } catch (error) { const errMessage = getErrorMessage(error); if (errMessage === Messages.MASTER_PASSWORD_MISSING) { - throw new HttpException( - { - message: Messages.MASTER_PASSWORD_MISSING, - type: 'no_master_key', - }, - HttpStatus.BAD_REQUEST, - ); + throw new MasterPasswordMissingException(); } if (errMessage === Messages.MASTER_PASSWORD_INCORRECT) { - throw new HttpException( - { - message: Messages.MASTER_PASSWORD_INCORRECT, - type: 'invalid_master_key', - }, - HttpStatus.BAD_REQUEST, - ); + throw new MasterPasswordIncorrectException(); } } if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } const dao = getDataAccessObject(connection); let userEmail = ''; diff --git a/backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts b/backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts index 2b1c2d356..7a2ea2faf 100644 --- a/backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts +++ b/backend/src/entities/table/use-cases/get-row-by-primary-key.use.case.ts @@ -8,6 +8,7 @@ import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; +import { PrimaryKeyMissingException } from '../../../exceptions/custom-exceptions/primary-key-missing-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { compareArrayElements } from '../../../helpers/compare-array-elements.js'; @@ -15,7 +16,7 @@ import { getErrorMessage } from '../../../helpers/get-error-message.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { buildActionEventDto } from '../../table-actions/table-action-rules-module/utils/build-found-action-event-dto.util.js'; import { GetRowByPrimaryKeyDs } from '../application/data-structures/get-row-by-primary-key.ds.js'; -import { ReferencedTableNamesAndColumnsDs, TableRowRODs } from '../table-datastructures.js'; +import { TableRowRODs } from '../table-datastructures.js'; import { attachForeignColumnNames } from '../utils/attach-foreign-column-names.util.js'; import { buildCommonTableSettingsInput } from '../utils/build-common-table-settings-input.util.js'; import { buildTableSettingsForResponse } from '../utils/build-table-settings-for-response.util.js'; @@ -49,12 +50,7 @@ export class GetRowByPrimaryKeyUseCase let { connectionId, masterPwd, primaryKey, tableName, userId } = inputData; const { uncached } = inputData; if (!primaryKey) { - throw new HttpException( - { - message: Messages.PRIMARY_KEY_MISSING, - }, - HttpStatus.BAD_REQUEST, - ); + throw new PrimaryKeyMissingException(); } const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); diff --git a/backend/src/entities/table/use-cases/get-table-rows.use.case.ts b/backend/src/entities/table/use-cases/get-table-rows.use.case.ts index f81e42fcc..8d07e2b16 100644 --- a/backend/src/entities/table/use-cases/get-table-rows.use.case.ts +++ b/backend/src/entities/table/use-cases/get-table-rows.use.case.ts @@ -1,8 +1,7 @@ -import { BadRequestException, HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; import { validateSchemaCache } from '@rocketadmin/shared-code/dist/src/caching/schema-cache-validator.js'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { ForeignKeyDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key.ds.js'; -import { ForeignKeyWithAutocompleteColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key-with-autocomplete-columns.ds.js'; import { TableSettingsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/table-settings.ds.js'; import { buildDAOsTableSettingsDs } from '@rocketadmin/shared-code/dist/src/helpers/data-structures-builders/table-settings.ds.builder.js'; import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js'; @@ -17,6 +16,7 @@ import { AmplitudeEventTypeEnum } from '../../../enums/amplitude-event-type.enum import { LogOperationTypeEnum } from '../../../enums/log-operation-type.enum.js'; import { OperationResultStatusEnum } from '../../../enums/operation-result-status.enum.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; +import { TableNotFoundException } from '../../../exceptions/custom-exceptions/table-not-found-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { hexToBinary, isBinary } from '../../../helpers/binary-to-hex.js'; @@ -74,7 +74,7 @@ export class GetTableRowsUseCase extends AbstractUseCase table.tableName); if (!tableNames.includes(tableName)) { - throw new BadRequestException(Messages.TABLE_NOT_FOUND); + throw new TableNotFoundException(); } const userEmail = await getUserEmailForAgent(connection, userId, this._dbContext.userRepository); diff --git a/backend/src/entities/table/use-cases/get-table-structure-without-cache.use.case.ts b/backend/src/entities/table/use-cases/get-table-structure-without-cache.use.case.ts index 67ff67260..4a33e37af 100644 --- a/backend/src/entities/table/use-cases/get-table-structure-without-cache.use.case.ts +++ b/backend/src/entities/table/use-cases/get-table-structure-without-cache.use.case.ts @@ -1,12 +1,12 @@ -import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { HttpException, Inject, Injectable } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { ForeignKeyWithAutocompleteColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key-with-autocomplete-columns.ds.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; +import { TableNotFoundException } from '../../../exceptions/custom-exceptions/table-not-found-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; -import { Messages } from '../../../exceptions/text/messages.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { buildFoundTableWidgetDs } from '../../widget/utils/build-found-table-widget-ds.js'; @@ -44,12 +44,7 @@ export class GetTableStructureWithoutCacheUseCase const dao = getDataAccessObject(foundConnection); const foundTalesInConnection = await dao.getTablesFromDB(); if (!foundTalesInConnection.find((el) => el.tableName === tableName)) { - throw new HttpException( - { - message: Messages.TABLE_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); + throw new TableNotFoundException(); } const userEmail = await getUserEmailForAgent(foundConnection, userId, this._dbContext.userRepository); diff --git a/backend/src/entities/table/use-cases/get-table-structure.use.case.ts b/backend/src/entities/table/use-cases/get-table-structure.use.case.ts index 310203947..fef1a9014 100644 --- a/backend/src/entities/table/use-cases/get-table-structure.use.case.ts +++ b/backend/src/entities/table/use-cases/get-table-structure.use.case.ts @@ -1,4 +1,4 @@ -import { HttpException, HttpStatus, Inject, Injectable } from '@nestjs/common'; +import { HttpException, Inject, Injectable } from '@nestjs/common'; import { validateSchemaCache } from '@rocketadmin/shared-code/dist/src/caching/schema-cache-validator.js'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { ForeignKeyWithAutocompleteColumnsDS } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/data-structures/foreign-key-with-autocomplete-columns.ds.js'; @@ -6,8 +6,8 @@ import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { ExceptionOperations } from '../../../exceptions/custom-exceptions/exception-operation.js'; +import { TableNotFoundException } from '../../../exceptions/custom-exceptions/table-not-found-exception.js'; import { UnknownSQLException } from '../../../exceptions/custom-exceptions/unknown-sql-exception.js'; -import { Messages } from '../../../exceptions/text/messages.js'; import { getErrorMessage } from '../../../helpers/get-error-message.js'; import { CedarPermissionsService } from '../../cedar-authorization/cedar-permissions.service.js'; import { buildFoundTableWidgetDs } from '../../widget/utils/build-found-table-widget-ds.js'; @@ -45,12 +45,7 @@ export class GetTableStructureUseCase const dao = getDataAccessObject(foundConnection); const foundTalesInConnection = await dao.getTablesFromDB(); if (!foundTalesInConnection.find((el) => el.tableName === tableName)) { - throw new HttpException( - { - message: Messages.TABLE_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); + throw new TableNotFoundException(); } const userEmail = await getUserEmailForAgent(foundConnection, userId, this._dbContext.userRepository); diff --git a/backend/src/entities/table/utils/validate-connection.util.ts b/backend/src/entities/table/utils/validate-connection.util.ts index 49307d120..f84f82529 100644 --- a/backend/src/entities/table/utils/validate-connection.util.ts +++ b/backend/src/entities/table/utils/validate-connection.util.ts @@ -1,4 +1,5 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; +import { HttpStatus } from '@nestjs/common'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { NonAvailableInFreePlanException } from '../../../exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { isConnectionTypeAgent } from '../../../helpers/is-connection-entity-agent.js'; @@ -6,12 +7,7 @@ import { ConnectionEntity } from '../../connection/connection.entity.js'; export function validateConnection(connection: ConnectionEntity | null): asserts connection is ConnectionEntity { if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.BAD_REQUEST, - ); + throw new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); } if (connection.is_frozen) { throw new NonAvailableInFreePlanException(Messages.CONNECTION_IS_FROZEN); diff --git a/backend/src/entities/visualizations/dashboard/use-cases/create-dashboard.use.case.ts b/backend/src/entities/visualizations/dashboard/use-cases/create-dashboard.use.case.ts index 598bd6e08..83e643bc3 100644 --- a/backend/src/entities/visualizations/dashboard/use-cases/create-dashboard.use.case.ts +++ b/backend/src/entities/visualizations/dashboard/use-cases/create-dashboard.use.case.ts @@ -1,8 +1,8 @@ -import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; -import { Messages } from '../../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { DashboardEntity } from '../dashboard.entity.js'; import { CreateDashboardDs } from '../data-structures/create-dashboard.ds.js'; import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; @@ -30,7 +30,7 @@ export class CreateDashboardUseCase ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const newDashboard = new DashboardEntity(); diff --git a/backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard.use.case.ts b/backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard.use.case.ts index bac08b56b..4327bc6a5 100644 --- a/backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard.use.case.ts +++ b/backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard.use.case.ts @@ -1,7 +1,8 @@ -import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../../exceptions/text/messages.js'; import { FindDashboardDs } from '../data-structures/find-dashboard.ds.js'; import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; @@ -29,7 +30,7 @@ export class DeleteDashboardUseCase ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const foundDashboard = await this._dbContext.dashboardRepository.findDashboardByIdAndConnectionId( diff --git a/backend/src/entities/visualizations/dashboard/use-cases/find-all-dashboards.use.case.ts b/backend/src/entities/visualizations/dashboard/use-cases/find-all-dashboards.use.case.ts index 1ebda4df1..3c7397753 100644 --- a/backend/src/entities/visualizations/dashboard/use-cases/find-all-dashboards.use.case.ts +++ b/backend/src/entities/visualizations/dashboard/use-cases/find-all-dashboards.use.case.ts @@ -1,10 +1,10 @@ -import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { CedarAction } from '../../../cedar-authorization/cedar-action-map.js'; import { CedarAuthorizationService } from '../../../cedar-authorization/cedar-authorization.service.js'; -import { Messages } from '../../../../exceptions/text/messages.js'; import { FindAllDashboardsDs } from '../data-structures/find-all-dashboards.ds.js'; import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; import { buildFoundDashboardDto } from '../utils/build-found-dashboard-dto.util.js'; @@ -32,7 +32,7 @@ export class FindAllDashboardsUseCase ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const dashboards = diff --git a/backend/src/entities/visualizations/dashboard/use-cases/find-dashboard.use.case.ts b/backend/src/entities/visualizations/dashboard/use-cases/find-dashboard.use.case.ts index 1c41ecfa7..e6d6d44d7 100644 --- a/backend/src/entities/visualizations/dashboard/use-cases/find-dashboard.use.case.ts +++ b/backend/src/entities/visualizations/dashboard/use-cases/find-dashboard.use.case.ts @@ -1,7 +1,8 @@ -import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../../exceptions/text/messages.js'; import { FindDashboardDs } from '../data-structures/find-dashboard.ds.js'; import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; @@ -29,7 +30,7 @@ export class FindDashboardUseCase ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const foundDashboard = await this._dbContext.dashboardRepository.findDashboardWithWidgets(dashboardId); diff --git a/backend/src/entities/visualizations/dashboard/use-cases/update-dashboard.use.case.ts b/backend/src/entities/visualizations/dashboard/use-cases/update-dashboard.use.case.ts index 3f8eb0423..6ff753da1 100644 --- a/backend/src/entities/visualizations/dashboard/use-cases/update-dashboard.use.case.ts +++ b/backend/src/entities/visualizations/dashboard/use-cases/update-dashboard.use.case.ts @@ -1,7 +1,8 @@ -import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../../exceptions/text/messages.js'; import { UpdateDashboardDs } from '../data-structures/update-dashboard.ds.js'; import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; @@ -29,7 +30,7 @@ export class UpdateDashboardUseCase ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const foundDashboard = await this._dbContext.dashboardRepository.findDashboardByIdAndConnectionId( diff --git a/backend/src/entities/visualizations/panel-position/use-cases/create-panel-position.use.case.ts b/backend/src/entities/visualizations/panel-position/use-cases/create-panel-position.use.case.ts index 28facaf11..6510d7b45 100644 --- a/backend/src/entities/visualizations/panel-position/use-cases/create-panel-position.use.case.ts +++ b/backend/src/entities/visualizations/panel-position/use-cases/create-panel-position.use.case.ts @@ -1,11 +1,12 @@ -import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../../exceptions/text/messages.js'; -import { PanelPositionEntity } from '../panel-position.entity.js'; import { CreatePanelPositionDs } from '../data-structures/create-panel-position.ds.js'; import { FoundPanelPositionDto } from '../dto/found-panel-position.dto.js'; +import { PanelPositionEntity } from '../panel-position.entity.js'; import { buildFoundPanelPositionDto } from '../utils/build-found-dashboard-widget-dto.util.js'; import { ICreatePanelPositionWidget } from './panel-position-use-cases.interface.js'; @@ -30,7 +31,7 @@ export class CreatePanelPositionUseCase ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const foundDashboard = await this._dbContext.dashboardRepository.findDashboardByIdAndConnectionId( diff --git a/backend/src/entities/visualizations/panel-position/use-cases/delete-panel-position.use.case.ts b/backend/src/entities/visualizations/panel-position/use-cases/delete-panel-position.use.case.ts index c6f5631b5..bf43a55ac 100644 --- a/backend/src/entities/visualizations/panel-position/use-cases/delete-panel-position.use.case.ts +++ b/backend/src/entities/visualizations/panel-position/use-cases/delete-panel-position.use.case.ts @@ -1,7 +1,8 @@ -import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../../exceptions/text/messages.js'; import { DeletePanelPositionDs } from '../data-structures/delete-panel-position.ds.js'; import { FoundPanelPositionDto } from '../dto/found-panel-position.dto.js'; @@ -29,7 +30,7 @@ export class DeletePanelPositionUseCase ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const foundDashboard = await this._dbContext.dashboardRepository.findDashboardByIdAndConnectionId( diff --git a/backend/src/entities/visualizations/panel-position/use-cases/generate-panel-position-with-ai.use.case.ts b/backend/src/entities/visualizations/panel-position/use-cases/generate-panel-position-with-ai.use.case.ts index d43730ee0..ac6986e91 100644 --- a/backend/src/entities/visualizations/panel-position/use-cases/generate-panel-position-with-ai.use.case.ts +++ b/backend/src/entities/visualizations/panel-position/use-cases/generate-panel-position-with-ai.use.case.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable, Logger, NotFoundException, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, Logger, Scope } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js'; import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object.interface.js'; @@ -14,7 +14,7 @@ import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; import { DashboardWidgetTypeEnum } from '../../../../enums/dashboard-widget-type.enum.js'; -import { Messages } from '../../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { getErrorMessage } from '../../../../helpers/get-error-message.js'; import { isConnectionTypeAgent } from '../../../../helpers/is-connection-entity-agent.js'; import { validateQuerySafety } from '../../panel/utils/check-query-is-safe.util.js'; @@ -83,7 +83,7 @@ export class GeneratePanelPositionWithAiUseCase ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const dao = getDataAccessObject(foundConnection); diff --git a/backend/src/entities/visualizations/panel-position/use-cases/generate-table-dashboard-with-ai.use.case.ts b/backend/src/entities/visualizations/panel-position/use-cases/generate-table-dashboard-with-ai.use.case.ts index b728b6945..09e0e97ce 100644 --- a/backend/src/entities/visualizations/panel-position/use-cases/generate-table-dashboard-with-ai.use.case.ts +++ b/backend/src/entities/visualizations/panel-position/use-cases/generate-table-dashboard-with-ai.use.case.ts @@ -1,4 +1,4 @@ -import { BadRequestException, Inject, Injectable, Logger, NotFoundException, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, Logger, Scope } from '@nestjs/common'; import { getDataAccessObject } from '@rocketadmin/shared-code/dist/src/data-access-layer/shared/create-data-access-object.js'; import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js'; import { IDataAccessObject } from '@rocketadmin/shared-code/dist/src/shared/interfaces/data-access-object.interface.js'; @@ -14,7 +14,7 @@ import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; import { DashboardWidgetTypeEnum } from '../../../../enums/dashboard-widget-type.enum.js'; -import { Messages } from '../../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { getErrorMessage } from '../../../../helpers/get-error-message.js'; import { isConnectionTypeAgent } from '../../../../helpers/is-connection-entity-agent.js'; import { DashboardEntity } from '../../dashboard/dashboard.entity.js'; @@ -98,7 +98,7 @@ export class GenerateTableDashboardWithAiUseCase ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const dao = getDataAccessObject(foundConnection); diff --git a/backend/src/entities/visualizations/panel-position/use-cases/update-panel-position.use.case.ts b/backend/src/entities/visualizations/panel-position/use-cases/update-panel-position.use.case.ts index ba0151fa9..1b3b8b73b 100644 --- a/backend/src/entities/visualizations/panel-position/use-cases/update-panel-position.use.case.ts +++ b/backend/src/entities/visualizations/panel-position/use-cases/update-panel-position.use.case.ts @@ -1,7 +1,8 @@ -import { BadRequestException, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { BadRequestException, HttpStatus, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../../exceptions/text/messages.js'; import { UpdatePanelPositionDs } from '../data-structures/update-panel-position.ds.js'; import { FoundPanelPositionDto } from '../dto/found-panel-position.dto.js'; @@ -30,7 +31,7 @@ export class UpdateDashboardWidgetUseCase ); if (!foundConnection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const foundDashboard = await this._dbContext.dashboardRepository.findDashboardByIdAndConnectionId( diff --git a/backend/src/entities/visualizations/panel/use-cases/create-panel.use.case.ts b/backend/src/entities/visualizations/panel/use-cases/create-panel.use.case.ts index 7154a40f0..be57a9e02 100644 --- a/backend/src/entities/visualizations/panel/use-cases/create-panel.use.case.ts +++ b/backend/src/entities/visualizations/panel/use-cases/create-panel.use.case.ts @@ -1,9 +1,9 @@ -import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import { ConnectionTypesEnum } from '@rocketadmin/shared-code/dist/src/shared/enums/connection-types-enum.js'; import AbstractUseCase from '../../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../../common/data-injection.tokens.js'; -import { Messages } from '../../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { CreatePanelDs } from '../data-structures/create-panel.ds.js'; import { FoundPanelDto } from '../dto/found-saved-db-query.dto.js'; import { PanelEntity } from '../panel.entity.js'; @@ -30,7 +30,7 @@ export class CreatePanelUseCase extends AbstractUseCase = await validateCreateWidgetsDs(widgets, userId, foundConnection, tableName, ''); if (errors.length > 0) { diff --git a/backend/src/entities/widget/use-cases/find-table-widgets.use.case.ts b/backend/src/entities/widget/use-cases/find-table-widgets.use.case.ts index eaebfc348..d97ab8804 100644 --- a/backend/src/entities/widget/use-cases/find-table-widgets.use.case.ts +++ b/backend/src/entities/widget/use-cases/find-table-widgets.use.case.ts @@ -3,6 +3,7 @@ import { HttpException } from '@nestjs/common/exceptions/http.exception.js'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { findTablesInConnectionUtil } from '../../table/utils/find-tables-in-connection.util.js'; import { FindTableWidgetsDs } from '../application/data-sctructures/find-table-widgets.ds.js'; @@ -26,12 +27,7 @@ export class FindTableWidgetsUseCase const { connectionId, masterPwd, tableName, userId } = inputData; const connection = await this._dbContext.connectionRepository.findAndDecryptConnection(connectionId, masterPwd); if (!connection) { - throw new HttpException( - { - message: Messages.CONNECTION_NOT_FOUND, - }, - HttpStatus.NOT_FOUND, - ); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const tablesInConnection = await findTablesInConnectionUtil(connection, userId, ''); if (!tablesInConnection.includes(tableName)) { diff --git a/backend/src/exceptions/all-exceptions.filter.ts b/backend/src/exceptions/all-exceptions.filter.ts index 00ab8d6f2..470cd1f20 100644 --- a/backend/src/exceptions/all-exceptions.filter.ts +++ b/backend/src/exceptions/all-exceptions.filter.ts @@ -2,13 +2,16 @@ import { ArgumentsHost, Catch, ExceptionFilter, HttpException, HttpStatus } from import Sentry from '@sentry/minimal'; import { WinstonLogger } from '../entities/logging/winston-logger.js'; import { getErrorMessage } from '../helpers/get-error-message.js'; +import { ExceptionType } from './custom-exceptions/exception-type.js'; +import { translateDomainError } from './domain-errors/translate-domain-error.js'; import { Messages } from './text/messages.js'; import { processExceptionMessage } from './utils/process-exception-message.js'; -export type ExceptionType = 'no_master_key' | 'invalid_master_key' | 'query_timeout'; +export { ExceptionType }; interface RocketadminException { - response?: { type?: string }; + type?: ExceptionType; + response?: { type?: ExceptionType }; originalMessage?: string; internalCode?: string | number; } @@ -27,13 +30,14 @@ export class AllExceptionsFilter implements ExceptionFilter { const ctx = host.switchToHttp(); const response = ctx.getResponse(); const request = ctx.getRequest(); - let text = getErrorMessage(exception); + const effective = translateDomainError(exception) ?? exception; + let text = getErrorMessage(effective); text = processExceptionMessage(text); - const meta = asRocketadminException(exception); - const type = meta.response?.type; + const meta = asRocketadminException(effective); + const type = meta.type ?? meta.response?.type; const originalMessage = meta.originalMessage; const internalCode = meta.internalCode; - const status = exception instanceof HttpException ? exception.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; + const status = effective instanceof HttpException ? effective.getStatus() : HttpStatus.INTERNAL_SERVER_ERROR; const sentryContextObject = { extra: { original_exception_message: originalMessage, diff --git a/backend/src/exceptions/custom-exceptions/base-rocketadmin.exception.ts b/backend/src/exceptions/custom-exceptions/base-rocketadmin.exception.ts new file mode 100644 index 000000000..8d05cce1c --- /dev/null +++ b/backend/src/exceptions/custom-exceptions/base-rocketadmin.exception.ts @@ -0,0 +1,22 @@ +import { HttpException, HttpStatus } from '@nestjs/common'; +import { ExceptionsInternalCodes } from './custom-exceptions-internal-codes/exceptions-internal-codes.js'; +import { ExceptionType } from './exception-type.js'; + +export interface RocketAdminExceptionOptions { + originalMessage?: string; + internalCode?: ExceptionsInternalCodes; + type?: ExceptionType; +} + +export class BaseRocketAdminException extends HttpException { + public readonly originalMessage?: string; + public readonly internalCode?: ExceptionsInternalCodes; + public readonly type?: ExceptionType; + + constructor(message: string, status: HttpStatus | number, options: RocketAdminExceptionOptions = {}) { + super(message, status); + this.originalMessage = options.originalMessage; + this.internalCode = options.internalCode; + this.type = options.type; + } +} diff --git a/backend/src/exceptions/custom-exceptions/connection-not-found-exception.ts b/backend/src/exceptions/custom-exceptions/connection-not-found-exception.ts new file mode 100644 index 000000000..1c50c5023 --- /dev/null +++ b/backend/src/exceptions/custom-exceptions/connection-not-found-exception.ts @@ -0,0 +1,12 @@ +import { HttpStatus } from '@nestjs/common'; +import { Messages } from '../text/messages.js'; +import { BaseRocketAdminException } from './base-rocketadmin.exception.js'; +import { ExceptionsInternalCodes } from './custom-exceptions-internal-codes/exceptions-internal-codes.js'; + +export class ConnectionNotFoundException extends BaseRocketAdminException { + constructor(status: HttpStatus = HttpStatus.NOT_FOUND) { + super(Messages.CONNECTION_NOT_FOUND, status, { + internalCode: ExceptionsInternalCodes.CONNECTION_NOT_FOUND, + }); + } +} diff --git a/backend/src/exceptions/custom-exceptions/custom-exceptions-internal-codes/exceptions-internal-codes.ts b/backend/src/exceptions/custom-exceptions/custom-exceptions-internal-codes/exceptions-internal-codes.ts index 7136e7747..a3afa4733 100644 --- a/backend/src/exceptions/custom-exceptions/custom-exceptions-internal-codes/exceptions-internal-codes.ts +++ b/backend/src/exceptions/custom-exceptions/custom-exceptions-internal-codes/exceptions-internal-codes.ts @@ -1,8 +1,61 @@ +/** + * Internal exception codes for the RocketAdmin backend. + * + * Codes are grouped into numeric ranges by domain so the frontend can branch either + * on a whole category (e.g. `code >= 1000 && code < 1100` → database error) or on a + * specific code. These numbers are a stable wire contract: never renumber an existing + * code, only append new ones inside the appropriate range. + * + * Ranges: + * 1000–1099 Database / SQL + * 1100–1199 Validation + * 1200–1299 Authentication / access + * 1300–1399 Not found + * 1400–1499 Subscription / billing + * 1500–1599 External services (SaaS gateway, agent) + */ export enum ExceptionsInternalCodes { + // --- 1000–1099: Database / SQL --- + /** Generic / uncategorized SQL error. Legacy value, kept for backward compatibility. */ UNKNOWN_SQL_EXCEPTION = 1000, - GET_TABLE_STRUCTURE_EXCEPTION = 1003, - GET_ROW_BY_PRIMARY_KEY_EXCEPTION = 1004, - GET_TABLES_EXCEPTION = 1005, - GET_TABLE_ROWS_EXCEPTION = 1006, + /** Foreign key constraint violation (PostgreSQL, MySQL, MSSQL, Oracle). */ + FOREIGN_KEY_VIOLATION = 1001, + /** Database user lacks permission to perform the operation. */ + DB_PERMISSION_DENIED = 1002, + /** Could not reach the database within the timeout window. */ + CONNECTION_TIMEOUT = 1003, + /** Database host could not be resolved or is unreachable (ENOTFOUND, ECONNREFUSED, etc.). */ + HOST_UNREACHABLE = 1004, + /** Failed to decrypt the connection — usually a wrong/missing master password. */ + DECRYPTION_FAILED = 1005, + /** Agent returned no data for the query. */ + AGENT_NO_DATA = 1006, + + // --- 1100–1199: Validation --- + /** DTO / request validation failure. Legacy value 1007, kept for backward compatibility. */ VALIDATOR_EXCEPTION = 1007, + /** Required primary key was missing from the request. */ + PRIMARY_KEY_MISSING = 1101, + + // --- 1200–1299: Authentication / access --- + /** Connection master password is required but was not provided. */ + MASTER_PASSWORD_MISSING = 1200, + /** Provided connection master password is incorrect. */ + MASTER_PASSWORD_INCORRECT = 1201, + /** Two-factor authentication is required to continue. */ + TWO_FA_REQUIRED = 1202, + + // --- 1300–1399: Not found --- + /** Connection entity was not found. */ + CONNECTION_NOT_FOUND = 1300, + /** Table was not found in the database. */ + TABLE_NOT_FOUND = 1301, + + // --- 1400–1499: Subscription / billing --- + /** Feature is not available on the current (free) plan. */ + FEATURE_NON_AVAILABLE_IN_FREE_PLAN = 1400, + + // --- 1500–1599: External services --- + /** An external dependency (SaaS gateway, agent) returned an error. */ + EXTERNAL_SERVICE_ERROR = 1500, } diff --git a/backend/src/exceptions/custom-exceptions/delete-row-exception.ts b/backend/src/exceptions/custom-exceptions/delete-row-exception.ts index 70efae676..5682ee513 100644 --- a/backend/src/exceptions/custom-exceptions/delete-row-exception.ts +++ b/backend/src/exceptions/custom-exceptions/delete-row-exception.ts @@ -1,14 +1,17 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; +import { HttpStatus } from '@nestjs/common'; +import { categorizeExceptionMessage } from '../utils/process-exception-message.js'; +import { BaseRocketAdminException } from './base-rocketadmin.exception.js'; import { ExceptionsInternalCodes } from './custom-exceptions-internal-codes/exceptions-internal-codes.js'; import { ErrorsMessages } from './messages/custom-errors-messages.js'; -export class DeleteRowException extends HttpException { - public readonly originalMessage: string; - public readonly internalCode: ExceptionsInternalCodes; +export class DeleteRowException extends BaseRocketAdminException { constructor(originalMessage: string) { - const readableMessage = ErrorsMessages.FAILED_TO_DELETE_ROW; - super(readableMessage, HttpStatus.INTERNAL_SERVER_ERROR); - this.originalMessage = originalMessage; - this.internalCode = ExceptionsInternalCodes.UNKNOWN_SQL_EXCEPTION; + const categorized = categorizeExceptionMessage(originalMessage); + const recognized = categorized.internalCode !== undefined; + super(recognized ? categorized.message : ErrorsMessages.FAILED_TO_DELETE_ROW, HttpStatus.INTERNAL_SERVER_ERROR, { + originalMessage, + internalCode: recognized ? categorized.internalCode : ExceptionsInternalCodes.UNKNOWN_SQL_EXCEPTION, + type: categorized.type, + }); } } diff --git a/backend/src/exceptions/custom-exceptions/exception-type.ts b/backend/src/exceptions/custom-exceptions/exception-type.ts new file mode 100644 index 000000000..02bb1f28c --- /dev/null +++ b/backend/src/exceptions/custom-exceptions/exception-type.ts @@ -0,0 +1,8 @@ +export type ExceptionType = + | 'no_master_key' + | 'invalid_master_key' + | 'query_timeout' + | 'foreign_key_violation' + | 'db_permission_denied' + | 'host_unreachable' + | 'decryption_failed'; diff --git a/backend/src/exceptions/custom-exceptions/external-service-exception.ts b/backend/src/exceptions/custom-exceptions/external-service-exception.ts new file mode 100644 index 000000000..0a4161d73 --- /dev/null +++ b/backend/src/exceptions/custom-exceptions/external-service-exception.ts @@ -0,0 +1,16 @@ +import { BaseRocketAdminException } from './base-rocketadmin.exception.js'; +import { ExceptionsInternalCodes } from './custom-exceptions-internal-codes/exceptions-internal-codes.js'; + +/** + * Raised when an external dependency (the SaaS gateway, the agent, ...) returns an error. + * The upstream HTTP status is preserved, and the upstream message — when available — is + * surfaced under `originalMessage`. + */ +export class ExternalServiceException extends BaseRocketAdminException { + constructor(message: string, status: number, originalMessage?: string) { + super(message, status, { + internalCode: ExceptionsInternalCodes.EXTERNAL_SERVICE_ERROR, + originalMessage, + }); + } +} diff --git a/backend/src/exceptions/custom-exceptions/master-password-incorrect-exception.ts b/backend/src/exceptions/custom-exceptions/master-password-incorrect-exception.ts new file mode 100644 index 000000000..1a5aad7da --- /dev/null +++ b/backend/src/exceptions/custom-exceptions/master-password-incorrect-exception.ts @@ -0,0 +1,13 @@ +import { HttpStatus } from '@nestjs/common'; +import { Messages } from '../text/messages.js'; +import { BaseRocketAdminException } from './base-rocketadmin.exception.js'; +import { ExceptionsInternalCodes } from './custom-exceptions-internal-codes/exceptions-internal-codes.js'; + +export class MasterPasswordIncorrectException extends BaseRocketAdminException { + constructor() { + super(Messages.MASTER_PASSWORD_INCORRECT, HttpStatus.BAD_REQUEST, { + internalCode: ExceptionsInternalCodes.MASTER_PASSWORD_INCORRECT, + type: 'invalid_master_key', + }); + } +} diff --git a/backend/src/exceptions/custom-exceptions/master-password-missing-exception.ts b/backend/src/exceptions/custom-exceptions/master-password-missing-exception.ts new file mode 100644 index 000000000..80f1dbcf6 --- /dev/null +++ b/backend/src/exceptions/custom-exceptions/master-password-missing-exception.ts @@ -0,0 +1,13 @@ +import { HttpStatus } from '@nestjs/common'; +import { Messages } from '../text/messages.js'; +import { BaseRocketAdminException } from './base-rocketadmin.exception.js'; +import { ExceptionsInternalCodes } from './custom-exceptions-internal-codes/exceptions-internal-codes.js'; + +export class MasterPasswordMissingException extends BaseRocketAdminException { + constructor() { + super(Messages.MASTER_PASSWORD_MISSING, HttpStatus.BAD_REQUEST, { + internalCode: ExceptionsInternalCodes.MASTER_PASSWORD_MISSING, + type: 'no_master_key', + }); + } +} diff --git a/backend/src/exceptions/custom-exceptions/non-available-in-free-plan-exception.ts b/backend/src/exceptions/custom-exceptions/non-available-in-free-plan-exception.ts index 754757eac..54df3347e 100644 --- a/backend/src/exceptions/custom-exceptions/non-available-in-free-plan-exception.ts +++ b/backend/src/exceptions/custom-exceptions/non-available-in-free-plan-exception.ts @@ -1,8 +1,12 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; +import { HttpStatus } from '@nestjs/common'; import { Messages } from '../text/messages.js'; +import { BaseRocketAdminException } from './base-rocketadmin.exception.js'; +import { ExceptionsInternalCodes } from './custom-exceptions-internal-codes/exceptions-internal-codes.js'; -export class NonAvailableInFreePlanException extends HttpException { +export class NonAvailableInFreePlanException extends BaseRocketAdminException { constructor(message: string = Messages.FEATURE_NON_AVAILABLE_IN_FREE_PLAN) { - super(message, HttpStatus.PAYMENT_REQUIRED); + super(message, HttpStatus.PAYMENT_REQUIRED, { + internalCode: ExceptionsInternalCodes.FEATURE_NON_AVAILABLE_IN_FREE_PLAN, + }); } } diff --git a/backend/src/exceptions/custom-exceptions/primary-key-missing-exception.ts b/backend/src/exceptions/custom-exceptions/primary-key-missing-exception.ts new file mode 100644 index 000000000..25c9de1ed --- /dev/null +++ b/backend/src/exceptions/custom-exceptions/primary-key-missing-exception.ts @@ -0,0 +1,12 @@ +import { HttpStatus } from '@nestjs/common'; +import { Messages } from '../text/messages.js'; +import { BaseRocketAdminException } from './base-rocketadmin.exception.js'; +import { ExceptionsInternalCodes } from './custom-exceptions-internal-codes/exceptions-internal-codes.js'; + +export class PrimaryKeyMissingException extends BaseRocketAdminException { + constructor(message: string = Messages.PRIMARY_KEY_MISSING) { + super(message, HttpStatus.BAD_REQUEST, { + internalCode: ExceptionsInternalCodes.PRIMARY_KEY_MISSING, + }); + } +} diff --git a/backend/src/exceptions/custom-exceptions/table-not-found-exception.ts b/backend/src/exceptions/custom-exceptions/table-not-found-exception.ts new file mode 100644 index 000000000..dd7ccc169 --- /dev/null +++ b/backend/src/exceptions/custom-exceptions/table-not-found-exception.ts @@ -0,0 +1,12 @@ +import { HttpStatus } from '@nestjs/common'; +import { Messages } from '../text/messages.js'; +import { BaseRocketAdminException } from './base-rocketadmin.exception.js'; +import { ExceptionsInternalCodes } from './custom-exceptions-internal-codes/exceptions-internal-codes.js'; + +export class TableNotFoundException extends BaseRocketAdminException { + constructor() { + super(Messages.TABLE_NOT_FOUND, HttpStatus.BAD_REQUEST, { + internalCode: ExceptionsInternalCodes.TABLE_NOT_FOUND, + }); + } +} diff --git a/backend/src/exceptions/custom-exceptions/two-fa-required-exception.ts b/backend/src/exceptions/custom-exceptions/two-fa-required-exception.ts new file mode 100644 index 000000000..d20b7b1a6 --- /dev/null +++ b/backend/src/exceptions/custom-exceptions/two-fa-required-exception.ts @@ -0,0 +1,12 @@ +import { HttpStatus } from '@nestjs/common'; +import { Messages } from '../text/messages.js'; +import { BaseRocketAdminException } from './base-rocketadmin.exception.js'; +import { ExceptionsInternalCodes } from './custom-exceptions-internal-codes/exceptions-internal-codes.js'; + +export class TwoFaRequiredException extends BaseRocketAdminException { + constructor() { + super(Messages.TWO_FA_REQUIRED, HttpStatus.BAD_REQUEST, { + internalCode: ExceptionsInternalCodes.TWO_FA_REQUIRED, + }); + } +} diff --git a/backend/src/exceptions/custom-exceptions/unknown-sql-exception.ts b/backend/src/exceptions/custom-exceptions/unknown-sql-exception.ts index ba9129958..8cfd97128 100644 --- a/backend/src/exceptions/custom-exceptions/unknown-sql-exception.ts +++ b/backend/src/exceptions/custom-exceptions/unknown-sql-exception.ts @@ -1,18 +1,33 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; +import { HttpStatus } from '@nestjs/common'; +import { categorizeExceptionMessage } from '../utils/process-exception-message.js'; +import { BaseRocketAdminException } from './base-rocketadmin.exception.js'; import { ExceptionsInternalCodes } from './custom-exceptions-internal-codes/exceptions-internal-codes.js'; +import { ExceptionType } from './exception-type.js'; -export class UnknownSQLException extends HttpException { - public readonly originalMessage: string; - public readonly internalCode: ExceptionsInternalCodes; +const GENERIC_SQL_MESSAGE = + 'It seems like something went wrong while processing your query. Please try again later or contact our support team.'; + +export class UnknownSQLException extends BaseRocketAdminException { constructor(originalMessage: string, operationType?: string) { - const additionalMessage = isAgentNoDataError(originalMessage) - ? 'No data returned from agent' - : `It seems like something went wrong while processing your query. Please try again later or contact our support team.`; - const readableMessage = `${operationType ? `${operationType} ` : ''}${additionalMessage}`; - super(readableMessage, HttpStatus.INTERNAL_SERVER_ERROR); - this.originalMessage = originalMessage; - this.internalCode = ExceptionsInternalCodes.UNKNOWN_SQL_EXCEPTION; + const { message, internalCode, type } = resolveSqlException(originalMessage); + const readableMessage = `${operationType ? `${operationType} ` : ''}${message}`; + super(readableMessage, HttpStatus.INTERNAL_SERVER_ERROR, { originalMessage, internalCode, type }); + } +} + +function resolveSqlException(originalMessage: string): { + message: string; + internalCode: ExceptionsInternalCodes; + type?: ExceptionType; +} { + if (isAgentNoDataError(originalMessage)) { + return { message: 'No data returned from agent', internalCode: ExceptionsInternalCodes.AGENT_NO_DATA }; + } + const categorized = categorizeExceptionMessage(originalMessage); + if (categorized.internalCode !== undefined) { + return { message: categorized.message, internalCode: categorized.internalCode, type: categorized.type }; } + return { message: GENERIC_SQL_MESSAGE, internalCode: ExceptionsInternalCodes.UNKNOWN_SQL_EXCEPTION }; } function isAgentNoDataError(originalMessage: string): boolean { diff --git a/backend/src/exceptions/custom-exceptions/validation-exception.ts b/backend/src/exceptions/custom-exceptions/validation-exception.ts index 2c29ec3cf..721c68dc4 100644 --- a/backend/src/exceptions/custom-exceptions/validation-exception.ts +++ b/backend/src/exceptions/custom-exceptions/validation-exception.ts @@ -1,11 +1,9 @@ -import { HttpException, HttpStatus } from '@nestjs/common'; +import { HttpStatus } from '@nestjs/common'; import { ValidationError } from 'class-validator'; +import { BaseRocketAdminException } from './base-rocketadmin.exception.js'; import { ExceptionsInternalCodes } from './custom-exceptions-internal-codes/exceptions-internal-codes.js'; -export class ValidationException extends HttpException { - public readonly originalMessage: string; - public readonly internalCode: ExceptionsInternalCodes; - +export class ValidationException extends BaseRocketAdminException { constructor(originalMessage: string | ValidationError[]) { if (Array.isArray(originalMessage)) { originalMessage = originalMessage @@ -16,7 +14,6 @@ export class ValidationException extends HttpException { }) .join('.\n'); } - super(originalMessage, HttpStatus.BAD_REQUEST); - this.internalCode = ExceptionsInternalCodes.VALIDATOR_EXCEPTION; + super(originalMessage, HttpStatus.BAD_REQUEST, { internalCode: ExceptionsInternalCodes.VALIDATOR_EXCEPTION }); } } diff --git a/backend/src/exceptions/domain-errors/master-password.errors.ts b/backend/src/exceptions/domain-errors/master-password.errors.ts new file mode 100644 index 000000000..028463eb9 --- /dev/null +++ b/backend/src/exceptions/domain-errors/master-password.errors.ts @@ -0,0 +1,15 @@ +import { Messages } from '../text/messages.js'; + +export class MasterPasswordMissingError extends Error { + constructor() { + super(Messages.MASTER_PASSWORD_MISSING); + this.name = 'MasterPasswordMissingError'; + } +} + +export class MasterPasswordIncorrectError extends Error { + constructor() { + super(Messages.MASTER_PASSWORD_INCORRECT); + this.name = 'MasterPasswordIncorrectError'; + } +} diff --git a/backend/src/exceptions/domain-errors/translate-domain-error.ts b/backend/src/exceptions/domain-errors/translate-domain-error.ts new file mode 100644 index 000000000..f5ffcfcc4 --- /dev/null +++ b/backend/src/exceptions/domain-errors/translate-domain-error.ts @@ -0,0 +1,14 @@ +import { HttpException } from '@nestjs/common'; +import { MasterPasswordIncorrectException } from '../custom-exceptions/master-password-incorrect-exception.js'; +import { MasterPasswordMissingException } from '../custom-exceptions/master-password-missing-exception.js'; +import { MasterPasswordIncorrectError, MasterPasswordMissingError } from './master-password.errors.js'; + +export function translateDomainError(exception: unknown): HttpException | null { + if (exception instanceof MasterPasswordMissingError) { + return new MasterPasswordMissingException(); + } + if (exception instanceof MasterPasswordIncorrectError) { + return new MasterPasswordIncorrectException(); + } + return null; +} diff --git a/backend/src/exceptions/utils/process-exception-message.ts b/backend/src/exceptions/utils/process-exception-message.ts index aab83a569..1f10f7708 100644 --- a/backend/src/exceptions/utils/process-exception-message.ts +++ b/backend/src/exceptions/utils/process-exception-message.ts @@ -1,36 +1,93 @@ +import { ExceptionsInternalCodes } from '../custom-exceptions/custom-exceptions-internal-codes/exceptions-internal-codes.js'; +import { ExceptionType } from '../custom-exceptions/exception-type.js'; import { PROCESSING_MESSAGES_FIND } from './processing-messages-find.js'; import { PROCESSING_MESSAGES_REPLACE } from './processing-messages-replace.js'; -export function processExceptionMessage(message: string): string { +export interface CategorizedExceptionMessage { + message: string; + internalCode?: ExceptionsInternalCodes; + type?: ExceptionType; +} + +export function categorizeExceptionMessage(message: string): CategorizedExceptionMessage { const msgInLowerCase = message.toLowerCase(); switch (true) { case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.ETIMEDOUT) && msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.AMAZON_HOSTNAME): - return PROCESSING_MESSAGES_REPLACE.ALLOW_CONNECTIONS; + return { + message: PROCESSING_MESSAGES_REPLACE.ALLOW_CONNECTIONS, + internalCode: ExceptionsInternalCodes.HOST_UNREACHABLE, + type: 'host_unreachable', + }; case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.ETIMEDOUT): + return { + message: PROCESSING_MESSAGES_REPLACE.getaddrinfo_ENOTFOUND, + internalCode: ExceptionsInternalCodes.CONNECTION_TIMEOUT, + type: 'query_timeout', + }; case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.getaddrinfo_ENOTFOUND): case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.ECONNREFUSED): case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.EHOSTUNREACH): case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.ECONNRESET): case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.SERVER_CLOSED): - return PROCESSING_MESSAGES_REPLACE.getaddrinfo_ENOTFOUND; + return { + message: PROCESSING_MESSAGES_REPLACE.getaddrinfo_ENOTFOUND, + internalCode: ExceptionsInternalCodes.HOST_UNREACHABLE, + type: 'host_unreachable', + }; case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.ORA_02292): - return PROCESSING_MESSAGES_REPLACE.ORA_02292; + return { + message: PROCESSING_MESSAGES_REPLACE.ORA_02292, + internalCode: ExceptionsInternalCodes.FOREIGN_KEY_VIOLATION, + type: 'foreign_key_violation', + }; case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.NO_PGHBA_ENTRY): - return PROCESSING_MESSAGES_REPLACE.NO_PGHBA_ENTRY; + return { + message: PROCESSING_MESSAGES_REPLACE.NO_PGHBA_ENTRY, + internalCode: ExceptionsInternalCodes.DB_PERMISSION_DENIED, + type: 'db_permission_denied', + }; case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.UPDATE_COMMAND_DENIED): - return PROCESSING_MESSAGES_REPLACE.UPDATE_COMMAND_DENIED; + return { + message: PROCESSING_MESSAGES_REPLACE.UPDATE_COMMAND_DENIED, + internalCode: ExceptionsInternalCodes.DB_PERMISSION_DENIED, + type: 'db_permission_denied', + }; case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.VIOLATES_FOREIGN_CONSTRAINT_PG): - return PROCESSING_MESSAGES_REPLACE.VIOLATES_FOREIGN_CONSTRAINT_PG(msgInLowerCase); + return { + message: PROCESSING_MESSAGES_REPLACE.VIOLATES_FOREIGN_CONSTRAINT_PG(msgInLowerCase), + internalCode: ExceptionsInternalCodes.FOREIGN_KEY_VIOLATION, + type: 'foreign_key_violation', + }; case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.VIOLATES_FOREIGN_CONSTRAINT_MYSQL): - return PROCESSING_MESSAGES_REPLACE.VIOLATES_FOREIGN_CONSTRAINT_MYSQL(msgInLowerCase); + return { + message: PROCESSING_MESSAGES_REPLACE.VIOLATES_FOREIGN_CONSTRAINT_MYSQL(msgInLowerCase), + internalCode: ExceptionsInternalCodes.FOREIGN_KEY_VIOLATION, + type: 'foreign_key_violation', + }; case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.VIOLATES_FOREIGN_CONSTRAINT_MSSQL): - return PROCESSING_MESSAGES_REPLACE.VIOLATES_FOREIGN_CONSTRAINT_MSSQL(msgInLowerCase); + return { + message: PROCESSING_MESSAGES_REPLACE.VIOLATES_FOREIGN_CONSTRAINT_MSSQL(msgInLowerCase), + internalCode: ExceptionsInternalCodes.FOREIGN_KEY_VIOLATION, + type: 'foreign_key_violation', + }; case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.SELECT_COMMAND_DENIED_MYSQL): - return PROCESSING_MESSAGES_REPLACE.SELECT_COMMAND_DENIED_MYSQL(msgInLowerCase); + return { + message: PROCESSING_MESSAGES_REPLACE.SELECT_COMMAND_DENIED_MYSQL(msgInLowerCase), + internalCode: ExceptionsInternalCodes.DB_PERMISSION_DENIED, + type: 'db_permission_denied', + }; case msgInLowerCase.includes(PROCESSING_MESSAGES_FIND.Malformed_UTF_data): - return PROCESSING_MESSAGES_REPLACE.Malformed_UTF_data; + return { + message: PROCESSING_MESSAGES_REPLACE.Malformed_UTF_data, + internalCode: ExceptionsInternalCodes.DECRYPTION_FAILED, + type: 'decryption_failed', + }; default: - return message; + return { message }; } } + +export function processExceptionMessage(message: string): string { + return categorizeExceptionMessage(message).message; +} diff --git a/backend/src/guards/tables-receive.guard.ts b/backend/src/guards/tables-receive.guard.ts index 212b4fe2e..218ac08e9 100644 --- a/backend/src/guards/tables-receive.guard.ts +++ b/backend/src/guards/tables-receive.guard.ts @@ -2,6 +2,7 @@ import { BadRequestException, CanActivate, ExecutionContext, + HttpStatus, Inject, Injectable, UnauthorizedException, @@ -10,6 +11,7 @@ import { Observable } from 'rxjs'; import { IRequestWithCognitoInfo } from '../authorization/cognito-decoded.interface.js'; import { IGlobalDatabaseContext } from '../common/application/global-database-context.interface.js'; import { BaseType } from '../common/data-injection.tokens.js'; +import { ConnectionNotFoundException } from '../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../exceptions/text/messages.js'; import { ValidationHelper } from '../helpers/validators/validation-helper.js'; import { validateUuidByRegex } from './utils/validate-uuid-by-regex.js'; @@ -49,7 +51,7 @@ export class TablesReceiveGuard implements CanActivate { resolve(canUserReadTables); return; } else { - reject(new BadRequestException(Messages.CONNECTION_NOT_FOUND)); + reject(new ConnectionNotFoundException(HttpStatus.BAD_REQUEST)); } }); } diff --git a/backend/src/microservices/gateways/saas-gateway.ts/saas-company-gateway.service.ts b/backend/src/microservices/gateways/saas-gateway.ts/saas-company-gateway.service.ts index ad7f1757d..1d6a91595 100644 --- a/backend/src/microservices/gateways/saas-gateway.ts/saas-company-gateway.service.ts +++ b/backend/src/microservices/gateways/saas-gateway.ts/saas-company-gateway.service.ts @@ -1,4 +1,5 @@ -import { HttpException, Injectable } from '@nestjs/common'; +import { Injectable } from '@nestjs/common'; +import { ExternalServiceException } from '../../../exceptions/custom-exceptions/external-service-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { isSaaS } from '../../../helpers/app/is-saas.js'; import { isObjectEmpty } from '../../../helpers/is-object-empty.js'; @@ -25,12 +26,10 @@ export class SaasCompanyGatewayService extends BaseSaasGatewayService { return null; } if (result.status > 299) { - throw new HttpException( - { - message: Messages.SAAS_DELETE_COMPANY_FAILED_UNHANDLED_ERROR, - originalMessage: result?.body?.message ? result.body.message : undefined, - }, + throw new ExternalServiceException( + Messages.SAAS_DELETE_COMPANY_FAILED_UNHANDLED_ERROR, result.status, + result?.body?.message ? (result.body.message as string) : undefined, ); } if (!isObjectEmpty(result.body)) { @@ -47,12 +46,10 @@ export class SaasCompanyGatewayService extends BaseSaasGatewayService { return null; } if (result.status > 299) { - throw new HttpException( - { - message: Messages.SAAS_GET_COMPANY_ID_BY_CUSTOM_DOMAIN_FAILED_UNHANDLED_ERROR, - originalMessage: result?.body?.message ? result.body.message : undefined, - }, + throw new ExternalServiceException( + Messages.SAAS_GET_COMPANY_ID_BY_CUSTOM_DOMAIN_FAILED_UNHANDLED_ERROR, result.status, + result?.body?.message ? (result.body.message as string) : undefined, ); } if (!isObjectEmpty(result.body)) { @@ -70,12 +67,10 @@ export class SaasCompanyGatewayService extends BaseSaasGatewayService { return null; } if (result.status > 299) { - throw new HttpException( - { - message: Messages.SAAS_GET_COMPANY_CUSTOM_DOMAIN_BY_ID_FAILED_UNHANDLED_ERROR, - originalMessage: result?.body?.message ? result.body.message : undefined, - }, + throw new ExternalServiceException( + Messages.SAAS_GET_COMPANY_CUSTOM_DOMAIN_BY_ID_FAILED_UNHANDLED_ERROR, result.status, + result?.body?.message ? (result.body.message as string) : undefined, ); } if (!isObjectEmpty(result.body)) { @@ -93,12 +88,10 @@ export class SaasCompanyGatewayService extends BaseSaasGatewayService { return null; } if (result.status > 299) { - throw new HttpException( - { - message: Messages.SAAS_RECOUNT_USERS_IN_COMPANY_FAILED_UNHANDLED_ERROR, - originalMessage: result?.body?.message ? result.body.message : undefined, - }, + throw new ExternalServiceException( + Messages.SAAS_RECOUNT_USERS_IN_COMPANY_FAILED_UNHANDLED_ERROR, result.status, + result?.body?.message ? (result.body.message as string) : undefined, ); } if (!isObjectEmpty(result.body)) { diff --git a/backend/src/microservices/saas-microservice/use-cases/delete-connection-for-hosted-db.use.case.ts b/backend/src/microservices/saas-microservice/use-cases/delete-connection-for-hosted-db.use.case.ts index 45ee6671a..368087b20 100644 --- a/backend/src/microservices/saas-microservice/use-cases/delete-connection-for-hosted-db.use.case.ts +++ b/backend/src/microservices/saas-microservice/use-cases/delete-connection-for-hosted-db.use.case.ts @@ -1,9 +1,10 @@ -import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { CreatedConnectionDTO } from '../../../entities/connection/application/dto/created-connection.dto.js'; import { buildCreatedConnectionDs } from '../../../entities/connection/utils/build-created-connection.ds.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { DeleteConnectionForHostedDbDto } from '../data-structures/delete-connection-for-hosted-db.dto.js'; import { IDeleteConnectionForHostedDb } from './saas-use-cases.interface.js'; @@ -28,7 +29,7 @@ export class DeleteConnectionForHostedDbUseCase '', ); if (!connectionToDelete) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } const foundCompany = diff --git a/backend/src/microservices/saas-microservice/use-cases/get-hosted-connection-credentials.use.case.ts b/backend/src/microservices/saas-microservice/use-cases/get-hosted-connection-credentials.use.case.ts index 476a46da5..1880b0933 100644 --- a/backend/src/microservices/saas-microservice/use-cases/get-hosted-connection-credentials.use.case.ts +++ b/backend/src/microservices/saas-microservice/use-cases/get-hosted-connection-credentials.use.case.ts @@ -1,8 +1,8 @@ -import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; -import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { GetHostedConnectionCredentialsDto } from '../data-structures/get-hosted-connection-credentials.dto.js'; import { HostedConnectionCredentialsRO } from '../data-structures/hosted-connection-credentials.ro.js'; import { IGetHostedConnectionCredentials } from './saas-use-cases.interface.js'; @@ -22,7 +22,7 @@ export class GetHostedConnectionCredentialsUseCase protected async implementation(inputData: GetHostedConnectionCredentialsDto): Promise { const connection = await this._dbContext.connectionRepository.findOneConnection(inputData.hostedDatabaseId); if (!connection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } return { diff --git a/backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts b/backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts index 8f38ca904..26217d060 100644 --- a/backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts +++ b/backend/src/microservices/saas-microservice/use-cases/update-hosted-connection-password.use.case.ts @@ -1,8 +1,9 @@ -import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; +import { HttpStatus, Inject, Injectable, NotFoundException, Scope } from '@nestjs/common'; import AbstractUseCase from '../../../common/abstract-use.case.js'; import { IGlobalDatabaseContext } from '../../../common/application/global-database-context.interface.js'; import { BaseType } from '../../../common/data-injection.tokens.js'; import { decryptConnectionsCredentialsAsync } from '../../../entities/connection/utils/decrypt-connection-credentials-async.js'; +import { ConnectionNotFoundException } from '../../../exceptions/custom-exceptions/connection-not-found-exception.js'; import { Messages } from '../../../exceptions/text/messages.js'; import { SuccessResponse } from '../data-structures/common-responce.ds.js'; import { UpdateHostedConnectionPasswordDto } from '../data-structures/update-hosted-connection-password.dto.js'; @@ -35,7 +36,7 @@ export class UpdateHostedConnectionPasswordUseCase await decryptConnectionsCredentialsAsync(companyConnections); const connection = companyConnections.find((conn) => conn.database === databaseName); if (!connection) { - throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + throw new ConnectionNotFoundException(HttpStatus.NOT_FOUND); } connection.password = password; diff --git a/backend/test/ava-tests/unit-tests/categorize-exception-message.test.ts b/backend/test/ava-tests/unit-tests/categorize-exception-message.test.ts new file mode 100644 index 000000000..7caee42b6 --- /dev/null +++ b/backend/test/ava-tests/unit-tests/categorize-exception-message.test.ts @@ -0,0 +1,120 @@ +import test from 'ava'; +import { ExceptionsInternalCodes } from '../../../src/exceptions/custom-exceptions/custom-exceptions-internal-codes/exceptions-internal-codes.js'; +import { ExceptionType } from '../../../src/exceptions/custom-exceptions/exception-type.js'; +import { + categorizeExceptionMessage, + processExceptionMessage, +} from '../../../src/exceptions/utils/process-exception-message.js'; + +interface Case { + name: string; + raw: string; + code: ExceptionsInternalCodes; + type: ExceptionType; +} + +const CASES: Array = [ + { + name: 'AWS host + timeout -> host unreachable', + raw: 'connect ETIMEDOUT mydb.abcdef.us-east-1.rds.amazonaws.com:5432', + code: ExceptionsInternalCodes.HOST_UNREACHABLE, + type: 'host_unreachable', + }, + { + name: 'plain timeout -> connection timeout', + raw: 'connect ETIMEDOUT 10.0.0.5:5432', + code: ExceptionsInternalCodes.CONNECTION_TIMEOUT, + type: 'query_timeout', + }, + { + name: 'getaddrinfo ENOTFOUND -> host unreachable', + raw: 'getaddrinfo ENOTFOUND nonexistent.host', + code: ExceptionsInternalCodes.HOST_UNREACHABLE, + type: 'host_unreachable', + }, + { + name: 'ECONNREFUSED -> host unreachable', + raw: 'connect ECONNREFUSED 127.0.0.1:5432', + code: ExceptionsInternalCodes.HOST_UNREACHABLE, + type: 'host_unreachable', + }, + { + name: 'ORA-02292 -> foreign key violation', + raw: 'ORA-02292: integrity constraint (SCHEMA.FK_CHILD) violated - child record found', + code: ExceptionsInternalCodes.FOREIGN_KEY_VIOLATION, + type: 'foreign_key_violation', + }, + { + name: 'no pg_hba.conf entry -> db permission denied', + raw: 'no pg_hba.conf entry for host "1.2.3.4", user "u", database "d", no encryption', + code: ExceptionsInternalCodes.DB_PERMISSION_DENIED, + type: 'db_permission_denied', + }, + { + name: 'UPDATE command denied -> db permission denied', + raw: "UPDATE command denied to user 'reader'@'%' for table 'orders'", + code: ExceptionsInternalCodes.DB_PERMISSION_DENIED, + type: 'db_permission_denied', + }, + { + name: 'PG foreign key constraint -> foreign key violation', + raw: 'update or delete on table "parent" violates foreign key constraint "fk_child" on table "child"', + code: ExceptionsInternalCodes.FOREIGN_KEY_VIOLATION, + type: 'foreign_key_violation', + }, + { + name: 'MySQL parent row -> foreign key violation', + raw: 'Cannot delete or update a parent row: a foreign key constraint fails (`db`.`child`, CONSTRAINT `fk` FOREIGN KEY (`pid`) REFERENCES `parent` (`id`))', + code: ExceptionsInternalCodes.FOREIGN_KEY_VIOLATION, + type: 'foreign_key_violation', + }, + { + name: 'MSSQL reference constraint -> foreign key violation', + raw: 'The DELETE statement conflicted with the REFERENCE constraint "FK_child". The conflict occurred in database "d", table "dbo.child"', + code: ExceptionsInternalCodes.FOREIGN_KEY_VIOLATION, + type: 'foreign_key_violation', + }, + { + name: 'MySQL SELECT denied -> db permission denied', + raw: "SELECT command denied to user 'reader'@'%' for table 'secrets'", + code: ExceptionsInternalCodes.DB_PERMISSION_DENIED, + type: 'db_permission_denied', + }, + { + name: 'malformed utf-8 -> decryption failed', + raw: 'Malformed UTF-8 data', + code: ExceptionsInternalCodes.DECRYPTION_FAILED, + type: 'decryption_failed', + }, +]; + +for (const c of CASES) { + test(`categorizeExceptionMessage: ${c.name}`, (t) => { + const result = categorizeExceptionMessage(c.raw); + t.is(result.internalCode, c.code, 'internalCode should match the category'); + t.is(result.type, c.type, 'type should match the category'); + t.truthy(result.message, 'a user-facing message should be present'); + }); +} + +test('categorizeExceptionMessage: unknown error returns the original message with no code/type', (t) => { + const raw = 'some completely unrecognized driver error xyz'; + const result = categorizeExceptionMessage(raw); + t.is(result.message, raw); + t.is(result.internalCode, undefined); + t.is(result.type, undefined); +}); + +test('categorizeExceptionMessage: matching is case-insensitive', (t) => { + const upper = categorizeExceptionMessage('GETADDRINFO ENOTFOUND SOME.HOST'); + t.is(upper.internalCode, ExceptionsInternalCodes.HOST_UNREACHABLE); + t.is(upper.type, 'host_unreachable'); +}); + +test('processExceptionMessage: returns only the (possibly rewritten) message string', (t) => { + const friendly = processExceptionMessage('getaddrinfo ENOTFOUND nope.host'); + t.is(typeof friendly, 'string'); + t.true(friendly.length > 0); + // unrecognized input passes through unchanged + t.is(processExceptionMessage('totally unknown'), 'totally unknown'); +}); diff --git a/backend/test/ava-tests/unit-tests/custom-exceptions.test.ts b/backend/test/ava-tests/unit-tests/custom-exceptions.test.ts new file mode 100644 index 000000000..49f42694e --- /dev/null +++ b/backend/test/ava-tests/unit-tests/custom-exceptions.test.ts @@ -0,0 +1,139 @@ +import { HttpStatus } from '@nestjs/common'; +import test from 'ava'; +import { ValidationError } from 'class-validator'; +import { ConnectionNotFoundException } from '../../../src/exceptions/custom-exceptions/connection-not-found-exception.js'; +import { ExceptionsInternalCodes } from '../../../src/exceptions/custom-exceptions/custom-exceptions-internal-codes/exceptions-internal-codes.js'; +import { DeleteRowException } from '../../../src/exceptions/custom-exceptions/delete-row-exception.js'; +import { ExternalServiceException } from '../../../src/exceptions/custom-exceptions/external-service-exception.js'; +import { MasterPasswordIncorrectException } from '../../../src/exceptions/custom-exceptions/master-password-incorrect-exception.js'; +import { MasterPasswordMissingException } from '../../../src/exceptions/custom-exceptions/master-password-missing-exception.js'; +import { ErrorsMessages } from '../../../src/exceptions/custom-exceptions/messages/custom-errors-messages.js'; +import { NonAvailableInFreePlanException } from '../../../src/exceptions/custom-exceptions/non-available-in-free-plan-exception.js'; +import { PrimaryKeyMissingException } from '../../../src/exceptions/custom-exceptions/primary-key-missing-exception.js'; +import { TableNotFoundException } from '../../../src/exceptions/custom-exceptions/table-not-found-exception.js'; +import { TwoFaRequiredException } from '../../../src/exceptions/custom-exceptions/two-fa-required-exception.js'; +import { UnknownSQLException } from '../../../src/exceptions/custom-exceptions/unknown-sql-exception.js'; +import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; +import { Messages } from '../../../src/exceptions/text/messages.js'; + +const FK_RAW = 'update or delete on table "parent" violates foreign key constraint "fk" on table "child"'; + +test('UnknownSQLException: recognized SQL error gets specific code/type and keeps originalMessage', (t) => { + const exc = new UnknownSQLException(FK_RAW, 'Failed to get rows from table.'); + t.is(exc.getStatus(), HttpStatus.INTERNAL_SERVER_ERROR); + t.is(exc.internalCode, ExceptionsInternalCodes.FOREIGN_KEY_VIOLATION); + t.is(exc.type, 'foreign_key_violation'); + t.is(exc.originalMessage, FK_RAW); + t.true(exc.message.startsWith('Failed to get rows from table.'), 'operation prefix is preserved'); +}); + +test('UnknownSQLException: unrecognized error falls back to generic message + UNKNOWN_SQL_EXCEPTION', (t) => { + const raw = 'some weird driver failure'; + const exc = new UnknownSQLException(raw); + t.is(exc.internalCode, ExceptionsInternalCodes.UNKNOWN_SQL_EXCEPTION); + t.is(exc.type, undefined); + t.is(exc.originalMessage, raw); + t.true(exc.message.length > 0); +}); + +test('UnknownSQLException: agent no-data maps to AGENT_NO_DATA', (t) => { + const exc = new UnknownSQLException('No data returned from agent'); + t.is(exc.internalCode, ExceptionsInternalCodes.AGENT_NO_DATA); + t.is(exc.originalMessage, 'No data returned from agent'); +}); + +test('DeleteRowException: FK error surfaces specific code; unknown falls back', (t) => { + const fk = new DeleteRowException(FK_RAW); + t.is(fk.internalCode, ExceptionsInternalCodes.FOREIGN_KEY_VIOLATION); + t.is(fk.type, 'foreign_key_violation'); + t.is(fk.originalMessage, FK_RAW); + + const unknown = new DeleteRowException('weird failure'); + t.is(unknown.internalCode, ExceptionsInternalCodes.UNKNOWN_SQL_EXCEPTION); + t.is(unknown.message, ErrorsMessages.FAILED_TO_DELETE_ROW); + t.is(unknown.getStatus(), HttpStatus.INTERNAL_SERVER_ERROR); +}); + +test('ConnectionNotFoundException: defaults to 404 and preserves an explicit status', (t) => { + const def = new ConnectionNotFoundException(); + t.is(def.getStatus(), HttpStatus.NOT_FOUND); + t.is(def.internalCode, ExceptionsInternalCodes.CONNECTION_NOT_FOUND); + t.is(def.message, Messages.CONNECTION_NOT_FOUND); + + const badReq = new ConnectionNotFoundException(HttpStatus.BAD_REQUEST); + t.is(badReq.getStatus(), HttpStatus.BAD_REQUEST); + t.is(badReq.internalCode, ExceptionsInternalCodes.CONNECTION_NOT_FOUND); +}); + +test('MasterPasswordMissingException: 400 + code 1200 + type no_master_key', (t) => { + const exc = new MasterPasswordMissingException(); + t.is(exc.getStatus(), HttpStatus.BAD_REQUEST); + t.is(exc.internalCode, ExceptionsInternalCodes.MASTER_PASSWORD_MISSING); + t.is(exc.type, 'no_master_key'); + t.is(exc.message, Messages.MASTER_PASSWORD_MISSING); +}); + +test('MasterPasswordIncorrectException: 400 + code 1201 + type invalid_master_key', (t) => { + const exc = new MasterPasswordIncorrectException(); + t.is(exc.getStatus(), HttpStatus.BAD_REQUEST); + t.is(exc.internalCode, ExceptionsInternalCodes.MASTER_PASSWORD_INCORRECT); + t.is(exc.type, 'invalid_master_key'); +}); + +test('TwoFaRequiredException: 400 + code 1202', (t) => { + const exc = new TwoFaRequiredException(); + t.is(exc.getStatus(), HttpStatus.BAD_REQUEST); + t.is(exc.internalCode, ExceptionsInternalCodes.TWO_FA_REQUIRED); + t.is(exc.message, Messages.TWO_FA_REQUIRED); +}); + +test('PrimaryKeyMissingException: 400 + code 1101, default and custom message', (t) => { + const def = new PrimaryKeyMissingException(); + t.is(def.getStatus(), HttpStatus.BAD_REQUEST); + t.is(def.internalCode, ExceptionsInternalCodes.PRIMARY_KEY_MISSING); + t.is(def.message, Messages.PRIMARY_KEY_MISSING); + + const custom = new PrimaryKeyMissingException('custom pk message'); + t.is(custom.message, 'custom pk message'); + t.is(custom.internalCode, ExceptionsInternalCodes.PRIMARY_KEY_MISSING); +}); + +test('TableNotFoundException: 400 + code 1301', (t) => { + const exc = new TableNotFoundException(); + t.is(exc.getStatus(), HttpStatus.BAD_REQUEST); + t.is(exc.internalCode, ExceptionsInternalCodes.TABLE_NOT_FOUND); + t.is(exc.message, Messages.TABLE_NOT_FOUND); +}); + +test('ValidationException: string message and array formatting, code 1007', (t) => { + const strExc = new ValidationException('bad input'); + t.is(strExc.getStatus(), HttpStatus.BAD_REQUEST); + t.is(strExc.internalCode, ExceptionsInternalCodes.VALIDATOR_EXCEPTION); + t.is(strExc.message, 'bad input'); + + const errors = [ + { property: 'email', constraints: { isEmail: 'email must be valid' } }, + ] as unknown as ValidationError[]; + const arrExc = new ValidationException(errors); + t.is(arrExc.internalCode, ExceptionsInternalCodes.VALIDATOR_EXCEPTION); + t.true(arrExc.message.includes('email'), 'formatted message mentions the property'); + t.true(arrExc.message.includes('email must be valid'), 'formatted message includes the constraint text'); +}); + +test('NonAvailableInFreePlanException: 402 + code 1400', (t) => { + const exc = new NonAvailableInFreePlanException(); + t.is(exc.getStatus(), HttpStatus.PAYMENT_REQUIRED); + t.is(exc.internalCode, ExceptionsInternalCodes.FEATURE_NON_AVAILABLE_IN_FREE_PLAN); +}); + +test('ExternalServiceException: preserves upstream status and originalMessage, code 1500', (t) => { + const exc = new ExternalServiceException( + Messages.SAAS_DELETE_COMPANY_FAILED_UNHANDLED_ERROR, + 503, + 'upstream blew up', + ); + t.is(exc.getStatus(), 503); + t.is(exc.internalCode, ExceptionsInternalCodes.EXTERNAL_SERVICE_ERROR); + t.is(exc.originalMessage, 'upstream blew up'); + t.is(exc.message, Messages.SAAS_DELETE_COMPANY_FAILED_UNHANDLED_ERROR); +}); diff --git a/backend/test/ava-tests/unit-tests/domain-error-translation.test.ts b/backend/test/ava-tests/unit-tests/domain-error-translation.test.ts new file mode 100644 index 000000000..cd7d5a62b --- /dev/null +++ b/backend/test/ava-tests/unit-tests/domain-error-translation.test.ts @@ -0,0 +1,42 @@ +import { HttpException, HttpStatus } from '@nestjs/common'; +import test from 'ava'; +import { ExceptionsInternalCodes } from '../../../src/exceptions/custom-exceptions/custom-exceptions-internal-codes/exceptions-internal-codes.js'; +import { MasterPasswordIncorrectException } from '../../../src/exceptions/custom-exceptions/master-password-incorrect-exception.js'; +import { MasterPasswordMissingException } from '../../../src/exceptions/custom-exceptions/master-password-missing-exception.js'; +import { + MasterPasswordIncorrectError, + MasterPasswordMissingError, +} from '../../../src/exceptions/domain-errors/master-password.errors.js'; +import { translateDomainError } from '../../../src/exceptions/domain-errors/translate-domain-error.js'; +import { Messages } from '../../../src/exceptions/text/messages.js'; + +test('domain errors are plain Errors with no HTTP coupling', (t) => { + const missing = new MasterPasswordMissingError(); + t.true(missing instanceof Error); + t.false(missing instanceof HttpException, 'data-layer error must not be an HttpException'); + t.is(missing.message, Messages.MASTER_PASSWORD_MISSING, 'message mirrors the constant for legacy string checks'); + t.is(missing.name, 'MasterPasswordMissingError'); +}); + +test('translateDomainError: MasterPasswordMissingError -> 400 + code 1200 + type no_master_key', (t) => { + const http = translateDomainError(new MasterPasswordMissingError()); + t.true(http instanceof MasterPasswordMissingException); + t.is(http?.getStatus(), HttpStatus.BAD_REQUEST); + t.is((http as MasterPasswordMissingException).internalCode, ExceptionsInternalCodes.MASTER_PASSWORD_MISSING); + t.is((http as MasterPasswordMissingException).type, 'no_master_key'); +}); + +test('translateDomainError: MasterPasswordIncorrectError -> 400 + code 1201 + type invalid_master_key', (t) => { + const http = translateDomainError(new MasterPasswordIncorrectError()); + t.true(http instanceof MasterPasswordIncorrectException); + t.is(http?.getStatus(), HttpStatus.BAD_REQUEST); + t.is((http as MasterPasswordIncorrectException).internalCode, ExceptionsInternalCodes.MASTER_PASSWORD_INCORRECT); + t.is((http as MasterPasswordIncorrectException).type, 'invalid_master_key'); +}); + +test('translateDomainError: returns null for anything that is not a known domain error', (t) => { + t.is(translateDomainError(new Error('something else')), null); + t.is(translateDomainError('a string'), null); + t.is(translateDomainError(undefined), null); + t.is(translateDomainError({ message: 'plain object' }), null); +});