summaryrefslogtreecommitdiffstats
path: root/common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/model.js
diff options
context:
space:
mode:
Diffstat (limited to 'common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/model.js')
-rw-r--r--common/src/main/webapp/usageguide/appserver/node_modules/node-restful/lib/model.js479
1 files changed, 479 insertions, 0 deletions
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;
+ }
+ }
+}