191 lines
5.9 KiB
JavaScript
191 lines
5.9 KiB
JavaScript
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
|
|
}
|