From 9c57b1de87ca77e2ba927aefe93714e2d6898bf1 Mon Sep 17 00:00:00 2001 From: Josh Harvey Date: Wed, 26 Oct 2016 04:10:23 +0300 Subject: [PATCH] WIP --- lib/routes.js | 91 ++++++++++++++------- migrations/016-new_cached_requests_table.js | 20 +++++ package.json | 2 + todo.txt | 5 ++ 4 files changed, 88 insertions(+), 30 deletions(-) create mode 100644 migrations/016-new_cached_requests_table.js diff --git a/lib/routes.js b/lib/routes.js index b5252473..3cb4ce20 100644 --- a/lib/routes.js +++ b/lib/routes.js @@ -3,6 +3,7 @@ const BigNumber = require('bignumber.js') const logger = require('./logger') const configManager = require('./config-manager') +const db = require('./db') let mock = false @@ -121,16 +122,15 @@ function poll (req, res) { .catch(logger.error) } -function trade (req, res) { +function trade (req, res, next) { const tx = req.body tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms) plugins.trade(getDeviceId(req), tx) - .then(() => res.status(201).json({})) - .catch(err => { - logger.error(err) - res.status(500).json({err}) - }) + .then(() => { + updateCachedAction(req, {}, 201) + res.status(201).json({}) + }) } function stateChange (req, res) { @@ -147,20 +147,11 @@ function send (req, res) { tx.cryptoAtoms = new BigNumber(tx.cryptoAtoms) return plugins.sendCoins(getDeviceId(req), tx) - .then(status => { - res.json({ - txHash: status && status.txHash, - txId: status && status.txId - }) - }) - .catch(err => { - console.log('DEBUG15: %s', err) - logger.error(err) - res.json({ - err: err.message, - errType: err.name - }) - }) + .then(status => { + const body = {txId: status && status.txId} + updateCachedAction(req, body, 200) + .then(() => res.json(body)) + }) } function cashOut (req, res) { @@ -232,8 +223,14 @@ function pair (req, res) { return pair.pair(token, deviceId) .then(valid => { - if (valid) return res.status(200).end() - return res.status(408).end() + updateCachedAction(req, {}) + if (valid) { + updateCachedAction(req, {}, 200) + return res.json({}) + } + + updateCachedAction(req, {}, 408) + return res.status(408).json({}) }) } @@ -333,6 +330,44 @@ function dispense (req, res) { .catch(err => logger.error(err)) } +function isUniqueViolation (err) { + return err.code === '23505' +} + +function cacheAction (req, res, next) { + const sql = `insert into idempotents (request_id, device_id, body, status, pending) + values ($1, $2, $3, $4, $5)` + + const requestId = req.headers['request-id'] + const deviceId = getDeviceId(req) + + db.none(sql, [requestId, deviceId, {}, 204, true]) + .then(() => next()) + .catch(err => { + if (!isUniqueViolation(err)) throw err + + const sql2 = 'select body, status, pending from idempotents where request_id=$1' + return db.one(sql2, [requestId]) + .then(row => { + if (row.pending) return res.status(204).end() + return res.status(row.status).json(row.body) + }) + }) +} + +function cachedActionError (req, err, status) { + return updateCachedAction(req, {error: err}, status || 500) +} + +function updateCachedAction (req, body, status) { + const sql = 'update idempotents set body=$1, status=$2, pending=$3 where request_id=$4 and device_id=$5' + + const requestId = req.headers['request-id'] + const deviceId = getDeviceId(req) + + return db.none(sql, [body, status, false, requestId, deviceId]) +} + function init (opts) { plugins = opts.plugins mock = opts.mock @@ -341,6 +376,8 @@ function init (opts) { const app = opts.app const localApp = opts.localApp + app.post('*', cacheAction) + app.post('/pair', pair) app.get('/ca', ca) @@ -363,6 +400,8 @@ function init (opts) { app.get('/await_dispense/:txId', authMiddleware, waitForDispense) app.post('/dispense', authMiddleware, dispense) + app.post('*', updateCachedAction) + localApp.get('/pid', (req, res) => { const deviceId = req.query.device_id const pidRec = pids[deviceId] @@ -403,11 +442,3 @@ function getDeviceId (req) { req.connection.getPeerCertificate().fingerprint) || 'unknown' } -function cachedResponse (deviceId, txId, req) { - return plugins.cachedResponse(deviceId, txId, req.path, req.method) - .then(r => r.body) -} - -function cacheResponse (deviceId, txId, req, body) { - return plugins.cacheResponse(deviceId, txId, req.path, req.method, body) -} diff --git a/migrations/016-new_cached_requests_table.js b/migrations/016-new_cached_requests_table.js new file mode 100644 index 00000000..f94f4d6a --- /dev/null +++ b/migrations/016-new_cached_requests_table.js @@ -0,0 +1,20 @@ +var db = require('./db') + +exports.up = function (next) { + var sql = [ + 'drop table if exists cached_responses', + `create table idempotents ( + request_id text PRIMARY KEY, + device_id text NOT NULL, + body json NOT NULL, + status integer NOT NULL, + pending boolean NOT NULL, + created timestamptz NOT NULL default now() + )` + ] + db.multi(sql, next) +} + +exports.down = function (next) { + next() +} diff --git a/package.json b/package.json index 397cd91b..b3efc584 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "bunyan": "^1.8.1", "chalk": "^1.1.3", "express": "^4.13.4", + "express-limiter": "^1.6.0", "helmet": "^2.3.0", "inquirer": "^1.0.0", "lamassu-bitcoinaverage": "~1.0.0", @@ -36,6 +37,7 @@ "node-hkdf-sync": "^1.0.0", "node-uuid": "^1.4.2", "numeral": "^1.5.3", + "on-finished": "^2.3.0", "pg": "^4.5.5", "pg-promise": "^4.3.3", "pify": "^2.3.0", diff --git a/todo.txt b/todo.txt index 6be057cc..8694c76b 100644 --- a/todo.txt +++ b/todo.txt @@ -109,3 +109,8 @@ options: configure per machine; configure per crypto/fiat v need to create CA: http://stackoverflow.com/questions/19665863/how-do-i-use-a-self-signed-certificate-for-a-https-node-js-server +-------------------------------- + +- consistent error handling +- usage of http status codes (good for got) +- finish idempotency for all calls