diff --git a/api/app.js b/api/app.js deleted file mode 100644 index 3ca81b93..00000000 --- a/api/app.js +++ /dev/null @@ -1,41 +0,0 @@ -'use strict'; - -var cors = require('cors') -var express = require('express'); -var compress = require('compression'); -var bodyParser = require('body-parser'); - -var routes = require('./routes'); - -function init(backend) { - var app = express(); - - // parse POST data - app.use(cors()); - app.use(compress()); - app.use(bodyParser.json()); - app.use(bodyParser.urlencoded({ extended: false })); - - // 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); - }); - - // production error handler - app.use(function(err, req, res, next) { - res.status(err.status || 500); - res.send({ - message: err.message, - error: {} - }); - }); - - return app; -} - -module.exports = init; diff --git a/api/bin/www b/api/bin/www deleted file mode 100755 index 55d52e15..00000000 --- a/api/bin/www +++ /dev/null @@ -1,99 +0,0 @@ -#!/usr/bin/env node - -/** - * Module dependencies. - */ - -var API = require('../app'); -var http = require('http'); - -/** - * Init api with backing services - */ - -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..1ba65a51 --- /dev/null +++ b/api/config/default.yml @@ -0,0 +1,8 @@ +BitcoreHTTP: + port: 8080 + logging: true + BitcoreNode: + NetworkMonitor: + network: livenet + host: localhost + port: 8333 diff --git a/api/index.js b/api/index.js new file mode 100755 index 00000000..d11977c6 --- /dev/null +++ b/api/index.js @@ -0,0 +1,8 @@ +'use strict'; + +var config = require('config'); +var BitcoreHTTP = require('./lib/http'); + +var http = BitcoreHTTP.create(config.get('BitcoreHTTP')); +http.start(); + diff --git a/api/lib/http.js b/api/lib/http.js new file mode 100644 index 00000000..50c66a4e --- /dev/null +++ b/api/lib/http.js @@ -0,0 +1,104 @@ +'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 BitcoreHTTP(node, opts) { + $.checkArgument(node); + opts = opts || {}; + this.node = node; + this.port = opts.port || 8000; + this.logging = opts.logging || false; + 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 + app.use(cors()); + app.use(compress()); + app.use(bodyParser.json()); + app.use(bodyParser.urlencoded({ + extended: false + })); + 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) { + res.status(404).send('Not Found'); + }); + + app.set('port', this.port); + + 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/routes/index.js b/api/routes/index.js index 645f8d9f..6289cd35 100644 --- a/api/routes/index.js +++ b/api/routes/index.js @@ -1,17 +1,18 @@ 'use strict'; + 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); - router.get('/', function(req, res, next) { - res.send('bitcore node api'); + router.get('/', function(req, res) { + res.send('bitcore-node API'); }); return router; diff --git a/api/routes/v1.js b/api/routes/v1.js index bfcaabf3..dec035bb 100644 --- a/api/routes/v1.js +++ b/api/routes/v1.js @@ -2,47 +2,45 @@ var express = require('express'); -function initRouter(backend) { +function initRouter(node) { var router = express.Router(); - function mockResponse(req, res, next) { - res.send('This is a mocked response. Backed service is: ' + backend.status); + function mockResponse(req, res) { + res.send({'message': 'This is a mocked response'}); } + // 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/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; 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/index.js b/index.js old mode 100755 new mode 100644 diff --git a/package.json b/package.json index a349d3cc..dce6461a 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", @@ -65,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", @@ -78,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