You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
162 lines
5.1 KiB
162 lines
5.1 KiB
'use strict';
|
|
|
|
var util = require('util'),
|
|
Base = require('./base'),
|
|
_ = require('lodash'),
|
|
errors = require('../Errors');
|
|
|
|
var List = function(args) {
|
|
List.super_.call(this, args);
|
|
};
|
|
|
|
util.inherits(List, Base);
|
|
|
|
List.prototype.action = 'list';
|
|
List.prototype.method = 'get';
|
|
List.prototype.plurality = 'plural';
|
|
|
|
List.prototype._safeishParse = function(value) {
|
|
try {
|
|
return JSON.parse(value);
|
|
} catch(err) {
|
|
return value;
|
|
}
|
|
};
|
|
|
|
var stringOperators = /like|iLike|notLike|notILike/;
|
|
List.prototype.fetch = function(req, res, context) {
|
|
var self = this,
|
|
model = this.model,
|
|
options = context.options || {},
|
|
criteria = context.criteria || {},
|
|
include = this.include,
|
|
includeAttributes = this.includeAttributes,
|
|
Sequelize = this.resource.sequelize,
|
|
defaultCount = 100,
|
|
count = +context.count || +req.query.count || defaultCount,
|
|
offset = +context.offset || +req.query.offset || 0;
|
|
|
|
// only look up attributes we care about
|
|
options.attributes = options.attributes || this.resource.attributes;
|
|
|
|
// account for offset and count
|
|
offset += context.page * count || req.query.page * count || 0;
|
|
if (count > 1000) count = 1000;
|
|
if (count < 0) count = defaultCount;
|
|
|
|
options.offset = offset;
|
|
options.limit = count;
|
|
if (!this.resource.pagination)
|
|
delete options.limit;
|
|
|
|
if (context.include && context.include.length) {
|
|
include = include.concat(context.include);
|
|
}
|
|
if (include.length) {
|
|
options.include = include;
|
|
}
|
|
|
|
var searchParams = this.resource.search.length ? this.resource.search : [this.resource.search];
|
|
searchParams.forEach(function(searchData) {
|
|
var searchParam = searchData.param;
|
|
if (_.has(req.query, searchParam)) {
|
|
var search = [];
|
|
var searchOperator = searchData.operator || '$like';
|
|
var searchAttributes =
|
|
searchData.attributes || Object.keys(model.rawAttributes);
|
|
searchAttributes.forEach(function(attr) {
|
|
if(stringOperators.test(searchOperator)){
|
|
var attrType = model.rawAttributes[attr].type;
|
|
if (!(attrType instanceof Sequelize.STRING) &&
|
|
!(attrType instanceof Sequelize.TEXT)) {
|
|
// NOTE: Sequelize has added basic validation on types, so we can't get
|
|
// away with blind comparisons anymore. The feature is up for
|
|
// debate so this may be changed in the future
|
|
return;
|
|
}
|
|
}
|
|
|
|
var item = {};
|
|
var query = {};
|
|
var searchString;
|
|
if (!~searchOperator.toLowerCase().indexOf('like')) {
|
|
searchString = req.query[searchParam];
|
|
} else {
|
|
searchString = '%' + req.query[searchParam] + '%';
|
|
}
|
|
query[searchOperator] = searchString;
|
|
item[attr] = query;
|
|
search.push(item);
|
|
});
|
|
|
|
if (Object.keys(criteria).length)
|
|
criteria = Sequelize.and(criteria, Sequelize.or.apply(null, search));
|
|
else
|
|
criteria = Sequelize.or.apply(null, search);
|
|
}
|
|
});
|
|
|
|
var sortParam = this.resource.sort.param;
|
|
if (_.has(req.query, sortParam) || _.has(this.resource.sort, 'default')) {
|
|
var order = [];
|
|
var columnNames = [];
|
|
var sortQuery = req.query[sortParam] || this.resource.sort.default || '';
|
|
var sortColumns = sortQuery.split(',');
|
|
sortColumns.forEach(function(sortColumn) {
|
|
if (sortColumn.indexOf('-') === 0) {
|
|
var actualName = sortColumn.substring(1);
|
|
order.push([actualName, 'DESC']);
|
|
columnNames.push(actualName);
|
|
} else {
|
|
columnNames.push(sortColumn);
|
|
order.push([sortColumn, 'ASC']);
|
|
}
|
|
});
|
|
var allowedColumns = this.resource.sort.attributes || Object.keys(model.rawAttributes);
|
|
var disallowedColumns = _.difference(columnNames, allowedColumns);
|
|
if (disallowedColumns.length) {
|
|
throw new errors.BadRequestError('Sorting not allowed on given attributes', disallowedColumns);
|
|
}
|
|
|
|
if (order.length)
|
|
options.order = order;
|
|
}
|
|
|
|
// all other query parameters are passed to search
|
|
var extraSearchCriteria = _.reduce(req.query, function(result, value, key) {
|
|
if (_.has(model.rawAttributes, key)) result[key] = self._safeishParse(value);
|
|
return result;
|
|
}, {});
|
|
|
|
if (Object.keys(extraSearchCriteria).length)
|
|
criteria = _.assign(criteria, extraSearchCriteria);
|
|
|
|
// do the actual lookup
|
|
if (Object.keys(criteria).length)
|
|
options.where = criteria;
|
|
|
|
return model
|
|
.findAndCountAll(options)
|
|
.then(function(result) {
|
|
context.instance = result.rows;
|
|
var start = offset;
|
|
var end = start + result.rows.length - 1;
|
|
end = end === -1 ? 0 : end;
|
|
|
|
if (self.resource.associationOptions.removeForeignKeys) {
|
|
_.each(context.instance, function(instance) {
|
|
_.each(includeAttributes, function(attr) {
|
|
delete instance[attr];
|
|
delete instance.dataValues[attr];
|
|
});
|
|
});
|
|
}
|
|
|
|
if (!!self.resource.pagination)
|
|
res.header('Content-Range', 'items ' + [[start, end].join('-'), result.count].join('/'));
|
|
|
|
return context.continue;
|
|
});
|
|
};
|
|
|
|
module.exports = List;
|
|
|