mirror of
https://github.com/strapi/strapi.git
synced 2025-11-25 22:51:33 +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';
|
'use strict';
|
||||||
|
|
||||||
|
// TODO: migration
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const { rulesToQuery } = require('@casl/ability/extra');
|
const { rulesToQuery } = require('@casl/ability/extra');
|
||||||
const { VALID_REST_OPERATORS } = require('@strapi/utils');
|
const { VALID_REST_OPERATORS } = require('@strapi/utils');
|
||||||
|
|||||||
@ -9,7 +9,7 @@ const {
|
|||||||
convertStartQueryParams,
|
convertStartQueryParams,
|
||||||
convertPopulateQueryParams,
|
convertPopulateQueryParams,
|
||||||
convertFiltersQueryParams,
|
convertFiltersQueryParams,
|
||||||
} = require('@strapi/utils/lib/convert-rest-query-params');
|
} = require('@strapi/utils/lib/convert-query-params');
|
||||||
|
|
||||||
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
|
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ const transformParamsToQuery = (uid, params = {}) => {
|
|||||||
|
|
||||||
const query = {};
|
const query = {};
|
||||||
|
|
||||||
// TODO: check invalid values add defaults ....
|
// TODO: check invalid values / add defaults ....
|
||||||
|
|
||||||
const {
|
const {
|
||||||
start,
|
start,
|
||||||
@ -79,7 +79,7 @@ const transformParamsToQuery = (uid, params = {}) => {
|
|||||||
query.populate = convertPopulateQueryParams(populate);
|
query.populate = convertPopulateQueryParams(populate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: move to layer above ?
|
// TODO: move to convert-query-params ?
|
||||||
if (publicationState && contentTypesUtils.hasDraftAndPublish(model)) {
|
if (publicationState && contentTypesUtils.hasDraftAndPublish(model)) {
|
||||||
const { publicationState = 'live' } = params;
|
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
|
* Export shared utilities
|
||||||
*/
|
*/
|
||||||
const { buildQuery, hasDeepFilters } = require('./build-query');
|
const { buildQuery, hasDeepFilters } = require('./build-query');
|
||||||
const {
|
const { VALID_REST_OPERATORS, QUERY_OPERATORS } = require('./convert-query-params');
|
||||||
convertRestQueryParams,
|
|
||||||
VALID_REST_OPERATORS,
|
|
||||||
QUERY_OPERATORS,
|
|
||||||
} = require('./convert-rest-query-params');
|
|
||||||
const parseMultipartData = require('./parse-multipart');
|
const parseMultipartData = require('./parse-multipart');
|
||||||
const sanitizeEntity = require('./sanitize-entity');
|
const sanitizeEntity = require('./sanitize-entity');
|
||||||
const parseType = require('./parse-type');
|
const parseType = require('./parse-type');
|
||||||
@ -41,7 +37,6 @@ module.exports = {
|
|||||||
formatYupErrors,
|
formatYupErrors,
|
||||||
policy,
|
policy,
|
||||||
templateConfiguration,
|
templateConfiguration,
|
||||||
convertRestQueryParams,
|
|
||||||
VALID_REST_OPERATORS,
|
VALID_REST_OPERATORS,
|
||||||
QUERY_OPERATORS,
|
QUERY_OPERATORS,
|
||||||
buildQuery,
|
buildQuery,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user