refactor: logic and routing folders on new-admin
This commit is contained in:
parent
0c7c7ccf42
commit
d9e3a9e61f
10 changed files with 94 additions and 87 deletions
92
lib/new-admin/modules/funding.js
Normal file
92
lib/new-admin/modules/funding.js
Normal 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 }
|
||||
48
lib/new-admin/modules/login.js
Normal file
48
lib/new-admin/modules/login.js
Normal 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
|
||||
}
|
||||
19
lib/new-admin/modules/machines.js
Normal file
19
lib/new-admin/modules/machines.js
Normal 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 }
|
||||
33
lib/new-admin/modules/pairing.js
Normal file
33
lib/new-admin/modules/pairing.js
Normal 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 }
|
||||
17
lib/new-admin/modules/server-logs.js
Normal file
17
lib/new-admin/modules/server-logs.js
Normal 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 }
|
||||
62
lib/new-admin/modules/supervisor.js
Normal file
62
lib/new-admin/modules/supervisor.js
Normal 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 }
|
||||
165
lib/new-admin/modules/transactions.js
Normal file
165
lib/new-admin/modules/transactions.js
Normal 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 }
|
||||
Loading…
Add table
Add a link
Reference in a new issue