chore: server code formatting

This commit is contained in:
Rafael Taranto 2025-05-12 15:35:00 +01:00
parent aedabcbdee
commit 68517170e2
234 changed files with 9824 additions and 6195 deletions

View file

@ -4,7 +4,10 @@ const nmd = require('nano-markdown')
const plugins = require('../plugins')
const configManager = require('../new-config-manager')
const settingsLoader = require('../new-settings-loader')
const { batchGetCustomInfoRequest, getCustomInfoRequests } = require('../new-admin/services/customInfoRequests')
const {
batchGetCustomInfoRequest,
getCustomInfoRequests,
} = require('../new-admin/services/customInfoRequests')
const state = require('../middlewares/state')
const { getMachine } = require('../machine-loader')
@ -14,23 +17,26 @@ const urlsToPing = [
`us.archive.ubuntu.com`,
`uk.archive.ubuntu.com`,
`za.archive.ubuntu.com`,
`cn.archive.ubuntu.com`
`cn.archive.ubuntu.com`,
]
const speedtestFiles = [
{
url: 'https://github.com/lamassu/speed-test-assets/raw/main/python-defaults_2.7.18-3.tar.gz',
size: 44668
}
size: 44668,
},
]
const addSmthInfo = (dstField, srcFields) => smth =>
(smth && smth.active) ? _.set(dstField, _.pick(srcFields, smth)) : _.identity
smth && smth.active ? _.set(dstField, _.pick(srcFields, smth)) : _.identity
const addOperatorInfo = addSmthInfo(
'operatorInfo',
['name', 'phone', 'email', 'website', 'companyNumber']
)
const addOperatorInfo = addSmthInfo('operatorInfo', [
'name',
'phone',
'email',
'website',
'companyNumber',
])
const addReceiptInfo = receiptInfo => ret => {
if (!receiptInfo) return ret
@ -56,87 +62,90 @@ const addReceiptInfo = receiptInfo => ret => {
_.pick(fields),
)(receiptInfo)
return (receiptInfo.paper || receiptInfo.sms) ?
_.set('receiptInfo', receiptInfo, ret) :
ret
return receiptInfo.paper || receiptInfo.sms
? _.set('receiptInfo', receiptInfo, ret)
: ret
}
const addMachineScreenOpts = smth => _.update(
'screenOptions',
_.flow(
addSmthInfo(
'rates',
[
'active'
]
)(smth.rates)
const addMachineScreenOpts = smth =>
_.update(
'screenOptions',
_.flow(addSmthInfo('rates', ['active'])(smth.rates)),
)
)
/* TODO: Simplify this. */
const buildTriggers = allTriggers => {
const normalTriggers = []
const customTriggers = _.filter(o => {
if (_.isEmpty(o.customInfoRequestId) || _.isNil(o.customInfoRequestId)) normalTriggers.push(o)
if (_.isEmpty(o.customInfoRequestId) || _.isNil(o.customInfoRequestId))
normalTriggers.push(o)
return !_.isNil(o.customInfoRequestId) && !_.isEmpty(o.customInfoRequestId)
}, allTriggers)
return _.flow(
_.map(_.get('customInfoRequestId')),
batchGetCustomInfoRequest
)(customTriggers)
.then(res => {
res.forEach((details, index) => {
// make sure we aren't attaching the details to the wrong trigger
if (customTriggers[index].customInfoRequestId !== details.id) return
customTriggers[index] = { ...customTriggers[index], customInfoRequest: details }
})
return [...normalTriggers, ...customTriggers]
batchGetCustomInfoRequest,
)(customTriggers).then(res => {
res.forEach((details, index) => {
// make sure we aren't attaching the details to the wrong trigger
if (customTriggers[index].customInfoRequestId !== details.id) return
customTriggers[index] = {
...customTriggers[index],
customInfoRequest: details,
}
})
return [...normalTriggers, ...customTriggers]
})
}
const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings, }) => {
const massageCoins = _.map(_.pick([
'batchable',
'cashInCommission',
'cashInFee',
'cashOutCommission',
'cashOutFee',
'cryptoCode',
'cryptoCodeDisplay',
'cryptoNetwork',
'cryptoUnits',
'display',
'minimumTx',
'isCashInOnly'
]))
const staticConfig = ({
currentConfigVersion,
deviceId,
deviceName,
pq,
settings,
}) => {
const massageCoins = _.map(
_.pick([
'batchable',
'cashInCommission',
'cashInFee',
'cashOutCommission',
'cashOutFee',
'cryptoCode',
'cryptoCodeDisplay',
'cryptoNetwork',
'cryptoUnits',
'display',
'minimumTx',
'isCashInOnly',
]),
)
const staticConf = _.flow(
_.pick([
'coins',
'configVersion',
'timezone',
'screenOptions'
]),
_.pick(['coins', 'configVersion', 'timezone', 'screenOptions']),
_.update('coins', massageCoins),
_.set('serverVersion', VERSION),
)(pq)
return Promise.all([
!!configManager.getCompliance(settings.config).enablePaperWalletOnly,
configManager.getTriggersAutomation(getCustomInfoRequests(true), settings.config),
configManager.getTriggersAutomation(
getCustomInfoRequests(true),
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.getOperatorInfo(settings.config),
configManager.getReceipt(settings.config),
configManager.getAllMachineScreenOpts(settings.config),
!!configManager.getCashOut(deviceId, settings.config).active,
getMachine(deviceId, currentConfigVersion),
configManager.getCustomerAuthenticationMethod(settings.config)
])
.then(([
configManager.getCustomerAuthenticationMethod(settings.config),
]).then(
([
enablePaperWalletOnly,
triggersAutomation,
triggers,
@ -149,88 +158,121 @@ const staticConfig = ({ currentConfigVersion, deviceId, deviceName, pq, settings
{ numberOfCassettes, numberOfRecyclers },
customerAuthentication,
]) =>
(currentConfigVersion && currentConfigVersion >= staticConf.configVersion) ?
null :
_.flow(
_.assign({
enablePaperWalletOnly,
triggersAutomation,
triggers,
hasLightning,
localeInfo: {
country: localeInfo.country,
languages: localeInfo.languages,
fiatCode: localeInfo.fiatCurrency
},
machineInfo: { deviceId, deviceName, numberOfCassettes, numberOfRecyclers },
twoWayMode,
customerAuthentication,
speedtestFiles,
urlsToPing,
}),
addOperatorInfo(operatorInfo),
addReceiptInfo(receiptInfo),
addMachineScreenOpts(machineScreenOpts)
)(staticConf))
currentConfigVersion && currentConfigVersion >= staticConf.configVersion
? null
: _.flow(
_.assign({
enablePaperWalletOnly,
triggersAutomation,
triggers,
hasLightning,
localeInfo: {
country: localeInfo.country,
languages: localeInfo.languages,
fiatCode: localeInfo.fiatCurrency,
},
machineInfo: {
deviceId,
deviceName,
numberOfCassettes,
numberOfRecyclers,
},
twoWayMode,
customerAuthentication,
speedtestFiles,
urlsToPing,
}),
addOperatorInfo(operatorInfo),
addReceiptInfo(receiptInfo),
addMachineScreenOpts(machineScreenOpts),
)(staticConf),
)
}
const setZeroConfLimit = config => coin =>
_.set(
'zeroConfLimit',
configManager.getWalletSettings(coin.cryptoCode, config).zeroConfLimit ?? 0,
coin
coin,
)
const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings }) => {
const massageCassettes = cassettes =>
cassettes ?
_.flow(
cassettes => _.set('physical', _.get('cassettes', cassettes), cassettes),
cassettes => _.set('virtual', _.get('virtualCassettes', cassettes), cassettes),
_.unset('cassettes'),
_.unset('virtualCassettes')
)(cassettes) :
null
cassettes
? _.flow(
cassettes =>
_.set('physical', _.get('cassettes', cassettes), cassettes),
cassettes =>
_.set('virtual', _.get('virtualCassettes', cassettes), cassettes),
_.unset('cassettes'),
_.unset('virtualCassettes'),
)(cassettes)
: null
const massageRecyclers = recyclers =>
recyclers ?
_.flow(
recyclers => _.set('physical', _.get('recyclers', recyclers), recyclers),
recyclers => _.set('virtual', _.get('virtualRecyclers', recyclers), recyclers),
_.unset('recyclers'),
_.unset('virtualRecyclers')
)(recyclers) :
null
recyclers
? _.flow(
recyclers =>
_.set('physical', _.get('recyclers', recyclers), recyclers),
recyclers =>
_.set('virtual', _.get('virtualRecyclers', recyclers), recyclers),
_.unset('recyclers'),
_.unset('virtualRecyclers'),
)(recyclers)
: null
state.pids = _.update(operatorId, _.set(deviceId, { pid, ts: Date.now() }), state.pids)
state.pids = _.update(
operatorId,
_.set(deviceId, { pid, ts: Date.now() }),
state.pids,
)
const res = _.flow(
_.pick(['areThereAvailablePromoCodes', 'balances', 'cassettes', 'recyclers', 'coins', 'rates']),
_.pick([
'areThereAvailablePromoCodes',
'balances',
'cassettes',
'recyclers',
'coins',
'rates',
]),
_.update('cassettes', massageCassettes),
_.update('recyclers', massageRecyclers),
/* [{ cryptoCode, rates }, ...] => [[cryptoCode, rates], ...] */
_.update('coins', _.map(({ cryptoCode, rates }) => [cryptoCode, rates])),
_.update(
'coins',
_.map(({ cryptoCode, rates }) => [cryptoCode, rates]),
),
/* [{ cryptoCode: balance }, ...] => [[cryptoCode, { balance }], ...] */
_.update('balances', _.flow(
_.toPairs,
_.map(([cryptoCode, balance]) => [cryptoCode, { balance }])
)),
_.update(
'balances',
_.flow(
_.toPairs,
_.map(([cryptoCode, balance]) => [cryptoCode, { balance }]),
),
),
/* Group the separate objects by cryptoCode */
/* { balances, coins, rates } => { cryptoCode: { balance, ask, bid, cashIn, cashOut }, ... } */
({ areThereAvailablePromoCodes, balances, cassettes, recyclers, coins, rates }) => ({
({
areThereAvailablePromoCodes,
balances,
cassettes,
recyclers,
coins,
rates,
}) => ({
areThereAvailablePromoCodes,
cassettes,
recyclers,
coins: _.flow(
_.reduce(
(ret, [cryptoCode, obj]) => _.update(cryptoCode, _.assign(obj), ret),
rates
rates,
),
/* { cryptoCode: { balance, ask, bid, cashIn, cashOut }, ... } => [[cryptoCode, { balance, ask, bid, cashIn, cashOut }], ...] */
@ -240,17 +282,36 @@ const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
_.map(([cryptoCode, obj]) => _.set('cryptoCode', cryptoCode, obj)),
/* Only send coins which have all information needed by the machine. This prevents the machine going down if there's an issue with the coin node */
_.filter(coin => ['ask', 'bid', 'balance', 'cashIn', 'cashOut', 'cryptoCode'].every(it => it in coin))
)(_.concat(balances, coins))
_.filter(coin =>
['ask', 'bid', 'balance', 'cashIn', 'cashOut', 'cryptoCode'].every(
it => it in coin,
),
),
)(_.concat(balances, coins)),
}),
_.update('coins', _.map(setZeroConfLimit(settings.config))),
_.set('reboot', !!pid && state.reboots?.[operatorId]?.[deviceId] === pid),
_.set('shutdown', !!pid && state.shutdowns?.[operatorId]?.[deviceId] === pid),
_.set('restartServices', !!pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid),
_.set('emptyUnit', !!pid && state.emptyUnit?.[operatorId]?.[deviceId] === pid),
_.set('refillUnit', !!pid && state.refillUnit?.[operatorId]?.[deviceId] === pid),
_.set('diagnostics', !!pid && state.diagnostics?.[operatorId]?.[deviceId] === pid),
_.set(
'shutdown',
!!pid && state.shutdowns?.[operatorId]?.[deviceId] === pid,
),
_.set(
'restartServices',
!!pid && state.restartServicesMap?.[operatorId]?.[deviceId] === pid,
),
_.set(
'emptyUnit',
!!pid && state.emptyUnit?.[operatorId]?.[deviceId] === pid,
),
_.set(
'refillUnit',
!!pid && state.refillUnit?.[operatorId]?.[deviceId] === pid,
),
_.set(
'diagnostics',
!!pid && state.diagnostics?.[operatorId]?.[deviceId] === pid,
),
)(pq)
// Clean up the state middleware and prevent commands from being issued more than once
@ -269,8 +330,11 @@ const dynamicConfig = ({ deviceId, operatorId, pid, pq, settings, }) => {
return res
}
const configs = (parent, { currentConfigVersion }, { deviceId, deviceName, operatorId, pid, settings }, info) =>
const configs = (
parent,
{ currentConfigVersion },
{ deviceId, deviceName, operatorId, pid, settings },
) =>
plugins(settings, deviceId)
.pollQueries()
.then(pq => ({
@ -290,15 +354,17 @@ const configs = (parent, { currentConfigVersion }, { deviceId, deviceName, opera
}),
}))
const massageTerms = terms => (terms.active && terms.text) ? ({
tcPhoto: Boolean(terms.tcPhoto),
delay: Boolean(terms.delay),
title: terms.title,
text: nmd(terms.text),
accept: terms.acceptButtonText,
cancel: terms.cancelButtonText,
}) : null
const massageTerms = terms =>
terms.active && terms.text
? {
tcPhoto: Boolean(terms.tcPhoto),
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
@ -327,7 +393,7 @@ const massageTerms = terms => (terms.active && terms.text) ? ({
* If the `hash` differs from `currentHash` then everything is resent (to
* simplify machine implementation).
*/
const terms = (parent, { currentConfigVersion, currentHash }, { deviceId, settings }, info) => {
const terms = (parent, { currentConfigVersion, currentHash }, { settings }) => {
const isNone = x => _.isNil(x) || _.isEmpty(x)
let latestTerms = configManager.getTermsConditions(settings.config)
@ -342,17 +408,22 @@ const terms = (parent, { currentConfigVersion, currentHash }, { deviceId, settin
const isHashNew = hash !== currentHash
const text = isHashNew ? latestTerms.text : null
return settingsLoader.fetchCurrentConfigVersion()
return settingsLoader
.fetchCurrentConfigVersion()
.catch(() => null)
.then(configVersion => isHashNew || _.isNil(currentConfigVersion) || currentConfigVersion < configVersion)
.then(isVersionNew => isVersionNew ? _.omit(['text'], latestTerms) : null)
.then(
configVersion =>
isHashNew ||
_.isNil(currentConfigVersion) ||
currentConfigVersion < configVersion,
)
.then(isVersionNew => (isVersionNew ? _.omit(['text'], latestTerms) : null))
.then(details => ({ hash, details, text }))
}
module.exports = {
Query: {
configs,
terms,
}
},
}

View file

@ -5,11 +5,11 @@ const { ApolloServer } = require('@apollo/server')
const devMode = !!require('minimist')(process.argv.slice(2)).dev
const context = ({ req, res }) => ({
deviceId: req.deviceId, /* lib/middlewares/populateDeviceId.js */
deviceName: req.deviceName, /* lib/middlewares/authorize.js */
operatorId: res.locals.operatorId, /* lib/middlewares/operatorId.js */
deviceId: req.deviceId /* lib/middlewares/populateDeviceId.js */,
deviceName: req.deviceName /* lib/middlewares/authorize.js */,
operatorId: res.locals.operatorId /* lib/middlewares/operatorId.js */,
pid: req.query.pid,
settings: req.settings, /* lib/middlewares/populateSettings.js */
settings: req.settings /* lib/middlewares/populateSettings.js */,
})
const graphQLServer = new ApolloServer({
@ -21,7 +21,7 @@ const graphQLServer = new ApolloServer({
return error
},
includeStacktraceInErrorResponses: devMode,
logger
logger,
})
module.exports = { graphQLServer, context }
module.exports = { graphQLServer, context }

View file

@ -1,234 +1,234 @@
const gql = require('graphql-tag')
module.exports = gql`
type Coin {
cryptoCode: String!
cryptoCodeDisplay: String!
display: String!
minimumTx: String!
cashInFee: String!
cashOutFee: String!
cashInCommission: String!
cashOutCommission: String!
cryptoNetwork: String!
cryptoUnits: String!
batchable: Boolean!
isCashInOnly: Boolean!
}
type Coin {
cryptoCode: String!
cryptoCodeDisplay: String!
display: String!
minimumTx: String!
cashInFee: String!
cashOutFee: String!
cashInCommission: String!
cashOutCommission: String!
cryptoNetwork: String!
cryptoUnits: String!
batchable: Boolean!
isCashInOnly: Boolean!
}
type LocaleInfo {
country: String!
fiatCode: String!
languages: [String!]!
}
type LocaleInfo {
country: String!
fiatCode: String!
languages: [String!]!
}
type OperatorInfo {
name: String!
phone: String!
email: String!
website: String!
companyNumber: String!
}
type OperatorInfo {
name: String!
phone: String!
email: String!
website: String!
companyNumber: String!
}
type MachineInfo {
deviceId: String! @deprecated(reason: "unused by the machine")
deviceName: String
numberOfCassettes: Int
numberOfRecyclers: Int
}
type MachineInfo {
deviceId: String! @deprecated(reason: "unused by the machine")
deviceName: String
numberOfCassettes: Int
numberOfRecyclers: Int
}
type ReceiptInfo {
paper: Boolean!
automaticPrint: Boolean!
sms: Boolean!
operatorWebsite: Boolean!
operatorEmail: Boolean!
operatorPhone: Boolean!
companyNumber: Boolean!
machineLocation: Boolean!
customerNameOrPhoneNumber: Boolean!
exchangeRate: Boolean!
addressQRCode: Boolean!
}
type ReceiptInfo {
paper: Boolean!
automaticPrint: Boolean!
sms: Boolean!
operatorWebsite: Boolean!
operatorEmail: Boolean!
operatorPhone: Boolean!
companyNumber: Boolean!
machineLocation: Boolean!
customerNameOrPhoneNumber: Boolean!
exchangeRate: Boolean!
addressQRCode: Boolean!
}
type MachineScreenOptions {
rates: RateScreenOptions!
}
type MachineScreenOptions {
rates: RateScreenOptions!
}
type RateScreenOptions {
active: Boolean!
}
type RateScreenOptions {
active: Boolean!
}
type SpeedtestFile {
url: String!
size: Int!
}
type SpeedtestFile {
url: String!
size: Int!
}
enum TriggerAutomationType {
Automatic
Manual
}
enum TriggerAutomationType {
Automatic
Manual
}
type CustomTriggersAutomation {
id: ID!
type: TriggerAutomationType!
}
type CustomTriggersAutomation {
id: ID!
type: TriggerAutomationType!
}
type TriggersAutomation {
sanctions: TriggerAutomationType!
idCardPhoto: TriggerAutomationType!
idCardData: TriggerAutomationType!
facephoto: TriggerAutomationType!
usSsn: TriggerAutomationType!
custom: [CustomTriggersAutomation]!
}
type TriggersAutomation {
sanctions: TriggerAutomationType!
idCardPhoto: TriggerAutomationType!
idCardData: TriggerAutomationType!
facephoto: TriggerAutomationType!
usSsn: TriggerAutomationType!
custom: [CustomTriggersAutomation]!
}
type CustomScreen {
text: String!
title: String!
}
type CustomScreen {
text: String!
title: String!
}
type CustomInput {
type: String!
constraintType: String!
label1: String
label2: String
choiceList: [String]
}
type CustomInput {
type: String!
constraintType: String!
label1: String
label2: String
choiceList: [String]
}
type CustomRequest {
name: String!
input: CustomInput!
screen1: CustomScreen!
screen2: CustomScreen!
}
type CustomRequest {
name: String!
input: CustomInput!
screen1: CustomScreen!
screen2: CustomScreen!
}
type CustomInfoRequest {
id: String!
enabled: Boolean!
customRequest: CustomRequest!
}
type CustomInfoRequest {
id: String!
enabled: Boolean!
customRequest: CustomRequest!
}
type Trigger {
id: String!
direction: String!
requirement: String!
triggerType: String!
type Trigger {
id: String!
direction: String!
requirement: String!
triggerType: String!
suspensionDays: Float
threshold: Int
thresholdDays: Int
customInfoRequestId: String @deprecated(reason: "use customInfoRequest.id")
customInfoRequest: CustomInfoRequest
externalService: String
}
suspensionDays: Float
threshold: Int
thresholdDays: Int
customInfoRequestId: String @deprecated(reason: "use customInfoRequest.id")
customInfoRequest: CustomInfoRequest
externalService: String
}
type TermsDetails {
tcPhoto: Boolean!
delay: Boolean!
title: String!
accept: String!
cancel: String!
}
type TermsDetails {
tcPhoto: Boolean!
delay: Boolean!
title: String!
accept: String!
cancel: String!
}
type Terms {
hash: String!
text: String
details: TermsDetails
}
type Terms {
hash: String!
text: String
details: TermsDetails
}
enum CustomerAuthentication {
EMAIL
SMS
}
enum CustomerAuthentication {
EMAIL
SMS
}
type StaticConfig {
configVersion: Int!
type StaticConfig {
configVersion: Int!
coins: [Coin!]!
enablePaperWalletOnly: Boolean!
hasLightning: Boolean!
serverVersion: String!
timezone: Int!
twoWayMode: Boolean!
customerAuthentication: CustomerAuthentication!
coins: [Coin!]!
enablePaperWalletOnly: Boolean!
hasLightning: Boolean!
serverVersion: String!
timezone: Int!
twoWayMode: Boolean!
customerAuthentication: CustomerAuthentication!
localeInfo: LocaleInfo!
operatorInfo: OperatorInfo
machineInfo: MachineInfo!
receiptInfo: ReceiptInfo
screenOptions: MachineScreenOptions
localeInfo: LocaleInfo!
operatorInfo: OperatorInfo
machineInfo: MachineInfo!
receiptInfo: ReceiptInfo
screenOptions: MachineScreenOptions
speedtestFiles: [SpeedtestFile!]!
urlsToPing: [String!]!
speedtestFiles: [SpeedtestFile!]!
urlsToPing: [String!]!
triggersAutomation: TriggersAutomation!
triggers: [Trigger!]!
}
triggersAutomation: TriggersAutomation!
triggers: [Trigger!]!
}
type DynamicCoinValues {
# NOTE: Doesn't seem to be used anywhere outside of lib/plugins.js.
# However, it can be used to generate the cache key, if we ever move to an
# actual caching mechanism.
#timestamp: String!
type DynamicCoinValues {
# NOTE: Doesn't seem to be used anywhere outside of lib/plugins.js.
# However, it can be used to generate the cache key, if we ever move to an
# actual caching mechanism.
#timestamp: String!
cryptoCode: String!
balance: String!
cryptoCode: String!
balance: String!
# Raw rates
ask: String!
bid: String!
# Raw rates
ask: String!
bid: String!
# Rates with commissions applied
cashIn: String!
cashOut: String!
# Rates with commissions applied
cashIn: String!
cashOut: String!
zeroConfLimit: Int!
}
zeroConfLimit: Int!
}
type PhysicalCassette {
name: String!
denomination: Int!
count: Int!
}
type PhysicalCassette {
name: String!
denomination: Int!
count: Int!
}
type PhysicalRecycler {
name: String!
number: Int!
denomination: Int!
count: Int!
}
type PhysicalRecycler {
name: String!
number: Int!
denomination: Int!
count: Int!
}
type Cassettes {
physical: [PhysicalCassette!]!
virtual: [Int!]!
}
type Cassettes {
physical: [PhysicalCassette!]!
virtual: [Int!]!
}
type Recyclers {
physical: [PhysicalRecycler!]!
virtual: [Int!]!
}
type Recyclers {
physical: [PhysicalRecycler!]!
virtual: [Int!]!
}
type DynamicConfig {
areThereAvailablePromoCodes: Boolean!
cassettes: Cassettes
recyclers: Recyclers
coins: [DynamicCoinValues!]!
reboot: Boolean!
shutdown: Boolean!
restartServices: Boolean!
emptyUnit: Boolean!
refillUnit: Boolean!
diagnostics: Boolean!
}
type DynamicConfig {
areThereAvailablePromoCodes: Boolean!
cassettes: Cassettes
recyclers: Recyclers
coins: [DynamicCoinValues!]!
reboot: Boolean!
shutdown: Boolean!
restartServices: Boolean!
emptyUnit: Boolean!
refillUnit: Boolean!
diagnostics: Boolean!
}
type Configs {
static: StaticConfig
dynamic: DynamicConfig!
}
type Configs {
static: StaticConfig
dynamic: DynamicConfig!
}
type Query {
configs(currentConfigVersion: Int): Configs!
terms(currentHash: String, currentConfigVersion: Int): Terms
}
type Query {
configs(currentConfigVersion: Int): Configs!
terms(currentHash: String, currentConfigVersion: Int): Terms
}
`