Source: webapi/webapi.js

/*! webapi.js */

var bodyParser = require('body-parser');

var WebMethods = require('./webmethods');

var BAD_REQUEST = 400;
var INTERNAL_SERVER_ERROR = 500;

/**
 * This mixin provides
 * [webapi()]{@linkcode KagoDB#webapi} and
 * [webmethods()]{@linkcode KagoDB#webmethods} and
 * method to implement a RESTful Web API feature for
 * {@link http://expressjs.com Express.js}.
 *
 * @class webapi
 * @mixin
 * @example
 *
 * // This emulates how webapi() works with express to provide RESTful Web APIs.
 *
 * var express = require('express');
 * var KagoDB = require('KagoDB');
 *
 * var collection = new KagoDB({ storage: 'memory' });
 * var webapi = collection.webapi();
 * var webmethods = collection.webmethods(); // == webapi.methods
 *
 * var app = express();
 * app.use(webapi.bodyParser());
 * app.use(webapi.prepare());
 * app.use(webapi.ready());
 * app.put('/data/:id?', webmethods.write);
 * app.del('/data/:id?', webmethods.erase);
 * app.all('/data/:id?', webapi.dispatch(webmethods));
 * app.head('/data/:id?', webmethods.read);
 * app.get('/data/:id?', webmethods.read);
 * app.use(webapi.cleanup());
 * app.use(webapi.error(400));
 * app.listen(3000);
 */

module.exports = function() {
  var mixin = {};
  mixin.webapi = webapi;
  mixin.webmethods = webmethods;
  return mixin;
};

/**
 * This returns a set of bridge methods from express app to KagoDB.
 *
 * @method KagoDB.prototype.webmethods
 * @returns {WebMethods}
 */

function webmethods() {
  return new WebMethods();
}

/**
 * This generates a RESTful Web API function for {@link http://expressjs.com Express.js}.
 *
 * @method KagoDB.prototype.webapi
 * @returns {Function} a Web API function for express
 * @example
 * var express = require('express');
 * var KagoDB = require('KagoDB');
 *
 * var opts = {
 *   storage: 'json',
 *   path: './data/'
 * };
 *
 * var app = express();
 * app.use(express.static(__dirname + '/public'));
 * app.all('/data/:id?', KagoDB(opts).webapi());
 * app.listen(3000);
 */

function webapi() {
  var collection = this;

  var api = function(req, res, next) {
    var app = mw_pipe();
    app.use(api.bodyParser());
    app.use(api.prepare());
    app.use(api.ready());
    app.use(verb('put', api.methods.write));
    app.use(verb('delete', api.methods.erase));
    app.use(api.dispatch(api.methods));
    app.use(verb('head', api.methods.read));
    app.use(verb('get', api.methods.read));
    app.use(api.cleanup());
    app.use(api.error(BAD_REQUEST));
    app.run(req, res, next);
  };

  api.bodyParser = getBodyParser();

  api.prepare = function() {
    return function(req, res, next) {
      req.kagodb = collection;
      req.kagoapi = api;
      next();
    };
  };

  api.ready = function() {
    return NOOP;
  };

  api.methods = collection.webmethods();

  api.dispatch = function(methods) {
    methods = methods || api.methods;
    return function(req, res, next) {
      var method = req.param('method');
      if (!method) {
        return next();
      }
      var func = methods[method];
      if (!func) {
        return next();
      }
      func(req, res, next);
    };
  };

  api.cleanup = function() {
    return function(req, res, next) {
      delete req.kagodb;
      delete req.api;
      next();
    };
  };

  api.error = function(code) {
    return function(req, res, next) {
      if (!code) code = INTERNAL_SERVER_ERROR;
      res.status(code).end();
    };
  };

  api.parse = function(value) {
    if ('string' !== typeof value) return value;
    try {
      value = JSON.parse(value);
    } catch (err) {
      collection.emit('warn', 'JSON.parse error', err, value);
      value = err;
    }
    return value;
  };

  return api;
}

function mw_pipe() {
  function mw() {}
  mw.run = NOOP;
  mw.use = function(job) {
    mw.run = mw_join(mw.run, job);
  };
  return mw;
}

function mw_join(first, second) {
  return function(req, res, next) {
    var that = this;
    first.call(that, req, res, function() {
      second.call(that, req, res, next);
    });
  };
}

function verb(method, job) {
  return function(req, res, next) {
    if (req.method.toLowerCase() == method) {
      job(req, res, next);
    } else {
      next();
    }
  };
}

function getBodyParser() {
  if (!bodyParser) return checkBodyParsed;
  var urlencoded = bodyParser.urlencoded;
  var json = bodyParser.json;
  if (!urlencoded || !json) return checkBodyParsed;
  var opts = {
    extended: true
  };
  var joined = mw_join(urlencoded(opts), json());
  return function() {
    return joined;
  };
}

function checkBodyParsed() {
  return function(req, res, next) {
    if (!req.body) {
      throw new Error('req.body not parsed. add "app.use(express.bodyParser());" before using webapi()');
    }
    next();
  };
}

function NOOP(req, res, next) {
  next();
}