diff --git a/redisinsight/api/config/default.ts b/redisinsight/api/config/default.ts index 9888175669..a23500013a 100644 --- a/redisinsight/api/config/default.ts +++ b/redisinsight/api/config/default.ts @@ -153,7 +153,7 @@ export default { }, ], redisStack: { - id: process.env.REDIS_STACK_DATABASE_ID, + id: process.env.BUILD_TYPE === 'REDIS_STACK' ? process.env.REDIS_STACK_DATABASE_ID || 'redis-stack' : undefined, name: process.env.REDIS_STACK_DATABASE_NAME, host: process.env.REDIS_STACK_DATABASE_HOST, port: process.env.REDIS_STACK_DATABASE_PORT, diff --git a/redisinsight/api/src/modules/shared/services/instances-business/databases.provider.ts b/redisinsight/api/src/modules/shared/services/instances-business/databases.provider.ts index 3638e251f5..67f173aa4d 100644 --- a/redisinsight/api/src/modules/shared/services/instances-business/databases.provider.ts +++ b/redisinsight/api/src/modules/shared/services/instances-business/databases.provider.ts @@ -3,38 +3,24 @@ import { Injectable, Logger, NotFoundException, - OnApplicationBootstrap, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { merge } from 'lodash'; import { Repository } from 'typeorm'; import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; import ERROR_MESSAGES from 'src/constants/error-messages'; -import config from 'src/utils/config'; import { EncryptionService } from 'src/modules/core/encryption/encryption.service'; import { DatabaseInstanceEntity } from 'src/modules/core/models/database-instance.entity'; @Injectable() -export class DatabasesProvider implements OnApplicationBootstrap { - private logger = new Logger('DatabaseProvider'); +export class DatabasesProvider { + protected logger = new Logger('DatabaseProvider'); constructor( @InjectRepository(DatabaseInstanceEntity) - private readonly databasesRepository: Repository, - private readonly encryptionService: EncryptionService, + protected readonly databasesRepository: Repository, + protected readonly encryptionService: EncryptionService, ) {} - async onApplicationBootstrap() { - const REDIS_STACK_CONFIG = config.get('redisStack'); - if (REDIS_STACK_CONFIG?.id) { - await this.setPredefinedDatabase(merge({ - name: 'Redis Stack', - host: 'localhost', - port: '6379', - }, REDIS_STACK_CONFIG)); - } - } - /** * Fast check if database exists. * No need to retrieve any fields. @@ -207,32 +193,4 @@ export class DatabasesProvider implements OnApplicationBootstrap { sentinelMasterPassword, }; } - - private async setPredefinedDatabase( - options: { id: string; name: string; host: string; port: string; }, - ): Promise { - try { - const { - id, name, host, port, - } = options; - const isExist = await this.exists(id); - if (!isExist) { - const database: any = this.databasesRepository.create({ - id, - host, - port: parseInt(port, 10), - name, - username: null, - password: null, - tls: false, - verifyServerCert: false, - db: 0, - }); - await this.save(database); - } - this.logger.log(`Succeed to set predefined database ${id}`); - } catch (error) { - this.logger.error('Failed to set predefined database', error); - } - } } diff --git a/redisinsight/api/src/modules/shared/services/instances-business/stack.database.provider.spec.ts b/redisinsight/api/src/modules/shared/services/instances-business/stack.database.provider.spec.ts new file mode 100644 index 0000000000..f53a3d3648 --- /dev/null +++ b/redisinsight/api/src/modules/shared/services/instances-business/stack.database.provider.spec.ts @@ -0,0 +1,59 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { getRepositoryToken } from '@nestjs/typeorm'; +import { + mockDataToEncrypt, + mockEncryptionService, + mockEncryptResult, + mockRepository, + MockType, +} from 'src/__mocks__'; +import { EncryptionService } from 'src/modules/core/encryption/encryption.service'; +import { DatabaseInstanceEntity } from 'src/modules/core/models/database-instance.entity'; +import { StackDatabasesProvider } from './stack.databases.provider'; + +describe('StackDatabasesProvider', () => { + let service: StackDatabasesProvider; + let encryptionService: MockType; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + StackDatabasesProvider, + { + provide: EncryptionService, + useFactory: mockEncryptionService, + }, + { + provide: getRepositoryToken(DatabaseInstanceEntity), + useFactory: mockRepository, + }, + ], + }).compile(); + + service = module.get(StackDatabasesProvider); + encryptionService = module.get(EncryptionService); + + encryptionService.decrypt.mockReturnValue(mockDataToEncrypt); + encryptionService.encrypt.mockReturnValue(mockEncryptResult); + }); + + describe('onApplicationBootstrap', () => { + beforeEach(() => { + service.save = jest.fn(); + }); + it('should save database if it is not exist', async () => { + service.exists = jest.fn().mockResolvedValue(false); + + await service.onApplicationBootstrap(); + + expect(service.save).toHaveBeenCalled(); + }); + it('should not save database if it is exist', async () => { + service.exists = jest.fn().mockResolvedValue(true); + + await service.onApplicationBootstrap(); + + expect(service.save).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/redisinsight/api/src/modules/shared/services/instances-business/stack.databases.provider.ts b/redisinsight/api/src/modules/shared/services/instances-business/stack.databases.provider.ts new file mode 100644 index 0000000000..ed0310ddf9 --- /dev/null +++ b/redisinsight/api/src/modules/shared/services/instances-business/stack.databases.provider.ts @@ -0,0 +1,118 @@ +import { + Injectable, + Logger, + OnApplicationBootstrap, +} from '@nestjs/common'; +import { merge } from 'lodash'; +import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity'; +import config from 'src/utils/config'; +import { DatabaseInstanceEntity } from 'src/modules/core/models/database-instance.entity'; +import { DatabasesProvider } from 'src/modules/shared/services/instances-business/databases.provider'; + +const REDIS_STACK_CONFIG = config.get('redisStack'); + +@Injectable() +export class StackDatabasesProvider extends DatabasesProvider implements OnApplicationBootstrap { + protected logger = new Logger('StackDatabasesProvider'); + + async onApplicationBootstrap() { + await this.setPredefinedDatabase(merge({ + name: 'Redis Stack', + host: 'localhost', + port: '6379', + }, REDIS_STACK_CONFIG)); + } + + /** + * Check if database for Stack exists. + */ + async exists(): Promise { + return super.exists(REDIS_STACK_CONFIG.id); + } + + /** + * Get list of databases from the local db + */ + async getAll(): Promise { + this.logger.log('Getting databases list'); + return [await this.getOneById(REDIS_STACK_CONFIG.id)]; + } + + /** + * Get single database by id from the local db + * @throws NotFoundException in case when no database found + */ + async getOneById( + id: string, + ignoreEncryptionErrors: boolean = false, + ): Promise { + return super.getOneById(REDIS_STACK_CONFIG.id, ignoreEncryptionErrors); + } + + /** + * Save entire entity + * @param database + */ + async save(database: DatabaseInstanceEntity): Promise { + return super.save(new DatabaseInstanceEntity({ + ...database, + id: REDIS_STACK_CONFIG.id, + })); + } + + /** + * Update database field(s) without encryption logic + * @param id + * @param data + * @throws BadRequestException error when try to update password or sentinelMasterPassword fields + */ + async patch(id: string, data: QueryDeepPartialEntity) { + return super.patch(REDIS_STACK_CONFIG.id, { + ...data, + id: REDIS_STACK_CONFIG.id, + }); + } + + /** + * Update entire database entity with fields encryption logic + * + * @param id + * @param data + */ + async update(id: string, data: DatabaseInstanceEntity) { + return super.update(REDIS_STACK_CONFIG.id, new DatabaseInstanceEntity({ + ...data, + id: REDIS_STACK_CONFIG.id, + })); + } + + /** + * Save database entity for Stack + * + * @param options + */ + private async setPredefinedDatabase( + options: { id: string; name: string; host: string; port: string; }, + ): Promise { + try { + const { + id, name, host, port, + } = options; + const isExist = await this.exists(); + if (!isExist) { + const database: any = this.databasesRepository.create({ + id, + host, + port: parseInt(port, 10), + name, + tls: false, + verifyServerCert: false, + }); + await this.save(database); + } + this.logger.log(`Succeed to set predefined database ${id}`); + } catch (error) { + this.logger.error('Failed to set predefined database', error); + } + } +} diff --git a/redisinsight/api/src/modules/shared/shared.module.ts b/redisinsight/api/src/modules/shared/shared.module.ts index 078c831da8..64112eeae5 100644 --- a/redisinsight/api/src/modules/shared/shared.module.ts +++ b/redisinsight/api/src/modules/shared/shared.module.ts @@ -9,6 +9,7 @@ import { import { DatabasesProvider } from 'src/modules/shared/services/instances-business/databases.provider'; import { OverviewService } from 'src/modules/shared/services/instances-business/overview.service'; import { RedisToolFactory } from 'src/modules/shared/services/base/redis-tool.factory'; +import { StackDatabasesProvider } from 'src/modules/shared/services/instances-business/stack.databases.provider'; import { InstancesBusinessService } from './services/instances-business/instances-business.service'; import { RedisEnterpriseBusinessService } from './services/redis-enterprise-business/redis-enterprise-business.service'; import { RedisCloudBusinessService } from './services/redis-cloud-business/redis-cloud-business.service'; @@ -28,7 +29,10 @@ const SERVER_CONFIG = config.get('server'); TypeOrmModule.forFeature([DatabaseInstanceEntity]), ], providers: [ - DatabasesProvider, + { + provide: DatabasesProvider, + useClass: SERVER_CONFIG.buildType === 'REDIS_STACK' ? StackDatabasesProvider : DatabasesProvider, + }, InstancesBusinessService, InstancesAnalyticsService, RedisEnterpriseBusinessService,