mirror of
https://github.com/strapi/strapi.git
synced 2025-09-22 06:50:51 +00:00
Merge branch 'cm-configure-view-lv' of https://github.com/strapi/strapi into cm-settings/drag-and-drop
This commit is contained in:
commit
5cce0eb854
@ -100,6 +100,7 @@ function ListView({
|
||||
|
||||
try {
|
||||
const opts = source ? { cancelToken: source.token } : null;
|
||||
|
||||
const {
|
||||
data: { results, pagination: paginationResult },
|
||||
} = await axiosInstance.get(endPoint, opts);
|
||||
|
@ -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),
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -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 };
|
@ -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;
|
@ -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';
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
@ -223,7 +223,7 @@ module.exports = {
|
||||
const params = {
|
||||
...permissionQuery,
|
||||
filters: {
|
||||
$and: [idsWhereClause].concat(permissionQuery._where || []),
|
||||
$and: [idsWhereClause].concat(permissionQuery.filters || []),
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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);
|
||||
|
@ -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);
|
||||
|
@ -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');
|
||||
}
|
||||
};
|
||||
|
@ -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.
|
||||
}
|
||||
|
@ -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('../');
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
},
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const destroyOnSignal = () => {
|
||||
const destroyOnSignal = strapi => {
|
||||
let signalReceived = false;
|
||||
|
||||
// For unknown reasons, we receive signals 2 times.
|
||||
|
@ -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",
|
||||
|
@ -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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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'],
|
||||
};
|
||||
|
||||
|
@ -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'],
|
||||
},
|
||||
|
@ -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)),
|
||||
};
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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 {
|
||||
|
32
yarn.lock
32
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"
|
||||
|
Loading…
x
Reference in New Issue
Block a user