UNPKG

rested

Version:

{REST}ed; {REST}ful Enterprise Data-as-a-Service (DaaS)

183 lines (177 loc) 7.5 kB
const errors = require('http-errors'); const isDefinedAndNotNull = n => { if (n === null) return false; if (n === undefined) return false; return true; }; const isPositiveInteger = n => { if (!isDefinedAndNotNull(n)) return false; n = Number(n); if (n < 1) return false; return n === Math.ceil(n); }; const getAsInt = n => Math.ceil(Number(n)); const isNonNegativeInteger = n => { if (!isDefinedAndNotNull(n)) return false; n = Number(n); if (n < 0) return false; return n === Math.ceil(n); }; module.exports = function () { // Check for bad selection const checkBadSelection = select => this.deselected().some(path => new RegExp('[+]?' + path + '\\b', 'i').exec(select)) // ignore skip, limit and select on distinct or count this.query((request, response, next) => { if (!!(request.query.distinct || request.query.count)) { delete request.query.skip; delete request.query.limit; delete request.query.select; } next(); }); // Perform distinct query. this.query((request, response, next) => { let distinct = request.query.distinct; if (!distinct) return next(); if (this.deselected(distinct)) return next(errors.Forbidden('You may not find distinct values for the requested path')); if (!this.model().schema.path(distinct)) distinct = Object.keys(this.model().translateAliases({ [distinct]: distinct }))[0]; this.model().distinct(distinct, request.rested.conditions, (error, values) => { if (!error) request.rested.documents = request.query.sort === distinct ? values.sort() : values; next(error); }); }); // Apply controller sort options to the query. this.query((request, response, next) => { let sort = this.sort(); if (sort) request.rested.query.sort(sort); next(); }); // Apply incoming request sort. this.query((request, response, next) => { let sort = request.query.sort; if (sort) request.rested.query.sort(sort); next(); }); // Apply controller select options to the query. this.query((request, response, next) => { let select = this.select(); // skip when no select is not preset if (!select) return next(); let reselect = request.query.select || ''; // skip when query select is an object if (typeof reselect === 'object') return next(); // take the deselected _id field out of the comparison // as it is the only field that can be always deselected reselect = reselect.replace('-_id', '').trim(); // take default selection into consideration when no query select or both have the same sign +- if (!reselect || (select[0] === '-' && reselect[0] === '-') || (select[0] !== '-' && reselect[0] !== '-')) request.rested.query.select(select); next(); }); // Apply incoming request select to the query. this.query((request, response, next) => { let select = request.query.select; if (!select) return next(); if (typeof select === 'string' && select.indexOf('+') !== -1) return next(errors.Forbidden('Including excluded fields is not permitted')); if (typeof select === 'string' && checkBadSelection(select)) return next(errors.Forbidden('Including excluded fields is not permitted')); // handle aliases, first translate the string into an object if (typeof select === 'string') select = select.split(' ').reduce((obj, key) => ({ ...obj, [key.substr(Number(key[0] === '-'))]: key[0] !== '-' }), {}); // translate the select to adjust for any aliased fields select = this.model().translateAliases(select); // correctly parse select nuerical values into valid ones select = JSON.parse(JSON.stringify(select).replace(/:"(\d|true|false)"/gi, ':$1')); // set the query select request.rested.query.select(select); next(); }); // Apply incoming request populate. this.query((request, response, next) => { let populate = request.query.populate; let allowPopulateSelect = request.rested.allowPopulateSelect; let error = null; if (populate) { if (typeof populate === 'string') { if (populate.indexOf('{') !== -1) populate = JSON.parse(populate); else if (populate.indexOf('[') !== -1) populate = JSON.parse(populate); } if (!Array.isArray(populate)) populate = [populate]; populate.forEach(field => { if (error) return; if (checkBadSelection(field.path || field)) return error = errors.Forbidden('Including excluded fields is not permitted'); // Don't allow selecting fields from client when populating if (field.select) { if (!allowPopulateSelect) return error = errors.Forbidden('Selecting fields of populated documents is not permitted'); //console.warn('WARNING: Allowing populate with select is experimental and bypasses security.'); } request.rested.query.populate(field); }); } next(error); }); // Apply incoming request skip. this.query((request, response, next) => { let skip = request.query.skip; if (skip === undefined || skip === null) return next(); if (!isNonNegativeInteger(skip)) return next(errors.BadRequest('Skip must be a non-negative integer if set')); request.rested.query.skip(getAsInt(skip)); next(); }); // Apply incoming request limit. this.query((request, response, next) => { let limit = request.rested.query.op === 'findOne' ? 1 : request.query.limit; if (limit === undefined || limit === null) return next(); if (!isPositiveInteger(limit)) return next(errors.BadRequest('Limit must be a positive integer if set')); request.rested.query.limit(getAsInt(limit)); next(); }); // Set count flag. this.query((request, response, next) => { if (!request.query.count) return next(); if (request.query.count === 'false') return next(); if (request.query.count !== 'true') return next(errors.BadRequest('Count must be "true" or "false" if set')); request.rested.count = true; next(); }); // Check for query comment. this.query((request, response, next) => { let comment = request.query.comment; if (!comment) return next(); if (this.comments()) request.rested.query.comment(comment); //else console.warn('Query comment was ignored.'); next(); }); // Check for query hint. this.query((request, response, next) => { let hint = request.query.hint; if (!hint) return next(); if (!this.hints()) return next(errors.Forbidden('Hints are not enabled for this resource')); if (typeof hint === 'string') hint = JSON.parse(hint); // Convert the value for each path from stirng to number. Object.keys(hint).forEach(path => hint[path] = Number(hint[path])); request.rested.query.hint(hint); next(); }); // Check for query lean and explain. this.query((request, response, next) => { if (request.method !== 'GET') return next(); let options = Object.assign({}, this.model().schema.options.toJSON, this.model().schema.options.toObject); let lean = request.query.lean || request.query.explain; lean = lean || !(options.virtuals || options.getters || options.transform); if (lean === 'true' || lean === true) request.rested.query.lean(); next(); }); };