diff --git a/package-lock.json b/package-lock.json index 02fe5d4d..6fba6825 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,11 +6,12 @@ "packages": { "": { "name": "dex-backend", - "version": "1.2.1", + "version": "1.4.0", "hasInstallScript": true, "license": "UNLICENSED", "dependencies": { "@aeternity/aepp-sdk": "^13.3.2", + "@nestjs/cache-manager": "^2.2.2", "@nestjs/common": "^10.3.3", "@nestjs/core": "^10.3.3", "@nestjs/platform-express": "^10.3.3", @@ -39,7 +40,6 @@ "@types/ws": "^8.5.10", "@typescript-eslint/eslint-plugin": "^7.0.1", "@typescript-eslint/parser": "^7.0.1", - "dotenv": "^16.4.5", "dotenv-cli": "^7.3.0", "eslint": "^8.56.0", "eslint-config-prettier": "^9.1.0", @@ -1877,6 +1877,17 @@ "resolved": "https://registry.npmjs.org/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz", "integrity": "sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==" }, + "node_modules/@nestjs/cache-manager": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/@nestjs/cache-manager/-/cache-manager-2.2.2.tgz", + "integrity": "sha512-+n7rpU1QABeW2WV17Dl1vZCG3vWjJU1MaamWgZvbGxYE9EeCM0lVLfw3z7acgDTNwOy+K68xuQPoIMxD0bhjlA==", + "peerDependencies": { + "@nestjs/common": "^9.0.0 || ^10.0.0", + "@nestjs/core": "^9.0.0 || ^10.0.0", + "cache-manager": "<=5", + "rxjs": "^7.0.0" + } + }, "node_modules/@nestjs/cli": { "version": "10.3.2", "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.3.2.tgz", @@ -3767,6 +3778,27 @@ "node": ">= 0.8" } }, + "node_modules/cache-manager": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/cache-manager/-/cache-manager-5.7.3.tgz", + "integrity": "sha512-Vp2gd2aDm/MXdEWD0FLdOflvcVj4rdJ1FFmPUeOKq+fuL7MEUcezbTWxQmVB1TTN5Ig92CabMfi5z+HyQwVg9A==", + "peer": true, + "dependencies": { + "eventemitter3": "^5.0.1", + "lodash.clonedeep": "^4.5.0", + "lru-cache": "^10.2.2", + "promise-coalesce": "^1.1.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/cache-manager/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "peer": true + }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -4983,6 +5015,12 @@ "node": ">= 0.6" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "peer": true + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -7024,6 +7062,12 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", + "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", + "peer": true + }, "node_modules/lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", @@ -7969,6 +8013,15 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "node_modules/promise-coalesce": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/promise-coalesce/-/promise-coalesce-1.1.2.tgz", + "integrity": "sha512-zLaJ9b8hnC564fnJH6NFSOGZYYdzrAJn2JUUIwzoQb32fG2QAakpDNM+CZo1km6keXkRXRM+hml1BFAPVnPkxg==", + "peer": true, + "engines": { + "node": ">=16" + } + }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", diff --git a/package.json b/package.json index 23a5ca23..a1a72f6e 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ }, "dependencies": { "@aeternity/aepp-sdk": "^13.3.2", + "@nestjs/cache-manager": "^2.2.2", "@nestjs/common": "^10.3.3", "@nestjs/core": "^10.3.3", "@nestjs/platform-express": "^10.3.3", diff --git a/src/api/pair-liquidity-info-history/pair-liquidity-info-history.controller.spec.ts b/src/api/pair-liquidity-info-history/pair-liquidity-info-history.controller.spec.ts index 9afb3407..2c046119 100644 --- a/src/api/pair-liquidity-info-history/pair-liquidity-info-history.controller.spec.ts +++ b/src/api/pair-liquidity-info-history/pair-liquidity-info-history.controller.spec.ts @@ -1,3 +1,4 @@ +import { CacheModule } from '@nestjs/cache-manager'; import { INestApplication } from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { Pair, PairLiquidityInfoHistory } from '@prisma/client'; @@ -29,6 +30,11 @@ describe('PairLiquidityInfoHistoryController', () => { useValue: mockPairLiquidityInfoHistoryService, }, ], + imports: [ + CacheModule.register({ + isGlobal: true, + }), + ], }).compile(); app = module.createNestApplication(); diff --git a/src/api/pair-liquidity-info-history/pair-liquidity-info-history.controller.ts b/src/api/pair-liquidity-info-history/pair-liquidity-info-history.controller.ts index 29f7c8d2..85a5cc76 100644 --- a/src/api/pair-liquidity-info-history/pair-liquidity-info-history.controller.ts +++ b/src/api/pair-liquidity-info-history/pair-liquidity-info-history.controller.ts @@ -1,9 +1,11 @@ +import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager'; import { Controller, Get, ParseEnumPipe, ParseIntPipe, Query, + UseInterceptors, } from '@nestjs/common'; import { ApiOperation, ApiQuery, ApiResponse } from '@nestjs/swagger'; import BigNumber from 'bignumber.js'; @@ -15,6 +17,7 @@ import { ContractAddress } from '@/clients/sdk-client.model'; import { calculateUsdValue } from '@/lib/utils'; @Controller('history') +@UseInterceptors(CacheInterceptor) export class PairLiquidityInfoHistoryController { constructor( private readonly pairLiquidityInfoHistoryService: PairLiquidityInfoHistoryService, @@ -76,6 +79,7 @@ export class PairLiquidityInfoHistoryController { required: false, }) @ApiResponse({ status: 200, type: [PairLiquidityInfoHistoryEntry] }) + @CacheTTL(24 * 60 * 60 * 1000) async findAll( @Query('limit', new ParseIntPipe({ optional: true })) limit: number = 100, @Query('offset', new ParseIntPipe({ optional: true })) offset: number = 0, diff --git a/src/api/pairs/pairs.controller.ts b/src/api/pairs/pairs.controller.ts index 222b2251..564f2262 100644 --- a/src/api/pairs/pairs.controller.ts +++ b/src/api/pairs/pairs.controller.ts @@ -1,9 +1,11 @@ +import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager'; import { Controller, Get, NotFoundException, Param, Query, + UseInterceptors, } from '@nestjs/common'; import { ApiOperation, ApiParam, ApiQuery, ApiResponse } from '@nestjs/swagger'; import * as prisma from '@prisma/client'; @@ -45,6 +47,7 @@ const toLiquidityInfoDto = ( ): dto.LiquidityInfo | undefined => liquidityInfo ? removeId(liquidityInfo) : undefined; +@UseInterceptors(CacheInterceptor) @Controller('pairs') export class PairsController { constructor(private readonly pairsService: PairsService) {} @@ -70,6 +73,7 @@ for this purpose use the individual \`pairs/:address\` route`, required: false, }) @ApiResponse({ status: 200, type: [dto.PairWithUsd] }) + @CacheTTL(24 * 60 * 60 * 1000) async getAllPairs( @Query('only-listed') onlyListedStr?: string, // false | true @Query('token') token?: ContractAddress, diff --git a/src/api/tokens/tokens.controller.ts b/src/api/tokens/tokens.controller.ts index 42cb1459..6ca08700 100644 --- a/src/api/tokens/tokens.controller.ts +++ b/src/api/tokens/tokens.controller.ts @@ -1,3 +1,4 @@ +import { CacheInterceptor, CacheTTL } from '@nestjs/cache-manager'; import { Controller, Delete, @@ -7,6 +8,7 @@ import { Param, Post, UnauthorizedException, + UseInterceptors, } from '@nestjs/common'; import { ApiHeaders, @@ -43,6 +45,8 @@ const toDtoToken = ({ id, // eslint-disable-line @typescript-eslint/no-unused-vars ...tail }: prisma.Token) => tail; + +@UseInterceptors(CacheInterceptor) @Controller('tokens') export class TokensController { constructor(private readonly tokensService: TokensService) {} @@ -53,6 +57,7 @@ export class TokensController { description: `All the tokens no matter if are officially supported by the DEX (listed=true) or not will be retrieved`, }) @ApiResponse({ status: 200, type: [dto.TokenWithUsd] }) + @CacheTTL(24 * 60 * 60 * 1000) async getAllTokens(): Promise { return this.tokensService.getAllTokensWithAggregation(); } diff --git a/src/app.module.ts b/src/app.module.ts index 9c8141d8..81e2d991 100644 --- a/src/app.module.ts +++ b/src/app.module.ts @@ -1,3 +1,4 @@ +import { CacheModule } from '@nestjs/cache-manager'; import { Module } from '@nestjs/common'; import { ApiModule } from '@/api/api.module'; @@ -13,7 +14,15 @@ import { PairSyncService } from '@/tasks/pair-sync/pair-sync.service'; import { TasksModule } from '@/tasks/tasks.module'; @Module({ - imports: [ApiModule, ClientsModule, DatabaseModule, TasksModule], + imports: [ + ApiModule, + ClientsModule, + DatabaseModule, + TasksModule, + CacheModule.register({ + isGlobal: true, + }), + ], controllers: [AppController], providers: [ MdwWsClientService, diff --git a/src/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service.spec.ts b/src/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service.spec.ts index 85bdb151..b836b3af 100644 --- a/src/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service.spec.ts +++ b/src/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service.spec.ts @@ -7,6 +7,8 @@ import { PairLiquidityInfoHistoryDbService } from '@/database/pair-liquidity-inf import { bigIntToDecimal } from '@/lib/utils'; import { PairLiquidityInfoHistoryImporterService } from '@/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service'; import resetAllMocks = jest.resetAllMocks; +import { CacheModule } from '@nestjs/cache-manager'; + import { CoinmarketcapClientService } from '@/clients/coinmarketcap-client.service'; import { PairLiquidityInfoHistoryErrorDbService } from '@/database/pair-liquidity-info-history-error/pair-liquidity-info-history-error-db.service'; import { @@ -73,6 +75,11 @@ describe('PairLiquidityInfoHistoryImporterService', () => { useValue: mockCoinmarketcapClient, }, ], + imports: [ + CacheModule.register({ + isGlobal: true, + }), + ], }).compile(); service = module.get( PairLiquidityInfoHistoryImporterService, diff --git a/src/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service.ts b/src/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service.ts index a315279a..1d4cbcd4 100644 --- a/src/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service.ts +++ b/src/tasks/pair-liquidity-info-history-importer/pair-liquidity-info-history-importer.service.ts @@ -1,3 +1,4 @@ +import { Cache } from '@nestjs/cache-manager'; import { Injectable, Logger } from '@nestjs/common'; import { orderBy } from 'lodash'; @@ -41,6 +42,7 @@ export class PairLiquidityInfoHistoryImporterService { private mdwClient: MdwHttpClientService, private sdkClient: SdkClientService, private coinmarketcapClient: CoinmarketcapClientService, + private cacheManager: Cache, ) {} readonly logger = new Logger(PairLiquidityInfoHistoryImporterService.name); @@ -265,6 +267,7 @@ export class PairLiquidityInfoHistoryImporterService { } if (numUpserted > 0) { + await this.cacheManager.reset(); this.logger.log( `Completed sync for pair ${pairWithTokens.id} ${pairWithTokens.address}. Synced ${numUpserted} log(s).`, ); diff --git a/src/tasks/pair-path-calculator/pair-path-calculator.service.ts b/src/tasks/pair-path-calculator/pair-path-calculator.service.ts index 77b2d8fc..89f3242f 100644 --- a/src/tasks/pair-path-calculator/pair-path-calculator.service.ts +++ b/src/tasks/pair-path-calculator/pair-path-calculator.service.ts @@ -1,3 +1,4 @@ +import { Cache } from '@nestjs/cache-manager'; import { Injectable, Logger } from '@nestjs/common'; import { Decimal } from '@prisma/client/runtime/library'; import BigNumber from 'bignumber.js'; @@ -11,6 +12,7 @@ export class PairPathCalculatorService { constructor( private pairLiquidityInfoHistoryDb: PairLiquidityInfoHistoryDbService, private tokenDb: TokenDbService, + private cacheManager: Cache, ) {} readonly logger = new Logger(PairPathCalculatorService.name); @@ -27,6 +29,8 @@ export class PairPathCalculatorService { throw new Error('WAE token not found in db'); } + let updates = 0; + for (const entry of entries) { this.logger.debug(`Processing entry ${entry.id}`); // for each entry get the timestamp and use the info history db service to fetch the graph at the time @@ -127,6 +131,11 @@ export class PairPathCalculatorService { ? new Decimal(aePrices[1].toString()) : new Decimal(-1), }); + updates += 1; + } + if (updates > 0) { + this.logger.debug(`Updated tokenAePrice in ${updates} entries`); + await this.cacheManager.reset(); } } } diff --git a/src/tasks/tasks.service.spec.ts b/src/tasks/tasks.service.spec.ts index dfa745cf..64433271 100644 --- a/src/tasks/tasks.service.spec.ts +++ b/src/tasks/tasks.service.spec.ts @@ -1,3 +1,4 @@ +import { CacheModule } from '@nestjs/cache-manager'; import { Test, TestingModule } from '@nestjs/testing'; import { CoinmarketcapClientService } from '@/clients/coinmarketcap-client.service'; @@ -36,6 +37,11 @@ describe('TasksService', () => { TokenDbService, PairPathCalculatorService, ], + imports: [ + CacheModule.register({ + isGlobal: true, + }), + ], }).compile(); tasksService = module.get(TasksService);