diff --git a/packages/core/content-manager/server/controllers/relations.js b/packages/core/content-manager/server/controllers/relations.js index 8f30fd27db..c5f7d9123c 100644 --- a/packages/core/content-manager/server/controllers/relations.js +++ b/packages/core/content-manager/server/controllers/relations.js @@ -1,13 +1,22 @@ 'use strict'; -const { prop, isEmpty, isNil } = require('lodash/fp'); -const { convertFiltersQueryParams } = require('@strapi/utils/lib/convert-query-params'); +const { prop, isEmpty, defaultsDeep } = require('lodash/fp'); const { hasDraftAndPublish } = require('@strapi/utils').contentTypes; const { PUBLISHED_AT_ATTRIBUTE } = require('@strapi/utils').contentTypes.constants; +const { transformParamsToQuery } = require('@strapi/utils/lib/convert-query-params'); const { getService } = require('../utils'); const { validateFindAvailable } = require('./validation/relations'); +const addWhereClause = (params, whereClause) => { + params.where = params.where || {}; + if (Array.isArray(params.where)) { + params.where.push(whereClause); + } else { + params.where = [params.where, whereClause]; + } +}; + module.exports = { async findAvailable(ctx) { const { userAbility } = ctx.state; @@ -15,7 +24,7 @@ module.exports = { await validateFindAvailable(ctx.request.query); - const { component, entityId, idsToOmit, page = 1, pageSize = 10, _q } = ctx.request.query; + const { component, entityId, idsToOmit, ...query } = ctx.request.query; const sourceModelUid = component || model; @@ -64,26 +73,18 @@ module.exports = { fieldsToSelect.push(PUBLISHED_AT_ATTRIBUTE); } - const queryParams = { - where: { $and: [] }, - select: fieldsToSelect, - orderBy: mainField, - page, - pageSize, - }; - - if (!isNil(_q)) { - queryParams._q = _q; - } - - if (!isNil(ctx.request.query.filters)) { - queryParams.where.$and.push( - convertFiltersQueryParams(ctx.request.query.filters, targetedModel).filters - ); - } + const queryParams = defaultsDeep( + { + orderBy: mainField, + }, + { + ...transformParamsToQuery(targetedModel.uid, query), // ⚠️ Mmmh should not be able to filter for RBAC reasons + select: fieldsToSelect, // cannot select other fields as the user may not have the permissions + } + ); if (!isEmpty(idsToOmit)) { - queryParams.where.$and.push({ id: { $notIn: idsToOmit } }); + addWhereClause(queryParams, { id: { $notIn: idsToOmit } }); } if (entityId) { @@ -97,7 +98,7 @@ module.exports = { .select(`${alias}.id`) .getKnexQuery(); - queryParams.where.$and.push({ id: { $notIn: knexSubQuery } }); + addWhereClause(queryParams, { id: { $notIn: knexSubQuery } }); } const results = await strapi.query(targetedModel.uid).findPage(queryParams); diff --git a/packages/core/strapi/lib/services/entity-service/index.js b/packages/core/strapi/lib/services/entity-service/index.js index f5bdffdb68..c21cf9ec49 100644 --- a/packages/core/strapi/lib/services/entity-service/index.js +++ b/packages/core/strapi/lib/services/entity-service/index.js @@ -10,6 +10,7 @@ const { sanitize, } = require('@strapi/utils'); const { ValidationError } = require('@strapi/utils').errors; +const { transformParamsToQuery } = require('@strapi/utils/lib/convert-query-params'); const uploadFiles = require('../utils/upload-files'); const { @@ -18,7 +19,7 @@ const { updateComponents, deleteComponents, } = require('./components'); -const { transformParamsToQuery, pickSelectionParams } = require('./params'); +const { pickSelectionParams } = require('./params'); const { applyTransforms } = require('./attributes'); // TODO: those should be strapi events used by the webhooks not the other way arround diff --git a/packages/core/strapi/lib/services/entity-service/params.js b/packages/core/strapi/lib/services/entity-service/params.js index 27c138c32c..44e2ff461f 100644 --- a/packages/core/strapi/lib/services/entity-service/params.js +++ b/packages/core/strapi/lib/services/entity-service/params.js @@ -1,95 +1,9 @@ 'use strict'; -const { pick, isNil, toNumber, isInteger } = require('lodash/fp'); -const { PaginationError } = require('@strapi/utils').errors; - -const { - convertSortQueryParams, - convertLimitQueryParams, - convertStartQueryParams, - convertPopulateQueryParams, - convertFiltersQueryParams, - convertFieldsQueryParams, - convertPublicationStateParams, -} = require('@strapi/utils/lib/convert-query-params'); +const { pick } = require('lodash/fp'); const pickSelectionParams = pick(['fields', 'populate']); -const transformParamsToQuery = (uid, params) => { - // NOTE: can be a CT, a Compo or nothing in the case of polymorphism (DZ & morph relations) - const schema = strapi.getModel(uid); - - const query = {}; - - const { _q, sort, filters, fields, populate, page, pageSize, start, limit } = params; - - if (!isNil(_q)) { - query._q = _q; - } - - if (!isNil(sort)) { - query.orderBy = convertSortQueryParams(sort); - } - - if (!isNil(filters)) { - query.where = convertFiltersQueryParams(filters, schema); - } - - if (!isNil(fields)) { - query.select = convertFieldsQueryParams(fields); - } - - if (!isNil(populate)) { - query.populate = convertPopulateQueryParams(populate, schema); - } - - const isPagePagination = !isNil(page) || !isNil(pageSize); - const isOffsetPagination = !isNil(start) || !isNil(limit); - - if (isPagePagination && isOffsetPagination) { - throw new PaginationError( - 'Invalid pagination attributes. You cannot use page and offset pagination in the same query' - ); - } - - if (!isNil(page)) { - const pageVal = toNumber(page); - - if (!isInteger(pageVal) || pageVal <= 0) { - throw new PaginationError( - `Invalid 'page' parameter. Expected an integer > 0, received: ${page}` - ); - } - - query.page = pageVal; - } - - if (!isNil(pageSize)) { - const pageSizeVal = toNumber(pageSize); - - if (!isInteger(pageSizeVal) || pageSizeVal <= 0) { - throw new PaginationError( - `Invalid 'pageSize' parameter. Expected an integer > 0, received: ${page}` - ); - } - - query.pageSize = pageSizeVal; - } - - if (!isNil(start)) { - query.offset = convertStartQueryParams(start); - } - - if (!isNil(limit)) { - query.limit = convertLimitQueryParams(limit); - } - - convertPublicationStateParams(schema, params, query); - - return query; -}; - module.exports = { - transformParamsToQuery, pickSelectionParams, }; diff --git a/packages/core/utils/lib/convert-query-params.js b/packages/core/utils/lib/convert-query-params.js index 7b470fe9bd..8f47e1d3b2 100644 --- a/packages/core/utils/lib/convert-query-params.js +++ b/packages/core/utils/lib/convert-query-params.js @@ -6,10 +6,22 @@ * Converts the standard Strapi REST query params to a more usable format for querying * You can read more here: https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest-api.html#filters */ -const { has, isEmpty, isObject, isPlainObject, cloneDeep, get, mergeAll } = require('lodash/fp'); +const { + has, + isEmpty, + isObject, + isPlainObject, + cloneDeep, + get, + mergeAll, + isNil, + toNumber, + isInteger, +} = require('lodash/fp'); const _ = require('lodash'); const parseType = require('./parse-type'); const contentTypesUtils = require('./content-types'); +const { PaginationError } = require('./errors'); const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants; @@ -389,6 +401,80 @@ const convertPublicationStateParams = (type, params = {}, query = {}) => { } }; +const transformParamsToQuery = (uid, params) => { + // NOTE: can be a CT, a Compo or nothing in the case of polymorphism (DZ & morph relations) + const schema = strapi.getModel(uid); + + const query = {}; + + const { _q, sort, filters, fields, populate, page, pageSize, start, limit } = params; + + if (!isNil(_q)) { + query._q = _q; + } + + if (!isNil(sort)) { + query.orderBy = convertSortQueryParams(sort); + } + + if (!isNil(filters)) { + query.where = convertFiltersQueryParams(filters, schema); + } + + if (!isNil(fields)) { + query.select = convertFieldsQueryParams(fields); + } + + if (!isNil(populate)) { + query.populate = convertPopulateQueryParams(populate, schema); + } + + const isPagePagination = !isNil(page) || !isNil(pageSize); + const isOffsetPagination = !isNil(start) || !isNil(limit); + + if (isPagePagination && isOffsetPagination) { + throw new PaginationError( + 'Invalid pagination attributes. You cannot use page and offset pagination in the same query' + ); + } + + if (!isNil(page)) { + const pageVal = toNumber(page); + + if (!isInteger(pageVal) || pageVal <= 0) { + throw new PaginationError( + `Invalid 'page' parameter. Expected an integer > 0, received: ${page}` + ); + } + + query.page = pageVal; + } + + if (!isNil(pageSize)) { + const pageSizeVal = toNumber(pageSize); + + if (!isInteger(pageSizeVal) || pageSizeVal <= 0) { + throw new PaginationError( + `Invalid 'pageSize' parameter. Expected an integer > 0, received: ${page}` + ); + } + + query.pageSize = pageSizeVal; + } + + if (!isNil(start)) { + query.offset = convertStartQueryParams(start); + } + + if (!isNil(limit)) { + query.limit = convertLimitQueryParams(limit); + } + + convertPublicationStateParams(schema, params, query); + + return query; +}; + module.exports = { convertSortQueryParams, convertStartQueryParams, @@ -397,4 +483,5 @@ module.exports = { convertFiltersQueryParams, convertFieldsQueryParams, convertPublicationStateParams, + transformParamsToQuery, };