From ca68fdd0a20a90d74164e55a3de360888a41dec8 Mon Sep 17 00:00:00 2001 From: Rafael Taranto Date: Thu, 27 Mar 2025 12:22:46 +0000 Subject: [PATCH] chore: deprecate old migrations --- lib/admin/config-manager.js | 67 -- lib/admin/config-validate.js | 191 ---- lib/admin/config.js | 230 ---- lib/admin/lamassu-schema.json | 1011 ----------------- lib/admin/settings-loader.js | 250 ---- lib/config-migration.js | 477 -------- lib/machine-loader.js | 2 + lib/new-admin/middlewares/context.js | 1 - ...09439657189-add_machine_name_to_devices.js | 26 +- migrations/1599523522436-migrate-config.js | 35 +- migrations/migrate-tools.js | 16 - new-lamassu-admin/package.json | 1 - package.json | 1 - 13 files changed, 15 insertions(+), 2293 deletions(-) delete mode 100644 lib/admin/config-manager.js delete mode 100644 lib/admin/config-validate.js delete mode 100644 lib/admin/config.js delete mode 100644 lib/admin/lamassu-schema.json delete mode 100644 lib/admin/settings-loader.js delete mode 100644 lib/config-migration.js delete mode 100644 migrations/migrate-tools.js diff --git a/lib/admin/config-manager.js b/lib/admin/config-manager.js deleted file mode 100644 index bbf94c9c..00000000 --- a/lib/admin/config-manager.js +++ /dev/null @@ -1,67 +0,0 @@ -const _ = require('lodash/fp') - -module.exports = { - unscoped, - cryptoScoped, - machineScoped, - scoped, - scopedValue, - all -} - -function matchesValue (crypto, machine, instance) { - return instance.fieldLocator.fieldScope.crypto === crypto && - instance.fieldLocator.fieldScope.machine === machine -} - -function permutations (crypto, machine) { - return _.uniq([ - [crypto, machine], - [crypto, 'global'], - ['global', machine], - ['global', 'global'] - ]) -} - -function fallbackValue (crypto, machine, instances) { - const notNil = _.negate(_.isNil) - const pickValue = arr => _.find(instance => matchesValue(arr[0], arr[1], instance), instances) - const fallbackRec = _.find(notNil, _.map(pickValue, permutations(crypto, machine))) - return fallbackRec && fallbackRec.fieldValue.value -} - -function scopedValue (crypto, machine, fieldCode, config) { - const allScopes = config.filter(_.pathEq(['fieldLocator', 'code'], fieldCode)) - - return fallbackValue(crypto, machine, allScopes) -} - -function generalScoped (crypto, machine, config) { - const localScopedValue = key => - scopedValue(crypto, machine, key, config) - - const keys = _.uniq(_.map(r => r.fieldLocator.code, config)) - const keyedValues = keys.map(localScopedValue) - - return _.zipObject(keys, keyedValues) -} - -function machineScoped (machine, config) { - return generalScoped('global', machine, config) -} - -function unscoped (config) { - return generalScoped('global', 'global', config) -} - -function cryptoScoped (crypto, config) { - return generalScoped(crypto, 'global', config) -} - -function scoped (crypto, machine, config) { - return generalScoped(crypto, machine, config) -} - -function all (code, config) { - return _.uniq(_.map('fieldValue.value', _.filter(i => i.fieldLocator.code === code, config))) -} diff --git a/lib/admin/config-validate.js b/lib/admin/config-validate.js deleted file mode 100644 index 1bc04b3f..00000000 --- a/lib/admin/config-validate.js +++ /dev/null @@ -1,191 +0,0 @@ -const _ = require('lodash/fp') - -const db = require('../db') -const configManager = require('./config-manager') -const logger = require('../logger') -const schema = require('./lamassu-schema.json') - -const REMOVED_FIELDS = ['crossRefVerificationActive', 'crossRefVerificationThreshold'] - -const SETTINGS_LOADER_SCHEMA_VERSION = 1 - -function allScopes (cryptoScopes, machineScopes) { - const scopes = [] - cryptoScopes.forEach(c => { - machineScopes.forEach(m => scopes.push([c, m])) - }) - - return scopes -} - -function allCryptoScopes (cryptos, cryptoScope) { - const cryptoScopes = [] - - if (cryptoScope === 'global' || cryptoScope === 'both') cryptoScopes.push('global') - if (cryptoScope === 'specific' || cryptoScope === 'both') cryptos.forEach(r => cryptoScopes.push(r)) - - return cryptoScopes -} - -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 satisfiesRequire (config, cryptos, machineList, field, anyFields, allFields) { - const fieldCode = field.code - - const scopes = allScopes( - allCryptoScopes(cryptos, field.cryptoScope), - allMachineScopes(machineList, field.machineScope) - ) - - return scopes.every(scope => { - const isAnyEnabled = () => _.some(refField => { - return isScopeEnabled(config, cryptos, machineList, refField, scope) - }, anyFields) - - const areAllEnabled = () => _.every(refField => { - return isScopeEnabled(config, cryptos, machineList, refField, scope) - }, allFields) - - const isBlank = _.isNil(configManager.scopedValue(scope[0], scope[1], fieldCode, config)) - const isRequired = (_.isEmpty(anyFields) || isAnyEnabled()) && - (_.isEmpty(allFields) || areAllEnabled()) - const hasDefault = !_.isNil(_.get('default', field)) - - const isValid = !isRequired || !isBlank || hasDefault - - return isValid - }) -} - -function isScopeEnabled (config, cryptos, machineList, refField, scope) { - const [cryptoScope, machineScope] = scope - const candidateCryptoScopes = cryptoScope === 'global' - ? allCryptoScopes(cryptos, refField.cryptoScope) - : [cryptoScope] - - const candidateMachineScopes = machineScope === 'global' - ? allMachineScopes(machineList, refField.machineScope) - : [ machineScope ] - - const allRefCandidateScopes = allScopes(candidateCryptoScopes, candidateMachineScopes) - const getFallbackValue = scope => configManager.scopedValue(scope[0], scope[1], refField.code, config) - const values = allRefCandidateScopes.map(getFallbackValue) - - return values.some(r => r) -} - -function getCryptos (config, machineList) { - const scopes = allScopes(['global'], allMachineScopes(machineList, 'both')) - const scoped = scope => configManager.scopedValue(scope[0], scope[1], 'cryptoCurrencies', config) - return scopes.reduce((acc, scope) => _.union(acc, scoped(scope)), []) -} - -function getGroup (fieldCode) { - return _.find(group => _.includes(fieldCode, group.fields), schema.groups) -} - -function getField (fieldCode) { - const group = getGroup(fieldCode) - return getGroupField(group, fieldCode) -} - -function getGroupField (group, fieldCode) { - const field = _.find(_.matchesProperty('code', fieldCode), schema.fields) - return _.merge(_.pick(['cryptoScope', 'machineScope'], group), field) -} - -// Note: We can't use machine-loader because it relies on settings-loader, -// which relies on this -function getMachines () { - return db.any('select device_id from devices') -} - -function fetchMachines () { - return getMachines() - .then(machineList => machineList.map(r => r.device_id)) -} - -function validateFieldParameter (value, validator) { - switch (validator.code) { - case 'required': - return true // We don't validate this here - case 'min': - return value >= validator.min - case 'max': - return value <= validator.max - default: - throw new Error('Unknown validation type: ' + validator.code) - } -} - -function ensureConstraints (config) { - const pickField = fieldCode => schema.fields.find(r => r.code === fieldCode) - - return Promise.resolve() - .then(() => { - config.every(fieldInstance => { - const fieldCode = fieldInstance.fieldLocator.code - if (_.includes(fieldCode, REMOVED_FIELDS)) return - - const field = pickField(fieldCode) - if (!field) { - logger.warn('No such field: %s, %j', fieldCode, fieldInstance.fieldLocator.fieldScope) - return - } - - const fieldValue = fieldInstance.fieldValue - - const isValid = field.fieldValidation - .every(validator => validateFieldParameter(fieldValue.value, validator)) - - if (isValid) return true - throw new Error('Invalid config value') - }) - }) -} - -function validateRequires (config) { - return fetchMachines() - .then(machineList => { - const cryptos = getCryptos(config, machineList) - - return schema.groups.filter(group => { - return group.fields.some(fieldCode => { - const field = getGroupField(group, fieldCode) - - if (!field.fieldValidation.find(r => r.code === 'required')) return false - - const refFieldsAny = _.map(_.partial(getField, group), field.enabledIfAny) - const refFieldsAll = _.map(_.partial(getField, group), field.enabledIfAll) - const isInvalid = !satisfiesRequire(config, cryptos, machineList, field, refFieldsAny, refFieldsAll) - - return isInvalid - }) - }) - }) - .then(arr => arr.map(r => r.code)) -} - -function validate (config) { - return Promise.resolve() - .then(() => ensureConstraints(config)) - .then(() => validateRequires(config)) - .then(arr => { - if (arr.length === 0) return config - throw new Error('Invalid configuration:' + arr) - }) -} - -module.exports = { - SETTINGS_LOADER_SCHEMA_VERSION, - validate, - ensureConstraints, - validateRequires -} diff --git a/lib/admin/config.js b/lib/admin/config.js deleted file mode 100644 index 441a17b2..00000000 --- a/lib/admin/config.js +++ /dev/null @@ -1,230 +0,0 @@ -const _ = require('lodash/fp') -const devMode = require('minimist')(process.argv.slice(2)).dev - -const currencies = require('../new-admin/config/data/currencies.json') -const languageRec = require('../new-admin/config/data/languages.json') -const countries = require('../new-admin/config/data/countries.json') -const machineLoader = require('../machine-loader') - -const configManager = require('./config-manager') - -const db = require('../db') -const settingsLoader = require('./settings-loader') -const configValidate = require('./config-validate') -const jsonSchema = require('./lamassu-schema.json') - - -function fetchSchema () { - return _.cloneDeep(jsonSchema) -} - -function fetchConfig () { - const sql = `select data from user_config where type=$1 and schema_version=$2 - order by id desc limit 1` - - return db.oneOrNone(sql, ['config', configValidate.SETTINGS_LOADER_SCHEMA_VERSION]) - .then(row => row ? row.data.config : []) -} - -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 getCryptos (config, machineList) { - const scopes = allScopes(['global'], allMachineScopes(machineList, 'both')) - const scoped = scope => configManager.scopedValue(scope[0], scope[1], 'cryptoCurrencies', config) - return scopes.reduce((acc, scope) => _.union(acc, scoped(scope)), []) -} - -function getGroup (schema, fieldCode) { - return schema.groups.find(group => group.fields.find(_.isEqual(fieldCode))) -} - -function getField (schema, group, fieldCode) { - if (!group) group = getGroup(schema, fieldCode) - const field = schema.fields.find(r => r.code === fieldCode) - return _.merge(_.pick(['cryptoScope', 'machineScope'], group), field) -} - -const fetchMachines = () => machineLoader.getMachines() - .then(machineList => machineList.map(r => r.deviceId)) - -function validateCurrentConfig () { - return fetchConfig() - .then(configValidate.validateRequires) -} - -const decorateEnabledIf = _.curry((schemaFields, schemaField) => { - const code = schemaField.fieldLocator.code - const field = _.find(f => f.code === code, schemaFields) - - return _.assign(schemaField, { - fieldEnabledIfAny: field.enabledIfAny || [], - fieldEnabledIfAll: field.enabledIfAll || [] - }) -}) - -function fetchConfigGroup (code) { - const fieldLocatorCodeEq = _.matchesProperty(['fieldLocator', 'code']) - return Promise.all([fetchSchema(), fetchData(), fetchConfig(), fetchMachines()]) - .then(([schema, data, config, machineList]) => { - const groupSchema = schema.groups.find(r => r.code === code) - - if (!groupSchema) throw new Error('No such group schema: ' + code) - - const schemaFields = groupSchema.fields - .map(_.curry(getField)(schema, groupSchema)) - .map(f => _.assign(f, { - fieldEnabledIfAny: f.enabledIfAny || [], - fieldEnabledIfAll: f.enabledIfAll || [] - })) - - const candidateFields = [ - schemaFields.map(_.get('requiredIf')), - schemaFields.map(_.get('enabledIfAny')), - schemaFields.map(_.get('enabledIfAll')), - groupSchema.fields, - 'fiatCurrency' - ] - - const smush = _.flow(_.flattenDeep, _.compact, _.uniq) - const configFields = smush(candidateFields) - - // Expand this to check against full schema - const fieldValidator = field => !_.isNil(_.get('fieldLocator.fieldScope.crypto', field)) - - const reducer = (acc, configField) => { - return acc.concat(config.filter(fieldLocatorCodeEq(configField))) - } - - const reducedFields = _.filter(fieldValidator, configFields.reduce(reducer, [])) - const values = _.map(decorateEnabledIf(schema.fields), reducedFields) - - groupSchema.fields = undefined - groupSchema.entries = schemaFields - - const selectedCryptos = _.defaultTo([], getCryptos(config, machineList)) - - return { - schema: groupSchema, - values, - selectedCryptos, - data - } - }) -} - -function massageCurrencies (currencies) { - const convert = r => ({ - code: r['Alphabetic Code'], - display: r['Currency'] - }) - const top5Codes = ['USD', 'EUR', 'GBP', 'CAD', 'AUD'] - const mapped = _.map(convert, currencies) - const codeToRec = code => _.find(_.matchesProperty('code', code), mapped) - const top5 = _.map(codeToRec, top5Codes) - const raw = _.uniqBy(_.get('code'), _.concat(top5, mapped)) - return raw.filter(r => r.code !== '' && r.code[0] !== 'X' && r.display.indexOf('(') === -1) -} - -const mapLanguage = lang => { - const arr = lang.split('-') - const code = arr[0] - const country = arr[1] - const langNameArr = languageRec.lang[code] - if (!langNameArr) return null - const langName = langNameArr[0] - if (!country) return {code: lang, display: langName} - return {code: lang, display: `${langName} [${country}]`} -} - -const supportedLanguages = languageRec.supported -const languages = supportedLanguages.map(mapLanguage).filter(r => r) -const ALL_CRYPTOS = ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH'] - -const filterAccounts = (data, isDevMode) => { - const notAllowed = ['mock-ticker', 'mock-wallet', 'mock-exchange', 'mock-sms', 'mock-id-verify', 'mock-zero-conf'] - const filterOut = o => _.includes(o.code, notAllowed) - return isDevMode ? data : {...data, accounts: _.filter(a => !filterOut(a), data.accounts)} -} - -function fetchData () { - return machineLoader.getMachineNames() - .then(machineList => ({ - currencies: massageCurrencies(currencies), - cryptoCurrencies: [ - {crypto: 'BTC', display: 'Bitcoin'}, - {crypto: 'ETH', display: 'Ethereum'}, - {crypto: 'LTC', display: 'Litecoin'}, - {crypto: 'DASH', display: 'Dash'}, - {crypto: 'ZEC', display: 'Zcash'}, - {crypto: 'BCH', display: 'Bitcoin Cash'} - ], - languages: languages, - countries, - accounts: [ - {code: 'bitpay', display: 'Bitpay', class: 'ticker', cryptos: ['BTC', 'BCH']}, - {code: 'kraken', display: 'Kraken', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']}, - {code: 'bitstamp', display: 'Bitstamp', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, - {code: 'coinbase', display: 'Coinbase', class: 'ticker', cryptos: ['BTC', 'ETH', 'LTC', 'BCH', 'ZEC', 'DASH']}, - {code: 'itbit', display: 'itBit', class: 'ticker', cryptos: ['BTC', 'ETH']}, - {code: 'mock-ticker', display: 'Mock (Caution!)', class: 'ticker', cryptos: ALL_CRYPTOS}, - {code: 'bitcoind', display: 'bitcoind', class: 'wallet', cryptos: ['BTC']}, - {code: 'no-layer2', display: 'No Layer 2', class: 'layer2', cryptos: ALL_CRYPTOS}, - {code: 'infura', display: 'Infura', class: 'wallet', cryptos: ['ETH']}, - {code: 'geth', display: 'geth', class: 'wallet', cryptos: ['ETH']}, - {code: 'zcashd', display: 'zcashd', class: 'wallet', cryptos: ['ZEC']}, - {code: 'litecoind', display: 'litecoind', class: 'wallet', cryptos: ['LTC']}, - {code: 'dashd', display: 'dashd', class: 'wallet', cryptos: ['DASH']}, - {code: 'bitcoincashd', display: 'bitcoincashd', class: 'wallet', cryptos: ['BCH']}, - {code: 'bitgo', display: 'BitGo', class: 'wallet', cryptos: ['BTC', 'ZEC', 'LTC', 'BCH', 'DASH']}, - {code: 'bitstamp', display: 'Bitstamp', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'BCH']}, - {code: 'itbit', display: 'itBit', class: 'exchange', cryptos: ['BTC', 'ETH']}, - {code: 'kraken', display: 'Kraken', class: 'exchange', cryptos: ['BTC', 'ETH', 'LTC', 'DASH', 'ZEC', 'BCH']}, - {code: 'mock-wallet', display: 'Mock (Caution!)', class: 'wallet', cryptos: ALL_CRYPTOS}, - {code: 'no-exchange', display: 'No exchange', class: 'exchange', cryptos: ALL_CRYPTOS}, - {code: 'mock-exchange', display: 'Mock exchange', class: 'exchange', cryptos: ALL_CRYPTOS}, - {code: 'mock-sms', display: 'Mock SMS', class: 'sms'}, - {code: 'mock-id-verify', display: 'Mock ID verifier', class: 'idVerifier'}, - {code: 'twilio', display: 'Twilio', class: 'sms'}, - {code: 'mailgun', display: 'Mailgun', class: 'email'}, - {code: 'all-zero-conf', display: 'Always 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH']}, - {code: 'no-zero-conf', display: 'Always 1-conf', class: 'zeroConf', cryptos: ALL_CRYPTOS}, - {code: 'blockcypher', display: 'Blockcypher', class: 'zeroConf', cryptos: ['BTC']}, - {code: 'mock-zero-conf', display: 'Mock 0-conf', class: 'zeroConf', cryptos: ['BTC', 'ZEC', 'LTC', 'DASH', 'BCH', 'ETH']} - ], - machines: machineList.map(machine => ({machine: machine.deviceId, display: machine.name})) - })) - .then((data) => { - return filterAccounts(data, devMode) - }) -} - -function saveConfigGroup (results) { - if (results.values.length === 0) return fetchConfigGroup(results.groupCode) - - return settingsLoader.modifyConfig(results.values) - .then(() => fetchConfigGroup(results.groupCode)) -} - -module.exports = { - fetchConfigGroup, - saveConfigGroup, - validateCurrentConfig, - fetchConfig, - filterAccounts -} diff --git a/lib/admin/lamassu-schema.json b/lib/admin/lamassu-schema.json deleted file mode 100644 index 2e41936a..00000000 --- a/lib/admin/lamassu-schema.json +++ /dev/null @@ -1,1011 +0,0 @@ -{ - "groups": [ - { - "code": "definition", - "display": "Definition", - "cryptoScope": "global", - "machineScope": "specific", - "fields": [ - "machineName", - "machineModel", - "machineLocation" - ] - }, - { - "code": "setup", - "display": "Setup", - "cryptoScope": "global", - "machineScope": "both", - "fields": [ - "fiatCurrency", - "country", - "machineLanguages", - "cryptoCurrencies" - ] - }, - { - "code": "cashOut", - "display": "Cash Out", - "cryptoScope": "global", - "machineScope": "both", - "fields": [ - "cashOutEnabled", - "topCashOutDenomination", - "bottomCashOutDenomination", - "virtualCashOutDenomination", - "zeroConfLimit" - ] - }, - { - "code": "commissions", - "display": "Commissions", - "cryptoScope": "both", - "machineScope": "both", - "fields": [ - "cashInCommission", - "cashOutCommission", - "cashInFee", - "minimumTx" - ] - }, - { - "code": "balanceAlerts", - "display": "Balance Alerts", - "cryptoScope": "both", - "machineScope": "both", - "fields": [ - "cryptoAlertThreshold", - "cashInAlertThreshold", - "cashOutCassette1AlertThreshold", - "cashOutCassette2AlertThreshold" - ] - }, - { - "code": "compliance", - "display": "Compliance", - "cryptoScope": "global", - "machineScope": "global", - "fields": [ - "smsVerificationActive", - "smsVerificationThreshold", - "idCardDataVerificationActive", - "idCardDataVerificationThreshold", - "idCardPhotoVerificationActive", - "idCardPhotoVerificationThreshold", - "sanctionsVerificationActive", - "sanctionsVerificationThreshold", - "frontCameraVerificationActive", - "frontCameraVerificationThreshold", - "hardLimitVerificationActive", - "hardLimitVerificationThreshold", - "receiptPrintingActive", - "rejectAddressReuseActive" - ] - }, - { - "code": "coinAtmRadar", - "display": "Coin ATM Radar", - "cryptoScope": "global", - "machineScope": "global", - "fields": [ - "coinAtmRadarActive", - "coinAtmRadarShowRates" - ] - }, - { - "code": "walletSettings", - "display": "Wallet Settings", - "cryptoScope": "specific", - "machineScope": "global", - "fields": [ - "ticker", - "wallet", - "layer2", - "exchange", - "zeroConf" - ] - }, - { - "code": "notifications", - "display": "Notifications", - "cryptoScope": "global", - "machineScope": "global", - "fields": [ - "notificationsEnabled", - "notificationsEmailEnabled", - "notificationsSMSEnabled", - "transactionNotificationsEnabled", - "transactionNotificationsEmailEnabled", - "transactionNotificationsSMSEnabled", - "sms", - "email" - ] - }, - { - "code": "terms", - "display": "Terms and Conditions", - "cryptoScope": "global", - "machineScope": "global", - "fields": [ - "termsScreenActive", - "termsScreenTitle", - "termsScreenText", - "termsAcceptButtonText", - "termsCancelButtonText" - ] - }, - { - "code": "operatorInfo", - "display": "Operator Info", - "cryptoScope": "global", - "machineScope": "global", - "fields": [ - "operatorInfoActive", - "operatorInfoEmail", - "operatorInfoName", - "operatorInfoPhone", - "operatorInfoWebsite", - "operatorInfoCompanyNumber" - ] - }, - { - "code": "fudgeFactor", - "display": "Fudge Factor", - "cryptoScope": "global", - "machineScope": "global", - "fields": [ - "fudgeFactorActive" - ] - } - ], - "fields": [ - { - "code": "receiptPrintingActive", - "displayTop": "Receipt Printing", - "displayBottom": "Receipt Priting", - "displayTopCount": 2, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "rejectAddressReuseActive", - "displayTop": "Reject Address Reuse", - "displayBottom": "Reject Address Reuse", - "displayTopCount": 2, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "hardLimitVerificationActive", - "displayTop": "Hard Limit", - "displayBottom": "Hard Limit", - "displayTopCount": 2, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "hardLimitVerificationThreshold", - "displayBottom": "Daily Value Threshold", - "displayTopCount": 0, - "fieldType": "integer", - "fieldClass": "fiat", - "enabledIfAny": [ - "hardLimitVerificationActive" - ], - "fieldValidation": [{"code": "required"}] - }, - { - "code": "sanctionsVerificationActive", - "displayTop": "Sanctions Verification", - "displayBottom": "Sanctions", - "displayTopCount": 2, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "sanctionsVerificationThreshold", - "displayBottom": "Daily Value Threshold", - "displayTopCount": 0, - "fieldType": "integer", - "fieldClass": "fiat", - "enabledIfAny": [ - "sanctionsVerificationActive" - ], - "fieldValidation": [{"code": "required"}] - }, - { - "code": "frontCameraVerificationActive", - "displayTop": "Front Facing Camer", - "displayBottom": "Front Facing Camera", - "displayTopCount": 2, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "frontCameraVerificationThreshold", - "displayBottom": "Daily Value Threshold", - "displayTopCount": 0, - "fieldType": "integer", - "fieldClass": "fiat", - "enabledIfAny": [ - "frontCameraVerificationActive" - ], - "fieldValidation": [{"code": "required"}] - }, - { - "code": "idCardPhotoVerificationActive", - "displayTop": "ID Card Photo", - "displayBottom": "ID Photo", - "displayTopCount": 2, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "idCardPhotoVerificationThreshold", - "displayBottom": "Daily Value Threshold", - "displayTopCount": 0, - "fieldType": "integer", - "fieldClass": "fiat", - "enabledIfAny": [ - "idCardPhotoVerificationActive" - ], - "fieldValidation": [{"code": "required"}] - }, - { - "code": "idCardDataVerificationActive", - "displayTop": "ID Card Verification", - "displayBottom": "Card", - "displayTopCount": 2, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "idCardDataVerificationThreshold", - "displayBottom": "Daily Value Threshold", - "displayTopCount": 0, - "fieldType": "integer", - "fieldClass": "fiat", - "enabledIfAny": [ - "idCardDataVerificationActive" - ], - "fieldValidation": [{"code": "required"}] - }, - { - "code": "smsVerificationActive", - "displayTop": "SMS Verification", - "displayBottom": "SMS", - "displayTopCount": 2, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "smsVerificationThreshold", - "displayBottom": "Daily Value Threshold", - "displayTopCount": 0, - "fieldType": "integer", - "fieldClass": "fiat", - "enabledIfAny": [ - "smsVerificationActive" - ], - "fieldValidation": [{"code": "required"}] - }, - { - "code": "cashInCommission", - "displayTop": "Commissions", - "displayTopCount": 2, - "displayBottom": "Cash-in", - "fieldType": "percentage", - "fieldClass": null, - "cryptoScope": "both", - "machineScope": "both", - "fieldValidation": [ - { - "code": "required" - }, - { - "code": "min", - "min": 0 - } - ] - }, - { - "code": "cashOutCommission", - "displayBottom": "Cash-out", - "displayTopCount": 0, - "fieldType": "percentage", - "fieldClass": null, - "cryptoScope": "both", - "machineScope": "both", - "enabledIfAny": [ - "cashOutEnabled" - ], - "fieldValidation": [ - { - "code": "required" - }, - { - "code": "min", - "min": 0 - } - ] - }, - { - "code": "cashInFee", - "displayTop": "Cash-in", - "displayBottom": "Fixed Fee", - "displayTopCount": 2, - "fieldType": "decimal", - "fieldClass": "fiat", - "cryptoScope": "both", - "machineScope": "both", - "default": 0, - "fieldValidation": [ - { - "code": "required" - }, - { - "code": "min", - "min": 0 - } - ] - }, - { - "code": "minimumTx", - "displayBottom": "Minimum Tx", - "displayTopCount": 0, - "fieldType": "integer", - "fieldClass": "fiat", - "cryptoScope": "both", - "machineScope": "both", - "default": 0, - "enabledIfAny": [ - ], - "fieldValidation": [ - { - "code": "required" - }, - { - "code": "min", - "min": 0 - } - ] - }, - { - "code": "cryptoAlertThreshold", - "displayTop": "Crypto", - "displayBottom": "Threshold", - "fieldType": "integer", - "fieldClass": "fiat", - "cryptoScope": "both", - "machineScope": "global", - "enabledIfAny": [ - "notificationsEnabled" - ], - "fieldValidation": [ - { - "code": "required" - }, - { - "code": "min", - "min": 0 - } - ] - }, - { - "code": "cashInAlertThreshold", - "displayTop": "Cash-in", - "displayBottom": "Threshold", - "fieldType": "integer", - "fieldClass": "banknotes", - "cryptoScope": "global", - "machineScope": "both", - "enabledIfAny": [ - "notificationsEnabled" - ], - "fieldValidation": [ - { - "code": "required" - }, - { - "code": "min", - "min": 0 - } - ] - }, - { - "code": "cashOutCassette1AlertThreshold", - "displayTop": "Cash-out Thresholds", - "displayTopCount": 2, - "displayBottom": "Top", - "fieldType": "integer", - "fieldClass": "banknotes", - "cryptoScope": "global", - "machineScope": "both", - "default": 10, - "enabledIfAll": [ - "notificationsEnabled", - "cashOutEnabled" - ], - "fieldValidation": [ - { - "code": "required" - }, - { - "code": "min", - "min": 0 - } - ] - }, - { - "code": "cashOutCassette2AlertThreshold", - "displayTopCount": 0, - "displayBottom": "Bottom", - "fieldType": "integer", - "fieldClass": "banknotes", - "cryptoScope": "global", - "machineScope": "both", - "default": 10, - "enabledIfAll": [ - "notificationsEnabled", - "cashOutEnabled" - ], - "fieldValidation": [ - { - "code": "required" - }, - { - "code": "min", - "min": 0 - } - ] - }, - { - "code": "zeroConfLimit", - "displayTop": "0-conf", - "displayBottom": "Limit", - "fieldType": "integer", - "fieldClass": "fiat", - "cryptoScope": "global", - "machineScope": "both", - "enabledIfAny": [ - "cashOutEnabled" - ], - "fieldValidation": [{"code": "required"}] - }, - { - "code": "ticker", - "displayBottom": "Ticker", - "fieldType": "account", - "fieldClass": "ticker", - "fieldValidation": [ - { - "code": "required" - } - ] - }, - { - "code": "wallet", - "displayBottom": "Wallet", - "fieldType": "account", - "fieldClass": "wallet", - "fieldValidation": [ - { - "code": "required" - } - ] - }, - { - "code": "layer2", - "displayBottom": "Layer 2", - "fieldType": "account", - "fieldClass": "layer2", - "fieldValidation": [ - { - "code": "required" - } - ], - "default": "no-layer2" - }, - { - "code": "exchange", - "displayBottom": "Exchange", - "fieldType": "account", - "fieldClass": "exchange", - "fieldValidation": [], - "default": "no-exchange" - }, - { - "code": "zeroConf", - "displayBottom": "Zero Conf", - "fieldType": "account", - "fieldClass": "zeroConf", - "fieldValidation": [], - "default": "all-zero-conf" - }, - { - "code": "fiatCurrency", - "displayBottom": "Fiat Currency", - "fieldType": "fiatCurrency", - "cryptoScope": "global", - "machineScope": "global", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ] - }, - { - "code": "country", - "displayBottom": "Country", - "fieldType": "country", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ] - }, - { - "code": "machineLanguages", - "displayBottom": "Languages", - "fieldType": "language", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": ["en-US"] - }, - { - "code": "cryptoCurrencies", - "displayBottom": "Crypto Currencies", - "fieldType": "cryptoCurrency", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": ["BTC"] - }, - { - "code": "topCashOutDenomination", - "displayTop": "Cash-out denominations", - "displayBottom": "Top", - "displayTopCount": 3, - "fieldType": "integer", - "fieldClass": null, - "enabledIfAny": [ - "cashOutEnabled" - ], - "fieldValidation": [{"code": "required"}] - }, - { - "code": "bottomCashOutDenomination", - "displayBottom": "Bottom", - "displayTopCount": 0, - "fieldType": "integer", - "fieldClass": null, - "enabledIfAny": [ - "cashOutEnabled" - ], - "fieldValidation": [{"code": "required"}] - }, - { - "code": "virtualCashOutDenomination", - "displayBottom": "Virtual", - "displayTopCount": 0, - "fieldType": "integer", - "fieldClass": null, - "enabledIfAny": [ - "cashOutEnabled" - ], - "fieldValidation": [{"code": "required"}] - }, - { - "code": "emptyBillMargin", - "displayTop": "Empty bill", - "displayBottom": "Margin", - "fieldType": "integer", - "fieldClass": "banknotes", - "enabledIfAny": [ - "cashOutEnabled" - ], - "fieldValidation": [{"code": "required"}], - "default": 0 - }, - { - "code": "smsVerificationEnabled", - "displayTop": "Verifications enabled", - "displayBottom": "SMS", - "displayTopCount": 2, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "idVerificationEnabled", - "displayBottom": "ID", - "displayTopCount": 0, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "idVerifier", - "displayTop": "ID verification", - "displayTopCount": 2, - "displayBottom": "Service", - "fieldType": "account", - "fieldClass": "idVerifier", - "enabledIfAny": [ - "idVerificationEnabled" - ], - "fieldValidation": [{"code": "required"}] - }, - { - "code": "idVerificationLimit", - "displayBottom": "Limit", - "displayTopCount": 0, - "fieldType": "integer", - "fieldClass": null, - "enabledIfAny": [ - "idVerificationEnabled" - ], - "fieldValidation": [{"code": "required"}] - }, - { - "code": "cashOutEnabled", - "displayBottom": "Cash Out", - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "machineName", - "cryptoScope": "global", - "machineScope": "specific", - "displayBottom": "Name", - "fieldType": "string", - "fieldClass": null, - "fieldValidation": [{"code": "required"}] - }, - { - "code": "machineModel", - "cryptoScope": "global", - "machineScope": "specific", - "displayBottom": "Model", - "fieldType": "string", - "fieldClass": null, - "readOnly": true, - "fieldValidation": [] - }, - { - "code": "machineLocation", - "displayBottom": "Location", - "fieldType": "string", - "fieldClass": null, - "fieldValidation": [] - }, - { - "code": "notificationsEnabled", - "displayTop": "Notifications enabled", - "displayBottom": "General", - "displayTopCount": 3, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "notificationsEmailEnabled", - "displayBottom": "Email", - "displayTopCount": 0, - "fieldType": "onOff", - "fieldClass": null, - "enabledIfAny": [ - "notificationsEnabled" - ], - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "notificationsSMSEnabled", - "displayBottom": "SMS", - "displayTopCount": 0, - "fieldType": "onOff", - "fieldClass": null, - "enabledIfAny": [ - "notificationsEnabled" - ], - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "transactionNotificationsEnabled", - "displayTop": "Transaction Notifications enabled", - "displayBottom": "General", - "displayTopCount": 3, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "transactionNotificationsEmailEnabled", - "displayBottom": "Email", - "displayTopCount": 0, - "fieldType": "onOff", - "fieldClass": null, - "enabledIfAny": [ - "transactionNotificationsEnabled" - ], - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "transactionNotificationsSMSEnabled", - "displayBottom": "SMS", - "displayTopCount": 0, - "fieldType": "onOff", - "fieldClass": null, - "enabledIfAny": [ - "transactionNotificationsEnabled" - ], - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "sms", - "displayTop": "Gateways", - "displayBottom": "SMS", - "displayTopCount": 2, - "fieldType": "account", - "fieldClass": "sms", - "enabledIfAny": [ - "notificationsSMSEnabled", - "cashOutEnabled" - ], - "fieldValidation": [{"code": "required"}], - "default": "twilio" - }, - { - "code": "email", - "displayBottom": "Email", - "displayTopCount": 0, - "fieldType": "account", - "fieldClass": "email", - "enabledIfAny": [ - "notificationsEmailEnabled" - ], - "fieldValidation": [{"code": "required"}], - "default": "mailgun" - }, - { - "code": "coinAtmRadarActive", - "displayBottom": "Active", - "displayTopCount": 0, - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [{"code": "required"}], - "default": false - }, - { - "code": "coinAtmRadarShowRates", - "displayBottom": "Show Rates", - "displayTopCount": 0, - "fieldType": "onOff", - "fieldClass": null, - "enabledIfAny": [ - "coinAtmRadarActive" - ], - "fieldValidation": [{"code": "required"}], - "default": true - }, - { - "code": "termsScreenActive", - "displayBottom": "Terms and Conditions Screen enabled", - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [ - { - "code": "required" - } - ], - "default": false - }, - { - "code": "termsScreenTitle", - "displayBottom": "Title", - "displayTopCount": 0, - "fieldType": "string", - "fieldClass": null, - "default": "Terms and Conditions", - "enabledIfAny": [ - "termsScreenActive" - ], - "fieldValidation": [] - }, - { - "code": "termsScreenText", - "displayBottom": "Text", - "displayTopCount": 0, - "fieldType": "markdown", - "fieldClass": null, - "enabledIfAny": [ - "termsScreenActive" - ], - "fieldValidation": [] - }, - { - "code": "termsAcceptButtonText", - "displayBottom": "Accept button text", - "displayTopCount": 0, - "fieldType": "string", - "fieldClass": null, - "default": "Accept", - "enabledIfAny": [ - "termsScreenActive" - ], - "fieldValidation": [] - }, - { - "code": "termsCancelButtonText", - "displayBottom": "Cancel button text", - "displayTopCount": 0, - "fieldType": "string", - "fieldClass": null, - "default": "Cancel", - "enabledIfAny": [ - "termsScreenActive" - ], - "fieldValidation": [] - }, - { - "code": "fudgeFactorActive", - "displayBottom": "Enabled", - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [], - "default": false - }, - { - "code": "operatorInfoActive", - "displayBottom": "Info card enabled", - "fieldType": "onOff", - "fieldClass": null, - "fieldValidation": [], - "default": false - }, - { - "code": "operatorInfoName", - "displayBottom": "Name", - "displayTopCount": 0, - "fieldType": "string", - "fieldClass": null, - "default": "", - "fieldValidation": [] - }, - { - "code": "operatorInfoEmail", - "displayBottom": "Email", - "displayTopCount": 0, - "fieldType": "string", - "fieldClass": "email", - "fieldValidation": [] - }, - { - "code": "operatorInfoPhone", - "displayBottom": "Phone", - "displayTopCount": 0, - "fieldType": "string", - "fieldClass": null, - "fieldValidation": [] - }, - { - "code": "operatorInfoWebsite", - "displayBottom": "Website", - "displayTopCount": 0, - "fieldType": "string", - "fieldClass": null, - "fieldValidation": [] - }, - { - "code": "operatorInfoCompanyNumber", - "displayBottom": "Company Number", - "displayTopCount": 0, - "fieldType": "string", - "fieldClass": null, - "fieldValidation": [] - } - ] -} diff --git a/lib/admin/settings-loader.js b/lib/admin/settings-loader.js deleted file mode 100644 index cb4bacf7..00000000 --- a/lib/admin/settings-loader.js +++ /dev/null @@ -1,250 +0,0 @@ -const path = require('path') -const fs = require('fs') - -const _ = require('lodash/fp') -const argv = require('minimist')(process.argv.slice(2)) -const pify = require('pify') - -const pgp = require('pg-promise')() -const db = require('../db') -const configValidate = require('./config-validate') -const schema = require('./lamassu-schema.json') - -let settingsCache - -function loadFixture () { - const fixture = argv.fixture - const machine = argv.machine - - if (fixture && !machine) throw new Error('Missing --machine parameter for --fixture') - - const fixturePath = fixture => path.resolve(__dirname, '..', 'test', 'fixtures', fixture + '.json') - - const promise = fixture - ? pify(fs.readFile)(fixturePath(fixture)).then(JSON.parse) - : Promise.resolve([]) - - return promise - .then(values => _.map(v => { - return (v.fieldLocator.fieldScope.machine === 'machine') - ? _.set('fieldLocator.fieldScope.machine', machine, v) - : v - }, values)) -} - -function isEquivalentField (a, b) { - return _.isEqual( - [a.fieldLocator.code, a.fieldLocator.fieldScope], - [b.fieldLocator.code, b.fieldLocator.fieldScope] - ) -} - -// b overrides a -function mergeValues (a, b) { - return _.reject(r => _.isNil(r.fieldValue), _.unionWith(isEquivalentField, b, a)) -} - -function load (versionId) { - if (!versionId) throw new Error('versionId is required') - - return Promise.all([loadConfig(versionId), loadAccounts()]) - .then(([config, accounts]) => ({ - config, - accounts - })) -} - -function loadLatest (filterSchemaVersion = true) { - return Promise.all([loadLatestConfig(filterSchemaVersion), loadAccounts(filterSchemaVersion)]) - .then(([config, accounts]) => ({ - config, - accounts - })) -} - -function loadConfig (versionId) { - if (argv.fixture) return loadFixture() - - const sql = `select data - from user_config - where id=$1 and type=$2 and schema_version=$3 - and valid` - - return db.one(sql, [versionId, 'config', configValidate.SETTINGS_LOADER_SCHEMA_VERSION]) - .then(row => row.data.config) - .then(configValidate.validate) - .catch(err => { - if (err.name === 'QueryResultError') { - throw new Error('No such config version: ' + versionId) - } - - throw err - }) -} - -function loadLatestConfig (filterSchemaVersion = true) { - if (argv.fixture) return loadFixture() - - const sql = `select id, valid, data - from user_config - where type=$1 ${filterSchemaVersion ? 'and schema_version=$2' : ''} - and valid - order by id desc - limit 1` - - return db.oneOrNone(sql, ['config', configValidate.SETTINGS_LOADER_SCHEMA_VERSION]) - .then(row => row.data.config) - .then(configValidate.validate) - .catch(err => { - if (err.name === 'QueryResultError') { - throw new Error('lamassu-server is not configured') - } - - throw err - }) -} - -function loadRecentConfig () { - if (argv.fixture) return loadFixture() - - const sql = `select id, data - from user_config - where type=$1 and schema_version=$2 - order by id desc - limit 1` - - return db.one(sql, ['config', configValidate.SETTINGS_LOADER_SCHEMA_VERSION]) - .then(row => row.data.config) -} - -function loadAccounts (filterSchemaVersion = true) { - const toFields = fieldArr => _.fromPairs(_.map(r => [r.code, r.value], fieldArr)) - const toPairs = r => [r.code, toFields(r.fields)] - - return db.oneOrNone(`select data from user_config where type=$1 ${filterSchemaVersion ? 'and schema_version=$2' : ''}`, ['accounts', configValidate.SETTINGS_LOADER_SCHEMA_VERSION]) - .then(function (data) { - if (!data) return {} - return _.fromPairs(_.map(toPairs, data.data.accounts)) - }) -} - -function settings () { - return settingsCache -} - -function save (config) { - const sql = 'insert into user_config (type, data, valid) values ($1, $2, $3)' - - return configValidate.validate(config) - .then(() => db.none(sql, ['config', {config}, true])) - .catch(() => db.none(sql, ['config', {config}, false])) -} - -function configAddField (scope, fieldCode, fieldType, fieldClass, value) { - return { - fieldLocator: { - fieldScope: { - crypto: scope.crypto, - machine: scope.machine - }, - code: fieldCode, - fieldType, - fieldClass - }, - fieldValue: {fieldType, value} - } -} - -function configDeleteField (scope, fieldCode) { - return { - fieldLocator: { - fieldScope: { - crypto: scope.crypto, - machine: scope.machine - }, - code: fieldCode - }, - fieldValue: null - } -} - -function populateScopes (schema) { - const scopeLookup = {} - _.forEach(r => { - const scope = { - cryptoScope: r.cryptoScope, - machineScope: r.machineScope - } - - _.forEach(field => { scopeLookup[field] = scope }, r.fields) - }, schema.groups) - - return _.map(r => _.assign(scopeLookup[r.code], r), schema.fields) -} - -function cryptoDefaultOverride (cryptoCode, code, defaultValue) { - if (cryptoCode === 'ETH' && code === 'zeroConf') { - return 'no-zero-conf' - } - - return defaultValue -} - -function cryptoCodeDefaults (schema, cryptoCode) { - const scope = {crypto: cryptoCode, machine: 'global'} - - const schemaEntries = populateScopes(schema) - const hasCryptoSpecificDefault = r => r.cryptoScope === 'specific' && !_.isNil(r.default) - const cryptoSpecificFields = _.filter(hasCryptoSpecificDefault, schemaEntries) - - return _.map(r => { - const defaultValue = cryptoDefaultOverride(cryptoCode, r.code, r.default) - - return configAddField(scope, r.code, r.fieldType, r.fieldClass, defaultValue) - }, cryptoSpecificFields) -} - -const uniqCompact = _.flow(_.compact, _.uniq) - -function addCryptoDefaults (oldConfig, newFields) { - const cryptoCodeEntries = _.filter(v => v.fieldLocator.code === 'cryptoCurrencies', newFields) - const cryptoCodes = _.flatMap(_.get('fieldValue.value'), cryptoCodeEntries) - const uniqueCryptoCodes = uniqCompact(cryptoCodes) - - const mapDefaults = cryptoCode => cryptoCodeDefaults(schema, cryptoCode) - const defaults = _.flatMap(mapDefaults, uniqueCryptoCodes) - - return mergeValues(defaults, oldConfig) -} - -function modifyConfig (newFields) { - const TransactionMode = pgp.txMode.TransactionMode - const isolationLevel = pgp.txMode.isolationLevel - const mode = new TransactionMode({ tiLevel: isolationLevel.serializable }) - - function transaction (t) { - return loadRecentConfig() - .then(oldConfig => { - const oldConfigWithDefaults = addCryptoDefaults(oldConfig, newFields) - const doSave = _.flow(mergeValues, save) - return doSave(oldConfigWithDefaults, newFields) - }) - } - - return db.tx({ mode }, transaction) -} - -module.exports = { - settings, - loadConfig, - loadRecentConfig, - load, - loadLatest, - loadLatestConfig, - save, - loadFixture, - mergeValues, - modifyConfig, - configAddField, - configDeleteField -} diff --git a/lib/config-migration.js b/lib/config-migration.js deleted file mode 100644 index 493bc4ea..00000000 --- a/lib/config-migration.js +++ /dev/null @@ -1,477 +0,0 @@ -const _ = require('lodash/fp') -const uuid = require('uuid') -const { COINS } = require('@lamassu/coins') -const { scopedValue } = require('./admin/config-manager') - -const GLOBAL = 'global' -const ALL_CRYPTOS = _.values(COINS).sort() -const ALL_CRYPTOS_STRING = 'ALL_COINS' -const ALL_MACHINES = 'ALL_MACHINES' - -const GLOBAL_SCOPE = { - crypto: ALL_CRYPTOS, - machine: GLOBAL -} - -function getConfigFields (codes, config) { - const stringfiedGlobalScope = JSON.stringify(GLOBAL_SCOPE) - - const fields = config - .filter(i => codes.includes(i.fieldLocator.code)) - .map(f => { - const crypto = Array.isArray(f.fieldLocator.fieldScope.crypto) - ? f.fieldLocator.fieldScope.crypto.sort() - : f.fieldLocator.fieldScope.crypto === GLOBAL - ? ALL_CRYPTOS - : [f.fieldLocator.fieldScope.crypto] - const machine = f.fieldLocator.fieldScope.machine - - return { - code: f.fieldLocator.code, - scope: { - crypto, - machine - }, - value: f.fieldValue.value - } - }) - .filter(f => f.value != null) - - const grouped = _.chain(fields) - .groupBy(f => JSON.stringify(f.scope)) - .value() - - return { - global: grouped[stringfiedGlobalScope] || [], - scoped: - _.entries( - _.chain(grouped) - .omit([stringfiedGlobalScope]) - .value() - ).map(f => { - const fallbackValues = - _.difference(codes, f[1].map(v => v.code)) - .map(v => ({ - code: v, - scope: JSON.parse(f[0]), - value: scopedValue(f[0].crypto, f[0].machine, v, config) - })) - .filter(f => f.value != null) - - return { - scope: JSON.parse(f[0]), - values: f[1].concat(fallbackValues) - } - }) || [] - } -} - -function migrateCommissions (config) { - const areArraysEquals = (arr1, arr2) => Array.isArray(arr1) && Array.isArray(arr2) && _.isEmpty(_.xor(arr1, arr2)) - const getMachine = _.get('scope.machine') - const getCrypto = _.get('scope.crypto') - const flattenCoins = _.compose(_.flatten, _.map(getCrypto)) - const diffAllCryptos = _.compose(_.difference(ALL_CRYPTOS)) - - const codes = { - minimumTx: 'minimumTx', - cashInFee: 'fixedFee', - cashInCommission: 'cashIn', - cashOutCommission: 'cashOut' - } - - const { global, scoped } = getConfigFields(_.keys(codes), config) - const defaultCashOutCommissions = { code: 'cashOutCommission', value: 0, scope: global[0].scope } - const isCashOutDisabled = - _.isEmpty(_.filter(commissionElement => commissionElement.code === 'cashOutCommission', global)) - const globalWithDefaults = - isCashOutDisabled ? _.concat(global, defaultCashOutCommissions) : global - - const machineAndCryptoScoped = scoped.filter( - f => f.scope.machine !== GLOBAL_SCOPE.machine && f.scope.crypto.length === 1 - ) - const cryptoScoped = scoped.filter( - f => - f.scope.machine === GLOBAL_SCOPE.machine && - !areArraysEquals(f.scope.crypto, GLOBAL_SCOPE.crypto) - ) - const machineScoped = scoped.filter( - f => - f.scope.machine !== GLOBAL_SCOPE.machine && - areArraysEquals(f.scope.crypto, GLOBAL_SCOPE.crypto) - ) - - const withCryptoScoped = machineAndCryptoScoped.concat(cryptoScoped) - - const filteredMachineScoped = _.map(it => { - const filterByMachine = _.filter(_.includes(getMachine(it))) - const unrepeatedCryptos = _.compose( - diffAllCryptos, - flattenCoins, - filterByMachine - )(withCryptoScoped) - - return _.set('scope.crypto', unrepeatedCryptos)(it) - })(machineScoped) - - const allCommissionsOverrides = withCryptoScoped.concat(filteredMachineScoped) - - return { - ..._.fromPairs(globalWithDefaults.map(f => [`commissions_${codes[f.code]}`, f.value])), - ...(allCommissionsOverrides.length > 0 && { - commissions_overrides: allCommissionsOverrides.map(s => ({ - ..._.fromPairs(s.values.map(f => [codes[f.code], f.value])), - machine: s.scope.machine === GLOBAL ? ALL_MACHINES : s.scope.machine, - cryptoCurrencies: areArraysEquals(s.scope.crypto, ALL_CRYPTOS) ? [ALL_CRYPTOS_STRING] : s.scope.crypto, - id: uuid.v4() - })) - }) - } -} - -function migrateLocales (config) { - const codes = { - country: 'country', - fiatCurrency: 'fiatCurrency', - machineLanguages: 'languages', - cryptoCurrencies: 'cryptoCurrencies', - timezone: 'timezone' - } - - const { global, scoped } = getConfigFields(_.keys(codes), config) - - return { - ..._.fromPairs(global.map(f => [`locale_${codes[f.code]}`, f.value])), - ...(scoped.length > 0 && { - locale_overrides: scoped.map(s => ({ - ..._.fromPairs(s.values.map(f => [codes[f.code], f.value])), - machine: s.scope.machine, - id: uuid.v4() - })) - }) - } -} - -function migrateCashOut (config) { - const globalCodes = { - fudgeFactorActive: 'fudgeFactorActive' - } - - const scopedCodes = { - cashOutEnabled: 'active', - topCashOutDenomination: 'top', - bottomCashOutDenomination: 'bottom', - zeroConfLimit: 'zeroConfLimit' - } - - const { global } = getConfigFields(_.keys(globalCodes), config) - const { scoped } = getConfigFields(_.keys(scopedCodes), config) - - return { - ..._.fromPairs( - global.map(f => [`cashOut_${globalCodes[f.code]}`, f.value]) - ), - ..._.fromPairs( - _.flatten( - scoped.map(s => { - const fields = s.values.map(f => [ - `cashOut_${f.scope.machine}_${scopedCodes[f.code]}`, - f.value - ]) - - fields.push([`cashOut_${s.scope.machine}_id`, s.scope.machine]) - - return fields - }) - ) - ) - } -} - -function migrateNotifications (config) { - const globalCodes = { - notificationsEmailEnabled: 'email_active', - notificationsSMSEnabled: 'sms_active', - cashOutCassette1AlertThreshold: 'fiatBalanceCassette1', - cashOutCassette2AlertThreshold: 'fiatBalanceCassette2', - cryptoAlertThreshold: 'cryptoLowBalance' - } - - const machineScopedCodes = { - cashOutCassette1AlertThreshold: 'cassette1', - cashOutCassette2AlertThreshold: 'cassette2' - } - - const cryptoScopedCodes = { - cryptoAlertThreshold: 'lowBalance' - } - - const { global } = getConfigFields(_.keys(globalCodes), config) - const machineScoped = getConfigFields( - _.keys(machineScopedCodes), - config - ).scoped.filter(f => f.scope.crypto === GLOBAL && f.scope.machine !== GLOBAL) - const cryptoScoped = getConfigFields( - _.keys(cryptoScopedCodes), - config - ).scoped.filter(f => f.scope.crypto !== GLOBAL && f.scope.machine === GLOBAL) - - return { - ..._.fromPairs( - global.map(f => [`notifications_${globalCodes[f.code]}`, f.value]) - ), - notifications_email_balance: true, - notifications_email_transactions: true, - notifications_email_compliance: true, - notifications_email_errors: true, - notifications_sms_balance: true, - notifications_sms_transactions: true, - notifications_sms_compliance: true, - notifications_sms_errors: true, - ...(machineScoped.length > 0 && { - notifications_fiatBalanceOverrides: machineScoped.map(s => ({ - ..._.fromPairs( - s.values.map(f => [machineScopedCodes[f.code], f.value]) - ), - machine: s.scope.machine, - id: uuid.v4() - })) - }), - ...(cryptoScoped.length > 0 && { - notifications_cryptoBalanceOverrides: cryptoScoped.map(s => ({ - ..._.fromPairs(s.values.map(f => [cryptoScopedCodes[f.code], f.value])), - cryptoCurrency: s.scope.crypto, - id: uuid.v4() - })) - }) - } -} - -function migrateWallet (config) { - const codes = { - ticker: 'ticker', - wallet: 'wallet', - exchange: 'exchange', - zeroConf: 'zeroConf' - } - - const { scoped } = getConfigFields(_.keys(codes), config) - - return { - ...(scoped.length > 0 && - _.fromPairs( - _.flatten( - scoped.map(s => - s.values.map(f => [ - `wallets_${f.scope.crypto}_${codes[f.code]}`, - f.value - ]) - ) - ) - )) - } -} - -function migrateOperatorInfo (config) { - const codes = { - operatorInfoActive: 'active', - operatorInfoEmail: 'email', - operatorInfoName: 'name', - operatorInfoPhone: 'phone', - operatorInfoWebsite: 'website', - operatorInfoCompanyNumber: 'companyNumber' - } - - const { global } = getConfigFields(_.keys(codes), config) - - return { - ..._.fromPairs(global.map(f => [`operatorInfo_${codes[f.code]}`, f.value])) - } -} - -function migrateReceiptPrinting (config) { - const codes = { - receiptPrintingActive: 'active' - } - - const { global } = getConfigFields(_.keys(codes), config) - - return { - ..._.fromPairs(global.map(f => [`receipt_${codes[f.code]}`, f.value])), - receipt_operatorWebsite: true, - receipt_operatorEmail: true, - receipt_operatorPhone: true, - receipt_companyRegistration: true, - receipt_machineLocation: true, - receipt_customerNameOrPhoneNumber: true, - receipt_exchangeRate: true, - receipt_addressQRCode: true - } -} - -function migrateCoinATMRadar (config) { - const codes = ['coinAtmRadarActive', 'coinAtmRadarShowRates'] - - const { global } = getConfigFields(codes, config) - const coinAtmRadar = _.fromPairs(global.map(f => [f.code, f.value])) - - return { - coinAtmRadar_active: coinAtmRadar.coinAtmRadarActive, - coinAtmRadar_commissions: coinAtmRadar.coinAtmRadarShowRates, - coinAtmRadar_limitsAndVerification: coinAtmRadar.coinAtmRadarShowRates - } -} - -function migrateTermsAndConditions (config) { - const codes = { - termsScreenActive: 'active', - termsScreenTitle: 'title', - termsScreenText: 'text', - termsAcceptButtonText: 'acceptButtonText', - termsCancelButtonText: 'cancelButtonText' - } - - const { global } = getConfigFields(_.keys(codes), config) - - return { - ..._.fromPairs( - global.map(f => [`termsConditions_${codes[f.code]}`, f.value]) - ) - } -} - -function migrateComplianceTriggers (config) { - - const suspensionDays = 1 - - const triggerTypes = { - amount: 'txAmount', - velocity: 'txVelocity', - volume: 'txVolume', - consecutiveDays: 'consecutiveDays' - } - - const requirements = { - sms: 'sms', - idData: 'idCardData', - idPhoto: 'idCardPhoto', - facePhoto: 'facephoto', - sanctions: 'sanctions', - suspend: 'suspend' - } - - function createTrigger ( - requirement, - threshold, - suspensionDays - ) { - const triggerConfig = { - id: uuid.v4(), - direction: 'both', - threshold, - thresholdDays: 1, - triggerType: triggerTypes.volume, - requirement - } - if (!requirement === 'suspend') return triggerConfig - return _.assign(triggerConfig, { suspensionDays }) - } - - const codes = [ - 'smsVerificationActive', - 'smsVerificationThreshold', - 'idCardDataVerificationActive', - 'idCardDataVerificationThreshold', - 'idCardPhotoVerificationActive', - 'idCardPhotoVerificationThreshold', - 'frontCameraVerificationActive', - 'frontCameraVerificationThreshold', - 'sanctionsVerificationActive', - 'sanctionsVerificationThreshold', - 'hardLimitVerificationActive', - 'hardLimitVerificationThreshold', - 'rejectAddressReuseActive' - ] - - const global = _.fromPairs( - getConfigFields(codes, config).global.map(f => [f.code, f.value]) - ) - - const triggers = [] - if (global.smsVerificationActive && _.isNumber(global.smsVerificationThreshold)) { - triggers.push( - createTrigger(requirements.sms, global.smsVerificationThreshold) - ) - } - if (global.idCardDataVerificationActive && _.isNumber(global.idCardDataVerificationThreshold)) { - triggers.push( - createTrigger(requirements.idData, global.idCardDataVerificationThreshold) - ) - } - if (global.idCardPhotoVerificationActive && _.isNumber(global.idCardPhotoVerificationThreshold)) { - triggers.push( - createTrigger(requirements.idPhoto, global.idCardPhotoVerificationThreshold) - ) - } - if (global.frontCameraVerificationActive && _.isNumber(global.frontCameraVerificationThreshold)) { - triggers.push( - createTrigger(requirements.facePhoto, global.frontCameraVerificationThreshold) - ) - } - if (global.sanctionsVerificationActive && _.isNumber(global.sanctionsVerificationThreshold)) { - triggers.push( - createTrigger(requirements.sanctions, global.sanctionsVerificationThreshold) - ) - } - if (global.hardLimitVerificationActive && _.isNumber(global.hardLimitVerificationThreshold)) { - triggers.push( - createTrigger(requirements.suspend, global.hardLimitVerificationThreshold, suspensionDays) - ) - } - return { - triggers, - ['compliance_rejectAddressReuse']: global.rejectAddressReuseActive - } -} - -function migrateConfig (config) { - return { - ...migrateCommissions(config), - ...migrateLocales(config), - ...migrateCashOut(config), - ...migrateNotifications(config), - ...migrateWallet(config), - ...migrateOperatorInfo(config), - ...migrateReceiptPrinting(config), - ...migrateCoinATMRadar(config), - ...migrateTermsAndConditions(config), - ...migrateComplianceTriggers(config) - } -} - -function migrateAccounts (accounts) { - const accountArray = [ - 'bitgo', - 'bitstamp', - 'blockcypher', - 'infura', - 'itbit', - 'kraken', - 'mailgun', - 'twilio' - ] - - const services = _.keyBy('code', accounts) - const serviceFields = _.mapValues(({ fields }) => _.keyBy('code', fields))(services) - const allAccounts = _.mapValues(_.mapValues(_.get('value')))(serviceFields) - return _.pick(accountArray)(allAccounts) -} - -function migrate (config, accounts) { - return { - config: migrateConfig(config), - accounts: migrateAccounts(accounts) - } -} - -module.exports = { migrate } diff --git a/lib/machine-loader.js b/lib/machine-loader.js index dcaa78b6..f49d57cf 100644 --- a/lib/machine-loader.js +++ b/lib/machine-loader.js @@ -129,6 +129,8 @@ function getMachineNames (config) { .then(([rawMachines, pings, events, config, heartbeat, performance]) => { const mergeByDeviceId = (x, y) => _.values(_.merge(_.keyBy('deviceId', x), _.keyBy('deviceId', y))) const machines = mergeByDeviceId(mergeByDeviceId(rawMachines, heartbeat), performance) + console.log('machines', machines) + console.log(machines.map(addName(pings, events, config))) return machines.map(addName(pings, events, config)) }) diff --git a/lib/new-admin/middlewares/context.js b/lib/new-admin/middlewares/context.js index c955bb49..8e69bf3d 100644 --- a/lib/new-admin/middlewares/context.js +++ b/lib/new-admin/middlewares/context.js @@ -1,5 +1,4 @@ const { AuthenticationError } = require('apollo-server-express') -const base64 = require('base-64') const users = require('../../users') const buildApolloContext = async ({ req, res }) => { diff --git a/migrations/1509439657189-add_machine_name_to_devices.js b/migrations/1509439657189-add_machine_name_to_devices.js index 36895d43..89a4109c 100644 --- a/migrations/1509439657189-add_machine_name_to_devices.js +++ b/migrations/1509439657189-add_machine_name_to_devices.js @@ -1,25 +1,15 @@ const db = require('./db') -const migrateTools = require('./migrate-tools') +// This migration was updated on v10.2 +// it's from before 7.5 and we update one major version at a time +// Data migration was removed, keeping only the schema update exports.up = function (next) { - return migrateTools.migrateNames() - .then(updateSql => { - const sql = [ - 'alter table devices add column name text', - updateSql, - 'alter table devices alter column name set not null' - ] + const sql = [ + 'alter table devices add column name text', + 'alter table devices alter column name set not null' + ] - return db.multi(sql, next) - }) - .catch(() => { - const sql = [ - 'alter table devices add column name text', - 'alter table devices alter column name set not null' - ] - - return db.multi(sql, next) - }) + return db.multi(sql, next) } exports.down = function (next) { diff --git a/migrations/1599523522436-migrate-config.js b/migrations/1599523522436-migrate-config.js index 6b950f95..1f232bca 100644 --- a/migrations/1599523522436-migrate-config.js +++ b/migrations/1599523522436-migrate-config.js @@ -1,34 +1,9 @@ -const db = require('./db') -const machineLoader = require('../lib/machine-loader') -const { migrationSaveConfig, saveAccounts, loadLatest } = require('../lib/new-settings-loader') -const { migrate } = require('../lib/config-migration') - -const _ = require('lodash/fp') - -const OLD_SETTINGS_LOADER_SCHEMA_VERSION = 1 - +// This migration was actually a config update +// it's from before 7.5 and we update one major version at a time +// v10.2 is good enough to deprecate it +// file still has to exist so that the migration tool doesn't throw an error module.exports.up = function (next) { - function migrateConfig (settings) { - const newSettings = migrate(settings.config, settings.accounts) - return Promise.all([ - migrationSaveConfig(newSettings.config), - saveAccounts(newSettings.accounts) - ]) - .then(() => next()) - } - - loadLatest(OLD_SETTINGS_LOADER_SCHEMA_VERSION) - .then(settings => _.isEmpty(settings.config) - ? next() - : migrateConfig(settings) - ) - .catch(err => { - if (err.message === 'lamassu-server is not configured') { - return next() - } - console.log(err.message) - return next(err) - }) + next() } module.exports.down = function (next) { diff --git a/migrations/migrate-tools.js b/migrations/migrate-tools.js deleted file mode 100644 index 0aa1f62c..00000000 --- a/migrations/migrate-tools.js +++ /dev/null @@ -1,16 +0,0 @@ -const pgp = require('pg-promise')() -const _ = require('lodash/fp') - -const settingsLoader = require('../lib/admin/settings-loader') -const machineLoader = require('../lib/machine-loader') - -module.exports = {migrateNames} - -function migrateNames () { - const cs = new pgp.helpers.ColumnSet(['?device_id', 'name'], {table: 'devices'}) - - return settingsLoader.loadLatestConfig(false) - .then(config => machineLoader.getMachineNames(config)) - .then(_.map(r => ({device_id: r.deviceId, name: r.name}))) - .then(data => pgp.helpers.update(data, cs) + ' WHERE t.device_id=v.device_id') -} diff --git a/new-lamassu-admin/package.json b/new-lamassu-admin/package.json index e97b48a6..f04464d6 100644 --- a/new-lamassu-admin/package.json +++ b/new-lamassu-admin/package.json @@ -18,7 +18,6 @@ "apollo-link-http": "^1.5.17", "apollo-upload-client": "^13.0.0", "axios": "0.21.1", - "base-64": "^1.0.0", "bignumber.js": "9.0.0", "classnames": "2.2.6", "countries-and-timezones": "^2.4.0", diff --git a/package.json b/package.json index 9c9cdaa8..9eedefdc 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,6 @@ "apollo-server-express": "2.25.1", "argon2": "0.28.2", "axios": "0.21.1", - "base-64": "^1.0.0", "base-x": "3.0.9", "base64url": "^3.0.1", "bchaddrjs": "^0.3.0",