refactor: logic and routing folders on new-admin

This commit is contained in:
Sérgio Salgado 2021-01-22 15:36:40 +00:00 committed by Josh Harvey
parent 0c7c7ccf42
commit d9e3a9e61f
10 changed files with 94 additions and 87 deletions

View file

@ -0,0 +1,92 @@
const _ = require('lodash/fp')
const BN = require('../../bn')
const settingsLoader = require('../../new-settings-loader')
const configManager = require('../../new-config-manager')
const wallet = require('../../wallet')
const ticker = require('../../ticker')
const coinUtils = require('../../coin-utils')
const logger = require('../../logger')
function allScopes (cryptoScopes, machineScopes) {
const scopes = []
cryptoScopes.forEach(c => {
machineScopes.forEach(m => scopes.push([c, m]))
})
return scopes
}
function allMachineScopes (machineList, machineScope) {
const machineScopes = []
if (machineScope === 'global' || machineScope === 'both') machineScopes.push('global')
if (machineScope === 'specific' || machineScope === 'both') machineList.forEach(r => machineScopes.push(r))
return machineScopes
}
function computeCrypto (cryptoCode, _balance) {
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
const unitScale = cryptoRec.unitScale
return BN(_balance).shift(-unitScale).round(5)
}
function computeFiat (rate, cryptoCode, _balance) {
const cryptoRec = coinUtils.getCryptoCurrency(cryptoCode)
const unitScale = cryptoRec.unitScale
return BN(_balance).shift(-unitScale).mul(rate).round(5)
}
function getSingleCoinFunding (settings, fiatCode, cryptoCode) {
const promises = [
wallet.newFunding(settings, cryptoCode),
ticker.getRates(settings, fiatCode, cryptoCode)
]
return Promise.all(promises)
.then(([fundingRec, ratesRec]) => {
const rates = ratesRec.rates
const rate = (rates.ask.add(rates.bid)).div(2)
const fundingConfirmedBalance = fundingRec.fundingConfirmedBalance
const fiatConfirmedBalance = computeFiat(rate, cryptoCode, fundingConfirmedBalance)
const pending = fundingRec.fundingPendingBalance
const fiatPending = computeFiat(rate, cryptoCode, pending)
const fundingAddress = fundingRec.fundingAddress
const fundingAddressUrl = coinUtils.buildUrl(cryptoCode, fundingAddress)
return {
cryptoCode,
fundingAddress,
fundingAddressUrl,
confirmedBalance: computeCrypto(cryptoCode, fundingConfirmedBalance).toFormat(5),
pending: computeCrypto(cryptoCode, pending).toFormat(5),
fiatConfirmedBalance: fiatConfirmedBalance,
fiatPending: fiatPending,
fiatCode
}
})
}
// Promise.allSettled not running on current version of node
const reflect = p => p.then(value => ({ value, status: 'fulfilled' }), error => ({ error: error.toString(), status: 'rejected' }))
function getFunding () {
return settingsLoader.loadLatest().then(settings => {
const cryptoCodes = configManager.getAllCryptoCurrencies(settings.config)
const fiatCode = configManager.getGlobalLocale(settings.config).fiatCurrency
const pareCoins = c => _.includes(c.cryptoCode, cryptoCodes)
const cryptoCurrencies = coinUtils.cryptoCurrencies()
const cryptoDisplays = _.filter(pareCoins, cryptoCurrencies)
const promises = cryptoDisplays.map(it => getSingleCoinFunding(settings, fiatCode, it.cryptoCode))
return Promise.all(promises.map(reflect))
.then((response) => {
const mapped = response.map(it => _.merge({ errorMsg: it.error }, it.value))
return _.toArray(_.merge(mapped, cryptoDisplays))
})
})
}
module.exports = { getFunding }

View file

