@accounter/server
Version:
Accounter GraphQL server
138 lines • 5.88 kB
JavaScript
import { __decorate, __metadata } from "tslib";
import DataLoader from 'dataloader';
import { format, subHours } from 'date-fns';
import { GraphQLError } from 'graphql';
import { Injectable, Scope } from 'graphql-modules';
import { sql } from '@pgtyped/runtime';
import { AdminContextProvider } from '../../admin-context/providers/admin-context.provider.js';
import { CoinMarketCapProvider } from '../../app-providers/coinmarketcap.js';
import { TenantAwareDBClient } from '../../app-providers/tenant-db-client.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 {
db;
adminContextProvider;
coinMarketCap;
constructor(db, adminContextProvider, coinMarketCap) {
this.db = db;
this.adminContextProvider = adminContextProvider;
this.coinMarketCap = coinMarketCap;
}
async getCryptoExchangeRatesFromDB(currency, date) {
return getRateByCurrencyAndDate.run({ currency, date }, this.db);
}
async addRates(rates) {
this.clearCache();
return insertRates.run({ rates }, this.db);
}
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.db);
return symbols.map(symbol => currencies.find(currency => currency.symbol === symbol));
}
getCryptoCurrenciesBySymbolLoader = new DataLoader((symbols) => this.getCryptoCurrenciesBySymbol(symbols));
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);
const { defaultCryptoConversionFiatCurrency } = await this.adminContextProvider.getVerifiedAdminContext();
// Add rate to DB
await this.addCryptoRateLoader.load({
date,
currency: currencySymbol,
value: rate,
against: defaultCryptoConversionFiatCurrency,
sampleDate,
});
return rate;
}
getCryptoExchangeRateLoader = new DataLoader(async (keys) => {
const { defaultCryptoConversionFiatCurrency } = await this.adminContextProvider.getVerifiedAdminContext();
const rates = await Promise.all(keys.map(async ({ cryptoCurrency, date, against = defaultCryptoConversionFiatCurrency }) => {
// 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 }) => `${cryptoCurrency}-${date.getTime()}-${against}`,
});
clearCache() {
this.getCryptoCurrenciesBySymbolLoader.clearAll();
this.getCryptoExchangeRateLoader.clearAll();
}
};
CryptoExchangeProvider = __decorate([
Injectable({
scope: Scope.Operation,
global: true,
}),
__metadata("design:paramtypes", [TenantAwareDBClient,
AdminContextProvider,
CoinMarketCapProvider])
], CryptoExchangeProvider);
export { CryptoExchangeProvider };
//# sourceMappingURL=crypto-exchange.provider.js.map