From 1fc3ba7dd3bbb5e28c1ccec9376495a4188e33a6 Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Wed, 14 Jan 2026 15:29:05 +0000 Subject: [PATCH 1/3] Add dashboard and widgets entities and functionality, and tests --- backend/src/app.module.ts | 2 + .../global-database-context.interface.ts | 8 + .../application/global-database-context.ts | 24 + backend/src/common/data-injection.tokens.ts | 9 + .../entities/connection/connection.entity.ts | 490 +++++++------- .../dashboard/dashboard-widget.entity.ts | 99 +++ .../dashboard/dashboard-widgets.controller.ts | 150 +++++ .../dashboard/dashboard.entity.ts | 34 + .../dashboard/dashboards.controller.ts | 203 ++++++ .../dashboard/dashboards.module.ts | 77 +++ .../create-dashboard-widget.ds.ts | 17 + .../data-structures/create-dashboard.ds.ts | 7 + .../delete-dashboard-widget.ds.ts | 7 + .../data-structures/find-all-dashboards.ds.ts | 5 + .../data-structures/find-dashboard.ds.ts | 6 + .../update-dashboard-widget.ds.ts | 18 + .../data-structures/update-dashboard.ds.ts | 8 + .../dto/create-dashboard-widget.dto.ts | 55 ++ .../dashboard/dto/create-dashboard.dto.ts | 15 + .../dto/found-dashboard-widget.dto.ts | 37 ++ .../dashboard/dto/found-dashboard.dto.ts | 25 + .../dto/update-dashboard-widget.dto.ts | 55 ++ .../dashboard/dto/update-dashboard.dto.ts | 15 + .../dashboard-custom-repository-extension.ts | 35 + ...oard-widget-custom-repository-extension.ts | 28 + .../dashboard.repository.interface.ts | 19 + .../create-dashboard-widget.use.case.ts | 84 +++ .../use-cases/create-dashboard.use.case.ts | 44 ++ .../dashboard-use-cases.interface.ts | 42 ++ .../delete-dashboard-widget.use.case.ts | 58 ++ .../use-cases/delete-dashboard.use.case.ts | 49 ++ .../use-cases/find-all-dashboards.use.case.ts | 39 ++ .../use-cases/find-dashboard.use.case.ts | 43 ++ .../update-dashboard-widget.use.case.ts | 109 +++ .../use-cases/update-dashboard.use.case.ts | 55 ++ .../utils/build-found-dashboard-dto.util.ts | 15 + .../build-found-dashboard-widget-dto.util.ts | 18 + .../saved-db-query/saved-db-query.entity.ts | 8 + .../src/enums/dashboard-widget-type.enum.ts | 6 + backend/src/enums/index.ts | 1 + backend/src/exceptions/text/messages.ts | 2 + ...9-AddDashboardAndDashboardWigetEntities.ts | 31 + .../non-saas-dashboard-e2e.test.ts | 622 ++++++++++++++++++ .../saas-tests/dashboard-e2e.test.ts | 621 +++++++++++++++++ 44 files changed, 3072 insertions(+), 223 deletions(-) create mode 100644 backend/src/entities/visualizations/dashboard/dashboard-widget.entity.ts create mode 100644 backend/src/entities/visualizations/dashboard/dashboard-widgets.controller.ts create mode 100644 backend/src/entities/visualizations/dashboard/dashboard.entity.ts create mode 100644 backend/src/entities/visualizations/dashboard/data-structures/create-dashboard-widget.ds.ts create mode 100644 backend/src/entities/visualizations/dashboard/data-structures/create-dashboard.ds.ts create mode 100644 backend/src/entities/visualizations/dashboard/data-structures/delete-dashboard-widget.ds.ts create mode 100644 backend/src/entities/visualizations/dashboard/data-structures/find-all-dashboards.ds.ts create mode 100644 backend/src/entities/visualizations/dashboard/data-structures/find-dashboard.ds.ts create mode 100644 backend/src/entities/visualizations/dashboard/data-structures/update-dashboard-widget.ds.ts create mode 100644 backend/src/entities/visualizations/dashboard/data-structures/update-dashboard.ds.ts create mode 100644 backend/src/entities/visualizations/dashboard/dto/create-dashboard-widget.dto.ts create mode 100644 backend/src/entities/visualizations/dashboard/dto/create-dashboard.dto.ts create mode 100644 backend/src/entities/visualizations/dashboard/dto/found-dashboard-widget.dto.ts create mode 100644 backend/src/entities/visualizations/dashboard/dto/found-dashboard.dto.ts create mode 100644 backend/src/entities/visualizations/dashboard/dto/update-dashboard-widget.dto.ts create mode 100644 backend/src/entities/visualizations/dashboard/dto/update-dashboard.dto.ts create mode 100644 backend/src/entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.ts create mode 100644 backend/src/entities/visualizations/dashboard/repository/dashboard-widget-custom-repository-extension.ts create mode 100644 backend/src/entities/visualizations/dashboard/repository/dashboard.repository.interface.ts create mode 100644 backend/src/entities/visualizations/dashboard/use-cases/create-dashboard-widget.use.case.ts create mode 100644 backend/src/entities/visualizations/dashboard/use-cases/create-dashboard.use.case.ts create mode 100644 backend/src/entities/visualizations/dashboard/use-cases/dashboard-use-cases.interface.ts create mode 100644 backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard-widget.use.case.ts create mode 100644 backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard.use.case.ts create mode 100644 backend/src/entities/visualizations/dashboard/use-cases/find-all-dashboards.use.case.ts create mode 100644 backend/src/entities/visualizations/dashboard/use-cases/find-dashboard.use.case.ts create mode 100644 backend/src/entities/visualizations/dashboard/use-cases/update-dashboard-widget.use.case.ts create mode 100644 backend/src/entities/visualizations/dashboard/use-cases/update-dashboard.use.case.ts create mode 100644 backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-dto.util.ts create mode 100644 backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-widget-dto.util.ts create mode 100644 backend/src/enums/dashboard-widget-type.enum.ts create mode 100644 backend/src/migrations/1768393822639-AddDashboardAndDashboardWigetEntities.ts create mode 100644 backend/test/ava-tests/non-saas-tests/non-saas-dashboard-e2e.test.ts create mode 100644 backend/test/ava-tests/saas-tests/dashboard-e2e.test.ts diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index 8629a0e2d..df50f8135 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -43,6 +43,7 @@ import { DatabaseModule } from './shared/database/database.module.js'; import { GetHelloUseCase } from './use-cases-app/get-hello.use.case.js'; import { PersonalTableSettingsModule } from './entities/table-settings/personal-table-settings/personal-table-settings.module.js'; import { SavedDbQueryModule } from './entities/visualizations/saved-db-query/saved-db-query.module.js'; +import { DashboardModule } from './entities/visualizations/dashboard/dashboards.module.js'; @Module({ imports: [ @@ -90,6 +91,7 @@ import { SavedDbQueryModule } from './entities/visualizations/saved-db-query/sav PersonalTableSettingsModule, S3WidgetModule, SavedDbQueryModule, + DashboardModule, ], controllers: [AppController], providers: [ diff --git a/backend/src/common/application/global-database-context.interface.ts b/backend/src/common/application/global-database-context.interface.ts index e7509f7c7..212bd5df6 100644 --- a/backend/src/common/application/global-database-context.interface.ts +++ b/backend/src/common/application/global-database-context.interface.ts @@ -58,6 +58,12 @@ import { IPersonalTableSettingsRepository } from '../../entities/table-settings/ import { PersonalTableSettingsEntity } from '../../entities/table-settings/personal-table-settings/personal-table-settings.entity.js'; import { SavedDbQueryEntity } from '../../entities/visualizations/saved-db-query/saved-db-query.entity.js'; import { ISavedDbQueryRepository } from '../../entities/visualizations/saved-db-query/repository/saved-db-query.repository.interface.js'; +import { DashboardEntity } from '../../entities/visualizations/dashboard/dashboard.entity.js'; +import { DashboardWidgetEntity } from '../../entities/visualizations/dashboard/dashboard-widget.entity.js'; +import { + IDashboardRepository, + IDashboardWidgetRepository, +} from '../../entities/visualizations/dashboard/repository/dashboard.repository.interface.js'; export interface IGlobalDatabaseContext extends IDatabaseContext { userRepository: Repository & IUserRepository; @@ -98,4 +104,6 @@ export interface IGlobalDatabaseContext extends IDatabaseContext { signInAuditRepository: Repository & ISignInAuditRepository; personalTableSettingsRepository: Repository & IPersonalTableSettingsRepository; savedDbQueryRepository: Repository & ISavedDbQueryRepository; + dashboardRepository: Repository & IDashboardRepository; + dashboardWidgetRepository: Repository & IDashboardWidgetRepository; } diff --git a/backend/src/common/application/global-database-context.ts b/backend/src/common/application/global-database-context.ts index 704da7df8..854ad40d6 100644 --- a/backend/src/common/application/global-database-context.ts +++ b/backend/src/common/application/global-database-context.ts @@ -105,6 +105,14 @@ import { personalTableSettingsCustomRepositoryExtension } from '../../entities/t import { SavedDbQueryEntity } from '../../entities/visualizations/saved-db-query/saved-db-query.entity.js'; import { ISavedDbQueryRepository } from '../../entities/visualizations/saved-db-query/repository/saved-db-query.repository.interface.js'; import { savedDbQueryCustomRepositoryExtension } from '../../entities/visualizations/saved-db-query/repository/saved-db-query-custom-repository-extension.js'; +import { DashboardEntity } from '../../entities/visualizations/dashboard/dashboard.entity.js'; +import { DashboardWidgetEntity } from '../../entities/visualizations/dashboard/dashboard-widget.entity.js'; +import { + IDashboardRepository, + IDashboardWidgetRepository, +} from '../../entities/visualizations/dashboard/repository/dashboard.repository.interface.js'; +import { dashboardCustomRepositoryExtension } from '../../entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.js'; +import { dashboardWidgetCustomRepositoryExtension } from '../../entities/visualizations/dashboard/repository/dashboard-widget-custom-repository-extension.js'; @Injectable({ scope: Scope.REQUEST }) export class GlobalDatabaseContext implements IGlobalDatabaseContext { @@ -148,6 +156,8 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext { private _signInAuditRepository: Repository & ISignInAuditRepository; private _personalTableSettingsRepository: Repository & IPersonalTableSettingsRepository; private _savedDbQueryRepository: Repository & ISavedDbQueryRepository; + private _dashboardRepository: Repository & IDashboardRepository; + private _dashboardWidgetRepository: Repository & IDashboardWidgetRepository; public constructor( @Inject(BaseType.DATA_SOURCE) @@ -251,6 +261,12 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext { this._savedDbQueryRepository = this.appDataSource .getRepository(SavedDbQueryEntity) .extend(savedDbQueryCustomRepositoryExtension); + this._dashboardRepository = this.appDataSource + .getRepository(DashboardEntity) + .extend(dashboardCustomRepositoryExtension); + this._dashboardWidgetRepository = this.appDataSource + .getRepository(DashboardWidgetEntity) + .extend(dashboardWidgetCustomRepositoryExtension); } public get userRepository(): Repository & IUserRepository { @@ -407,6 +423,14 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext { return this._savedDbQueryRepository; } + public get dashboardRepository(): Repository & IDashboardRepository { + return this._dashboardRepository; + } + + public get dashboardWidgetRepository(): Repository & IDashboardWidgetRepository { + return this._dashboardWidgetRepository; + } + public startTransaction(): Promise { this._queryRunner = this.appDataSource.createQueryRunner(); this._queryRunner.startTransaction(); diff --git a/backend/src/common/data-injection.tokens.ts b/backend/src/common/data-injection.tokens.ts index 03f595f00..9352e13b7 100644 --- a/backend/src/common/data-injection.tokens.ts +++ b/backend/src/common/data-injection.tokens.ts @@ -188,4 +188,13 @@ export enum UseCaseType { DELETE_SAVED_DB_QUERY = 'DELETE_SAVED_DB_QUERY', EXECUTE_SAVED_DB_QUERY = 'EXECUTE_SAVED_DB_QUERY', TEST_DB_QUERY = 'TEST_DB_QUERY', + + CREATE_DASHBOARD = 'CREATE_DASHBOARD', + UPDATE_DASHBOARD = 'UPDATE_DASHBOARD', + FIND_DASHBOARD = 'FIND_DASHBOARD', + FIND_ALL_DASHBOARDS = 'FIND_ALL_DASHBOARDS', + DELETE_DASHBOARD = 'DELETE_DASHBOARD', + CREATE_DASHBOARD_WIDGET = 'CREATE_DASHBOARD_WIDGET', + UPDATE_DASHBOARD_WIDGET = 'UPDATE_DASHBOARD_WIDGET', + DELETE_DASHBOARD_WIDGET = 'DELETE_DASHBOARD_WIDGET', } diff --git a/backend/src/entities/connection/connection.entity.ts b/backend/src/entities/connection/connection.entity.ts index 751f462ab..5b94748f6 100644 --- a/backend/src/entities/connection/connection.entity.ts +++ b/backend/src/entities/connection/connection.entity.ts @@ -1,16 +1,16 @@ import { Expose } from 'class-transformer'; import { - AfterLoad, - BeforeInsert, - BeforeUpdate, - Column, - Entity, - JoinTable, - ManyToOne, - OneToMany, - OneToOne, - PrimaryColumn, - Relation, + AfterLoad, + BeforeInsert, + BeforeUpdate, + Column, + Entity, + JoinTable, + ManyToOne, + OneToMany, + OneToOne, + PrimaryColumn, + Relation, } from 'typeorm'; import { isConnectionTypeAgent } from '../../helpers/index.js'; import { Encryptor } from '../../helpers/encryption/encryptor.js'; @@ -28,225 +28,269 @@ import { Constants } from '../../helpers/constants/constants.js'; import { TableFiltersEntity } from '../table-filters/table-filters.entity.js'; import { PersonalTableSettingsEntity } from '../table-settings/personal-table-settings/personal-table-settings.entity.js'; import { SavedDbQueryEntity } from '../visualizations/saved-db-query/saved-db-query.entity.js'; +import { DashboardEntity } from '../visualizations/dashboard/dashboard.entity.js'; @Entity('connection') export class ConnectionEntity { - @Expose() - @PrimaryColumn('varchar', { length: 38 }) - id: string; + @Expose() + @PrimaryColumn('varchar', { length: 38 }) + id: string; - @Column({ default: '' }) - title?: string; + @Column({ default: '' }) + title?: string; - @Column({ default: false, type: 'boolean' }) - masterEncryption: boolean; + @Column({ default: false, type: 'boolean' }) + masterEncryption: boolean; - @Column({ default: null }) - type?: string | null; + @Column({ default: null }) + type?: string | null; - @Column({ default: null }) - host?: string | null; + @Column({ default: null }) + host?: string | null; - @Column({ default: null }) - port?: number; + @Column({ default: null }) + port?: number; - @Column({ default: null }) - username?: string | null; - - @Column({ default: null }) - password?: string | null; - - @Column({ default: null }) - database?: string | null; - - @Column({ default: null }) - schema?: string | null; - - @Column({ default: null, length: 255 }) - sid?: string | null; - - @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) - createdAt: Date; - - @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) - updatedAt: Date; - - @Column({ default: false, type: 'boolean' }) - ssh?: boolean; - - @Column({ default: null }) - privateSSHKey?: string | null; - - @Column({ default: null }) - sshHost?: string | null; - - @Column({ default: null }) - sshPort?: number | null; - - @Column({ default: null }) - sshUsername?: string | null; - - @Column({ default: false, type: 'boolean' }) - ssl?: boolean | null; - - @Column({ default: null }) - cert?: string | null; - - @Column({ default: false, type: 'boolean' }) - isTestConnection: boolean; - - @Column({ default: false, type: 'boolean' }) - azure_encryption: boolean; - - @Column({ default: false, type: 'boolean' }) - is_frozen: boolean; - - @Column({ default: 0 }) - saved_table_info: number; - - @Column({ default: null }) - signing_key: string; - - @Column({ default: null }) - authSource?: string | null; - - @Column({ default: null }) - dataCenter?: string | null; - - @Column({ default: null }) - master_hash?: string | null; - - @BeforeUpdate() - updateTimestampEncryptCredentials(): void { - this.updatedAt = new Date(); - if (!isConnectionTypeAgent(this.type)) { - this.host = Encryptor.encryptData(this.host); - this.database = Encryptor.encryptData(this.database); - this.password = Encryptor.encryptData(this.password); - this.username = Encryptor.encryptData(this.username); - if (this.authSource) { - this.authSource = Encryptor.encryptData(this.authSource); - } - if (this.ssh) { - this.privateSSHKey = Encryptor.encryptData(this.privateSSHKey); - this.sshHost = Encryptor.encryptData(this.sshHost); - this.sshUsername = Encryptor.encryptData(this.sshUsername); - } - if (this.ssl && this.cert) { - this.cert = Encryptor.encryptData(this.cert); - } - } - } - - @BeforeInsert() - encryptCredentialsAndGenerateNanoid(): void { - if (!this.id) { - this.id = nanoid(8); - } - this.signing_key = Encryptor.generateRandomString(40); - if (process.env.NODE_ENV === 'test') { - this.signing_key = 'test'; - } - if (!isConnectionTypeAgent(this.type)) { - this.host = Encryptor.encryptData(this.host); - this.database = Encryptor.encryptData(this.database); - this.password = Encryptor.encryptData(this.password); - this.username = Encryptor.encryptData(this.username); - if (this.authSource) { - this.authSource = Encryptor.encryptData(this.authSource); - } - if (this.ssh) { - this.privateSSHKey = Encryptor.encryptData(this.privateSSHKey); - this.sshHost = Encryptor.encryptData(this.sshHost); - this.sshUsername = Encryptor.encryptData(this.sshUsername); - } - if (this.ssl && this.cert) { - this.cert = Encryptor.encryptData(this.cert); - } - } - } - - @AfterLoad() - decryptCredentials(): void { - if (this.isTestConnection) { - const testConnectionsArray = Constants.getTestConnectionsArr(); - const foundTestConnectionByType = testConnectionsArray.find( - (testConnection) => testConnection.type === this.type, - ); - if (foundTestConnectionByType) { - this.host = foundTestConnectionByType.host; - this.database = foundTestConnectionByType.database; - this.username = foundTestConnectionByType.username; - this.password = foundTestConnectionByType.password; - this.port = foundTestConnectionByType.port; - this.ssh = foundTestConnectionByType.ssh; - this.privateSSHKey = foundTestConnectionByType.privateSSHKey; - this.sshHost = foundTestConnectionByType.sshHost; - this.sshPort = foundTestConnectionByType.sshPort; - this.sshUsername = foundTestConnectionByType.sshUsername; - this.ssl = foundTestConnectionByType.ssl; - this.cert = foundTestConnectionByType.cert; - this.authSource = foundTestConnectionByType.authSource; - this.sid = foundTestConnectionByType.sid; - this.schema = foundTestConnectionByType.schema; - this.cert = foundTestConnectionByType.cert; - this.azure_encryption = foundTestConnectionByType.azure_encryption; - } - } else { - if (!isConnectionTypeAgent(this.type)) { - this.host = Encryptor.decryptData(this.host); - this.database = Encryptor.decryptData(this.database); - this.password = Encryptor.decryptData(this.password); - this.username = Encryptor.decryptData(this.username); - if (this.authSource) { - this.authSource = Encryptor.decryptData(this.authSource); - } - if (this.ssh) { - this.privateSSHKey = Encryptor.decryptData(this.privateSSHKey); - this.sshHost = Encryptor.decryptData(this.sshHost); - this.sshUsername = Encryptor.decryptData(this.sshUsername); - } - if (this.ssl && this.cert) { - this.cert = Encryptor.decryptData(this.cert); - } - } - } - } - - @ManyToOne((_) => UserEntity, (user) => user.connections, { onDelete: 'SET NULL', nullable: true }) - author: Relation; - - @OneToMany((_) => GroupEntity, (group) => group.connection) - groups: Relation[]; - - @OneToMany((_) => TableSettingsEntity, (settings) => settings.connection_id) - settings: Relation[]; - - @OneToMany((_) => PersonalTableSettingsEntity, (personal_table_settings) => personal_table_settings.connection) - personal_table_settings: Relation[]; - - @OneToMany((_) => TableLogsEntity, (logs) => logs.connection_id) - logs: Relation[]; - - @OneToMany((_) => ActionRulesEntity, (action_rules) => action_rules.connection) - action_rules: Relation[]; - - @OneToOne((_) => AgentEntity, (agent) => agent.connection) - agent: Relation; - - @OneToOne((_) => ConnectionPropertiesEntity, (connection_properties) => connection_properties.connection) - connection_properties: Relation; - - @OneToMany((_) => TableInfoEntity, (table_info) => table_info.connection) - tables_info: Relation[]; - - @ManyToOne((_) => CompanyInfoEntity, (company) => company.connections) - @JoinTable() - company: Relation; - - @OneToMany((_) => TableFiltersEntity, (table_filters) => table_filters.connection) - table_filters: Relation[]; - - @OneToMany(() => SavedDbQueryEntity, (saved_db_query) => saved_db_query.connection) - saved_db_queries: Relation[]; + @Column({ default: null }) + username?: string | null; + + @Column({ default: null }) + password?: string | null; + + @Column({ default: null }) + database?: string | null; + + @Column({ default: null }) + schema?: string | null; + + @Column({ default: null, length: 255 }) + sid?: string | null; + + @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) + createdAt: Date; + + @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) + updatedAt: Date; + + @Column({ default: false, type: 'boolean' }) + ssh?: boolean; + + @Column({ default: null }) + privateSSHKey?: string | null; + + @Column({ default: null }) + sshHost?: string | null; + + @Column({ default: null }) + sshPort?: number | null; + + @Column({ default: null }) + sshUsername?: string | null; + + @Column({ default: false, type: 'boolean' }) + ssl?: boolean | null; + + @Column({ default: null }) + cert?: string | null; + + @Column({ default: false, type: 'boolean' }) + isTestConnection: boolean; + + @Column({ default: false, type: 'boolean' }) + azure_encryption: boolean; + + @Column({ default: false, type: 'boolean' }) + is_frozen: boolean; + + @Column({ default: 0 }) + saved_table_info: number; + + @Column({ default: null }) + signing_key: string; + + @Column({ default: null }) + authSource?: string | null; + + @Column({ default: null }) + dataCenter?: string | null; + + @Column({ default: null }) + master_hash?: string | null; + + @BeforeUpdate() + updateTimestampEncryptCredentials(): void { + this.updatedAt = new Date(); + if (!isConnectionTypeAgent(this.type)) { + this.host = Encryptor.encryptData(this.host); + this.database = Encryptor.encryptData(this.database); + this.password = Encryptor.encryptData(this.password); + this.username = Encryptor.encryptData(this.username); + if (this.authSource) { + this.authSource = Encryptor.encryptData(this.authSource); + } + if (this.ssh) { + this.privateSSHKey = Encryptor.encryptData(this.privateSSHKey); + this.sshHost = Encryptor.encryptData(this.sshHost); + this.sshUsername = Encryptor.encryptData(this.sshUsername); + } + if (this.ssl && this.cert) { + this.cert = Encryptor.encryptData(this.cert); + } + } + } + + @BeforeInsert() + encryptCredentialsAndGenerateNanoid(): void { + if (!this.id) { + this.id = nanoid(8); + } + this.signing_key = Encryptor.generateRandomString(40); + if (process.env.NODE_ENV === 'test') { + this.signing_key = 'test'; + } + if (!isConnectionTypeAgent(this.type)) { + this.host = Encryptor.encryptData(this.host); + this.database = Encryptor.encryptData(this.database); + this.password = Encryptor.encryptData(this.password); + this.username = Encryptor.encryptData(this.username); + if (this.authSource) { + this.authSource = Encryptor.encryptData(this.authSource); + } + if (this.ssh) { + this.privateSSHKey = Encryptor.encryptData(this.privateSSHKey); + this.sshHost = Encryptor.encryptData(this.sshHost); + this.sshUsername = Encryptor.encryptData(this.sshUsername); + } + if (this.ssl && this.cert) { + this.cert = Encryptor.encryptData(this.cert); + } + } + } + + @AfterLoad() + decryptCredentials(): void { + if (this.isTestConnection) { + const testConnectionsArray = Constants.getTestConnectionsArr(); + const foundTestConnectionByType = testConnectionsArray.find( + (testConnection) => testConnection.type === this.type, + ); + if (foundTestConnectionByType) { + this.host = foundTestConnectionByType.host; + this.database = foundTestConnectionByType.database; + this.username = foundTestConnectionByType.username; + this.password = foundTestConnectionByType.password; + this.port = foundTestConnectionByType.port; + this.ssh = foundTestConnectionByType.ssh; + this.privateSSHKey = foundTestConnectionByType.privateSSHKey; + this.sshHost = foundTestConnectionByType.sshHost; + this.sshPort = foundTestConnectionByType.sshPort; + this.sshUsername = foundTestConnectionByType.sshUsername; + this.ssl = foundTestConnectionByType.ssl; + this.cert = foundTestConnectionByType.cert; + this.authSource = foundTestConnectionByType.authSource; + this.sid = foundTestConnectionByType.sid; + this.schema = foundTestConnectionByType.schema; + this.cert = foundTestConnectionByType.cert; + this.azure_encryption = foundTestConnectionByType.azure_encryption; + } + } else { + if (!isConnectionTypeAgent(this.type)) { + this.host = Encryptor.decryptData(this.host); + this.database = Encryptor.decryptData(this.database); + this.password = Encryptor.decryptData(this.password); + this.username = Encryptor.decryptData(this.username); + if (this.authSource) { + this.authSource = Encryptor.decryptData(this.authSource); + } + if (this.ssh) { + this.privateSSHKey = Encryptor.decryptData(this.privateSSHKey); + this.sshHost = Encryptor.decryptData(this.sshHost); + this.sshUsername = Encryptor.decryptData(this.sshUsername); + } + if (this.ssl && this.cert) { + this.cert = Encryptor.decryptData(this.cert); + } + } + } + } + + @ManyToOne( + (_) => UserEntity, + (user) => user.connections, + { onDelete: 'SET NULL', nullable: true }, + ) + author: Relation; + + @OneToMany( + (_) => GroupEntity, + (group) => group.connection, + ) + groups: Relation[]; + + @OneToMany( + (_) => TableSettingsEntity, + (settings) => settings.connection_id, + ) + settings: Relation[]; + + @OneToMany( + (_) => PersonalTableSettingsEntity, + (personal_table_settings) => personal_table_settings.connection, + ) + personal_table_settings: Relation[]; + + @OneToMany( + (_) => TableLogsEntity, + (logs) => logs.connection_id, + ) + logs: Relation[]; + + @OneToMany( + (_) => ActionRulesEntity, + (action_rules) => action_rules.connection, + ) + action_rules: Relation[]; + + @OneToOne( + (_) => AgentEntity, + (agent) => agent.connection, + ) + agent: Relation; + + @OneToOne( + (_) => ConnectionPropertiesEntity, + (connection_properties) => connection_properties.connection, + ) + connection_properties: Relation; + + @OneToMany( + (_) => TableInfoEntity, + (table_info) => table_info.connection, + ) + tables_info: Relation[]; + + @ManyToOne( + (_) => CompanyInfoEntity, + (company) => company.connections, + ) + @JoinTable() + company: Relation; + + @OneToMany( + (_) => TableFiltersEntity, + (table_filters) => table_filters.connection, + ) + table_filters: Relation[]; + + @OneToMany( + () => SavedDbQueryEntity, + (saved_db_query) => saved_db_query.connection, + ) + saved_db_queries: Relation[]; + + @OneToMany( + () => DashboardEntity, + (dashboard) => dashboard.connection, + ) + dashboards: Relation[]; } diff --git a/backend/src/entities/visualizations/dashboard/dashboard-widget.entity.ts b/backend/src/entities/visualizations/dashboard/dashboard-widget.entity.ts new file mode 100644 index 000000000..ae1a56350 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/dashboard-widget.entity.ts @@ -0,0 +1,99 @@ +import sjson from 'secure-json-parse'; +import { + AfterLoad, + BeforeInsert, + BeforeUpdate, + Column, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, + Relation, +} from 'typeorm'; +import { DashboardWidgetTypeEnum } from '../../../enums/dashboard-widget-type.enum.js'; +import { DashboardEntity } from './dashboard.entity.js'; +import { SavedDbQueryEntity } from '../saved-db-query/saved-db-query.entity.js'; + +@Entity('dashboard_widget') +export class DashboardWidgetEntity { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column({ type: 'varchar' }) + widget_type: DashboardWidgetTypeEnum; + + @Column({ default: null, nullable: true }) + name: string | null; + + @Column({ type: 'text', default: null, nullable: true }) + description: string | null; + + @Column({ type: 'int', default: 0 }) + position_x: number; + + @Column({ type: 'int', default: 0 }) + position_y: number; + + @Column({ type: 'int', default: 4 }) + width: number; + + @Column({ type: 'int', default: 3 }) + height: number; + + @Column('json', { default: null, nullable: true }) + widget_options: string | null; + + @Column({ type: 'uuid' }) + dashboard_id: string; + + @ManyToOne( + () => DashboardEntity, + (dashboard) => dashboard.widgets, + { onDelete: 'CASCADE' }, + ) + @JoinColumn({ name: 'dashboard_id' }) + dashboard: Relation; + + @Column({ type: 'uuid', nullable: true }) + query_id: string | null; + + @ManyToOne(() => SavedDbQueryEntity, { onDelete: 'SET NULL', nullable: true }) + @JoinColumn({ name: 'query_id' }) + query: Relation | null; + + @BeforeUpdate() + stringifyOptionsOnUpdate(): void { + try { + if (this.widget_options && typeof this.widget_options === 'object') { + this.widget_options = JSON.stringify(this.widget_options); + } + } catch (e) { + console.error('-> Error widget options stringify ' + e.message); + } + } + + @BeforeInsert() + stringifyOptions(): void { + try { + if (this.widget_options && typeof this.widget_options === 'object') { + this.widget_options = JSON.stringify(this.widget_options); + } + } catch (e) { + console.error('-> Error widget options stringify ' + e.message); + } + } + + @AfterLoad() + parseOptions(): void { + try { + if (this.widget_options && typeof this.widget_options === 'string') { + this.widget_options = sjson.parse(this.widget_options, null, { + protoAction: 'remove', + constructorAction: 'remove', + }); + } + } catch (e) { + console.error('-> Error widget options parse ' + e.message); + } + } +} diff --git a/backend/src/entities/visualizations/dashboard/dashboard-widgets.controller.ts b/backend/src/entities/visualizations/dashboard/dashboard-widgets.controller.ts new file mode 100644 index 000000000..72063958c --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/dashboard-widgets.controller.ts @@ -0,0 +1,150 @@ +import { + Body, + Controller, + Delete, + Inject, + Injectable, + Param, + Post, + Put, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { UseCaseType } from '../../../common/data-injection.tokens.js'; +import { MasterPassword } from '../../../decorators/master-password.decorator.js'; +import { SlugUuid } from '../../../decorators/slug-uuid.decorator.js'; +import { UserId } from '../../../decorators/user-id.decorator.js'; +import { InTransactionEnum } from '../../../enums/in-transaction.enum.js'; +import { ConnectionEditGuard } from '../../../guards/connection-edit.guard.js'; +import { SentryInterceptor } from '../../../interceptors/sentry.interceptor.js'; +import { CreateDashboardWidgetDs } from './data-structures/create-dashboard-widget.ds.js'; +import { DeleteDashboardWidgetDs } from './data-structures/delete-dashboard-widget.ds.js'; +import { UpdateDashboardWidgetDs } from './data-structures/update-dashboard-widget.ds.js'; +import { CreateDashboardWidgetDto } from './dto/create-dashboard-widget.dto.js'; +import { FoundDashboardWidgetDto } from './dto/found-dashboard-widget.dto.js'; +import { UpdateDashboardWidgetDto } from './dto/update-dashboard-widget.dto.js'; +import { + ICreateDashboardWidget, + IDeleteDashboardWidget, + IUpdateDashboardWidget, +} from './use-cases/dashboard-use-cases.interface.js'; + +@UseInterceptors(SentryInterceptor) +@Controller() +@ApiBearerAuth() +@ApiTags('Dashboard Widgets') +@Injectable() +export class DashboardWidgetController { + constructor( + @Inject(UseCaseType.CREATE_DASHBOARD_WIDGET) + private readonly createDashboardWidgetUseCase: ICreateDashboardWidget, + @Inject(UseCaseType.UPDATE_DASHBOARD_WIDGET) + private readonly updateDashboardWidgetUseCase: IUpdateDashboardWidget, + @Inject(UseCaseType.DELETE_DASHBOARD_WIDGET) + private readonly deleteDashboardWidgetUseCase: IDeleteDashboardWidget, + ) {} + + @ApiOperation({ summary: 'Create a new widget in a dashboard' }) + @ApiResponse({ + status: 201, + description: 'Widget created.', + type: FoundDashboardWidgetDto, + }) + @ApiBody({ type: CreateDashboardWidgetDto }) + @ApiParam({ name: 'dashboardId', required: true }) + @ApiParam({ name: 'connectionId', required: true }) + @UseGuards(ConnectionEditGuard) + @Post('/dashboard/:dashboardId/widget/:connectionId') + async createWidget( + @SlugUuid('connectionId') connectionId: string, + @Param('dashboardId') dashboardId: string, + @MasterPassword() masterPwd: string, + @UserId() userId: string, + @Body() createDto: CreateDashboardWidgetDto, + ): Promise { + const inputData: CreateDashboardWidgetDs = { + dashboardId, + connectionId, + masterPassword: masterPwd, + userId, + widget_type: createDto.widget_type, + name: createDto.name, + description: createDto.description, + position_x: createDto.position_x, + position_y: createDto.position_y, + width: createDto.width, + height: createDto.height, + widget_options: createDto.widget_options, + query_id: createDto.query_id, + }; + return await this.createDashboardWidgetUseCase.execute(inputData, InTransactionEnum.ON); + } + + @ApiOperation({ summary: 'Update a widget in a dashboard' }) + @ApiResponse({ + status: 200, + description: 'Widget updated.', + type: FoundDashboardWidgetDto, + }) + @ApiBody({ type: UpdateDashboardWidgetDto }) + @ApiParam({ name: 'dashboardId', required: true }) + @ApiParam({ name: 'widgetId', required: true }) + @ApiParam({ name: 'connectionId', required: true }) + @UseGuards(ConnectionEditGuard) + @Put('/dashboard/:dashboardId/widget/:widgetId/:connectionId') + async updateWidget( + @SlugUuid('connectionId') connectionId: string, + @Param('dashboardId') dashboardId: string, + @Param('widgetId') widgetId: string, + @MasterPassword() masterPwd: string, + @UserId() userId: string, + @Body() updateDto: UpdateDashboardWidgetDto, + ): Promise { + const inputData: UpdateDashboardWidgetDs = { + widgetId, + dashboardId, + connectionId, + masterPassword: masterPwd, + userId, + widget_type: updateDto.widget_type, + name: updateDto.name, + description: updateDto.description, + position_x: updateDto.position_x, + position_y: updateDto.position_y, + width: updateDto.width, + height: updateDto.height, + widget_options: updateDto.widget_options, + query_id: updateDto.query_id, + }; + return await this.updateDashboardWidgetUseCase.execute(inputData, InTransactionEnum.ON); + } + + @ApiOperation({ summary: 'Delete a widget from a dashboard' }) + @ApiResponse({ + status: 200, + description: 'Widget deleted.', + type: FoundDashboardWidgetDto, + }) + @ApiParam({ name: 'dashboardId', required: true }) + @ApiParam({ name: 'widgetId', required: true }) + @ApiParam({ name: 'connectionId', required: true }) + @UseGuards(ConnectionEditGuard) + @Delete('/dashboard/:dashboardId/widget/:widgetId/:connectionId') + async deleteWidget( + @SlugUuid('connectionId') connectionId: string, + @Param('dashboardId') dashboardId: string, + @Param('widgetId') widgetId: string, + @MasterPassword() masterPwd: string, + @UserId() userId: string, + ): Promise { + const inputData: DeleteDashboardWidgetDs = { + widgetId, + dashboardId, + connectionId, + masterPassword: masterPwd, + userId, + }; + return await this.deleteDashboardWidgetUseCase.execute(inputData, InTransactionEnum.ON); + } +} diff --git a/backend/src/entities/visualizations/dashboard/dashboard.entity.ts b/backend/src/entities/visualizations/dashboard/dashboard.entity.ts new file mode 100644 index 000000000..fb66a6c0c --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/dashboard.entity.ts @@ -0,0 +1,34 @@ +import { Column, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, Relation } from 'typeorm'; +import { ConnectionEntity } from '../../connection/connection.entity.js'; +import { DashboardWidgetEntity } from './dashboard-widget.entity.js'; + +@Entity('dashboard') +export class DashboardEntity { + @PrimaryGeneratedColumn('uuid') + id: string; + + @Column() + name: string; + + @Column({ type: 'text', default: null, nullable: true }) + description: string | null; + + @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) + created_at: Date; + + @Column({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) + updated_at: Date; + + @Column({ type: 'uuid' }) + connection_id: string; + + @ManyToOne(() => ConnectionEntity, { onDelete: 'CASCADE' }) + @JoinColumn({ name: 'connection_id' }) + connection: Relation; + + @OneToMany( + () => DashboardWidgetEntity, + (widget) => widget.dashboard, + ) + widgets: Relation[]; +} diff --git a/backend/src/entities/visualizations/dashboard/dashboards.controller.ts b/backend/src/entities/visualizations/dashboard/dashboards.controller.ts index e69de29bb..73624bad3 100644 --- a/backend/src/entities/visualizations/dashboard/dashboards.controller.ts +++ b/backend/src/entities/visualizations/dashboard/dashboards.controller.ts @@ -0,0 +1,203 @@ +import { + Body, + Controller, + Delete, + Get, + HttpException, + HttpStatus, + Inject, + Injectable, + Param, + Post, + Put, + UseGuards, + UseInterceptors, +} from '@nestjs/common'; +import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { UseCaseType } from '../../../common/data-injection.tokens.js'; +import { MasterPassword } from '../../../decorators/master-password.decorator.js'; +import { SlugUuid } from '../../../decorators/slug-uuid.decorator.js'; +import { UserId } from '../../../decorators/user-id.decorator.js'; +import { InTransactionEnum } from '../../../enums/in-transaction.enum.js'; +import { Messages } from '../../../exceptions/text/messages.js'; +import { ConnectionReadGuard } from '../../../guards/connection-read.guard.js'; +import { ConnectionEditGuard } from '../../../guards/connection-edit.guard.js'; +import { SentryInterceptor } from '../../../interceptors/sentry.interceptor.js'; +import { CreateDashboardDs } from './data-structures/create-dashboard.ds.js'; +import { FindAllDashboardsDs } from './data-structures/find-all-dashboards.ds.js'; +import { FindDashboardDs } from './data-structures/find-dashboard.ds.js'; +import { UpdateDashboardDs } from './data-structures/update-dashboard.ds.js'; +import { CreateDashboardDto } from './dto/create-dashboard.dto.js'; +import { FoundDashboardDto } from './dto/found-dashboard.dto.js'; +import { UpdateDashboardDto } from './dto/update-dashboard.dto.js'; +import { + ICreateDashboard, + IDeleteDashboard, + IFindAllDashboards, + IFindDashboard, + IUpdateDashboard, +} from './use-cases/dashboard-use-cases.interface.js'; + +@UseInterceptors(SentryInterceptor) +@Controller() +@ApiBearerAuth() +@ApiTags('Dashboards') +@Injectable() +export class DashboardController { + constructor( + @Inject(UseCaseType.CREATE_DASHBOARD) + private readonly createDashboardUseCase: ICreateDashboard, + @Inject(UseCaseType.UPDATE_DASHBOARD) + private readonly updateDashboardUseCase: IUpdateDashboard, + @Inject(UseCaseType.FIND_DASHBOARD) + private readonly findDashboardUseCase: IFindDashboard, + @Inject(UseCaseType.FIND_ALL_DASHBOARDS) + private readonly findAllDashboardsUseCase: IFindAllDashboards, + @Inject(UseCaseType.DELETE_DASHBOARD) + private readonly deleteDashboardUseCase: IDeleteDashboard, + ) {} + + @ApiOperation({ summary: 'Get all dashboards for a connection' }) + @ApiResponse({ + status: 200, + description: 'Dashboards found.', + type: FoundDashboardDto, + isArray: true, + }) + @ApiParam({ name: 'connectionId', required: true }) + @UseGuards(ConnectionReadGuard) + @Get('/dashboards/:connectionId') + async findAllDashboards( + @SlugUuid('connectionId') connectionId: string, + @MasterPassword() masterPwd: string, + @UserId() userId: string, + ): Promise { + if (!connectionId) { + throw new HttpException( + { + message: Messages.CONNECTION_ID_MISSING, + }, + HttpStatus.BAD_REQUEST, + ); + } + const inputData: FindAllDashboardsDs = { + connectionId, + masterPassword: masterPwd, + userId, + }; + return await this.findAllDashboardsUseCase.execute(inputData, InTransactionEnum.OFF); + } + + @ApiOperation({ summary: 'Get a dashboard by id' }) + @ApiResponse({ + status: 200, + description: 'Dashboard found.', + type: FoundDashboardDto, + }) + @ApiParam({ name: 'dashboardId', required: true }) + @ApiParam({ name: 'connectionId', required: true }) + @UseGuards(ConnectionReadGuard) + @Get('/dashboard/:dashboardId/:connectionId') + async findDashboard( + @SlugUuid('connectionId') connectionId: string, + @Param('dashboardId') dashboardId: string, + @MasterPassword() masterPwd: string, + @UserId() userId: string, + ): Promise { + const inputData: FindDashboardDs = { + dashboardId, + connectionId, + masterPassword: masterPwd, + userId, + }; + return await this.findDashboardUseCase.execute(inputData, InTransactionEnum.OFF); + } + + @ApiOperation({ summary: 'Create a new dashboard' }) + @ApiResponse({ + status: 201, + description: 'Dashboard created.', + type: FoundDashboardDto, + }) + @ApiBody({ type: CreateDashboardDto }) + @ApiParam({ name: 'connectionId', required: true }) + @UseGuards(ConnectionEditGuard) + @Post('/dashboards/:connectionId') + async createDashboard( + @SlugUuid('connectionId') connectionId: string, + @MasterPassword() masterPwd: string, + @UserId() userId: string, + @Body() createDto: CreateDashboardDto, + ): Promise { + if (!connectionId) { + throw new HttpException( + { + message: Messages.CONNECTION_ID_MISSING, + }, + HttpStatus.BAD_REQUEST, + ); + } + const inputData: CreateDashboardDs = { + connectionId, + masterPassword: masterPwd, + userId, + name: createDto.name, + description: createDto.description, + }; + return await this.createDashboardUseCase.execute(inputData, InTransactionEnum.ON); + } + + @ApiOperation({ summary: 'Update a dashboard' }) + @ApiResponse({ + status: 200, + description: 'Dashboard updated.', + type: FoundDashboardDto, + }) + @ApiBody({ type: UpdateDashboardDto }) + @ApiParam({ name: 'dashboardId', required: true }) + @ApiParam({ name: 'connectionId', required: true }) + @UseGuards(ConnectionEditGuard) + @Put('/dashboard/:dashboardId/:connectionId') + async updateDashboard( + @SlugUuid('connectionId') connectionId: string, + @Param('dashboardId') dashboardId: string, + @MasterPassword() masterPwd: string, + @UserId() userId: string, + @Body() updateDto: UpdateDashboardDto, + ): Promise { + const inputData: UpdateDashboardDs = { + dashboardId, + connectionId, + masterPassword: masterPwd, + userId, + name: updateDto.name, + description: updateDto.description, + }; + return await this.updateDashboardUseCase.execute(inputData, InTransactionEnum.ON); + } + + @ApiOperation({ summary: 'Delete a dashboard' }) + @ApiResponse({ + status: 200, + description: 'Dashboard deleted.', + type: FoundDashboardDto, + }) + @ApiParam({ name: 'dashboardId', required: true }) + @ApiParam({ name: 'connectionId', required: true }) + @UseGuards(ConnectionEditGuard) + @Delete('/dashboard/:dashboardId/:connectionId') + async deleteDashboard( + @SlugUuid('connectionId') connectionId: string, + @Param('dashboardId') dashboardId: string, + @MasterPassword() masterPwd: string, + @UserId() userId: string, + ): Promise { + const inputData: FindDashboardDs = { + dashboardId, + connectionId, + masterPassword: masterPwd, + userId, + }; + return await this.deleteDashboardUseCase.execute(inputData, InTransactionEnum.ON); + } +} diff --git a/backend/src/entities/visualizations/dashboard/dashboards.module.ts b/backend/src/entities/visualizations/dashboard/dashboards.module.ts index e69de29bb..bf9726225 100644 --- a/backend/src/entities/visualizations/dashboard/dashboards.module.ts +++ b/backend/src/entities/visualizations/dashboard/dashboards.module.ts @@ -0,0 +1,77 @@ +import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { GlobalDatabaseContext } from '../../../common/application/global-database-context.js'; +import { BaseType, UseCaseType } from '../../../common/data-injection.tokens.js'; +import { AuthMiddleware } from '../../../authorization/auth.middleware.js'; +import { UserEntity } from '../../user/user.entity.js'; +import { LogOutEntity } from '../../log-out/log-out.entity.js'; +import { DashboardController } from './dashboards.controller.js'; +import { DashboardWidgetController } from './dashboard-widgets.controller.js'; +import { CreateDashboardUseCase } from './use-cases/create-dashboard.use.case.js'; +import { UpdateDashboardUseCase } from './use-cases/update-dashboard.use.case.js'; +import { FindDashboardUseCase } from './use-cases/find-dashboard.use.case.js'; +import { FindAllDashboardsUseCase } from './use-cases/find-all-dashboards.use.case.js'; +import { DeleteDashboardUseCase } from './use-cases/delete-dashboard.use.case.js'; +import { CreateDashboardWidgetUseCase } from './use-cases/create-dashboard-widget.use.case.js'; +import { UpdateDashboardWidgetUseCase } from './use-cases/update-dashboard-widget.use.case.js'; +import { DeleteDashboardWidgetUseCase } from './use-cases/delete-dashboard-widget.use.case.js'; + +@Module({ + imports: [TypeOrmModule.forFeature([UserEntity, LogOutEntity])], + providers: [ + { + provide: BaseType.GLOBAL_DB_CONTEXT, + useClass: GlobalDatabaseContext, + }, + { + provide: UseCaseType.CREATE_DASHBOARD, + useClass: CreateDashboardUseCase, + }, + { + provide: UseCaseType.UPDATE_DASHBOARD, + useClass: UpdateDashboardUseCase, + }, + { + provide: UseCaseType.FIND_DASHBOARD, + useClass: FindDashboardUseCase, + }, + { + provide: UseCaseType.FIND_ALL_DASHBOARDS, + useClass: FindAllDashboardsUseCase, + }, + { + provide: UseCaseType.DELETE_DASHBOARD, + useClass: DeleteDashboardUseCase, + }, + { + provide: UseCaseType.CREATE_DASHBOARD_WIDGET, + useClass: CreateDashboardWidgetUseCase, + }, + { + provide: UseCaseType.UPDATE_DASHBOARD_WIDGET, + useClass: UpdateDashboardWidgetUseCase, + }, + { + provide: UseCaseType.DELETE_DASHBOARD_WIDGET, + useClass: DeleteDashboardWidgetUseCase, + }, + ], + controllers: [DashboardController, DashboardWidgetController], + exports: [], +}) +export class DashboardModule { + public configure(consumer: MiddlewareConsumer): void { + consumer + .apply(AuthMiddleware) + .forRoutes( + { path: '/dashboards/:connectionId', method: RequestMethod.GET }, + { path: '/dashboards/:connectionId', method: RequestMethod.POST }, + { path: '/dashboard/:dashboardId/:connectionId', method: RequestMethod.GET }, + { path: '/dashboard/:dashboardId/:connectionId', method: RequestMethod.PUT }, + { path: '/dashboard/:dashboardId/:connectionId', method: RequestMethod.DELETE }, + { path: '/dashboard/:dashboardId/widget/:connectionId', method: RequestMethod.POST }, + { path: '/dashboard/:dashboardId/widget/:widgetId/:connectionId', method: RequestMethod.PUT }, + { path: '/dashboard/:dashboardId/widget/:widgetId/:connectionId', method: RequestMethod.DELETE }, + ); + } +} diff --git a/backend/src/entities/visualizations/dashboard/data-structures/create-dashboard-widget.ds.ts b/backend/src/entities/visualizations/dashboard/data-structures/create-dashboard-widget.ds.ts new file mode 100644 index 000000000..06398b4a1 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/data-structures/create-dashboard-widget.ds.ts @@ -0,0 +1,17 @@ +import { DashboardWidgetTypeEnum } from '../../../../enums/dashboard-widget-type.enum.js'; + +export class CreateDashboardWidgetDs { + dashboardId: string; + connectionId: string; + masterPassword: string; + userId: string; + widget_type: DashboardWidgetTypeEnum; + name?: string; + description?: string; + position_x?: number; + position_y?: number; + width?: number; + height?: number; + widget_options?: Record; + query_id?: string; +} diff --git a/backend/src/entities/visualizations/dashboard/data-structures/create-dashboard.ds.ts b/backend/src/entities/visualizations/dashboard/data-structures/create-dashboard.ds.ts new file mode 100644 index 000000000..78ad64502 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/data-structures/create-dashboard.ds.ts @@ -0,0 +1,7 @@ +export class CreateDashboardDs { + connectionId: string; + masterPassword: string; + userId: string; + name: string; + description?: string; +} diff --git a/backend/src/entities/visualizations/dashboard/data-structures/delete-dashboard-widget.ds.ts b/backend/src/entities/visualizations/dashboard/data-structures/delete-dashboard-widget.ds.ts new file mode 100644 index 000000000..1d9431f04 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/data-structures/delete-dashboard-widget.ds.ts @@ -0,0 +1,7 @@ +export class DeleteDashboardWidgetDs { + widgetId: string; + dashboardId: string; + connectionId: string; + masterPassword: string; + userId: string; +} diff --git a/backend/src/entities/visualizations/dashboard/data-structures/find-all-dashboards.ds.ts b/backend/src/entities/visualizations/dashboard/data-structures/find-all-dashboards.ds.ts new file mode 100644 index 000000000..614662da6 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/data-structures/find-all-dashboards.ds.ts @@ -0,0 +1,5 @@ +export class FindAllDashboardsDs { + connectionId: string; + masterPassword: string; + userId: string; +} diff --git a/backend/src/entities/visualizations/dashboard/data-structures/find-dashboard.ds.ts b/backend/src/entities/visualizations/dashboard/data-structures/find-dashboard.ds.ts new file mode 100644 index 000000000..68538ec81 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/data-structures/find-dashboard.ds.ts @@ -0,0 +1,6 @@ +export class FindDashboardDs { + dashboardId: string; + connectionId: string; + masterPassword: string; + userId: string; +} diff --git a/backend/src/entities/visualizations/dashboard/data-structures/update-dashboard-widget.ds.ts b/backend/src/entities/visualizations/dashboard/data-structures/update-dashboard-widget.ds.ts new file mode 100644 index 000000000..4a3fe5fa3 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/data-structures/update-dashboard-widget.ds.ts @@ -0,0 +1,18 @@ +import { DashboardWidgetTypeEnum } from '../../../../enums/dashboard-widget-type.enum.js'; + +export class UpdateDashboardWidgetDs { + widgetId: string; + dashboardId: string; + connectionId: string; + masterPassword: string; + userId: string; + widget_type?: DashboardWidgetTypeEnum; + name?: string; + description?: string; + position_x?: number; + position_y?: number; + width?: number; + height?: number; + widget_options?: Record; + query_id?: string; +} diff --git a/backend/src/entities/visualizations/dashboard/data-structures/update-dashboard.ds.ts b/backend/src/entities/visualizations/dashboard/data-structures/update-dashboard.ds.ts new file mode 100644 index 000000000..dbc471d21 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/data-structures/update-dashboard.ds.ts @@ -0,0 +1,8 @@ +export class UpdateDashboardDs { + dashboardId: string; + connectionId: string; + masterPassword: string; + userId: string; + name?: string; + description?: string; +} diff --git a/backend/src/entities/visualizations/dashboard/dto/create-dashboard-widget.dto.ts b/backend/src/entities/visualizations/dashboard/dto/create-dashboard-widget.dto.ts new file mode 100644 index 000000000..869ab0c59 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/dto/create-dashboard-widget.dto.ts @@ -0,0 +1,55 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsInt, IsNotEmpty, IsOptional, IsString, IsUUID, Max, Min } from 'class-validator'; +import { DashboardWidgetTypeEnum } from '../../../../enums/dashboard-widget-type.enum.js'; + +export class CreateDashboardWidgetDto { + @ApiProperty({ description: 'Widget type', enum: DashboardWidgetTypeEnum }) + @IsNotEmpty() + @IsEnum(DashboardWidgetTypeEnum) + widget_type: DashboardWidgetTypeEnum; + + @ApiPropertyOptional({ description: 'Widget name' }) + @IsOptional() + @IsString() + name?: string; + + @ApiPropertyOptional({ description: 'Widget description' }) + @IsOptional() + @IsString() + description?: string; + + @ApiPropertyOptional({ description: 'Position X in grid', default: 0 }) + @IsOptional() + @IsInt() + @Min(0) + position_x?: number; + + @ApiPropertyOptional({ description: 'Position Y in grid', default: 0 }) + @IsOptional() + @IsInt() + @Min(0) + position_y?: number; + + @ApiPropertyOptional({ description: 'Widget width in grid units', default: 4 }) + @IsOptional() + @IsInt() + @Min(1) + @Max(12) + width?: number; + + @ApiPropertyOptional({ description: 'Widget height in grid units', default: 3 }) + @IsOptional() + @IsInt() + @Min(1) + @Max(12) + height?: number; + + @ApiPropertyOptional({ description: 'Visualization options as JSON' }) + @IsOptional() + widget_options?: Record; + + @ApiPropertyOptional({ description: 'Associated saved query ID' }) + @IsOptional() + @IsUUID() + query_id?: string; +} diff --git a/backend/src/entities/visualizations/dashboard/dto/create-dashboard.dto.ts b/backend/src/entities/visualizations/dashboard/dto/create-dashboard.dto.ts new file mode 100644 index 000000000..d77719558 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/dto/create-dashboard.dto.ts @@ -0,0 +1,15 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-validator'; + +export class CreateDashboardDto { + @ApiProperty({ description: 'Dashboard name' }) + @IsNotEmpty() + @IsString() + @MaxLength(255) + name: string; + + @ApiPropertyOptional({ description: 'Dashboard description' }) + @IsOptional() + @IsString() + description?: string; +} diff --git a/backend/src/entities/visualizations/dashboard/dto/found-dashboard-widget.dto.ts b/backend/src/entities/visualizations/dashboard/dto/found-dashboard-widget.dto.ts new file mode 100644 index 000000000..7bc5b0c37 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/dto/found-dashboard-widget.dto.ts @@ -0,0 +1,37 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { DashboardWidgetTypeEnum } from '../../../../enums/dashboard-widget-type.enum.js'; + +export class FoundDashboardWidgetDto { + @ApiProperty({ description: 'Widget ID' }) + id: string; + + @ApiProperty({ description: 'Widget type', enum: DashboardWidgetTypeEnum }) + widget_type: DashboardWidgetTypeEnum; + + @ApiPropertyOptional({ description: 'Widget name' }) + name: string | null; + + @ApiPropertyOptional({ description: 'Widget description' }) + description: string | null; + + @ApiProperty({ description: 'Position X in grid' }) + position_x: number; + + @ApiProperty({ description: 'Position Y in grid' }) + position_y: number; + + @ApiProperty({ description: 'Widget width in grid units' }) + width: number; + + @ApiProperty({ description: 'Widget height in grid units' }) + height: number; + + @ApiPropertyOptional({ description: 'Visualization options' }) + widget_options: Record | null; + + @ApiProperty({ description: 'Dashboard ID' }) + dashboard_id: string; + + @ApiPropertyOptional({ description: 'Associated saved query ID' }) + query_id: string | null; +} diff --git a/backend/src/entities/visualizations/dashboard/dto/found-dashboard.dto.ts b/backend/src/entities/visualizations/dashboard/dto/found-dashboard.dto.ts new file mode 100644 index 000000000..e0ff24fa6 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/dto/found-dashboard.dto.ts @@ -0,0 +1,25 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { FoundDashboardWidgetDto } from './found-dashboard-widget.dto.js'; + +export class FoundDashboardDto { + @ApiProperty({ description: 'Dashboard ID' }) + id: string; + + @ApiProperty({ description: 'Dashboard name' }) + name: string; + + @ApiPropertyOptional({ description: 'Dashboard description' }) + description: string | null; + + @ApiProperty({ description: 'Connection ID' }) + connection_id: string; + + @ApiProperty({ description: 'Creation timestamp' }) + created_at: Date; + + @ApiProperty({ description: 'Last update timestamp' }) + updated_at: Date; + + @ApiPropertyOptional({ description: 'Dashboard widgets', type: FoundDashboardWidgetDto, isArray: true }) + widgets?: FoundDashboardWidgetDto[]; +} diff --git a/backend/src/entities/visualizations/dashboard/dto/update-dashboard-widget.dto.ts b/backend/src/entities/visualizations/dashboard/dto/update-dashboard-widget.dto.ts new file mode 100644 index 000000000..bd77eca2b --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/dto/update-dashboard-widget.dto.ts @@ -0,0 +1,55 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsInt, IsOptional, IsString, IsUUID, Max, Min } from 'class-validator'; +import { DashboardWidgetTypeEnum } from '../../../../enums/dashboard-widget-type.enum.js'; + +export class UpdateDashboardWidgetDto { + @ApiPropertyOptional({ description: 'Widget type', enum: DashboardWidgetTypeEnum }) + @IsOptional() + @IsEnum(DashboardWidgetTypeEnum) + widget_type?: DashboardWidgetTypeEnum; + + @ApiPropertyOptional({ description: 'Widget name' }) + @IsOptional() + @IsString() + name?: string; + + @ApiPropertyOptional({ description: 'Widget description' }) + @IsOptional() + @IsString() + description?: string; + + @ApiPropertyOptional({ description: 'Position X in grid' }) + @IsOptional() + @IsInt() + @Min(0) + position_x?: number; + + @ApiPropertyOptional({ description: 'Position Y in grid' }) + @IsOptional() + @IsInt() + @Min(0) + position_y?: number; + + @ApiPropertyOptional({ description: 'Widget width in grid units' }) + @IsOptional() + @IsInt() + @Min(1) + @Max(12) + width?: number; + + @ApiPropertyOptional({ description: 'Widget height in grid units' }) + @IsOptional() + @IsInt() + @Min(1) + @Max(12) + height?: number; + + @ApiPropertyOptional({ description: 'Visualization options as JSON' }) + @IsOptional() + widget_options?: Record; + + @ApiPropertyOptional({ description: 'Associated saved query ID' }) + @IsOptional() + @IsUUID() + query_id?: string; +} diff --git a/backend/src/entities/visualizations/dashboard/dto/update-dashboard.dto.ts b/backend/src/entities/visualizations/dashboard/dto/update-dashboard.dto.ts new file mode 100644 index 000000000..ec25cac03 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/dto/update-dashboard.dto.ts @@ -0,0 +1,15 @@ +import { ApiPropertyOptional } from '@nestjs/swagger'; +import { IsOptional, IsString, MaxLength } from 'class-validator'; + +export class UpdateDashboardDto { + @ApiPropertyOptional({ description: 'Dashboard name' }) + @IsOptional() + @IsString() + @MaxLength(255) + name?: string; + + @ApiPropertyOptional({ description: 'Dashboard description' }) + @IsOptional() + @IsString() + description?: string; +} diff --git a/backend/src/entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.ts b/backend/src/entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.ts new file mode 100644 index 000000000..a133e168f --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.ts @@ -0,0 +1,35 @@ +import { DashboardEntity } from '../dashboard.entity.js'; + +export const dashboardCustomRepositoryExtension = { + async findDashboardById(dashboardId: string): Promise { + return await this.findOne({ where: { id: dashboardId } }); + }, + + async findDashboardByIdAndConnectionId(dashboardId: string, connectionId: string): Promise { + return await this.findOne({ + where: { id: dashboardId, connection_id: connectionId }, + }); + }, + + async findDashboardWithWidgets(dashboardId: string): Promise { + const qb = this.createQueryBuilder('dashboard') + .leftJoinAndSelect('dashboard.widgets', 'widgets') + .where('dashboard.id = :dashboardId', { dashboardId }); + return await qb.getOne(); + }, + + async findAllDashboardsByConnectionId(connectionId: string): Promise { + return await this.find({ + where: { connection_id: connectionId }, + order: { created_at: 'DESC' }, + }); + }, + + async saveDashboard(dashboard: DashboardEntity): Promise { + return await this.save(dashboard); + }, + + async removeDashboard(dashboard: DashboardEntity): Promise { + return await this.remove(dashboard); + }, +}; diff --git a/backend/src/entities/visualizations/dashboard/repository/dashboard-widget-custom-repository-extension.ts b/backend/src/entities/visualizations/dashboard/repository/dashboard-widget-custom-repository-extension.ts new file mode 100644 index 000000000..9f4247521 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/repository/dashboard-widget-custom-repository-extension.ts @@ -0,0 +1,28 @@ +import { DashboardWidgetEntity } from '../dashboard-widget.entity.js'; + +export const dashboardWidgetCustomRepositoryExtension = { + async findWidgetById(widgetId: string): Promise { + return await this.findOne({ where: { id: widgetId } }); + }, + + async findWidgetByIdAndDashboardId(widgetId: string, dashboardId: string): Promise { + return await this.findOne({ + where: { id: widgetId, dashboard_id: dashboardId }, + }); + }, + + async findAllWidgetsByDashboardId(dashboardId: string): Promise { + return await this.find({ + where: { dashboard_id: dashboardId }, + order: { position_y: 'ASC', position_x: 'ASC' }, + }); + }, + + async saveWidget(widget: DashboardWidgetEntity): Promise { + return await this.save(widget); + }, + + async removeWidget(widget: DashboardWidgetEntity): Promise { + return await this.remove(widget); + }, +}; diff --git a/backend/src/entities/visualizations/dashboard/repository/dashboard.repository.interface.ts b/backend/src/entities/visualizations/dashboard/repository/dashboard.repository.interface.ts new file mode 100644 index 000000000..83579764d --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/repository/dashboard.repository.interface.ts @@ -0,0 +1,19 @@ +import { DashboardEntity } from '../dashboard.entity.js'; +import { DashboardWidgetEntity } from '../dashboard-widget.entity.js'; + +export interface IDashboardRepository { + findDashboardById(dashboardId: string): Promise; + findDashboardByIdAndConnectionId(dashboardId: string, connectionId: string): Promise; + findDashboardWithWidgets(dashboardId: string): Promise; + findAllDashboardsByConnectionId(connectionId: string): Promise; + saveDashboard(dashboard: DashboardEntity): Promise; + removeDashboard(dashboard: DashboardEntity): Promise; +} + +export interface IDashboardWidgetRepository { + findWidgetById(widgetId: string): Promise; + findWidgetByIdAndDashboardId(widgetId: string, dashboardId: string): Promise; + findAllWidgetsByDashboardId(dashboardId: string): Promise; + saveWidget(widget: DashboardWidgetEntity): Promise; + removeWidget(widget: DashboardWidgetEntity): Promise; +} diff --git a/backend/src/entities/visualizations/dashboard/use-cases/create-dashboard-widget.use.case.ts b/backend/src/entities/visualizations/dashboard/use-cases/create-dashboard-widget.use.case.ts new file mode 100644 index 000000000..62e771eaf --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/use-cases/create-dashboard-widget.use.case.ts @@ -0,0 +1,84 @@ +import { BadRequestException, 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 { Messages } from '../../../../exceptions/text/messages.js'; +import { CreateDashboardWidgetDs } from '../data-structures/create-dashboard-widget.ds.js'; +import { DashboardWidgetEntity } from '../dashboard-widget.entity.js'; +import { FoundDashboardWidgetDto } from '../dto/found-dashboard-widget.dto.js'; +import { buildFoundDashboardWidgetDto } from '../utils/build-found-dashboard-widget-dto.util.js'; +import { ICreateDashboardWidget } from './dashboard-use-cases.interface.js'; + +@Injectable({ scope: Scope.REQUEST }) +export class CreateDashboardWidgetUseCase + extends AbstractUseCase + implements ICreateDashboardWidget +{ + constructor( + @Inject(BaseType.GLOBAL_DB_CONTEXT) + protected _dbContext: IGlobalDatabaseContext, + ) { + super(); + } + + public async implementation(inputData: CreateDashboardWidgetDs): Promise { + const { + dashboardId, + connectionId, + masterPassword, + widget_type, + name, + description, + position_x, + position_y, + width, + height, + widget_options, + query_id, + } = inputData; + + const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( + connectionId, + masterPassword, + ); + + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } + + const foundDashboard = await this._dbContext.dashboardRepository.findDashboardByIdAndConnectionId( + dashboardId, + connectionId, + ); + + if (!foundDashboard) { + throw new NotFoundException(Messages.DASHBOARD_NOT_FOUND); + } + + // Validate query_id if provided + if (query_id) { + const foundQuery = await this._dbContext.savedDbQueryRepository.findQueryByIdAndConnectionId( + query_id, + connectionId, + ); + if (!foundQuery) { + throw new BadRequestException(Messages.SAVED_QUERY_NOT_FOUND); + } + } + + const newWidget = new DashboardWidgetEntity(); + newWidget.widget_type = widget_type; + newWidget.name = name || null; + newWidget.description = description || null; + newWidget.position_x = position_x ?? 0; + newWidget.position_y = position_y ?? 0; + newWidget.width = width ?? 4; + newWidget.height = height ?? 3; + newWidget.widget_options = widget_options ? JSON.stringify(widget_options) : null; + newWidget.dashboard_id = dashboardId; + newWidget.query_id = query_id || null; + + const savedWidget = await this._dbContext.dashboardWidgetRepository.saveWidget(newWidget); + return buildFoundDashboardWidgetDto(savedWidget); + } +} 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 new file mode 100644 index 000000000..fedc83105 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/use-cases/create-dashboard.use.case.ts @@ -0,0 +1,44 @@ +import { 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 { Messages } from '../../../../exceptions/text/messages.js'; +import { CreateDashboardDs } from '../data-structures/create-dashboard.ds.js'; +import { DashboardEntity } from '../dashboard.entity.js'; +import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; +import { buildFoundDashboardDto } from '../utils/build-found-dashboard-dto.util.js'; +import { ICreateDashboard } from './dashboard-use-cases.interface.js'; + +@Injectable({ scope: Scope.REQUEST }) +export class CreateDashboardUseCase + extends AbstractUseCase + implements ICreateDashboard +{ + constructor( + @Inject(BaseType.GLOBAL_DB_CONTEXT) + protected _dbContext: IGlobalDatabaseContext, + ) { + super(); + } + + public async implementation(inputData: CreateDashboardDs): Promise { + const { connectionId, masterPassword, name, description } = inputData; + + const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( + connectionId, + masterPassword, + ); + + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } + + const newDashboard = new DashboardEntity(); + newDashboard.name = name; + newDashboard.description = description || null; + newDashboard.connection_id = foundConnection.id; + + const savedDashboard = await this._dbContext.dashboardRepository.saveDashboard(newDashboard); + return buildFoundDashboardDto(savedDashboard); + } +} diff --git a/backend/src/entities/visualizations/dashboard/use-cases/dashboard-use-cases.interface.ts b/backend/src/entities/visualizations/dashboard/use-cases/dashboard-use-cases.interface.ts new file mode 100644 index 000000000..fe5ca62f8 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/use-cases/dashboard-use-cases.interface.ts @@ -0,0 +1,42 @@ +import { InTransactionEnum } from '../../../../enums/in-transaction.enum.js'; +import { CreateDashboardDs } from '../data-structures/create-dashboard.ds.js'; +import { UpdateDashboardDs } from '../data-structures/update-dashboard.ds.js'; +import { FindDashboardDs } from '../data-structures/find-dashboard.ds.js'; +import { FindAllDashboardsDs } from '../data-structures/find-all-dashboards.ds.js'; +import { CreateDashboardWidgetDs } from '../data-structures/create-dashboard-widget.ds.js'; +import { UpdateDashboardWidgetDs } from '../data-structures/update-dashboard-widget.ds.js'; +import { DeleteDashboardWidgetDs } from '../data-structures/delete-dashboard-widget.ds.js'; +import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; +import { FoundDashboardWidgetDto } from '../dto/found-dashboard-widget.dto.js'; + +export interface ICreateDashboard { + execute(inputData: CreateDashboardDs, inTransaction: InTransactionEnum): Promise; +} + +export interface IUpdateDashboard { + execute(inputData: UpdateDashboardDs, inTransaction: InTransactionEnum): Promise; +} + +export interface IFindDashboard { + execute(inputData: FindDashboardDs, inTransaction: InTransactionEnum): Promise; +} + +export interface IFindAllDashboards { + execute(inputData: FindAllDashboardsDs, inTransaction: InTransactionEnum): Promise; +} + +export interface IDeleteDashboard { + execute(inputData: FindDashboardDs, inTransaction: InTransactionEnum): Promise; +} + +export interface ICreateDashboardWidget { + execute(inputData: CreateDashboardWidgetDs, inTransaction: InTransactionEnum): Promise; +} + +export interface IUpdateDashboardWidget { + execute(inputData: UpdateDashboardWidgetDs, inTransaction: InTransactionEnum): Promise; +} + +export interface IDeleteDashboardWidget { + execute(inputData: DeleteDashboardWidgetDs, inTransaction: InTransactionEnum): Promise; +} diff --git a/backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard-widget.use.case.ts b/backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard-widget.use.case.ts new file mode 100644 index 000000000..064042665 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard-widget.use.case.ts @@ -0,0 +1,58 @@ +import { 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 { Messages } from '../../../../exceptions/text/messages.js'; +import { DeleteDashboardWidgetDs } from '../data-structures/delete-dashboard-widget.ds.js'; +import { FoundDashboardWidgetDto } from '../dto/found-dashboard-widget.dto.js'; +import { buildFoundDashboardWidgetDto } from '../utils/build-found-dashboard-widget-dto.util.js'; +import { IDeleteDashboardWidget } from './dashboard-use-cases.interface.js'; + +@Injectable({ scope: Scope.REQUEST }) +export class DeleteDashboardWidgetUseCase + extends AbstractUseCase + implements IDeleteDashboardWidget +{ + constructor( + @Inject(BaseType.GLOBAL_DB_CONTEXT) + protected _dbContext: IGlobalDatabaseContext, + ) { + super(); + } + + public async implementation(inputData: DeleteDashboardWidgetDs): Promise { + const { widgetId, dashboardId, connectionId, masterPassword } = inputData; + + const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( + connectionId, + masterPassword, + ); + + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } + + const foundDashboard = await this._dbContext.dashboardRepository.findDashboardByIdAndConnectionId( + dashboardId, + connectionId, + ); + + if (!foundDashboard) { + throw new NotFoundException(Messages.DASHBOARD_NOT_FOUND); + } + + const foundWidget = await this._dbContext.dashboardWidgetRepository.findWidgetByIdAndDashboardId( + widgetId, + dashboardId, + ); + + if (!foundWidget) { + throw new NotFoundException(Messages.DASHBOARD_WIDGET_NOT_FOUND); + } + + const widgetDto = buildFoundDashboardWidgetDto(foundWidget); + await this._dbContext.dashboardWidgetRepository.removeWidget(foundWidget); + + return widgetDto; + } +} 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 new file mode 100644 index 000000000..bac08b56b --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard.use.case.ts @@ -0,0 +1,49 @@ +import { 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 { Messages } from '../../../../exceptions/text/messages.js'; +import { FindDashboardDs } from '../data-structures/find-dashboard.ds.js'; +import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; +import { buildFoundDashboardDto } from '../utils/build-found-dashboard-dto.util.js'; +import { IDeleteDashboard } from './dashboard-use-cases.interface.js'; + +@Injectable({ scope: Scope.REQUEST }) +export class DeleteDashboardUseCase + extends AbstractUseCase + implements IDeleteDashboard +{ + constructor( + @Inject(BaseType.GLOBAL_DB_CONTEXT) + protected _dbContext: IGlobalDatabaseContext, + ) { + super(); + } + + public async implementation(inputData: FindDashboardDs): Promise { + const { dashboardId, connectionId, masterPassword } = inputData; + + const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( + connectionId, + masterPassword, + ); + + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } + + const foundDashboard = await this._dbContext.dashboardRepository.findDashboardByIdAndConnectionId( + dashboardId, + connectionId, + ); + + if (!foundDashboard) { + throw new NotFoundException(Messages.DASHBOARD_NOT_FOUND); + } + + const dashboardDto = buildFoundDashboardDto(foundDashboard); + await this._dbContext.dashboardRepository.removeDashboard(foundDashboard); + + return dashboardDto; + } +} 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 new file mode 100644 index 000000000..59663834a --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/use-cases/find-all-dashboards.use.case.ts @@ -0,0 +1,39 @@ +import { 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 { 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'; +import { IFindAllDashboards } from './dashboard-use-cases.interface.js'; + +@Injectable({ scope: Scope.REQUEST }) +export class FindAllDashboardsUseCase + extends AbstractUseCase + implements IFindAllDashboards +{ + constructor( + @Inject(BaseType.GLOBAL_DB_CONTEXT) + protected _dbContext: IGlobalDatabaseContext, + ) { + super(); + } + + public async implementation(inputData: FindAllDashboardsDs): Promise { + const { connectionId, masterPassword } = inputData; + + const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( + connectionId, + masterPassword, + ); + + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } + + const dashboards = await this._dbContext.dashboardRepository.findAllDashboardsByConnectionId(connectionId); + + return dashboards.map(buildFoundDashboardDto); + } +} 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 new file mode 100644 index 000000000..1c41ecfa7 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/use-cases/find-dashboard.use.case.ts @@ -0,0 +1,43 @@ +import { 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 { Messages } from '../../../../exceptions/text/messages.js'; +import { FindDashboardDs } from '../data-structures/find-dashboard.ds.js'; +import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; +import { buildFoundDashboardDto } from '../utils/build-found-dashboard-dto.util.js'; +import { IFindDashboard } from './dashboard-use-cases.interface.js'; + +@Injectable({ scope: Scope.REQUEST }) +export class FindDashboardUseCase + extends AbstractUseCase + implements IFindDashboard +{ + constructor( + @Inject(BaseType.GLOBAL_DB_CONTEXT) + protected _dbContext: IGlobalDatabaseContext, + ) { + super(); + } + + public async implementation(inputData: FindDashboardDs): Promise { + const { dashboardId, connectionId, masterPassword } = inputData; + + const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( + connectionId, + masterPassword, + ); + + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } + + const foundDashboard = await this._dbContext.dashboardRepository.findDashboardWithWidgets(dashboardId); + + if (!foundDashboard || foundDashboard.connection_id !== connectionId) { + throw new NotFoundException(Messages.DASHBOARD_NOT_FOUND); + } + + return buildFoundDashboardDto(foundDashboard); + } +} diff --git a/backend/src/entities/visualizations/dashboard/use-cases/update-dashboard-widget.use.case.ts b/backend/src/entities/visualizations/dashboard/use-cases/update-dashboard-widget.use.case.ts new file mode 100644 index 000000000..18850c6cd --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/use-cases/update-dashboard-widget.use.case.ts @@ -0,0 +1,109 @@ +import { BadRequestException, 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 { Messages } from '../../../../exceptions/text/messages.js'; +import { UpdateDashboardWidgetDs } from '../data-structures/update-dashboard-widget.ds.js'; +import { FoundDashboardWidgetDto } from '../dto/found-dashboard-widget.dto.js'; +import { buildFoundDashboardWidgetDto } from '../utils/build-found-dashboard-widget-dto.util.js'; +import { IUpdateDashboardWidget } from './dashboard-use-cases.interface.js'; + +@Injectable({ scope: Scope.REQUEST }) +export class UpdateDashboardWidgetUseCase + extends AbstractUseCase + implements IUpdateDashboardWidget +{ + constructor( + @Inject(BaseType.GLOBAL_DB_CONTEXT) + protected _dbContext: IGlobalDatabaseContext, + ) { + super(); + } + + public async implementation(inputData: UpdateDashboardWidgetDs): Promise { + const { + widgetId, + dashboardId, + connectionId, + masterPassword, + widget_type, + name, + description, + position_x, + position_y, + width, + height, + widget_options, + query_id, + } = inputData; + + const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( + connectionId, + masterPassword, + ); + + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } + + const foundDashboard = await this._dbContext.dashboardRepository.findDashboardByIdAndConnectionId( + dashboardId, + connectionId, + ); + + if (!foundDashboard) { + throw new NotFoundException(Messages.DASHBOARD_NOT_FOUND); + } + + const foundWidget = await this._dbContext.dashboardWidgetRepository.findWidgetByIdAndDashboardId( + widgetId, + dashboardId, + ); + + if (!foundWidget) { + throw new NotFoundException(Messages.DASHBOARD_WIDGET_NOT_FOUND); + } + + // Validate query_id if provided + if (query_id !== undefined && query_id !== null) { + const foundQuery = await this._dbContext.savedDbQueryRepository.findQueryByIdAndConnectionId( + query_id, + connectionId, + ); + if (!foundQuery) { + throw new BadRequestException(Messages.SAVED_QUERY_NOT_FOUND); + } + } + + if (widget_type !== undefined) { + foundWidget.widget_type = widget_type; + } + if (name !== undefined) { + foundWidget.name = name; + } + if (description !== undefined) { + foundWidget.description = description; + } + if (position_x !== undefined) { + foundWidget.position_x = position_x; + } + if (position_y !== undefined) { + foundWidget.position_y = position_y; + } + if (width !== undefined) { + foundWidget.width = width; + } + if (height !== undefined) { + foundWidget.height = height; + } + if (widget_options !== undefined) { + foundWidget.widget_options = widget_options ? JSON.stringify(widget_options) : null; + } + if (query_id !== undefined) { + foundWidget.query_id = query_id; + } + + const savedWidget = await this._dbContext.dashboardWidgetRepository.saveWidget(foundWidget); + return buildFoundDashboardWidgetDto(savedWidget); + } +} 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 new file mode 100644 index 000000000..3f8eb0423 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/use-cases/update-dashboard.use.case.ts @@ -0,0 +1,55 @@ +import { 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 { Messages } from '../../../../exceptions/text/messages.js'; +import { UpdateDashboardDs } from '../data-structures/update-dashboard.ds.js'; +import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; +import { buildFoundDashboardDto } from '../utils/build-found-dashboard-dto.util.js'; +import { IUpdateDashboard } from './dashboard-use-cases.interface.js'; + +@Injectable({ scope: Scope.REQUEST }) +export class UpdateDashboardUseCase + extends AbstractUseCase + implements IUpdateDashboard +{ + constructor( + @Inject(BaseType.GLOBAL_DB_CONTEXT) + protected _dbContext: IGlobalDatabaseContext, + ) { + super(); + } + + public async implementation(inputData: UpdateDashboardDs): Promise { + const { dashboardId, connectionId, masterPassword, name, description } = inputData; + + const foundConnection = await this._dbContext.connectionRepository.findAndDecryptConnection( + connectionId, + masterPassword, + ); + + if (!foundConnection) { + throw new NotFoundException(Messages.CONNECTION_NOT_FOUND); + } + + const foundDashboard = await this._dbContext.dashboardRepository.findDashboardByIdAndConnectionId( + dashboardId, + connectionId, + ); + + if (!foundDashboard) { + throw new NotFoundException(Messages.DASHBOARD_NOT_FOUND); + } + + if (name !== undefined) { + foundDashboard.name = name; + } + if (description !== undefined) { + foundDashboard.description = description; + } + foundDashboard.updated_at = new Date(); + + const savedDashboard = await this._dbContext.dashboardRepository.saveDashboard(foundDashboard); + return buildFoundDashboardDto(savedDashboard); + } +} diff --git a/backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-dto.util.ts b/backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-dto.util.ts new file mode 100644 index 000000000..0168fcc8f --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-dto.util.ts @@ -0,0 +1,15 @@ +import { DashboardEntity } from '../dashboard.entity.js'; +import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; +import { buildFoundDashboardWidgetDto } from './build-found-dashboard-widget-dto.util.js'; + +export function buildFoundDashboardDto(dashboard: DashboardEntity): FoundDashboardDto { + return { + id: dashboard.id, + name: dashboard.name, + description: dashboard.description, + connection_id: dashboard.connection_id, + created_at: dashboard.created_at, + updated_at: dashboard.updated_at, + widgets: dashboard.widgets?.map(buildFoundDashboardWidgetDto), + }; +} diff --git a/backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-widget-dto.util.ts b/backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-widget-dto.util.ts new file mode 100644 index 000000000..130ad81fc --- /dev/null +++ b/backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-widget-dto.util.ts @@ -0,0 +1,18 @@ +import { DashboardWidgetEntity } from '../dashboard-widget.entity.js'; +import { FoundDashboardWidgetDto } from '../dto/found-dashboard-widget.dto.js'; + +export function buildFoundDashboardWidgetDto(widget: DashboardWidgetEntity): FoundDashboardWidgetDto { + return { + id: widget.id, + widget_type: widget.widget_type, + name: widget.name, + description: widget.description, + position_x: widget.position_x, + position_y: widget.position_y, + width: widget.width, + height: widget.height, + widget_options: widget.widget_options as unknown as Record | null, + dashboard_id: widget.dashboard_id, + query_id: widget.query_id, + }; +} diff --git a/backend/src/entities/visualizations/saved-db-query/saved-db-query.entity.ts b/backend/src/entities/visualizations/saved-db-query/saved-db-query.entity.ts index bf58b04c2..5d1b44467 100644 --- a/backend/src/entities/visualizations/saved-db-query/saved-db-query.entity.ts +++ b/backend/src/entities/visualizations/saved-db-query/saved-db-query.entity.ts @@ -6,11 +6,13 @@ import { Entity, JoinColumn, ManyToOne, + OneToMany, PrimaryGeneratedColumn, Relation, } from 'typeorm'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; import { ConnectionEntity } from '../../connection/connection.entity.js'; +import { DashboardWidgetEntity } from '../dashboard/dashboard-widget.entity.js'; @Entity('saved_db_query') export class SavedDbQueryEntity { @@ -64,4 +66,10 @@ export class SavedDbQueryEntity { @Column({ type: 'varchar', length: 38 }) connection_id: string; + + @OneToMany( + () => DashboardWidgetEntity, + (widget) => widget.query, + ) + widgets: Relation[]; } diff --git a/backend/src/enums/dashboard-widget-type.enum.ts b/backend/src/enums/dashboard-widget-type.enum.ts new file mode 100644 index 000000000..b7589d9c8 --- /dev/null +++ b/backend/src/enums/dashboard-widget-type.enum.ts @@ -0,0 +1,6 @@ +export enum DashboardWidgetTypeEnum { + Table = 'table', + Chart = 'chart', + Counter = 'counter', + Text = 'text', +} diff --git a/backend/src/enums/index.ts b/backend/src/enums/index.ts index ac784e03b..917246f4f 100644 --- a/backend/src/enums/index.ts +++ b/backend/src/enums/index.ts @@ -1,5 +1,6 @@ export { AccessLevelEnum } from './access-level.enum.js'; export { AmplitudeEventTypeEnum } from './amplitude-event-type.enum.js'; +export { DashboardWidgetTypeEnum } from './dashboard-widget-type.enum.js'; export { EncryptionAlgorithmEnum } from './encryption-algorithm.enum.js'; export { FilterCriteriaEnum } from './filter-criteria.enum.js'; export { InTransactionEnum } from './in-transaction.enum.js'; diff --git a/backend/src/exceptions/text/messages.ts b/backend/src/exceptions/text/messages.ts index 8e875608f..99ef4d3da 100644 --- a/backend/src/exceptions/text/messages.ts +++ b/backend/src/exceptions/text/messages.ts @@ -107,6 +107,8 @@ export const Messages = { DONT_HAVE_PERMISSIONS: 'You do not have permission to perform this operation', DONT_HAVE_NON_TEST_CONNECTIONS: 'You only have test connections. To remove test connections please add your connection first', + DASHBOARD_NOT_FOUND: 'Dashboard with specified parameters not found', + DASHBOARD_WIDGET_NOT_FOUND: 'Dashboard widget with specified parameters not found', SAVED_QUERY_NOT_FOUND: 'Saved query with specified parameters not found', ENCRYPTION_ALGORITHM_INCORRECT: (alg: string) => `Unsupported algorithm type${alg ? ` ${alg}.` : '.'} We supports only ${enumToString( diff --git a/backend/src/migrations/1768393822639-AddDashboardAndDashboardWigetEntities.ts b/backend/src/migrations/1768393822639-AddDashboardAndDashboardWigetEntities.ts new file mode 100644 index 000000000..50a1b6648 --- /dev/null +++ b/backend/src/migrations/1768393822639-AddDashboardAndDashboardWigetEntities.ts @@ -0,0 +1,31 @@ +import { MigrationInterface, QueryRunner } from 'typeorm'; + +export class AddDashboardAndDashboardWigetEntities1768393822639 implements MigrationInterface { + name = 'AddDashboardAndDashboardWigetEntities1768393822639'; + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query( + `CREATE TABLE "dashboard" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "name" character varying NOT NULL, "description" text, "created_at" TIMESTAMP NOT NULL DEFAULT now(), "updated_at" TIMESTAMP NOT NULL DEFAULT now(), "connection_id" character varying NOT NULL, CONSTRAINT "PK_233ed28fa3a1f9fbe743f571f75" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `CREATE TABLE "dashboard_widget" ("id" uuid NOT NULL DEFAULT uuid_generate_v4(), "widget_type" character varying NOT NULL, "name" character varying, "description" text, "position_x" integer NOT NULL DEFAULT '0', "position_y" integer NOT NULL DEFAULT '0', "width" integer NOT NULL DEFAULT '4', "height" integer NOT NULL DEFAULT '3', "widget_options" json, "dashboard_id" uuid NOT NULL, "query_id" uuid, CONSTRAINT "PK_d776e45a42322c53e9167b00ead" PRIMARY KEY ("id"))`, + ); + await queryRunner.query( + `ALTER TABLE "dashboard" ADD CONSTRAINT "FK_61891f58faf0242381786d60334" FOREIGN KEY ("connection_id") REFERENCES "connection"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "dashboard_widget" ADD CONSTRAINT "FK_1d4cbbe2829d760116ce4472bd5" FOREIGN KEY ("dashboard_id") REFERENCES "dashboard"("id") ON DELETE CASCADE ON UPDATE NO ACTION`, + ); + await queryRunner.query( + `ALTER TABLE "dashboard_widget" ADD CONSTRAINT "FK_2d30b309abbaf0e051fd89560b9" FOREIGN KEY ("query_id") REFERENCES "saved_db_query"("id") ON DELETE SET NULL ON UPDATE NO ACTION`, + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "dashboard_widget" DROP CONSTRAINT "FK_2d30b309abbaf0e051fd89560b9"`); + await queryRunner.query(`ALTER TABLE "dashboard_widget" DROP CONSTRAINT "FK_1d4cbbe2829d760116ce4472bd5"`); + await queryRunner.query(`ALTER TABLE "dashboard" DROP CONSTRAINT "FK_61891f58faf0242381786d60334"`); + await queryRunner.query(`DROP TABLE "dashboard_widget"`); + await queryRunner.query(`DROP TABLE "dashboard"`); + } +} diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-dashboard-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-dashboard-e2e.test.ts new file mode 100644 index 000000000..df3f98488 --- /dev/null +++ b/backend/test/ava-tests/non-saas-tests/non-saas-dashboard-e2e.test.ts @@ -0,0 +1,622 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { faker } from '@faker-js/faker'; +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import test from 'ava'; +import { ValidationError } from 'class-validator'; +import cookieParser from 'cookie-parser'; +import request from 'supertest'; +import { ApplicationModule } from '../../../src/app.module.js'; +import { WinstonLogger } from '../../../src/entities/logging/winston-logger.js'; +import { AllExceptionsFilter } from '../../../src/exceptions/all-exceptions.filter.js'; +import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; +import { Cacher } from '../../../src/helpers/cache/cacher.js'; +import { DatabaseModule } from '../../../src/shared/database/database.module.js'; +import { DatabaseService } from '../../../src/shared/database/database.service.js'; +import { MockFactory } from '../../mock.factory.js'; +import { getTestData } from '../../utils/get-test-data.js'; +import { registerUserAndReturnUserInfo } from '../../utils/register-user-and-return-user-info.js'; +import { setSaasEnvVariable } from '../../utils/set-saas-env-variable.js'; +import { TestUtils } from '../../utils/test.utils.js'; + +const mockFactory = new MockFactory(); +let app: INestApplication; +let _testUtils: TestUtils; +let currentTest: string; + +const _uuidRegex: RegExp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + +test.before(async () => { + setSaasEnvVariable(); + const moduleFixture = await Test.createTestingModule({ + imports: [ApplicationModule, DatabaseModule], + providers: [DatabaseService, TestUtils], + }).compile(); + app = moduleFixture.createNestApplication(); + _testUtils = moduleFixture.get(TestUtils); + + app.use(cookieParser()); + app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger))); + app.useGlobalPipes( + new ValidationPipe({ + exceptionFactory(validationErrors: ValidationError[] = []) { + return new ValidationException(validationErrors); + }, + }), + ); + await app.init(); + app.getHttpServer().listen(0); +}); + +test.after(async () => { + try { + await Cacher.clearAllCache(); + await app.close(); + } catch (e) { + console.error('After tests error ' + e); + } +}); + +// ==================== Dashboard CRUD Tests ==================== + +currentTest = 'GET /dashboards/:connectionId'; + +test.serial(`${currentTest} should return empty array when no dashboards created`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const getDashboards = await request(app.getHttpServer()) + .get(`/dashboards/${connectionId}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const getDashboardsRO = JSON.parse(getDashboards.text); + t.is(getDashboards.status, 200); + t.is(Array.isArray(getDashboardsRO), true); + t.is(getDashboardsRO.length, 0); +}); + +currentTest = 'POST /dashboards/:connectionId'; + +test.serial(`${currentTest} should create a new dashboard`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const newDashboard = { + name: 'Test Dashboard', + description: 'Test dashboard description', + }; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send(newDashboard) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createDashboardRO = JSON.parse(createDashboard.text); + t.is(createDashboard.status, 201); + t.is(createDashboardRO.name, newDashboard.name); + t.is(createDashboardRO.description, newDashboard.description); + t.is(createDashboardRO.connection_id, connectionId); + t.truthy(createDashboardRO.id); + t.truthy(createDashboardRO.created_at); + t.truthy(createDashboardRO.updated_at); +}); + +test.serial(`${currentTest} should fail to create dashboard without name`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({}) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(createDashboard.status, 400); +}); + +currentTest = 'GET /dashboard/:dashboardId'; + +test.serial(`${currentTest} should return dashboard with widgets`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const newDashboard = { + name: 'Test Dashboard', + description: 'Test dashboard description', + }; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send(newDashboard) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const getDashboard = await request(app.getHttpServer()) + .get(`/dashboard/${dashboardId}/${connectionId}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const getDashboardRO = JSON.parse(getDashboard.text); + t.is(getDashboard.status, 200); + t.is(getDashboardRO.id, dashboardId); + t.is(getDashboardRO.name, newDashboard.name); + t.is(getDashboardRO.description, newDashboard.description); + t.is(Array.isArray(getDashboardRO.widgets), true); +}); + +test.serial(`${currentTest} should return 404 for non-existent dashboard`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + const fakeDashboardId = faker.string.uuid(); + + const getDashboard = await request(app.getHttpServer()) + .get(`/dashboard/${fakeDashboardId}/${connectionId}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + t.is(getDashboard.status, 404); +}); + +currentTest = 'PUT /dashboard/:dashboardId'; + +test.serial(`${currentTest} should update dashboard`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Original Name' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const updateData = { + name: 'Updated Name', + description: 'Updated description', + }; + + const updateDashboard = await request(app.getHttpServer()) + .put(`/dashboard/${dashboardId}/${connectionId}`) + .send(updateData) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const updateDashboardRO = JSON.parse(updateDashboard.text); + t.is(updateDashboard.status, 200); + t.is(updateDashboardRO.name, updateData.name); + t.is(updateDashboardRO.description, updateData.description); +}); + +currentTest = 'DELETE /dashboard/:dashboardId'; + +test.serial(`${currentTest} should delete dashboard`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard to Delete' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const deleteDashboard = await request(app.getHttpServer()) + .delete(`/dashboard/${dashboardId}/${connectionId}`) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteDashboard.status, 200); + + // Verify dashboard is deleted + const getDashboard = await request(app.getHttpServer()) + .get(`/dashboard/${dashboardId}/${connectionId}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + t.is(getDashboard.status, 404); +}); + +// ==================== Widget CRUD Tests ==================== + +currentTest = 'POST /dashboards/:connectionId/:dashboardId/widget'; + +test.serial(`${currentTest} should create a new widget in dashboard`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard with Widgets' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const newWidget = { + widget_type: 'table', + name: 'Test Widget', + description: 'Test widget description', + position_x: 0, + position_y: 0, + width: 4, + height: 3, + }; + + const createWidget = await request(app.getHttpServer()) + .post(`/dashboard/${dashboardId}/widget/${connectionId}`) + .send(newWidget) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createWidgetRO = JSON.parse(createWidget.text); + t.is(createWidget.status, 201); + t.is(createWidgetRO.widget_type, newWidget.widget_type); + t.is(createWidgetRO.name, newWidget.name); + t.is(createWidgetRO.description, newWidget.description); + t.is(createWidgetRO.position_x, newWidget.position_x); + t.is(createWidgetRO.position_y, newWidget.position_y); + t.is(createWidgetRO.width, newWidget.width); + t.is(createWidgetRO.height, newWidget.height); + t.is(createWidgetRO.dashboard_id, dashboardId); +}); + +test.serial(`${currentTest} should create widget with all types`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard with All Widget Types' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const widgetTypes = ['table', 'chart', 'counter', 'text']; + + for (const widgetType of widgetTypes) { + const createWidget = await request(app.getHttpServer()) + .post(`/dashboard/${dashboardId}/widget/${connectionId}`) + .send({ widget_type: widgetType, name: `${widgetType} Widget` }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(createWidget.status, 201); + const createWidgetRO = JSON.parse(createWidget.text); + t.is(createWidgetRO.widget_type, widgetType); + } +}); + +currentTest = 'PUT /dashboard/:dashboardId/widget/:widgetId'; + +test.serial(`${currentTest} should update widget`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard for Widget Update' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const createWidget = await request(app.getHttpServer()) + .post(`/dashboard/${dashboardId}/widget/${connectionId}`) + .send({ widget_type: 'table', name: 'Original Widget Name' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const widgetId = JSON.parse(createWidget.text).id; + + const updateData = { + name: 'Updated Widget Name', + position_x: 2, + position_y: 1, + width: 6, + height: 4, + }; + + const updateWidget = await request(app.getHttpServer()) + .put(`/dashboard/${dashboardId}/widget/${widgetId}/${connectionId}`) + .send(updateData) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const updateWidgetRO = JSON.parse(updateWidget.text); + t.is(updateWidget.status, 200); + t.is(updateWidgetRO.name, updateData.name); + t.is(updateWidgetRO.position_x, updateData.position_x); + t.is(updateWidgetRO.position_y, updateData.position_y); + t.is(updateWidgetRO.width, updateData.width); + t.is(updateWidgetRO.height, updateData.height); +}); + +currentTest = 'DELETE /dashboard/:dashboardId/widget/:widgetId'; + +test.serial(`${currentTest} should delete widget from dashboard`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard for Widget Delete' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const createWidget = await request(app.getHttpServer()) + .post(`/dashboard/${dashboardId}/widget/${connectionId}`) + .send({ widget_type: 'counter', name: 'Widget to Delete' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const widgetId = JSON.parse(createWidget.text).id; + + const deleteWidget = await request(app.getHttpServer()) + .delete(`/dashboard/${dashboardId}/widget/${widgetId}/${connectionId}`) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteWidget.status, 200); + + // Verify widget is deleted by getting dashboard + const getDashboard = await request(app.getHttpServer()) + .get(`/dashboard/${dashboardId}/${connectionId}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const getDashboardRO = JSON.parse(getDashboard.text); + t.is(getDashboard.status, 200); + t.is(getDashboardRO.widgets.length, 0); +}); + +// ==================== Widget with Saved Query Tests ==================== + +currentTest = 'Widget with Saved Query'; + +test.serial(`${currentTest} should create widget linked to a saved query`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + // Create a saved query first + const createQuery = await request(app.getHttpServer()) + .post(`/connection/${connectionId}/saved-query`) + .send({ + name: 'Test Query for Widget', + query_text: 'SELECT * FROM connection LIMIT 10', + }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const queryId = JSON.parse(createQuery.text).id; + + // Create dashboard + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard with Query Widget' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + // Create widget linked to saved query + const createWidget = await request(app.getHttpServer()) + .post(`/dashboard/${dashboardId}/widget/${connectionId}`) + .send({ + widget_type: 'table', + name: 'Query Result Widget', + query_id: queryId, + }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createWidgetRO = JSON.parse(createWidget.text); + t.is(createWidget.status, 201); + t.is(createWidgetRO.query_id, queryId); +}); + +test.serial(`${currentTest} should fail to create widget with non-existent query`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard with Invalid Query' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const fakeQueryId = faker.string.uuid(); + + const createWidget = await request(app.getHttpServer()) + .post(`/dashboard/${dashboardId}/widget/${connectionId}`) + .send({ + widget_type: 'table', + name: 'Widget with Fake Query', + query_id: fakeQueryId, + }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(createWidget.status, 400); +}); diff --git a/backend/test/ava-tests/saas-tests/dashboard-e2e.test.ts b/backend/test/ava-tests/saas-tests/dashboard-e2e.test.ts new file mode 100644 index 000000000..e735bc3fb --- /dev/null +++ b/backend/test/ava-tests/saas-tests/dashboard-e2e.test.ts @@ -0,0 +1,621 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +import { faker } from '@faker-js/faker'; +import { INestApplication, ValidationPipe } from '@nestjs/common'; +import { Test } from '@nestjs/testing'; +import test from 'ava'; +import { ValidationError } from 'class-validator'; +import cookieParser from 'cookie-parser'; +import request from 'supertest'; +import { ApplicationModule } from '../../../src/app.module.js'; +import { WinstonLogger } from '../../../src/entities/logging/winston-logger.js'; +import { AllExceptionsFilter } from '../../../src/exceptions/all-exceptions.filter.js'; +import { ValidationException } from '../../../src/exceptions/custom-exceptions/validation-exception.js'; +import { Cacher } from '../../../src/helpers/cache/cacher.js'; +import { DatabaseModule } from '../../../src/shared/database/database.module.js'; +import { DatabaseService } from '../../../src/shared/database/database.service.js'; +import { MockFactory } from '../../mock.factory.js'; +import { getTestData } from '../../utils/get-test-data.js'; +import { registerUserAndReturnUserInfo } from '../../utils/register-user-and-return-user-info.js'; +import { TestUtils } from '../../utils/test.utils.js'; + +const mockFactory = new MockFactory(); +let app: INestApplication; +let _testUtils: TestUtils; +let currentTest: string; + +const _uuidRegex: RegExp = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-5][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$/i; + +test.before(async () => { + const moduleFixture = await Test.createTestingModule({ + imports: [ApplicationModule, DatabaseModule], + providers: [DatabaseService, TestUtils], + }).compile(); + app = moduleFixture.createNestApplication(); + _testUtils = moduleFixture.get(TestUtils); + + app.use(cookieParser()); + app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger))); + app.useGlobalPipes( + new ValidationPipe({ + exceptionFactory(validationErrors: ValidationError[] = []) { + return new ValidationException(validationErrors); + }, + }), + ); + await app.init(); + app.getHttpServer().listen(0); +}); + +test.after(async () => { + try { + await Cacher.clearAllCache(); + await app.close(); + } catch (e) { + console.error('After tests error ' + e); + } +}); + +// ==================== Dashboard CRUD Tests ==================== + +currentTest = 'GET /dashboards/:connectionId'; + +test.serial(`${currentTest} should return empty array when no dashboards created`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const getDashboards = await request(app.getHttpServer()) + .get(`/dashboards/${connectionId}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const getDashboardsRO = JSON.parse(getDashboards.text); + t.is(getDashboards.status, 200); + t.is(Array.isArray(getDashboardsRO), true); + t.is(getDashboardsRO.length, 0); +}); + +currentTest = 'POST /dashboards/:connectionId'; + +test.serial(`${currentTest} should create a new dashboard`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const newDashboard = { + name: 'Test Dashboard', + description: 'Test dashboard description', + }; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send(newDashboard) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createDashboardRO = JSON.parse(createDashboard.text); + console.log('🚀 ~ createDashboardRO:', createDashboardRO); + t.is(createDashboard.status, 201); + t.is(createDashboardRO.name, newDashboard.name); + t.is(createDashboardRO.description, newDashboard.description); + t.is(createDashboardRO.connection_id, connectionId); + t.truthy(createDashboardRO.id); + t.truthy(createDashboardRO.created_at); + t.truthy(createDashboardRO.updated_at); +}); + +test.serial(`${currentTest} should fail to create dashboard without name`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({}) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(createDashboard.status, 400); +}); + +currentTest = 'GET /dashboard/:dashboardId'; + +test.serial(`${currentTest} should return dashboard with widgets`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const newDashboard = { + name: 'Test Dashboard', + description: 'Test dashboard description', + }; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send(newDashboard) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const getDashboard = await request(app.getHttpServer()) + .get(`/dashboard/${dashboardId}/${connectionId}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const getDashboardRO = JSON.parse(getDashboard.text); + t.is(getDashboard.status, 200); + t.is(getDashboardRO.id, dashboardId); + t.is(getDashboardRO.name, newDashboard.name); + t.is(getDashboardRO.description, newDashboard.description); + t.is(Array.isArray(getDashboardRO.widgets), true); +}); + +test.serial(`${currentTest} should return 404 for non-existent dashboard`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + const fakeDashboardId = faker.string.uuid(); + + const getDashboard = await request(app.getHttpServer()) + .get(`/dashboard/${fakeDashboardId}/${connectionId}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + t.is(getDashboard.status, 404); +}); + +currentTest = 'PUT /dashboard/:dashboardId'; + +test.serial(`${currentTest} should update dashboard`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Original Name' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const updateData = { + name: 'Updated Name', + description: 'Updated description', + }; + + const updateDashboard = await request(app.getHttpServer()) + .put(`/dashboard/${dashboardId}/${connectionId}`) + .send(updateData) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const updateDashboardRO = JSON.parse(updateDashboard.text); + t.is(updateDashboard.status, 200); + t.is(updateDashboardRO.name, updateData.name); + t.is(updateDashboardRO.description, updateData.description); +}); + +currentTest = 'DELETE /dashboard/:dashboardId'; + +test.serial(`${currentTest} should delete dashboard`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard to Delete' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const deleteDashboard = await request(app.getHttpServer()) + .delete(`/dashboard/${dashboardId}/${connectionId}`) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteDashboard.status, 200); + + // Verify dashboard is deleted + const getDashboard = await request(app.getHttpServer()) + .get(`/dashboard/${dashboardId}/${connectionId}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + t.is(getDashboard.status, 404); +}); + +// ==================== Widget CRUD Tests ==================== + +currentTest = 'POST /dashboards/:connectionId/:dashboardId/widget'; + +test.serial(`${currentTest} should create a new widget in dashboard`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard with Widgets' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const newWidget = { + widget_type: 'table', + name: 'Test Widget', + description: 'Test widget description', + position_x: 0, + position_y: 0, + width: 4, + height: 3, + }; + + const createWidget = await request(app.getHttpServer()) + .post(`/dashboard/${dashboardId}/widget/${connectionId}`) + .send(newWidget) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createWidgetRO = JSON.parse(createWidget.text); + t.is(createWidget.status, 201); + t.is(createWidgetRO.widget_type, newWidget.widget_type); + t.is(createWidgetRO.name, newWidget.name); + t.is(createWidgetRO.description, newWidget.description); + t.is(createWidgetRO.position_x, newWidget.position_x); + t.is(createWidgetRO.position_y, newWidget.position_y); + t.is(createWidgetRO.width, newWidget.width); + t.is(createWidgetRO.height, newWidget.height); + t.is(createWidgetRO.dashboard_id, dashboardId); +}); + +test.serial(`${currentTest} should create widget with all types`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard with All Widget Types' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const widgetTypes = ['table', 'chart', 'counter', 'text']; + + for (const widgetType of widgetTypes) { + const createWidget = await request(app.getHttpServer()) + .post(`/dashboard/${dashboardId}/widget/${connectionId}`) + .send({ widget_type: widgetType, name: `${widgetType} Widget` }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(createWidget.status, 201); + const createWidgetRO = JSON.parse(createWidget.text); + t.is(createWidgetRO.widget_type, widgetType); + } +}); + +currentTest = 'PUT /dashboard/:dashboardId/widget/:widgetId'; + +test.serial(`${currentTest} should update widget`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard for Widget Update' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const createWidget = await request(app.getHttpServer()) + .post(`/dashboard/${dashboardId}/widget/${connectionId}`) + .send({ widget_type: 'table', name: 'Original Widget Name' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const widgetId = JSON.parse(createWidget.text).id; + + const updateData = { + name: 'Updated Widget Name', + position_x: 2, + position_y: 1, + width: 6, + height: 4, + }; + + const updateWidget = await request(app.getHttpServer()) + .put(`/dashboard/${dashboardId}/widget/${widgetId}/${connectionId}`) + .send(updateData) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const updateWidgetRO = JSON.parse(updateWidget.text); + t.is(updateWidget.status, 200); + t.is(updateWidgetRO.name, updateData.name); + t.is(updateWidgetRO.position_x, updateData.position_x); + t.is(updateWidgetRO.position_y, updateData.position_y); + t.is(updateWidgetRO.width, updateData.width); + t.is(updateWidgetRO.height, updateData.height); +}); + +currentTest = 'DELETE /dashboard/:dashboardId/widget/:widgetId'; + +test.serial(`${currentTest} should delete widget from dashboard`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard for Widget Delete' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const createWidget = await request(app.getHttpServer()) + .post(`/dashboard/${dashboardId}/widget/${connectionId}`) + .send({ widget_type: 'counter', name: 'Widget to Delete' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const widgetId = JSON.parse(createWidget.text).id; + + const deleteWidget = await request(app.getHttpServer()) + .delete(`/dashboard/${dashboardId}/widget/${widgetId}/${connectionId}`) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(deleteWidget.status, 200); + + // Verify widget is deleted by getting dashboard + const getDashboard = await request(app.getHttpServer()) + .get(`/dashboard/${dashboardId}/${connectionId}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const getDashboardRO = JSON.parse(getDashboard.text); + t.is(getDashboard.status, 200); + t.is(getDashboardRO.widgets.length, 0); +}); + +// ==================== Widget with Saved Query Tests ==================== + +currentTest = 'Widget with Saved Query'; + +test.serial(`${currentTest} should create widget linked to a saved query`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + // Create a saved query first + const createQuery = await request(app.getHttpServer()) + .post(`/connection/${connectionId}/saved-query`) + .send({ + name: 'Test Query for Widget', + query_text: 'SELECT * FROM connection LIMIT 10', + }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const queryId = JSON.parse(createQuery.text).id; + + // Create dashboard + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard with Query Widget' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + // Create widget linked to saved query + const createWidget = await request(app.getHttpServer()) + .post(`/dashboard/${dashboardId}/widget/${connectionId}`) + .send({ + widget_type: 'table', + name: 'Query Result Widget', + query_id: queryId, + }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const createWidgetRO = JSON.parse(createWidget.text); + t.is(createWidget.status, 201); + t.is(createWidgetRO.query_id, queryId); +}); + +test.serial(`${currentTest} should fail to create widget with non-existent query`, async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const newConnection = getTestData(mockFactory).newEncryptedConnection; + const createdConnection = await request(app.getHttpServer()) + .post('/connection') + .send(newConnection) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const connectionId = JSON.parse(createdConnection.text).id; + + const createDashboard = await request(app.getHttpServer()) + .post(`/dashboards/${connectionId}`) + .send({ name: 'Dashboard with Invalid Query' }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + const dashboardId = JSON.parse(createDashboard.text).id; + + const fakeQueryId = faker.string.uuid(); + + const createWidget = await request(app.getHttpServer()) + .post(`/dashboard/${dashboardId}/widget/${connectionId}`) + .send({ + widget_type: 'table', + name: 'Widget with Fake Query', + query_id: fakeQueryId, + }) + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + + t.is(createWidget.status, 400); +}); From e20c5c70e9aa1cfe165ca672a5265753992b4f60 Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Wed, 14 Jan 2026 15:49:34 +0000 Subject: [PATCH 2/3] lint formatting --- .../non-saas-custom-field-e2e.test.ts | 3192 ++++++++--------- 1 file changed, 1596 insertions(+), 1596 deletions(-) diff --git a/backend/test/ava-tests/non-saas-tests/non-saas-custom-field-e2e.test.ts b/backend/test/ava-tests/non-saas-tests/non-saas-custom-field-e2e.test.ts index a991952d7..83c9ee91a 100644 --- a/backend/test/ava-tests/non-saas-tests/non-saas-custom-field-e2e.test.ts +++ b/backend/test/ava-tests/non-saas-tests/non-saas-custom-field-e2e.test.ts @@ -30,1718 +30,1718 @@ const mockFactory = new MockFactory(); const masterPwd = 'ahalaimahalai'; const _decryptValue = (data) => { - return Encryptor.decryptData(data); + return Encryptor.decryptData(data); }; const _decryptValueMaterPwd = (data) => { - return Encryptor.decryptDataMasterPwd(data, masterPwd); + return Encryptor.decryptDataMasterPwd(data, masterPwd); }; test.before(async () => { - setSaasEnvVariable(); - const moduleFixture = await Test.createTestingModule({ - imports: [ApplicationModule, DatabaseModule], - providers: [DatabaseService, TestUtils], - }).compile(); - app = moduleFixture.createNestApplication(); - _testUtils = moduleFixture.get(TestUtils); - - app.use(cookieParser()); - app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger))); - app.useGlobalPipes( - new ValidationPipe({ - exceptionFactory(validationErrors: ValidationError[] = []) { - return new ValidationException(validationErrors); - }, - }), - ); - await app.init(); - app.getHttpServer().listen(0); + setSaasEnvVariable(); + const moduleFixture = await Test.createTestingModule({ + imports: [ApplicationModule, DatabaseModule], + providers: [DatabaseService, TestUtils], + }).compile(); + app = moduleFixture.createNestApplication(); + _testUtils = moduleFixture.get(TestUtils); + + app.use(cookieParser()); + app.useGlobalFilters(new AllExceptionsFilter(app.get(WinstonLogger))); + app.useGlobalPipes( + new ValidationPipe({ + exceptionFactory(validationErrors: ValidationError[] = []) { + return new ValidationException(validationErrors); + }, + }), + ); + await app.init(); + app.getHttpServer().listen(0); }); test.after(async () => { - try { - await Cacher.clearAllCache(); - await app.close(); - } catch (e) { - console.error('After tests error ' + e); - } + try { + await Cacher.clearAllCache(); + await app.close(); + } catch (e) { + console.error('After tests error ' + e); + } }); let currentTest = 'GET /fields/:slug'; test.serial(`${currentTest} should return empty array, when custom fields not created`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - t.is(getCustomFields.status, 200); - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 0); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + t.is(getCustomFields.status, 200); + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 0); }); test.serial(`${currentTest} should return custom fields array, when custom fields are created`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const getTableRowsResponse = await request(app.getHttpServer()) - .get(`/table/rows/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getTableRowsResponse.status, 200); - const getTableRowsRO = JSON.parse(getTableRowsResponse.text); - t.is(getTableRowsRO.rows.length >= 10, true); - for (const row of getTableRowsRO.rows) { - t.is(Object.hasOwn(row, '#autoadmin:customFields'), true); - t.is(typeof row['#autoadmin:customFields'], 'object'); - t.is(row['#autoadmin:customFields'].length, 1); - t.is(row['#autoadmin:customFields'][0].type, newCustomField.type); - t.is(row['#autoadmin:customFields'][0].text, newCustomField.text); - const urlTemplate = replaceTextInCurlies(newCustomField.template_string, ['id', 'title'], [row.id, row.title]); - t.is(row['#autoadmin:customFields'][0].url_template, urlTemplate); - } + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const getTableRowsResponse = await request(app.getHttpServer()) + .get(`/table/rows/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getTableRowsResponse.status, 200); + const getTableRowsRO = JSON.parse(getTableRowsResponse.text); + t.is(getTableRowsRO.rows.length >= 10, true); + for (const row of getTableRowsRO.rows) { + t.is(Object.hasOwn(row, '#autoadmin:customFields'), true); + t.is(typeof row['#autoadmin:customFields'], 'object'); + t.is(row['#autoadmin:customFields'].length, 1); + t.is(row['#autoadmin:customFields'][0].type, newCustomField.type); + t.is(row['#autoadmin:customFields'][0].text, newCustomField.text); + const urlTemplate = replaceTextInCurlies(newCustomField.template_string, ['id', 'title'], [row.id, row.title]); + t.is(row['#autoadmin:customFields'][0].url_template, urlTemplate); + } }); test.serial(`${currentTest} should throw exception when connection id not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - - t.is(createConnectionResponse.status, 201); - - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - t.is(createCustomFieldResponse.status, 201); - - createConnectionRO.id = ''; - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 404); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + + t.is(createConnectionResponse.status, 201); + + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + t.is(createCustomFieldResponse.status, 201); + + createConnectionRO.id = ''; + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 404); }); test.serial(`${currentTest} should throw exception when connection id passed in request is incorrect`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - - createConnectionRO.id = faker.string.uuid(); - - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - - t.is(createCustomFieldResponse.status, 403); - t.is(createCustomField.message, Messages.DONT_HAVE_PERMISSIONS); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + + createConnectionRO.id = faker.string.uuid(); + + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + + t.is(createCustomFieldResponse.status, 403); + t.is(createCustomField.message, Messages.DONT_HAVE_PERMISSIONS); }); test.serial(`${currentTest} should throw exception when tableName passed in request is incorrect`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const tableName = faker.lorem.words(2); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=${tableName}`) - .send(newCustomField) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - - t.is(createCustomFieldResponse.status, 400); - t.is(createCustomField.message, Messages.TABLE_NOT_FOUND); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const tableName = faker.lorem.words(2); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=${tableName}`) + .send(newCustomField) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + + t.is(createCustomFieldResponse.status, 400); + t.is(createCustomField.message, Messages.TABLE_NOT_FOUND); }); test.serial(`${currentTest} should throw exception when tableName not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=`) - .send(newCustomField) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - - t.is(createCustomFieldResponse.status, 400); - t.is(createCustomField.message, Messages.TABLE_NAME_MISSING); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=`) + .send(newCustomField) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + + t.is(createCustomFieldResponse.status, 400); + t.is(createCustomField.message, Messages.TABLE_NAME_MISSING); }); currentTest = 'POST /fields/:slug'; test.serial(`${currentTest} should return table settings with created custom field`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResponse.text); - - t.is(createConnectionResponse.status, 201); - - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResponse.text); + + t.is(createConnectionResponse.status, 201); + + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); }); test.serial( - `${currentTest} should throw exception when custom field without text field passed in request`, - async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - delete newCustomField.text; - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const _createCustomField = JSON.parse(createCustomFieldResponse.text); - - t.is(createCustomFieldResponse.status, 400); - // t.is(createCustomField.message, ErrorsMessages.VALIDATION_FAILED); - }, + `${currentTest} should throw exception when custom field without text field passed in request`, + async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + delete newCustomField.text; + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const _createCustomField = JSON.parse(createCustomFieldResponse.text); + + t.is(createCustomFieldResponse.status, 400); + // t.is(createCustomField.message, ErrorsMessages.VALIDATION_FAILED); + }, ); test.serial( - `${currentTest} should throw exception when custom field without type field passed in request`, - async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - delete newCustomField.type; - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const _createCustomField = JSON.parse(createCustomFieldResponse.text); - - t.is(createCustomFieldResponse.status, 400); - // t.is(createCustomField.message, ErrorsMessages.VALIDATION_FAILED); - }, + `${currentTest} should throw exception when custom field without type field passed in request`, + async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + delete newCustomField.type; + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const _createCustomField = JSON.parse(createCustomFieldResponse.text); + + t.is(createCustomFieldResponse.status, 400); + // t.is(createCustomField.message, ErrorsMessages.VALIDATION_FAILED); + }, ); test.serial( - `${currentTest} should throw exception when custom field without template_string field passed in request`, - async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResponse.text); - - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - delete newCustomField.template_string; - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const _createCustomField = JSON.parse(createCustomFieldResponse.text); - - t.is(createCustomFieldResponse.status, 400); - // t.is(createCustomField.message, ErrorsMessages.VALIDATION_FAILED); - }, + `${currentTest} should throw exception when custom field without template_string field passed in request`, + async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResponse.text); + + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + delete newCustomField.template_string; + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const _createCustomField = JSON.parse(createCustomFieldResponse.text); + + t.is(createCustomFieldResponse.status, 400); + // t.is(createCustomField.message, ErrorsMessages.VALIDATION_FAILED); + }, ); test.serial( - `${currentTest} should throw exception when custom field with incorrect type passed in request`, - async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - newCustomField.type = 'test'; - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - - t.is(createCustomFieldResponse.status, 400); - t.is(createCustomField.message, Messages.CUSTOM_FIELD_TYPE_INCORRECT); - }, + `${currentTest} should throw exception when custom field with incorrect type passed in request`, + async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + newCustomField.type = 'test'; + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + + t.is(createCustomFieldResponse.status, 400); + t.is(createCustomField.message, Messages.CUSTOM_FIELD_TYPE_INCORRECT); + }, ); test.serial( - `${currentTest} should throw complex exception when custom field with incorrect type and without text property passed in request`, - async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - newCustomField.type = 'test'; - delete newCustomField.text; - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const _createCustomField = JSON.parse(createCustomFieldResponse.text); - - t.is(createCustomFieldResponse.status, 400); - // t.is(createCustomField.message, ErrorsMessages.VALIDATION_FAILED); - }, + `${currentTest} should throw complex exception when custom field with incorrect type and without text property passed in request`, + async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + newCustomField.type = 'test'; + delete newCustomField.text; + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const _createCustomField = JSON.parse(createCustomFieldResponse.text); + + t.is(createCustomFieldResponse.status, 400); + // t.is(createCustomField.message, ErrorsMessages.VALIDATION_FAILED); + }, ); test.serial(`${currentTest} should throw exception when connection id not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - createConnectionRO.id = ''; - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(createCustomFieldResponse.status, 404); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + createConnectionRO.id = ''; + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(createCustomFieldResponse.status, 404); }); test.serial(`${currentTest} should throw exception when connection id passed in request is incorrect`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const createConnectionRO = JSON.parse(createConnectionResponse.text); - - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - createConnectionRO.id = faker.string.uuid(); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - - t.is(createCustomFieldResponse.status, 403); - t.is(createCustomField.message, Messages.DONT_HAVE_PERMISSIONS); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const createConnectionRO = JSON.parse(createConnectionResponse.text); + + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + createConnectionRO.id = faker.string.uuid(); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + + t.is(createCustomFieldResponse.status, 403); + t.is(createCustomField.message, Messages.DONT_HAVE_PERMISSIONS); }); test.serial(`${currentTest} should throw exception when tableName passed in request is incorrect`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const tableName = faker.lorem.words(2); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=${tableName}`) - .send(newCustomField) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - - t.is(createCustomFieldResponse.status, 400); - t.is(createCustomField.message, Messages.TABLE_NOT_FOUND); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const tableName = faker.lorem.words(2); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=${tableName}`) + .send(newCustomField) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + + t.is(createCustomFieldResponse.status, 400); + t.is(createCustomField.message, Messages.TABLE_NOT_FOUND); }); test.serial(`${currentTest} should throw exception when tableName not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=`) - .send(newCustomField) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - - t.is(createCustomFieldResponse.status, 400); - t.is(createCustomField.message, Messages.TABLE_NAME_MISSING); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=`) + .send(newCustomField) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + + t.is(createCustomFieldResponse.status, 400); + t.is(createCustomField.message, Messages.TABLE_NAME_MISSING); }); currentTest = 'PUT /fields/:slug'; test.serial(`${currentTest} should return updated custom field`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const updateDTO = { - id: getCustomFieldsRO[0].id, - type: getCustomFieldsRO[0].type, - text: 'updated', - template_string: 'https//?connectionId={{id}}&connectionType={{type}}', - }; - const updateCustomFieldResponse = await request(app.getHttpServer()) - .put(`/field/${createConnectionRO.id}?tableName=connection`) - .send(updateDTO) - .set('Cookie', token) - .set('Content-Type', 'application/json') - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(updateCustomFieldResponse.status, 200); - const updateCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); - - t.is(updateCustomFieldRO.type, updateDTO.type); - t.is(updateCustomFieldRO.template_string, updateDTO.template_string); - t.is(updateCustomFieldRO.text, updateDTO.text); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const updateDTO = { + id: getCustomFieldsRO[0].id, + type: getCustomFieldsRO[0].type, + text: 'updated', + template_string: 'https//?connectionId={{id}}&connectionType={{type}}', + }; + const updateCustomFieldResponse = await request(app.getHttpServer()) + .put(`/field/${createConnectionRO.id}?tableName=connection`) + .send(updateDTO) + .set('Cookie', token) + .set('Content-Type', 'application/json') + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(updateCustomFieldResponse.status, 200); + const updateCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); + + t.is(updateCustomFieldRO.type, updateDTO.type); + t.is(updateCustomFieldRO.template_string, updateDTO.template_string); + t.is(updateCustomFieldRO.text, updateDTO.text); }); test.serial(`${currentTest} should throw exception, when connection id not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const updateDTO = { - id: getCustomFieldsRO[0].id, - type: getCustomFieldsRO[0].type, - text: 'updated', - template_string: 'https//?connectionId={{id}}&connectionType={{type}}', - }; - createConnectionRO.id = ''; - const updateCustomFieldResponse = await request(app.getHttpServer()) - .put(`/field/${createConnectionRO.id}?tableName=connection`) - .send(updateDTO) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(updateCustomFieldResponse.status, 404); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const updateDTO = { + id: getCustomFieldsRO[0].id, + type: getCustomFieldsRO[0].type, + text: 'updated', + template_string: 'https//?connectionId={{id}}&connectionType={{type}}', + }; + createConnectionRO.id = ''; + const updateCustomFieldResponse = await request(app.getHttpServer()) + .put(`/field/${createConnectionRO.id}?tableName=connection`) + .send(updateDTO) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(updateCustomFieldResponse.status, 404); }); test.serial(`${currentTest} should throw exception, when connection id passed in request is incorrect`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const updateDTO = { - id: getCustomFieldsRO[0].id, - type: getCustomFieldsRO[0].type, - text: 'updated', - template_string: 'https//?connectionId={{id}}&connectionType={{type}}', - }; - createConnectionRO.id = faker.string.uuid(); - const updateCustomFieldResponse = await request(app.getHttpServer()) - .put(`/field/${createConnectionRO.id}?tableName=connection`) - .send(updateDTO) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); - t.is(updateCustomFieldResponse.status, 403); - t.is(updatedCustomFieldRO.message, Messages.DONT_HAVE_PERMISSIONS); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const updateDTO = { + id: getCustomFieldsRO[0].id, + type: getCustomFieldsRO[0].type, + text: 'updated', + template_string: 'https//?connectionId={{id}}&connectionType={{type}}', + }; + createConnectionRO.id = faker.string.uuid(); + const updateCustomFieldResponse = await request(app.getHttpServer()) + .put(`/field/${createConnectionRO.id}?tableName=connection`) + .send(updateDTO) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); + t.is(updateCustomFieldResponse.status, 403); + t.is(updatedCustomFieldRO.message, Messages.DONT_HAVE_PERMISSIONS); }); test.serial(`${currentTest} should throw exception, when tableName passed in request is incorrect`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const updateDTO = { - id: getCustomFieldsRO[0].id, - type: getCustomFieldsRO[0].type, - text: 'updated', - template_string: 'https//?connectionId={{id}}&connectionType={{type}}', - }; - const tableName = faker.lorem.words(); - const updateCustomFieldResponse = await request(app.getHttpServer()) - .put(`/field/${createConnectionRO.id}?tableName=${tableName}`) - .send(updateDTO) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); - t.is(updateCustomFieldResponse.status, 400); - t.is(updatedCustomFieldRO.message, Messages.TABLE_NOT_FOUND); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const updateDTO = { + id: getCustomFieldsRO[0].id, + type: getCustomFieldsRO[0].type, + text: 'updated', + template_string: 'https//?connectionId={{id}}&connectionType={{type}}', + }; + const tableName = faker.lorem.words(); + const updateCustomFieldResponse = await request(app.getHttpServer()) + .put(`/field/${createConnectionRO.id}?tableName=${tableName}`) + .send(updateDTO) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); + t.is(updateCustomFieldResponse.status, 400); + t.is(updatedCustomFieldRO.message, Messages.TABLE_NOT_FOUND); }); test.serial(`${currentTest} should throw exception, when tableName not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const updateDTO = { - id: getCustomFieldsRO[0].id, - type: getCustomFieldsRO[0].type, - text: 'updated', - template_string: 'https//?connectionId={{id}}&connectionType={{type}}', - }; - const tableName = ''; - const updateCustomFieldResponse = await request(app.getHttpServer()) - .put(`/field/${createConnectionRO.id}?tableName=${tableName}`) - .send(updateDTO) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); - t.is(updateCustomFieldResponse.status, 400); - t.is(updatedCustomFieldRO.message, Messages.TABLE_NAME_MISSING); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const updateDTO = { + id: getCustomFieldsRO[0].id, + type: getCustomFieldsRO[0].type, + text: 'updated', + template_string: 'https//?connectionId={{id}}&connectionType={{type}}', + }; + const tableName = ''; + const updateCustomFieldResponse = await request(app.getHttpServer()) + .put(`/field/${createConnectionRO.id}?tableName=${tableName}`) + .send(updateDTO) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); + t.is(updateCustomFieldResponse.status, 400); + t.is(updatedCustomFieldRO.message, Messages.TABLE_NAME_MISSING); }); test.serial(`${currentTest} should throw exception, when field id not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const updateDTO = { - id: undefined, - type: getCustomFieldsRO[0].type, - text: 'updated', - template_string: 'https//?connectionId={{id}}&connectionType={{type}}', - }; - const updateCustomFieldResponse = await request(app.getHttpServer()) - .put(`/field/${createConnectionRO.id}?tableName=connection`) - .send(updateDTO) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const _updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); - t.is(updateCustomFieldResponse.status, 400); - // t.is(updatedCustomFieldRO.message, ErrorsMessages.VALIDATION_FAILED); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const updateDTO = { + id: undefined, + type: getCustomFieldsRO[0].type, + text: 'updated', + template_string: 'https//?connectionId={{id}}&connectionType={{type}}', + }; + const updateCustomFieldResponse = await request(app.getHttpServer()) + .put(`/field/${createConnectionRO.id}?tableName=connection`) + .send(updateDTO) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const _updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); + t.is(updateCustomFieldResponse.status, 400); + // t.is(updatedCustomFieldRO.message, ErrorsMessages.VALIDATION_FAILED); }); test.serial(`${currentTest} should throw exception, when field type not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const updateDTO = { - id: getCustomFieldsRO[0].id, - type: undefined, - text: 'updated', - template_string: 'https//?connectionId={{id}}&connectionType={{type}}', - }; - const updateCustomFieldResponse = await request(app.getHttpServer()) - .put(`/field/${createConnectionRO.id}?tableName=connection`) - .send(updateDTO) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const _updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); - t.is(updateCustomFieldResponse.status, 400); - // t.is(updatedCustomFieldRO.message, ErrorsMessages.VALIDATION_FAILED); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const updateDTO = { + id: getCustomFieldsRO[0].id, + type: undefined, + text: 'updated', + template_string: 'https//?connectionId={{id}}&connectionType={{type}}', + }; + const updateCustomFieldResponse = await request(app.getHttpServer()) + .put(`/field/${createConnectionRO.id}?tableName=connection`) + .send(updateDTO) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const _updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); + t.is(updateCustomFieldResponse.status, 400); + // t.is(updatedCustomFieldRO.message, ErrorsMessages.VALIDATION_FAILED); }); test.serial(`${currentTest} should throw exception, when field type passed in request is incorrect`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const updateDTO = { - id: getCustomFieldsRO[0].id, - type: faker.lorem.words(), - text: 'updated', - template_string: 'https//?connectionId={{id}}&connectionType={{type}}', - }; - const updateCustomFieldResponse = await request(app.getHttpServer()) - .put(`/field/${createConnectionRO.id}?tableName=connection`) - .send(updateDTO) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); - t.is(updateCustomFieldResponse.status, 400); - t.is(updatedCustomFieldRO.message, Messages.CUSTOM_FIELD_TYPE_INCORRECT); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const updateDTO = { + id: getCustomFieldsRO[0].id, + type: faker.lorem.words(), + text: 'updated', + template_string: 'https//?connectionId={{id}}&connectionType={{type}}', + }; + const updateCustomFieldResponse = await request(app.getHttpServer()) + .put(`/field/${createConnectionRO.id}?tableName=connection`) + .send(updateDTO) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); + t.is(updateCustomFieldResponse.status, 400); + t.is(updatedCustomFieldRO.message, Messages.CUSTOM_FIELD_TYPE_INCORRECT); }); test.serial(`${currentTest} should throw exception, when field text is not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const updateDTO = { - id: getCustomFieldsRO[0].id, - type: getCustomFieldsRO[0].type, - text: undefined, - template_string: 'https//?connectionId={{id}}&connectionType={{type}}', - }; - const updateCustomFieldResponse = await request(app.getHttpServer()) - .put(`/field/${createConnectionRO.id}?tableName=connection`) - .send(updateDTO) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const _updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); - t.is(updateCustomFieldResponse.status, 400); - // t.is(updatedCustomFieldRO.message, ErrorsMessages.VALIDATION_FAILED); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const updateDTO = { + id: getCustomFieldsRO[0].id, + type: getCustomFieldsRO[0].type, + text: undefined, + template_string: 'https//?connectionId={{id}}&connectionType={{type}}', + }; + const updateCustomFieldResponse = await request(app.getHttpServer()) + .put(`/field/${createConnectionRO.id}?tableName=connection`) + .send(updateDTO) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const _updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); + t.is(updateCustomFieldResponse.status, 400); + // t.is(updatedCustomFieldRO.message, ErrorsMessages.VALIDATION_FAILED); }); test.serial(`${currentTest} should throw exception, when field template_string is not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const updateDTO = { - id: getCustomFieldsRO[0].id, - type: getCustomFieldsRO[0].type, - text: 'updated', - template_string: undefined, - }; - const updateCustomFieldResponse = await request(app.getHttpServer()) - .put(`/field/${createConnectionRO.id}?tableName=connection`) - .send(updateDTO) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const _updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); - t.is(updateCustomFieldResponse.status, 400); - // t.is(updatedCustomFieldRO.message, ErrorsMessages.VALIDATION_FAILED); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const updateDTO = { + id: getCustomFieldsRO[0].id, + type: getCustomFieldsRO[0].type, + text: 'updated', + template_string: undefined, + }; + const updateCustomFieldResponse = await request(app.getHttpServer()) + .put(`/field/${createConnectionRO.id}?tableName=connection`) + .send(updateDTO) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const _updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); + t.is(updateCustomFieldResponse.status, 400); + // t.is(updatedCustomFieldRO.message, ErrorsMessages.VALIDATION_FAILED); }); test.serial(`${currentTest} should throw exception, when fields passed in template string are incorrect`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const randomField1 = faker.lorem.words(1); - const randomField2 = faker.lorem.words(1); - const updateDTO = { - id: getCustomFieldsRO[0].id, - type: getCustomFieldsRO[0].type, - text: 'updated', - template_string: `https//?connectionId={{${randomField1}}}&connectionType={{${randomField2}}}`, - }; - const updateCustomFieldResponse = await request(app.getHttpServer()) - .put(`/field/${createConnectionRO.id}?tableName=connection`) - .send(updateDTO) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); - t.is(updateCustomFieldResponse.status, 400); - t.is( - updatedCustomFieldRO.message, - `${Messages.EXCLUDED_OR_NOT_EXISTS(randomField1)}, ${Messages.EXCLUDED_OR_NOT_EXISTS(randomField2)}`, - ); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const randomField1 = faker.lorem.words(1); + const randomField2 = faker.lorem.words(1); + const updateDTO = { + id: getCustomFieldsRO[0].id, + type: getCustomFieldsRO[0].type, + text: 'updated', + template_string: `https//?connectionId={{${randomField1}}}&connectionType={{${randomField2}}}`, + }; + const updateCustomFieldResponse = await request(app.getHttpServer()) + .put(`/field/${createConnectionRO.id}?tableName=connection`) + .send(updateDTO) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); + t.is(updateCustomFieldResponse.status, 400); + t.is( + updatedCustomFieldRO.message, + `${Messages.EXCLUDED_OR_NOT_EXISTS(randomField1)}, ${Messages.EXCLUDED_OR_NOT_EXISTS(randomField2)}`, + ); }); test.serial( - `${currentTest} should throw complex exception, when some required fields not passed in request`, - async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const updateDTO = { - id: getCustomFieldsRO[0].id, - type: undefined, - text: 'updated', - template_string: undefined, - }; - const updateCustomFieldResponse = await request(app.getHttpServer()) - .put(`/field/${createConnectionRO.id}?tableName=connection`) - .send(updateDTO) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - - const _updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); - t.is(updateCustomFieldResponse.status, 400); - // t.is(updatedCustomFieldRO.message, ErrorsMessages.VALIDATION_FAILED); - }, + `${currentTest} should throw complex exception, when some required fields not passed in request`, + async (t) => { + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const updateDTO = { + id: getCustomFieldsRO[0].id, + type: undefined, + text: 'updated', + template_string: undefined, + }; + const updateCustomFieldResponse = await request(app.getHttpServer()) + .put(`/field/${createConnectionRO.id}?tableName=connection`) + .send(updateDTO) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + + const _updatedCustomFieldRO = JSON.parse(updateCustomFieldResponse.text); + t.is(updateCustomFieldResponse.status, 400); + // t.is(updatedCustomFieldRO.message, ErrorsMessages.VALIDATION_FAILED); + }, ); currentTest = 'DELETE /fields/:slug'; test.serial(`${currentTest} should return table settings without deleted custom field`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - let getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - let getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const deleteCustomField = await request(app.getHttpServer()) - .delete(`/field/${createConnectionRO.id}?tableName=connection&id=${getCustomFieldsRO[0].id}`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(deleteCustomField.status, 200); - const deleteCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(deleteCustomFieldsRO.length, 1); - - t.is(deleteCustomFieldsRO[0].type, newCustomField.type); - t.is(deleteCustomFieldsRO[0].text, newCustomField.text); - t.is(deleteCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 0); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + let getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + let getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const deleteCustomField = await request(app.getHttpServer()) + .delete(`/field/${createConnectionRO.id}?tableName=connection&id=${getCustomFieldsRO[0].id}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(deleteCustomField.status, 200); + const deleteCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(deleteCustomFieldsRO.length, 1); + + t.is(deleteCustomFieldsRO[0].type, newCustomField.type); + t.is(deleteCustomFieldsRO[0].text, newCustomField.text); + t.is(deleteCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 0); }); test.serial(`${currentTest} should throw exception, when connection id not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - createConnectionRO.id = ''; - const deleteCustomField = await request(app.getHttpServer()) - .delete(`/field/${createConnectionRO.id}?tableName=connection&id=${getCustomFieldsRO[0].id}`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(deleteCustomField.status, 404); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + createConnectionRO.id = ''; + const deleteCustomField = await request(app.getHttpServer()) + .delete(`/field/${createConnectionRO.id}?tableName=connection&id=${getCustomFieldsRO[0].id}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(deleteCustomField.status, 404); }); test.serial(`${currentTest} should throw exception, when connection id passed in request is incorrect`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - createConnectionRO.id = faker.string.uuid(); - const deleteCustomField = await request(app.getHttpServer()) - .delete(`/field/${createConnectionRO.id}?tableName=connection&id=${getCustomFieldsRO[0].id}`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(deleteCustomField.status, 403); - const deleteCustomFieldsRO = JSON.parse(deleteCustomField.text); - t.is(deleteCustomFieldsRO.message, Messages.DONT_HAVE_PERMISSIONS); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + createConnectionRO.id = faker.string.uuid(); + const deleteCustomField = await request(app.getHttpServer()) + .delete(`/field/${createConnectionRO.id}?tableName=connection&id=${getCustomFieldsRO[0].id}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(deleteCustomField.status, 403); + const deleteCustomFieldsRO = JSON.parse(deleteCustomField.text); + t.is(deleteCustomFieldsRO.message, Messages.DONT_HAVE_PERMISSIONS); }); test.serial(`${currentTest} should throw exception, when tableName not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const deleteCustomField = await request(app.getHttpServer()) - .delete(`/field/${createConnectionRO.id}?tableName=&id=${getCustomFieldsRO[0].id}`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(deleteCustomField.status, 400); - const deleteCustomFieldsRO = JSON.parse(deleteCustomField.text); - t.is(deleteCustomFieldsRO.message, Messages.TABLE_NAME_MISSING); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const deleteCustomField = await request(app.getHttpServer()) + .delete(`/field/${createConnectionRO.id}?tableName=&id=${getCustomFieldsRO[0].id}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(deleteCustomField.status, 400); + const deleteCustomFieldsRO = JSON.parse(deleteCustomField.text); + t.is(deleteCustomFieldsRO.message, Messages.TABLE_NAME_MISSING); }); test.serial(`${currentTest} should throw exception, when tableName passed in request is incorrect`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - const tableName = faker.lorem.words(); - const deleteCustomField = await request(app.getHttpServer()) - .delete(`/field/${createConnectionRO.id}?tableName=${tableName}&id=${getCustomFieldsRO[0].id}`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(deleteCustomField.status, 400); - const deleteCustomFieldsRO = JSON.parse(deleteCustomField.text); - t.is(deleteCustomFieldsRO.message, Messages.TABLE_SETTINGS_NOT_FOUND); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + const tableName = faker.lorem.words(); + const deleteCustomField = await request(app.getHttpServer()) + .delete(`/field/${createConnectionRO.id}?tableName=${tableName}&id=${getCustomFieldsRO[0].id}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(deleteCustomField.status, 400); + const deleteCustomFieldsRO = JSON.parse(deleteCustomField.text); + t.is(deleteCustomFieldsRO.message, Messages.TABLE_SETTINGS_NOT_FOUND); }); test.serial(`${currentTest} should throw exception, when field id is not passed in request`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - getCustomFieldsRO[0].id = ''; - const deleteCustomField = await request(app.getHttpServer()) - .delete(`/field/${createConnectionRO.id}?tableName=connection&id=${getCustomFieldsRO[0].id}`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(deleteCustomField.status, 400); - const deleteCustomFieldsRO = JSON.parse(deleteCustomField.text); - t.is(deleteCustomFieldsRO.message, Messages.UUID_INVALID); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + getCustomFieldsRO[0].id = ''; + const deleteCustomField = await request(app.getHttpServer()) + .delete(`/field/${createConnectionRO.id}?tableName=connection&id=${getCustomFieldsRO[0].id}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(deleteCustomField.status, 400); + const deleteCustomFieldsRO = JSON.parse(deleteCustomField.text); + t.is(deleteCustomFieldsRO.message, Messages.UUID_INVALID); }); test.serial(`${currentTest} should throw exception, when field id passed in request is incorrect`, async (t) => { - const { token } = await registerUserAndReturnUserInfo(app); - const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); - const createConnectionResponse = await request(app.getHttpServer()) - .post('/connection') - .send(newEncryptedConnection) - .set('masterpwd', 'ahalaimahalai') - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createConnectionRO = JSON.parse(createConnectionResponse.text); - t.is(createConnectionResponse.status, 201); - const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); - const createCustomFieldResponse = await request(app.getHttpServer()) - .post(`/field/${createConnectionRO.id}?tableName=connection`) - .send(newCustomField) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - const createCustomField = JSON.parse(createCustomFieldResponse.text); - t.is(createCustomFieldResponse.status, 201); - t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); - t.is(typeof createCustomField.custom_fields, 'object'); - - const getCustomFields = await request(app.getHttpServer()) - .get(`/fields/${createConnectionRO.id}?tableName=connection`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(getCustomFields.status, 200); - const getCustomFieldsRO = JSON.parse(getCustomFields.text); - - t.is(typeof getCustomFieldsRO, 'object'); - t.is(getCustomFieldsRO.length, 1); - - t.is(getCustomFieldsRO[0].type, newCustomField.type); - t.is(getCustomFieldsRO[0].text, newCustomField.text); - t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); - - getCustomFieldsRO[0].id = faker.string.uuid(); - const deleteCustomField = await request(app.getHttpServer()) - .delete(`/field/${createConnectionRO.id}?tableName=connection&id=${getCustomFieldsRO[0].id}`) - .set('Content-Type', 'application/json') - .set('Cookie', token) - .set('masterpwd', 'ahalaimahalai') - .set('Accept', 'application/json'); - t.is(deleteCustomField.status, 400); - const deleteCustomFieldsRO = JSON.parse(deleteCustomField.text); - t.is(deleteCustomFieldsRO.message, Messages.CUSTOM_FIELD_NOT_FOUND); + const { token } = await registerUserAndReturnUserInfo(app); + const { newEncryptedConnection, newEncryptedConnection2 } = getTestData(mockFactory); + const createConnectionResponse = await request(app.getHttpServer()) + .post('/connection') + .send(newEncryptedConnection) + .set('masterpwd', 'ahalaimahalai') + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createConnectionRO = JSON.parse(createConnectionResponse.text); + t.is(createConnectionResponse.status, 201); + const newCustomField = mockFactory.generateCustomFieldForConnectionTable('id', 'title'); + const createCustomFieldResponse = await request(app.getHttpServer()) + .post(`/field/${createConnectionRO.id}?tableName=connection`) + .send(newCustomField) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + const createCustomField = JSON.parse(createCustomFieldResponse.text); + t.is(createCustomFieldResponse.status, 201); + t.is(Object.hasOwn(createCustomField, 'custom_fields'), true); + t.is(typeof createCustomField.custom_fields, 'object'); + + const getCustomFields = await request(app.getHttpServer()) + .get(`/fields/${createConnectionRO.id}?tableName=connection`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(getCustomFields.status, 200); + const getCustomFieldsRO = JSON.parse(getCustomFields.text); + + t.is(typeof getCustomFieldsRO, 'object'); + t.is(getCustomFieldsRO.length, 1); + + t.is(getCustomFieldsRO[0].type, newCustomField.type); + t.is(getCustomFieldsRO[0].text, newCustomField.text); + t.is(getCustomFieldsRO[0].template_string, 'https//?connectionId={{id}}&connectionTitle={{title}}'); + + getCustomFieldsRO[0].id = faker.string.uuid(); + const deleteCustomField = await request(app.getHttpServer()) + .delete(`/field/${createConnectionRO.id}?tableName=connection&id=${getCustomFieldsRO[0].id}`) + .set('Content-Type', 'application/json') + .set('Cookie', token) + .set('masterpwd', 'ahalaimahalai') + .set('Accept', 'application/json'); + t.is(deleteCustomField.status, 400); + const deleteCustomFieldsRO = JSON.parse(deleteCustomField.text); + t.is(deleteCustomFieldsRO.message, Messages.CUSTOM_FIELD_NOT_FOUND); }); From c2520a0948f2a6816eff7a6c0cd7f259e8b1aa3f Mon Sep 17 00:00:00 2001 From: Artem Niehrieiev Date: Wed, 14 Jan 2026 16:43:52 +0000 Subject: [PATCH 3/3] Refactor dashboard widget structure --- backend/src/app.module.ts | 2 + .../global-database-context.interface.ts | 8 ++-- .../application/global-database-context.ts | 10 ++-- .../dashboard-widget.entity.ts | 2 +- .../dashboard-widget.module.ts | 46 +++++++++++++++++++ .../dashboard-widgets.controller.ts | 2 +- .../create-dashboard-widget.ds.ts | 0 .../delete-dashboard-widget.ds.ts | 0 .../update-dashboard-widget.ds.ts | 0 .../dto/create-dashboard-widget.dto.ts | 0 .../dto/found-dashboard-widget.dto.ts | 0 .../dto/update-dashboard-widget.dto.ts | 0 ...oard-widget-custom-repository-extension.ts | 0 .../dashboard-widget.repository.interface.ts | 9 ++++ .../create-dashboard-widget.use.case.ts | 2 +- .../dashboard-widget-use-cases.interface.ts | 17 +++++++ .../delete-dashboard-widget.use.case.ts | 2 +- .../update-dashboard-widget.use.case.ts | 2 +- .../build-found-dashboard-widget-dto.util.ts | 0 .../dashboard/dashboard.entity.ts | 2 +- .../dashboard/dashboards.module.ts | 21 +-------- .../dashboard/dto/found-dashboard.dto.ts | 2 +- .../dashboard.repository.interface.ts | 9 ---- .../dashboard-use-cases.interface.ts | 16 ------- .../utils/build-found-dashboard-dto.util.ts | 2 +- .../saved-db-query/saved-db-query.entity.ts | 2 +- 26 files changed, 91 insertions(+), 65 deletions(-) rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/dashboard-widget.entity.ts (97%) create mode 100644 backend/src/entities/visualizations/dashboard-widget/dashboard-widget.module.ts rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/dashboard-widgets.controller.ts (98%) rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/data-structures/create-dashboard-widget.ds.ts (100%) rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/data-structures/delete-dashboard-widget.ds.ts (100%) rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/data-structures/update-dashboard-widget.ds.ts (100%) rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/dto/create-dashboard-widget.dto.ts (100%) rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/dto/found-dashboard-widget.dto.ts (100%) rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/dto/update-dashboard-widget.dto.ts (100%) rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/repository/dashboard-widget-custom-repository-extension.ts (100%) create mode 100644 backend/src/entities/visualizations/dashboard-widget/repository/dashboard-widget.repository.interface.ts rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/use-cases/create-dashboard-widget.use.case.ts (97%) create mode 100644 backend/src/entities/visualizations/dashboard-widget/use-cases/dashboard-widget-use-cases.interface.ts rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/use-cases/delete-dashboard-widget.use.case.ts (96%) rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/use-cases/update-dashboard-widget.use.case.ts (97%) rename backend/src/entities/visualizations/{dashboard => dashboard-widget}/utils/build-found-dashboard-widget-dto.util.ts (100%) diff --git a/backend/src/app.module.ts b/backend/src/app.module.ts index df50f8135..b0b458187 100644 --- a/backend/src/app.module.ts +++ b/backend/src/app.module.ts @@ -44,6 +44,7 @@ import { GetHelloUseCase } from './use-cases-app/get-hello.use.case.js'; import { PersonalTableSettingsModule } from './entities/table-settings/personal-table-settings/personal-table-settings.module.js'; import { SavedDbQueryModule } from './entities/visualizations/saved-db-query/saved-db-query.module.js'; import { DashboardModule } from './entities/visualizations/dashboard/dashboards.module.js'; +import { DashboardWidgetModule } from './entities/visualizations/dashboard-widget/dashboard-widget.module.js'; @Module({ imports: [ @@ -92,6 +93,7 @@ import { DashboardModule } from './entities/visualizations/dashboard/dashboards. S3WidgetModule, SavedDbQueryModule, DashboardModule, + DashboardWidgetModule, ], controllers: [AppController], providers: [ diff --git a/backend/src/common/application/global-database-context.interface.ts b/backend/src/common/application/global-database-context.interface.ts index 212bd5df6..5296f1a2d 100644 --- a/backend/src/common/application/global-database-context.interface.ts +++ b/backend/src/common/application/global-database-context.interface.ts @@ -59,11 +59,9 @@ import { PersonalTableSettingsEntity } from '../../entities/table-settings/perso import { SavedDbQueryEntity } from '../../entities/visualizations/saved-db-query/saved-db-query.entity.js'; import { ISavedDbQueryRepository } from '../../entities/visualizations/saved-db-query/repository/saved-db-query.repository.interface.js'; import { DashboardEntity } from '../../entities/visualizations/dashboard/dashboard.entity.js'; -import { DashboardWidgetEntity } from '../../entities/visualizations/dashboard/dashboard-widget.entity.js'; -import { - IDashboardRepository, - IDashboardWidgetRepository, -} from '../../entities/visualizations/dashboard/repository/dashboard.repository.interface.js'; +import { DashboardWidgetEntity } from '../../entities/visualizations/dashboard-widget/dashboard-widget.entity.js'; +import { IDashboardRepository } from '../../entities/visualizations/dashboard/repository/dashboard.repository.interface.js'; +import { IDashboardWidgetRepository } from '../../entities/visualizations/dashboard-widget/repository/dashboard-widget.repository.interface.js'; export interface IGlobalDatabaseContext extends IDatabaseContext { userRepository: Repository & IUserRepository; diff --git a/backend/src/common/application/global-database-context.ts b/backend/src/common/application/global-database-context.ts index 854ad40d6..f8740b69b 100644 --- a/backend/src/common/application/global-database-context.ts +++ b/backend/src/common/application/global-database-context.ts @@ -106,13 +106,11 @@ import { SavedDbQueryEntity } from '../../entities/visualizations/saved-db-query import { ISavedDbQueryRepository } from '../../entities/visualizations/saved-db-query/repository/saved-db-query.repository.interface.js'; import { savedDbQueryCustomRepositoryExtension } from '../../entities/visualizations/saved-db-query/repository/saved-db-query-custom-repository-extension.js'; import { DashboardEntity } from '../../entities/visualizations/dashboard/dashboard.entity.js'; -import { DashboardWidgetEntity } from '../../entities/visualizations/dashboard/dashboard-widget.entity.js'; -import { - IDashboardRepository, - IDashboardWidgetRepository, -} from '../../entities/visualizations/dashboard/repository/dashboard.repository.interface.js'; +import { DashboardWidgetEntity } from '../../entities/visualizations/dashboard-widget/dashboard-widget.entity.js'; +import { IDashboardRepository } from '../../entities/visualizations/dashboard/repository/dashboard.repository.interface.js'; +import { IDashboardWidgetRepository } from '../../entities/visualizations/dashboard-widget/repository/dashboard-widget.repository.interface.js'; import { dashboardCustomRepositoryExtension } from '../../entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.js'; -import { dashboardWidgetCustomRepositoryExtension } from '../../entities/visualizations/dashboard/repository/dashboard-widget-custom-repository-extension.js'; +import { dashboardWidgetCustomRepositoryExtension } from '../../entities/visualizations/dashboard-widget/repository/dashboard-widget-custom-repository-extension.js'; @Injectable({ scope: Scope.REQUEST }) export class GlobalDatabaseContext implements IGlobalDatabaseContext { diff --git a/backend/src/entities/visualizations/dashboard/dashboard-widget.entity.ts b/backend/src/entities/visualizations/dashboard-widget/dashboard-widget.entity.ts similarity index 97% rename from backend/src/entities/visualizations/dashboard/dashboard-widget.entity.ts rename to backend/src/entities/visualizations/dashboard-widget/dashboard-widget.entity.ts index ae1a56350..e7c91f9f6 100644 --- a/backend/src/entities/visualizations/dashboard/dashboard-widget.entity.ts +++ b/backend/src/entities/visualizations/dashboard-widget/dashboard-widget.entity.ts @@ -11,7 +11,7 @@ import { Relation, } from 'typeorm'; import { DashboardWidgetTypeEnum } from '../../../enums/dashboard-widget-type.enum.js'; -import { DashboardEntity } from './dashboard.entity.js'; +import { DashboardEntity } from '../dashboard/dashboard.entity.js'; import { SavedDbQueryEntity } from '../saved-db-query/saved-db-query.entity.js'; @Entity('dashboard_widget') diff --git a/backend/src/entities/visualizations/dashboard-widget/dashboard-widget.module.ts b/backend/src/entities/visualizations/dashboard-widget/dashboard-widget.module.ts new file mode 100644 index 000000000..a8b04e898 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard-widget/dashboard-widget.module.ts @@ -0,0 +1,46 @@ +import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { GlobalDatabaseContext } from '../../../common/application/global-database-context.js'; +import { BaseType, UseCaseType } from '../../../common/data-injection.tokens.js'; +import { AuthMiddleware } from '../../../authorization/auth.middleware.js'; +import { UserEntity } from '../../user/user.entity.js'; +import { LogOutEntity } from '../../log-out/log-out.entity.js'; +import { DashboardWidgetController } from './dashboard-widgets.controller.js'; +import { CreateDashboardWidgetUseCase } from './use-cases/create-dashboard-widget.use.case.js'; +import { UpdateDashboardWidgetUseCase } from './use-cases/update-dashboard-widget.use.case.js'; +import { DeleteDashboardWidgetUseCase } from './use-cases/delete-dashboard-widget.use.case.js'; + +@Module({ + imports: [TypeOrmModule.forFeature([UserEntity, LogOutEntity])], + providers: [ + { + provide: BaseType.GLOBAL_DB_CONTEXT, + useClass: GlobalDatabaseContext, + }, + { + provide: UseCaseType.CREATE_DASHBOARD_WIDGET, + useClass: CreateDashboardWidgetUseCase, + }, + { + provide: UseCaseType.UPDATE_DASHBOARD_WIDGET, + useClass: UpdateDashboardWidgetUseCase, + }, + { + provide: UseCaseType.DELETE_DASHBOARD_WIDGET, + useClass: DeleteDashboardWidgetUseCase, + }, + ], + controllers: [DashboardWidgetController], + exports: [], +}) +export class DashboardWidgetModule { + public configure(consumer: MiddlewareConsumer): void { + consumer + .apply(AuthMiddleware) + .forRoutes( + { path: '/dashboard/:dashboardId/widget/:connectionId', method: RequestMethod.POST }, + { path: '/dashboard/:dashboardId/widget/:widgetId/:connectionId', method: RequestMethod.PUT }, + { path: '/dashboard/:dashboardId/widget/:widgetId/:connectionId', method: RequestMethod.DELETE }, + ); + } +} diff --git a/backend/src/entities/visualizations/dashboard/dashboard-widgets.controller.ts b/backend/src/entities/visualizations/dashboard-widget/dashboard-widgets.controller.ts similarity index 98% rename from backend/src/entities/visualizations/dashboard/dashboard-widgets.controller.ts rename to backend/src/entities/visualizations/dashboard-widget/dashboard-widgets.controller.ts index 72063958c..b80bb3455 100644 --- a/backend/src/entities/visualizations/dashboard/dashboard-widgets.controller.ts +++ b/backend/src/entities/visualizations/dashboard-widget/dashboard-widgets.controller.ts @@ -28,7 +28,7 @@ import { ICreateDashboardWidget, IDeleteDashboardWidget, IUpdateDashboardWidget, -} from './use-cases/dashboard-use-cases.interface.js'; +} from './use-cases/dashboard-widget-use-cases.interface.js'; @UseInterceptors(SentryInterceptor) @Controller() diff --git a/backend/src/entities/visualizations/dashboard/data-structures/create-dashboard-widget.ds.ts b/backend/src/entities/visualizations/dashboard-widget/data-structures/create-dashboard-widget.ds.ts similarity index 100% rename from backend/src/entities/visualizations/dashboard/data-structures/create-dashboard-widget.ds.ts rename to backend/src/entities/visualizations/dashboard-widget/data-structures/create-dashboard-widget.ds.ts diff --git a/backend/src/entities/visualizations/dashboard/data-structures/delete-dashboard-widget.ds.ts b/backend/src/entities/visualizations/dashboard-widget/data-structures/delete-dashboard-widget.ds.ts similarity index 100% rename from backend/src/entities/visualizations/dashboard/data-structures/delete-dashboard-widget.ds.ts rename to backend/src/entities/visualizations/dashboard-widget/data-structures/delete-dashboard-widget.ds.ts diff --git a/backend/src/entities/visualizations/dashboard/data-structures/update-dashboard-widget.ds.ts b/backend/src/entities/visualizations/dashboard-widget/data-structures/update-dashboard-widget.ds.ts similarity index 100% rename from backend/src/entities/visualizations/dashboard/data-structures/update-dashboard-widget.ds.ts rename to backend/src/entities/visualizations/dashboard-widget/data-structures/update-dashboard-widget.ds.ts diff --git a/backend/src/entities/visualizations/dashboard/dto/create-dashboard-widget.dto.ts b/backend/src/entities/visualizations/dashboard-widget/dto/create-dashboard-widget.dto.ts similarity index 100% rename from backend/src/entities/visualizations/dashboard/dto/create-dashboard-widget.dto.ts rename to backend/src/entities/visualizations/dashboard-widget/dto/create-dashboard-widget.dto.ts diff --git a/backend/src/entities/visualizations/dashboard/dto/found-dashboard-widget.dto.ts b/backend/src/entities/visualizations/dashboard-widget/dto/found-dashboard-widget.dto.ts similarity index 100% rename from backend/src/entities/visualizations/dashboard/dto/found-dashboard-widget.dto.ts rename to backend/src/entities/visualizations/dashboard-widget/dto/found-dashboard-widget.dto.ts diff --git a/backend/src/entities/visualizations/dashboard/dto/update-dashboard-widget.dto.ts b/backend/src/entities/visualizations/dashboard-widget/dto/update-dashboard-widget.dto.ts similarity index 100% rename from backend/src/entities/visualizations/dashboard/dto/update-dashboard-widget.dto.ts rename to backend/src/entities/visualizations/dashboard-widget/dto/update-dashboard-widget.dto.ts diff --git a/backend/src/entities/visualizations/dashboard/repository/dashboard-widget-custom-repository-extension.ts b/backend/src/entities/visualizations/dashboard-widget/repository/dashboard-widget-custom-repository-extension.ts similarity index 100% rename from backend/src/entities/visualizations/dashboard/repository/dashboard-widget-custom-repository-extension.ts rename to backend/src/entities/visualizations/dashboard-widget/repository/dashboard-widget-custom-repository-extension.ts diff --git a/backend/src/entities/visualizations/dashboard-widget/repository/dashboard-widget.repository.interface.ts b/backend/src/entities/visualizations/dashboard-widget/repository/dashboard-widget.repository.interface.ts new file mode 100644 index 000000000..a0a705877 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard-widget/repository/dashboard-widget.repository.interface.ts @@ -0,0 +1,9 @@ +import { DashboardWidgetEntity } from '../dashboard-widget.entity.js'; + +export interface IDashboardWidgetRepository { + findWidgetById(widgetId: string): Promise; + findWidgetByIdAndDashboardId(widgetId: string, dashboardId: string): Promise; + findAllWidgetsByDashboardId(dashboardId: string): Promise; + saveWidget(widget: DashboardWidgetEntity): Promise; + removeWidget(widget: DashboardWidgetEntity): Promise; +} diff --git a/backend/src/entities/visualizations/dashboard/use-cases/create-dashboard-widget.use.case.ts b/backend/src/entities/visualizations/dashboard-widget/use-cases/create-dashboard-widget.use.case.ts similarity index 97% rename from backend/src/entities/visualizations/dashboard/use-cases/create-dashboard-widget.use.case.ts rename to backend/src/entities/visualizations/dashboard-widget/use-cases/create-dashboard-widget.use.case.ts index 62e771eaf..33ff5f0be 100644 --- a/backend/src/entities/visualizations/dashboard/use-cases/create-dashboard-widget.use.case.ts +++ b/backend/src/entities/visualizations/dashboard-widget/use-cases/create-dashboard-widget.use.case.ts @@ -7,7 +7,7 @@ import { CreateDashboardWidgetDs } from '../data-structures/create-dashboard-wid import { DashboardWidgetEntity } from '../dashboard-widget.entity.js'; import { FoundDashboardWidgetDto } from '../dto/found-dashboard-widget.dto.js'; import { buildFoundDashboardWidgetDto } from '../utils/build-found-dashboard-widget-dto.util.js'; -import { ICreateDashboardWidget } from './dashboard-use-cases.interface.js'; +import { ICreateDashboardWidget } from './dashboard-widget-use-cases.interface.js'; @Injectable({ scope: Scope.REQUEST }) export class CreateDashboardWidgetUseCase diff --git a/backend/src/entities/visualizations/dashboard-widget/use-cases/dashboard-widget-use-cases.interface.ts b/backend/src/entities/visualizations/dashboard-widget/use-cases/dashboard-widget-use-cases.interface.ts new file mode 100644 index 000000000..5ae3885c0 --- /dev/null +++ b/backend/src/entities/visualizations/dashboard-widget/use-cases/dashboard-widget-use-cases.interface.ts @@ -0,0 +1,17 @@ +import { InTransactionEnum } from '../../../../enums/in-transaction.enum.js'; +import { CreateDashboardWidgetDs } from '../data-structures/create-dashboard-widget.ds.js'; +import { UpdateDashboardWidgetDs } from '../data-structures/update-dashboard-widget.ds.js'; +import { DeleteDashboardWidgetDs } from '../data-structures/delete-dashboard-widget.ds.js'; +import { FoundDashboardWidgetDto } from '../dto/found-dashboard-widget.dto.js'; + +export interface ICreateDashboardWidget { + execute(inputData: CreateDashboardWidgetDs, inTransaction: InTransactionEnum): Promise; +} + +export interface IUpdateDashboardWidget { + execute(inputData: UpdateDashboardWidgetDs, inTransaction: InTransactionEnum): Promise; +} + +export interface IDeleteDashboardWidget { + execute(inputData: DeleteDashboardWidgetDs, inTransaction: InTransactionEnum): Promise; +} diff --git a/backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard-widget.use.case.ts b/backend/src/entities/visualizations/dashboard-widget/use-cases/delete-dashboard-widget.use.case.ts similarity index 96% rename from backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard-widget.use.case.ts rename to backend/src/entities/visualizations/dashboard-widget/use-cases/delete-dashboard-widget.use.case.ts index 064042665..056c90f20 100644 --- a/backend/src/entities/visualizations/dashboard/use-cases/delete-dashboard-widget.use.case.ts +++ b/backend/src/entities/visualizations/dashboard-widget/use-cases/delete-dashboard-widget.use.case.ts @@ -6,7 +6,7 @@ import { Messages } from '../../../../exceptions/text/messages.js'; import { DeleteDashboardWidgetDs } from '../data-structures/delete-dashboard-widget.ds.js'; import { FoundDashboardWidgetDto } from '../dto/found-dashboard-widget.dto.js'; import { buildFoundDashboardWidgetDto } from '../utils/build-found-dashboard-widget-dto.util.js'; -import { IDeleteDashboardWidget } from './dashboard-use-cases.interface.js'; +import { IDeleteDashboardWidget } from './dashboard-widget-use-cases.interface.js'; @Injectable({ scope: Scope.REQUEST }) export class DeleteDashboardWidgetUseCase diff --git a/backend/src/entities/visualizations/dashboard/use-cases/update-dashboard-widget.use.case.ts b/backend/src/entities/visualizations/dashboard-widget/use-cases/update-dashboard-widget.use.case.ts similarity index 97% rename from backend/src/entities/visualizations/dashboard/use-cases/update-dashboard-widget.use.case.ts rename to backend/src/entities/visualizations/dashboard-widget/use-cases/update-dashboard-widget.use.case.ts index 18850c6cd..81a8bc869 100644 --- a/backend/src/entities/visualizations/dashboard/use-cases/update-dashboard-widget.use.case.ts +++ b/backend/src/entities/visualizations/dashboard-widget/use-cases/update-dashboard-widget.use.case.ts @@ -6,7 +6,7 @@ import { Messages } from '../../../../exceptions/text/messages.js'; import { UpdateDashboardWidgetDs } from '../data-structures/update-dashboard-widget.ds.js'; import { FoundDashboardWidgetDto } from '../dto/found-dashboard-widget.dto.js'; import { buildFoundDashboardWidgetDto } from '../utils/build-found-dashboard-widget-dto.util.js'; -import { IUpdateDashboardWidget } from './dashboard-use-cases.interface.js'; +import { IUpdateDashboardWidget } from './dashboard-widget-use-cases.interface.js'; @Injectable({ scope: Scope.REQUEST }) export class UpdateDashboardWidgetUseCase diff --git a/backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-widget-dto.util.ts b/backend/src/entities/visualizations/dashboard-widget/utils/build-found-dashboard-widget-dto.util.ts similarity index 100% rename from backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-widget-dto.util.ts rename to backend/src/entities/visualizations/dashboard-widget/utils/build-found-dashboard-widget-dto.util.ts diff --git a/backend/src/entities/visualizations/dashboard/dashboard.entity.ts b/backend/src/entities/visualizations/dashboard/dashboard.entity.ts index fb66a6c0c..ff93d80e5 100644 --- a/backend/src/entities/visualizations/dashboard/dashboard.entity.ts +++ b/backend/src/entities/visualizations/dashboard/dashboard.entity.ts @@ -1,6 +1,6 @@ import { Column, Entity, JoinColumn, ManyToOne, OneToMany, PrimaryGeneratedColumn, Relation } from 'typeorm'; import { ConnectionEntity } from '../../connection/connection.entity.js'; -import { DashboardWidgetEntity } from './dashboard-widget.entity.js'; +import { DashboardWidgetEntity } from '../dashboard-widget/dashboard-widget.entity.js'; @Entity('dashboard') export class DashboardEntity { diff --git a/backend/src/entities/visualizations/dashboard/dashboards.module.ts b/backend/src/entities/visualizations/dashboard/dashboards.module.ts index bf9726225..5e888cbc7 100644 --- a/backend/src/entities/visualizations/dashboard/dashboards.module.ts +++ b/backend/src/entities/visualizations/dashboard/dashboards.module.ts @@ -6,15 +6,11 @@ import { AuthMiddleware } from '../../../authorization/auth.middleware.js'; import { UserEntity } from '../../user/user.entity.js'; import { LogOutEntity } from '../../log-out/log-out.entity.js'; import { DashboardController } from './dashboards.controller.js'; -import { DashboardWidgetController } from './dashboard-widgets.controller.js'; import { CreateDashboardUseCase } from './use-cases/create-dashboard.use.case.js'; import { UpdateDashboardUseCase } from './use-cases/update-dashboard.use.case.js'; import { FindDashboardUseCase } from './use-cases/find-dashboard.use.case.js'; import { FindAllDashboardsUseCase } from './use-cases/find-all-dashboards.use.case.js'; import { DeleteDashboardUseCase } from './use-cases/delete-dashboard.use.case.js'; -import { CreateDashboardWidgetUseCase } from './use-cases/create-dashboard-widget.use.case.js'; -import { UpdateDashboardWidgetUseCase } from './use-cases/update-dashboard-widget.use.case.js'; -import { DeleteDashboardWidgetUseCase } from './use-cases/delete-dashboard-widget.use.case.js'; @Module({ imports: [TypeOrmModule.forFeature([UserEntity, LogOutEntity])], @@ -43,20 +39,8 @@ import { DeleteDashboardWidgetUseCase } from './use-cases/delete-dashboard-widge provide: UseCaseType.DELETE_DASHBOARD, useClass: DeleteDashboardUseCase, }, - { - provide: UseCaseType.CREATE_DASHBOARD_WIDGET, - useClass: CreateDashboardWidgetUseCase, - }, - { - provide: UseCaseType.UPDATE_DASHBOARD_WIDGET, - useClass: UpdateDashboardWidgetUseCase, - }, - { - provide: UseCaseType.DELETE_DASHBOARD_WIDGET, - useClass: DeleteDashboardWidgetUseCase, - }, ], - controllers: [DashboardController, DashboardWidgetController], + controllers: [DashboardController], exports: [], }) export class DashboardModule { @@ -69,9 +53,6 @@ export class DashboardModule { { path: '/dashboard/:dashboardId/:connectionId', method: RequestMethod.GET }, { path: '/dashboard/:dashboardId/:connectionId', method: RequestMethod.PUT }, { path: '/dashboard/:dashboardId/:connectionId', method: RequestMethod.DELETE }, - { path: '/dashboard/:dashboardId/widget/:connectionId', method: RequestMethod.POST }, - { path: '/dashboard/:dashboardId/widget/:widgetId/:connectionId', method: RequestMethod.PUT }, - { path: '/dashboard/:dashboardId/widget/:widgetId/:connectionId', method: RequestMethod.DELETE }, ); } } diff --git a/backend/src/entities/visualizations/dashboard/dto/found-dashboard.dto.ts b/backend/src/entities/visualizations/dashboard/dto/found-dashboard.dto.ts index e0ff24fa6..ebf13f5e5 100644 --- a/backend/src/entities/visualizations/dashboard/dto/found-dashboard.dto.ts +++ b/backend/src/entities/visualizations/dashboard/dto/found-dashboard.dto.ts @@ -1,5 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; -import { FoundDashboardWidgetDto } from './found-dashboard-widget.dto.js'; +import { FoundDashboardWidgetDto } from '../../dashboard-widget/dto/found-dashboard-widget.dto.js'; export class FoundDashboardDto { @ApiProperty({ description: 'Dashboard ID' }) diff --git a/backend/src/entities/visualizations/dashboard/repository/dashboard.repository.interface.ts b/backend/src/entities/visualizations/dashboard/repository/dashboard.repository.interface.ts index 83579764d..86ff6b714 100644 --- a/backend/src/entities/visualizations/dashboard/repository/dashboard.repository.interface.ts +++ b/backend/src/entities/visualizations/dashboard/repository/dashboard.repository.interface.ts @@ -1,5 +1,4 @@ import { DashboardEntity } from '../dashboard.entity.js'; -import { DashboardWidgetEntity } from '../dashboard-widget.entity.js'; export interface IDashboardRepository { findDashboardById(dashboardId: string): Promise; @@ -9,11 +8,3 @@ export interface IDashboardRepository { saveDashboard(dashboard: DashboardEntity): Promise; removeDashboard(dashboard: DashboardEntity): Promise; } - -export interface IDashboardWidgetRepository { - findWidgetById(widgetId: string): Promise; - findWidgetByIdAndDashboardId(widgetId: string, dashboardId: string): Promise; - findAllWidgetsByDashboardId(dashboardId: string): Promise; - saveWidget(widget: DashboardWidgetEntity): Promise; - removeWidget(widget: DashboardWidgetEntity): Promise; -} diff --git a/backend/src/entities/visualizations/dashboard/use-cases/dashboard-use-cases.interface.ts b/backend/src/entities/visualizations/dashboard/use-cases/dashboard-use-cases.interface.ts index fe5ca62f8..f7d062c31 100644 --- a/backend/src/entities/visualizations/dashboard/use-cases/dashboard-use-cases.interface.ts +++ b/backend/src/entities/visualizations/dashboard/use-cases/dashboard-use-cases.interface.ts @@ -3,11 +3,7 @@ import { CreateDashboardDs } from '../data-structures/create-dashboard.ds.js'; import { UpdateDashboardDs } from '../data-structures/update-dashboard.ds.js'; import { FindDashboardDs } from '../data-structures/find-dashboard.ds.js'; import { FindAllDashboardsDs } from '../data-structures/find-all-dashboards.ds.js'; -import { CreateDashboardWidgetDs } from '../data-structures/create-dashboard-widget.ds.js'; -import { UpdateDashboardWidgetDs } from '../data-structures/update-dashboard-widget.ds.js'; -import { DeleteDashboardWidgetDs } from '../data-structures/delete-dashboard-widget.ds.js'; import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; -import { FoundDashboardWidgetDto } from '../dto/found-dashboard-widget.dto.js'; export interface ICreateDashboard { execute(inputData: CreateDashboardDs, inTransaction: InTransactionEnum): Promise; @@ -28,15 +24,3 @@ export interface IFindAllDashboards { export interface IDeleteDashboard { execute(inputData: FindDashboardDs, inTransaction: InTransactionEnum): Promise; } - -export interface ICreateDashboardWidget { - execute(inputData: CreateDashboardWidgetDs, inTransaction: InTransactionEnum): Promise; -} - -export interface IUpdateDashboardWidget { - execute(inputData: UpdateDashboardWidgetDs, inTransaction: InTransactionEnum): Promise; -} - -export interface IDeleteDashboardWidget { - execute(inputData: DeleteDashboardWidgetDs, inTransaction: InTransactionEnum): Promise; -} diff --git a/backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-dto.util.ts b/backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-dto.util.ts index 0168fcc8f..d51ba5260 100644 --- a/backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-dto.util.ts +++ b/backend/src/entities/visualizations/dashboard/utils/build-found-dashboard-dto.util.ts @@ -1,6 +1,6 @@ import { DashboardEntity } from '../dashboard.entity.js'; import { FoundDashboardDto } from '../dto/found-dashboard.dto.js'; -import { buildFoundDashboardWidgetDto } from './build-found-dashboard-widget-dto.util.js'; +import { buildFoundDashboardWidgetDto } from '../../dashboard-widget/utils/build-found-dashboard-widget-dto.util.js'; export function buildFoundDashboardDto(dashboard: DashboardEntity): FoundDashboardDto { return { diff --git a/backend/src/entities/visualizations/saved-db-query/saved-db-query.entity.ts b/backend/src/entities/visualizations/saved-db-query/saved-db-query.entity.ts index 5d1b44467..b21557ad0 100644 --- a/backend/src/entities/visualizations/saved-db-query/saved-db-query.entity.ts +++ b/backend/src/entities/visualizations/saved-db-query/saved-db-query.entity.ts @@ -12,7 +12,7 @@ import { } from 'typeorm'; import { Encryptor } from '../../../helpers/encryption/encryptor.js'; import { ConnectionEntity } from '../../connection/connection.entity.js'; -import { DashboardWidgetEntity } from '../dashboard/dashboard-widget.entity.js'; +import { DashboardWidgetEntity } from '../dashboard-widget/dashboard-widget.entity.js'; @Entity('saved_db_query') export class SavedDbQueryEntity {