From 6391975c2df7620e37eea7d3a435687306d1f815 Mon Sep 17 00:00:00 2001 From: "yevhen.burkovskyi" Date: Wed, 8 May 2024 10:06:48 +0300 Subject: [PATCH] feat: added supply growth metric --- .../enums/range.enum.ts} | 5 ++- src/modules/price/enums/price-range.enum.ts | 8 ---- src/modules/price/price.controller.ts | 4 +- src/modules/price/schemas/range.schema.ts | 4 +- src/modules/price/services/price.cache.ts | 18 ++++---- .../enums/historical-supply-range.enum.ts | 7 ---- .../supply/enums/supply-endpoints.enum.ts | 1 + .../supply/schemas/change-range.schema.ts | 4 +- .../supply/schemas/growth-range.schema.ts | 4 ++ .../supply/schemas/historical-range.schema.ts | 4 +- src/modules/supply/services/supply.cache.ts | 14 +++---- src/modules/supply/services/supply.service.ts | 42 ++++++++++++++----- src/modules/supply/supply.controller.ts | 16 +++++-- 13 files changed, 76 insertions(+), 55 deletions(-) rename src/{modules/supply/enums/change-supply-range.enum.ts => core/enums/range.enum.ts} (54%) delete mode 100644 src/modules/price/enums/price-range.enum.ts delete mode 100644 src/modules/supply/enums/historical-supply-range.enum.ts create mode 100644 src/modules/supply/schemas/growth-range.schema.ts diff --git a/src/modules/supply/enums/change-supply-range.enum.ts b/src/core/enums/range.enum.ts similarity index 54% rename from src/modules/supply/enums/change-supply-range.enum.ts rename to src/core/enums/range.enum.ts index 97f8a0a..6ed6d15 100644 --- a/src/modules/supply/enums/change-supply-range.enum.ts +++ b/src/core/enums/range.enum.ts @@ -1,8 +1,11 @@ -export enum ChangeSupplyRange { +export enum Range { + ALL = 'all', FIVE_MIN = 'fiveMin', HOUR = 'hour', DAY = 'day', WEEK = 'week', MONTH = 'month', + THREE_MONTH = 'threeMonth', + YEAR = 'year', } \ No newline at end of file diff --git a/src/modules/price/enums/price-range.enum.ts b/src/modules/price/enums/price-range.enum.ts deleted file mode 100644 index 545e767..0000000 --- a/src/modules/price/enums/price-range.enum.ts +++ /dev/null @@ -1,8 +0,0 @@ -export enum PriceRange { - ALL = 'all', - DAY = 'day', - WEEK = 'week', - MONTH = 'month', - THREE_MONTH = 'threeMonth', - YEAR = 'year', -} diff --git a/src/modules/price/price.controller.ts b/src/modules/price/price.controller.ts index 856df4a..176713f 100644 --- a/src/modules/price/price.controller.ts +++ b/src/modules/price/price.controller.ts @@ -2,9 +2,9 @@ import { Controller, Get, Query } from '@nestjs/common'; import { Routes } from '@core/enums/routes.enum'; import { SchemaValidatePipe } from '@core/pipes/schema-validate.pipe'; +import { Range } from '@core/enums/range.enum'; import { QueryParam } from './enums/query-param.enum'; -import { PriceRange } from './enums/price-range.enum'; import { RangeSchema } from './schemas/range.schema'; import { PriceCache } from './services/price.cache'; import { PriceEndpoints } from './enums/price-endpoints.enum'; @@ -16,7 +16,7 @@ export class PriceController { @Get(PriceEndpoints.HISTORICAL) async getHistoricalPrice( @Query(QueryParam.RANGE, new SchemaValidatePipe(RangeSchema)) - range: PriceRange, + range: Range, ) { return this.cache.getCacheByRange(range); } diff --git a/src/modules/price/schemas/range.schema.ts b/src/modules/price/schemas/range.schema.ts index 052cfff..c69442a 100644 --- a/src/modules/price/schemas/range.schema.ts +++ b/src/modules/price/schemas/range.schema.ts @@ -1,7 +1,7 @@ import * as Joi from 'joi'; -import { PriceRange } from '../enums/price-range.enum'; +import { Range } from '@core/enums/range.enum'; export const RangeSchema = Joi.string() - .valid(...Object.values(PriceRange)) + .valid(Range.ALL, Range.DAY, Range.MONTH, Range.THREE_MONTH, Range.WEEK, Range.YEAR) .required(); diff --git a/src/modules/price/services/price.cache.ts b/src/modules/price/services/price.cache.ts index 2a6f238..a2b8a37 100644 --- a/src/modules/price/services/price.cache.ts +++ b/src/modules/price/services/price.cache.ts @@ -8,7 +8,7 @@ import { DBOrder } from '@core/enums/db-order.enum'; import { HistoricalPrice } from '../dtos/historical-price.dto'; import { TimeBucketDto } from '../dtos/time-bucket.dto'; -import { PriceRange } from '../enums/price-range.enum'; +import { Range } from '@core/enums/range.enum'; @Injectable() export class PriceCache implements OnModuleInit { @@ -23,7 +23,7 @@ export class PriceCache implements OnModuleInit { await this.init(); } - async getCacheByRange(range: PriceRange): Promise { + async getCacheByRange(range: Range): Promise { const serializedCache = await this.cacheService.get(this.createRedisKey(range)); return JSON.parse(serializedCache as string); } @@ -54,32 +54,32 @@ export class PriceCache implements OnModuleInit { async initAllCache() { const allBucket = await this.timeBucket(DBTimeInterval.MONTH, DBOrder.ASC); - this.cacheService.set(this.createRedisKey(PriceRange.ALL), JSON.stringify(allBucket)); + this.cacheService.set(this.createRedisKey(Range.ALL), JSON.stringify(allBucket)); } async initDayCache() { const dayBucket = await this.timeBucket(DBTimeInterval.TWO_HOUR, DBOrder.ASC, 12); - this.cacheService.set(this.createRedisKey(PriceRange.DAY), JSON.stringify(dayBucket)); + this.cacheService.set(this.createRedisKey(Range.DAY), JSON.stringify(dayBucket)); } async initWeekCache() { const weekBucket = await this.timeBucket(DBTimeInterval.SIX_HOUR, DBOrder.ASC, 28); - this.cacheService.set(this.createRedisKey(PriceRange.WEEK), JSON.stringify(weekBucket)); + this.cacheService.set(this.createRedisKey(Range.WEEK), JSON.stringify(weekBucket)); } async initMonthCache() { const monthBucket = await this.timeBucket(DBTimeInterval.DAY, DBOrder.ASC, 30); - this.cacheService.set(this.createRedisKey(PriceRange.MONTH), JSON.stringify(monthBucket)); + this.cacheService.set(this.createRedisKey(Range.MONTH), JSON.stringify(monthBucket)); } async initThreeMonthCache() { const threeMonthBucket = await this.timeBucket(DBTimeInterval.THREE_DAY, DBOrder.ASC, 30); - this.cacheService.set(this.createRedisKey(PriceRange.THREE_MONTH), JSON.stringify(threeMonthBucket)); + this.cacheService.set(this.createRedisKey(Range.THREE_MONTH), JSON.stringify(threeMonthBucket)); } async initYearCache() { const yearBucket = await this.timeBucket(DBTimeInterval.MONTH, DBOrder.DESC, 12); - this.cacheService.set(this.createRedisKey(PriceRange.YEAR), JSON.stringify(yearBucket)); + this.cacheService.set(this.createRedisKey(Range.YEAR), JSON.stringify(yearBucket)); } async init() { @@ -93,7 +93,7 @@ export class PriceCache implements OnModuleInit { ]); } - private createRedisKey(range: PriceRange) { + private createRedisKey(range: Range) { return `${this.redisPricePrefix}_${range}`; } } diff --git a/src/modules/supply/enums/historical-supply-range.enum.ts b/src/modules/supply/enums/historical-supply-range.enum.ts deleted file mode 100644 index a2adaf5..0000000 --- a/src/modules/supply/enums/historical-supply-range.enum.ts +++ /dev/null @@ -1,7 +0,0 @@ -export enum HistoricalSupplyRange { - ALL = 'all', - DAY = 'day', - WEEK = 'week', - MONTH = 'month', -} - \ No newline at end of file diff --git a/src/modules/supply/enums/supply-endpoints.enum.ts b/src/modules/supply/enums/supply-endpoints.enum.ts index 85c5ecc..ef7af66 100644 --- a/src/modules/supply/enums/supply-endpoints.enum.ts +++ b/src/modules/supply/enums/supply-endpoints.enum.ts @@ -1,4 +1,5 @@ export enum SupplyEndpoints { HISTORICAL = '/historical', CHANGE = '/change', + GROWTH = '/growth', } \ No newline at end of file diff --git a/src/modules/supply/schemas/change-range.schema.ts b/src/modules/supply/schemas/change-range.schema.ts index 9b30cea..d49da3e 100644 --- a/src/modules/supply/schemas/change-range.schema.ts +++ b/src/modules/supply/schemas/change-range.schema.ts @@ -1,6 +1,6 @@ +import { Range } from '@core/enums/range.enum'; import * as Joi from 'joi'; -import { ChangeSupplyRange } from '../enums/change-supply-range.enum'; export const ChangeRangeSchema = Joi.string() - .valid(...Object.values(ChangeSupplyRange)) + .valid(Range.FIVE_MIN, Range.HOUR, Range.DAY, Range.WEEK, Range.MONTH) .required(); \ No newline at end of file diff --git a/src/modules/supply/schemas/growth-range.schema.ts b/src/modules/supply/schemas/growth-range.schema.ts new file mode 100644 index 0000000..35dd7a0 --- /dev/null +++ b/src/modules/supply/schemas/growth-range.schema.ts @@ -0,0 +1,4 @@ +import { Range } from '@core/enums/range.enum'; +import * as Joi from 'joi'; + +export const GrowthRangeSchema = Joi.string().valid(Range.FIVE_MIN, Range.HOUR, Range.DAY, Range.WEEK, Range.MONTH).required(); \ No newline at end of file diff --git a/src/modules/supply/schemas/historical-range.schema.ts b/src/modules/supply/schemas/historical-range.schema.ts index 113a389..b52b001 100644 --- a/src/modules/supply/schemas/historical-range.schema.ts +++ b/src/modules/supply/schemas/historical-range.schema.ts @@ -1,6 +1,6 @@ +import { Range } from '@core/enums/range.enum'; import * as Joi from 'joi'; -import { HistoricalSupplyRange } from '../enums/historical-supply-range.enum'; export const HistoricalRangeSchema = Joi.string() - .valid(...Object.values(HistoricalSupplyRange)) + .valid(Range.ALL, Range.DAY, Range.WEEK, Range.MONTH) .required(); \ No newline at end of file diff --git a/src/modules/supply/services/supply.cache.ts b/src/modules/supply/services/supply.cache.ts index bc2b13c..269ff1b 100644 --- a/src/modules/supply/services/supply.cache.ts +++ b/src/modules/supply/services/supply.cache.ts @@ -6,9 +6,9 @@ import { DBOrder } from "@core/enums/db-order.enum"; import { DBTimeInterval } from "@core/enums/db-time-interval.enum"; import { PrismaService } from "@core/lib/prisma.service"; -import { HistoricalSupplyRange } from "../enums/historical-supply-range.enum"; import { TimeBucketDto } from "../dtos/time-bucket.dto"; import { ChangeIntervalDto } from "../dtos/change-interval.dto"; +import { Range } from "@core/enums/range.enum"; @Injectable() export class SupplyCache { @@ -23,7 +23,7 @@ export class SupplyCache { await this.init(); } - async getCacheByRange(range: HistoricalSupplyRange): Promise { + async getCacheByRange(range: Range): Promise { const serializedCache = await this.cacheService.get(this.createRedisKey(range)); return JSON.parse(serializedCache as string); } @@ -54,22 +54,22 @@ export class SupplyCache { async initAllCache() { const allBucket = await this.timeBucket(DBTimeInterval.MONTH, DBOrder.ASC); - this.cacheService.set(this.createRedisKey(HistoricalSupplyRange.ALL), JSON.stringify(allBucket)); + this.cacheService.set(this.createRedisKey(Range.ALL), JSON.stringify(allBucket)); } async initDayCache() { const dayBucket = await this.timeBucket(DBTimeInterval.TWO_HOUR, DBOrder.ASC, 12); - this.cacheService.set(this.createRedisKey(HistoricalSupplyRange.DAY), JSON.stringify(dayBucket)); + this.cacheService.set(this.createRedisKey(Range.DAY), JSON.stringify(dayBucket)); } async initWeekCache() { const weekBucket = await this.timeBucket(DBTimeInterval.SIX_HOUR, DBOrder.ASC, 28); - this.cacheService.set(this.createRedisKey(HistoricalSupplyRange.WEEK), JSON.stringify(weekBucket)); + this.cacheService.set(this.createRedisKey(Range.WEEK), JSON.stringify(weekBucket)); } async initMonthCache() { const monthBucket = await this.timeBucket(DBTimeInterval.DAY, DBOrder.ASC, 30); - this.cacheService.set(this.createRedisKey(HistoricalSupplyRange.MONTH), JSON.stringify(monthBucket)); + this.cacheService.set(this.createRedisKey(Range.MONTH), JSON.stringify(monthBucket)); } async init() { @@ -81,7 +81,7 @@ export class SupplyCache { ]); } - private createRedisKey(range: HistoricalSupplyRange) { + private createRedisKey(range: Range) { return `${this.redisSupplyPrefix}_${range}`; } } \ No newline at end of file diff --git a/src/modules/supply/services/supply.service.ts b/src/modules/supply/services/supply.service.ts index d9e4c10..0cab18b 100644 --- a/src/modules/supply/services/supply.service.ts +++ b/src/modules/supply/services/supply.service.ts @@ -7,7 +7,7 @@ import { Okp4Service } from "@core/lib/okp4/okp4.service"; import { PrismaService } from "@core/lib/prisma.service"; import { CurrentSupplyDto } from "../dtos/current-supply.dto"; -import { ChangeSupplyRange } from "../enums/change-supply-range.enum"; +import { Range } from "@core/enums/range.enum"; @Injectable() export class SupplyService { @@ -63,14 +63,14 @@ export class SupplyService { return change; } - async getSupplyChange(range: ChangeSupplyRange) { - const previousSupply = await this.getSupplyForChangeByRange(range); + async getSupplyChange(range: Range) { + const previousSupply = await this.getPastSupplyByRange(range); const currentSupply = await this.getSupplyByOrder(); if (previousSupply && currentSupply) return Big(currentSupply.supply).minus(previousSupply.supply); } - private async getSupplyForChangeByRange(range: ChangeSupplyRange) { - const dateByRange = this.createDateForChangeSupply(range); + private async getPastSupplyByRange(range: Range) { + const dateByRange = this.calculatePastDateByRange(range); const supply = await this.prismaService.historicalSupply.findFirst({ where: { time: { @@ -89,16 +89,36 @@ export class SupplyService { return supply; } - private createDateForChangeSupply(range: ChangeSupplyRange): Date { + private calculatePastDateByRange(range: Range): Date { let date = new Date(); switch (range) { - case ChangeSupplyRange.FIVE_MIN: date = new Date(date.setMinutes(date.getMinutes() - 5)); break; - case ChangeSupplyRange.HOUR: date = new Date(date.setHours(date.getHours() - 1)); break; - case ChangeSupplyRange.DAY: date = new Date(date.setDate(date.getDate() - 1)); break; - case ChangeSupplyRange.WEEK: date = new Date(date.setDate(date.getDate() - 7)); break; - case ChangeSupplyRange.MONTH: date = new Date(date.setMonth(date.getMonth() - 1)); break; + case Range.FIVE_MIN: date = new Date(date.setMinutes(date.getMinutes() - 5)); break; + case Range.HOUR: date = new Date(date.setHours(date.getHours() - 1)); break; + case Range.DAY: date = new Date(date.setDate(date.getDate() - 1)); break; + case Range.WEEK: date = new Date(date.setDate(date.getDate() - 7)); break; + case Range.MONTH: date = new Date(date.setMonth(date.getMonth() - 1)); break; } return date; } + + async getSupplyGrowth(range: Range) { + const pastDate = this.calculatePastDateByRange(range); + const supplyChangeByPeriod = await this.prismaService.historicalSupply.aggregate({ + where: { + time: { + gte: pastDate, + } + }, + _sum: { + change: true, + } + }); + + if (supplyChangeByPeriod._sum.change) { + return Big(supplyChangeByPeriod._sum.change).toFixed(2); + } + + return 0; + } } \ No newline at end of file diff --git a/src/modules/supply/supply.controller.ts b/src/modules/supply/supply.controller.ts index 96f0fc8..c059a60 100644 --- a/src/modules/supply/supply.controller.ts +++ b/src/modules/supply/supply.controller.ts @@ -5,10 +5,10 @@ import { SupplyCache } from "./services/supply.cache"; import { QueryParam } from "./enums/query-param.enum"; import { SchemaValidatePipe } from "@core/pipes/schema-validate.pipe"; import { HistoricalRangeSchema } from "./schemas/historical-range.schema"; -import { HistoricalSupplyRange } from "./enums/historical-supply-range.enum"; import { SupplyService } from "./services/supply.service"; import { ChangeRangeSchema } from "./schemas/change-range.schema"; -import { ChangeSupplyRange } from "./enums/change-supply-range.enum"; +import { Range } from "@core/enums/range.enum"; +import { GrowthRangeSchema } from "./schemas/growth-range.schema"; @Controller(Routes.SUPPLY) export class SupplyController { @@ -20,7 +20,7 @@ export class SupplyController { @Get(SupplyEndpoints.HISTORICAL) async getHistoricalSupply( @Query(QueryParam.RANGE, new SchemaValidatePipe(HistoricalRangeSchema)) - range: HistoricalSupplyRange, + range: Range, ) { return this.cache.getCacheByRange(range); } @@ -33,8 +33,16 @@ export class SupplyController { @Get(SupplyEndpoints.CHANGE) async getSupplyChange( @Query(QueryParam.RANGE, new SchemaValidatePipe(ChangeRangeSchema)) - range: ChangeSupplyRange, + range: Range, ) { return this.service.getSupplyChange(range); } + + @Get(SupplyEndpoints.GROWTH) + async getSupplyGrowth( + @Query(QueryParam.RANGE, new SchemaValidatePipe(GrowthRangeSchema)) + range: Range, + ) { + return this.service.getSupplyGrowth(range); + } } \ No newline at end of file