feat: Create migration from old config to new (#424)
* fix: adapt old settings loader to the new schema (filter schema_version) feat: migrate commissions globals feat: migrate locales refactor: generalize the old fields search chore: created functions signatures for all config migrations feat: created wallet migration feat: migrate operator info feat: migrate coin atm radar feat: migrate terms and conditions feat: migrate commissions overrides fix: removed the wallet_COIN_active field (don't exist anymore) chore: moved the config-migration lib to the lib folder feat: migrate cashout configurations feat: migrate notifications globals feat: export migration function feat: migrate most of notifications scoped configs fix: added the missing text property to the terms and conditions migration feat: migrate compliance triggers feat: migrate receipt printing feat: migrate accounts chore: remove test code form module refactor: change some functions naming fix: set default trigger type to 'volume' feat: added threshold days (default 1) to triggers fix: removed strike from the accounts importing refactor: cleaner code on fixed properties feat: avoid repeated crypto/machine pairs on the commissions overrides migrations refactor: make renameAccountFields function internal to the account migration function fix: migrate all crypto scoped commission overrides * fix: return plain objects from functions to make the jsons more readable fix: fix bitgo fields casing fix: improve commissions migration function readability refactor: standard styling * feat: add fallback values to the migration * feat: created db migration for the new config * feat: create migration to move machine names from file to db fix: updates machine names before the config migration fix: load machineLoader fix: create a param to ignore the schema version when loading the latest config using the old loader * refactor: remove unnecessary arguments on createTrigger function fix: check if there's an smsVerificationThreshold configured prior to migrating triggers * fix: migrate triggers with the correct thresholds and verify if they're valid
This commit is contained in:
parent
3c6f547349
commit
ccf7eacfad
8 changed files with 546 additions and 42 deletions
458
lib/config-migration.js
Normal file
458
lib/config-migration.js
Normal file
|
|
@ -0,0 +1,458 @@
|
|||
const _ = require('lodash/fp')
|
||||
const uuid = require('uuid')
|
||||
const { COINS } = require('../lib/new-admin/config/coins')
|
||||
const { scopedValue } = require('./config-manager')
|
||||
|
||||
const GLOBAL = 'global'
|
||||
const ALL_CRYPTOS = _.values(COINS).sort()
|
||||
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) => _.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 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(global.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: s.scope.crypto,
|
||||
id: uuid.v4()
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
function migrateLocales (config) {
|
||||
const codes = {
|
||||
country: 'country',
|
||||
fiatCurrency: 'fiatCurrency',
|
||||
machineLanguages: 'languages',
|
||||
cryptoCurrencies: 'cryptoCurrencies'
|
||||
}
|
||||
|
||||
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()
|
||||
}))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TODO new-admin: virtualCashOutDenomination
|
||||
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])
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// TODO new-admin: rejectAddressReuseActive
|
||||
function migrateComplianceTriggers (config) {
|
||||
const triggerTypes = {
|
||||
amount: 'txAmount',
|
||||
velocity: 'txVelocity',
|
||||
volume: 'txVolume',
|
||||
consecutiveDays: 'consecutiveDays'
|
||||
}
|
||||
|
||||
const requirements = {
|
||||
sms: 'sms',
|
||||
idData: 'idData',
|
||||
idPhoto: 'idPhoto',
|
||||
facePhoto: 'facePhoto',
|
||||
sanctions: 'sanctions'
|
||||
}
|
||||
|
||||
function createTrigger (
|
||||
requirement,
|
||||
threshold
|
||||
) {
|
||||
return {
|
||||
id: uuid.v4(),
|
||||
cashDirection: 'both',
|
||||
threshold,
|
||||
thresholdDays: 1,
|
||||
triggerType: triggerTypes.volume,
|
||||
requirement
|
||||
}
|
||||
}
|
||||
|
||||
const codes = [
|
||||
'smsVerificationActive',
|
||||
'smsVerificationThreshold',
|
||||
'idCardDataVerificationActive',
|
||||
'idCardDataVerificationThreshold',
|
||||
'idCardPhotoVerificationActive',
|
||||
'idCardPhotoVerificationThreshold',
|
||||
'frontCameraVerificationActive',
|
||||
'frontCameraVerificationThreshold',
|
||||
'sanctionsVerificationActive',
|
||||
'sanctionsVerificationThreshold'
|
||||
]
|
||||
|
||||
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)
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
triggers
|
||||
}
|
||||
}
|
||||
|
||||
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'
|
||||
]
|
||||
|
||||
return _.pick(accountArray)(accounts)
|
||||
}
|
||||
|
||||
function migrate (config, accounts) {
|
||||
return {
|
||||
config: migrateConfig(config),
|
||||
accounts: migrateAccounts(accounts)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
migrateConfig,
|
||||
migrateAccounts,
|
||||
migrate
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue