diff --git a/packages/strapi-plugin-graphql/services/Loaders.js b/packages/strapi-plugin-graphql/services/Loaders.js index 0772a996f3..4da6e24b6e 100644 --- a/packages/strapi-plugin-graphql/services/Loaders.js +++ b/packages/strapi-plugin-graphql/services/Loaders.js @@ -85,8 +85,8 @@ module.exports = { // Generate constant for skip parameters. // Note: we shouldn't support both way of doing this kind of things in the future. - const skip = query.options.start || query.options.skip || 0; - const limit = query.options.limit || 100; + const skip = query.options._start || query.options._skip || 0; + const limit = _.get(query, 'options._limit', 100); // Take into account the limit if its equal 0 // Extracting ids from original request to map with query results. const ids = this.extractIds(query); @@ -133,6 +133,9 @@ module.exports = { ...query.options, populate: ast ? [query.alias] : [], // Avoid useless population for performance reason query: {}, + _start: 0, // Don't apply start or skip + _skip: 0, // Don't apply start or skip + _limit: -1, // Don't apply a limit }; params.query[`${query.alias}_in`] = _.chain(query.ids) diff --git a/packages/strapi-utils/lib/builder.js b/packages/strapi-utils/lib/builder.js index 02b847f3dc..fad9ff2942 100644 --- a/packages/strapi-utils/lib/builder.js +++ b/packages/strapi-utils/lib/builder.js @@ -1,7 +1,7 @@ const _ = require('lodash'); const getFilterKey = (key) => { - const matched = key.match(/^_?(sort|limit|start)$/); + const matched = key.match(/^_?(sort|limit|start|skip)$/); if (matched) { return matched[1]; } @@ -47,11 +47,48 @@ class Builder { if (matched) { [field, operation] = matched; } - this[operation].apply(this, [field, value]); + + if (this.isValidFieldId(field)) { + this[operation].apply(this, [field, value]); + } else { + strapi.log.warn( + `Your filter: ${JSON.stringify(filter, null, 2)} contains a field "${field}" that doesn't appear neither on your model definition nor in the basic filter operators, + This field will be ignored for now.` + ); + } } } } + isValidFieldId(fieldId) { + let model = this.model; + const fieldParts = fieldId.split('.'); + const fieldPartsSize = fieldParts.length; + return _.every(fieldParts, (fieldPart, index) => { + const fieldIdx = index + 1; + const isAttribute = + !!model.attributes[fieldPart] || model.primaryKey === fieldPart; + const association = model.associations.find( + ast => ast.alias === fieldPart + ); + const isAssociation = !!association; + if (fieldIdx < fieldPartsSize) { + if (isAssociation) { + const { models } = association.plugin + ? strapi.plugins[association.plugin] + : strapi; + model = models[association.collection || association.model]; + return true; + } + } else if (fieldIdx === fieldPartsSize) { + if (isAttribute || isAssociation) { + return true; + } + } + return false; + }); + } + sort(sort) { const [key, order = 'ASC'] = _.isString(sort) ? sort.split(':') : sort; @@ -62,11 +99,27 @@ class Builder { } limit(limit) { - this.filter.limit = _.toNumber(limit); + const _limit = _.toNumber(limit); + // If the limit is explicitly set to -1, then don't apply a limit + if (_limit === -1) { + delete this.filter.limit; + } else { + this.filter.limit = _limit; + } + + return this; } start(start) { this.filter.start = _.toNumber(start); + return this; + } + + /** + * This is just an alias for start, it'll be deprecated in the future. + */ + skip(start) { + return this.start(start); } add(w) {