From 08050a6d30b62cf0a56c244b21e58d6503384555 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 2 Mar 2015 17:09:23 -0300 Subject: [PATCH 1/6] fix routes with design doc --- api/routes/index.js | 3 ++- api/routes/v1.js | 59 ++++++++++++++++++++++----------------------- package.json | 3 ++- 3 files changed, 33 insertions(+), 32 deletions(-) diff --git a/api/routes/index.js b/api/routes/index.js index 645f8d9f..3cc8a738 100644 --- a/api/routes/index.js +++ b/api/routes/index.js @@ -11,7 +11,8 @@ function initRouter(backend) { router.use('/v2', v2); router.get('/', function(req, res, next) { - res.send('bitcore node api'); + res.send('bitcore-node API'); + next(); }); return router; diff --git a/api/routes/v1.js b/api/routes/v1.js index bfcaabf3..27aca18b 100644 --- a/api/routes/v1.js +++ b/api/routes/v1.js @@ -6,43 +6,42 @@ function initRouter(backend) { var router = express.Router(); function mockResponse(req, res, next) { - res.send('This is a mocked response. Backed service is: ' + backend.status); + res.send({'message': 'This is a mocked response'}); + next(); } + // Node routes + router.get('/node', mockResponse); + + // Block routes router.get('/blocks', mockResponse); - router.get('/block/:blockHash', mockResponse); - router.get('/block-index/:height', mockResponse); + router.get('/blocks/latest', mockResponse); + router.get('/blocks/:blockHash', mockResponse); + router.get('/blocks/:height', mockResponse); + router.get('/blocks/:blockHash/transactions/:txIndex', mockResponse); // Transaction routes - router.get('/tx/:txid', mockResponse); - router.get('/txs', mockResponse); - router.post('/tx/send', mockResponse); + router.get('/transactions', mockResponse); + router.get('/transactions/:txHash', mockResponse); + router.post('/transactions/send', mockResponse); + router.get('/transactions/:txHash/addresses', mockResponse); + router.get('/transactions/:txHash/outputs/addresses', mockResponse); + router.get('/transactions/:txHash/inputs/addresses', mockResponse); + + // Input routes + router.get('/transactions/:txHash/inputs', mockResponse); + router.get('/transactions/:txHash/inputs/:index', mockResponse); + + // Output routes + router.get('/transactions/:txHash/outputs', mockResponse); + router.get('/transactions/:txHash/outputs/:index', mockResponse); // Address routes - router.get('/addr/:addr', mockResponse); - router.get('/addr/:addr/utxo', mockResponse); - router.get('/addrs/:addrs/utxo', mockResponse); - router.post('/addrs/utxo', mockResponse); - router.get('/addrs/:addrs/txs', mockResponse); - router.post('/addrs/txs', mockResponse); - - // Address property routes - router.get('/addr/:addr/balance', mockResponse); - router.get('/addr/:addr/totalReceived', mockResponse); - router.get('/addr/:addr/totalSent', mockResponse); - router.get('/addr/:addr/unconfirmedBalance', mockResponse); - - // Status route - router.get('/status', mockResponse); - router.get('/sync', mockResponse); - router.get('/peer', mockResponse); - - // Currency - router.get('/currency', mockResponse); - - // Address routes - router.get('/messages/verify', mockResponse); - router.post('/messages/verify', mockResponse); + router.get('/addresses/:address', mockResponse); + router.get('/addresses/:address/transactions', mockResponse); + router.get('/addresses/:address/utxos', mockResponse); + // TODO: check if this is really restful + router.get('/addresses/:addresses/utxos', mockResponse); return router; } diff --git a/package.json b/package.json index a349d3cc..e4eec183 100644 --- a/package.json +++ b/package.json @@ -48,11 +48,12 @@ "bitcore": "bitpay/bitcore", "bitcore-p2p": "bitpay/bitcore-p2p", "bluebird": "^2.9.12", + "body-parser": "^1.12.0", "bufferput": "bitpay/node-bufferput", "buffertools": "*", "commander": "^2.3.0", - "config": "^1.12.0", "compression": "^1.4.1", + "config": "^1.12.0", "cors": "^2.5.3", "cron": "^1.0.4", "eventemitter2": "^0.4.14", From 244e13c55ae47c5771cc914a5487891f83c8a8e9 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 2 Mar 2015 17:14:33 -0300 Subject: [PATCH 2/6] add logging --- api/app.js | 24 ++++++++++++++---------- api/routes/v1.js | 1 - package.json | 1 + 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/api/app.js b/api/app.js index 3ca81b93..592cfce4 100644 --- a/api/app.js +++ b/api/app.js @@ -1,9 +1,10 @@ 'use strict'; -var cors = require('cors') +var cors = require('cors'); var express = require('express'); var compress = require('compression'); var bodyParser = require('body-parser'); +var morgan = require('morgan'); var routes = require('./routes'); @@ -14,25 +15,28 @@ function init(backend) { app.use(cors()); app.use(compress()); app.use(bodyParser.json()); - app.use(bodyParser.urlencoded({ extended: false })); + app.use(bodyParser.urlencoded({ + extended: false + })); + app.use(morgan('dev')); // install routes app.use('/', routes(backend)); // catch 404 and forward to error handler app.use(function(req, res, next) { - var err = new Error('Not Found'); - err.status = 404; - next(err); + var err = new Error('Not Found'); + err.status = 404; + next(err); }); // production error handler app.use(function(err, req, res, next) { - res.status(err.status || 500); - res.send({ - message: err.message, - error: {} - }); + res.status(err.status || 500); + res.send({ + message: err.message, + error: {} + }); }); return app; diff --git a/api/routes/v1.js b/api/routes/v1.js index 27aca18b..d8904b1f 100644 --- a/api/routes/v1.js +++ b/api/routes/v1.js @@ -7,7 +7,6 @@ function initRouter(backend) { function mockResponse(req, res, next) { res.send({'message': 'This is a mocked response'}); - next(); } // Node routes diff --git a/package.json b/package.json index e4eec183..3c30ed6e 100644 --- a/package.json +++ b/package.json @@ -66,6 +66,7 @@ "microtime": "^0.6.0", "mkdirp": "^0.5.0", "moment": "~2.5.0", + "morgan": "^1.5.1", "preconditions": "^1.0.7", "request": "^2.48.0", "socket.io": "1.0.6", From d027bba2c7937b613a58043b94bcb171c469d6b4 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 2 Mar 2015 17:43:41 -0300 Subject: [PATCH 3/6] refactor app into BitcoreHTTP --- api/app.js | 79 ++++++++++++++++++++++++++++++--- api/bin/www | 99 +++--------------------------------------- api/config/default.yml | 7 +++ api/routes/v1.js | 2 +- 4 files changed, 86 insertions(+), 101 deletions(-) create mode 100644 api/config/default.yml diff --git a/api/app.js b/api/app.js index 592cfce4..f56812ca 100644 --- a/api/app.js +++ b/api/app.js @@ -1,14 +1,35 @@ 'use strict'; +var http = require('http'); var cors = require('cors'); var express = require('express'); var compress = require('compression'); var bodyParser = require('body-parser'); var morgan = require('morgan'); +var bitcore = require('bitcore'); +var $ = bitcore.util.preconditions; +var BitcoreNode = require('../lib/node'); + var routes = require('./routes'); -function init(backend) { + +function BitcoreHTTP(node, opts) { + $.checkArgument(node); + opts = opts || {}; + this.node = node; + this.port = opts.port || 8000; + this.setupExpress(); +} + +BitcoreHTTP.create = function(opts) { + opts = opts || {}; + var node = BitcoreNode.create(opts.BitcoreNode); + return new BitcoreHTTP(node, opts); +}; + + +BitcoreHTTP.prototype.setupExpress = function() { var app = express(); // parse POST data @@ -21,7 +42,7 @@ function init(backend) { app.use(morgan('dev')); // install routes - app.use('/', routes(backend)); + app.use('/', routes(this.node)); // catch 404 and forward to error handler app.use(function(req, res, next) { @@ -31,7 +52,7 @@ function init(backend) { }); // production error handler - app.use(function(err, req, res, next) { + app.use(function(err, req, res) { res.status(err.status || 500); res.send({ message: err.message, @@ -39,7 +60,53 @@ function init(backend) { }); }); - return app; -} + app.set('port', this.port); -module.exports = init; + var server = http.createServer(app); + server.on('error', this.onError.bind(this)); + server.on('listening', this.onListening.bind(this)); + + this.app = app; + this.server = server; +}; + +/** + * Event listener for HTTP server "error" event. + */ +BitcoreHTTP.prototype.onError = function(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' ? 'Pipe ' + this.port : 'Port ' + this.port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +}; + +/** + * Event listener for HTTP server "listening" event. + */ +BitcoreHTTP.prototype.onListening = function() { + var addr = this.server.address(); + var bind = typeof addr === 'string' ? 'pipe ' + addr : 'port ' + addr.port; + console.log('Listening on ' + bind); +}; + + +BitcoreHTTP.prototype.start = function() { + this.server.listen(this.port); +}; + +module.exports = BitcoreHTTP; diff --git a/api/bin/www b/api/bin/www index 55d52e15..5650961f 100755 --- a/api/bin/www +++ b/api/bin/www @@ -1,99 +1,10 @@ #!/usr/bin/env node -/** - * Module dependencies. - */ +'use strict'; -var API = require('../app'); -var http = require('http'); +var config = require('config'); +var BitcoreHTTP = require('../app'); -/** - * Init api with backing services - */ +var app = BitcoreHTTP.create(config.get('BitcoreHTTP')); +app.start(); -var Node = { - status: 'live', - nodes: 123 -} -var app = new API(Node); - -/** - * Get port from environment and store in Express. - */ - -var port = normalizePort(process.env.PORT || '8000'); -app.set('port', port); - -/** - * Create HTTP server. - */ - -var server = http.createServer(app); - -/** - * Listen on provided port, on all network interfaces. - */ - -server.listen(port); -server.on('error', onError); -server.on('listening', onListening); - -/** - * Normalize a port into a number, string, or false. - */ - -function normalizePort(val) { - var port = parseInt(val, 10); - - if (isNaN(port)) { - // named pipe - return val; - } - - if (port >= 0) { - // port number - return port; - } - - return false; -} - -/** - * Event listener for HTTP server "error" event. - */ - -function onError(error) { - if (error.syscall !== 'listen') { - throw error; - } - - var bind = typeof port === 'string' - ? 'Pipe ' + port - : 'Port ' + port - - // handle specific listen errors with friendly messages - switch (error.code) { - case 'EACCES': - console.error(bind + ' requires elevated privileges'); - process.exit(1); - break; - case 'EADDRINUSE': - console.error(bind + ' is already in use'); - process.exit(1); - break; - default: - throw error; - } -} - -/** - * Event listener for HTTP server "listening" event. - */ - -function onListening() { - var addr = server.address(); - var bind = typeof addr === 'string' - ? 'pipe ' + addr - : 'port ' + addr.port; - console.log('Listening on ' + bind); -} diff --git a/api/config/default.yml b/api/config/default.yml new file mode 100644 index 00000000..98713c7f --- /dev/null +++ b/api/config/default.yml @@ -0,0 +1,7 @@ +BitcoreHTTP: + port: 8080 + BitcoreNode: + NetworkMonitor: + network: livenet + host: localhost + port: 8333 diff --git a/api/routes/v1.js b/api/routes/v1.js index d8904b1f..252a8ecc 100644 --- a/api/routes/v1.js +++ b/api/routes/v1.js @@ -5,7 +5,7 @@ var express = require('express'); function initRouter(backend) { var router = express.Router(); - function mockResponse(req, res, next) { + function mockResponse(req, res) { res.send({'message': 'This is a mocked response'}); } From 7ef0c36d3845b897bfa3dfccbf92f169d478e436 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Mon, 2 Mar 2015 17:46:02 -0300 Subject: [PATCH 4/6] backend->node --- api/routes/index.js | 6 +++--- api/routes/v1.js | 2 +- api/routes/v2.js | 6 +++--- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/api/routes/index.js b/api/routes/index.js index 3cc8a738..64a1fd86 100644 --- a/api/routes/index.js +++ b/api/routes/index.js @@ -3,9 +3,9 @@ var express = require('express'); var router = express.Router(); -function initRouter(backend) { - var v1 = require('./v1')(backend); - var v2 = require('./v2')(backend); +function initRouter(node) { + var v1 = require('./v1')(node); + var v2 = require('./v2')(node); router.use('/v1', v1); router.use('/v2', v2); diff --git a/api/routes/v1.js b/api/routes/v1.js index 252a8ecc..dec035bb 100644 --- a/api/routes/v1.js +++ b/api/routes/v1.js @@ -2,7 +2,7 @@ var express = require('express'); -function initRouter(backend) { +function initRouter(node) { var router = express.Router(); function mockResponse(req, res) { diff --git a/api/routes/v2.js b/api/routes/v2.js index b6e51377..29a0f799 100644 --- a/api/routes/v2.js +++ b/api/routes/v2.js @@ -2,11 +2,11 @@ var express = require('express'); -function initRouter(backend) { +function initRouter(node) { var router = express.Router(); - router.get('/blocks', function(req, res, next) { - res.send('blocks v2 ' + backend.nodes); + router.get('/blocks', function(req, res) { + res.send('blocks v2 '); }); return router; From 509a82ac3a47ed8a2bf3ec8b6cc665618ea696c3 Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 3 Mar 2015 12:18:06 -0300 Subject: [PATCH 5/6] reorder files --- api/{bin/www => index.js} | 2 +- api/{app.js => lib/http.js} | 4 ++-- api/routes/index.js | 4 ++-- index.js | 0 4 files changed, 5 insertions(+), 5 deletions(-) rename api/{bin/www => index.js} (77%) rename api/{app.js => lib/http.js} (96%) mode change 100755 => 100644 index.js diff --git a/api/bin/www b/api/index.js similarity index 77% rename from api/bin/www rename to api/index.js index 5650961f..2cd99f4e 100755 --- a/api/bin/www +++ b/api/index.js @@ -3,7 +3,7 @@ 'use strict'; var config = require('config'); -var BitcoreHTTP = require('../app'); +var BitcoreHTTP = require('./lib/http'); var app = BitcoreHTTP.create(config.get('BitcoreHTTP')); app.start(); diff --git a/api/app.js b/api/lib/http.js similarity index 96% rename from api/app.js rename to api/lib/http.js index f56812ca..afec7f41 100644 --- a/api/app.js +++ b/api/lib/http.js @@ -9,9 +9,9 @@ var morgan = require('morgan'); var bitcore = require('bitcore'); var $ = bitcore.util.preconditions; -var BitcoreNode = require('../lib/node'); +var BitcoreNode = require('../../lib/node'); -var routes = require('./routes'); +var routes = require('../routes'); function BitcoreHTTP(node, opts) { diff --git a/api/routes/index.js b/api/routes/index.js index 64a1fd86..6289cd35 100644 --- a/api/routes/index.js +++ b/api/routes/index.js @@ -1,5 +1,6 @@ 'use strict'; + var express = require('express'); var router = express.Router(); @@ -10,9 +11,8 @@ function initRouter(node) { router.use('/v1', v1); router.use('/v2', v2); - router.get('/', function(req, res, next) { + router.get('/', function(req, res) { res.send('bitcore-node API'); - next(); }); return router; diff --git a/index.js b/index.js old mode 100755 new mode 100644 From dcc8ade166d8b9e7f86fea1b2cb9172400f501cf Mon Sep 17 00:00:00 2001 From: Manuel Araoz Date: Tue, 3 Mar 2015 14:59:01 -0300 Subject: [PATCH 6/6] add tests for api --- api/config/default.yml | 1 + api/index.js | 6 ++---- api/lib/http.js | 20 ++++++-------------- api/test/http.js | 32 ++++++++++++++++++++++++++++++++ api/test/routes.js | 40 ++++++++++++++++++++++++++++++++++++++++ package.json | 3 ++- test/mocha.opts | 4 ++++ 7 files changed, 87 insertions(+), 19 deletions(-) create mode 100644 api/test/http.js create mode 100644 api/test/routes.js create mode 100644 test/mocha.opts diff --git a/api/config/default.yml b/api/config/default.yml index 98713c7f..1ba65a51 100644 --- a/api/config/default.yml +++ b/api/config/default.yml @@ -1,5 +1,6 @@ BitcoreHTTP: port: 8080 + logging: true BitcoreNode: NetworkMonitor: network: livenet diff --git a/api/index.js b/api/index.js index 2cd99f4e..d11977c6 100755 --- a/api/index.js +++ b/api/index.js @@ -1,10 +1,8 @@ -#!/usr/bin/env node - 'use strict'; var config = require('config'); var BitcoreHTTP = require('./lib/http'); -var app = BitcoreHTTP.create(config.get('BitcoreHTTP')); -app.start(); +var http = BitcoreHTTP.create(config.get('BitcoreHTTP')); +http.start(); diff --git a/api/lib/http.js b/api/lib/http.js index afec7f41..50c66a4e 100644 --- a/api/lib/http.js +++ b/api/lib/http.js @@ -19,6 +19,7 @@ function BitcoreHTTP(node, opts) { opts = opts || {}; this.node = node; this.port = opts.port || 8000; + this.logging = opts.logging || false; this.setupExpress(); } @@ -39,25 +40,16 @@ BitcoreHTTP.prototype.setupExpress = function() { app.use(bodyParser.urlencoded({ extended: false })); - app.use(morgan('dev')); + if (this.logging) { + app.use(morgan('dev')); + } // install routes app.use('/', routes(this.node)); // catch 404 and forward to error handler - app.use(function(req, res, next) { - var err = new Error('Not Found'); - err.status = 404; - next(err); - }); - - // production error handler - app.use(function(err, req, res) { - res.status(err.status || 500); - res.send({ - message: err.message, - error: {} - }); + app.use(function(req, res) { + res.status(404).send('Not Found'); }); app.set('port', this.port); diff --git a/api/test/http.js b/api/test/http.js new file mode 100644 index 00000000..0566540b --- /dev/null +++ b/api/test/http.js @@ -0,0 +1,32 @@ +'use strict'; + +var chai = require('chai'); +var should = chai.should(); + +var EventEmitter = require('eventemitter2').EventEmitter2; + +var BitcoreHTTP = require('../lib/http'); + +describe('BitcoreHTTP', function() { + + // mocks + var nodeMock; + beforeEach(function() { + nodeMock = new EventEmitter(); + }); + describe('instantiates', function() { + it('from constructor', function() { + var http = new BitcoreHTTP(nodeMock); + should.exist(http); + }); + it('from create', function() { + var http = new BitcoreHTTP.create(); + should.exist(http); + }); + }); + it('starts', function() { + var http = new BitcoreHTTP(nodeMock); + http.start.bind(http).should.not.throw(); + }); + +}); diff --git a/api/test/routes.js b/api/test/routes.js new file mode 100644 index 00000000..70d987aa --- /dev/null +++ b/api/test/routes.js @@ -0,0 +1,40 @@ +'use strict'; + +var chai = require('chai'); +var should = chai.should(); +var request = require('supertest'); + +var EventEmitter = require('eventemitter2').EventEmitter2; + +var BitcoreHTTP = require('../lib/http'); + +describe('BitcoreHTTP routes', function() { + + // mocks + var nodeMock, app, agent; + beforeEach(function() { + var opts = { + port: 1234 + }; + nodeMock = new EventEmitter(); + app = new BitcoreHTTP(nodeMock, opts).app; + agent = request(app); + }); + it('404s', function(cb) { + agent.get('/invalid/url/') + .expect(404, cb); + }); + it('main', function(cb) { + agent.get('/') + .expect(200) + .expect('bitcore-node API', cb); + }); + it('blocks', function(cb) { + agent.get('/v1/blocks/') + .expect(200) + .expect({ + 'message': 'This is a mocked response' + }, cb); + }); + +}); diff --git a/package.json b/package.json index 3c30ed6e..dce6461a 100644 --- a/package.json +++ b/package.json @@ -80,6 +80,7 @@ "chai": "*", "gulp": "^3.8.10", "should": "^2.1.1", - "sinon": "^1.10.3" + "sinon": "^1.10.3", + "supertest": "^0.15.0" } } diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 00000000..4bb7852a --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1,4 @@ +test/ +api/test +--recursive +-R spec