@ -0,0 +1,48 @@
const crypto = require('crypto')
const db = require('../../db')
function generateOTP (name) {
const otp = crypto.randomBytes(32).toString('hex')
const sql = 'insert into one_time_passes (token, name) values ($1, $2)'
return db.none(sql, [otp, name])
.then(() => otp)
}
function validateOTP (otp) {
const sql = `delete from one_time_passes
where token=$1
returning name, created < now() - interval '1 hour' as expired`
return db.one(sql, [otp])
.then(r => ({ success: !r.expired, expired: r.expired, name: r.name }))
.catch(() => ({ success: false, expired: false }))
}
function register (otp, ua, ip) {
return validateOTP(otp)
.then(r => {
if (!r.success) return r
const token = crypto.randomBytes(32).toString('hex')
const sql = 'insert into user_tokens (token, name, user_agent, ip_address) values ($1, $2, $3, $4)'
return db.none(sql, [token, r.name, ua, ip])
.then(() => ({ success: true, token: token }))
})
.catch(() => ({ success: false, expired: false }))
}
function authenticate (token) {
const sql = 'select token from user_tokens where token=$1'
return db.one(sql, [token]).then(() => true).catch(() => false)
}
module.exports = {
generateOTP,
register,
authenticate
}

View file

@ -0,0 +1,19 @@
const machineLoader = require('../../machine-loader')
const { UserInputError } = require('apollo-server-express')
function getMachine (machineId) {
return machineLoader.getMachines()
.then(machines => machines.find(({ deviceId }) => deviceId === machineId))
}
function machineAction ({ deviceId, action, cashbox, cassette1, cassette2, newName }) {
return getMachine(deviceId)
.then(machine => {
if (!machine) throw new UserInputError(`machine:${deviceId} not found`, { deviceId })
return machine
})
.then(machineLoader.setMachine({ deviceId, action, cashbox, cassettes: [cassette1, cassette2], newName }))
.then(getMachine(deviceId))
}
module.exports = { machineAction }

View file

@ -0,0 +1,33 @@
const fs = require('fs')
const pify = require('pify')
const readFile = pify(fs.readFile)
const crypto = require('crypto')
const baseX = require('base-x')
const options = require('../../options')
const db = require('../../db')
const pairing = require('../../pairing')
const ALPHA_BASE = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:'
const bsAlpha = baseX(ALPHA_BASE)
const unpair = pairing.unpair
function totem (name) {
const caPath = options.caPath
return readFile(caPath)
.then(data => {
const caHash = crypto.createHash('sha256').update(data).digest()
const token = crypto.randomBytes(32)
const hexToken = token.toString('hex')
const caHexToken = crypto.createHash('sha256').update(hexToken).digest('hex')
const buf = Buffer.concat([caHash, token, Buffer.from(options.hostname)])
const sql = 'insert into pairing_tokens (token, name) values ($1, $3), ($2, $3)'
return db.none(sql, [hexToken, caHexToken, name])
.then(() => bsAlpha.encode(buf))
})
}
module.exports = { totem, unpair }

View file

@ -0,0 +1,17 @@
const _ = require('lodash/fp')
const uuid = require('uuid')
const db = require('../../db')
function getServerLogs (from = new Date(0).toISOString(), until = new Date().toISOString(), limit = null, offset = 0) {
const sql = `select id, log_level, timestamp, message from server_logs
where timestamp >= $1 and timestamp <= $2
order by timestamp desc
limit $3
offset $4`
return db.any(sql, [ from, until, limit, offset ])
.then(_.map(_.mapKeys(_.camelCase)))
}
module.exports = { getServerLogs }

View file

