summaryrefslogtreecommitdiffstats
path: root/common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib
diff options
context:
space:
mode:
Diffstat (limited to 'common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib')
-rw-r--r--common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/handlers.js221
-rw-r--r--common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/model.js479
-rw-r--r--common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/restful.js7
3 files changed, 707 insertions, 0 deletions
diff --git a/common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/handlers.js b/common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/handlers.js
new file mode 100644
index 0000000..8423310
--- /dev/null
+++ b/common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/handlers.js
@@ -0,0 +1,221 @@
+var _ = require('underscore');
+
+/*
+ * The last handler to be called in the chain of middleware
+ * This figures out what response format it should be in and sends it
+ */
+exports.last = function(req, res, next) {
+ if (res.locals.bundle) {
+ if (req.body.format === 'js') {
+ return res.send(res.locals.bundle);
+ } else if (req.body.format === 'html' || req.query.format === 'html') {
+ return res.render(this.templateRoot + '/' + req.templatePath, res.locals.bundle);
+ } else {
+ return res.status(res.locals.status_code).json(res.locals.bundle);
+ }
+ }
+ res.send();
+};
+
+exports.schema = function(req, res, next) {
+ // We can mount a model to multiple apps, so we need to get the base url from the request url
+ var baseuri = req.url.split('/');
+ baseuri = baseuri.slice(0, baseuri.length - 1).join('/');
+ var detailuri = baseuri + '/:id';
+ exports.respond(res, 200, {
+ resource: this.modelName,
+ allowed_methods: Object.keys(this.allowed_methods),
+ list_uri: baseuri,
+ detail_uri: detailuri,
+ fields: keep(this.schema.paths, ['regExp', 'path', 'instance', 'isRequired'])
+ });
+ next();
+};
+
+exports.get = function(req, res, next) {
+ req.quer.exec(function(err, list) {
+ if (err) {
+ exports.respond(res, 500, err);
+ } else if (req.params.id) {
+ exports.respondOrErr(res, 404, !list && exports.objectNotFound(), 200, (list && _.isArray(list)) ? list[0] : list);
+ } else {
+ exports.respondOrErr(res, 500, err, 200, list);
+ }
+ next();
+ });
+};
+
+exports.getDetail = function(req, res, next) {
+ req.quer.exec(function(err, one) {
+ exports.respondOrErr(res, 500, err, 200, one);
+ next();
+ });
+};
+
+/**
+ * Generates a handler that returns the object at @param pathName
+ * where pathName is the path to an objectId field
+ */
+exports.getPath = function(pathName) {
+ return function(req, res, next) {
+ req.quer = req.quer.populate(pathName);
+ req.quer.exec(function(err, one) {
+ var errStatus = ((err && err.status) ? err.status : 500);
+ exports.respondOrErr(res, errStatus, err, 200, (one && one.get(pathName)) || {});
+ next();
+ });
+ };
+};
+
+exports.post = function(req, res, next) {
+ var obj = new this(req.body);
+ obj.save(function(err) {
+ exports.respondOrErr(res, 400, err, 201, obj);
+ next();
+ });
+};
+
+exports.put = function(req, res, next) {
+ // Remove immutable ObjectId from update attributes to prevent request failure
+ if (req.body._id && req.body._id === req.params.id) {
+ delete req.body._id;
+ }
+
+ // Update in 1 atomic operation on the database if not specified otherwise
+ if (this.shouldUseAtomicUpdate) {
+ req.quer.findOneAndUpdate({}, req.body, this.update_options, function(err, newObj) {
+ if (err) {
+ exports.respond(res, 500, err);
+ } else if (!newObj) {
+ exports.respond(res, 404, exports.objectNotFound());
+ } else {
+ exports.respond(res, 200, newObj);
+ }
+ next();
+ });
+ } else {
+ // Preform the update in two operations allowing mongoose to fire its schema update hook
+ req.quer.findOne({"_id": req.params.id}, function(err, docToUpdate) {
+ if (err) {
+ exports.respond(res, 500, err);
+ }
+ var objNotFound = !docToUpdate && exports.objectNotFound();
+ if (objNotFound) {
+ exports.respond(res, 404, objNotFound);
+ return next();
+ }
+
+ docToUpdate.set(req.body);
+ docToUpdate.save(function (err, obj) {
+ exports.respondOrErr(res, 400, err, 200, obj);
+ next();
+ });
+ });
+ }
+};
+
+exports.delete = function(req, res, next) {
+ // Delete in 1 atomic operation on the database if not specified otherwise
+ if (this.shouldUseAtomicUpdate) {
+ req.quer.findOneAndRemove({}, this.delete_options, function(err, obj) {
+ if (err) {
+ exports.respond(res, 500, err);
+ }
+ exports.respondOrErr(res, 404, !obj && exports.objectNotFound(), 204, {});
+ next();
+ });
+ } else {
+ // Preform the remove in two steps allowing mongoose to fire its schema update hook
+ req.quer.findOne({"_id": req.params.id}, function(err, docToRemove) {
+ if (err) {
+ exports.respond(res, 500, err);
+ }
+ var objNotFound = !docToRemove && exports.objectNotFound();
+ if (objNotFound) {
+ exports.respond(res, 404, objNotFound);
+ return next();
+ }
+
+ docToRemove.remove(function (err, obj) {
+ exports.respondOrErr(res, 400, err, 204, {});
+ next();
+ });
+ });
+ }
+};
+
+// I'm going to leave these here because it might be nice to have standardized
+// error messages for common failures
+
+exports.objectNotFound = function() {
+ return {
+ status: 404,
+ message: 'Object not found',
+ name: 'ObjectNotFound',
+ errors: {
+ _id: {
+ message: "Could not find object with specified attributes"
+ }
+ }
+ };
+};
+exports.respond404 = function() {
+ return {
+ status: 404,
+ message: 'Page Not Found',
+ name: "PageNotFound",
+ errors: 'Endpoint not found for model ' + this.modelName
+ };
+};
+exports.authFailure = function() {
+ return {
+ status: 401,
+ message: 'Unauthorized',
+ name: "Unauthorized",
+ errors: 'Operation not authorzed on ' + this.modelName
+ };
+};
+exports.badRequest = function(errobj) {
+ return {
+ status: 400,
+ message: 'Bad Request',
+ name: "BadRequest",
+ errors: errobj || "Your request was invalid"
+ };
+};
+
+/**
+ * Takes a response, error, success statusCode and success payload
+ *
+ * If there is an error, it returns a 400 with the error as the payload
+ * If there is no error, it returns statusCode with the specified payload
+ *
+ */
+exports.respondOrErr = function(res, errStatusCode, err, statusCode, content) {
+ if (err) {
+ exports.respond(res, errStatusCode, err);
+ } else {
+ exports.respond(res, statusCode, content);
+ }
+};
+
+exports.respond = function(res, statusCode, content) {
+ res.locals.status_code = statusCode;
+ res.locals.bundle = content;
+};
+
+function keep(obj, keepers) {
+ var result = {};
+ for (var key in obj) {
+ result[key] = {};
+ for (var key2 in obj[key]) {
+ if (keepers.indexOf(key2) > -1) {
+ result[key][key2] = obj[key][key2];
+ }
+ if ('schema' === key2) {
+ result[key][key2] = keep(obj[key][key2].paths, keepers);
+ }
+ }
+ }
+ return result;
+}
diff --git a/common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/model.js b/common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/model.js
new file mode 100644
index 0000000..1531498
--- /dev/null
+++ b/common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/model.js
@@ -0,0 +1,479 @@
+var mongoose = require('mongoose'),
+ _ = require('underscore'),
+ Model = mongoose.Model,
+ handlers = require('./handlers');
+
+exports = module.exports = model;
+
+var methods = ['get', 'post', 'put', 'delete'], // All HTTP methods, PATCH not currently supported
+ endpoints = ['get', 'post', 'put', 'delete', 'getDetail'],
+ defaultroutes = ['schema'],
+ lookup = {
+ 'get': 'index',
+ 'getDetail': 'show',
+ 'put': 'updated',
+ 'post': 'created',
+ 'delete': 'deleted'
+ },
+ valid_alterables = filterable({
+ 'populate': query('populate'),
+ }, {});
+ valid_filters = filterable({
+ 'limit': query('limit'),
+ 'skip': query('skip'),
+ 'offset': query('offset'),
+ 'select': query('select'),
+ 'sort': query('sort'),
+ }, {
+ 'equals': query('equals'),
+ 'gte': query('gte'),
+ 'gt': query('gt'),
+ 'lt': query('lt'),
+ 'lte': query('lte'),
+ 'ne': query('ne'),
+ 'regex': function(val, query) {
+ var regParts = val.match(/^\/(.*?)\/([gim]*)$/);
+ if (regParts) {
+ // the parsed pattern had delimiters and modifiers. handle them.
+ val = new RegExp(regParts[1], regParts[2]);
+ } else {
+ // we got pattern string without delimiters
+ val = new RegExp(val);
+ }
+
+ return query.regex(val);
+ },
+ 'in': query('in'),
+ 'nin': query('nin'),
+ });
+ defaults = function() {
+ return {
+ routes: {},
+ allowed_methods: {
+ get: { detail: false }
+ },
+ update_options: {},
+ remove_options: {},
+ templateRoot: '',
+ shouldIncludeSchema: true,
+ shouldUseAtomicUpdate: true
+ };
+ };
+
+/**
+ * Returns the model associated with the given name or
+ * registers the model with mongoose
+ */
+function model() {
+ var result = mongoose.model.apply(mongoose, arguments),
+ default_properties = defaults();
+ if (1 === arguments.length) return result;
+
+ for (var key in default_properties) {
+ result[key] = default_properties[key];
+ }
+
+ return result;
+}
+
+Model.includeSchema = function(shouldIncludeSchema) {
+ this.shouldIncludeSchema = shouldIncludeSchema;
+ return this;
+};
+
+Model.methods = function(newmethods) {
+ var self = this,
+ get = contains(newmethods, 'get');
+
+ methods.forEach(function(method) {
+ delete self.routes[method];
+ });
+
+ this.allowed_methods = [];
+ if (!Array.isArray(newmethods)) {
+ newmethods = [newmethods];
+ }
+ if (get && !contains(newmethods, 'getDetail')) {
+ newmethods.push({
+ method: 'getDetail',
+ before: (typeof get !== 'string') ? get.before : null,
+ after: (typeof get !== 'string') ? get.after : null
+ });
+ }
+ newmethods.forEach(function(meth) {
+ var method = meth.method;
+ if ('string' === typeof meth) {
+ method = meth;
+ meth = {};
+ }
+ if (!method) throw new Error("Method object must have a method property");
+ self.allowed_methods.push(method);
+
+ meth.handler = handlers[method];
+ meth.detail = (method !== 'get' && method !== 'post');
+ self.route(method, meth);
+ });
+ return this;
+};
+
+Model.updateOptions = function(options) {
+ this['update_options'] = options;
+ return this;
+};
+
+Model.removeOptions = function(options) {
+ this['remove_options'] = options;
+ return this;
+};
+
+Model.template = function(templatePath) {
+ if (templatePath.substr(-1) == '/') {
+ templatePath = templatePath.substr(0, templatePath.length - 1);
+ }
+ this.templateRoot = templatePath;
+ return this;
+};
+
+/**
+ * Adds the default routes for the HTTP methods and one to get the schema
+ */
+Model.addDefaultRoutes = function() {
+ if (this.shouldIncludeSchema) {
+ this.route('schema', handlers.schema);
+ }
+ this.addSchemaRoutes();
+};
+
+Model.addSchemaRoutes = function() {
+ var self = this;
+ this.schema.eachPath(function(pathName, schemaType) {
+ if (pathName.indexOf('_id') === -1 && schemaType.instance === 'ObjectID') {
+ // Right now, getting nested models is the only operation supported
+ ['get'].forEach(function(method) {
+ self.route(pathName, method , {
+ handler: handlers[method + 'Path'].call(self, pathName),
+ detail: true
+ });
+ });
+ }
+ });
+};
+
+/**
+ * Adds an internal route for a path and method or methods to a function
+ *
+ * @param {String|Object} path: absolute path (including method) or object of routes
+ * @param {String|Function} method: the method to route to or the handler function
+ * @param {Function} fn: The handler function
+ * @return {Model} for chaining
+ * @api public
+ */
+Model.route = function(path, method, fn) {
+ var route = getRoute(this.routes, path),
+ meths = methods, // Default to all methods
+ lastPath = path.substr(path.lastIndexOf('.') + 1);
+
+ if (2 === arguments.length) {
+ fn = method;
+ if (!fn.methods && endpoints.indexOf(lastPath) > -1) {
+ meths = [lastPath];
+ } else if (fn.methods) {
+ meths = fn.methods;
+ }
+ } else {
+ meths = [method];
+ }
+
+ if (fn) {
+ fn = normalizeHandler(fn);
+
+ meths.forEach(function(meth) {
+ route[meth] = merge(route[meth], fn);
+ });
+ }
+ return this;
+};
+
+Model.before = function(path, method, fn) {
+ if (2 == arguments.length) {
+ arguments[1] = { before: arguments[1] };
+ }
+ return this.route.apply(this, arguments);
+};
+
+Model.after = function(path, method, fn) {
+ if (2 == arguments.length) {
+ arguments[1] = { after: arguments[1] };
+ }
+ return this.route.apply(this, arguments);
+};
+
+/**
+ * Registers all of the routes in routeObj to the given app
+ *
+ * TODO(baugarten): refactor to make less ugly
+ *
+ * if (isEndpoint(routeObj, path)) { handleRegistration(app, prefix, path, routeObj); }
+ * else {
+ * for (var key in routeObj) { recurse }
+ * }
+ */
+Model.registerRoutes = function(app, prefix, path, routeObj) {
+ var self = this;
+ for (var key in routeObj) {
+ if (isEndpoint(routeObj, key)) {
+ var route = routeObj[key];
+ var routehandlers = _.isArray(route.handler) ? route.handler : [route.handler];
+ routehandlers = _.map(routehandlers, function(handler) { return handler.bind(self); });
+ var detailGet = !route.detail && !path && key === 'get',
+ handlerlist = route.before.concat(
+ [preprocess.bind(self)],
+ routehandlers,
+ route.after,
+ [handlers.last]
+ );
+ /**
+ * TODO(baugarten): Add an enum type-thing to specify detail route, detail optional or list
+ * aka prettify this
+ */
+ if (route.detail) {
+ app[key](prefix + '/:id([0-9a-fA-F]{0,24})' + path , handlerlist);
+ } else if (detailGet) {
+ app[key](prefix + '/:id([0-9a-fA-F]{0,24}$)?', handlerlist);
+ } else {
+ app[key](prefix + path, handlerlist);
+ }
+ } else {
+ this.registerRoutes(app, prefix, path + '/' + key, routeObj[key]);
+ }
+ }
+};
+
+/**
+ * Registers this model to the given app
+ *
+ * This includes registering endpoints for all the methods desired
+ * in the model definition
+ *
+ */
+Model.register = function(app, url) {
+ this.addDefaultRoutes();
+ app.getDetail = app.get;
+ this.registerRoutes(app, url, '', this.routes);
+};
+
+// Will I still support handle()? I think maybe for default routes it might be nice, but
+// exposed via model.get, model.post, etc.
+/*Model.prototype.handle = function(route, filters, data, callback) {
+ if (arguments.length === 3) {
+ callback = data;
+ data = {};
+ } else if (arguments.length === 2) {
+ callback = filters;
+ filters = [];
+ data = {};
+ }
+ route = route.replace(/\//g, /\./);
+ data.format = 'js';
+ var req = {
+ url: route,
+ filters: filters,
+ body: data,
+ format: 'js',
+ }
+ var res = {
+ writeHeader: function() { },
+ write: function(ret) { callback(ret); },
+ send: function() {},
+ };
+ this.send(route.split(/\./), req, res);
+}
+
+Model.prototype.send = function(routes, req, res, next) {
+ var handler = this.routes;
+ req.quer = this.filter(req.filters, req.body, req.query, this.Model.find({}));
+ req.templatePath = this.template(routes, req.filters);
+ routes.forEach(function(route) {
+ if (route in handler) handler = handler[route];
+ else if (!('all' in handler)) {
+ handlers.respond(res, 404, handlers.respond404());
+ handlers.last(req, res);
+ }
+ });
+ if ('all' in handler) handler = handler.all;
+
+ if ('function' === typeof handler) {
+ return handler.call(this, req, res, next);
+ }
+
+ handlers.respond(res, 404, handlers.respond404());
+ handlers.last(req, res);
+}*/
+
+/**
+ * Returns a query filtered by the data in the request
+ * Looks in req.body and req.query to get the filterable data
+ * Filters the query based on functions in valid_filters
+ */
+Model.filter = function(req, quer) {
+ var detail = false; // detail route
+ // filter by id
+ if (req.params.id) {
+ quer = this.findById(req.params.id);
+ detail = true
+ }
+
+ [req.body, req.query, req.headers].forEach(function(alterableResponse) {
+ Object.keys(alterableResponse).filter(function(potential) {
+ return valid_alterables.contains(potential, quer);
+ }).forEach(function(valid_key) {
+ query = valid_alterables.filter(valid_key, alterableResponse[valid_key], quer);
+ });
+ });
+
+ if (!detail) {
+ [req.body, req.query, req.headers].forEach(function(filterableData) {
+ Object.keys(filterableData).filter(function(potential_filter) {
+ return valid_filters.contains(potential_filter, quer);
+ }).forEach(function(valid_key) {
+ quer = valid_filters.filter(valid_key, filterableData[valid_key], quer);
+ });
+ });
+ }
+ return quer;
+}
+
+function preprocess(req, res, next) {
+ req.body = req.body || {};
+ req.query = req.query || {};
+ req.quer = this.filter(req, this.find({}));
+ if (!('locals' in res)) {
+ res.locals = {};
+ }
+ res.locals.bundle = {};
+
+ req.templatePath = resolveTemplate(req);
+ next();
+}
+
+function query(key) {
+ return function(val, query) {
+ return query[key](val);
+ };
+}
+
+function haveOneModel(req) {
+ return !!req.params.id;
+}
+
+function resolveTemplate(req) {
+ var method = req.method.toLowerCase(),
+ tmplName;
+ if (methods.indexOf(method) > -1) {
+ if (haveOneModel(req) && method === 'get') {
+ method += 'Detail';
+ }
+ tmplName = lookup[method];
+ }
+ return tmplName;
+}
+
+/**
+ * Merges a route with another function object
+ * fn.before is called after the old before
+ * fn.after is called before the old after
+ * If fn.handler is specified, then route.handler is overwritten
+ */
+function merge(route, fn) {
+ if (!route) return fn;
+
+ route.before = route.before.concat(fn.before);
+ route.handler = fn.handler || route.handler;
+ route.after = fn.after.concat(route.after);
+ return route;
+}
+
+function getRoute(routes, path) {
+ path = path.replace(/\//g, /\./).split(/\./);
+ if (1 === path.length && '' === path[0]) { // we got the empty string
+ path = [];
+ }
+ if (endpoints.indexOf(path[path.length - 1]) > -1) {
+ path.splice(path.length - 1, 1);
+ }
+ path.forEach(function(sub, i) {
+ if (!routes[sub]) routes[sub] = {};
+ routes = routes[sub];
+ });
+ return routes;
+}
+
+function normalizeHandler(fn) {
+ var result = {};
+ result.handler = fn.handler;
+ result.detail = fn.detail;
+ if ({}.toString.call(fn) == '[object Function]') {
+ result = {
+ handler: fn
+ };
+ }
+ ['before', 'after'].forEach(function(hook) {
+ result[hook] = fn[hook] || [];
+ if (!Array.isArray(result[hook])) {
+ result[hook] = [ result[hook] ];
+ }
+ });
+ return result;
+}
+
+function isEndpoint(route, method) {
+ return endpoints.indexOf(method) > -1 && route[method].handler;
+}
+
+function contains(arr, key) {
+ if (arr.indexOf(key) > -1) return true;
+ for (var obj in arr) {
+ if (obj.method === key) {
+ return true;
+ }
+ }
+ return false;
+};
+
+function coerceData(filter_func, data) {
+ // Assume data is a string
+ if (data && data.toLowerCase && data.toLowerCase() === 'true') {
+ return true;
+ } else if (data && data.toLowerCase && data.toLowerCase() === 'false') {
+ return false;
+ } else if (filter_func === 'limit' || filter_func === 'skip') {
+ return parseInt(data);
+ }
+ return data;
+};
+
+function filterable(props, subfilters) {
+ return {
+ filter: function(key, val, quer) {
+ if (props[key]) {
+ return props[key](val, quer);
+ }
+ var field = key.split('__'),
+ filter_func = field[1] || 'equals',
+ data = coerceData(filter_func, val);
+
+ // Turn data into array for $in and $nin clause
+ if (filter_func === 'in' || filter_func === 'nin') {
+ data = data.split(',');
+ }
+
+ return subfilters[filter_func](data, quer.where(field[0]));
+ },
+ contains: function(key, quer) {
+ if (key in props) return true;
+ var field = key.split('__');
+ var filter_func = field[1] || 'equals';
+ return field[0] in quer.model.schema.paths && filter_func in subfilters;
+ }
+ }
+}
diff --git a/common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/restful.js b/common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/restful.js
new file mode 100644
index 0000000..0194a5b
--- /dev/null
+++ b/common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/restful.js
@@ -0,0 +1,7 @@
+var model = require('./model'),
+ handlers = require('./handlers'),
+ mongoose = require('mongoose');
+
+exports = module.exports = handlers;
+exports.model = model;
+exports.mongoose = mongoose;