strapi/packages/core/utils/lib/convert-query-params.js

165 lines
3.9 KiB
JavaScript
Raw Normal View History

'use strict';
/**
* Converts the standard Strapi REST query params to a moe usable format for querying
* You can read more here: https://strapi.io/documentation/developer-docs/latest/developer-resources/content-api/content-api.html#filters
*/
const _ = require('lodash');
// const BOOLEAN_OPERATORS = ['or', 'and'];
const QUERY_OPERATORS = ['_where', '_or', '_and'];
/**
* Sort query parser
* @param {string} sortQuery - ex: id:asc,price:desc
*/
const convertSortQueryParams = sortQuery => {
if (Array.isArray(sortQuery)) {
return sortQuery.flatMap(sortValue => convertSortQueryParams(sortValue));
}
if (typeof sortQuery !== 'string') {
throw new Error(`convertSortQueryParams expected a string, got ${typeof sortQuery}`);
}
const sortKeys = [];
sortQuery.split(',').forEach(part => {
// split field and order param with default order to ascending
const [field, order = 'asc'] = part.split(':');
if (field.length === 0) {
throw new Error('Field cannot be empty');
}
if (!['asc', 'desc'].includes(order.toLocaleLowerCase())) {
throw new Error('order can only be one of asc|desc|ASC|DESC');
}
sortKeys.push(_.set({}, field, order.toLowerCase()));
});
return sortKeys;
};
/**
* Start query parser
* @param {string} startQuery - ex: id:asc,price:desc
*/
const convertStartQueryParams = startQuery => {
const startAsANumber = _.toNumber(startQuery);
if (!_.isInteger(startAsANumber) || startAsANumber < 0) {
throw new Error(`convertStartQueryParams expected a positive integer got ${startAsANumber}`);
}
return startAsANumber;
};
/**
* Limit query parser
* @param {string} limitQuery - ex: id:asc,price:desc
*/
const convertLimitQueryParams = limitQuery => {
const limitAsANumber = _.toNumber(limitQuery);
if (!_.isInteger(limitAsANumber) || (limitAsANumber !== -1 && limitAsANumber < 0)) {
throw new Error(`convertLimitQueryParams expected a positive integer got ${limitAsANumber}`);
}
return limitAsANumber;
};
// NOTE: we could support foo.* or foo.bar.* etc later on
const convertPopulateQueryParams = (populate, depth = 0) => {
if (depth === 0 && populate === '*') {
return true;
}
if (typeof populate === 'string') {
return populate.split(',').map(value => _.trim(value));
}
if (Array.isArray(populate)) {
// map convert
return populate.flatMap(value => convertPopulateQueryParams(value, depth + 1));
}
if (_.isPlainObject(populate)) {
const transformedPopulate = {};
for (const key in populate) {
transformedPopulate[key] = convertNestedPopulate(populate[key]);
}
return transformedPopulate;
}
throw new Error(
'Invalid populate parameter. Expected a string or an array of strings or a populate object'
);
};
const convertNestedPopulate = subPopulate => {
if (subPopulate === '*') {
return true;
}
if (!_.isPlainObject(subPopulate)) {
console.log(subPopulate);
throw new Error(`Invalid nested populate. Expected '*' or an object`);
}
// TODO: We will need to consider a way to add limitation / pagination
const { sort, filters, fields, populate } = subPopulate;
const query = {};
if (sort) {
query.orderBy = convertSortQueryParams(sort);
}
if (filters) {
query.where = convertFiltersQueryParams(filters);
}
if (fields) {
query.select = _.castArray(fields);
}
if (populate) {
query.populate = convertPopulateQueryParams(populate);
}
return query;
};
// NOTE: We could validate the parameters are on existing / non private attributes
const convertFiltersQueryParams = filters => filters;
// TODO: migrate
const VALID_REST_OPERATORS = [
'eq',
'ne',
'in',
'nin',
'contains',
'ncontains',
'containss',
'ncontainss',
'lt',
'lte',
'gt',
'gte',
'null',
];
module.exports = {
convertSortQueryParams,
convertStartQueryParams,
convertLimitQueryParams,
convertPopulateQueryParams,
convertFiltersQueryParams,
VALID_REST_OPERATORS,
QUERY_OPERATORS,
};