diff --git a/packages/core/admin/admin/src/content-manager/pages/ListView/index.js b/packages/core/admin/admin/src/content-manager/pages/ListView/index.js index 7514849573..26c74c17c9 100644 --- a/packages/core/admin/admin/src/content-manager/pages/ListView/index.js +++ b/packages/core/admin/admin/src/content-manager/pages/ListView/index.js @@ -100,6 +100,7 @@ function ListView({ try { const opts = source ? { cancelToken: source.token } : null; + const { data: { results, pagination: paginationResult }, } = await axiosInstance.get(endPoint, opts); diff --git a/packages/core/admin/admin/src/content-manager/pages/ListView/utils/buildQueryString.js b/packages/core/admin/admin/src/content-manager/pages/ListView/utils/buildQueryString.js index 632fe1bd03..9fbe60820f 100644 --- a/packages/core/admin/admin/src/content-manager/pages/ListView/utils/buildQueryString.js +++ b/packages/core/admin/admin/src/content-manager/pages/ListView/utils/buildQueryString.js @@ -4,19 +4,16 @@ import createPluginsFilter from './createPluginsFilter'; /** * Creates a valid query string from an object of queryParams * This includes: - * - a _where clause + * - a filters clause * - plugin options */ const buildQueryString = (queryParams = {}) => { - const _where = queryParams._where || []; - /** * Extracting pluginOptions from the query since we don't want them to be part * of the url */ const { plugins: _, ...otherQueryParams } = { ...queryParams, - _where, ...createPluginsFilter(queryParams.plugins), }; diff --git a/packages/core/admin/admin/src/content-manager/pages/ListView/utils/createPluginsFilter.js b/packages/core/admin/admin/src/content-manager/pages/ListView/utils/createPluginsFilter.js index caa2420b73..57571928b2 100644 --- a/packages/core/admin/admin/src/content-manager/pages/ListView/utils/createPluginsFilter.js +++ b/packages/core/admin/admin/src/content-manager/pages/ListView/utils/createPluginsFilter.js @@ -1,13 +1,4 @@ const createPluginsFilter = obj => - Object.values(obj || {}).reduce((acc, current) => { - return { - ...acc, - ...Object.keys(current).reduce((accumulator, key) => { - accumulator[`_${key}`] = current[key]; - - return accumulator; - }, {}), - }; - }, {}); + Object.values(obj || {}).reduce((acc, current) => Object.assign(acc, current), {}); export default createPluginsFilter; diff --git a/packages/core/admin/admin/src/content-manager/pages/ListView/utils/tests/buildQueryString.test.js b/packages/core/admin/admin/src/content-manager/pages/ListView/utils/tests/buildQueryString.test.js index 19dc020199..2236be012e 100644 --- a/packages/core/admin/admin/src/content-manager/pages/ListView/utils/tests/buildQueryString.test.js +++ b/packages/core/admin/admin/src/content-manager/pages/ListView/utils/tests/buildQueryString.test.js @@ -25,28 +25,28 @@ describe('buildQueryString', () => { const queryString = buildQueryString(queryParams); - expect(queryString).toBe('?page=1&pageSize=10&sort=name:ASC&_locale=en'); + expect(queryString).toBe('?page=1&pageSize=10&sort=name:ASC&locale=en'); }); - it('creates a valid query string with a _where clause', () => { + it('creates a valid query string with a filters clause', () => { const queryParams = { page: '1', pageSize: '10', sort: 'name:ASC', - _where: [{ name: 'hello world' }], + filters: [{ name: 'hello world' }], }; const queryString = buildQueryString(queryParams); - expect(queryString).toBe('?page=1&pageSize=10&sort=name:ASC&_where[0][name]=hello world'); + expect(queryString).toBe('?page=1&pageSize=10&sort=name:ASC&filters[0][name]=hello world'); }); - it('creates a valid query string with a _where and plugin options', () => { + it('creates a valid query string with a filters and plugin options', () => { const queryParams = { page: '1', pageSize: '10', sort: 'name:ASC', - _where: [{ name: 'hello world' }], + filters: [{ name: 'hello world' }], plugins: { i18n: { locale: 'en' }, }, @@ -55,7 +55,7 @@ describe('buildQueryString', () => { const queryString = buildQueryString(queryParams); expect(queryString).toBe( - '?page=1&pageSize=10&sort=name:ASC&_where[0][name]=hello world&_locale=en' + '?page=1&pageSize=10&sort=name:ASC&filters[0][name]=hello world&locale=en' ); }); }); diff --git a/packages/core/admin/admin/src/content-manager/utils/formatFiltersFromQuery.js b/packages/core/admin/admin/src/content-manager/utils/formatFiltersFromQuery.js deleted file mode 100644 index 39c260e28e..0000000000 --- a/packages/core/admin/admin/src/content-manager/utils/formatFiltersFromQuery.js +++ /dev/null @@ -1,55 +0,0 @@ -// List of all the possible filters -const VALID_REST_OPERATORS = [ - 'eq', - 'ne', - 'in', - 'nin', - 'contains', - 'ncontains', - 'containss', - 'ncontainss', - 'lt', - 'lte', - 'gt', - 'gte', - 'null', -]; - -// from strapi-utils/convert-rest-query-params -const findAppliedFilter = whereClause => { - // Useful to remove the mainField of relation fields. - const formattedWhereClause = whereClause.split('.')[0]; - const separatorIndex = whereClause.lastIndexOf('_'); - - if (separatorIndex === -1) { - return { operator: '=', field: formattedWhereClause }; - } - - const fieldName = formattedWhereClause.substring(0, separatorIndex); - const operator = whereClause.slice(separatorIndex + 1); - - // the field as underscores - if (!VALID_REST_OPERATORS.includes(operator)) { - return { operator: '=', field: formattedWhereClause }; - } - - return { operator: `_${operator}`, field: fieldName }; -}; - -const formatFiltersFromQuery = ({ _where }) => { - if (!_where) { - return []; - } - - return _where.map(obj => { - const [key] = Object.keys(obj); - const { field, operator } = findAppliedFilter(key); - - const value = obj[key]; - - return { name: field, filter: operator, value }; - }); -}; - -export default formatFiltersFromQuery; -export { findAppliedFilter }; diff --git a/packages/core/admin/admin/src/content-manager/utils/formatFiltersToQuery.js b/packages/core/admin/admin/src/content-manager/utils/formatFiltersToQuery.js deleted file mode 100644 index d64ab966a2..0000000000 --- a/packages/core/admin/admin/src/content-manager/utils/formatFiltersToQuery.js +++ /dev/null @@ -1,27 +0,0 @@ -import { get } from 'lodash'; - -const formatFilterName = (name, metadatas) => { - const mainField = get(metadatas, [name, 'list', 'mainField', 'name'], null); - - if (mainField) { - return `${name}.${mainField}`; - } - - return name; -}; - -const formatFiltersToQuery = (array, metadatas) => { - const nextFilters = array.map(({ name, filter, value }) => { - const formattedName = formatFilterName(name, metadatas); - - if (filter === '=') { - return { [formattedName]: value }; - } - - return { [`${formattedName}${filter}`]: value }; - }); - - return { _where: nextFilters }; -}; - -export default formatFiltersToQuery; diff --git a/packages/core/admin/admin/src/content-manager/utils/index.js b/packages/core/admin/admin/src/content-manager/utils/index.js index 8d3eb9272d..a916121406 100644 --- a/packages/core/admin/admin/src/content-manager/utils/index.js +++ b/packages/core/admin/admin/src/content-manager/utils/index.js @@ -2,8 +2,6 @@ export { default as arrayMoveItem } from './arrayMoveItem'; export { default as checkIfAttributeIsDisplayable } from './checkIfAttributeIsDisplayable'; export { default as createDefaultForm } from './createDefaultForm'; export { default as dateFormats } from './dateFormats'; -export { default as formatFiltersFromQuery } from './formatFiltersFromQuery'; -export { default as formatFiltersToQuery } from './formatFiltersToQuery'; export { default as formatLayoutToApi } from './formatLayoutToApi'; export { default as generatePermissionsObject } from './generatePermissionsObject'; export { default as getDisplayedValue } from './getDisplayedValue'; diff --git a/packages/core/admin/admin/src/content-manager/utils/tests/formatFilterFromQuery.test.js b/packages/core/admin/admin/src/content-manager/utils/tests/formatFilterFromQuery.test.js deleted file mode 100644 index a1b21305cd..0000000000 --- a/packages/core/admin/admin/src/content-manager/utils/tests/formatFilterFromQuery.test.js +++ /dev/null @@ -1,66 +0,0 @@ -import formatFiltersFromQuery, { findAppliedFilter } from '../formatFiltersFromQuery'; - -describe('CONTENT MANAGER | utils', () => { - describe('findAppliedFilter', () => { - it('should return the correct filter', () => { - expect(findAppliedFilter('categories.name')).toEqual({ operator: '=', field: 'categories' }); - expect(findAppliedFilter('categories.name_lt')).toEqual({ - operator: '_lt', - field: 'categories', - }); - expect(findAppliedFilter('city')).toEqual({ operator: '=', field: 'city' }); - expect(findAppliedFilter('city_nee')).toEqual({ operator: '=', field: 'city_nee' }); - expect(findAppliedFilter('city_ne')).toEqual({ operator: '_ne', field: 'city' }); - expect(findAppliedFilter('city_lt')).toEqual({ operator: '_lt', field: 'city' }); - expect(findAppliedFilter('city_lte')).toEqual({ operator: '_lte', field: 'city' }); - expect(findAppliedFilter('city_gt')).toEqual({ operator: '_gt', field: 'city' }); - expect(findAppliedFilter('city_gte')).toEqual({ operator: '_gte', field: 'city' }); - }); - }); - - describe('formatFiltersFromQuery', () => { - it('should return an empty array if there is no where clause', () => { - expect(formatFiltersFromQuery({})).toHaveLength(0); - }); - - it('should return array of filter', () => { - const query = { - _where: [ - { - city_ne_ne: 'paris', - }, - { - city_ne: 'paris', - }, - { - city: 'paris', - }, - { - 'categories.name_ne': 'first', - }, - { - 'like.numbers_lt': 34, - }, - ], - }; - - const expected = [ - { name: 'city_ne', filter: '_ne', value: 'paris' }, - { name: 'city', filter: '_ne', value: 'paris' }, - { name: 'city', filter: '=', value: 'paris' }, - { - name: 'categories', - filter: '_ne', - value: 'first', - }, - { - name: 'like', - filter: '_lt', - value: 34, - }, - ]; - - expect(formatFiltersFromQuery(query)).toEqual(expected); - }); - }); -}); diff --git a/packages/core/admin/admin/src/content-manager/utils/tests/formatFiltersToQuery.test.js b/packages/core/admin/admin/src/content-manager/utils/tests/formatFiltersToQuery.test.js deleted file mode 100644 index b0e8cbba69..0000000000 --- a/packages/core/admin/admin/src/content-manager/utils/tests/formatFiltersToQuery.test.js +++ /dev/null @@ -1,57 +0,0 @@ -import formatFiltersToQuery from '../formatFiltersToQuery'; - -describe('CONTENT MANAGER | utils', () => { - describe('formatFiltersToQuery', () => { - it('should return the filters query', () => { - const metadatas = { - categories: { - list: { - mainField: { name: 'name' }, - }, - }, - like: { - list: { - mainField: { name: 'numbers' }, - }, - }, - }; - const data = [ - { name: 'city_ne', filter: '_ne', value: 'paris' }, - { name: 'city', filter: '_ne', value: 'paris' }, - { name: 'city', filter: '=', value: 'paris' }, - { - name: 'categories', - filter: '_ne', - value: 'first', - }, - { - name: 'like', - filter: '_lt', - value: 34, - }, - ]; - - const expected = { - _where: [ - { - city_ne_ne: 'paris', - }, - { - city_ne: 'paris', - }, - { - city: 'paris', - }, - { - 'categories.name_ne': 'first', - }, - { - 'like.numbers_lt': 34, - }, - ], - }; - - expect(formatFiltersToQuery(data, metadatas)).toEqual(expected); - }); - }); -}); diff --git a/packages/core/content-manager/server/controllers/collection-types.js b/packages/core/content-manager/server/controllers/collection-types.js index 07e1649422..84037dbc54 100644 --- a/packages/core/content-manager/server/controllers/collection-types.js +++ b/packages/core/content-manager/server/controllers/collection-types.js @@ -223,7 +223,7 @@ module.exports = { const params = { ...permissionQuery, filters: { - $and: [idsWhereClause].concat(permissionQuery._where || []), + $and: [idsWhereClause].concat(permissionQuery.filters || []), }, }; diff --git a/packages/core/database/lib/entity-manager.js b/packages/core/database/lib/entity-manager.js index d0d79dab5c..c614e5a4e7 100644 --- a/packages/core/database/lib/entity-manager.js +++ b/packages/core/database/lib/entity-manager.js @@ -142,7 +142,7 @@ const createEntityManager = db => { await db.lifecycles.run('beforeCount', uid, { params }); const res = await this.createQueryBuilder(uid) - .init(_.pick(['_q', 'where'], params)) + .init(_.pick(['_q', 'where', 'filters'], params)) .count() .first() .execute(); @@ -172,7 +172,7 @@ const createEntityManager = db => { 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 ? const result = await this.findOne(uid, { where: { id }, @@ -296,7 +296,7 @@ const createEntityManager = db => { 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, { select: select && ['id'].concat(select), where, @@ -469,7 +469,6 @@ const createEntityManager = db => { const { joinTable } = attribute; const { joinColumn, inverseJoinColumn } = joinTable; - // TODO: validate logic of delete if (isOneToAny(attribute) && isBidirectional(attribute)) { await this.createQueryBuilder(joinTable.name) .delete() diff --git a/packages/core/database/lib/query/helpers/populate.js b/packages/core/database/lib/query/helpers/populate.js index eca5d12b97..b8740e2458 100644 --- a/packages/core/database/lib/query/helpers/populate.js +++ b/packages/core/database/lib/query/helpers/populate.js @@ -122,7 +122,11 @@ const applyPopulate = async (results, populate, ctx) => { const attribute = meta.attributes[key]; 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 fromTargetRow = rowOrRows => fromRow(targetMeta, rowOrRows); diff --git a/packages/core/database/lib/query/query-builder.js b/packages/core/database/lib/query/query-builder.js index 715ec8f493..b559a0a378 100644 --- a/packages/core/database/lib/query/query-builder.js +++ b/packages/core/database/lib/query/query-builder.js @@ -29,6 +29,7 @@ const createQueryBuilder = (uid, db) => { return { alias: getAlias(), getAlias, + state, select(args) { state.type = 'select'; @@ -115,7 +116,7 @@ const createQueryBuilder = (uid, db) => { }, 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)) { this.where(where); @@ -151,9 +152,17 @@ const createQueryBuilder = (uid, db) => { this.populate(populate); } + if (!_.isNil(filters)) { + this.filters(filters); + } + return this; }, + filters(filters) { + state.filters = filters; + }, + first() { state.first = true; return this; @@ -206,6 +215,19 @@ const createQueryBuilder = (uid, db) => { processState() { 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.populate = helpers.processPopulate(state.populate, { qb: this, uid, db }); state.data = helpers.toRow(meta, state.data); diff --git a/packages/core/strapi/lib/Strapi.js b/packages/core/strapi/lib/Strapi.js index e0e2c00b04..08ea1c6396 100644 --- a/packages/core/strapi/lib/Strapi.js +++ b/packages/core/strapi/lib/Strapi.js @@ -259,7 +259,7 @@ class Strapi { } stop(exitCode = 1) { - this.server.destroy(); + this.destroy(); if (this.config.get('autoReload')) { process.send('stop'); @@ -431,7 +431,6 @@ class Strapi { } if (this.config.get('autoReload')) { - this.destroy(); process.send('reload'); } }; diff --git a/packages/core/strapi/lib/commands/develop.js b/packages/core/strapi/lib/commands/develop.js index e8d39193d1..aa96471a3b 100644 --- a/packages/core/strapi/lib/commands/develop.js +++ b/packages/core/strapi/lib/commands/develop.js @@ -52,14 +52,12 @@ module.exports = async function({ build, watchAdmin, polling, browser }) { switch (message) { case 'reload': logger.info('The server is restarting\n'); - worker.send('isKilled'); + worker.send('kill'); break; - case 'kill': - worker.kill(); + case 'killed': cluster.fork(); break; case 'stop': - worker.kill(); process.exit(1); default: return; @@ -85,10 +83,10 @@ module.exports = async function({ build, watchAdmin, polling, browser }) { process.on('message', async message => { switch (message) { - case 'isKilled': - await strapiInstance.server.destroy(); - process.send('kill'); - break; + case 'kill': + await strapiInstance.destroy(); + process.send('killed'); + process.exit(); default: // Do nothing. } diff --git a/packages/core/strapi/lib/services/entity-service/__tests__/entity-service.test.js b/packages/core/strapi/lib/services/entity-service/__tests__/entity-service.test.js index 6428d053dc..5ad4924b55 100644 --- a/packages/core/strapi/lib/services/entity-service/__tests__/entity-service.test.js +++ b/packages/core/strapi/lib/services/entity-service/__tests__/entity-service.test.js @@ -1,6 +1,6 @@ 'use strict'; -jest.mock('bcrypt', () => ({ hashSync: () => 'secret-password' })); +jest.mock('bcryptjs', () => ({ hashSync: () => 'secret-password' })); const { EventEmitter } = require('events'); const createEntityService = require('../'); diff --git a/packages/core/strapi/lib/services/entity-service/attributes/transforms.js b/packages/core/strapi/lib/services/entity-service/attributes/transforms.js index 406a622123..c2e40b808c 100644 --- a/packages/core/strapi/lib/services/entity-service/attributes/transforms.js +++ b/packages/core/strapi/lib/services/entity-service/attributes/transforms.js @@ -1,7 +1,7 @@ 'use strict'; const { getOr, toNumber, isString, isBuffer } = require('lodash/fp'); -const bcrypt = require('bcrypt'); +const bcrypt = require('bcryptjs'); const transforms = { password(value, context) { diff --git a/packages/core/strapi/lib/services/entity-service/index.js b/packages/core/strapi/lib/services/entity-service/index.js index e37228c56d..b37071098a 100644 --- a/packages/core/strapi/lib/services/entity-service/index.js +++ b/packages/core/strapi/lib/services/entity-service/index.js @@ -1,7 +1,6 @@ 'use strict'; const delegate = require('delegates'); -const { pipe } = require('lodash/fp'); const { sanitizeEntity, @@ -16,12 +15,7 @@ const { updateComponents, deleteComponents, } = require('./components'); -const { - transformCommonParams, - transformPaginationParams, - transformParamsToQuery, - pickSelectionParams, -} = require('./params'); +const { transformParamsToQuery, pickSelectionParams } = require('./params'); const { applyTransforms } = require('./attributes'); // TODO: those should be strapi events used by the webhooks not the other way arround @@ -244,13 +238,26 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator }) const attribute = attributes[field]; - const loadParams = - attribute.type === 'relation' - ? transformParamsToQuery(attribute.target, params) - : pipe( - transformCommonParams, - transformPaginationParams - )(params); + const loadParams = {}; + + switch (attribute.type) { + case 'relation': { + Object.assign(loadParams, transformParamsToQuery(attribute.target, params)); + break; + } + 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; + } + } return db.query(uid).load(entity, field, loadParams); }, diff --git a/packages/core/strapi/lib/services/entity-service/params.js b/packages/core/strapi/lib/services/entity-service/params.js index a7ef523798..877d6a5f13 100644 --- a/packages/core/strapi/lib/services/entity-service/params.js +++ b/packages/core/strapi/lib/services/entity-service/params.js @@ -1,6 +1,7 @@ 'use strict'; -const { pick, pipe, isNil } = require('lodash/fp'); +const assert = require('assert').strict; +const { pick, isNil, toNumber, isInteger } = require('lodash/fp'); const { convertSortQueryParams, @@ -9,62 +10,39 @@ const { convertPopulateQueryParams, convertFiltersQueryParams, convertFieldsQueryParams, + convertPublicationStateParams, } = 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); -// TODO: to remove once the front is migrated -const convertOldQuery = params => { const query = {}; - Object.keys(params).forEach(key => { - if (key.startsWith('_')) { - query[key.slice(1)] = params[key]; - } else { - query[key] = params[key]; - } - }); + const { _q, sort, filters, fields, populate, page, pageSize, start, limit } = params; - return query; -}; - -const transformCommonParams = (params = {}) => { - const { _q, sort, filters, _where, fields, populate, ...query } = params; - - if (_q) { + if (!isNil(_q)) { query._q = _q; } - if (sort) { + if (!isNil(sort)) { query.orderBy = convertSortQueryParams(sort); } - if (filters) { + if (!isNil(filters)) { query.where = convertFiltersQueryParams(filters); } - if (_where) { - query.where = { - $and: [_where].concat(query.where || []), - }; - } - - if (fields) { + if (!isNil(fields)) { query.select = convertFieldsQueryParams(fields); } - if (populate) { + if (!isNil(populate)) { query.populate = convertPopulateQueryParams(populate); } - return { ...convertOldQuery(query), ...query }; -}; - -const transformPaginationParams = (params = {}) => { - const { page, pageSize, start, limit, ...query } = params; - const isPagePagination = !isNil(page) || !isNil(pageSize); const isOffsetPagination = !isNil(start) || !isNil(limit); @@ -74,72 +52,38 @@ const transformPaginationParams = (params = {}) => { ); } - if (page) { - query.page = Number(page); + if (!isNil(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) { - query.pageSize = Number(pageSize); + if (!isNil(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); } - if (limit) { + if (!isNil(limit)) { query.limit = convertLimitQueryParams(limit); } - return { ...convertOldQuery(query), ...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 { ...convertOldQuery(query), ...query }; -}; - -const pickSelectionParams = pick(['fields', 'populate']); - -const transformParamsToQuery = (uid, params) => { - return pipe( - // _q, _where, filters, etc... - transformCommonParams, - // page, pageSize, start, limit - transformPaginationParams, - // publicationState - transformPublicationStateParams(uid) - )(params); + return query; }; module.exports = { - transformCommonParams, - transformPublicationStateParams, - transformPaginationParams, transformParamsToQuery, pickSelectionParams, }; diff --git a/packages/core/strapi/lib/utils/signals.js b/packages/core/strapi/lib/utils/signals.js index 5a8b05af34..65e539721d 100644 --- a/packages/core/strapi/lib/utils/signals.js +++ b/packages/core/strapi/lib/utils/signals.js @@ -1,6 +1,6 @@ 'use strict'; -const destroyOnSignal = () => { +const destroyOnSignal = strapi => { let signalReceived = false; // For unknown reasons, we receive signals 2 times. diff --git a/packages/core/strapi/package.json b/packages/core/strapi/package.json index 164f5250ee..df0e8ed4f7 100644 --- a/packages/core/strapi/package.json +++ b/packages/core/strapi/package.json @@ -90,7 +90,7 @@ "@strapi/plugin-email": "3.6.8", "@strapi/plugin-upload": "3.6.8", "@strapi/utils": "3.6.8", - "bcrypt": "5.0.1", + "bcryptjs": "2.4.3", "boxen": "5.1.2", "chalk": "4.1.2", "chokidar": "3.5.2", diff --git a/packages/core/strapi/tests/publication-state.test.e2e.js b/packages/core/strapi/tests/publication-state.test.e2e.js index 999da335b6..80fcf9f548 100644 --- a/packages/core/strapi/tests/publication-state.test.e2e.js +++ b/packages/core/strapi/tests/publication-state.test.e2e.js @@ -171,6 +171,7 @@ describe('Publication State', () => { const res = await rq({ method: 'GET', url: `${baseUrl}${query}` }); expect(res.body.data).toHaveLength(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', () => { @@ -199,35 +200,29 @@ describe('Publication State', () => { test('Root level', () => { 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 }) => { - // const length = getApiRef(id).categories.filter(c => c.publishedAt !== null).length; - // expect(categories).toHaveLength(length); + test('Second level through component (countries) to be published only', () => { + products.forEach(({ attributes }) => { + const countries = attributes.comp.countries.data; - // categories.forEach(category => { - // expect(category.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(); - // }); - // }); - // }); + countries.forEach(country => { + expect(country.attributes.publishedAt).toBeISODate(); + }); + }); + }); }); }); }); diff --git a/packages/core/utils/lib/convert-query-params.js b/packages/core/utils/lib/convert-query-params.js index 9de8c2ca51..e4c9f94763 100644 --- a/packages/core/utils/lib/convert-query-params.js +++ b/packages/core/utils/lib/convert-query-params.js @@ -4,11 +4,12 @@ * Converts the standard Strapi REST query params to a more usable format for querying * You can read more here: https://strapi.io/documentation/developer-docs/latest/developer-resources/content-api/content-api.html#filters */ - +const { has } = require('lodash/fp'); const _ = require('lodash'); const parseType = require('./parse-type'); +const contentTypesUtils = require('./content-types'); -const QUERY_OPERATORS = ['_where', '_or', '_and']; +const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants; class InvalidOrderError extends Error { constructor() { @@ -132,13 +133,15 @@ const convertPopulateQueryParams = (populate, depth = 0) => { if (Array.isArray(populate)) { // map convert - return populate.flatMap(value => { - if (typeof value !== 'string') { - throw new InvalidPopulateError(); - } + return _.uniq( + populate.flatMap(value => { + if (typeof value !== 'string') { + throw new InvalidPopulateError(); + } - return value.split(',').map(value => _.trim(value)); - }); + return value.split(',').map(value => _.trim(value)); + }) + ); } if (_.isPlainObject(populate)) { @@ -146,6 +149,7 @@ const convertPopulateQueryParams = (populate, depth = 0) => { for (const key in populate) { transformedPopulate[key] = convertNestedPopulate(populate[key]); } + return transformedPopulate; } @@ -212,9 +216,31 @@ const convertFieldsQueryParams = (fields, depth = 0) => { 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 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 = { convertSortQueryParams, convertStartQueryParams, @@ -222,5 +248,5 @@ module.exports = { convertPopulateQueryParams, convertFiltersQueryParams, convertFieldsQueryParams, - QUERY_OPERATORS, + convertPublicationStateParams, }; diff --git a/packages/core/utils/lib/index.js b/packages/core/utils/lib/index.js index c6b8bde425..a7ddbea9ab 100644 --- a/packages/core/utils/lib/index.js +++ b/packages/core/utils/lib/index.js @@ -4,7 +4,6 @@ * Export shared utilities */ const { buildQuery, hasDeepFilters } = require('./build-query'); -const { QUERY_OPERATORS } = require('./convert-query-params'); const parseMultipartData = require('./parse-multipart'); const sanitizeEntity = require('./sanitize-entity'); const parseType = require('./parse-type'); @@ -38,7 +37,6 @@ module.exports = { formatYupErrors, policy, templateConfiguration, - QUERY_OPERATORS, buildQuery, hasDeepFilters, parseMultipartData, diff --git a/packages/plugins/i18n/admin/src/contentManagerHooks/mutateEditViewLayout.js b/packages/plugins/i18n/admin/src/contentManagerHooks/mutateEditViewLayout.js index defae91c07..77ca58e9de 100644 --- a/packages/plugins/i18n/admin/src/contentManagerHooks/mutateEditViewLayout.js +++ b/packages/plugins/i18n/admin/src/contentManagerHooks/mutateEditViewLayout.js @@ -19,7 +19,7 @@ const enhanceRelationLayout = (layout, locale) => if (get(current, ['targetModelPluginOptions', 'i18n', 'localized'], false)) { queryInfos = { ...queryInfos, - defaultParams: { ...queryInfos.defaultParams, _locale: locale }, + defaultParams: { ...queryInfos.defaultParams, locale }, paramsToKeep: ['plugins.i18n.locale'], }; } @@ -82,7 +82,7 @@ const enhanceComponentLayoutForRelations = (layout, locale) => ) { const queryInfos = { ...field.queryInfos, - defaultParams: { ...field.queryInfos.defaultParams, _locale: locale }, + defaultParams: { ...field.queryInfos.defaultParams, locale }, paramsToKeep: ['plugins.i18n.locale'], }; diff --git a/packages/plugins/i18n/admin/src/contentManagerHooks/tests/mutateEditViewLayout.test.js b/packages/plugins/i18n/admin/src/contentManagerHooks/tests/mutateEditViewLayout.test.js index 19a401c91d..49718a9814 100644 --- a/packages/plugins/i18n/admin/src/contentManagerHooks/tests/mutateEditViewLayout.test.js +++ b/packages/plugins/i18n/admin/src/contentManagerHooks/tests/mutateEditViewLayout.test.js @@ -277,7 +277,7 @@ describe('i18n | contentManagerHooks | mutateEditViewLayout', () => { fieldSchema: { type: 'relation' }, targetModelPluginOptions: { i18n: { localized: true } }, queryInfos: { - defaultParams: { test: true, _locale: 'en' }, + defaultParams: { test: true, locale: 'en' }, paramsToKeep: ['plugins.i18n.locale'], }, }, @@ -299,7 +299,7 @@ describe('i18n | contentManagerHooks | mutateEditViewLayout', () => { fieldSchema: { type: 'relation' }, targetModelPluginOptions: { i18n: { localized: true } }, queryInfos: { - defaultParams: { test: true, _locale: 'en' }, + defaultParams: { test: true, locale: 'en' }, paramsToKeep: ['plugins.i18n.locale'], }, }, @@ -518,7 +518,7 @@ describe('i18n | contentManagerHooks | mutateEditViewLayout', () => { queryInfos: { defaultParams: { test: true, - _locale: 'en', + locale: 'en', }, paramsToKeep: ['plugins.i18n.locale'], }, diff --git a/packages/plugins/i18n/server/services/__tests__/entity-service-decorator.test.js b/packages/plugins/i18n/server/services/__tests__/entity-service-decorator.test.js index db7aff7870..fc809381a6 100644 --- a/packages/plugins/i18n/server/services/__tests__/entity-service-decorator.test.js +++ b/packages/plugins/i18n/server/services/__tests__/entity-service-decorator.test.js @@ -141,7 +141,7 @@ describe('Entity service decorator', () => { } ); - test('Replaces _locale param', async () => { + test('Replaces locale param', async () => { const defaultService = { wrapParams: jest.fn(opts => Promise.resolve(opts)), }; diff --git a/packages/plugins/i18n/server/services/entity-service-decorator.js b/packages/plugins/i18n/server/services/entity-service-decorator.js index e1d0c627be..df9584a800 100644 --- a/packages/plugins/i18n/server/services/entity-service-decorator.js +++ b/packages/plugins/i18n/server/services/entity-service-decorator.js @@ -19,7 +19,6 @@ const paramsContain = (key, params) => { * @param {object} params - query params * @param {object} ctx */ -// TODO: remove _locale const wrapParams = async (params = {}, ctx = {}) => { const { action } = ctx; @@ -36,20 +35,6 @@ const wrapParams = async (params = {}, ctx = {}) => { }; } - // TODO: remove when the _locale is renamed to locale - if (has('_locale', params)) { - if (params['_locale'] === 'all') { - return omit('_locale', params); - } - - return { - ...omit('_locale', params), - filters: { - $and: [{ locale: params['_locale'] }].concat(params.filters || []), - }, - }; - } - const entityDefinedById = paramsContain('id', params) && SINGLE_ENTRY_ACTIONS.includes(action); const entitiesDefinedByIds = paramsContain('id.$in', params) && BULK_ACTIONS.includes(action); diff --git a/packages/plugins/sentry/server/middlewares/sentry.js b/packages/plugins/sentry/server/middlewares/sentry.js index a82e1d4f11..7c6c8c56d7 100644 --- a/packages/plugins/sentry/server/middlewares/sentry.js +++ b/packages/plugins/sentry/server/middlewares/sentry.js @@ -5,8 +5,14 @@ * @param {{ strapi: import('@strapi/strapi').Strapi }} */ module.exports = ({ strapi }) => { - const sentry = strapi.plugin('sentry').service('sentry'); - sentry.init(); + const sentryService = strapi.plugin('sentry').service('sentry'); + sentryService.init(); + const sentry = sentryService.getInstance(); + + if (!sentry) { + // initialization failed + return; + } strapi.server.use(async (ctx, next) => { try { diff --git a/yarn.lock b/yarn.lock index c1c2c99b64..a3b906f693 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2881,21 +2881,6 @@ npmlog "^4.1.2" write-file-atomic "^2.3.0" -"@mapbox/node-pre-gyp@^1.0.0": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.5.tgz#2a0b32fcb416fb3f2250fd24cb2a81421a4f5950" - integrity sha512-4srsKPXWlIxp5Vbqz5uLfBN+du2fJChBoYn/f2h991WLdk7jUvcSk/McVLSv/X+xQIPI8eGD5GjrnygdyHnhPA== - dependencies: - detect-libc "^1.0.3" - https-proxy-agent "^5.0.0" - make-dir "^3.1.0" - node-fetch "^2.6.1" - nopt "^5.0.0" - npmlog "^4.1.2" - rimraf "^3.0.2" - semver "^7.3.4" - tar "^6.1.0" - "@mdx-js/loader@^1.6.22": version "1.6.22" resolved "https://registry.yarnpkg.com/@mdx-js/loader/-/loader-1.6.22.tgz#d9e8fe7f8185ff13c9c8639c048b123e30d322c4" @@ -7221,15 +7206,7 @@ bcrypt-pbkdf@^1.0.0, bcrypt-pbkdf@^1.0.2: dependencies: tweetnacl "^0.14.3" -bcrypt@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/bcrypt/-/bcrypt-5.0.1.tgz#f1a2c20f208e2ccdceea4433df0c8b2c54ecdf71" - integrity sha512-9BTgmrhZM2t1bNuDtrtIMVSmmxZBrJ71n8Wg+YgdjHuIWYF7SjjmCPZFB+/5i/o/PIeRpwVJR3P+NrpIItUjqw== - dependencies: - "@mapbox/node-pre-gyp" "^1.0.0" - node-addon-api "^3.1.0" - -bcryptjs@^2.4.3: +bcryptjs@2.4.3, bcryptjs@^2.4.3: version "2.4.3" resolved "https://registry.yarnpkg.com/bcryptjs/-/bcryptjs-2.4.3.tgz#9ab5627b93e60621ff7cdac5da9733027df1d0cb" integrity sha1-mrVie5PmBiH/fNrF2pczAn3x0Ms= @@ -17349,13 +17326,6 @@ nopt@^4.0.1: abbrev "1" osenv "^0.1.4" -nopt@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/nopt/-/nopt-5.0.0.tgz#530942bb58a512fccafe53fe210f13a25355dc88" - integrity sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ== - dependencies: - abbrev "1" - normalize-package-data@^2.0.0, normalize-package-data@^2.3.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.3.5, normalize-package-data@^2.4.0, normalize-package-data@^2.5.0: version "2.5.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"