diff --git a/src/app.module.ts b/src/app.module.ts index 9adc2d3f4b..1f88caeea0 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -29,7 +29,6 @@ import { NotificationsModule } from '@/routes/notifications/notifications.module import { EstimationsModule } from '@/routes/estimations/estimations.module'; import { MessagesModule } from '@/routes/messages/messages.module'; import { ValidationModule } from '@/validation/validation.module'; -import { FlushModule } from '@/routes/flush/flush.module'; import { RequestScopedLoggingModule } from '@/logging/logging.module'; import { RouteLoggerInterceptor } from '@/routes/common/interceptors/route-logger.interceptor'; import { NotFoundLoggerMiddleware } from '@/middleware/not-found-logger.middleware'; @@ -67,7 +66,6 @@ export class AppModule implements NestModule { ? [AlertsControllerModule, EmailControllerModule, RecoveryModule] : []), EstimationsModule, - FlushModule, HealthModule, MessagesModule, NotificationsModule, diff --git a/src/domain.module.ts b/src/domain.module.ts index 05df9fe430..2e630ed8fa 100644 --- a/src/domain.module.ts +++ b/src/domain.module.ts @@ -45,8 +45,6 @@ import { EstimationsValidator } from '@/domain/estimations/estimations.validator import { MessagesRepository } from '@/domain/messages/messages.repository'; import { IMessagesRepository } from '@/domain/messages/messages.repository.interface'; import { MessageValidator } from '@/domain/messages/message.validator'; -import { FlushRepository } from '@/domain/flush/flush.repository'; -import { IFlushRepository } from '@/domain/flush/flush.repository.interface'; import { IHealthRepository } from '@/domain/health/health.repository.interface'; import { HealthRepository } from '@/domain/health/health.repository'; import { HumanDescriptionApiModule } from '@/datasources/human-description-api/human-description-api.module'; @@ -76,7 +74,6 @@ import { FiatCodesValidator } from '@/domain/prices/fiat-codes.validator'; { provide: IDataDecodedRepository, useClass: DataDecodedRepository }, { provide: IDelegateRepository, useClass: DelegateRepository }, { provide: IEstimationsRepository, useClass: EstimationsRepository }, - { provide: IFlushRepository, useClass: FlushRepository }, { provide: IHealthRepository, useClass: HealthRepository }, { provide: IHumanDescriptionRepository, @@ -119,7 +116,6 @@ import { FiatCodesValidator } from '@/domain/prices/fiat-codes.validator'; IDataDecodedRepository, IDelegateRepository, IEstimationsRepository, - IFlushRepository, IHealthRepository, IHumanDescriptionRepository, IMessagesRepository, diff --git a/src/domain/flush/entities/invalidation-pattern.dto.entity.ts b/src/domain/flush/entities/invalidation-pattern.dto.entity.ts deleted file mode 100644 index 9e8f1fbe75..0000000000 --- a/src/domain/flush/entities/invalidation-pattern.dto.entity.ts +++ /dev/null @@ -1,20 +0,0 @@ -export class InvalidationPatternDetails { - chainId: string | null; - - constructor(chainId: string | null) { - this.chainId = chainId; - } -} - -export class InvalidationPatternDto { - invalidate: string; - patternDetails: InvalidationPatternDetails | null; - - constructor( - invalidate: string, - patternDetails: InvalidationPatternDetails | null, - ) { - this.invalidate = invalidate; - this.patternDetails = patternDetails; - } -} diff --git a/src/domain/flush/entities/invalidation-target.entity.ts b/src/domain/flush/entities/invalidation-target.entity.ts deleted file mode 100644 index 8a02097929..0000000000 --- a/src/domain/flush/entities/invalidation-target.entity.ts +++ /dev/null @@ -1,3 +0,0 @@ -export enum InvalidationTarget { - Chains = 'Chains', -} diff --git a/src/domain/flush/flush.repository.interface.ts b/src/domain/flush/flush.repository.interface.ts deleted file mode 100644 index 852a8b4c72..0000000000 --- a/src/domain/flush/flush.repository.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { InvalidationPatternDto } from '@/domain/flush/entities/invalidation-pattern.dto.entity'; - -export const IFlushRepository = Symbol('IFlushRepository'); - -export interface IFlushRepository { - /** - * Invalidates cache data for the given {@link InvalidationPatternDto} - * - * @param pattern {@link InvalidationPatternDto} to invalidate. - */ - execute(pattern: InvalidationPatternDto): Promise; -} diff --git a/src/domain/flush/flush.repository.ts b/src/domain/flush/flush.repository.ts deleted file mode 100644 index abc7979d39..0000000000 --- a/src/domain/flush/flush.repository.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { InvalidationPatternDto } from '@/domain/flush/entities/invalidation-pattern.dto.entity'; -import { InvalidationTarget } from '@/domain/flush/entities/invalidation-target.entity'; -import { IFlushRepository } from '@/domain/flush/flush.repository.interface'; -import { IConfigApi } from '@/domain/interfaces/config-api.interface'; -import { ILoggingService, LoggingService } from '@/logging/logging.interface'; - -@Injectable() -export class FlushRepository implements IFlushRepository { - constructor( - @Inject(LoggingService) private readonly loggingService: ILoggingService, - @Inject(IConfigApi) private readonly configApi: IConfigApi, - ) {} - - async execute(pattern: InvalidationPatternDto): Promise { - switch (pattern.invalidate) { - case InvalidationTarget[InvalidationTarget.Chains]: - await Promise.all([ - this.configApi.clearChains(), - this.configApi.clearSafeApps(), - ]); - break; - default: - this.loggingService.warn(`Unknown flush pattern ${pattern.invalidate}`); - } - } -} diff --git a/src/routes/flush/entities/__tests__/invalidation-pattern-details.dto.builder.ts b/src/routes/flush/entities/__tests__/invalidation-pattern-details.dto.builder.ts deleted file mode 100644 index 861d59944d..0000000000 --- a/src/routes/flush/entities/__tests__/invalidation-pattern-details.dto.builder.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { faker } from '@faker-js/faker'; -import { Builder, IBuilder } from '@/__tests__/builder'; -import { InvalidationPatternDetails } from '@/routes/flush/entities/invalidation-pattern.dto.entity'; - -export function invalidationPatternDetailsBuilder(): IBuilder { - return new Builder().with( - 'chain_id', - faker.string.numeric(), - ); -} diff --git a/src/routes/flush/entities/__tests__/invalidation-pattern.dto.builder.ts b/src/routes/flush/entities/__tests__/invalidation-pattern.dto.builder.ts deleted file mode 100644 index 9dc470aaed..0000000000 --- a/src/routes/flush/entities/__tests__/invalidation-pattern.dto.builder.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { faker } from '@faker-js/faker'; -import { Builder, IBuilder } from '@/__tests__/builder'; -import { invalidationPatternDetailsBuilder } from '@/routes/flush/entities/__tests__/invalidation-pattern-details.dto.builder'; -import { InvalidationPatternDto } from '@/routes/flush/entities/invalidation-pattern.dto.entity'; - -export function invalidationPatternDtoBuilder(): IBuilder { - return new Builder() - .with('invalidate', faker.word.sample()) - .with('patternDetails', invalidationPatternDetailsBuilder().build()); -} diff --git a/src/routes/flush/entities/invalidation-pattern.dto.entity.ts b/src/routes/flush/entities/invalidation-pattern.dto.entity.ts deleted file mode 100644 index d883f48daa..0000000000 --- a/src/routes/flush/entities/invalidation-pattern.dto.entity.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; - -export class InvalidationPatternDetails { - @ApiPropertyOptional({ type: 'string', nullable: true }) - chain_id: string | null; -} - -export class InvalidationPatternDto { - @ApiProperty() - invalidate: string; - - @ApiPropertyOptional({ type: Object, nullable: true }) - patternDetails: InvalidationPatternDetails | null; -} diff --git a/src/routes/flush/entities/schemas/invalidation-pattern.dto.schema.ts b/src/routes/flush/entities/schemas/invalidation-pattern.dto.schema.ts deleted file mode 100644 index 63835a715b..0000000000 --- a/src/routes/flush/entities/schemas/invalidation-pattern.dto.schema.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Schema } from 'ajv'; -import { InvalidationTarget } from '@/domain/flush/entities/invalidation-target.entity'; - -export const INVALIDATION_PATTERN_DETAIL_SCHEMA_ID = - 'https://safe-client.safe.global/schemas/flush/invalidation-pattern-detail.json'; - -export const invalidationPatternDetailSchema: Schema = { - $id: INVALIDATION_PATTERN_DETAIL_SCHEMA_ID, - type: 'object', - properties: { - chain_id: { type: ['string', 'null'] }, - }, - required: [], -}; - -export const INVALIDATION_PATTERN_DTO_SCHEMA_ID = - 'https://safe-client.safe.global/schemas/flush/invalidation-pattern.dto.json'; - -export const invalidationPatternDtoSchema: Schema = { - $id: INVALIDATION_PATTERN_DTO_SCHEMA_ID, - type: 'object', - properties: { - invalidate: { type: 'string', enum: Object.values(InvalidationTarget) }, - patternDetails: { - anyOf: [{ type: 'null' }, { $ref: 'invalidation-pattern-detail.json' }], - }, - }, - required: ['invalidate'], -}; diff --git a/src/routes/flush/flush.controller.spec.ts b/src/routes/flush/flush.controller.spec.ts deleted file mode 100644 index aa18e0ab01..0000000000 --- a/src/routes/flush/flush.controller.spec.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { INestApplication } from '@nestjs/common'; -import { Test, TestingModule } from '@nestjs/testing'; -import * as request from 'supertest'; -import { TestAppProvider } from '@/__tests__/test-app.provider'; -import { TestCacheModule } from '@/datasources/cache/__tests__/test.cache.module'; -import { TestNetworkModule } from '@/datasources/network/__tests__/test.network.module'; -import { chainBuilder } from '@/domain/chains/entities/__tests__/chain.builder'; -import { pageBuilder } from '@/domain/entities/__tests__/page.builder'; -import { TestLoggingModule } from '@/logging/__tests__/test.logging.module'; -import configuration from '@/config/entities/__tests__/configuration'; -import { IConfigurationService } from '@/config/configuration.service.interface'; -import { FakeCacheService } from '@/datasources/cache/__tests__/fake.cache.service'; -import { CacheService } from '@/datasources/cache/cache.service.interface'; -import { AppModule } from '@/app.module'; -import { CacheModule } from '@/datasources/cache/cache.module'; -import { RequestScopedLoggingModule } from '@/logging/logging.module'; -import { NetworkModule } from '@/datasources/network/network.module'; -import { NetworkService } from '@/datasources/network/network.service.interface'; -import { invalidationPatternDtoBuilder } from '@/routes/flush/entities/__tests__/invalidation-pattern.dto.builder'; -import { EmailDataSourceModule } from '@/datasources/email/email.datasource.module'; -import { TestEmailDatasourceModule } from '@/datasources/email/__tests__/test.email.datasource.module'; - -describe('Flush Controller (Unit)', () => { - let app: INestApplication; - let safeConfigUrl; - let authToken; - let fakeCacheService: FakeCacheService; - let networkService; - - beforeEach(async () => { - jest.clearAllMocks(); - - const moduleFixture: TestingModule = await Test.createTestingModule({ - imports: [AppModule.register(configuration)], - }) - .overrideModule(EmailDataSourceModule) - .useModule(TestEmailDatasourceModule) - .overrideModule(CacheModule) - .useModule(TestCacheModule) - .overrideModule(RequestScopedLoggingModule) - .useModule(TestLoggingModule) - .overrideModule(NetworkModule) - .useModule(TestNetworkModule) - .compile(); - - fakeCacheService = moduleFixture.get(CacheService); - const configurationService = moduleFixture.get(IConfigurationService); - safeConfigUrl = configurationService.get('safeConfig.baseUri'); - authToken = configurationService.get('auth.token'); - networkService = moduleFixture.get(NetworkService); - - app = await new TestAppProvider().provide(moduleFixture); - await app.init(); - }); - - afterAll(async () => { - await app.close(); - }); - - it('should throw an error if authorization is not sent in the request headers', async () => { - await request(app.getHttpServer()).post('/v2/flush').send({}).expect(403); - }); - - it('should throw an error for a malformed request', async () => { - await request(app.getHttpServer()) - .post('/v2/flush') - .set('Authorization', `Basic ${authToken}`) - .send({}) - .expect(400); - }); - - it('should invalidate chains', async () => { - const chains = [ - chainBuilder().with('chainId', '1').build(), - chainBuilder().with('chainId', '2').build(), - ]; - networkService.get.mockImplementation((url) => { - switch (url) { - case `${safeConfigUrl}/api/v1/chains`: - return Promise.resolve({ - data: pageBuilder().with('results', chains).build(), - }); - case `${safeConfigUrl}/api/v1/chains/${chains[0].chainId}`: - return Promise.resolve({ data: chains[0] }); - case `${safeConfigUrl}/api/v1/chains/${chains[1].chainId}`: - return Promise.resolve({ data: chains[1] }); - default: - return Promise.reject(`No matching rule for url: ${url}`); - } - }); - - // fill cache by requesting chains - await request(app.getHttpServer()).get('/v1/chains'); - await request(app.getHttpServer()).get(`/v1/chains/${chains[0].chainId}`); - await request(app.getHttpServer()).get(`/v1/chains/${chains[1].chainId}`); - - // check the cache is filled - expect(fakeCacheService.keyCount()).toBe(3); - - // execute flush - await request(app.getHttpServer()) - .post('/v2/flush') - .set('Authorization', `Basic ${authToken}`) - .send( - invalidationPatternDtoBuilder().with('invalidate', 'Chains').build(), - ) - .expect(200); - - // check the cache contains the invalidationTimeMs key only - expect(fakeCacheService.keyCount()).toBe(1); - }); -}); diff --git a/src/routes/flush/flush.controller.ts b/src/routes/flush/flush.controller.ts deleted file mode 100644 index 21ddbd3d40..0000000000 --- a/src/routes/flush/flush.controller.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { Body, Controller, HttpCode, Post, UseGuards } from '@nestjs/common'; -import { ApiExcludeController, ApiOkResponse } from '@nestjs/swagger'; -import { BasicAuthGuard } from '@/routes/common/auth/basic-auth.guard'; -import { InvalidationPatternDto } from '@/routes/flush/entities/invalidation-pattern.dto.entity'; -import { FlushService } from '@/routes/flush/flush.service'; -import { InvalidationPatternDtoValidationPipe } from '@/routes/flush/pipes/invalidation-pattern.dto.validation.pipe'; - -@Controller({ - path: '', - version: '2', -}) -@ApiExcludeController() -export class FlushController { - constructor(private readonly flushService: FlushService) {} - - @UseGuards(BasicAuthGuard) - @ApiOkResponse() - @HttpCode(200) - @Post('flush') - async flush( - @Body(InvalidationPatternDtoValidationPipe) - invalidationPatternDto: InvalidationPatternDto, - ): Promise { - return this.flushService.flush(invalidationPatternDto); - } -} diff --git a/src/routes/flush/flush.module.ts b/src/routes/flush/flush.module.ts deleted file mode 100644 index cf1188a090..0000000000 --- a/src/routes/flush/flush.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Module } from '@nestjs/common'; -import { FlushController } from '@/routes/flush/flush.controller'; -import { FlushService } from '@/routes/flush/flush.service'; - -@Module({ - controllers: [FlushController], - providers: [FlushService], -}) -export class FlushModule {} diff --git a/src/routes/flush/flush.service.ts b/src/routes/flush/flush.service.ts deleted file mode 100644 index f1d3ab236a..0000000000 --- a/src/routes/flush/flush.service.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { Inject, Injectable } from '@nestjs/common'; -import { - InvalidationPatternDetails, - InvalidationPatternDto as DomainInvalidationPatternDto, -} from '@/domain/flush/entities/invalidation-pattern.dto.entity'; -import { FlushRepository } from '@/domain/flush/flush.repository'; -import { IFlushRepository } from '@/domain/flush/flush.repository.interface'; -import { InvalidationPatternDto } from '@/routes/flush/entities/invalidation-pattern.dto.entity'; - -@Injectable() -export class FlushService { - constructor( - @Inject(IFlushRepository) - private readonly flushRepository: FlushRepository, - ) {} - - async flush(pattern: InvalidationPatternDto): Promise { - const patternDetails = pattern.patternDetails - ? new InvalidationPatternDetails(pattern.patternDetails.chain_id) - : null; - - const invalidationPattern = new DomainInvalidationPatternDto( - pattern.invalidate, - patternDetails, - ); - - await this.flushRepository.execute(invalidationPattern); - } -} diff --git a/src/routes/flush/pipes/invalidation-pattern.dto.validation.pipe.ts b/src/routes/flush/pipes/invalidation-pattern.dto.validation.pipe.ts deleted file mode 100644 index a9182d76ac..0000000000 --- a/src/routes/flush/pipes/invalidation-pattern.dto.validation.pipe.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { InvalidationPatternDto } from '@/routes/flush/entities/invalidation-pattern.dto.entity'; -import { - INVALIDATION_PATTERN_DETAIL_SCHEMA_ID, - INVALIDATION_PATTERN_DTO_SCHEMA_ID, - invalidationPatternDetailSchema, - invalidationPatternDtoSchema, -} from '@/routes/flush/entities/schemas/invalidation-pattern.dto.schema'; -import { GenericValidator } from '@/validation/providers/generic.validator'; -import { JsonSchemaService } from '@/validation/providers/json-schema.service'; -import { HttpStatus, Injectable, PipeTransform } from '@nestjs/common'; -import { ValidateFunction } from 'ajv'; - -@Injectable() -export class InvalidationPatternDtoValidationPipe - implements PipeTransform -{ - private readonly isValid: ValidateFunction; - - constructor( - private readonly genericValidator: GenericValidator, - private readonly jsonSchemaService: JsonSchemaService, - ) { - this.jsonSchemaService.getSchema( - INVALIDATION_PATTERN_DETAIL_SCHEMA_ID, - invalidationPatternDetailSchema, - ); - this.isValid = this.jsonSchemaService.getSchema( - INVALIDATION_PATTERN_DTO_SCHEMA_ID, - invalidationPatternDtoSchema, - ); - } - transform(data: any): InvalidationPatternDto { - try { - return this.genericValidator.validate(this.isValid, data); - } catch (err) { - err.status = HttpStatus.BAD_REQUEST; - throw err; - } - } -}