diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..fc65214 --- /dev/null +++ b/.npmignore @@ -0,0 +1,3 @@ +node_modules +.db +.nyc_output diff --git a/README.md b/README.md index fc6058f..0dd645a 100644 --- a/README.md +++ b/README.md @@ -40,23 +40,29 @@ server.start(function () { }) ``` -### options.couchdb (required) +### options.couchdb Url to CouchDB ```js options: { - couchdb: 'http://localhost:5984' + couchdb: 'http://admin:secret@localhost:5984' } -// or +// or use a node url object options: { - couchdb: { - url: 'http://localhost:5984', - admin: { - username: 'admin', - secret: 'secret' - } - } + couchdb: url.parse('http://admin:secret@localhost:5984') +} +``` + +### options.PouchDB + +PouchDB constructor + +```js +options: { + PouchDB: PouchDB.defaults({ + db: require('memdown') + }) } ``` diff --git a/example.js b/example.js new file mode 100644 index 0000000..3ad1a83 --- /dev/null +++ b/example.js @@ -0,0 +1,23 @@ +var Hapi = require('hapi') +var PouchDB = require('pouchdb') + +var server = new Hapi.Server() + +server.connection({ + port: 3000 +}) + +server.register({ + register: require('./'), + options: { + PouchDB: PouchDB.defaults({ + db: require('memdown') + }) + // couchdb: 'http://admin:secret@localhost:5984' + } +}, function (err) { + if (err) return console.log(err) + server.start(function () { + console.log('Server running at:', server.info.uri) + }) +}) diff --git a/index.js b/index.js index 5974ba9..30da1ce 100644 --- a/index.js +++ b/index.js @@ -1,18 +1,76 @@ module.exports = hapiCouchDbStore + hapiCouchDbStore.attributes = { name: 'couchdb-store' } -var normaliseOptions = require('./lib/normalise-options') +var url = require('url') + +var boom = require('boom') + +var validDbName = /^[a-zA-Z%]/ function hapiCouchDbStore (server, options, next) { - try { - options = normaliseOptions(options) - } catch (error) { - return next(error) + var xapp = null + if (!options.couchdb) { + xapp = require('express-pouchdb')(options.PouchDB, { + mode: 'minimumForPouchDB' + }) + xapp.disable('x-powered-by') + } else { + server.register(require('h2o2')) } - server.register({ - register: require('./lib/couchdb-proxy'), - options: options - }, next) + + if (options.hooks) { + Object.keys(options.hooks).forEach(function (type) { + server.ext({ + type: type, + method: options.hooks[type], + options: { + sandbox: 'plugin' + } + }) + }) + } + + server.route({ + method: 'GET', + path: '/{path*}', + handler: handler + }) + + server.route({ + method: ['PUT', 'POST', 'COPY', 'DELETE', 'OPTIONS'], + path: '/{path*}', + config: { + payload: { + output: 'stream', + parse: false + } + }, + handler: handler + }) + + function handler (request, reply) { + var rawUrl = request.raw.req.url + .replace((server.realm.modifiers.route.prefix || '') + '/', '') + var path = rawUrl.split('/') + var dbname = path.shift() + if (!dbname) return reply({couchdb: 'Welcome', ok: true}) + if (!validDbName.test(dbname)) return reply(boom.notFound('database not found')) + var newUrl = '/' + dbname + '/' + path.join('/') + + if (options.couchdb) { + return reply.proxy({ + passThrough: true, + mapUri: function (request, callback) { + callback(null, url.resolve(options.couchdb, newUrl)) + } + }) + } + request.raw.req.url = newUrl + xapp(request.raw.req, request.raw.res) + } + + next() } diff --git a/lib/couchdb-proxy.js b/lib/couchdb-proxy.js deleted file mode 100644 index f7f0aac..0000000 --- a/lib/couchdb-proxy.js +++ /dev/null @@ -1,53 +0,0 @@ -var Joi = require('joi') - -module.exports = function (server, options, next) { - if (options.hooks) { - Object.keys(options.hooks).forEach(function (type) { - server.ext({ - type: type, - method: options.hooks[type], - options: { - sandbox: 'plugin' - } - }) - }) - } - - server.route({ - method: ['GET', 'PUT', 'POST', 'DELETE', 'OPTIONS'], - path: '/{path*}', - handler: { - proxy: { - passThrough: true, - mapUri: function (request, next) { - var path = request.params.path || '' - var queryString = request.url.search || '' - var location = typeof options.couchdb === 'string' - ? options.couchdb - : options.couchdb.location - - // TODO: workaroundkf or https://github.com/hoodiehq/hoodie-server-store/issues/14 - path = path.replace(/^user\//, 'user%2f') - - next(null, location + '/' + path + queryString) - } - } - }, - config: { - validate: { - params: { - path: Joi.string().regex(/^[a-zA-Z]/, 'database must start with character') - } - }, - ext: { - } - } - }) - - next() -} - -module.exports.attributes = { - name: 'couchdb-store-proxy', - dependencies: 'h2o2' -} diff --git a/lib/normalise-options.js b/lib/normalise-options.js deleted file mode 100644 index 3ad8278..0000000 --- a/lib/normalise-options.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = normaliseOptions - -function normaliseOptions (options) { - if (!options.couchdb) { - throw new TypeError('options.couchdb must be set') - } - - return options -} diff --git a/package.json b/package.json index e4d0a0e..8098ff5 100644 --- a/package.json +++ b/package.json @@ -29,15 +29,20 @@ "homepage": "https://github.com/hoodiehq/hoodie-server-store#readme", "devDependencies": { "coveralls": "^2.11.6", + "memdown": "^1.1.2", "nock": "^7.0.1", "nyc": "^6.0.0", + "pouchdb": "^5.2.1", + "request": "^2.69.0", "semantic-release": "^6.0.3", "standard": "^6.0.4", "tap": "^5.0.1" }, "dependencies": { + "boom": "^3.1.2", + "express": "^4.13.4", + "express-pouchdb": "^1.0.2", "h2o2": "^5.0.0", - "hapi": "^13.0.0", - "joi": "^8.0.1" + "hapi": "^13.0.0" } -} \ No newline at end of file +} diff --git a/tests/index.js b/tests/index.js index f52f6ac..8bd54e1 100644 --- a/tests/index.js +++ b/tests/index.js @@ -1,6 +1,8 @@ var Hapi = require('hapi') -var H2o2 = require('h2o2') +var h2o2 = require('h2o2') var nock = require('nock') +var PouchDB = require('pouchdb') +var request = require('request').defaults({json: true}) var test = require('tap').test var plugin = require('../') @@ -9,26 +11,16 @@ var noop = function () {} function provisionServer () { var server = new Hapi.Server() - server.connection() - server.register(H2o2, noop) + server.connection({port: 12345}) + server.register(h2o2, noop) return server } -test('options.couchdb is required', function (t) { - t.plan(2) - - var server = provisionServer() - server.register(plugin, {}, function (error) { - t.ok(error instanceof TypeError, 'register fails with TypeError') - t.is(error.message, 'options.couchdb must be set', 'register fails with error message') - }) -}) - test('proxies request to options.couchdb', function (t) { t.plan(1) var mock = nock('http://example.com') - .get('/foo') + .get('/foo/') .reply(200, {ok: true}) var server = provisionServer() @@ -40,7 +32,35 @@ test('proxies request to options.couchdb', function (t) { }, noop) server.inject('/foo', function (response) { - t.ok(mock.isDone(), 'request proxied to http://example.com/foo') + t.ok(mock.isDone(), 'request proxied to http://example.com/foo/') + }) +}) + +test('proxies request to options.pouchdb', function (t) { + t.plan(4) + + var server = provisionServer() + server.register({ + register: plugin, + options: { + PouchDB: PouchDB.defaults({ + db: require('memdown') + }) + } + }, noop) + + t.tearDown(server.stop.bind(server, null)) + + server.start(function () { + request.get('http://127.0.0.1:12345/foo', function (err, res, data) { + t.error(err, 'error') + t.equal(res.statusCode, 404, 'correct statusCode') + server.stop() + }) + request.get('http://127.0.0.1:12345/', function (err, res, data) { + t.error(err, 'error') + t.equal(res.statusCode, 200, 'correct statusCode') + }) }) })