mirror of
https://github.com/strapi/strapi.git
synced 2025-10-26 23:51:10 +00:00
Init very simple filters system for the query layer to implement publication state filtering
This commit is contained in:
parent
435ce3bc88
commit
9dd2824824
@ -142,7 +142,7 @@ const createEntityManager = db => {
|
|||||||
await db.lifecycles.run('beforeCount', uid, { params });
|
await db.lifecycles.run('beforeCount', uid, { params });
|
||||||
|
|
||||||
const res = await this.createQueryBuilder(uid)
|
const res = await this.createQueryBuilder(uid)
|
||||||
.init(_.pick(['_q', 'where'], params))
|
.init(_.pick(['_q', 'where', 'filters'], params))
|
||||||
.count()
|
.count()
|
||||||
.first()
|
.first()
|
||||||
.execute();
|
.execute();
|
||||||
@ -172,7 +172,7 @@ const createEntityManager = db => {
|
|||||||
|
|
||||||
await this.attachRelations(uid, id, data);
|
await this.attachRelations(uid, id, data);
|
||||||
|
|
||||||
// TODO: in case there is not select or populate specified return the inserted data ?
|
// TODO: in case there is no select or populate specified return the inserted data ?
|
||||||
// TODO: do not trigger the findOne lifecycles ?
|
// TODO: do not trigger the findOne lifecycles ?
|
||||||
const result = await this.findOne(uid, {
|
const result = await this.findOne(uid, {
|
||||||
where: { id },
|
where: { id },
|
||||||
@ -296,7 +296,7 @@ const createEntityManager = db => {
|
|||||||
throw new Error('Delete requires a where parameter');
|
throw new Error('Delete requires a where parameter');
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: avoid trigger the findOne lifecycles in the case ?
|
// TODO: do not trigger the findOne lifecycles ?
|
||||||
const entity = await this.findOne(uid, {
|
const entity = await this.findOne(uid, {
|
||||||
select: select && ['id'].concat(select),
|
select: select && ['id'].concat(select),
|
||||||
where,
|
where,
|
||||||
@ -469,7 +469,6 @@ const createEntityManager = db => {
|
|||||||
const { joinTable } = attribute;
|
const { joinTable } = attribute;
|
||||||
const { joinColumn, inverseJoinColumn } = joinTable;
|
const { joinColumn, inverseJoinColumn } = joinTable;
|
||||||
|
|
||||||
// TODO: validate logic of delete
|
|
||||||
if (isOneToAny(attribute) && isBidirectional(attribute)) {
|
if (isOneToAny(attribute) && isBidirectional(attribute)) {
|
||||||
await this.createQueryBuilder(joinTable.name)
|
await this.createQueryBuilder(joinTable.name)
|
||||||
.delete()
|
.delete()
|
||||||
|
|||||||
@ -122,7 +122,11 @@ const applyPopulate = async (results, populate, ctx) => {
|
|||||||
const attribute = meta.attributes[key];
|
const attribute = meta.attributes[key];
|
||||||
const targetMeta = db.metadata.get(attribute.target);
|
const targetMeta = db.metadata.get(attribute.target);
|
||||||
|
|
||||||
const populateValue = pickPopulateParams(populate[key]);
|
const populateValue = {
|
||||||
|
...pickPopulateParams(populate[key]),
|
||||||
|
filters: qb.state.filters,
|
||||||
|
};
|
||||||
|
|
||||||
const isCount = populateValue.count === true;
|
const isCount = populateValue.count === true;
|
||||||
|
|
||||||
const fromTargetRow = rowOrRows => fromRow(targetMeta, rowOrRows);
|
const fromTargetRow = rowOrRows => fromRow(targetMeta, rowOrRows);
|
||||||
|
|||||||
@ -29,6 +29,7 @@ const createQueryBuilder = (uid, db) => {
|
|||||||
return {
|
return {
|
||||||
alias: getAlias(),
|
alias: getAlias(),
|
||||||
getAlias,
|
getAlias,
|
||||||
|
state,
|
||||||
|
|
||||||
select(args) {
|
select(args) {
|
||||||
state.type = 'select';
|
state.type = 'select';
|
||||||
@ -115,7 +116,7 @@ const createQueryBuilder = (uid, db) => {
|
|||||||
},
|
},
|
||||||
|
|
||||||
init(params = {}) {
|
init(params = {}) {
|
||||||
const { _q, where, select, limit, offset, orderBy, groupBy, populate } = params;
|
const { _q, filters, where, select, limit, offset, orderBy, groupBy, populate } = params;
|
||||||
|
|
||||||
if (!_.isNil(where)) {
|
if (!_.isNil(where)) {
|
||||||
this.where(where);
|
this.where(where);
|
||||||
@ -151,9 +152,17 @@ const createQueryBuilder = (uid, db) => {
|
|||||||
this.populate(populate);
|
this.populate(populate);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!_.isNil(filters)) {
|
||||||
|
this.filters(filters);
|
||||||
|
}
|
||||||
|
|
||||||
return this;
|
return this;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
filters(filters) {
|
||||||
|
state.filters = filters;
|
||||||
|
},
|
||||||
|
|
||||||
first() {
|
first() {
|
||||||
state.first = true;
|
state.first = true;
|
||||||
return this;
|
return this;
|
||||||
@ -206,6 +215,19 @@ const createQueryBuilder = (uid, db) => {
|
|||||||
|
|
||||||
processState() {
|
processState() {
|
||||||
state.orderBy = helpers.processOrderBy(state.orderBy, { qb: this, uid, db });
|
state.orderBy = helpers.processOrderBy(state.orderBy, { qb: this, uid, db });
|
||||||
|
|
||||||
|
if (!_.isNil(state.filters)) {
|
||||||
|
if (_.isFunction(state.filters)) {
|
||||||
|
const filters = state.filters({ qb: this, uid, meta, db });
|
||||||
|
|
||||||
|
if (!_.isNil(filters)) {
|
||||||
|
state.where.push(filters);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
state.where.push(state.filters);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
state.where = helpers.processWhere(state.where, { qb: this, uid, db });
|
state.where = helpers.processWhere(state.where, { qb: this, uid, db });
|
||||||
state.populate = helpers.processPopulate(state.populate, { qb: this, uid, db });
|
state.populate = helpers.processPopulate(state.populate, { qb: this, uid, db });
|
||||||
state.data = helpers.toRow(meta, state.data);
|
state.data = helpers.toRow(meta, state.data);
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const delegate = require('delegates');
|
const delegate = require('delegates');
|
||||||
const { pipe } = require('lodash/fp');
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
sanitizeEntity,
|
sanitizeEntity,
|
||||||
@ -16,12 +15,7 @@ const {
|
|||||||
updateComponents,
|
updateComponents,
|
||||||
deleteComponents,
|
deleteComponents,
|
||||||
} = require('./components');
|
} = require('./components');
|
||||||
const {
|
const { transformParamsToQuery, pickSelectionParams } = require('./params');
|
||||||
transformCommonParams,
|
|
||||||
transformPaginationParams,
|
|
||||||
transformParamsToQuery,
|
|
||||||
pickSelectionParams,
|
|
||||||
} = require('./params');
|
|
||||||
|
|
||||||
// TODO: those should be strapi events used by the webhooks not the other way arround
|
// TODO: those should be strapi events used by the webhooks not the other way arround
|
||||||
const { ENTRY_CREATE, ENTRY_UPDATE, ENTRY_DELETE } = webhookUtils.webhookEvents;
|
const { ENTRY_CREATE, ENTRY_UPDATE, ENTRY_DELETE } = webhookUtils.webhookEvents;
|
||||||
@ -231,13 +225,34 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|||||||
|
|
||||||
const attribute = attributes[field];
|
const attribute = attributes[field];
|
||||||
|
|
||||||
const loadParams =
|
const loadParams = {};
|
||||||
attribute.type === 'relation'
|
|
||||||
? transformParamsToQuery(attribute.target, params)
|
switch (attribute.type) {
|
||||||
: pipe(
|
case 'relation': {
|
||||||
transformCommonParams,
|
Object.assign(loadParams, transformParamsToQuery(attribute.target, params));
|
||||||
transformPaginationParams
|
break;
|
||||||
)(params);
|
}
|
||||||
|
case 'component': {
|
||||||
|
Object.assign(loadParams, transformParamsToQuery(attribute.component, params));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'dynamiczone': {
|
||||||
|
Object.assign(loadParams, transformParamsToQuery(null, params));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'media': {
|
||||||
|
Object.assign(loadParams, transformParamsToQuery('plugin::upload.file', params));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// const loadParams =
|
||||||
|
// attribute.type === 'relation'
|
||||||
|
// ? transformParamsToQuery(attribute.target, params)
|
||||||
|
// : pipe(
|
||||||
|
// transformCommonParams,
|
||||||
|
// transformPaginationParams
|
||||||
|
// )(params);
|
||||||
|
|
||||||
return db.query(uid).load(entity, field, loadParams);
|
return db.query(uid).load(entity, field, loadParams);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const { pick, pipe, isNil } = require('lodash/fp');
|
const assert = require('assert').strict;
|
||||||
|
const { pick, isNil, toNumber, isInteger } = require('lodash/fp');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
convertSortQueryParams,
|
convertSortQueryParams,
|
||||||
@ -9,41 +10,39 @@ const {
|
|||||||
convertPopulateQueryParams,
|
convertPopulateQueryParams,
|
||||||
convertFiltersQueryParams,
|
convertFiltersQueryParams,
|
||||||
convertFieldsQueryParams,
|
convertFieldsQueryParams,
|
||||||
|
convertPublicationStateParams,
|
||||||
} = require('@strapi/utils/lib/convert-query-params');
|
} = require('@strapi/utils/lib/convert-query-params');
|
||||||
|
|
||||||
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
|
const pickSelectionParams = pick(['fields', 'populate']);
|
||||||
|
|
||||||
const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
|
const transformParamsToQuery = (uid, params) => {
|
||||||
|
// NOTE: can be a CT, a Compo or nothing in the case of polymorphism (DZ & morph relations)
|
||||||
|
const type = strapi.getModel(uid);
|
||||||
|
|
||||||
const transformCommonParams = (params = {}) => {
|
const query = {};
|
||||||
const { _q, sort, filters, fields, populate, ...query } = params;
|
|
||||||
|
|
||||||
if (_q) {
|
const { _q, sort, filters, fields, populate, page, pageSize, start, limit } = params;
|
||||||
|
|
||||||
|
if (!isNil(_q)) {
|
||||||
query._q = _q;
|
query._q = _q;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sort) {
|
if (!isNil(sort)) {
|
||||||
query.orderBy = convertSortQueryParams(sort);
|
query.orderBy = convertSortQueryParams(sort);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (filters) {
|
if (!isNil(filters)) {
|
||||||
query.where = convertFiltersQueryParams(filters);
|
query.where = convertFiltersQueryParams(filters);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fields) {
|
if (!isNil(fields)) {
|
||||||
query.select = convertFieldsQueryParams(fields);
|
query.select = convertFieldsQueryParams(fields);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (populate) {
|
if (!isNil(populate)) {
|
||||||
query.populate = convertPopulateQueryParams(populate);
|
query.populate = convertPopulateQueryParams(populate);
|
||||||
}
|
}
|
||||||
|
|
||||||
return query;
|
|
||||||
};
|
|
||||||
|
|
||||||
const transformPaginationParams = (params = {}) => {
|
|
||||||
const { page, pageSize, start, limit, ...query } = params;
|
|
||||||
|
|
||||||
const isPagePagination = !isNil(page) || !isNil(pageSize);
|
const isPagePagination = !isNil(page) || !isNil(pageSize);
|
||||||
const isOffsetPagination = !isNil(start) || !isNil(limit);
|
const isOffsetPagination = !isNil(start) || !isNil(limit);
|
||||||
|
|
||||||
@ -53,72 +52,38 @@ const transformPaginationParams = (params = {}) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (page) {
|
if (!isNil(page)) {
|
||||||
query.page = Number(page);
|
const pageVal = toNumber(page);
|
||||||
|
const isValid = isInteger(pageVal) && pageVal > 0;
|
||||||
|
|
||||||
|
assert(isValid, `Invalid 'page' parameter. Expected an integer > 0, received: ${page}`);
|
||||||
|
|
||||||
|
query.page = pageVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (pageSize) {
|
if (!isNil(pageSize)) {
|
||||||
query.pageSize = Number(pageSize);
|
const pageSizeVal = toNumber(pageSize);
|
||||||
|
const isValid = isInteger(pageSizeVal) && pageSizeVal > 0;
|
||||||
|
|
||||||
|
assert(isValid, `Invalid 'pageSize' parameter. Expected an integer > 0, received: ${page}`);
|
||||||
|
|
||||||
|
query.pageSize = pageSizeVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (start) {
|
if (!isNil(start)) {
|
||||||
query.offset = convertStartQueryParams(start);
|
query.offset = convertStartQueryParams(start);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (limit) {
|
if (!isNil(limit)) {
|
||||||
query.limit = convertLimitQueryParams(limit);
|
query.limit = convertLimitQueryParams(limit);
|
||||||
}
|
}
|
||||||
|
|
||||||
return query;
|
convertPublicationStateParams(type, params, query);
|
||||||
};
|
|
||||||
|
|
||||||
const transformPublicationStateParams = uid => (params = {}) => {
|
|
||||||
const contentType = strapi.getModel(uid);
|
|
||||||
|
|
||||||
if (!contentType) {
|
|
||||||
return params;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { publicationState, ...query } = params;
|
|
||||||
|
|
||||||
if (publicationState && contentTypesUtils.hasDraftAndPublish(contentType)) {
|
|
||||||
const { publicationState = 'live' } = params;
|
|
||||||
|
|
||||||
const liveClause = {
|
|
||||||
[PUBLISHED_AT_ATTRIBUTE]: {
|
|
||||||
$notNull: true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
if (publicationState === 'live') {
|
|
||||||
query.where = {
|
|
||||||
$and: [liveClause].concat(query.where || []),
|
|
||||||
};
|
|
||||||
|
|
||||||
// TODO: propagate nested publicationState filter somehow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return query;
|
return query;
|
||||||
};
|
};
|
||||||
|
|
||||||
const pickSelectionParams = pick(['fields', 'populate']);
|
|
||||||
|
|
||||||
const transformParamsToQuery = (uid, params) => {
|
|
||||||
return pipe(
|
|
||||||
// _q, filters, etc...
|
|
||||||
transformCommonParams,
|
|
||||||
// page, pageSize, start, limit
|
|
||||||
transformPaginationParams,
|
|
||||||
// publicationState
|
|
||||||
transformPublicationStateParams(uid)
|
|
||||||
)(params);
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
transformCommonParams,
|
|
||||||
transformPublicationStateParams,
|
|
||||||
transformPaginationParams,
|
|
||||||
transformParamsToQuery,
|
transformParamsToQuery,
|
||||||
pickSelectionParams,
|
pickSelectionParams,
|
||||||
};
|
};
|
||||||
|
|||||||
@ -171,6 +171,7 @@ describe('Publication State', () => {
|
|||||||
const res = await rq({ method: 'GET', url: `${baseUrl}${query}` });
|
const res = await rq({ method: 'GET', url: `${baseUrl}${query}` });
|
||||||
|
|
||||||
expect(res.body.data).toHaveLength(lengthFor(modelName, { mode }));
|
expect(res.body.data).toHaveLength(lengthFor(modelName, { mode }));
|
||||||
|
|
||||||
expect(res.body.meta.pagination.total).toBe(lengthFor(modelName, { mode }));
|
expect(res.body.meta.pagination.total).toBe(lengthFor(modelName, { mode }));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -190,7 +191,7 @@ describe('Publication State', () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
products = res.body.data.map(res => ({ id: res.id, ...res.attributes }));
|
products = res.body.data;
|
||||||
});
|
});
|
||||||
|
|
||||||
test('Payload integrity', () => {
|
test('Payload integrity', () => {
|
||||||
@ -199,35 +200,29 @@ describe('Publication State', () => {
|
|||||||
|
|
||||||
test('Root level', () => {
|
test('Root level', () => {
|
||||||
products.forEach(product => {
|
products.forEach(product => {
|
||||||
expect(product.publishedAt).toBeISODate();
|
expect(product.attributes.publishedAt).toBeISODate();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// const getApiRef = id => data.product.find(product => product.id === id);
|
test('First level (categories) to be published only', () => {
|
||||||
|
products.forEach(({ attributes }) => {
|
||||||
|
const categories = attributes.categories.data;
|
||||||
|
|
||||||
test.todo('First level (categories)');
|
categories.forEach(category => {
|
||||||
|
expect(category.attributes.publishedAt).toBeISODate();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
// products.forEach(({ id, categories }) => {
|
test('Second level through component (countries) to be published only', () => {
|
||||||
// const length = getApiRef(id).categories.filter(c => c.publishedAt !== null).length;
|
products.forEach(({ attributes }) => {
|
||||||
// expect(categories).toHaveLength(length);
|
const countries = attributes.comp.countries.data;
|
||||||
|
|
||||||
// categories.forEach(category => {
|
countries.forEach(country => {
|
||||||
// expect(category.publishedAt).toBeISODate();
|
expect(country.attributes.publishedAt).toBeISODate();
|
||||||
// });
|
});
|
||||||
// });
|
});
|
||||||
// });
|
});
|
||||||
|
|
||||||
test.todo('Second level through component (countries)');
|
|
||||||
|
|
||||||
// products.forEach(({ id, comp: { countries } }) => {
|
|
||||||
// const length = getApiRef(id).comp.countries.filter(c => c.publishedAt !== null).length;
|
|
||||||
// expect(countries).toHaveLength(length);
|
|
||||||
|
|
||||||
// countries.forEach(country => {
|
|
||||||
// expect(country.publishedAt).toBeISODate();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -4,9 +4,12 @@
|
|||||||
* Converts the standard Strapi REST query params to a more usable format for querying
|
* 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
|
* You can read more here: https://strapi.io/documentation/developer-docs/latest/developer-resources/content-api/content-api.html#filters
|
||||||
*/
|
*/
|
||||||
|
const { has } = require('lodash/fp');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
const parseType = require('./parse-type');
|
const parseType = require('./parse-type');
|
||||||
|
const contentTypesUtils = require('./content-types');
|
||||||
|
|
||||||
|
const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
|
||||||
|
|
||||||
class InvalidOrderError extends Error {
|
class InvalidOrderError extends Error {
|
||||||
constructor() {
|
constructor() {
|
||||||
@ -130,13 +133,15 @@ const convertPopulateQueryParams = (populate, depth = 0) => {
|
|||||||
|
|
||||||
if (Array.isArray(populate)) {
|
if (Array.isArray(populate)) {
|
||||||
// map convert
|
// map convert
|
||||||
return populate.flatMap(value => {
|
return _.uniq(
|
||||||
if (typeof value !== 'string') {
|
populate.flatMap(value => {
|
||||||
throw new InvalidPopulateError();
|
if (typeof value !== 'string') {
|
||||||
}
|
throw new InvalidPopulateError();
|
||||||
|
}
|
||||||
|
|
||||||
return value.split(',').map(value => _.trim(value));
|
return value.split(',').map(value => _.trim(value));
|
||||||
});
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_.isPlainObject(populate)) {
|
if (_.isPlainObject(populate)) {
|
||||||
@ -144,6 +149,7 @@ const convertPopulateQueryParams = (populate, depth = 0) => {
|
|||||||
for (const key in populate) {
|
for (const key in populate) {
|
||||||
transformedPopulate[key] = convertNestedPopulate(populate[key]);
|
transformedPopulate[key] = convertNestedPopulate(populate[key]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return transformedPopulate;
|
return transformedPopulate;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,9 +216,31 @@ const convertFieldsQueryParams = (fields, depth = 0) => {
|
|||||||
throw new Error('Invalid fields parameter. Expected a string or an array of strings');
|
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;
|
const convertFiltersQueryParams = filters => filters;
|
||||||
|
|
||||||
|
const convertPublicationStateParams = (type, params = {}, query = {}) => {
|
||||||
|
if (!type) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { publicationState } = params;
|
||||||
|
|
||||||
|
if (!_.isNil(publicationState)) {
|
||||||
|
if (!contentTypesUtils.constants.DP_PUB_STATES.includes(publicationState)) {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid publicationState. Expected one of 'preview','live' received: ${publicationState}.`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// NOTE: this is the query layer filters not the entity service filters
|
||||||
|
query.filters = ({ meta }) => {
|
||||||
|
if (publicationState === 'live' && has(PUBLISHED_AT_ATTRIBUTE, meta.attributes)) {
|
||||||
|
return { [PUBLISHED_AT_ATTRIBUTE]: { $notNull: true } };
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
convertSortQueryParams,
|
convertSortQueryParams,
|
||||||
convertStartQueryParams,
|
convertStartQueryParams,
|
||||||
@ -220,4 +248,5 @@ module.exports = {
|
|||||||
convertPopulateQueryParams,
|
convertPopulateQueryParams,
|
||||||
convertFiltersQueryParams,
|
convertFiltersQueryParams,
|
||||||
convertFieldsQueryParams,
|
convertFieldsQueryParams,
|
||||||
|
convertPublicationStateParams,
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user