From e609f755c7bb16ed1df9778b0ea17f3f705f76b6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jos=C3=A9=20Oliveira?= Date: Thu, 24 Mar 2022 17:57:01 +0000 Subject: [PATCH 01/26] fix: update db upon empty network metrics --- lib/machine-loader.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/machine-loader.js b/lib/machine-loader.js index ac6853e2..ec1f48a2 100644 --- a/lib/machine-loader.js +++ b/lib/machine-loader.js @@ -209,6 +209,7 @@ function setMachine (rec, operatorId) { } function updateNetworkPerformance (deviceId, data) { + if (_.isEmpty(data)) return Promise.resolve(true) const downloadSpeed = _.head(data) const dbData = { device_id: deviceId, @@ -224,6 +225,7 @@ function updateNetworkPerformance (deviceId, data) { } function updateNetworkHeartbeat (deviceId, data) { + if (_.isEmpty(data)) return Promise.resolve(true) const avgResponseTime = _.meanBy(e => _.toNumber(e.averageResponseTime), data) const avgPacketLoss = _.meanBy(e => _.toNumber(e.packetLoss), data) const dbData = { From bc8335adee60f43ed19faeefd9210247a1f97e55 Mon Sep 17 00:00:00 2001 From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com> Date: Thu, 14 Apr 2022 19:07:26 +0200 Subject: [PATCH 02/26] fix: date picker --- .../components/date-range-picker/Calendar.js | 4 +-- .../date-range-picker/DateRangePicker.js | 30 +++++++------------ 2 files changed, 11 insertions(+), 23 deletions(-) diff --git a/new-lamassu-admin/src/components/date-range-picker/Calendar.js b/new-lamassu-admin/src/components/date-range-picker/Calendar.js index c6151d73..c0480530 100644 --- a/new-lamassu-admin/src/components/date-range-picker/Calendar.js +++ b/new-lamassu-admin/src/components/date-range-picker/Calendar.js @@ -178,9 +178,7 @@ const Calendar = ({ minDate, maxDate, handleSelect, ...props }) => { {R.range(1, 8).map((row, key) => ( {getRow(currentDisplayedMonth, row).map((day, key) => ( - handleSelect(day, minDate, maxDate)}> + handleSelect(day)}> { ) return - if (from && !to && differenceInDays(day, from) > 0) { - setTo(from) - setFrom(day) - return - } - - if ( - from && - !to && - (isSameMonth(from, day) || differenceInMonths(from, day) > 0) - ) { - setTo( - set({ hours: 23, minutes: 59, seconds: 59, milliseconds: 999 }, day) - ) + if (from && !to) { + if (differenceInDays(from, day) >= 0) { + setTo( + set({ hours: 23, minutes: 59, seconds: 59, milliseconds: 999 }, day) + ) + } else { + setTo(from) + setFrom(day) + } return } From fea170e141a312af012799b45598ffed73876b59 Mon Sep 17 00:00:00 2001 From: Nikola Ubavic <53820106+ubavic@users.noreply.github.com> Date: Thu, 14 Apr 2022 19:26:23 +0200 Subject: [PATCH 03/26] fix: max date --- .../src/components/LogsDownloaderPopper.js | 12 ++++++++++-- .../components/date-range-picker/DateRangePicker.js | 6 +++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/new-lamassu-admin/src/components/LogsDownloaderPopper.js b/new-lamassu-admin/src/components/LogsDownloaderPopper.js index 5d879fe7..9c348810 100644 --- a/new-lamassu-admin/src/components/LogsDownloaderPopper.js +++ b/new-lamassu-admin/src/components/LogsDownloaderPopper.js @@ -1,7 +1,7 @@ import { useLazyQuery } from '@apollo/react-hooks' import { makeStyles, ClickAwayListener } from '@material-ui/core' import classnames from 'classnames' -import { format } from 'date-fns/fp' +import { format, set } from 'date-fns/fp' import FileSaver from 'file-saver' import * as R from 'ramda' import React, { useState, useCallback } from 'react' @@ -280,7 +280,15 @@ const LogsDownloaderPopover = ({ )} diff --git a/new-lamassu-admin/src/components/date-range-picker/DateRangePicker.js b/new-lamassu-admin/src/components/date-range-picker/DateRangePicker.js index b24e6622..7552c5d4 100644 --- a/new-lamassu-admin/src/components/date-range-picker/DateRangePicker.js +++ b/new-lamassu-admin/src/components/date-range-picker/DateRangePicker.js @@ -1,6 +1,6 @@ import { makeStyles } from '@material-ui/core/styles' import classnames from 'classnames' -import { differenceInDays, set } from 'date-fns/fp' +import { compareAsc, differenceInDays, set } from 'date-fns/fp' import React, { useState, useEffect } from 'react' import Calendar from './Calendar' @@ -24,9 +24,9 @@ const DateRangePicker = ({ minDate, maxDate, className, onRangeChange }) => { const classes = useStyles() - const handleSelect = (day, minDate, maxDate) => { + const handleSelect = day => { if ( - (maxDate && differenceInDays(maxDate, day) > 0) || + (maxDate && compareAsc(maxDate, day) > 0) || (minDate && differenceInDays(day, minDate) > 0) ) return From 7ddda203647dd4f40237d024772763e987bb11b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 19 Apr 2022 12:43:11 +0100 Subject: [PATCH 04/26] refactor: use Unix fileformat --- lib/plugins/ticker/ccxt.js | 98 +++++++++++++++++++------------------- 1 file changed, 49 insertions(+), 49 deletions(-) diff --git a/lib/plugins/ticker/ccxt.js b/lib/plugins/ticker/ccxt.js index 4f74f811..6c495a1c 100644 --- a/lib/plugins/ticker/ccxt.js +++ b/lib/plugins/ticker/ccxt.js @@ -1,49 +1,49 @@ -const ccxt = require('ccxt') - -const BN = require('../../bn') -const { buildMarket, verifyFiatSupport } = require('../common/ccxt') -const { getRate } = require('../../../lib/forex') - -const RETRIES = 2 - -function ticker (fiatCode, cryptoCode, tickerName) { - const ticker = new ccxt[tickerName]({ timeout: 3000 }) - if (verifyFiatSupport(fiatCode, tickerName)) { - return getCurrencyRates(ticker, fiatCode, cryptoCode) - } - - return getRate(RETRIES, fiatCode) - .then(({ fxRate }) => { - try { - return getCurrencyRates(ticker, 'USD', cryptoCode) - .then(res => ({ - rates: { - ask: res.rates.ask.times(fxRate), - bid: res.rates.bid.times(fxRate) - } - })) - } catch (e) { - return Promise.reject(e) - } - }) -} - -function getCurrencyRates (ticker, fiatCode, cryptoCode) { - try { - if (!ticker.has['fetchTicker']) { - throw new Error('Ticker not available') - } - const symbol = buildMarket(fiatCode, cryptoCode, ticker.id) - return ticker.fetchTicker(symbol) - .then(res => ({ - rates: { - ask: new BN(res.ask), - bid: new BN(res.bid) - } - })) - } catch (e) { - return Promise.reject(e) - } -} - -module.exports = { ticker } +const ccxt = require('ccxt') + +const BN = require('../../bn') +const { buildMarket, verifyFiatSupport } = require('../common/ccxt') +const { getRate } = require('../../../lib/forex') + +const RETRIES = 2 + +function ticker (fiatCode, cryptoCode, tickerName) { + const ticker = new ccxt[tickerName]({ timeout: 3000 }) + if (verifyFiatSupport(fiatCode, tickerName)) { + return getCurrencyRates(ticker, fiatCode, cryptoCode) + } + + return getRate(RETRIES, fiatCode) + .then(({ fxRate }) => { + try { + return getCurrencyRates(ticker, 'USD', cryptoCode) + .then(res => ({ + rates: { + ask: res.rates.ask.times(fxRate), + bid: res.rates.bid.times(fxRate) + } + })) + } catch (e) { + return Promise.reject(e) + } + }) +} + +function getCurrencyRates (ticker, fiatCode, cryptoCode) { + try { + if (!ticker.has['fetchTicker']) { + throw new Error('Ticker not available') + } + const symbol = buildMarket(fiatCode, cryptoCode, ticker.id) + return ticker.fetchTicker(symbol) + .then(res => ({ + rates: { + ask: new BN(res.ask), + bid: new BN(res.bid) + } + })) + } catch (e) { + return Promise.reject(e) + } +} + +module.exports = { ticker } From 4747e0b7f69f9fcde29d523d3addd5a3763d5258 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 19 Apr 2022 12:43:54 +0100 Subject: [PATCH 05/26] refactor: save CCXT ticker instance --- lib/plugins/ticker/ccxt.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/plugins/ticker/ccxt.js b/lib/plugins/ticker/ccxt.js index 6c495a1c..09d8dbd7 100644 --- a/lib/plugins/ticker/ccxt.js +++ b/lib/plugins/ticker/ccxt.js @@ -6,8 +6,13 @@ const { getRate } = require('../../../lib/forex') const RETRIES = 2 +const tickerObjects = {} + function ticker (fiatCode, cryptoCode, tickerName) { - const ticker = new ccxt[tickerName]({ timeout: 3000 }) + const ticker = tickerObjects[tickerName] ? + tickerObjects[tickerName] : + tickerObjects[tickerName] = new ccxt[tickerName]({ timeout: 3000 }) + if (verifyFiatSupport(fiatCode, tickerName)) { return getCurrencyRates(ticker, fiatCode, cryptoCode) } From 6c2c6353aa7658bcf4ecf8134f06a35f6558f9a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 19 Apr 2022 13:32:47 +0100 Subject: [PATCH 06/26] feat: lower the ticker rate limit --- lib/plugins/ticker/ccxt.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/plugins/ticker/ccxt.js b/lib/plugins/ticker/ccxt.js index 09d8dbd7..154d7eb4 100644 --- a/lib/plugins/ticker/ccxt.js +++ b/lib/plugins/ticker/ccxt.js @@ -11,7 +11,11 @@ const tickerObjects = {} function ticker (fiatCode, cryptoCode, tickerName) { const ticker = tickerObjects[tickerName] ? tickerObjects[tickerName] : - tickerObjects[tickerName] = new ccxt[tickerName]({ timeout: 3000 }) + tickerObjects[tickerName] = new ccxt[tickerName]({ + timeout: 3000, + enableRateLimit: true, + rateLimit: 333, + }) if (verifyFiatSupport(fiatCode, tickerName)) { return getCurrencyRates(ticker, fiatCode, cryptoCode) From 396e7058f5b29bb7b6857bd22a7a09cf09726c00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 19 Apr 2022 14:20:09 +0100 Subject: [PATCH 07/26] refactor: disable CCXT's rate limitting --- lib/plugins/ticker/ccxt.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/plugins/ticker/ccxt.js b/lib/plugins/ticker/ccxt.js index 154d7eb4..410b24fa 100644 --- a/lib/plugins/ticker/ccxt.js +++ b/lib/plugins/ticker/ccxt.js @@ -13,8 +13,7 @@ function ticker (fiatCode, cryptoCode, tickerName) { tickerObjects[tickerName] : tickerObjects[tickerName] = new ccxt[tickerName]({ timeout: 3000, - enableRateLimit: true, - rateLimit: 333, + enableRateLimit: false, }) if (verifyFiatSupport(fiatCode, tickerName)) { From 2fe92c2ebf5dd9736d833312a0b6082fa572e684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 19 Apr 2022 15:38:13 +0100 Subject: [PATCH 08/26] feat: periodically update ticker rates --- lib/poller.js | 2 ++ lib/ticker.js | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/poller.js b/lib/poller.js index 99ea5543..41f1850d 100644 --- a/lib/poller.js +++ b/lib/poller.js @@ -33,6 +33,7 @@ const SANCTIONS_UPDATE_INTERVAL = 1 * T.day const RADAR_UPDATE_INTERVAL = 5 * T.minutes const PRUNE_MACHINES_HEARTBEAT = 1 * T.day const TRANSACTION_BATCH_LIFECYCLE = 20 * T.minutes +const TICKER_RATES_INTERVAL = 59 * T.seconds const CHECK_NOTIFICATION_INTERVAL = 20 * T.seconds const PENDING_INTERVAL = 10 * T.seconds @@ -178,6 +179,7 @@ function doPolling (schema) { notifier.checkNotification(pi()) updateCoinAtmRadar() + addToQueue(pi().getRawRates, TICKER_RATES_INTERVAL, schema, QUEUE.FAST) addToQueue(pi().executeTrades, TRADE_INTERVAL, schema, QUEUE.FAST) addToQueue(cashOutTx.monitorLiveIncoming, LIVE_INCOMING_TX_INTERVAL, schema, QUEUE.FAST, settings, false, coinFilter) addToQueue(cashOutTx.monitorStaleIncoming, INCOMING_TX_INTERVAL, schema, QUEUE.FAST, settings, false, coinFilter) diff --git a/lib/ticker.js b/lib/ticker.js index c032a079..79ff7737 100644 --- a/lib/ticker.js +++ b/lib/ticker.js @@ -7,7 +7,7 @@ const ccxt = require('./plugins/ticker/ccxt') const mockTicker = require('./plugins/ticker/mock-ticker') const bitpay = require('./plugins/ticker/bitpay') -const FETCH_INTERVAL = 60000 +const FETCH_INTERVAL = 58000 function _getRates (settings, fiatCode, cryptoCode) { return Promise.resolve() From ad5293522dc1cb30953a7f239e7316f2dfdf676e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 26 Apr 2022 10:06:36 +0100 Subject: [PATCH 09/26] refactor: use a more idiomatic conditional set --- lib/plugins/ticker/ccxt.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/plugins/ticker/ccxt.js b/lib/plugins/ticker/ccxt.js index 410b24fa..080b2f18 100644 --- a/lib/plugins/ticker/ccxt.js +++ b/lib/plugins/ticker/ccxt.js @@ -9,12 +9,14 @@ const RETRIES = 2 const tickerObjects = {} function ticker (fiatCode, cryptoCode, tickerName) { - const ticker = tickerObjects[tickerName] ? - tickerObjects[tickerName] : + if (!tickerObjects[tickerName]) { tickerObjects[tickerName] = new ccxt[tickerName]({ timeout: 3000, enableRateLimit: false, }) + } + + const ticker = tickerObjects[tickerName] if (verifyFiatSupport(fiatCode, tickerName)) { return getCurrencyRates(ticker, fiatCode, cryptoCode) From 0a833fa11dba53f61143bcccf7be656d5a72e062 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 16:58:52 +0100 Subject: [PATCH 10/26] refactor: remove old server --- lib/app.js | 6 ------ 1 file changed, 6 deletions(-) diff --git a/lib/app.js b/lib/app.js index 72394605..7692ce2f 100644 --- a/lib/app.js +++ b/lib/app.js @@ -77,8 +77,6 @@ function startServer (settings) { : https.createServer(httpsServerOptions, routes.app) const port = argv.port || 3000 - const localPort = 3030 - const localServer = http.createServer(routes.localApp) if (options.devMode) logger.info('In dev mode') @@ -86,10 +84,6 @@ function startServer (settings) { logger.info('lamassu-server listening on port ' + port + ' ' + (devMode ? '(http)' : '(https)')) }) - - localServer.listen(localPort, 'localhost', () => { - logger.info('lamassu-server listening on local port ' + localPort) - }) }) } From 5256c7ea4b4bd1899fed610dc9abc990d8c07734 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 16:59:18 +0100 Subject: [PATCH 11/26] refactor: drop unnecessary imports --- lib/new-admin/admin-server.js | 3 +-- lib/new-config-manager.js | 1 - 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/new-admin/admin-server.js b/lib/new-admin/admin-server.js index 53f86ccc..550d71ed 100644 --- a/lib/new-admin/admin-server.js +++ b/lib/new-admin/admin-server.js @@ -8,13 +8,12 @@ const cors = require('cors') const helmet = require('helmet') const nocache = require('nocache') const cookieParser = require('cookie-parser') -const { ApolloServer, AuthenticationError } = require('apollo-server-express') +const { ApolloServer } = require('apollo-server-express') const { graphqlUploadExpress } = require('graphql-upload') const _ = require('lodash/fp') const { asyncLocalStorage, defaultStore } = require('../async-storage') const options = require('../options') -const users = require('../users') const logger = require('../logger') const { AuthDirective } = require('./graphql/directives') diff --git a/lib/new-config-manager.js b/lib/new-config-manager.js index 4c05b540..8951b2a4 100644 --- a/lib/new-config-manager.js +++ b/lib/new-config-manager.js @@ -48,7 +48,6 @@ const getCommissions = (cryptoCode, deviceId, config) => { const getLocale = (deviceId, it) => { const locale = fromNamespace(namespaces.LOCALE)(it) - const filter = _.matches({ machine: deviceId }) return _.omit('overrides', _.assignAll([locale, ..._.filter(filter)(locale.overrides)])) } From 3fe217c5ed0d18dbb0854963538814fca07aef05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 17:01:49 +0100 Subject: [PATCH 12/26] refactor: drop unused constant --- lib/plugins.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/plugins.js b/lib/plugins.js index 80bbcc9b..3080f681 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -39,7 +39,6 @@ const mapValuesWithKey = _.mapValues.convert({ const TRADE_TTL = 2 * T.minutes const STALE_TICKER = 3 * T.minutes const STALE_BALANCE = 3 * T.minutes -const PONG_TTL = '1 week' const tradesQueues = {} function plugins (settings, deviceId) { From 4a6da1b88884ffd6701cc5b60d51e9c790b3bf5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 17:28:34 +0100 Subject: [PATCH 13/26] refactor: drop unused parameter --- lib/plugins.js | 2 +- lib/routes/pollingRoutes.js | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index 3080f681..c06da71a 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -227,7 +227,7 @@ function plugins (settings, deviceId) { } } - function pollQueries (serialNumber, deviceTime, deviceRec, machineVersion, machineModel) { + function pollQueries (deviceTime, deviceRec, machineVersion, machineModel) { const localeConfig = configManager.getLocale(deviceId, settings.config) const fiatCode = localeConfig.fiatCurrency const cryptoCodes = localeConfig.cryptoCurrencies diff --git a/lib/routes/pollingRoutes.js b/lib/routes/pollingRoutes.js index a2ca4847..b43008bb 100644 --- a/lib/routes/pollingRoutes.js +++ b/lib/routes/pollingRoutes.js @@ -61,7 +61,6 @@ function poll (req, res, next) { const machineModel = req.query.model const deviceId = req.deviceId const deviceTime = req.deviceTime - const serialNumber = req.query.sn const pid = req.query.pid const settings = req.settings const operatorId = res.locals.operatorId @@ -85,7 +84,7 @@ function poll (req, res, next) { state.pids = _.update(operatorId, _.set(deviceId, { pid, ts: Date.now() }), state.pids) - return Promise.all([pi.pollQueries(serialNumber, deviceTime, req.query, machineVersion, machineModel), triggersPromise, triggersAutomationPromise]) + return Promise.all([pi.pollQueries(deviceTime, req.query, machineVersion, machineModel), triggersPromise, triggersAutomationPromise]) .then(([results, triggers, triggersAutomation]) => { const cassettes = results.cassettes From 6a9643beb864f9dea8ee519f75124df293c41300 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 17:30:18 +0100 Subject: [PATCH 14/26] fix: always run the `authorize` middleware --- lib/routes.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/routes.js b/lib/routes.js index 22b72bf2..6f727b02 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -56,7 +56,7 @@ app.use('/', pairingRoutes) app.use(findOperatorId) app.use(populateDeviceId) app.use(computeSchema) -if (!devMode) app.use(authorize) +app.use(authorize) app.use(configRequiredRoutes, populateSettings) app.use(filterOldRequests) From b74bd2ce1424ef788e33edfab49fe58580a1553a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 17:31:28 +0100 Subject: [PATCH 15/26] refactor: remove unnecessary assignment --- lib/routes/pollingRoutes.js | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/routes/pollingRoutes.js b/lib/routes/pollingRoutes.js index b43008bb..2e470650 100644 --- a/lib/routes/pollingRoutes.js +++ b/lib/routes/pollingRoutes.js @@ -86,8 +86,6 @@ function poll (req, res, next) { return Promise.all([pi.pollQueries(deviceTime, req.query, machineVersion, machineModel), triggersPromise, triggersAutomationPromise]) .then(([results, triggers, triggersAutomation]) => { - const cassettes = results.cassettes - const reboot = pid && state.reboots?.[operatorId]?.[deviceId] === pid const shutdown = pid && state.shutdowns?.[operatorId]?.[deviceId] === pid const restartServices = pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid @@ -109,7 +107,6 @@ function poll (req, res, next) { receiptPrintingActive: receipt.active, smsReceiptActive: receipt.sms, enablePaperWalletOnly, - cassettes, twoWayMode: cashOutConfig.active, zeroConfLimits, reboot, From ac7cb243af33c46ce36872713b12e9632500f7a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 17:31:52 +0100 Subject: [PATCH 16/26] refactor: use array destructuring instead of indexing --- lib/plugins.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index c06da71a..2a35b3cd 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -205,8 +205,7 @@ function plugins (settings, deviceId) { } function mapCoinSettings (coinParams) { - const cryptoCode = coinParams[0] - const cryptoNetwork = coinParams[1] + const [ cryptoCode, cryptoNetwork ] = coinParams const commissions = configManager.getCommissions(cryptoCode, deviceId, settings.config) const minimumTx = new BN(commissions.minimumTx) const cashInFee = new BN(commissions.fixedFee) From d2b78ea93d3862b0b8d5930e8a207296026c5672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 17:35:03 +0100 Subject: [PATCH 17/26] refactor: inline variables and reformat promises --- lib/routes/pollingRoutes.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/lib/routes/pollingRoutes.js b/lib/routes/pollingRoutes.js index 2e470650..812b376f 100644 --- a/lib/routes/pollingRoutes.js +++ b/lib/routes/pollingRoutes.js @@ -72,9 +72,6 @@ function poll (req, res, next) { const pi = plugins(settings, deviceId) const hasLightning = checkHasLightning(settings) - const triggersAutomationPromise = configManager.getTriggersAutomation(settings.config) - const triggersPromise = buildTriggers(configManager.getTriggers(settings.config)) - const operatorInfo = configManager.getOperatorInfo(settings.config) const machineInfo = { deviceId: req.deviceId, deviceName: req.deviceName } const cashOutConfig = configManager.getCashOut(deviceId, settings.config) @@ -84,7 +81,11 @@ function poll (req, res, next) { state.pids = _.update(operatorId, _.set(deviceId, { pid, ts: Date.now() }), state.pids) - return Promise.all([pi.pollQueries(deviceTime, req.query, machineVersion, machineModel), triggersPromise, triggersAutomationPromise]) + return Promise.all([ + pi.pollQueries(deviceTime, req.query, machineVersion, machineModel), + buildTriggers(configManager.getTriggers(settings.config)), + configManager.getTriggersAutomation(settings.config) + ]) .then(([results, triggers, triggersAutomation]) => { const reboot = pid && state.reboots?.[operatorId]?.[deviceId] === pid const shutdown = pid && state.shutdowns?.[operatorId]?.[deviceId] === pid From af0029581e1502861a940482e2135b5c42514744 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 17:42:02 +0100 Subject: [PATCH 18/26] refactor: no more array slicing in `pollQueries()` --- lib/plugins.js | 69 ++++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 33 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index 2a35b3cd..fb05d049 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -230,52 +230,55 @@ function plugins (settings, deviceId) { const localeConfig = configManager.getLocale(deviceId, settings.config) const fiatCode = localeConfig.fiatCurrency const cryptoCodes = localeConfig.cryptoCurrencies - const timezone = millisecondsToMinutes(getTimezoneOffset(localeConfig.timezone)) const tickerPromises = cryptoCodes.map(c => ticker.getRates(settings, fiatCode, c)) const balancePromises = cryptoCodes.map(c => fiatBalance(fiatCode, c)) - const testnetPromises = cryptoCodes.map(c => wallet.cryptoNetwork(settings, c)) - const pingPromise = recordPing(deviceTime, machineVersion, machineModel) - const currentConfigVersionPromise = fetchCurrentConfigVersion() - const currentAvailablePromoCodes = loyalty.getNumberOfAvailablePromoCodes() + const networkPromises = cryptoCodes.map(c => wallet.cryptoNetwork(settings, c)) const supportsBatchingPromise = cryptoCodes.map(c => wallet.supportsBatching(settings, c)) - const promises = [ + return Promise.all([ buildAvailableCassettes(), - pingPromise, - currentConfigVersionPromise, - timezone - ].concat( - supportsBatchingPromise, - tickerPromises, - balancePromises, - testnetPromises, - currentAvailablePromoCodes - ) + recordPing(deviceTime, machineVersion, machineModel), + fetchCurrentConfigVersion(), + millisecondsToMinutes(getTimezoneOffset(localeConfig.timezone)), + loyalty.getNumberOfAvailablePromoCodes(), + Promise.all(supportsBatchingPromise), + Promise.all(tickerPromises), + Promise.all(balancePromises), + Promise.all(networkPromises) + ]) + .then(([ + cassettes, + _pingRes, + configVersion, + timezone, + numberOfAvailablePromoCodes, + batchableCoins, + tickers, + balances, + networks + ]) => { + const coinsWithoutRate = _.flow( + _.zip(cryptoCodes), + _.map(mapCoinSettings) + )(networks) - return Promise.all(promises) - .then(arr => { - const cassettes = arr[0] - const configVersion = arr[2] - const tz = arr[3] - const cryptoCodesCount = cryptoCodes.length - const batchableCoinsRes = arr.slice(4, cryptoCodesCount + 4) - const batchableCoins = batchableCoinsRes.map(it => ({ batchable: it })) - const tickers = arr.slice(cryptoCodesCount + 4, 2 * cryptoCodesCount + 4) - const balances = arr.slice(2 * cryptoCodesCount + 4, 3 * cryptoCodesCount + 4) - const testNets = arr.slice(3 * cryptoCodesCount + 4, arr.length - 1) - const coinParams = _.zip(cryptoCodes, testNets) - const coinsWithoutRate = _.map(mapCoinSettings, coinParams) - const areThereAvailablePromoCodes = arr[arr.length - 1] > 0 + const coins = _.flow( + _.map(it => ({ batchable: it })), + _.zipWith( + _.assign, + _.zipWith(_.assign, coinsWithoutRate, tickers) + ) + )(batchableCoins) return { cassettes, rates: buildRates(tickers), balances: buildBalances(balances), - coins: _.zipWith(_.assign, _.zipWith(_.assign, coinsWithoutRate, tickers), batchableCoins), + coins, configVersion, - areThereAvailablePromoCodes, - timezone: tz + areThereAvailablePromoCodes: numberOfAvailablePromoCodes > 0, + timezone } }) } From 40791cb4cd1bd3b1c1653316c34e49e2176fa1ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 17:44:04 +0100 Subject: [PATCH 19/26] refactor: move `recordPing()` to `poll()` --- lib/plugins.js | 5 ++--- lib/routes/pollingRoutes.js | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index fb05d049..ca07d516 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -226,7 +226,7 @@ function plugins (settings, deviceId) { } } - function pollQueries (deviceTime, deviceRec, machineVersion, machineModel) { + function pollQueries () { const localeConfig = configManager.getLocale(deviceId, settings.config) const fiatCode = localeConfig.fiatCurrency const cryptoCodes = localeConfig.cryptoCurrencies @@ -238,7 +238,6 @@ function plugins (settings, deviceId) { return Promise.all([ buildAvailableCassettes(), - recordPing(deviceTime, machineVersion, machineModel), fetchCurrentConfigVersion(), millisecondsToMinutes(getTimezoneOffset(localeConfig.timezone)), loyalty.getNumberOfAvailablePromoCodes(), @@ -249,7 +248,6 @@ function plugins (settings, deviceId) { ]) .then(([ cassettes, - _pingRes, configVersion, timezone, numberOfAvailablePromoCodes, @@ -851,6 +849,7 @@ function plugins (settings, deviceId) { return { getRates, + recordPing, buildRates, getRawRates, buildRatesNoCommission, diff --git a/lib/routes/pollingRoutes.js b/lib/routes/pollingRoutes.js index 812b376f..89e7aab1 100644 --- a/lib/routes/pollingRoutes.js +++ b/lib/routes/pollingRoutes.js @@ -82,11 +82,12 @@ function poll (req, res, next) { state.pids = _.update(operatorId, _.set(deviceId, { pid, ts: Date.now() }), state.pids) return Promise.all([ - pi.pollQueries(deviceTime, req.query, machineVersion, machineModel), + pi.recordPing(deviceTime, machineVersion, machineModel), + pi.pollQueries(), buildTriggers(configManager.getTriggers(settings.config)), configManager.getTriggersAutomation(settings.config) ]) - .then(([results, triggers, triggersAutomation]) => { + .then(([_pingRes, results, triggers, triggersAutomation]) => { const reboot = pid && state.reboots?.[operatorId]?.[deviceId] === pid const shutdown = pid && state.shutdowns?.[operatorId]?.[deviceId] === pid const restartServices = pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid From 4d8c8c4b62ac4809c90408c7e05b94db7371a225 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 17:44:54 +0100 Subject: [PATCH 20/26] fix: percentage division --- lib/plugins.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/plugins.js b/lib/plugins.js index ca07d516..9030682f 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -364,12 +364,12 @@ function plugins (settings, deviceId) { const rate = rawRate.div(cashInCommission) - const lowBalanceMargin = new BN(1.05) + const lowBalanceMargin = new BN(0.95) const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode) const unitScale = cryptoRec.unitScale const shiftedRate = rate.shiftedBy(-unitScale) - const fiatTransferBalance = balance.times(shiftedRate).div(lowBalanceMargin) + const fiatTransferBalance = balance.times(shiftedRate).times(lowBalanceMargin) return { timestamp: balanceRec.timestamp, From a0ed5a3a0e0dd3bd3843ed93f4b72c792d5aedff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 17:47:01 +0100 Subject: [PATCH 21/26] feat: add GraphQL server and resolvers --- lib/graphql/resolvers.js | 140 +++++++++++++++++++++++++++++++++++++++ lib/graphql/server.js | 27 ++++++++ lib/graphql/types.js | 137 ++++++++++++++++++++++++++++++++++++++ lib/plugins.js | 77 +++++++++++++++++++++ lib/routes.js | 7 +- 5 files changed, 387 insertions(+), 1 deletion(-) create mode 100644 lib/graphql/resolvers.js create mode 100644 lib/graphql/server.js create mode 100644 lib/graphql/types.js diff --git a/lib/graphql/resolvers.js b/lib/graphql/resolvers.js new file mode 100644 index 00000000..62c24d4b --- /dev/null +++ b/lib/graphql/resolvers.js @@ -0,0 +1,140 @@ +const _ = require('lodash/fp') + +const { accounts: accountsConfig, countries, languages } = require('../new-admin/config') +const plugins = require('../plugins') +const configManager = require('../new-config-manager') +const customRequestQueries = require('../new-admin/services/customInfoRequests') +const state = require('../middlewares/state') + +const urlsToPing = [ + `us.archive.ubuntu.com`, + `uk.archive.ubuntu.com`, + `za.archive.ubuntu.com`, + `cn.archive.ubuntu.com` +] + +const speedtestFiles = [ + { + url: 'https://github.com/lamassu/speed-test-assets/raw/main/python-defaults_2.7.18-3.tar.gz', + size: 44668 + } +] + +const addSmthInfo = (dstField, srcFields) => smth => + smth && smth.active ? _.set(dstField, _.pick(srcFields, smth)) : _.identity + +const addOperatorInfo = addSmthInfo( + 'operatorInfo', + ['name', 'phone', 'email', 'website', 'companyNumber'] +) + +const addReceiptInfo = addSmthInfo( + 'receiptInfo', + [ + 'sms', + 'operatorWebsite', + 'operatorEmail', + 'operatorPhone', + 'companyNumber', + 'machineLocation', + 'customerNameOrPhoneNumber', + 'exchangeRate', + 'addressQRCode', + ] +) + +/* TODO: Simplify this. */ +const buildTriggers = (allTriggers) => { + const normalTriggers = [] + const customTriggers = _.filter(o => { + if (_.isEmpty(o.customInfoRequestId) || _.isNil(o.customInfoRequestId)) normalTriggers.push(o) + return !_.isNil(o.customInfoRequestId) && !_.isEmpty(o.customInfoRequestId) + }, allTriggers) + + return _.flow( + _.map(_.get('customInfoRequestId')), + customRequestQueries.batchGetCustomInfoRequest + )(customTriggers) + .then(res => { + res.forEach((details, index) => { + // make sure we aren't attaching the details to the wrong trigger + if (customTriggers[index].customInfoRequestId !== details.id) return + customTriggers[index] = { ...customTriggers[index], customInfoRequest: details } + }) + return [...normalTriggers, ...customTriggers] + }) +} + +const staticConfig = (parent, { currentConfigVersion }, { deviceId, deviceName, settings }, info) => + Promise.all([ + plugins(settings, deviceId).staticConfigQueries(), + !!configManager.getCompliance(settings.config).enablePaperWalletOnly, + configManager.getTriggersAutomation(settings.config), + buildTriggers(configManager.getTriggers(settings.config)), + configManager.getWalletSettings('BTC', settings.config).layer2 !== 'no-layer2', + configManager.getLocale(deviceId, settings.config), + configManager.getOperatorInfo(settings.config), + configManager.getReceipt(settings.config), + !!configManager.getCashOut(deviceId, settings.config).active, + ]) + .then(([ + staticConf, + enablePaperWalletOnly, + triggersAutomation, + triggers, + hasLightning, + localeInfo, + operatorInfo, + receiptInfo, + twoWayMode, + ]) => + (currentConfigVersion && currentConfigVersion >= staticConf.configVersion) ? + null : + _.flow( + _.assign({ + enablePaperWalletOnly, + triggersAutomation, + triggers, + hasLightning, + localeInfo: { + country: localeInfo.country, + languages: localeInfo.languages, + fiatCode: localeInfo.fiatCurrency + }, + machineInfo: { deviceId, deviceName }, + twoWayMode, + speedtestFiles, + urlsToPing, + }), + _.update('triggersAutomation', _.mapValues(_.eq('Automatic'))), + addOperatorInfo(operatorInfo), + addReceiptInfo(receiptInfo) + )(staticConf)) + + +const setZeroConfLimit = config => coin => + _.set( + 'zeroConfLimit', + configManager.getWalletSettings(coin.cryptoCode, config).zeroConfLimit, + coin + ) + +const dynamicConfig = (parent, variables, { deviceId, operatorId, pid, settings }, info) => { + state.pids = _.update(operatorId, _.set(deviceId, { pid, ts: Date.now() }), state.pids) + return plugins(settings, deviceId) + .dynamicConfigQueries() + .then(_.flow( + _.update('coins', _.map(setZeroConfLimit(settings.config))), + _.set('reboot', !!pid && state.reboots?.[operatorId]?.[deviceId] === pid), + _.set('shutdown', !!pid && state.shutdowns?.[operatorId]?.[deviceId] === pid), + _.set('restartServices', !!pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid), + )) +} + + +module.exports = { + Query: { + staticConfig, + dynamicConfig + } +} diff --git a/lib/graphql/server.js b/lib/graphql/server.js new file mode 100644 index 00000000..fda7d85c --- /dev/null +++ b/lib/graphql/server.js @@ -0,0 +1,27 @@ +const logger = require('../logger') + +const https = require('https') +const { ApolloServer } = require('apollo-server-express') + +const devMode = !!require('minimist')(process.argv.slice(2)).dev + +module.exports = new ApolloServer({ + typeDefs: require('./types'), + resolvers: require('./resolvers'), + context: ({ req, res }) => ({ + deviceId: req.deviceId, /* lib/middlewares/populateDeviceId.js */ + deviceName: req.deviceName, /* lib/middlewares/authorize.js */ + operatorId: res.locals.operatorId, /* lib/middlewares/operatorId.js */ + pid: req.query.pid, + settings: req.settings, /* lib/middlewares/populateSettings.js */ + }), + uploads: false, + playground: false, + introspection: false, + formatError: error => { + logger.error(error) + return error + }, + debug: devMode, + logger +}) diff --git a/lib/graphql/types.js b/lib/graphql/types.js new file mode 100644 index 00000000..1c8161ea --- /dev/null +++ b/lib/graphql/types.js @@ -0,0 +1,137 @@ +const { gql } = require('apollo-server-express') +module.exports = gql` +type Coin { + cryptoCode: String! + display: String! + minimumTx: String! + cashInFee: String! + cashInCommission: String! + cashOutCommission: String! + cryptoNetwork: Boolean! + cryptoUnits: String! + batchable: Boolean! +} + +type LocaleInfo { + country: String! + fiatCode: String! + languages: [String!]! +} + +type OperatorInfo { + name: String! + phone: String! + email: String! + website: String! + companyNumber: String! +} + +type MachineInfo { + deviceId: String! + deviceName: String +} + +type ReceiptInfo { + sms: Boolean! + operatorWebsite: Boolean! + operatorEmail: Boolean! + operatorPhone: Boolean! + companyNumber: Boolean! + machineLocation: Boolean! + customerNameOrPhoneNumber: Boolean! + exchangeRate: Boolean! + addressQRCode: Boolean! +} + +type SpeedtestFile { + url: String! + size: Int! +} + +# True if automatic, False otherwise +type TriggersAutomation { + sanctions: Boolean! + idCardPhoto: Boolean! + idCardData: Boolean! + facephoto: Boolean! + usSsn: Boolean! +} + +type Trigger { + id: String! + customInfoRequestId: String! + direction: String! + requirement: String! + triggerType: String! + + suspensionDays: Int + threshold: Int + thresholdDays: Int +} + +type StaticConfig { + configVersion: Int! + + areThereAvailablePromoCodes: Boolean! + coins: [Coin!]! + enablePaperWalletOnly: Boolean! + hasLightning: Boolean! + serverVersion: String! + timezone: Int! + twoWayMode: Boolean! + + localeInfo: LocaleInfo! + operatorInfo: OperatorInfo + machineInfo: MachineInfo! + receiptInfo: ReceiptInfo + + speedtestFiles: [SpeedtestFile!]! + urlsToPing: [String!]! + + triggersAutomation: TriggersAutomation! + triggers: [Trigger!]! +} + +type DynamicCoinValues { + # NOTE: Doesn't seem to be used anywhere outside of lib/plugins.js. + # However, it can be used to generate the cache key, if we ever move to an + # actual caching mechanism. + #timestamp: String! + + cryptoCode: String! + balance: String! + + # Raw rates + ask: String! + bid: String! + + # Rates with commissions applied + cashIn: String! + cashOut: String! + + zeroConfLimit: Int! +} + +type PhysicalCassette { + denomination: Int! + count: Int! +} + +type Cassettes { + physical: [PhysicalCassette!]! + virtual: [Int!]! +} + +type DynamicConfig { + cassettes: Cassettes + coins: [DynamicCoinValues!]! + reboot: Boolean! + shutdown: Boolean! + restartServices: Boolean! +} + +type Query { + staticConfig(currentConfigVersion: Int): StaticConfig + dynamicConfig: DynamicConfig! +} +` diff --git a/lib/plugins.js b/lib/plugins.js index 9030682f..4f3f172c 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -25,6 +25,9 @@ const customers = require('./customers') const commissionMath = require('./commission-math') const loyalty = require('./loyalty') const transactionBatching = require('./tx-batching') +const state = require('./middlewares/state') + +const VERSION = require('../package.json').version const { CASSETTE_MAX_CAPACITY, CASH_OUT_DISPENSE_READY, CONFIRMATION_CODE } = require('./constants') @@ -281,6 +284,78 @@ function plugins (settings, deviceId) { }) } + function staticConfigQueries () { + const massageCoins = _.map(_.pick([ + 'batchable', + 'cashInCommission', + 'cashInFee', + 'cashOutCommission', + 'cryptoCode', + 'cryptoNetwork', + 'cryptoUnits', + 'display', + 'minimumTx' + ])) + + return pollQueries() + .then(_.flow( + _.pick([ + 'areThereAvailablePromoCodes', + 'coins', + 'configVersion', + 'timezone' + ]), + _.update('coins', massageCoins), + _.set('serverVersion', VERSION) + )) + } + + function dynamicConfigQueries () { + const massageCassettes = cassettes => + cassettes ? + _.flow( + cassettes => _.set('physical', _.get('cassettes', cassettes), cassettes), + cassettes => _.set('virtual', _.get('virtualCassettes', cassettes), cassettes), + _.unset('cassettes'), + _.unset('virtualCassettes') + )(cassettes) : + null + + return pollQueries() + .then(_.flow( + _.pick(['balances', 'cassettes', 'coins', 'rates']), + + _.update('cassettes', massageCassettes), + + /* [{ cryptoCode, rates }, ...] => [[cryptoCode, rates], ...] */ + _.update('coins', _.map(({ cryptoCode, rates }) => [cryptoCode, rates])), + + /* [{ cryptoCode: balance }, ...] => [[cryptoCode, { balance }], ...] */ + _.update('balances', _.flow( + _.toPairs, + _.map(([cryptoCode, balance]) => [cryptoCode, { balance }]) + )), + + /* Group the separate objects by cryptoCode */ + /* { balances, coins, rates } => { cryptoCode: { balance, ask, bid, cashIn, cashOut }, ... } */ + ({ balances, cassettes, coins, rates }) => ({ + cassettes, + coins: _.flow( + _.reduce( + (ret, [cryptoCode, obj]) => _.update(cryptoCode, _.assign(obj), ret), + rates + ), + + /* { cryptoCode: { balance, ask, bid, cashIn, cashOut }, ... } => [[cryptoCode, { balance, ask, bid, cashIn, cashOut }], ...] */ + _.toPairs, + + /* [[cryptoCode, { balance, ask, bid, cashIn, cashOut }], ...] => [{ cryptoCode, balance, ask, bid, cashIn, cashOut }, ...] */ + _.map(([cryptoCode, obj]) => _.set('cryptoCode', cryptoCode, obj)) + )(_.concat(balances, coins)) + }) + )) + } + function sendCoins (tx) { return wallet.supportsBatching(settings, tx.cryptoCode) .then(supportsBatching => { @@ -854,6 +929,8 @@ function plugins (settings, deviceId) { getRawRates, buildRatesNoCommission, pollQueries, + staticConfigQueries, + dynamicConfigQueries, sendCoins, newAddress, isHd, diff --git a/lib/routes.js b/lib/routes.js index 6f727b02..5a608dd9 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -30,6 +30,8 @@ const verifyUserRoutes = require('./routes/verifyUserRoutes') const verifyTxRoutes = require('./routes/verifyTxRoutes') const verifyPromoCodeRoutes = require('./routes/verifyPromoCodeRoutes') +const graphQLServer = require('./graphql/server') + const app = express() const configRequiredRoutes = [ @@ -39,7 +41,8 @@ const configRequiredRoutes = [ '/phone_code', '/customer', '/tx', - '/verify_promo_code' + '/verify_promo_code', + '/graphql' ] const devMode = argv.dev || options.http @@ -79,6 +82,8 @@ app.use('/tx', txRoutes) app.use('/logs', logsRoutes) +graphQLServer.applyMiddleware({ app }) + app.use(errorHandler) app.use((req, res) => { res.status(404).json({ error: 'No such route' }) From e30d3c035d666f5151665bcd85cb602721a611a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 5 Apr 2022 17:47:50 +0100 Subject: [PATCH 22/26] feat: add `recordPing()` as middleware to `/graphql` --- lib/middlewares/recordPing.js | 7 +++++++ lib/routes.js | 2 ++ 2 files changed, 9 insertions(+) create mode 100644 lib/middlewares/recordPing.js diff --git a/lib/middlewares/recordPing.js b/lib/middlewares/recordPing.js new file mode 100644 index 00000000..e74de771 --- /dev/null +++ b/lib/middlewares/recordPing.js @@ -0,0 +1,7 @@ +const plugins = require('../plugins') + +module.exports = (req, res, next) => + plugins(req.settings, req.deviceId) + .recordPing(req.deviceTime, req.query.version, req.query.model) + .then(() => next()) + .catch(() => next()) diff --git a/lib/routes.js b/lib/routes.js index 5a608dd9..255490c8 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -15,6 +15,7 @@ const computeSchema = require('./middlewares/compute-schema') const findOperatorId = require('./middlewares/operatorId') const populateDeviceId = require('./middlewares/populateDeviceId') const populateSettings = require('./middlewares/populateSettings') +const recordPing = require('./middlewares/recordPing') const cashboxRoutes = require('./routes/cashboxRoutes') const customerRoutes = require('./routes/customerRoutes') @@ -64,6 +65,7 @@ app.use(configRequiredRoutes, populateSettings) app.use(filterOldRequests) // other app routes +app.use('/graphql', recordPing) app.use('/poll', pollingRoutes) app.use('/terms_conditions', termsAndConditionsRoutes) app.use('/state', stateRoutes) From b380e2845c41f3e6b4583f23247fdd5c62486eae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 26 Apr 2022 12:52:04 +0100 Subject: [PATCH 23/26] chore: version bump --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 5daa70b8..f5c222af 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "lamassu-server", - "version": "8.0.0-beta.4", + "version": "8.1.0-beta.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 539bdfaf..b4771d8a 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "lamassu-server", "description": "bitcoin atm client server protocol module", "keywords": [], - "version": "8.0.0-beta.4", + "version": "8.1.0-beta.0", "license": "Unlicense", "author": "Lamassu (https://lamassu.is)", "dependencies": { From df2213566e7570f7249f3815421b32fc66290b35 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Mon, 11 Apr 2022 17:48:15 +0100 Subject: [PATCH 24/26] feat: add T&C configs to GraphQL API --- lib/graphql/resolvers.js | 16 ++++++++++++++++ lib/graphql/types.js | 10 ++++++++++ 2 files changed, 26 insertions(+) diff --git a/lib/graphql/resolvers.js b/lib/graphql/resolvers.js index 62c24d4b..e831f4d3 100644 --- a/lib/graphql/resolvers.js +++ b/lib/graphql/resolvers.js @@ -1,4 +1,5 @@ const _ = require('lodash/fp') +const nmd = require('nano-markdown') const { accounts: accountsConfig, countries, languages } = require('../new-admin/config') const plugins = require('../plugins') @@ -65,6 +66,18 @@ const buildTriggers = (allTriggers) => { }) } +/* + * TODO: From `lib/routes/termsAndConditionsRoutes.js` -- remove this after + * terms are removed from the GraphQL API too. + */ +const massageTerms = terms => (terms.active && terms.text) ? ({ + delay: Boolean(terms.delay), + title: terms.title, + text: nmd(terms.text), + accept: terms.acceptButtonText, + cancel: terms.cancelButtonText, +}) : null + const staticConfig = (parent, { currentConfigVersion }, { deviceId, deviceName, settings }, info) => Promise.all([ plugins(settings, deviceId).staticConfigQueries(), @@ -75,6 +88,7 @@ const staticConfig = (parent, { currentConfigVersion }, { deviceId, deviceName, configManager.getLocale(deviceId, settings.config), configManager.getOperatorInfo(settings.config), configManager.getReceipt(settings.config), + massageTerms(configManager.getTermsConditions(settings.config)), !!configManager.getCashOut(deviceId, settings.config).active, ]) .then(([ @@ -86,6 +100,7 @@ const staticConfig = (parent, { currentConfigVersion }, { deviceId, deviceName, localeInfo, operatorInfo, receiptInfo, + terms, twoWayMode, ]) => (currentConfigVersion && currentConfigVersion >= staticConf.configVersion) ? @@ -105,6 +120,7 @@ const staticConfig = (parent, { currentConfigVersion }, { deviceId, deviceName, twoWayMode, speedtestFiles, urlsToPing, + terms, }), _.update('triggersAutomation', _.mapValues(_.eq('Automatic'))), addOperatorInfo(operatorInfo), diff --git a/lib/graphql/types.js b/lib/graphql/types.js index 1c8161ea..7e504182 100644 --- a/lib/graphql/types.js +++ b/lib/graphql/types.js @@ -69,6 +69,14 @@ type Trigger { thresholdDays: Int } +type Terms { + delay: Boolean! + title: String! + text: String! + accept: String! + cancel: String! +} + type StaticConfig { configVersion: Int! @@ -90,6 +98,8 @@ type StaticConfig { triggersAutomation: TriggersAutomation! triggers: [Trigger!]! + + terms: Terms } type DynamicCoinValues { From d20f4968f650bebbc698bbd95747e3ab4cb22d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Fri, 22 Apr 2022 16:06:33 +0100 Subject: [PATCH 25/26] refactor: join static and dynamic configs to reuse `pollQueries()` --- lib/graphql/resolvers.js | 113 +++++++++++++++++++++++++++++++++------ lib/graphql/types.js | 8 ++- lib/plugins.js | 76 -------------------------- 3 files changed, 104 insertions(+), 93 deletions(-) diff --git a/lib/graphql/resolvers.js b/lib/graphql/resolvers.js index e831f4d3..5d457c64 100644 --- a/lib/graphql/resolvers.js +++ b/lib/graphql/resolvers.js @@ -7,6 +7,8 @@ const configManager = require('../new-config-manager') const customRequestQueries = require('../new-admin/services/customInfoRequests') const state = require('../middlewares/state') +const VERSION = require('../../package.json').version + const urlsToPing = [ `us.archive.ubuntu.com`, `uk.archive.ubuntu.com`, @@ -78,9 +80,31 @@ const massageTerms = terms => (terms.active && terms.text) ? ({ cancel: terms.cancelButtonText, }) : null -const staticConfig = (parent, { currentConfigVersion }, { deviceId, deviceName, settings }, info) => - Promise.all([ - plugins(settings, deviceId).staticConfigQueries(), +const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings, }) => { + const massageCoins = _.map(_.pick([ + 'batchable', + 'cashInCommission', + 'cashInFee', + 'cashOutCommission', + 'cryptoCode', + 'cryptoNetwork', + 'cryptoUnits', + 'display', + 'minimumTx' + ])) + + const staticConf = _.flow( + _.pick([ + 'areThereAvailablePromoCodes', + 'coins', + 'configVersion', + 'timezone' + ]), + _.update('coins', massageCoins), + _.set('serverVersion', VERSION), + )(pq) + + return Promise.all([ !!configManager.getCompliance(settings.config).enablePaperWalletOnly, configManager.getTriggersAutomation(settings.config), buildTriggers(configManager.getTriggers(settings.config)), @@ -92,7 +116,6 @@ const staticConfig = (parent, { currentConfigVersion }, { deviceId, deviceName, !!configManager.getCashOut(deviceId, settings.config).active, ]) .then(([ - staticConf, enablePaperWalletOnly, triggersAutomation, triggers, @@ -126,6 +149,7 @@ const staticConfig = (parent, { currentConfigVersion }, { deviceId, deviceName, addOperatorInfo(operatorInfo), addReceiptInfo(receiptInfo) )(staticConf)) +} const setZeroConfLimit = config => coin => @@ -135,22 +159,81 @@ const setZeroConfLimit = config => coin => coin ) -const dynamicConfig = (parent, variables, { deviceId, operatorId, pid, settings }, info) => { +const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => { + const massageCassettes = cassettes => + cassettes ? + _.flow( + cassettes => _.set('physical', _.get('cassettes', cassettes), cassettes), + cassettes => _.set('virtual', _.get('virtualCassettes', cassettes), cassettes), + _.unset('cassettes'), + _.unset('virtualCassettes') + )(cassettes) : + null + state.pids = _.update(operatorId, _.set(deviceId, { pid, ts: Date.now() }), state.pids) - return plugins(settings, deviceId) - .dynamicConfigQueries() - .then(_.flow( - _.update('coins', _.map(setZeroConfLimit(settings.config))), - _.set('reboot', !!pid && state.reboots?.[operatorId]?.[deviceId] === pid), - _.set('shutdown', !!pid && state.shutdowns?.[operatorId]?.[deviceId] === pid), - _.set('restartServices', !!pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid), - )) + + return _.flow( + _.pick(['balances', 'cassettes', 'coins', 'rates']), + + _.update('cassettes', massageCassettes), + + /* [{ cryptoCode, rates }, ...] => [[cryptoCode, rates], ...] */ + _.update('coins', _.map(({ cryptoCode, rates }) => [cryptoCode, rates])), + + /* [{ cryptoCode: balance }, ...] => [[cryptoCode, { balance }], ...] */ + _.update('balances', _.flow( + _.toPairs, + _.map(([cryptoCode, balance]) => [cryptoCode, { balance }]) + )), + + /* Group the separate objects by cryptoCode */ + /* { balances, coins, rates } => { cryptoCode: { balance, ask, bid, cashIn, cashOut }, ... } */ + ({ balances, cassettes, coins, rates }) => ({ + cassettes, + coins: _.flow( + _.reduce( + (ret, [cryptoCode, obj]) => _.update(cryptoCode, _.assign(obj), ret), + rates + ), + + /* { cryptoCode: { balance, ask, bid, cashIn, cashOut }, ... } => [[cryptoCode, { balance, ask, bid, cashIn, cashOut }], ...] */ + _.toPairs, + + /* [[cryptoCode, { balance, ask, bid, cashIn, cashOut }], ...] => [{ cryptoCode, balance, ask, bid, cashIn, cashOut }, ...] */ + _.map(([cryptoCode, obj]) => _.set('cryptoCode', cryptoCode, obj)) + )(_.concat(balances, coins)) + }), + + _.update('coins', _.map(setZeroConfLimit(settings.config))), + _.set('reboot', !!pid && state.reboots?.[operatorId]?.[deviceId] === pid), + _.set('shutdown', !!pid && state.shutdowns?.[operatorId]?.[deviceId] === pid), + _.set('restartServices', !!pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid), + )(pq) } +const configs = (parent, { currentConfigVersion }, { deviceId, deviceName, operatorId, pid, settings }, info) => + plugins(settings, deviceId) + .pollQueries() + .then(pq => ({ + static: staticConfig({ + currentConfigVersion, + deviceId, + deviceName, + pq, + settings, + }), + dynamic: dynamicConfig({ + deviceId, + operatorId, + pid, + pq, + settings, + }), + })) + module.exports = { Query: { - staticConfig, - dynamicConfig + configs } } diff --git a/lib/graphql/types.js b/lib/graphql/types.js index 7e504182..a9318978 100644 --- a/lib/graphql/types.js +++ b/lib/graphql/types.js @@ -140,8 +140,12 @@ type DynamicConfig { restartServices: Boolean! } +type Configs { + static: StaticConfig + dynamic: DynamicConfig! +} + type Query { - staticConfig(currentConfigVersion: Int): StaticConfig - dynamicConfig: DynamicConfig! + configs(currentConfigVersion: Int): Configs! } ` diff --git a/lib/plugins.js b/lib/plugins.js index 4f3f172c..0fbff479 100644 --- a/lib/plugins.js +++ b/lib/plugins.js @@ -27,8 +27,6 @@ const loyalty = require('./loyalty') const transactionBatching = require('./tx-batching') const state = require('./middlewares/state') -const VERSION = require('../package.json').version - const { CASSETTE_MAX_CAPACITY, CASH_OUT_DISPENSE_READY, CONFIRMATION_CODE } = require('./constants') const notifier = require('./notifier') @@ -284,78 +282,6 @@ function plugins (settings, deviceId) { }) } - function staticConfigQueries () { - const massageCoins = _.map(_.pick([ - 'batchable', - 'cashInCommission', - 'cashInFee', - 'cashOutCommission', - 'cryptoCode', - 'cryptoNetwork', - 'cryptoUnits', - 'display', - 'minimumTx' - ])) - - return pollQueries() - .then(_.flow( - _.pick([ - 'areThereAvailablePromoCodes', - 'coins', - 'configVersion', - 'timezone' - ]), - _.update('coins', massageCoins), - _.set('serverVersion', VERSION) - )) - } - - function dynamicConfigQueries () { - const massageCassettes = cassettes => - cassettes ? - _.flow( - cassettes => _.set('physical', _.get('cassettes', cassettes), cassettes), - cassettes => _.set('virtual', _.get('virtualCassettes', cassettes), cassettes), - _.unset('cassettes'), - _.unset('virtualCassettes') - )(cassettes) : - null - - return pollQueries() - .then(_.flow( - _.pick(['balances', 'cassettes', 'coins', 'rates']), - - _.update('cassettes', massageCassettes), - - /* [{ cryptoCode, rates }, ...] => [[cryptoCode, rates], ...] */ - _.update('coins', _.map(({ cryptoCode, rates }) => [cryptoCode, rates])), - - /* [{ cryptoCode: balance }, ...] => [[cryptoCode, { balance }], ...] */ - _.update('balances', _.flow( - _.toPairs, - _.map(([cryptoCode, balance]) => [cryptoCode, { balance }]) - )), - - /* Group the separate objects by cryptoCode */ - /* { balances, coins, rates } => { cryptoCode: { balance, ask, bid, cashIn, cashOut }, ... } */ - ({ balances, cassettes, coins, rates }) => ({ - cassettes, - coins: _.flow( - _.reduce( - (ret, [cryptoCode, obj]) => _.update(cryptoCode, _.assign(obj), ret), - rates - ), - - /* { cryptoCode: { balance, ask, bid, cashIn, cashOut }, ... } => [[cryptoCode, { balance, ask, bid, cashIn, cashOut }], ...] */ - _.toPairs, - - /* [[cryptoCode, { balance, ask, bid, cashIn, cashOut }], ...] => [{ cryptoCode, balance, ask, bid, cashIn, cashOut }, ...] */ - _.map(([cryptoCode, obj]) => _.set('cryptoCode', cryptoCode, obj)) - )(_.concat(balances, coins)) - }) - )) - } - function sendCoins (tx) { return wallet.supportsBatching(settings, tx.cryptoCode) .then(supportsBatching => { @@ -929,8 +855,6 @@ function plugins (settings, deviceId) { getRawRates, buildRatesNoCommission, pollQueries, - staticConfigQueries, - dynamicConfigQueries, sendCoins, newAddress, isHd, From fa2fe36c46f6042b0198a998ac9676c5ab617099 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20S=C3=A1?= Date: Tue, 26 Apr 2022 14:37:39 +0100 Subject: [PATCH 26/26] fix: add missing `getCustomInfoRequests()` call --- lib/graphql/resolvers.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/graphql/resolvers.js b/lib/graphql/resolvers.js index 5d457c64..eb628fe7 100644 --- a/lib/graphql/resolvers.js +++ b/lib/graphql/resolvers.js @@ -106,7 +106,7 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings return Promise.all([ !!configManager.getCompliance(settings.config).enablePaperWalletOnly, - configManager.getTriggersAutomation(settings.config), + configManager.getTriggersAutomation(customRequestQueries.getCustomInfoRequests(), settings.config), buildTriggers(configManager.getTriggers(settings.config)), configManager.getWalletSettings('BTC', settings.config).layer2 !== 'no-layer2', configManager.getLocale(deviceId, settings.config),