feat: add GraphQL server and resolvers

This commit is contained in:
André Sá 2022-04-05 17:47:01 +01:00
parent 4d8c8c4b62
commit a0ed5a3a0e
5 changed files with 387 additions and 1 deletions

140
lib/graphql/resolvers.js Normal file
View file

@ -0,0 +1,140 @@
const _ = require('lodash/fp')
const { accounts: accountsConfig, countries, languages } = require('../new-admin/config')
const plugins = require('../plugins')
const configManager = require('../new-config-manager')
const customRequestQueries = require('../new-admin/services/customInfoRequests')
const state = require('../middlewares/state')
const urlsToPing = [
`us.archive.ubuntu.com`,
`uk.archive.ubuntu.com`,
`za.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
}
]
const addSmthInfo = (dstField, srcFields) => smth =>
smth && smth.active ? _.set(dstField, _.pick(srcFields, smth)) : _.identity
const addOperatorInfo = addSmthInfo(
'operatorInfo',
['name', 'phone', 'email', 'website', 'companyNumber']
)
const addReceiptInfo = addSmthInfo(
'receiptInfo',
[
'sms',
'operatorWebsite',
'operatorEmail',
'operatorPhone',
'companyNumber',
'machineLocation',
'customerNameOrPhoneNumber',
'exchangeRate',
'addressQRCode',
]
)
/* TODO: Simplify this. */
const buildTriggers = (allTriggers) => {
const normalTriggers = []
const customTriggers = _.filter(o => {
if (_.isEmpty(o.customInfoRequestId) || _.isNil(o.customInfoRequestId)) normalTriggers.push(o)
return !_.isNil(o.customInfoRequestId) && !_.isEmpty(o.customInfoRequestId)
}, allTriggers)
return _.flow(
_.map(_.get('customInfoRequestId')),
customRequestQueries.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 = (parent, { currentConfigVersion }, { deviceId, deviceName, settings }, info) =>
Promise.all([
plugins(settings, deviceId).staticConfigQueries(),
!!configManager.getCompliance(settings.config).enablePaperWalletOnly,
configManager.getTriggersAutomation(settings.config),
buildTriggers(configManager.getTriggers(settings.config)),
configManager.getWalletSettings('BTC', settings.config).layer2 !== 'no-layer2',
configManager.getLocale(deviceId, settings.config),
configManager.getOperatorInfo(settings.config),
configManager.getReceipt(settings.config),
!!configManager.getCashOut(deviceId, settings.config).active,
])
.then(([
staticConf,
enablePaperWalletOnly,
triggersAutomation,
triggers,
hasLightning,
localeInfo,
operatorInfo,
receiptInfo,
twoWayMode,
]) =>
(currentConfigVersion && currentConfigVersion >= staticConf.configVersion) ?
null :
_.flow(
_.assign({
enablePaperWalletOnly,
triggersAutomation,
triggers,
hasLightning,
localeInfo: {
country: localeInfo.country,
languages: localeInfo.languages,
fiatCode: localeInfo.fiatCurrency
},
machineInfo: { deviceId, deviceName },
twoWayMode,
speedtestFiles,
urlsToPing,
}),
_.update('triggersAutomation', _.mapValues(_.eq('Automatic'))),
addOperatorInfo(operatorInfo),
addReceiptInfo(receiptInfo)
)(staticConf))
const setZeroConfLimit = config => coin =>
_.set(
'zeroConfLimit',
configManager.getWalletSettings(coin.cryptoCode, config).zeroConfLimit,
coin
)
const dynamicConfig = (parent, variables, { deviceId, operatorId, pid, settings }, info) => {
state.pids = _.update(operatorId, _.set(deviceId, { pid, ts: Date.now() }), state.pids)
return plugins(settings, deviceId)
.dynamicConfigQueries()
.then(_.flow(
_.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),
))
}
module.exports = {
Query: {
staticConfig,
dynamicConfig
}
}

27
lib/graphql/server.js Normal file
View file

@ -0,0 +1,27 @@
const logger = require('../logger')
const https = require('https')
const { ApolloServer } = require('apollo-server-express')
const devMode = !!require('minimist')(process.argv.slice(2)).dev
module.exports = new ApolloServer({
typeDefs: require('./types'),
resolvers: require('./resolvers'),
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 */
pid: req.query.pid,
settings: req.settings, /* lib/middlewares/populateSettings.js */
}),
uploads: false,
playground: false,
introspection: false,
formatError: error => {
logger.error(error)
return error
},
debug: devMode,
logger
})

137
lib/graphql/types.js Normal file
View file

@ -0,0 +1,137 @@
const { gql } = require('apollo-server-express')
module.exports = gql`
type Coin {
cryptoCode: String!
display: String!
minimumTx: String!
cashInFee: String!
cashInCommission: String!
cashOutCommission: String!
cryptoNetwork: Boolean!
cryptoUnits: String!
batchable: Boolean!
}
type LocaleInfo {
country: String!
fiatCode: String!
languages: [String!]!
}
type OperatorInfo {
name: String!
phone: String!
email: String!
website: String!
companyNumber: String!
}
type MachineInfo {
deviceId: String!
deviceName: String
}
type ReceiptInfo {
sms: Boolean!
operatorWebsite: Boolean!
operatorEmail: Boolean!
operatorPhone: Boolean!
companyNumber: Boolean!
machineLocation: Boolean!
customerNameOrPhoneNumber: Boolean!
exchangeRate: Boolean!
addressQRCode: Boolean!
}
type SpeedtestFile {
url: String!
size: Int!
}
# True if automatic, False otherwise
type TriggersAutomation {
sanctions: Boolean!
idCardPhoto: Boolean!
idCardData: Boolean!
facephoto: Boolean!
usSsn: Boolean!
}
type Trigger {
id: String!
customInfoRequestId: String!
direction: String!
requirement: String!
triggerType: String!
suspensionDays: Int
threshold: Int
thresholdDays: Int
}
type StaticConfig {
configVersion: Int!
areThereAvailablePromoCodes: Boolean!
coins: [Coin!]!
enablePaperWalletOnly: Boolean!
hasLightning: Boolean!
serverVersion: String!
timezone: Int!
twoWayMode: Boolean!
localeInfo: LocaleInfo!
operatorInfo: OperatorInfo
machineInfo: MachineInfo!
receiptInfo: ReceiptInfo
speedtestFiles: [SpeedtestFile!]!
urlsToPing: [String!]!
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!
cryptoCode: String!
balance: String!
# Raw rates
ask: String!
bid: String!
# Rates with commissions applied
cashIn: String!
cashOut: String!
zeroConfLimit: Int!
}
type PhysicalCassette {
denomination: Int!
count: Int!
}
type Cassettes {
physical: [PhysicalCassette!]!
virtual: [Int!]!
}
type DynamicConfig {
cassettes: Cassettes
coins: [DynamicCoinValues!]!
reboot: Boolean!
shutdown: Boolean!
restartServices: Boolean!
}
type Query {
staticConfig(currentConfigVersion: Int): StaticConfig
dynamicConfig: DynamicConfig!
}
`