@ -0,0 +1,62 @@
const xmlrpc = require('xmlrpc')
const logger = require('../../logger')
const { promisify } = require('util')
// TODO new-admin: add the following to supervisor config
// [inet_http_server]
// port = 127.0.0.1:9001
function getAllProcessInfo () {
const convertStates = (state) => {
// From http://supervisord.org/subprocess.html#process-states
switch (state) {
case 'STOPPED':
return 'STOPPED'
case 'STARTING':
return 'RUNNING'
case 'RUNNING':
return 'RUNNING'
case 'BACKOFF':
return 'FATAL'
case 'STOPPING':
return 'STOPPED'
case 'EXITED':
return 'STOPPED'
case 'UNKNOWN':
return 'FATAL'
default:
logger.error(`Supervisord returned an unsupported state: ${state}`)
return 'FATAL'
}
}
const client = xmlrpc.createClient({
host: 'localhost',
port: '9001',
path: '/RPC2'
})
client.methodCall[promisify.custom] = (method, params) => {
return new Promise((resolve, reject) => client.methodCall(method, params, (err, value) => {
if (err) reject(err)
else resolve(value)
}))
}
return promisify(client.methodCall)('supervisor.getAllProcessInfo', [])
.then((value) => {
return value.map(process => (
{
name: process.name,
state: convertStates(process.statename),
uptime: (process.statename === 'RUNNING') ? process.now - process.start : 0
}
))
})
.catch((error) => {
if (error.code === 'ECONNREFUSED') logger.error('Failed to connect to supervisord HTTP server.')
else logger.error(error)
})
}
module.exports = { getAllProcessInfo }

View file

