mirror of
				https://github.com/strapi/strapi.git
				synced 2025-11-03 19:36:20 +00:00 
			
		
		
		
	Implement rest api populate syntax and Init refactor convert query params for v4
This commit is contained in:
		
							parent
							
								
									86961318f0
								
							
						
					
					
						commit
						3784cc5b5e
					
				@ -1,5 +1,6 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
// TODO: migration
 | 
			
		||||
const _ = require('lodash');
 | 
			
		||||
const { rulesToQuery } = require('@casl/ability/extra');
 | 
			
		||||
const { VALID_REST_OPERATORS } = require('@strapi/utils');
 | 
			
		||||
 | 
			
		||||
@ -9,7 +9,7 @@ const {
 | 
			
		||||
  convertStartQueryParams,
 | 
			
		||||
  convertPopulateQueryParams,
 | 
			
		||||
  convertFiltersQueryParams,
 | 
			
		||||
} = require('@strapi/utils/lib/convert-rest-query-params');
 | 
			
		||||
} = require('@strapi/utils/lib/convert-query-params');
 | 
			
		||||
 | 
			
		||||
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
 | 
			
		||||
 | 
			
		||||
@ -20,7 +20,7 @@ const transformParamsToQuery = (uid, params = {}) => {
 | 
			
		||||
 | 
			
		||||
  const query = {};
 | 
			
		||||
 | 
			
		||||
  // TODO: check invalid values add defaults ....
 | 
			
		||||
  // TODO: check invalid values / add defaults ....
 | 
			
		||||
 | 
			
		||||
  const {
 | 
			
		||||
    start,
 | 
			
		||||
@ -79,7 +79,7 @@ const transformParamsToQuery = (uid, params = {}) => {
 | 
			
		||||
    query.populate = convertPopulateQueryParams(populate);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // TODO: move to layer above ?
 | 
			
		||||
  // TODO: move to convert-query-params ?
 | 
			
		||||
  if (publicationState && contentTypesUtils.hasDraftAndPublish(model)) {
 | 
			
		||||
    const { publicationState = 'live' } = params;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1,370 +0,0 @@
 | 
			
		||||
'use strict';
 | 
			
		||||
 | 
			
		||||
const { convertRestQueryParams } = require('../convert-rest-query-params');
 | 
			
		||||
 | 
			
		||||
describe('convertRestQueryParams', () => {
 | 
			
		||||
  test('Throws on invalid input', () => {
 | 
			
		||||
    // throws when no params provided
 | 
			
		||||
    expect(() => convertRestQueryParams(1)).toThrow();
 | 
			
		||||
    expect(() => convertRestQueryParams('azdazd')).toThrow();
 | 
			
		||||
    expect(() => convertRestQueryParams(null)).toThrow();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  test('Runs correctly on valid input', () => {
 | 
			
		||||
    // returns empty if no params
 | 
			
		||||
    expect(convertRestQueryParams()).toMatchObject({});
 | 
			
		||||
    expect(convertRestQueryParams({})).toMatchObject({});
 | 
			
		||||
 | 
			
		||||
    expect(
 | 
			
		||||
      convertRestQueryParams({
 | 
			
		||||
        sort: 'id:desc,price',
 | 
			
		||||
        _start: '5',
 | 
			
		||||
        _limit: '10',
 | 
			
		||||
      })
 | 
			
		||||
    ).toMatchObject({
 | 
			
		||||
      sort: [{ id: 'desc' }, { price: 'asc' }],
 | 
			
		||||
      start: 5,
 | 
			
		||||
      limit: 10,
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('Sort param', () => {
 | 
			
		||||
    test('Throws on invalid params', () => {
 | 
			
		||||
      // invalid sort queries
 | 
			
		||||
      expect(() => convertRestQueryParams({ sort: 1 })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ sort: {} })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ sort: 'id,,test' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ sort: 'id,test,' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ sort: 'id:asc,test:dasc' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ sort: 'id:asc,:asc' })).toThrow();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test.each([
 | 
			
		||||
      ['id', [{ id: 'asc' }]],
 | 
			
		||||
      ['id:desc', [{ id: 'desc' }]],
 | 
			
		||||
      ['id:ASC', [{ id: 'asc' }]],
 | 
			
		||||
      ['id:DESC', [{ id: 'desc' }]],
 | 
			
		||||
      ['id:asc', [{ id: 'asc' }]],
 | 
			
		||||
      ['id,price', [{ id: 'asc' }, { price: 'asc' }]],
 | 
			
		||||
      ['id:desc,price', [{ id: 'desc' }, { price: 'asc' }]],
 | 
			
		||||
      ['id:desc,price:desc', [{ id: 'desc' }, { price: 'desc' }]],
 | 
			
		||||
      ['id:asc,price,date:desc', [{ id: 'asc' }, { price: 'asc' }, { date: 'desc' }]],
 | 
			
		||||
      [
 | 
			
		||||
        'published_at:asc,price:ASC,date:DESC',
 | 
			
		||||
        [{ published_at: 'asc' }, { price: 'asc' }, { date: 'desc' }],
 | 
			
		||||
      ],
 | 
			
		||||
    ])('Converts sort query "%s" correctly', (input, expected) => {
 | 
			
		||||
      expect(convertRestQueryParams({ sort: input })).toMatchObject({
 | 
			
		||||
        sort: expected,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('Start param', () => {
 | 
			
		||||
    test('Throws on invalid params', () => {
 | 
			
		||||
      // invalid sort queries
 | 
			
		||||
      expect(() => convertRestQueryParams({ _start: 'du text' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _start: '12 du text' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _start: '12.1' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _start: 'NaN' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _start: 'Infinity' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _start: Infinity })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _start: -Infinity })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _start: NaN })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _start: 1.2 })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _start: -10 })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _start: {} })).toThrow();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test.each([
 | 
			
		||||
      ['1', 1],
 | 
			
		||||
      ['12', 12],
 | 
			
		||||
      ['0', 0],
 | 
			
		||||
    ])('Converts start query "%s" correctly', (input, expected) => {
 | 
			
		||||
      expect(convertRestQueryParams({ _start: input })).toMatchObject({
 | 
			
		||||
        start: expected,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('Limit param', () => {
 | 
			
		||||
    test('Throws on invalid params', () => {
 | 
			
		||||
      // invalid sort queries
 | 
			
		||||
      expect(() => convertRestQueryParams({ _limit: 'du text' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _limit: '12 du text' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _limit: '12.1' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _limit: 'NaN' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _limit: 'Infinity' })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _limit: Infinity })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _limit: -Infinity })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _limit: NaN })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _limit: 1.2 })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _limit: -10 })).toThrow();
 | 
			
		||||
      expect(() => convertRestQueryParams({ _limit: {} })).toThrow();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test.each([
 | 
			
		||||
      ['1', 1],
 | 
			
		||||
      ['12', 12],
 | 
			
		||||
      ['0', 0],
 | 
			
		||||
    ])('Converts start query "%s" correctly', (input, expected) => {
 | 
			
		||||
      expect(convertRestQueryParams({ _start: input })).toMatchObject({
 | 
			
		||||
        start: expected,
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('Publication State param', () => {
 | 
			
		||||
    test.each([
 | 
			
		||||
      { _publicationState: 'foobar' },
 | 
			
		||||
      { _publicationState: undefined },
 | 
			
		||||
      { _publicationState: null },
 | 
			
		||||
    ])('Throws on invalid params (%#)', params => {
 | 
			
		||||
      expect(() => convertRestQueryParams(params)).toThrow();
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test.each([
 | 
			
		||||
      ['Live Mode', { _publicationState: 'live' }],
 | 
			
		||||
      ['Preview Mode', { _publicationState: 'preview' }, []],
 | 
			
		||||
    ])('%s', (name, params) => {
 | 
			
		||||
      const result = convertRestQueryParams(params);
 | 
			
		||||
 | 
			
		||||
      expect(result._publicationState).toBeUndefined();
 | 
			
		||||
      expect(result.publicationState).toBe(params._publicationState);
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('Filters', () => {
 | 
			
		||||
    test('Can combine filters', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ id: '1', test_ne: 'text', test_: 'content' })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'id',
 | 
			
		||||
            operator: 'eq',
 | 
			
		||||
            value: '1',
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            field: 'test',
 | 
			
		||||
            operator: 'ne',
 | 
			
		||||
            value: 'text',
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            field: 'test_',
 | 
			
		||||
            operator: 'eq',
 | 
			
		||||
            value: 'content',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Ok', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ id: '1', test: 'text' })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'id',
 | 
			
		||||
            operator: 'eq',
 | 
			
		||||
            value: '1',
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            field: 'test',
 | 
			
		||||
            operator: 'eq',
 | 
			
		||||
            value: 'text',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      expect(convertRestQueryParams({ id_eq: '1', test_eq: 'text' })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'id',
 | 
			
		||||
            operator: 'eq',
 | 
			
		||||
            value: '1',
 | 
			
		||||
          },
 | 
			
		||||
          {
 | 
			
		||||
            field: 'test',
 | 
			
		||||
            operator: 'eq',
 | 
			
		||||
            value: 'text',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
 | 
			
		||||
      expect(convertRestQueryParams({ published_at: '2019-01-01:00:00:00' })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'published_at',
 | 
			
		||||
            operator: 'eq',
 | 
			
		||||
            value: '2019-01-01:00:00:00',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Not Eq', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ id_ne: 1 })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'id',
 | 
			
		||||
            operator: 'ne',
 | 
			
		||||
            value: 1,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Less than', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ id_lt: 1 })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'id',
 | 
			
		||||
            operator: 'lt',
 | 
			
		||||
            value: 1,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Less than or equal', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ id_lte: 1 })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'id',
 | 
			
		||||
            operator: 'lte',
 | 
			
		||||
            value: 1,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Greater than', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ id_gt: 1 })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'id',
 | 
			
		||||
            operator: 'gt',
 | 
			
		||||
            value: 1,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Greater than or equal', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ id_gte: 1 })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'id',
 | 
			
		||||
            operator: 'gte',
 | 
			
		||||
            value: 1,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('In', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ id_in: [1, 2] })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'id',
 | 
			
		||||
            operator: 'in',
 | 
			
		||||
            value: [1, 2],
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Not in', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ id_nin: [1, 3] })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'id',
 | 
			
		||||
            operator: 'nin',
 | 
			
		||||
            value: [1, 3],
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Contains', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ id_contains: 'text' })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'id',
 | 
			
		||||
            operator: 'contains',
 | 
			
		||||
            value: 'text',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Contains sensitive', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ id_containss: 'test' })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'id',
 | 
			
		||||
            operator: 'containss',
 | 
			
		||||
            value: 'test',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Not Contains', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ sub_title_ncontains: 'text' })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'sub_title',
 | 
			
		||||
            operator: 'ncontains',
 | 
			
		||||
            value: 'text',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Not Contains sensitive', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ content_text_ncontainss: 'test' })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'content_text',
 | 
			
		||||
            operator: 'ncontainss',
 | 
			
		||||
            value: 'test',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Not Contains sensitive', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ 'content.text_ncontainss': 'test' })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'content.text',
 | 
			
		||||
            operator: 'ncontainss',
 | 
			
		||||
            value: 'test',
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Null', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ 'content.text_null': true })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'content.text',
 | 
			
		||||
            operator: 'null',
 | 
			
		||||
            value: true,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    test('Not Null', () => {
 | 
			
		||||
      expect(convertRestQueryParams({ 'content.text_null': false })).toMatchObject({
 | 
			
		||||
        where: [
 | 
			
		||||
          {
 | 
			
		||||
            field: 'content.text',
 | 
			
		||||
            operator: 'null',
 | 
			
		||||
            value: false,
 | 
			
		||||
          },
 | 
			
		||||
        ],
 | 
			
		||||
      });
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										164
									
								
								packages/core/utils/lib/convert-query-params.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										164
									
								
								packages/core/utils/lib/convert-query-params.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,164 @@
 | 
			
		||||
'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,
 | 
			
		||||
};
 | 
			
		||||
@ -1,312 +0,0 @@
 | 
			
		||||
'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 {
 | 
			
		||||
  constants: { DP_PUB_STATES },
 | 
			
		||||
} = require('./content-types');
 | 
			
		||||
 | 
			
		||||
const BOOLEAN_OPERATORS = ['or', 'and'];
 | 
			
		||||
const QUERY_OPERATORS = ['_where', '_or', '_and'];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Global converter
 | 
			
		||||
 * @param {Object} params
 | 
			
		||||
 * @param defaults
 | 
			
		||||
 */
 | 
			
		||||
const convertRestQueryParams = (params = {}, defaults = {}) => {
 | 
			
		||||
  if (typeof params !== 'object' || params === null) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      `convertRestQueryParams expected an object got ${params === null ? 'null' : typeof params}`
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let finalParams = Object.assign({ start: 0, limit: 100 }, defaults);
 | 
			
		||||
 | 
			
		||||
  if (Object.keys(params).length === 0) {
 | 
			
		||||
    return finalParams;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (_.has(params, 'sort')) {
 | 
			
		||||
    finalParams.sort = convertSortQueryParams(params.sort);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (_.has(params, '_start')) {
 | 
			
		||||
    finalParams.start = convertStartQueryParams(params._start);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (_.has(params, '_limit')) {
 | 
			
		||||
    finalParams.limit = convertLimitQueryParams(params._limit);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (_.has(params, '_publicationState')) {
 | 
			
		||||
    Object.assign(finalParams, convertPublicationStateParams(params._publicationState));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const whereParams = convertExtraRootParams(
 | 
			
		||||
    _.omit(params, ['sort', '_start', '_limit', '_where', '_publicationState'])
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  const whereClauses = [];
 | 
			
		||||
 | 
			
		||||
  if (_.keys(whereParams).length > 0) {
 | 
			
		||||
    whereClauses.push(...convertWhereParams(whereParams));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (_.has(params, '_where')) {
 | 
			
		||||
    whereClauses.push(...convertWhereParams(params._where));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Object.assign(finalParams, { where: whereClauses });
 | 
			
		||||
 | 
			
		||||
  return finalParams;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Convert params prefixed with _ by removing the prefix after we have handle the internal params
 | 
			
		||||
 * NOTE: This is only a temporary patch for v3 to handle extra params coming from plugins
 | 
			
		||||
 * @param {object} params
 | 
			
		||||
 * @returns {object}
 | 
			
		||||
 */
 | 
			
		||||
const convertExtraRootParams = params => {
 | 
			
		||||
  return Object.entries(params).reduce((acc, [key, value]) => {
 | 
			
		||||
    if (_.startsWith(key, '_') && !QUERY_OPERATORS.includes(key)) {
 | 
			
		||||
      acc[key.slice(1)] = value;
 | 
			
		||||
    } else {
 | 
			
		||||
      acc[key] = value;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return acc;
 | 
			
		||||
  }, {});
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * 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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * publicationState query parser
 | 
			
		||||
 * @param {string} publicationState - eg: 'live', 'preview'
 | 
			
		||||
 */
 | 
			
		||||
const convertPublicationStateParams = publicationState => {
 | 
			
		||||
  if (!DP_PUB_STATES.includes(publicationState)) {
 | 
			
		||||
    throw new Error(
 | 
			
		||||
      `convertPublicationStateParams expected a value from: ${DP_PUB_STATES.join(
 | 
			
		||||
        ', '
 | 
			
		||||
      )}. Got ${publicationState} instead`
 | 
			
		||||
    );
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return { publicationState };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// TODO: 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');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const convertNestedPopulate = subPopulate => {
 | 
			
		||||
  if (subPopulate === '*') {
 | 
			
		||||
    return true;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // if it isn't an object returns false ?
 | 
			
		||||
 | 
			
		||||
  const { sort, filters, /*start, limit,*/ fields, populate } = subPopulate;
 | 
			
		||||
 | 
			
		||||
  const query = {};
 | 
			
		||||
  // if (start) {
 | 
			
		||||
  //   query.offset = convertStartQueryParams(start);
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  // if (limit) {
 | 
			
		||||
  //   query.limit = convertLimitQueryParams(limit);
 | 
			
		||||
  // }
 | 
			
		||||
 | 
			
		||||
  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;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const convertFiltersQueryParams = filters => filters;
 | 
			
		||||
 | 
			
		||||
// List of all the possible filters
 | 
			
		||||
const VALID_REST_OPERATORS = [
 | 
			
		||||
  'eq',
 | 
			
		||||
  'ne',
 | 
			
		||||
  'in',
 | 
			
		||||
  'nin',
 | 
			
		||||
  'contains',
 | 
			
		||||
  'ncontains',
 | 
			
		||||
  'containss',
 | 
			
		||||
  'ncontainss',
 | 
			
		||||
  'lt',
 | 
			
		||||
  'lte',
 | 
			
		||||
  'gt',
 | 
			
		||||
  'gte',
 | 
			
		||||
  'null',
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Parse where params
 | 
			
		||||
 */
 | 
			
		||||
const convertWhereParams = whereParams => {
 | 
			
		||||
  let finalWhere = [];
 | 
			
		||||
 | 
			
		||||
  if (Array.isArray(whereParams)) {
 | 
			
		||||
    return whereParams.reduce((acc, whereParam) => {
 | 
			
		||||
      return acc.concat(convertWhereParams(whereParam));
 | 
			
		||||
    }, []);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  Object.keys(whereParams).forEach(whereClause => {
 | 
			
		||||
    const { field, operator = 'eq', value } = convertWhereClause(
 | 
			
		||||
      whereClause,
 | 
			
		||||
      whereParams[whereClause]
 | 
			
		||||
    );
 | 
			
		||||
 | 
			
		||||
    finalWhere.push({
 | 
			
		||||
      field,
 | 
			
		||||
      operator,
 | 
			
		||||
      value,
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  return finalWhere;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Parse single where param
 | 
			
		||||
 * @param {string} whereClause - Any possible where clause e.g: id_ne text_ncontains
 | 
			
		||||
 * @param {string} value - the value of the where clause e.g id_ne=value
 | 
			
		||||
 */
 | 
			
		||||
const convertWhereClause = (whereClause, value) => {
 | 
			
		||||
  const separatorIndex = whereClause.lastIndexOf('_');
 | 
			
		||||
 | 
			
		||||
  // eq operator
 | 
			
		||||
  if (separatorIndex === -1) {
 | 
			
		||||
    return { field: whereClause, value };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // split field and operator
 | 
			
		||||
  const field = whereClause.substring(0, separatorIndex);
 | 
			
		||||
  const operator = whereClause.slice(separatorIndex + 1);
 | 
			
		||||
 | 
			
		||||
  if (BOOLEAN_OPERATORS.includes(operator) && field === '') {
 | 
			
		||||
    return { field: null, operator, value: [].concat(value).map(convertWhereParams) };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // the field as underscores
 | 
			
		||||
  if (!VALID_REST_OPERATORS.includes(operator)) {
 | 
			
		||||
    return { field: whereClause, value };
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return { field, operator, value };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
module.exports = {
 | 
			
		||||
  convertRestQueryParams,
 | 
			
		||||
  convertSortQueryParams,
 | 
			
		||||
  convertStartQueryParams,
 | 
			
		||||
  convertLimitQueryParams,
 | 
			
		||||
  convertPopulateQueryParams,
 | 
			
		||||
  convertFiltersQueryParams,
 | 
			
		||||
  VALID_REST_OPERATORS,
 | 
			
		||||
  QUERY_OPERATORS,
 | 
			
		||||
};
 | 
			
		||||
@ -4,11 +4,7 @@
 | 
			
		||||
 * Export shared utilities
 | 
			
		||||
 */
 | 
			
		||||
const { buildQuery, hasDeepFilters } = require('./build-query');
 | 
			
		||||
const {
 | 
			
		||||
  convertRestQueryParams,
 | 
			
		||||
  VALID_REST_OPERATORS,
 | 
			
		||||
  QUERY_OPERATORS,
 | 
			
		||||
} = require('./convert-rest-query-params');
 | 
			
		||||
const { VALID_REST_OPERATORS, QUERY_OPERATORS } = require('./convert-query-params');
 | 
			
		||||
const parseMultipartData = require('./parse-multipart');
 | 
			
		||||
const sanitizeEntity = require('./sanitize-entity');
 | 
			
		||||
const parseType = require('./parse-type');
 | 
			
		||||
@ -41,7 +37,6 @@ module.exports = {
 | 
			
		||||
  formatYupErrors,
 | 
			
		||||
  policy,
 | 
			
		||||
  templateConfiguration,
 | 
			
		||||
  convertRestQueryParams,
 | 
			
		||||
  VALID_REST_OPERATORS,
 | 
			
		||||
  QUERY_OPERATORS,
 | 
			
		||||
  buildQuery,
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user