UNPKG

@accounter/server

Version:

The test suite is split into three Vitest projects for efficiency and isolation:

144 lines • 5.86 kB
import { __decorate, __metadata, __param } from "tslib"; import DataLoader from 'dataloader'; import { format, subHours } from 'date-fns'; import { GraphQLError } from 'graphql'; import { CONTEXT, Inject, Injectable, Scope } from 'graphql-modules'; import { sql } from '@pgtyped/runtime'; import { getCacheInstance } from '../../../shared/helpers/index.js'; import { CoinMarketCapProvider } from '../../app-providers/coinmarketcap.js'; import { DBProvider } from '../../app-providers/db.provider.js'; const getRateByCurrencyAndDate = sql ` SELECT * FROM accounter_schema.crypto_exchange_rates WHERE coin_symbol = $currency AND date = $date;`; const insertRates = sql ` INSERT INTO accounter_schema.crypto_exchange_rates (date, coin_symbol, value, against, sample_date) VALUES $$rates(date, currency, value, against, sampleDate) ON CONFLICT (date, coin_symbol, against) DO UPDATE SET value = EXCLUDED.value RETURNING *; `; const getCryptoCurrenciesBySymbol = sql ` SELECT * FROM accounter_schema.crypto_currencies WHERE symbol in $$currencySymbols;`; let CryptoExchangeProvider = class CryptoExchangeProvider { context; dbProvider; coinMarketCap; cache = getCacheInstance({ stdTTL: 60 * 60 * 24, // 24 hours }); fiatCurrency; constructor(context, dbProvider, coinMarketCap) { this.context = context; this.dbProvider = dbProvider; this.coinMarketCap = coinMarketCap; this.fiatCurrency = this.context.adminContext.defaultCryptoConversionFiatCurrency; } async getCryptoExchangeRatesFromDB(currency, date) { return getRateByCurrencyAndDate.run({ currency, date }, this.dbProvider); } async addRates(rates) { this.clearCache(); return insertRates.run({ rates }, this.dbProvider); } addCryptoRateLoader = new DataLoader((keys) => this.addRates(keys.map(rate => ({ ...rate, against: rate.against ?? null }))), { cache: false, }); async getCryptoCurrenciesBySymbol(symbols) { const currencies = await getCryptoCurrenciesBySymbol.run({ currencySymbols: symbols, }, this.dbProvider); return symbols.map(symbol => currencies.find(currency => currency.symbol === symbol)); } getCryptoCurrenciesBySymbolLoader = new DataLoader((symbols) => this.getCryptoCurrenciesBySymbol(symbols), { cacheKeyFn: key => `crypto-currencies-by-symbol-${key}`, cacheMap: this.cache, }); async getCryptoExchangeRatesFromAPI(currencySymbol, date) { // Fetch CoinMarketCap id from DB const currencyInfo = await this.getCryptoCurrenciesBySymbolLoader.load(currencySymbol); if (!currencyInfo) { throw new GraphQLError(`No data found for crypto currency ${currencySymbol}`); } const coinmarketcapId = currencyInfo?.coinmarketcap_id; if (!coinmarketcapId) { throw new GraphQLError(`No CoinMarketCap id found for crypto currency ${currencySymbol}`); } const fromDate = subHours(date, 23); const from = Math.floor(fromDate.getTime() / 1000); const to = Math.floor(date.getTime() / 1000); const ratesObject = await this.coinMarketCap.getExchangeRates(coinmarketcapId, { fromTimeStamp: from, toTimeStamp: to, }); let timestamp = undefined; let rate = undefined; for (const [rawTimestamp, rateData] of Object.entries(ratesObject)) { const newTimestamp = Number(rawTimestamp); if (newTimestamp <= to && rateData?.c?.[0]) { if (!timestamp || !rate) { timestamp = newTimestamp; rate = rateData.c[0]; } if (newTimestamp > timestamp) { timestamp = newTimestamp; rate = rateData.c[0]; } } } if (!rate || !timestamp) { throw new GraphQLError(`No suitable rate of ${currencySymbol} found in CoinMarketCap`); } const sampleDate = new Date(timestamp * 1000); // Add rate to DB await this.addCryptoRateLoader.load({ date, currency: currencySymbol, value: rate, against: this.fiatCurrency, sampleDate, }); return rate; } getCryptoExchangeRateLoader = new DataLoader(async (keys) => { const rates = await Promise.all(keys.map(async ({ cryptoCurrency, date, against = this.fiatCurrency }) => { // Fetch from DB first const res = await this.getCryptoExchangeRatesFromDB(cryptoCurrency, date); if (res.length > 0) { return res[0]; } // If not found in DB, fetch from API const rate = await this.getCryptoExchangeRatesFromAPI(cryptoCurrency, date); if (rate == null) { return new GraphQLError(`No data found for ${cryptoCurrency} on ${format(date, 'dd-MM-yyyy')}`); } return { date, coin_symbol: cryptoCurrency, value: rate.toString(), against, }; })); return rates; }, { cacheKeyFn: ({ cryptoCurrency, date, against = this.fiatCurrency }) => `${cryptoCurrency}-${date.getTime()}-${against}`, cacheMap: this.cache, }); clearCache() { this.cache.clear(); } }; CryptoExchangeProvider = __decorate([ Injectable({ scope: Scope.Operation, global: true, }), __param(0, Inject(CONTEXT)), __metadata("design:paramtypes", [Object, DBProvider, CoinMarketCapProvider]) ], CryptoExchangeProvider); export { CryptoExchangeProvider }; //# sourceMappingURL=crypto-exchange.provider.js.map