@ -0,0 +1,165 @@
const _ = require('lodash/fp')
const pgp = require('pg-promise')()
const db = require('../../db')
const machineLoader = require('../../machine-loader')
const tx = require('../../tx')
const cashInTx = require('../../cash-in/cash-in-tx')
const { REDEEMABLE_AGE } = require('../../cash-out/cash-out-helper')
const NUM_RESULTS = 1000
function addNames (txs) {
return machineLoader.getMachineNames()
.then(machines => {
const addName = tx => {
const machine = _.find(['deviceId', tx.deviceId], machines)
const name = machine ? machine.name : 'Unpaired'
return _.set('machineName', name, tx)
}
return _.map(addName, txs)
})
}
const camelize = _.mapKeys(_.camelCase)
function batch (from = new Date(0).toISOString(), until = new Date().toISOString(), limit = null, offset = 0, id = null) {
const packager = _.flow(_.flatten, _.orderBy(_.property('created'), ['desc']), _.map(camelize), addNames)
const cashInSql = `select 'cashIn' as tx_class, txs.*,
c.phone as customer_phone,
c.id_card_data_number as customer_id_card_data_number,
c.id_card_data_expiration as customer_id_card_data_expiration,
c.id_card_data as customer_id_card_data,
c.name as customer_name,
c.front_camera_path as customer_front_camera_path,
c.id_card_photo_path as customer_id_card_photo_path,
((not txs.send_confirmed) and (txs.created <= now() - interval $1)) as expired
from cash_in_txs as txs
left outer join customers c on txs.customer_id = c.id
where txs.created >= $2 and txs.created <= $3 ${
id !== null ? `and txs.device_id = $6` : ``
}
order by created desc limit $4 offset $5`
const cashOutSql = `select 'cashOut' as tx_class,
txs.*,
actions.tx_hash,
c.phone as customer_phone,
c.id_card_data_number as customer_id_card_data_number,
c.id_card_data_expiration as customer_id_card_data_expiration,
c.id_card_data as customer_id_card_data,
c.name as customer_name,
c.front_camera_path as customer_front_camera_path,
c.id_card_photo_path as customer_id_card_photo_path,
(extract(epoch from (now() - greatest(txs.created, txs.confirmed_at))) * 1000) >= $1 as expired
from cash_out_txs txs
inner join cash_out_actions actions on txs.id = actions.tx_id
and actions.action = 'provisionAddress'
left outer join customers c on txs.customer_id = c.id
where txs.created >= $2 and txs.created <= $3 ${
id !== null ? `and txs.device_id = $6` : ``
}
order by created desc limit $4 offset $5`
return Promise.all([
db.any(cashInSql, [cashInTx.PENDING_INTERVAL, from, until, limit, offset, id]),
db.any(cashOutSql, [REDEEMABLE_AGE, from, until, limit, offset, id])
])
.then(packager)
}
function getCustomerTransactionsBatch (ids) {
const packager = _.flow(it => {
return it
}, _.flatten, _.orderBy(_.property('created'), ['desc']), _.map(camelize), addNames)
const cashInSql = `select 'cashIn' as tx_class, txs.*,
c.phone as customer_phone,
c.id_card_data_number as customer_id_card_data_number,
c.id_card_data_expiration as customer_id_card_data_expiration,
c.id_card_data as customer_id_card_data,
c.name as customer_name,
c.front_camera_path as customer_front_camera_path,
c.id_card_photo_path as customer_id_card_photo_path,
((not txs.send_confirmed) and (txs.created <= now() - interval $2)) as expired
from cash_in_txs as txs
left outer join customers c on txs.customer_id = c.id
where c.id IN ($1^)
order by created desc limit $3`
const cashOutSql = `select 'cashOut' as tx_class,
txs.*,
actions.tx_hash,
c.phone as customer_phone,
c.id_card_data_number as customer_id_card_data_number,
c.id_card_data_expiration as customer_id_card_data_expiration,
c.id_card_data as customer_id_card_data,
c.name as customer_name,
c.front_camera_path as customer_front_camera_path,
c.id_card_photo_path as customer_id_card_photo_path,
(extract(epoch from (now() - greatest(txs.created, txs.confirmed_at))) * 1000) >= $3 as expired
from cash_out_txs txs
inner join cash_out_actions actions on txs.id = actions.tx_id
and actions.action = 'provisionAddress'
left outer join customers c on txs.customer_id = c.id
where c.id IN ($1^)
order by created desc limit $2`
return Promise.all([
db.any(cashInSql, [_.map(pgp.as.text, ids).join(','), cashInTx.PENDING_INTERVAL, NUM_RESULTS]),
db.any(cashOutSql, [_.map(pgp.as.text, ids).join(','), NUM_RESULTS, REDEEMABLE_AGE])
])
.then(packager).then(transactions => {
const transactionMap = _.groupBy('customerId', transactions)
return ids.map(id => transactionMap[id])
})
}
function single (txId) {
const packager = _.flow(_.compact, _.map(camelize), addNames)
const cashInSql = `select 'cashIn' as tx_class, txs.*,
c.phone as customer_phone,
c.id_card_data_number as customer_id_card_data_number,
c.id_card_data_expiration as customer_id_card_data_expiration,
c.id_card_data as customer_id_card_data,
c.name as customer_name,
c.front_camera_path as customer_front_camera_path,
c.id_card_photo_path as customer_id_card_photo_path,
((not txs.send_confirmed) and (txs.created <= now() - interval $1)) as expired
from cash_in_txs as txs
left outer join customers c on txs.customer_id = c.id
where id=$2`
const cashOutSql = `select 'cashOut' as tx_class,
txs.*,
actions.tx_hash,
c.phone as customer_phone,
c.id_card_data_number as customer_id_card_data_number,
c.id_card_data_expiration as customer_id_card_data_expiration,
c.id_card_data as customer_id_card_data,
c.name as customer_name,
c.front_camera_path as customer_front_camera_path,
c.id_card_photo_path as customer_id_card_photo_path,
(extract(epoch from (now() - greatest(txs.created, txs.confirmed_at))) * 1000) >= $2 as expired
from cash_out_txs txs
inner join cash_out_actions actions on txs.id = actions.tx_id
and actions.action = 'provisionAddress'
left outer join customers c on txs.customer_id = c.id
where id=$1`
return Promise.all([
db.oneOrNone(cashInSql, [cashInTx.PENDING_INTERVAL, txId]),
db.oneOrNone(cashOutSql, [txId, REDEEMABLE_AGE])
])
.then(packager)
.then(_.head)
}
function cancel (txId) {
return tx.cancel(txId)
.then(() => single(txId))
}
module.exports = { batch, single, cancel, getCustomerTransactionsBatch }