Merge pull request #1188 from siiky/feat/lam-434/tc-edition
feat: cache the T&C based on their hash
This commit is contained in:
commit
1b2d101392
7 changed files with 113 additions and 32 deletions
|
|
@ -4,7 +4,7 @@ const nmd = require('nano-markdown')
|
||||||
const { accounts: accountsConfig, countries, languages } = require('../new-admin/config')
|
const { accounts: accountsConfig, countries, languages } = require('../new-admin/config')
|
||||||
const plugins = require('../plugins')
|
const plugins = require('../plugins')
|
||||||
const configManager = require('../new-config-manager')
|
const configManager = require('../new-config-manager')
|
||||||
const customRequestQueries = require('../new-admin/services/customInfoRequests')
|
const { batchGetCustomInfoRequest, getCustomInfoRequests } = require('../new-admin/services/customInfoRequests')
|
||||||
const state = require('../middlewares/state')
|
const state = require('../middlewares/state')
|
||||||
|
|
||||||
const VERSION = require('../../package.json').version
|
const VERSION = require('../../package.json').version
|
||||||
|
|
@ -56,7 +56,7 @@ const buildTriggers = (allTriggers) => {
|
||||||
|
|
||||||
return _.flow(
|
return _.flow(
|
||||||
_.map(_.get('customInfoRequestId')),
|
_.map(_.get('customInfoRequestId')),
|
||||||
customRequestQueries.batchGetCustomInfoRequest
|
batchGetCustomInfoRequest
|
||||||
)(customTriggers)
|
)(customTriggers)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
res.forEach((details, index) => {
|
res.forEach((details, index) => {
|
||||||
|
|
@ -68,18 +68,6 @@ const buildTriggers = (allTriggers) => {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* TODO: From `lib/routes/termsAndConditionsRoutes.js` -- remove this after
|
|
||||||
* terms are removed from the GraphQL API too.
|
|
||||||
*/
|
|
||||||
const massageTerms = terms => (terms.active && terms.text) ? ({
|
|
||||||
delay: Boolean(terms.delay),
|
|
||||||
title: terms.title,
|
|
||||||
text: nmd(terms.text),
|
|
||||||
accept: terms.acceptButtonText,
|
|
||||||
cancel: terms.cancelButtonText,
|
|
||||||
}) : null
|
|
||||||
|
|
||||||
const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings, }) => {
|
const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings, }) => {
|
||||||
const massageCoins = _.map(_.pick([
|
const massageCoins = _.map(_.pick([
|
||||||
'batchable',
|
'batchable',
|
||||||
|
|
@ -106,13 +94,12 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
!!configManager.getCompliance(settings.config).enablePaperWalletOnly,
|
!!configManager.getCompliance(settings.config).enablePaperWalletOnly,
|
||||||
configManager.getTriggersAutomation(customRequestQueries.getCustomInfoRequests(), settings.config),
|
configManager.getTriggersAutomation(getCustomInfoRequests(true), settings.config),
|
||||||
buildTriggers(configManager.getTriggers(settings.config)),
|
buildTriggers(configManager.getTriggers(settings.config)),
|
||||||
configManager.getWalletSettings('BTC', settings.config).layer2 !== 'no-layer2',
|
configManager.getWalletSettings('BTC', settings.config).layer2 !== 'no-layer2',
|
||||||
configManager.getLocale(deviceId, settings.config),
|
configManager.getLocale(deviceId, settings.config),
|
||||||
configManager.getOperatorInfo(settings.config),
|
configManager.getOperatorInfo(settings.config),
|
||||||
configManager.getReceipt(settings.config),
|
configManager.getReceipt(settings.config),
|
||||||
massageTerms(configManager.getTermsConditions(settings.config)),
|
|
||||||
!!configManager.getCashOut(deviceId, settings.config).active,
|
!!configManager.getCashOut(deviceId, settings.config).active,
|
||||||
])
|
])
|
||||||
.then(([
|
.then(([
|
||||||
|
|
@ -123,7 +110,6 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
|
||||||
localeInfo,
|
localeInfo,
|
||||||
operatorInfo,
|
operatorInfo,
|
||||||
receiptInfo,
|
receiptInfo,
|
||||||
terms,
|
|
||||||
twoWayMode,
|
twoWayMode,
|
||||||
]) =>
|
]) =>
|
||||||
(currentConfigVersion && currentConfigVersion >= staticConf.configVersion) ?
|
(currentConfigVersion && currentConfigVersion >= staticConf.configVersion) ?
|
||||||
|
|
@ -143,7 +129,6 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
|
||||||
twoWayMode,
|
twoWayMode,
|
||||||
speedtestFiles,
|
speedtestFiles,
|
||||||
urlsToPing,
|
urlsToPing,
|
||||||
terms,
|
|
||||||
}),
|
}),
|
||||||
_.update('triggersAutomation', _.mapValues(_.eq('Automatic'))),
|
_.update('triggersAutomation', _.mapValues(_.eq('Automatic'))),
|
||||||
addOperatorInfo(operatorInfo),
|
addOperatorInfo(operatorInfo),
|
||||||
|
|
@ -232,8 +217,60 @@ const configs = (parent, { currentConfigVersion }, { deviceId, deviceName, opera
|
||||||
}),
|
}),
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
|
||||||
|
const massageTerms = terms => (terms.active && terms.text) ? ({
|
||||||
|
delay: Boolean(terms.delay),
|
||||||
|
title: terms.title,
|
||||||
|
text: nmd(terms.text),
|
||||||
|
accept: terms.acceptButtonText,
|
||||||
|
cancel: terms.cancelButtonText,
|
||||||
|
}) : null
|
||||||
|
|
||||||
|
/*
|
||||||
|
* The type of the result of `configManager.getTermsConditions()` is more or
|
||||||
|
* less `Maybe (Maybe Hash, Maybe TC)`. Each case has a specific meaning to the
|
||||||
|
* machine:
|
||||||
|
*
|
||||||
|
* Nothing => Nothing
|
||||||
|
* There are no T&C or they've been removed/disabled.
|
||||||
|
*
|
||||||
|
* Just (Nothing, _) => Nothing
|
||||||
|
* Shouldn't happen! Treated as if there were no T&C.
|
||||||
|
*
|
||||||
|
* Just (Just hash, Nothing) => Nothing
|
||||||
|
* May happen (after `massageTerms`) if T&C are disabled.
|
||||||
|
*
|
||||||
|
* Just (Just hash, Just tc) => Just (hash, Just tc) or Just (hash, Nothing)
|
||||||
|
* `tc` is sent depending on whether the `hash` differs from `currentHash` or
|
||||||
|
* not.
|
||||||
|
*/
|
||||||
|
const terms = (parent, { currentConfigVersion, currentHash }, { deviceId, settings }, info) => {
|
||||||
|
const isNone = x => _.isNil(x) || _.isEmpty(x)
|
||||||
|
|
||||||
|
let latestTerms = configManager.getTermsConditions(settings.config)
|
||||||
|
if (isNone(latestTerms)) return null
|
||||||
|
|
||||||
|
const hash = latestTerms.hash
|
||||||
|
if (!_.isString(hash)) return null
|
||||||
|
|
||||||
|
latestTerms = massageTerms(latestTerms)
|
||||||
|
if (isNone(latestTerms)) return null
|
||||||
|
|
||||||
|
const isHashNew = hash !== currentHash
|
||||||
|
const text = isHashNew ? latestTerms.text : null
|
||||||
|
|
||||||
|
return plugins(settings, deviceId)
|
||||||
|
.fetchCurrentConfigVersion()
|
||||||
|
.catch(() => null)
|
||||||
|
.then(configVersion => isHashNew || _.isNil(currentConfigVersion) || currentConfigVersion < configVersion)
|
||||||
|
.then(isVersionNew => isVersionNew ? _.omit(['text'], latestTerms) : null)
|
||||||
|
.then(details => ({ hash, details, text }))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
Query: {
|
Query: {
|
||||||
configs
|
configs,
|
||||||
|
terms,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,14 +69,19 @@ type Trigger {
|
||||||
thresholdDays: Int
|
thresholdDays: Int
|
||||||
}
|
}
|
||||||
|
|
||||||
type Terms {
|
type TermsDetails {
|
||||||
delay: Boolean!
|
delay: Boolean!
|
||||||
title: String!
|
title: String!
|
||||||
text: String!
|
|
||||||
accept: String!
|
accept: String!
|
||||||
cancel: String!
|
cancel: String!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Terms {
|
||||||
|
hash: String!
|
||||||
|
text: String
|
||||||
|
details: TermsDetails
|
||||||
|
}
|
||||||
|
|
||||||
type StaticConfig {
|
type StaticConfig {
|
||||||
configVersion: Int!
|
configVersion: Int!
|
||||||
|
|
||||||
|
|
@ -98,8 +103,6 @@ type StaticConfig {
|
||||||
|
|
||||||
triggersAutomation: TriggersAutomation!
|
triggersAutomation: TriggersAutomation!
|
||||||
triggers: [Trigger!]!
|
triggers: [Trigger!]!
|
||||||
|
|
||||||
terms: Terms
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type DynamicCoinValues {
|
type DynamicCoinValues {
|
||||||
|
|
@ -147,5 +150,6 @@ type Configs {
|
||||||
|
|
||||||
type Query {
|
type Query {
|
||||||
configs(currentConfigVersion: Int): Configs!
|
configs(currentConfigVersion: Int): Configs!
|
||||||
|
terms(currentHash: String, currentConfigVersion: Int): Terms
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,6 @@ function sha256 (buf) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const populateDeviceId = function (req, res, next) {
|
const populateDeviceId = function (req, res, next) {
|
||||||
logger.info(`DEBUG LOG - Method: ${req.method} Path: ${req.path}`)
|
|
||||||
const deviceId = _.isFunction(req.connection.getPeerCertificate)
|
const deviceId = _.isFunction(req.connection.getPeerCertificate)
|
||||||
? sha256(req.connection.getPeerCertificate().raw)
|
? sha256(req.connection.getPeerCertificate().raw)
|
||||||
: null
|
: null
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const { getCustomInfoRequests } = require('./new-admin/services/customInfoRequests')
|
|
||||||
|
|
||||||
const namespaces = {
|
const namespaces = {
|
||||||
ADVANCED: 'advanced',
|
ADVANCED: 'advanced',
|
||||||
|
|
@ -21,6 +20,7 @@ const filter = namespace => _.pickBy((value, key) => _.startsWith(`${namespace}_
|
||||||
const strip = key => _.mapKeys(stripl(`${key}_`))
|
const strip = key => _.mapKeys(stripl(`${key}_`))
|
||||||
|
|
||||||
const fromNamespace = _.curry((key, config) => _.compose(strip(key), filter(key))(config))
|
const fromNamespace = _.curry((key, config) => _.compose(strip(key), filter(key))(config))
|
||||||
|
const toNamespace = _.curry((ns, config) => _.mapKeys(key => `${ns}_${key}`, config))
|
||||||
|
|
||||||
const getCommissions = (cryptoCode, deviceId, config) => {
|
const getCommissions = (cryptoCode, deviceId, config) => {
|
||||||
const commissions = fromNamespace(namespaces.COMMISSIONS)(config)
|
const commissions = fromNamespace(namespaces.COMMISSIONS)(config)
|
||||||
|
|
@ -116,8 +116,9 @@ const getGlobalNotifications = config => getNotifications(null, null, config)
|
||||||
|
|
||||||
const getTriggers = _.get('triggers')
|
const getTriggers = _.get('triggers')
|
||||||
|
|
||||||
const getTriggersAutomation = config => {
|
/* `customInfoRequests` is the result of a call to `getCustomInfoRequests` */
|
||||||
return getCustomInfoRequests(true)
|
const getTriggersAutomation = (customInfoRequests, config) => {
|
||||||
|
return customInfoRequests
|
||||||
.then(infoRequests => {
|
.then(infoRequests => {
|
||||||
const defaultAutomation = _.get('triggersConfig_automation')(config)
|
const defaultAutomation = _.get('triggersConfig_automation')(config)
|
||||||
const requirements = {
|
const requirements = {
|
||||||
|
|
@ -154,6 +155,8 @@ const getCryptoUnits = (crypto, config) => {
|
||||||
return getWalletSettings(crypto, config).cryptoUnits
|
return getWalletSettings(crypto, config).cryptoUnits
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const setTermsConditions = toNamespace(namespaces.TERMS_CONDITIONS)
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
getWalletSettings,
|
getWalletSettings,
|
||||||
getCashInSettings,
|
getCashInSettings,
|
||||||
|
|
@ -173,5 +176,6 @@ module.exports = {
|
||||||
getGlobalCashOut,
|
getGlobalCashOut,
|
||||||
getCashOut,
|
getCashOut,
|
||||||
getCryptosFromWalletNamespace,
|
getCryptosFromWalletNamespace,
|
||||||
getCryptoUnits
|
getCryptoUnits,
|
||||||
|
setTermsConditions,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,11 @@
|
||||||
|
const crypto = require('crypto')
|
||||||
|
|
||||||
const _ = require('lodash/fp')
|
const _ = require('lodash/fp')
|
||||||
const db = require('./db')
|
const db = require('./db')
|
||||||
const migration = require('./config-migration')
|
const migration = require('./config-migration')
|
||||||
const { asyncLocalStorage } = require('./async-storage')
|
const { asyncLocalStorage } = require('./async-storage')
|
||||||
const { getOperatorId } = require('./operator')
|
const { getOperatorId } = require('./operator')
|
||||||
|
const { getTermsConditions, setTermsConditions } = require('./new-config-manager')
|
||||||
|
|
||||||
const OLD_SETTINGS_LOADER_SCHEMA_VERSION = 1
|
const OLD_SETTINGS_LOADER_SCHEMA_VERSION = 1
|
||||||
const NEW_SETTINGS_LOADER_SCHEMA_VERSION = 2
|
const NEW_SETTINGS_LOADER_SCHEMA_VERSION = 2
|
||||||
|
|
@ -23,6 +26,29 @@ const SECRET_FIELDS = [
|
||||||
'twilio.authToken'
|
'twilio.authToken'
|
||||||
]
|
]
|
||||||
|
|
||||||
|
/*
|
||||||
|
* JSON.stringify isn't necessarily deterministic so this function may compute
|
||||||
|
* different hashes for the same object.
|
||||||
|
*/
|
||||||
|
const md5hash = text =>
|
||||||
|
crypto
|
||||||
|
.createHash('MD5')
|
||||||
|
.update(text)
|
||||||
|
.digest('hex')
|
||||||
|
|
||||||
|
const addTermsHash = configs => {
|
||||||
|
const terms = _.omit(['hash'], getTermsConditions(configs))
|
||||||
|
return _.isEmpty(terms) ?
|
||||||
|
configs :
|
||||||
|
_.flow(
|
||||||
|
_.get('text'),
|
||||||
|
md5hash,
|
||||||
|
hash => _.set('hash', hash, terms),
|
||||||
|
setTermsConditions,
|
||||||
|
_.assign(configs),
|
||||||
|
)(terms)
|
||||||
|
}
|
||||||
|
|
||||||
const accountsSql = `update user_config set data = $2, valid = $3, schema_version = $4 where type = $1;
|
const accountsSql = `update user_config set data = $2, valid = $3, schema_version = $4 where type = $1;
|
||||||
insert into user_config (type, data, valid, schema_version)
|
insert into user_config (type, data, valid, schema_version)
|
||||||
select $1, $2, $3, $4 where $1 not in (select type from user_config)`
|
select $1, $2, $3, $4 where $1 not in (select type from user_config)`
|
||||||
|
|
@ -74,7 +100,7 @@ const configSql = 'insert into user_config (type, data, valid, schema_version) v
|
||||||
function saveConfig (config) {
|
function saveConfig (config) {
|
||||||
return Promise.all([loadLatestConfigOrNone(), getOperatorId('middleware')])
|
return Promise.all([loadLatestConfigOrNone(), getOperatorId('middleware')])
|
||||||
.then(([currentConfig, operatorId]) => {
|
.then(([currentConfig, operatorId]) => {
|
||||||
const newConfig = _.assign(currentConfig, config)
|
const newConfig = addTermsHash(_.assign(currentConfig, config))
|
||||||
return db.tx(t => {
|
return db.tx(t => {
|
||||||
return t.none(configSql, ['config', { config: newConfig }, true, NEW_SETTINGS_LOADER_SCHEMA_VERSION])
|
return t.none(configSql, ['config', { config: newConfig }, true, NEW_SETTINGS_LOADER_SCHEMA_VERSION])
|
||||||
.then(() => t.none('NOTIFY $1:name, $2', ['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })]))
|
.then(() => t.none('NOTIFY $1:name, $2', ['reload', JSON.stringify({ schema: asyncLocalStorage.getStore().get('schema'), operatorId })]))
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@ const plugins = require('../plugins')
|
||||||
const semver = require('semver')
|
const semver = require('semver')
|
||||||
const state = require('../middlewares/state')
|
const state = require('../middlewares/state')
|
||||||
const version = require('../../package.json').version
|
const version = require('../../package.json').version
|
||||||
const customRequestQueries = require('../new-admin/services/customInfoRequests')
|
const { batchGetCustomInfoRequest, getCustomInfoRequests } = require('../new-admin/services/customInfoRequests')
|
||||||
|
|
||||||
const urlsToPing = [
|
const urlsToPing = [
|
||||||
`us.archive.ubuntu.com`,
|
`us.archive.ubuntu.com`,
|
||||||
|
|
@ -45,7 +45,7 @@ const buildTriggers = (allTriggers) => {
|
||||||
return !_.isNil(o.customInfoRequestId) && !_.isEmpty(o.customInfoRequestId)
|
return !_.isNil(o.customInfoRequestId) && !_.isEmpty(o.customInfoRequestId)
|
||||||
}, allTriggers)
|
}, allTriggers)
|
||||||
|
|
||||||
return _.flow([_.map(_.get('customInfoRequestId')), customRequestQueries.batchGetCustomInfoRequest])(customTriggers)
|
return _.flow([_.map(_.get('customInfoRequestId')), batchGetCustomInfoRequest])(customTriggers)
|
||||||
.then(res => {
|
.then(res => {
|
||||||
res.forEach((details, index) => {
|
res.forEach((details, index) => {
|
||||||
// make sure we aren't attaching the details to the wrong trigger
|
// make sure we aren't attaching the details to the wrong trigger
|
||||||
|
|
@ -85,7 +85,7 @@ function poll (req, res, next) {
|
||||||
pi.recordPing(deviceTime, machineVersion, machineModel),
|
pi.recordPing(deviceTime, machineVersion, machineModel),
|
||||||
pi.pollQueries(),
|
pi.pollQueries(),
|
||||||
buildTriggers(configManager.getTriggers(settings.config)),
|
buildTriggers(configManager.getTriggers(settings.config)),
|
||||||
configManager.getTriggersAutomation(settings.config)
|
configManager.getTriggersAutomation(getCustomInfoRequests(true), settings.config),
|
||||||
])
|
])
|
||||||
.then(([_pingRes, results, triggers, triggersAutomation]) => {
|
.then(([_pingRes, results, triggers, triggersAutomation]) => {
|
||||||
const reboot = pid && state.reboots?.[operatorId]?.[deviceId] === pid
|
const reboot = pid && state.reboots?.[operatorId]?.[deviceId] === pid
|
||||||
|
|
|
||||||
11
migrations/1649944954805-terms-and-conditions-hash.js
Normal file
11
migrations/1649944954805-terms-and-conditions-hash.js
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
const { saveConfig } = require('../lib/new-settings-loader')
|
||||||
|
|
||||||
|
exports.up = function (next) {
|
||||||
|
return saveConfig({})
|
||||||
|
.then(next)
|
||||||
|
.catch(next)
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.down = function (next) {
|
||||||
|
next()
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue