/*jshint node:true*/ "use strict"; var queryHelpers = require("./query"), mongoose = require("mongoose"), CastError = mongoose.SchemaType.CastError; /*! * Misc helpers */ function getObject(req, item) { if (typeof item.toObject === "function") { item._request = req; return item.toObject(req.options.toObject); } else { return item; } } /*! * Document resource handlers */ function mongooseCollCount(req, cb) { var query = req.options.query(); if (req.query["query"]) { // Cache query operator if (!req._queryOperator) { req._queryOperator = queryHelpers.create(req.query["query"]); } query = query.find(req._queryOperator); } query.count(function(err, count) { cb(err, count); }); } function mongooseCollList(req, offset, limit, cb) { var options = req.options; var query = options.query(); if (req.query["query"]) { // Cache query operator if (!req._queryOperator) { req._queryOperator = queryHelpers.create(req.query["query"]); } query = query.find(req._queryOperator); } query = query.skip(offset).limit(limit); if (req.query["sort"]) { query = query.sort(req.query["sort"]); } else if (options.sort) { query = query.sort(options.sort); } return query.exec(function(err, items) { if (err) { cb(err); } else { cb(null, items.map(function(item) { var obj = getObject(req, item); return obj; })); } }); } function mongooseCollPost(req, cb) { var model = req.mongoose.model; model.create(req.body, function(err, doc) { if (err) { cb(err); } else { if (req.options.postResponse) { cb(null, getObject(req, doc)); } else { cb.created(); } } }); } /*! * Document resource handlers */ function mongooseDocHook(req, next) { var options = req.options; var crit = {}; crit[options.key] = req.params.id; req.mongoose.path += "/" + req.params.id; options.query().find(crit).findOne(function(err, item) { if (err instanceof CastError) { // id is not valid, just continue without saving item return next(); } if (err) { return next(err); } req.mongoose.doc = item; next(); }); } function mongooseDocGet(req, cb) { if (req.mongoose.doc) { cb(null, getObject(req, req.mongoose.doc)); } else { cb.notFound(); } } function mongooseDocPut(req, isPatch, cb) { var doc = req.mongoose.doc; if (!doc) { return cb.notFound(); } doc.set(req.body); doc.save(function(err) { cb(err); }); } function mongooseDocDel(req, cb) { if (!req.mongoose.doc) { return cb.notFound(); } req.mongoose.doc.remove(function(err) { cb(err); }); } /*! * Document path resource handlers */ function mongoosePathHook(req, next) { var doc = req.mongoose.doc; var docpath = req.mongoose.path; var subkeys = req.options.subkeys; if (!doc) { // We have no doc in the first place, don't try to find member return next(); } var path = req.params["*"]; var parts = path.split("/"); var fullpath = docpath; var current = doc; var parent = doc; var link = {}; while(parts.length > 0) { var part = parts.shift(); fullpath += "/" + part; var decoded = decodeURIComponent(part); if (current.isMongooseDocumentArray) { parent = current; var key = "_id"; if (subkeys) { if (typeof subkeys === "string") { key = subkeys; } else { Object.keys(subkeys).forEach(function(pattern) { if (req.match(pattern, fullpath)) { key = subkeys[pattern]; } }); } } if (key !== "_id") { current = current.filter(function(item) { return item[key] === decoded; })[0]; link = { id: current._id }; } else { current = current.id(decoded); link = { id: decoded }; } } else { if ("field" in link) { link.field += "." + decoded; } else { parent = current; link = { field: decoded }; } current = parent.get(link.field); } if (!current) { return next(); } } req.mongoose.parent = parent; req.mongoose.item = current; req.mongoose.link = link; next(); } function mongoosePathGet(req, cb) { if (!("item" in req.mongoose)) { return cb.notFound(); } var item = req.mongoose.item; if (item.isMongooseDocumentArray) { cb.list(mongooseDocArrayCount, mongooseDocArrayList); } else { cb(null, getObject(req, item)); } } function mongoosePathPut(req, isPatch, cb) { if (!("item" in req.mongoose)) { return cb.notFound(); } var parent = req.mongoose.parent; var link = req.mongoose.link; var doc = req.mongoose.doc; var value = req.body; if ("_value" in value) { value = value._value; } if ("id" in link) { parent.id(link.id).set(value); } else if ("field" in link) { parent.set(link.field, value); } else { return cb(new Error("Unknown link type")); } doc.save(function(err) { cb(err); }); } function mongoosePathDel(req, cb) { if (!("item" in req.mongoose)) { return cb.notFound(); } var parent = req.mongoose.parent; var link = req.mongoose.link; var doc = req.mongoose.doc; if ("id" in link) { parent.splice(parent.indexOf(parent.id(link.id)), 1); } else if ("field" in link) { parent.set(link.field, undefined); } else { return cb(new Error("Unknown link type")); } doc.save(function(err) { cb(err); }); } function mongoosePathPost(req, cb) { if (!("item" in req.mongoose)) { return cb.notFound(); } var item = req.mongoose.item; if (item.isMongooseDocumentArray) { mongooseDocArrayPost(req, cb); } else if (Array.isArray(item)) { if ("_value" in req.body) { req.body = req.body._value; } mongooseDocArrayPost(req, cb); } else { return cb.methodNotAllowed(); } } /*! * Mongoose DocumentArray helpers */ function queryDocArray(req) { var docArray = req.mongoose.item; if (req.query["query"]) { // Cache query result if (!req.mongoose._queryResult) { req.mongoose._queryResult = docArray.filter( queryHelpers.match.bind( null, queryHelpers.create(req.query["query"]) ) ); } return req.mongoose._queryResult; } else { return docArray; } } function mongooseDocArrayCount(req, cb) { var len = queryDocArray(req).length; cb(null, len); } function mongooseDocArrayList(req, offset, limit, cb) { var items = queryDocArray(req); if (limit > 0) { items = items.slice(offset, offset + limit); } else { items = items.slice(offset); } cb(null, items); } function mongooseDocArrayPost(req, cb) { var docArray = req.mongoose.item; var doc = req.mongoose.doc; var index = NaN; if (req.query["index"]) { index = Number(req.query["index"]); } if (isNaN(index)) { index = docArray.length; } docArray.splice(Math.max(0, Math.min(docArray.length, index)), 0, req.body); doc.save(function(err) { if (err) { cb(err); } else { if (req.options.postResponse) { cb(null, getObject(req, docArray[index])); } else { cb.created(); } } }); } /*! * Mongoose resource definition helper */ function mongooseResource(name, Model) { /*jshint validthis:true*/ var collResource = this.sub(name) .hook(function modelHook(req, next) { req.mongoose = { model: Model, path: name }; next(); }) .count(mongooseCollCount) .list(mongooseCollList) .post(mongooseCollPost) .set("query", function mongooseDefaultQuery() { return Model.find(); }) .set("key", "_id"); var docResource = collResource.sub(":id") .hook(mongooseDocHook) .get(mongooseDocGet) .put(mongooseDocPut) .del(mongooseDocDel); docResource.sub("*", mongoosePathHook) .get(mongoosePathGet) .put(mongoosePathPut) .del(mongoosePathDel) .post(mongoosePathPost); return collResource; } module.exports = mongooseResource;