Merge branch 'dev' into feat/lam-1291/stress-testing
* dev: (41 commits) build: use bullseye as target build chore: remove whitespace refactor: simplify denominations list construction fix: sort coins by descending denomination feat: address prompt feature toggle on ui feat: reuse last address option fix: performance issues on SystemPerformance chore: clarify requirements on comment feat: allow address reuse if same customer feat: address reuse is now per customer fix: hide anon and show phone on customers fix: dev environment restarts feat: batch diagnostics script fix: custom info request returns array fix: name on customer if custom data is filled build: testing cache hit build: server cache improvements build: node_modules was ignored on .dockerignored build: leftovers from npm chore: commented by mistake ...
This commit is contained in:
commit
5feee6d5df
105 changed files with 17323 additions and 31348 deletions
36
packages/server/bin/lamassu-batch-diagnostics
Normal file
36
packages/server/bin/lamassu-batch-diagnostics
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
require('../lib/environment-helper')
|
||||
|
||||
const db = require('../lib/db')
|
||||
const machineLoader = require('../lib/machine-loader')
|
||||
const operator = require('../lib/operator')
|
||||
|
||||
console.log('Running diagnostics on all paired devices...\n')
|
||||
|
||||
operator.getOperatorId('middleware')
|
||||
.then(operatorId => {
|
||||
if (!operatorId) {
|
||||
throw new Error('Operator ID not found in database')
|
||||
}
|
||||
|
||||
return db.any('SELECT device_id, name FROM devices')
|
||||
.then(devices => ({ operatorId, devices }))
|
||||
})
|
||||
.then(({ operatorId, devices }) => {
|
||||
if (devices.length === 0) {
|
||||
console.log('No paired devices found.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
const deviceIds = devices.map(d => d.device_id)
|
||||
return machineLoader.batchDiagnostics(deviceIds, operatorId)
|
||||
})
|
||||
.then(() => {
|
||||
console.log('\n✓ Diagnostics initiated for all devices. It can take a few minutes for the results to appear on the admin.')
|
||||
process.exit(0)
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('Error:', err.message)
|
||||
process.exit(1)
|
||||
})
|
||||
|
|
@ -13,11 +13,32 @@ const createMigration = `CREATE TABLE IF NOT EXISTS migrations (
|
|||
// no need to log the migration process
|
||||
process.env.SKIP_SERVER_LOGS = true
|
||||
|
||||
db.none(createMigration)
|
||||
.then(() => migrate.run())
|
||||
.then(() => {
|
||||
console.log('DB Migration succeeded.')
|
||||
process.exit(0)
|
||||
function checkPostgresVersion () {
|
||||
return db.one('SHOW server_version;')
|
||||
.then(result => {
|
||||
console.log(result)
|
||||
const versionString = result.server_version
|
||||
const match = versionString.match(/(\d+)\.(\d+)/i)
|
||||
if (!match) {
|
||||
throw new Error(`Could not parse PostgreSQL version: ${versionString}`)
|
||||
}
|
||||
return parseInt(match[1], 10)
|
||||
})
|
||||
}
|
||||
|
||||
checkPostgresVersion()
|
||||
.then(majorVersion => {
|
||||
if (majorVersion < 12) {
|
||||
console.error('PostgreSQL version must be 12 or higher. Current version:', majorVersion)
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
return db.none(createMigration)
|
||||
.then(() => migrate.run())
|
||||
.then(() => {
|
||||
console.log('DB Migration succeeded.')
|
||||
process.exit(0)
|
||||
})
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('DB Migration failed: %s', err)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ const logger = require('../logger')
|
|||
const settingsLoader = require('../new-settings-loader')
|
||||
const configManager = require('../new-config-manager')
|
||||
const notifier = require('../notifier')
|
||||
const constants = require('../constants')
|
||||
|
||||
const cashInAtomic = require('./cash-in-atomic')
|
||||
const cashInLow = require('./cash-in-low')
|
||||
|
|
@ -194,14 +195,27 @@ function postProcess(r, pi, isBlacklisted, addressReuse, walletScore) {
|
|||
})
|
||||
}
|
||||
|
||||
// This feels like it can be simplified,
|
||||
// but it's the most concise query to express the requirement and its edge cases.
|
||||
// At most only one authenticated customer can use an address.
|
||||
// If the current customer is anon, we can still allow one other customer to use the address,
|
||||
// So we count distinct customers plus the current customer if they are not anonymous.
|
||||
// To prevent malicious blocking of address, we only check for txs with actual fiat
|
||||
function doesTxReuseAddress(tx) {
|
||||
const sql = `
|
||||
SELECT EXISTS (
|
||||
SELECT DISTINCT to_address FROM (
|
||||
SELECT to_address FROM cash_in_txs WHERE id != $1
|
||||
) AS x WHERE to_address = $2
|
||||
)`
|
||||
return db.one(sql, [tx.id, tx.toAddress]).then(({ exists }) => exists)
|
||||
SELECT COUNT(*) > 1 as exists
|
||||
FROM (SELECT DISTINCT customer_id
|
||||
FROM cash_in_txs
|
||||
WHERE to_address = $1
|
||||
AND customer_id != $3
|
||||
AND fiat > 0
|
||||
UNION
|
||||
SELECT $2
|
||||
WHERE $2 != $3) t;
|
||||
`
|
||||
return db
|
||||
.one(sql, [tx.toAddress, tx.customerId, constants.anonymousCustomer.uuid])
|
||||
.then(({ exists }) => exists)
|
||||
}
|
||||
|
||||
function getWalletScore(tx, pi) {
|
||||
|
|
|
|||
|
|
@ -9,11 +9,12 @@
|
|||
*/
|
||||
const prepare_denominations = denominations =>
|
||||
JSON.parse(JSON.stringify(denominations))
|
||||
.sort(([d1], [d2]) => d1 < d2)
|
||||
.sort(([d1], [d2]) => d2 - d1)
|
||||
.reduce(
|
||||
([csum, denoms], [denom, count]) => {
|
||||
csum += denom * count
|
||||
return [csum, [{ denom, count, csum }].concat(denoms)]
|
||||
denoms.push({ denom, count, csum })
|
||||
return [csum, denoms]
|
||||
},
|
||||
[0, []],
|
||||
)[1] /* ([csum, denoms]) => denoms */
|
||||
|
|
|
|||
|
|
@ -8,16 +8,17 @@ const fs = require('fs')
|
|||
const util = require('util')
|
||||
|
||||
const db = require('./db')
|
||||
const anonymous = require('../lib/constants').anonymousCustomer
|
||||
const complianceOverrides = require('./compliance_overrides')
|
||||
const writeFile = util.promisify(fs.writeFile)
|
||||
const notifierQueries = require('./notifier/queries')
|
||||
const notifierUtils = require('./notifier/utils')
|
||||
const NUM_RESULTS = 1000
|
||||
const sms = require('./sms')
|
||||
const settingsLoader = require('./new-settings-loader')
|
||||
const logger = require('./logger')
|
||||
const externalCompliance = require('./compliance-external')
|
||||
const {
|
||||
customers: { getCustomerList },
|
||||
} = require('typesafe-db')
|
||||
|
||||
const { APPROVED, RETRY } = require('./plugins/compliance/consts')
|
||||
|
||||
|
|
@ -483,28 +484,6 @@ function addComplianceOverrides(id, customer, userToken) {
|
|||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Query all customers
|
||||
*
|
||||
* Add status as computed column,
|
||||
* which will indicate the name of the latest
|
||||
* compliance verfication completed by user.
|
||||
*
|
||||
* @returns {array} Array of customers populated with status field
|
||||
*/
|
||||
function batch() {
|
||||
const sql = `select * from customers
|
||||
where id != $1
|
||||
order by created desc limit $2`
|
||||
return db.any(sql, [anonymous.uuid, NUM_RESULTS]).then(customers =>
|
||||
Promise.all(
|
||||
_.map(customer => {
|
||||
return getCustomInfoRequestsData(customer).then(camelize)
|
||||
}, customers),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
function getSlimCustomerByIdBatch(ids) {
|
||||
const sql = `SELECT id, phone, id_card_data
|
||||
FROM customers
|
||||
|
|
@ -512,88 +491,8 @@ function getSlimCustomerByIdBatch(ids) {
|
|||
return db.any(sql, [ids]).then(customers => _.map(camelize, customers))
|
||||
}
|
||||
|
||||
// TODO: getCustomersList and getCustomerById are very similar, so this should be refactored
|
||||
|
||||
/**
|
||||
* Query all customers, ordered by last activity
|
||||
* and with aggregate columns based on their
|
||||
* transactions
|
||||
*
|
||||
* @returns {array} Array of customers with it's transactions aggregations
|
||||
*/
|
||||
|
||||
function getCustomersList(
|
||||
phone = null,
|
||||
name = null,
|
||||
address = null,
|
||||
id = null,
|
||||
email = null,
|
||||
) {
|
||||
const passableErrorCodes = _.map(
|
||||
Pgp.as.text,
|
||||
TX_PASSTHROUGH_ERROR_CODES,
|
||||
).join(',')
|
||||
|
||||
const sql = `SELECT id, authorized_override, days_suspended, is_suspended, front_camera_path, front_camera_override,
|
||||
phone, email, sms_override, id_card_data, id_card_data_override, id_card_data_expiration,
|
||||
id_card_photo_path, id_card_photo_override, us_ssn, us_ssn_override, sanctions, sanctions_at,
|
||||
sanctions_override, total_txs, total_spent, GREATEST(created, last_transaction, last_data_provided, last_auth_attempt) AS last_active, fiat AS last_tx_fiat,
|
||||
fiat_code AS last_tx_fiat_code, tx_class AS last_tx_class, custom_fields, notes, is_test_customer
|
||||
FROM (
|
||||
SELECT c.id, c.authorized_override,
|
||||
greatest(0, date_part('day', c.suspended_until - NOW())) AS days_suspended,
|
||||
c.suspended_until > NOW() AS is_suspended,
|
||||
c.front_camera_path, c.front_camera_override,
|
||||
c.phone, c.email, c.sms_override, c.id_card_data, c.id_card_data_override, c.id_card_data_expiration,
|
||||
c.id_card_photo_path, c.id_card_photo_override, c.us_ssn, c.us_ssn_override, c.sanctions, c.last_auth_attempt,
|
||||
GREATEST(c.phone_at, c.email_at, c.id_card_data_at, c.front_camera_at, c.id_card_photo_at, c.us_ssn_at) AS last_data_provided,
|
||||
c.sanctions_at, c.sanctions_override, c.is_test_customer, c.created, t.tx_class, t.fiat, t.fiat_code, t.created as last_transaction, cn.notes,
|
||||
row_number() OVER (partition by c.id order by t.created desc) AS rn,
|
||||
sum(CASE WHEN t.id IS NOT NULL THEN 1 ELSE 0 END) OVER (partition by c.id) AS total_txs,
|
||||
coalesce(sum(CASE WHEN error_code IS NULL OR error_code NOT IN ($1^) THEN t.fiat ELSE 0 END) OVER (partition by c.id), 0) AS total_spent, ccf.custom_fields
|
||||
FROM customers c LEFT OUTER JOIN (
|
||||
SELECT 'cashIn' AS tx_class, id, fiat, fiat_code, created, customer_id, error_code
|
||||
FROM cash_in_txs WHERE send_confirmed = true OR batched = true UNION
|
||||
SELECT 'cashOut' AS tx_class, id, fiat, fiat_code, created, customer_id, error_code
|
||||
FROM cash_out_txs WHERE confirmed_at IS NOT NULL) AS t ON c.id = t.customer_id
|
||||
LEFT OUTER JOIN (
|
||||
SELECT cf.customer_id, json_agg(json_build_object('id', cf.custom_field_id, 'label', cf.label, 'value', cf.value)) AS custom_fields FROM (
|
||||
SELECT ccfp.custom_field_id, ccfp.customer_id, cfd.label, ccfp.value FROM custom_field_definitions cfd
|
||||
LEFT OUTER JOIN customer_custom_field_pairs ccfp ON cfd.id = ccfp.custom_field_id
|
||||
) cf GROUP BY cf.customer_id
|
||||
) ccf ON c.id = ccf.customer_id
|
||||
LEFT OUTER JOIN (
|
||||
SELECT customer_id, coalesce(json_agg(customer_notes.*), '[]'::json) AS notes FROM customer_notes
|
||||
GROUP BY customer_notes.customer_id
|
||||
) cn ON c.id = cn.customer_id
|
||||
WHERE c.id != $2
|
||||
) AS cl WHERE rn = 1
|
||||
AND ($4 IS NULL OR phone = $4)
|
||||
AND ($5 IS NULL OR CONCAT(id_card_data::json->>'firstName', ' ', id_card_data::json->>'lastName') = $5 OR id_card_data::json->>'firstName' = $5 OR id_card_data::json->>'lastName' = $5)
|
||||
AND ($6 IS NULL OR id_card_data::json->>'address' = $6)
|
||||
AND ($7 IS NULL OR id_card_data::json->>'documentNumber' = $7)
|
||||
AND ($8 IS NULL OR email = $8)
|
||||
ORDER BY last_active DESC
|
||||
limit $3`
|
||||
return db
|
||||
.any(sql, [
|
||||
passableErrorCodes,
|
||||
anonymous.uuid,
|
||||
NUM_RESULTS,
|
||||
phone,
|
||||
name,
|
||||
address,
|
||||
id,
|
||||
email,
|
||||
])
|
||||
.then(customers =>
|
||||
Promise.all(
|
||||
_.map(
|
||||
customer => getCustomInfoRequestsData(customer).then(camelizeDeep),
|
||||
customers,
|
||||
),
|
||||
),
|
||||
)
|
||||
function getCustomersList() {
|
||||
return getCustomerList({ withCustomInfoRequest: true })
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -1081,12 +980,10 @@ function notifyApprovedExternalCompliance(settings, customerId) {
|
|||
|
||||
function checkExternalCompliance(settings) {
|
||||
return getOpenExternalCompliance().then(externals => {
|
||||
console.log(externals)
|
||||
const promises = _.map(external => {
|
||||
return externalCompliance
|
||||
.getStatus(settings, external.service, external.customer_id)
|
||||
.then(status => {
|
||||
console.log('status', status, external.customer_id, external.service)
|
||||
if (status.status.answer === RETRY)
|
||||
notifyRetryExternalCompliance(
|
||||
settings,
|
||||
|
|
@ -1112,12 +1009,16 @@ function addExternalCompliance(customerId, service, id) {
|
|||
return db.none(sql, [customerId, id, service])
|
||||
}
|
||||
|
||||
function getLastUsedAddress(id, cryptoCode) {
|
||||
const sql = `SELECT to_address FROM cash_in_txs WHERE customer_id=$1 AND crypto_code=$2 AND fiat > 0 ORDER BY created DESC LIMIT 1`
|
||||
return db.oneOrNone(sql, [id, cryptoCode]).then(it => it?.to_address)
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
add,
|
||||
addWithEmail,
|
||||
get,
|
||||
getWithEmail,
|
||||
batch,
|
||||
getSlimCustomerByIdBatch,
|
||||
getCustomersList,
|
||||
getCustomerById,
|
||||
|
|
@ -1139,4 +1040,5 @@ module.exports = {
|
|||
updateLastAuthAttempt,
|
||||
addExternalCompliance,
|
||||
checkExternalCompliance,
|
||||
getLastUsedAddress,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ const eventBus = require('./event-bus')
|
|||
const DATABASE_NOT_REACHABLE = 'Database not reachable.'
|
||||
|
||||
const pgp = Pgp({
|
||||
pgNative: true,
|
||||
schema: 'public',
|
||||
error: (err, e) => {
|
||||
if (e.cn) logger.error(DATABASE_NOT_REACHABLE)
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ function getBitPayFxRate(
|
|||
fiatCodeProperty,
|
||||
rateProperty,
|
||||
) {
|
||||
return getFiatRates().then(({ data: fxRates }) => {
|
||||
return getFiatRates().then(fxRates => {
|
||||
const defaultFiatRate = findCurrencyRates(
|
||||
fxRates,
|
||||
defaultFiatMarket,
|
||||
|
|
@ -69,14 +69,15 @@ const getRate = (retries = 1, fiatCode, defaultFiatMarket) => {
|
|||
defaultFiatMarket,
|
||||
fiatCodeProperty,
|
||||
rateProperty,
|
||||
).catch(() => {
|
||||
// Switch service
|
||||
).catch(err => {
|
||||
const erroredService = API_QUEUE.shift()
|
||||
API_QUEUE.push(erroredService)
|
||||
if (retries >= MAX_ROTATIONS)
|
||||
throw new Error(`FOREX API error from ${erroredService.name}`)
|
||||
throw new Error(
|
||||
`FOREX API error from ${erroredService.name} ${err?.message}`,
|
||||
)
|
||||
|
||||
return getRate(++retries, fiatCode)
|
||||
return getRate(++retries, fiatCode, defaultFiatMarket)
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -523,6 +523,43 @@ function diagnostics(rec) {
|
|||
)
|
||||
}
|
||||
|
||||
function batchDiagnostics(deviceIds, operatorId) {
|
||||
const diagnosticsDir = `${OPERATOR_DATA_DIR}/diagnostics/`
|
||||
|
||||
const removeDir = fsPromises
|
||||
.rm(diagnosticsDir, { recursive: true })
|
||||
.catch(err => {
|
||||
if (err.code !== 'ENOENT') {
|
||||
throw err
|
||||
}
|
||||
})
|
||||
|
||||
const sql = `UPDATE devices
|
||||
SET diagnostics_timestamp = NULL,
|
||||
diagnostics_scan_updated_at = NULL,
|
||||
diagnostics_front_updated_at = NULL
|
||||
WHERE device_id = ANY($1)`
|
||||
|
||||
// Send individual notifications for each machine
|
||||
const sendNotifications = deviceIds.map(deviceId =>
|
||||
db.none('NOTIFY $1:name, $2', [
|
||||
'machineAction',
|
||||
JSON.stringify({
|
||||
action: 'diagnostics',
|
||||
value: {
|
||||
deviceId,
|
||||
operatorId,
|
||||
action: 'diagnostics',
|
||||
},
|
||||
}),
|
||||
]),
|
||||
)
|
||||
|
||||
return removeDir
|
||||
.then(() => db.none(sql, [deviceIds]))
|
||||
.then(() => Promise.all(sendNotifications))
|
||||
}
|
||||
|
||||
function setMachine(rec, operatorId) {
|
||||
rec.operatorId = operatorId
|
||||
switch (rec.action) {
|
||||
|
|
@ -681,4 +718,5 @@ module.exports = {
|
|||
refillMachineUnits,
|
||||
updateDiagnostics,
|
||||
updateFailedQRScans,
|
||||
batchDiagnostics,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,18 +27,5 @@ function transaction() {
|
|||
|
||||
return db.any(sql)
|
||||
}
|
||||
function customer() {
|
||||
const sql = `SELECT DISTINCT * FROM (
|
||||
SELECT 'phone' AS type, phone AS value FROM customers WHERE phone IS NOT NULL UNION
|
||||
SELECT 'email' AS type, email AS value FROM customers WHERE email IS NOT NULL UNION
|
||||
SELECT 'name' AS type, id_card_data::json->>'firstName' AS value FROM customers WHERE id_card_data::json->>'firstName' IS NOT NULL AND id_card_data::json->>'lastName' IS NULL UNION
|
||||
SELECT 'name' AS type, id_card_data::json->>'lastName' AS value FROM customers WHERE id_card_data::json->>'firstName' IS NULL AND id_card_data::json->>'lastName' IS NOT NULL UNION
|
||||
SELECT 'name' AS type, concat(id_card_data::json->>'firstName', ' ', id_card_data::json->>'lastName') AS value FROM customers WHERE id_card_data::json->>'firstName' IS NOT NULL AND id_card_data::json->>'lastName' IS NOT NULL UNION
|
||||
SELECT 'address' as type, id_card_data::json->>'address' AS value FROM customers WHERE id_card_data::json->>'address' IS NOT NULL UNION
|
||||
SELECT 'id' AS type, id_card_data::json->>'documentNumber' AS value FROM customers WHERE id_card_data::json->>'documentNumber' IS NOT NULL
|
||||
) f`
|
||||
|
||||
return db.any(sql)
|
||||
}
|
||||
|
||||
module.exports = { transaction, customer }
|
||||
module.exports = { transaction }
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
const authentication = require('../modules/userManagement')
|
||||
const anonymous = require('../../../constants').anonymousCustomer
|
||||
const customers = require('../../../customers')
|
||||
const filters = require('../../filters')
|
||||
const customerNotes = require('../../../customer-notes')
|
||||
const machineLoader = require('../../../machine-loader')
|
||||
|
||||
|
|
@ -18,11 +17,9 @@ const resolvers = {
|
|||
isAnonymous: parent => parent.customerId === anonymous.uuid,
|
||||
},
|
||||
Query: {
|
||||
customers: (...[, { phone, email, name, address, id }]) =>
|
||||
customers.getCustomersList(phone, name, address, id, email),
|
||||
customers: () => customers.getCustomersList(),
|
||||
customer: (...[, { customerId }]) =>
|
||||
customers.getCustomerById(customerId).then(addLastUsedMachineName),
|
||||
customerFilters: () => filters.customer(),
|
||||
},
|
||||
Mutation: {
|
||||
setCustomer: (root, { customerId, customerInput }, context) => {
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ function ticker(fiatCode, cryptoCode, tickerName) {
|
|||
return getCurrencyRates(ticker, fiatCode, cryptoCode)
|
||||
}
|
||||
|
||||
return getRate(RETRIES, tickerName, defaultFiatMarket(tickerName)).then(
|
||||
return getRate(RETRIES, fiatCode, defaultFiatMarket(tickerName)).then(
|
||||
({ fxRate }) => {
|
||||
try {
|
||||
return getCurrencyRates(
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ const loadRoutes = async () => {
|
|||
app.use(compression({ threshold: 500 }))
|
||||
app.use(helmet())
|
||||
app.use(nocache())
|
||||
app.use(express.json({ limit: '2mb' }))
|
||||
app.use(express.json({ limit: '25mb' }))
|
||||
|
||||
morgan.token('bytesRead', (_req, res) => res.bytesRead)
|
||||
morgan.token('bytesWritten', (_req, res) => res.bytesWritten)
|
||||
|
|
|
|||
|
|
@ -311,7 +311,13 @@ function getExternalComplianceLink(req, res, next) {
|
|||
.then(url => respond(req, res, { url }))
|
||||
}
|
||||
|
||||
function addOrUpdateCustomer(customerData, deviceId, config, isEmailAuth) {
|
||||
function addOrUpdateCustomer(
|
||||
customerData,
|
||||
deviceId,
|
||||
config,
|
||||
isEmailAuth,
|
||||
cryptoCode,
|
||||
) {
|
||||
const triggers = configManager.getTriggers(config)
|
||||
const maxDaysThreshold = complianceTriggers.maxDaysThreshold(triggers)
|
||||
|
||||
|
|
@ -346,6 +352,18 @@ function addOrUpdateCustomer(customerData, deviceId, config, isEmailAuth) {
|
|||
.getCustomerActiveIndividualDiscount(customer.id)
|
||||
.then(discount => ({ ...customer, discount }))
|
||||
})
|
||||
.then(customer => {
|
||||
const enableLastUsedAddress = !!configManager.getWalletSettings(
|
||||
cryptoCode,
|
||||
config,
|
||||
).enableLastUsedAddress
|
||||
if (!cryptoCode || !enableLastUsedAddress) return customer
|
||||
return customers
|
||||
.getLastUsedAddress(customer.id, cryptoCode)
|
||||
.then(lastUsedAddress => {
|
||||
return { ...customer, lastUsedAddress }
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function getOrAddCustomerPhone(req, res, next) {
|
||||
|
|
@ -354,6 +372,7 @@ function getOrAddCustomerPhone(req, res, next) {
|
|||
|
||||
const pi = plugins(req.settings, deviceId)
|
||||
const phone = req.body.phone
|
||||
const cryptoCode = req.query.cryptoCode
|
||||
|
||||
return pi
|
||||
.getPhoneCode(phone)
|
||||
|
|
@ -363,6 +382,7 @@ function getOrAddCustomerPhone(req, res, next) {
|
|||
deviceId,
|
||||
req.settings.config,
|
||||
false,
|
||||
cryptoCode,
|
||||
).then(customer => respond(req, res, { code, customer }))
|
||||
})
|
||||
.catch(err => {
|
||||
|
|
@ -375,6 +395,7 @@ function getOrAddCustomerPhone(req, res, next) {
|
|||
function getOrAddCustomerEmail(req, res, next) {
|
||||
const deviceId = req.deviceId
|
||||
const customerData = req.body
|
||||
const cryptoCode = req.query.cryptoCode
|
||||
|
||||
const pi = plugins(req.settings, req.deviceId)
|
||||
const email = req.body.email
|
||||
|
|
@ -387,6 +408,7 @@ function getOrAddCustomerEmail(req, res, next) {
|
|||
deviceId,
|
||||
req.settings.config,
|
||||
true,
|
||||
cryptoCode,
|
||||
).then(customer => respond(req, res, { code, customer }))
|
||||
})
|
||||
.catch(err => {
|
||||
|
|
|
|||
|
|
@ -53,7 +53,12 @@ function getTx(req, res, next) {
|
|||
return helpers
|
||||
.fetchStatusTx(req.params.id, req.query.status)
|
||||
.then(r => res.json(r))
|
||||
.catch(next)
|
||||
.catch(err => {
|
||||
if (err.name === 'HTTPError') {
|
||||
return res.status(err.code).send(err.message)
|
||||
}
|
||||
next(err)
|
||||
})
|
||||
}
|
||||
|
||||
return next(httpError('Not Found', 404))
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ const T = require('./time')
|
|||
// FP operations on Postgres result in very big errors.
|
||||
// E.g.: 1853.013808 * 1000 = 1866149.494
|
||||
const REDEEMABLE_AGE = T.day / 1000
|
||||
const MAX_THRESHOLD_DAYS = 365 * 50 // 50 years maximum
|
||||
|
||||
function process(tx, pi) {
|
||||
const mtx = massage(tx)
|
||||
|
|
@ -92,7 +93,9 @@ function customerHistory(customerId, thresholdDays) {
|
|||
AND fiat > 0
|
||||
) ch WHERE NOT ch.expired ORDER BY ch.created`
|
||||
|
||||
const days = _.isNil(thresholdDays) ? 0 : thresholdDays
|
||||
const days = _.isNil(thresholdDays)
|
||||
? 0
|
||||
: Math.min(thresholdDays, MAX_THRESHOLD_DAYS)
|
||||
return db.any(sql, [customerId, `${days} days`, '60 minutes', REDEEMABLE_AGE])
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,12 @@
|
|||
const db = require('./db')
|
||||
|
||||
exports.up = next =>
|
||||
db.multi(
|
||||
[
|
||||
`CREATE INDEX cash_in_txs_customer_id_idx ON cash_in_txs (customer_id);`,
|
||||
`CREATE INDEX cash_out_txs_customer_id_idx ON cash_out_txs (customer_id);`,
|
||||
],
|
||||
next,
|
||||
)
|
||||
|
||||
exports.down = next => next()
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
const { saveConfig } = require('../lib/new-settings-loader')
|
||||
|
||||
exports.up = function (next) {
|
||||
const newConfig = {
|
||||
wallets_advanced_enableLastUsedAddress: false,
|
||||
}
|
||||
return saveConfig(newConfig)
|
||||
.then(next)
|
||||
.catch(err => {
|
||||
return next(err)
|
||||
})
|
||||
}
|
||||
|
||||
module.exports.down = function (next) {
|
||||
next()
|
||||
}
|
||||
|
|
@ -76,7 +76,6 @@
|
|||
"p-each-series": "^1.0.0",
|
||||
"p-queue": "^6.6.2",
|
||||
"p-retry": "^4.4.0",
|
||||
"pg-native": "^3.0.0",
|
||||
"pg-promise": "^10.10.2",
|
||||
"pify": "^3.0.0",
|
||||
"pretty-ms": "^2.1.0",
|
||||
|
|
@ -89,6 +88,7 @@
|
|||
"telnyx": "^1.25.5",
|
||||
"tronweb": "^5.3.0",
|
||||
"twilio": "^3.6.1",
|
||||
"typesafe-db": "workspace:*",
|
||||
"uuid": "8.3.2",
|
||||
"web3": "1.7.1",
|
||||
"winston": "^2.4.2",
|
||||
|
|
@ -123,34 +123,16 @@
|
|||
"lamassu-eth-recovery": "./bin/lamassu-eth-recovery",
|
||||
"lamassu-trx-recovery": "./bin/lamassu-trx-recovery",
|
||||
"lamassu-update-cassettes": "./bin/lamassu-update-cassettes",
|
||||
"lamassu-clean-parsed-id": "./bin/lamassu-clean-parsed-id"
|
||||
"lamassu-clean-parsed-id": "./bin/lamassu-clean-parsed-id",
|
||||
"lamassu-batch-diagnostics": "./bin/lamassu-batch-diagnostics"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "node bin/lamassu-server",
|
||||
"test": "mocha --recursive tests",
|
||||
"jtest": "jest --detectOpenHandles",
|
||||
"build-admin": "npm run build-admin:css && npm run build-admin:main && npm run build-admin:lamassu",
|
||||
"server": "nodemon bin/lamassu-server --mockScoring --logLevel silly",
|
||||
"admin-server": "nodemon bin/lamassu-admin-server --dev --logLevel silly",
|
||||
"watch": "concurrently \"npm:server\" \"npm:admin-server\"",
|
||||
"dev": "concurrently \"npm:server\" \"npm:admin-server\"",
|
||||
"server": "node --watch bin/lamassu-server --mockScoring --logLevel silly",
|
||||
"admin-server": "node --watch bin/lamassu-admin-server --dev --logLevel silly",
|
||||
"stress-test": "cd tests/stress/ && node index.js 50 -v"
|
||||
},
|
||||
"nodemonConfig": {
|
||||
"ignore": [
|
||||
"new-lamassu-admin/*"
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^5.3.0",
|
||||
"jest": "^26.6.3",
|
||||
"nodemon": "^2.0.6",
|
||||
"standard": "^12.0.1"
|
||||
},
|
||||
"standard": {
|
||||
"ignore": [
|
||||
"/lamassu-admin-elm",
|
||||
"/public",
|
||||
"/new-lamassu-admin"
|
||||
]
|
||||
"concurrently": "^5.3.0"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,14 +22,14 @@ setEnvVariable('KEY_PATH', `${process.env.PWD}/certs/Lamassu_OP.key`)
|
|||
|
||||
setEnvVariable(
|
||||
'MNEMONIC_PATH',
|
||||
`${process.env.HOME}/.lamassu/mnemonics/mnemonic.txt`,
|
||||
`${process.env.PWD}/.lamassu/mnemonics/mnemonic.txt`,
|
||||
)
|
||||
|
||||
setEnvVariable('BLOCKCHAIN_DIR', `${process.env.PWD}/blockchains`)
|
||||
setEnvVariable('OFAC_DATA_DIR', `${process.env.HOME}/.lamassu/ofac`)
|
||||
setEnvVariable('ID_PHOTO_CARD_DIR', `${process.env.HOME}/.lamassu/idphotocard`)
|
||||
setEnvVariable('FRONT_CAMERA_DIR', `${process.env.HOME}/.lamassu/frontcamera`)
|
||||
setEnvVariable('OPERATOR_DATA_DIR', `${process.env.HOME}/.lamassu/operatordata`)
|
||||
setEnvVariable('OFAC_DATA_DIR', `${process.env.PWD}/.lamassu/ofac`)
|
||||
setEnvVariable('ID_PHOTO_CARD_DIR', `${process.env.PWD}/.lamassu/idphotocard`)
|
||||
setEnvVariable('FRONT_CAMERA_DIR', `${process.env.PWD}/.lamassu/frontcamera`)
|
||||
setEnvVariable('OPERATOR_DATA_DIR', `${process.env.PWD}/.lamassu/operatordata`)
|
||||
|
||||
setEnvVariable('BTC_NODE_LOCATION', 'remote')
|
||||
setEnvVariable('BTC_WALLET_LOCATION', 'local')
|
||||
|
|
|
|||
|
|
@ -5,11 +5,13 @@ set -e
|
|||
DOMAIN=localhost
|
||||
[ ! -z "$1" ] && DOMAIN=$1
|
||||
|
||||
CONFIG_DIR=$HOME/.lamassu
|
||||
SERVER_DIR="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
CONFIG_DIR=$SERVER_DIR/.lamassu
|
||||
LOG_FILE=/tmp/cert-gen.log
|
||||
CERT_DIR=$PWD/certs
|
||||
KEY_DIR=$PWD/certs
|
||||
LAMASSU_CA_PATH=$PWD/Lamassu_CA.pem
|
||||
CERT_DIR=$SERVER_DIR/certs
|
||||
KEY_DIR=$SERVER_DIR/certs
|
||||
LAMASSU_CA_PATH=$SERVER_DIR/Lamassu_CA.pem
|
||||
POSTGRES_PASS=postgres123
|
||||
OFAC_DATA_DIR=$CONFIG_DIR/ofac
|
||||
IDPHOTOCARD_DIR=$CONFIG_DIR/idphotocard
|
||||
|
|
@ -24,7 +26,7 @@ MNEMONIC_DIR=$CONFIG_DIR/mnemonics
|
|||
MNEMONIC_FILE=$MNEMONIC_DIR/mnemonic.txt
|
||||
mkdir -p $MNEMONIC_DIR >> $LOG_FILE 2>&1
|
||||
SEED=$(openssl rand -hex 32)
|
||||
MNEMONIC=$($PWD/bin/bip39 $SEED)
|
||||
MNEMONIC=$($SERVER_DIR/bin/bip39 $SEED)
|
||||
echo "$MNEMONIC" > $MNEMONIC_FILE
|
||||
|
||||
echo "Generating SSL certificates..."
|
||||
|
|
@ -90,6 +92,6 @@ rm /tmp/Lamassu_OP.csr.pem
|
|||
mkdir -p $OFAC_DATA_DIR/sources
|
||||
touch $OFAC_DATA_DIR/etags.json
|
||||
|
||||
node tools/build-dev-env.js
|
||||
(cd $SERVER_DIR && node tools/build-dev-env.js)
|
||||
|
||||
echo "Done."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue