mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-04 11:54:10 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			236 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			236 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
'use strict';
 | 
						|
 | 
						|
/**
 | 
						|
 * Converts the standard Strapi REST query params to a more 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 QUERY_OPERATORS = ['_where', '_or', '_and'];
 | 
						|
 | 
						|
class InvalidOrderError extends Error {
 | 
						|
  constructor() {
 | 
						|
    super();
 | 
						|
    this.message = 'Invalid order. order can only be one of asc|desc|ASC|DESC';
 | 
						|
  }
 | 
						|
}
 | 
						|
class InvalidSortError extends Error {
 | 
						|
  constructor() {
 | 
						|
    super();
 | 
						|
    this.message =
 | 
						|
      'Invalid sort parameter. Expected a string, an array of strings, a sort object or an array of sort objects';
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
const validateOrder = order => {
 | 
						|
  if (!['asc', 'desc'].includes(order.toLocaleLowerCase())) {
 | 
						|
    throw new InvalidOrderError();
 | 
						|
  }
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Sort query parser
 | 
						|
 * @param {string} sortQuery - ex: id:asc,price:desc
 | 
						|
 */
 | 
						|
const convertSortQueryParams = sortQuery => {
 | 
						|
  if (typeof sortQuery === 'string') {
 | 
						|
    return sortQuery.split(',').map(value => convertSingleSortQueryParam(value));
 | 
						|
  }
 | 
						|
 | 
						|
  if (Array.isArray(sortQuery)) {
 | 
						|
    return sortQuery.flatMap(sortValue => convertSortQueryParams(sortValue));
 | 
						|
  }
 | 
						|
 | 
						|
  if (_.isPlainObject(sortQuery)) {
 | 
						|
    return convertNestedSortQueryParam(sortQuery);
 | 
						|
  }
 | 
						|
 | 
						|
  throw new InvalidSortError();
 | 
						|
};
 | 
						|
 | 
						|
const convertSingleSortQueryParam = sortQuery => {
 | 
						|
  // split field and order param with default order to ascending
 | 
						|
  const [field, order = 'asc'] = sortQuery.split(':');
 | 
						|
 | 
						|
  if (field.length === 0) {
 | 
						|
    throw new Error('Field cannot be empty');
 | 
						|
  }
 | 
						|
 | 
						|
  validateOrder(order);
 | 
						|
 | 
						|
  return _.set({}, field, order);
 | 
						|
};
 | 
						|
 | 
						|
const convertNestedSortQueryParam = sortQuery => {
 | 
						|
  const transformedSort = {};
 | 
						|
  for (const field in sortQuery) {
 | 
						|
    const order = sortQuery[field];
 | 
						|
 | 
						|
    // this is a deep sort
 | 
						|
    if (_.isPlainObject(order)) {
 | 
						|
      transformedSort[field] = convertNestedSortQueryParam(order);
 | 
						|
    } else {
 | 
						|
      validateOrder(order);
 | 
						|
      transformedSort[field] = order;
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  return transformedSort;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Start query parser
 | 
						|
 * @param {string} startQuery
 | 
						|
 */
 | 
						|
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
 | 
						|
 */
 | 
						|
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;
 | 
						|
};
 | 
						|
 | 
						|
class InvalidPopulateError extends Error {
 | 
						|
  constructor() {
 | 
						|
    super();
 | 
						|
    this.message =
 | 
						|
      'Invalid populate parameter. Expected a string, an array of strings, a populate object';
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
// 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 => {
 | 
						|
      if (typeof value !== 'string') {
 | 
						|
        throw new InvalidPopulateError();
 | 
						|
      }
 | 
						|
 | 
						|
      return value.split(',').map(value => _.trim(value));
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  if (_.isPlainObject(populate)) {
 | 
						|
    const transformedPopulate = {};
 | 
						|
    for (const key in populate) {
 | 
						|
      transformedPopulate[key] = convertNestedPopulate(populate[key]);
 | 
						|
    }
 | 
						|
    return transformedPopulate;
 | 
						|
  }
 | 
						|
 | 
						|
  throw new InvalidPopulateError();
 | 
						|
};
 | 
						|
 | 
						|
const convertNestedPopulate = subPopulate => {
 | 
						|
  if (subPopulate === '*') {
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
 | 
						|
  if (_.isBoolean(subPopulate)) {
 | 
						|
    return subPopulate;
 | 
						|
  }
 | 
						|
 | 
						|
  if (!_.isPlainObject(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 = convertFieldsQueryParams(fields);
 | 
						|
  }
 | 
						|
 | 
						|
  if (populate) {
 | 
						|
    query.populate = convertPopulateQueryParams(populate);
 | 
						|
  }
 | 
						|
 | 
						|
  return query;
 | 
						|
};
 | 
						|
 | 
						|
const convertFieldsQueryParams = (fields, depth = 0) => {
 | 
						|
  if (depth === 0 && fields === '*') {
 | 
						|
    return undefined;
 | 
						|
  }
 | 
						|
 | 
						|
  if (typeof fields === 'string') {
 | 
						|
    const fieldsValues = fields.split(',').map(value => _.trim(value));
 | 
						|
    return _.uniq(['id', ...fieldsValues]);
 | 
						|
  }
 | 
						|
 | 
						|
  if (Array.isArray(fields)) {
 | 
						|
    // map convert
 | 
						|
    const fieldsValues = fields.flatMap(value => convertFieldsQueryParams(value, depth + 1));
 | 
						|
    return _.uniq(['id', ...fieldsValues]);
 | 
						|
  }
 | 
						|
 | 
						|
  throw new Error('Invalid fields parameter. Expected a string or an array of strings');
 | 
						|
};
 | 
						|
 | 
						|
// 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,
 | 
						|
  convertFieldsQueryParams,
 | 
						|
  VALID_REST_OPERATORS,
 | 
						|
  QUERY_OPERATORS,
 | 
						|
};
 |