@strapi/utils 4.3.2 → 4.3.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -5
- package/lib/build-query.js +8 -9
- package/lib/code-generator.js +1 -1
- package/lib/config.js +15 -13
- package/lib/content-types.js +12 -12
- package/lib/convert-query-params.js +33 -32
- package/lib/env-helper.js +1 -1
- package/lib/errors.js +2 -0
- package/lib/format-yup-error.js +2 -2
- package/lib/hooks.js +1 -1
- package/lib/object-formatting.js +1 -1
- package/lib/pagination.js +6 -4
- package/lib/parse-multipart.js +1 -1
- package/lib/parse-type.js +5 -5
- package/lib/pipe-async.js +9 -7
- package/lib/policy.js +5 -4
- package/lib/print-value.js +9 -9
- package/lib/provider-factory.js +1 -1
- package/lib/relations.js +2 -2
- package/lib/sanitize/index.js +4 -4
- package/lib/sanitize/visitors/allowed-fields.js +60 -58
- package/lib/sanitize/visitors/remove-restricted-relations.js +45 -43
- package/lib/sanitize/visitors/restricted-fields.js +25 -24
- package/lib/set-creator-fields.js +11 -9
- package/lib/string-formatting.js +10 -9
- package/lib/template-configuration.js +1 -1
- package/lib/traverse-entity.js +4 -4
- package/lib/validators.js +36 -27
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
# strapi-utils
|
|
2
2
|
|
|
3
|
-
[](https://david-dm.org/strapi/strapi-utils)
|
|
6
|
-
[](https://travis-ci.org/strapi/strapi-utils)
|
|
7
|
-
[](https://slack.strapi.io)
|
|
3
|
+
[](https://www.npmjs.org/package/@strapi/utils)
|
|
4
|
+
[](https://www.npmjs.org/package/@strapi/utils)
|
|
8
5
|
|
|
9
6
|
Shared utilities between Strapi packages.
|
|
10
7
|
|
package/lib/build-query.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
//TODO: move to dbal
|
|
3
|
+
// TODO: move to dbal
|
|
4
4
|
|
|
5
5
|
const _ = require('lodash');
|
|
6
6
|
const parseType = require('./parse-type');
|
|
@@ -25,7 +25,7 @@ const getAssociationFromFieldKey = ({ model, field }) => {
|
|
|
25
25
|
const part = fieldParts[i];
|
|
26
26
|
attribute = part;
|
|
27
27
|
|
|
28
|
-
const assoc = tmpModel.associations.find(ast => ast.alias === part);
|
|
28
|
+
const assoc = tmpModel.associations.find((ast) => ast.alias === part);
|
|
29
29
|
|
|
30
30
|
if (assoc) {
|
|
31
31
|
association = assoc;
|
|
@@ -59,7 +59,7 @@ const getAssociationFromFieldKey = ({ model, field }) => {
|
|
|
59
59
|
*/
|
|
60
60
|
const castInput = ({ type, value, operator }) => {
|
|
61
61
|
return Array.isArray(value)
|
|
62
|
-
? value.map(val => castValue({ type, operator, value: val }))
|
|
62
|
+
? value.map((val) => castValue({ type, operator, value: val }))
|
|
63
63
|
: castValue({ type, operator, value });
|
|
64
64
|
};
|
|
65
65
|
|
|
@@ -84,9 +84,7 @@ const castValue = ({ type, value, operator }) => {
|
|
|
84
84
|
const normalizeFieldName = ({ model, field }) => {
|
|
85
85
|
const fieldPath = field.split('.');
|
|
86
86
|
return _.last(fieldPath) === 'id'
|
|
87
|
-
? _.initial(fieldPath)
|
|
88
|
-
.concat(model.primaryKey)
|
|
89
|
-
.join('.')
|
|
87
|
+
? _.initial(fieldPath).concat(model.primaryKey).join('.')
|
|
90
88
|
: fieldPath.join('.');
|
|
91
89
|
};
|
|
92
90
|
|
|
@@ -100,7 +98,7 @@ const hasDeepFilters = ({ where = [], sort = [] }, { minDepth = 1 } = {}) => {
|
|
|
100
98
|
|
|
101
99
|
const hasDeepWhereClauses = where.some(({ field, operator, value }) => {
|
|
102
100
|
if (BOOLEAN_OPERATORS.includes(operator)) {
|
|
103
|
-
return value.some(clauses => hasDeepFilters({ where: clauses }));
|
|
101
|
+
return value.some((clauses) => hasDeepFilters({ where: clauses }));
|
|
104
102
|
}
|
|
105
103
|
|
|
106
104
|
return field.split('.').length > minDepth;
|
|
@@ -114,7 +112,8 @@ const normalizeWhereClauses = (whereClauses, { model }) => {
|
|
|
114
112
|
.filter(({ field, value }) => {
|
|
115
113
|
if (_.isNull(value)) {
|
|
116
114
|
return false;
|
|
117
|
-
}
|
|
115
|
+
}
|
|
116
|
+
if (_.isUndefined(value)) {
|
|
118
117
|
strapi.log.warn(`The value of field: '${field}', in your where filter, is undefined.`);
|
|
119
118
|
return false;
|
|
120
119
|
}
|
|
@@ -125,7 +124,7 @@ const normalizeWhereClauses = (whereClauses, { model }) => {
|
|
|
125
124
|
return {
|
|
126
125
|
field,
|
|
127
126
|
operator,
|
|
128
|
-
value: value.map(clauses => normalizeWhereClauses(clauses, { model })),
|
|
127
|
+
value: value.map((clauses) => normalizeWhereClauses(clauses, { model })),
|
|
129
128
|
};
|
|
130
129
|
}
|
|
131
130
|
|
package/lib/code-generator.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
// Using timestamp (milliseconds) to be sure it is unique
|
|
4
4
|
// + converting timestamp to base 36 for better readibility
|
|
5
|
-
const generateTimestampCode = date => {
|
|
5
|
+
const generateTimestampCode = (date) => {
|
|
6
6
|
const referDate = date || new Date();
|
|
7
7
|
|
|
8
8
|
return referDate.getTime().toString(36);
|
package/lib/config.js
CHANGED
|
@@ -62,22 +62,24 @@ const getConfigUrls = (config, forAdminBuild = false) => {
|
|
|
62
62
|
};
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
-
const getAbsoluteUrl =
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
const getAbsoluteUrl =
|
|
66
|
+
(adminOrServer) =>
|
|
67
|
+
(config, forAdminBuild = false) => {
|
|
68
|
+
const { serverUrl, adminUrl } = getConfigUrls(config, forAdminBuild);
|
|
69
|
+
const url = adminOrServer === 'server' ? serverUrl : adminUrl;
|
|
68
70
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
71
|
+
if (url.startsWith('http')) {
|
|
72
|
+
return url;
|
|
73
|
+
}
|
|
72
74
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
75
|
+
const hostname =
|
|
76
|
+
config.get('environment') === 'development' &&
|
|
77
|
+
['127.0.0.1', '0.0.0.0'].includes(config.get('server.host'))
|
|
78
|
+
? 'localhost'
|
|
79
|
+
: config.get('server.host');
|
|
78
80
|
|
|
79
|
-
|
|
80
|
-
};
|
|
81
|
+
return `http://${hostname}:${config.get('server.port')}${url}`;
|
|
82
|
+
};
|
|
81
83
|
|
|
82
84
|
module.exports = {
|
|
83
85
|
getConfigUrls,
|
package/lib/content-types.js
CHANGED
|
@@ -32,7 +32,7 @@ const constants = {
|
|
|
32
32
|
COLLECTION_TYPE,
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
const getTimestamps = model => {
|
|
35
|
+
const getTimestamps = (model) => {
|
|
36
36
|
const attributes = [];
|
|
37
37
|
|
|
38
38
|
if (has(CREATED_AT_ATTRIBUTE, model.attributes)) {
|
|
@@ -64,7 +64,7 @@ const isWritableAttribute = (model, attributeName) => {
|
|
|
64
64
|
return getWritableAttributes(model).includes(attributeName);
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
const getNonVisibleAttributes = model => {
|
|
67
|
+
const getNonVisibleAttributes = (model) => {
|
|
68
68
|
const nonVisibleAttributes = _.reduce(
|
|
69
69
|
model.attributes,
|
|
70
70
|
(acc, attr, attrName) => (attr.visible === false ? acc.concat(attrName) : acc),
|
|
@@ -74,7 +74,7 @@ const getNonVisibleAttributes = model => {
|
|
|
74
74
|
return _.uniq([ID_ATTRIBUTE, ...getTimestamps(model), ...nonVisibleAttributes]);
|
|
75
75
|
};
|
|
76
76
|
|
|
77
|
-
const getVisibleAttributes = model => {
|
|
77
|
+
const getVisibleAttributes = (model) => {
|
|
78
78
|
return _.difference(_.keys(model.attributes), getNonVisibleAttributes(model));
|
|
79
79
|
};
|
|
80
80
|
|
|
@@ -82,20 +82,20 @@ const isVisibleAttribute = (model, attributeName) => {
|
|
|
82
82
|
return getVisibleAttributes(model).includes(attributeName);
|
|
83
83
|
};
|
|
84
84
|
|
|
85
|
-
const hasDraftAndPublish = model => _.get(model, 'options.draftAndPublish', false) === true;
|
|
85
|
+
const hasDraftAndPublish = (model) => _.get(model, 'options.draftAndPublish', false) === true;
|
|
86
86
|
|
|
87
87
|
const isDraft = (data, model) =>
|
|
88
88
|
hasDraftAndPublish(model) && _.get(data, PUBLISHED_AT_ATTRIBUTE) === null;
|
|
89
89
|
|
|
90
90
|
const isSingleType = ({ kind = COLLECTION_TYPE }) => kind === SINGLE_TYPE;
|
|
91
91
|
const isCollectionType = ({ kind = COLLECTION_TYPE }) => kind === COLLECTION_TYPE;
|
|
92
|
-
const isKind = kind => model => model.kind === kind;
|
|
92
|
+
const isKind = (kind) => (model) => model.kind === kind;
|
|
93
93
|
|
|
94
94
|
const getPrivateAttributes = (model = {}) => {
|
|
95
95
|
return _.union(
|
|
96
96
|
strapi.config.get('api.responses.privateAttributes', []),
|
|
97
97
|
_.get(model, 'options.privateAttributes', []),
|
|
98
|
-
_.keys(_.pickBy(model.attributes, attr => !!attr.private))
|
|
98
|
+
_.keys(_.pickBy(model.attributes, (attr) => !!attr.private))
|
|
99
99
|
);
|
|
100
100
|
};
|
|
101
101
|
|
|
@@ -103,11 +103,11 @@ const isPrivateAttribute = (model = {}, attributeName) => {
|
|
|
103
103
|
return model && model.privateAttributes && model.privateAttributes.includes(attributeName);
|
|
104
104
|
};
|
|
105
105
|
|
|
106
|
-
const isScalarAttribute = attribute => {
|
|
106
|
+
const isScalarAttribute = (attribute) => {
|
|
107
107
|
return !['media', 'component', 'relation', 'dynamiczone'].includes(attribute.type);
|
|
108
108
|
};
|
|
109
109
|
|
|
110
|
-
const getScalarAttributes = schema => {
|
|
110
|
+
const getScalarAttributes = (schema) => {
|
|
111
111
|
return _.reduce(
|
|
112
112
|
schema.attributes,
|
|
113
113
|
(acc, attr, attrName) => {
|
|
@@ -118,9 +118,9 @@ const getScalarAttributes = schema => {
|
|
|
118
118
|
);
|
|
119
119
|
};
|
|
120
120
|
|
|
121
|
-
const isMediaAttribute = attribute => attribute.type === 'media';
|
|
122
|
-
const isRelationalAttribute = attribute => attribute.type === 'relation';
|
|
123
|
-
const isComponentAttribute = attribute => ['component', 'dynamiczone'].includes(attribute.type);
|
|
121
|
+
const isMediaAttribute = (attribute) => attribute.type === 'media';
|
|
122
|
+
const isRelationalAttribute = (attribute) => attribute.type === 'relation';
|
|
123
|
+
const isComponentAttribute = (attribute) => ['component', 'dynamiczone'].includes(attribute.type);
|
|
124
124
|
|
|
125
125
|
/**
|
|
126
126
|
* Checks if an attribute is of type `type`
|
|
@@ -136,7 +136,7 @@ const isTypedAttribute = (attribute, type) => {
|
|
|
136
136
|
* @param {object} contentType
|
|
137
137
|
* @returns {string}
|
|
138
138
|
*/
|
|
139
|
-
const getContentTypeRoutePrefix = contentType => {
|
|
139
|
+
const getContentTypeRoutePrefix = (contentType) => {
|
|
140
140
|
return isSingleType(contentType)
|
|
141
141
|
? _.kebabCase(contentType.info.singularName)
|
|
142
142
|
: _.kebabCase(contentType.info.pluralName);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* eslint-disable max-classes-per-file */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -25,13 +27,13 @@ class InvalidSortError extends Error {
|
|
|
25
27
|
}
|
|
26
28
|
}
|
|
27
29
|
|
|
28
|
-
const validateOrder = order => {
|
|
30
|
+
const validateOrder = (order) => {
|
|
29
31
|
if (!['asc', 'desc'].includes(order.toLocaleLowerCase())) {
|
|
30
32
|
throw new InvalidOrderError();
|
|
31
33
|
}
|
|
32
34
|
};
|
|
33
35
|
|
|
34
|
-
const convertCountQueryParams = countQuery => {
|
|
36
|
+
const convertCountQueryParams = (countQuery) => {
|
|
35
37
|
return parseType({ type: 'boolean', value: countQuery });
|
|
36
38
|
};
|
|
37
39
|
|
|
@@ -39,13 +41,13 @@ const convertCountQueryParams = countQuery => {
|
|
|
39
41
|
* Sort query parser
|
|
40
42
|
* @param {string} sortQuery - ex: id:asc,price:desc
|
|
41
43
|
*/
|
|
42
|
-
const convertSortQueryParams = sortQuery => {
|
|
44
|
+
const convertSortQueryParams = (sortQuery) => {
|
|
43
45
|
if (typeof sortQuery === 'string') {
|
|
44
|
-
return sortQuery.split(',').map(value => convertSingleSortQueryParam(value));
|
|
46
|
+
return sortQuery.split(',').map((value) => convertSingleSortQueryParam(value));
|
|
45
47
|
}
|
|
46
48
|
|
|
47
49
|
if (Array.isArray(sortQuery)) {
|
|
48
|
-
return sortQuery.flatMap(sortValue => convertSortQueryParams(sortValue));
|
|
50
|
+
return sortQuery.flatMap((sortValue) => convertSortQueryParams(sortValue));
|
|
49
51
|
}
|
|
50
52
|
|
|
51
53
|
if (_.isPlainObject(sortQuery)) {
|
|
@@ -55,7 +57,7 @@ const convertSortQueryParams = sortQuery => {
|
|
|
55
57
|
throw new InvalidSortError();
|
|
56
58
|
};
|
|
57
59
|
|
|
58
|
-
const convertSingleSortQueryParam = sortQuery => {
|
|
60
|
+
const convertSingleSortQueryParam = (sortQuery) => {
|
|
59
61
|
// split field and order param with default order to ascending
|
|
60
62
|
const [field, order = 'asc'] = sortQuery.split(':');
|
|
61
63
|
|
|
@@ -68,9 +70,9 @@ const convertSingleSortQueryParam = sortQuery => {
|
|
|
68
70
|
return _.set({}, field, order);
|
|
69
71
|
};
|
|
70
72
|
|
|
71
|
-
const convertNestedSortQueryParam = sortQuery => {
|
|
73
|
+
const convertNestedSortQueryParam = (sortQuery) => {
|
|
72
74
|
const transformedSort = {};
|
|
73
|
-
for (const field
|
|
75
|
+
for (const field of Object.keys(sortQuery)) {
|
|
74
76
|
const order = sortQuery[field];
|
|
75
77
|
|
|
76
78
|
// this is a deep sort
|
|
@@ -89,7 +91,7 @@ const convertNestedSortQueryParam = sortQuery => {
|
|
|
89
91
|
* Start query parser
|
|
90
92
|
* @param {string} startQuery
|
|
91
93
|
*/
|
|
92
|
-
const convertStartQueryParams = startQuery => {
|
|
94
|
+
const convertStartQueryParams = (startQuery) => {
|
|
93
95
|
const startAsANumber = _.toNumber(startQuery);
|
|
94
96
|
|
|
95
97
|
if (!_.isInteger(startAsANumber) || startAsANumber < 0) {
|
|
@@ -103,7 +105,7 @@ const convertStartQueryParams = startQuery => {
|
|
|
103
105
|
* Limit query parser
|
|
104
106
|
* @param {string} limitQuery
|
|
105
107
|
*/
|
|
106
|
-
const convertLimitQueryParams = limitQuery => {
|
|
108
|
+
const convertLimitQueryParams = (limitQuery) => {
|
|
107
109
|
const limitAsANumber = _.toNumber(limitQuery);
|
|
108
110
|
|
|
109
111
|
if (!_.isInteger(limitAsANumber) || (limitAsANumber !== -1 && limitAsANumber < 0)) {
|
|
@@ -130,18 +132,18 @@ const convertPopulateQueryParams = (populate, schema, depth = 0) => {
|
|
|
130
132
|
}
|
|
131
133
|
|
|
132
134
|
if (typeof populate === 'string') {
|
|
133
|
-
return populate.split(',').map(value => _.trim(value));
|
|
135
|
+
return populate.split(',').map((value) => _.trim(value));
|
|
134
136
|
}
|
|
135
137
|
|
|
136
138
|
if (Array.isArray(populate)) {
|
|
137
139
|
// map convert
|
|
138
140
|
return _.uniq(
|
|
139
|
-
populate.flatMap(value => {
|
|
141
|
+
populate.flatMap((value) => {
|
|
140
142
|
if (typeof value !== 'string') {
|
|
141
143
|
throw new InvalidPopulateError();
|
|
142
144
|
}
|
|
143
145
|
|
|
144
|
-
return value.split(',').map(value => _.trim(value));
|
|
146
|
+
return value.split(',').map((value) => _.trim(value));
|
|
145
147
|
})
|
|
146
148
|
);
|
|
147
149
|
}
|
|
@@ -171,8 +173,8 @@ const convertPopulateObject = (populate, schema) => {
|
|
|
171
173
|
// fixed when we'll implement a more accurate way to query them
|
|
172
174
|
if (attribute.type === 'dynamiczone') {
|
|
173
175
|
const populates = attribute.components
|
|
174
|
-
.map(uid => strapi.getModel(uid))
|
|
175
|
-
.map(schema => convertNestedPopulate(subPopulate, schema));
|
|
176
|
+
.map((uid) => strapi.getModel(uid))
|
|
177
|
+
.map((schema) => convertNestedPopulate(subPopulate, schema));
|
|
176
178
|
|
|
177
179
|
return {
|
|
178
180
|
...acc,
|
|
@@ -255,13 +257,13 @@ const convertFieldsQueryParams = (fields, depth = 0) => {
|
|
|
255
257
|
}
|
|
256
258
|
|
|
257
259
|
if (typeof fields === 'string') {
|
|
258
|
-
const fieldsValues = fields.split(',').map(value => _.trim(value));
|
|
260
|
+
const fieldsValues = fields.split(',').map((value) => _.trim(value));
|
|
259
261
|
return _.uniq(['id', ...fieldsValues]);
|
|
260
262
|
}
|
|
261
263
|
|
|
262
264
|
if (Array.isArray(fields)) {
|
|
263
265
|
// map convert
|
|
264
|
-
const fieldsValues = fields.flatMap(value => convertFieldsQueryParams(value, depth + 1));
|
|
266
|
+
const fieldsValues = fields.flatMap((value) => convertFieldsQueryParams(value, depth + 1));
|
|
265
267
|
return _.uniq(['id', ...fieldsValues]);
|
|
266
268
|
}
|
|
267
269
|
|
|
@@ -290,13 +292,13 @@ const convertAndSanitizeFilters = (filters, schema) => {
|
|
|
290
292
|
return (
|
|
291
293
|
filters
|
|
292
294
|
// Sanitize each filter
|
|
293
|
-
.map(filter => convertAndSanitizeFilters(filter, schema))
|
|
295
|
+
.map((filter) => convertAndSanitizeFilters(filter, schema))
|
|
294
296
|
// Filter out empty filters
|
|
295
|
-
.filter(filter => !isObject(filter) || !isEmpty(filter))
|
|
297
|
+
.filter((filter) => !isObject(filter) || !isEmpty(filter))
|
|
296
298
|
);
|
|
297
299
|
}
|
|
298
300
|
|
|
299
|
-
const removeOperator = operator => delete filters[operator];
|
|
301
|
+
const removeOperator = (operator) => delete filters[operator];
|
|
300
302
|
|
|
301
303
|
// Here, `key` can either be an operator or an attribute name
|
|
302
304
|
for (const [key, value] of Object.entries(filters)) {
|
|
@@ -324,24 +326,23 @@ const convertAndSanitizeFilters = (filters, schema) => {
|
|
|
324
326
|
removeOperator(key);
|
|
325
327
|
}
|
|
326
328
|
|
|
329
|
+
// Password attributes
|
|
330
|
+
else if (attribute.type === 'password') {
|
|
331
|
+
// Always remove password attributes from filters object
|
|
332
|
+
removeOperator(key);
|
|
333
|
+
}
|
|
334
|
+
|
|
327
335
|
// Scalar attributes
|
|
328
336
|
else {
|
|
329
|
-
|
|
330
|
-
if (attribute.type === 'password') {
|
|
331
|
-
removeOperator(key);
|
|
332
|
-
} else {
|
|
333
|
-
filters[key] = convertAndSanitizeFilters(value, schema);
|
|
334
|
-
}
|
|
337
|
+
filters[key] = convertAndSanitizeFilters(value, schema);
|
|
335
338
|
}
|
|
336
339
|
}
|
|
337
340
|
|
|
338
341
|
// Handle operators
|
|
339
|
-
else {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
filters[key] = convertAndSanitizeFilters(value, schema);
|
|
344
|
-
}
|
|
342
|
+
else if (['$null', '$notNull'].includes(key)) {
|
|
343
|
+
filters[key] = parseType({ type: 'boolean', value: filters[key], forceCast: true });
|
|
344
|
+
} else if (isObject(value)) {
|
|
345
|
+
filters[key] = convertAndSanitizeFilters(value, schema);
|
|
345
346
|
}
|
|
346
347
|
|
|
347
348
|
// Remove empty objects & arrays
|
package/lib/env-helper.js
CHANGED
package/lib/errors.js
CHANGED
package/lib/format-yup-error.js
CHANGED
|
@@ -2,13 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
const { isEmpty, toPath } = require('lodash/fp');
|
|
4
4
|
|
|
5
|
-
const formatYupInnerError = yupError => ({
|
|
5
|
+
const formatYupInnerError = (yupError) => ({
|
|
6
6
|
path: toPath(yupError.path),
|
|
7
7
|
message: yupError.message,
|
|
8
8
|
name: yupError.name,
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
-
const formatYupErrors = yupError => ({
|
|
11
|
+
const formatYupErrors = (yupError) => ({
|
|
12
12
|
errors: isEmpty(yupError.inner)
|
|
13
13
|
? [formatYupInnerError(yupError)]
|
|
14
14
|
: yupError.inner.map(formatYupInnerError),
|
package/lib/hooks.js
CHANGED
|
@@ -85,7 +85,7 @@ const createAsyncParallelHook = () => ({
|
|
|
85
85
|
...createHook(),
|
|
86
86
|
|
|
87
87
|
async call(context) {
|
|
88
|
-
const promises = this.getHandlers().map(handler => handler(cloneDeep(context)));
|
|
88
|
+
const promises = this.getHandlers().map((handler) => handler(cloneDeep(context)));
|
|
89
89
|
|
|
90
90
|
return Promise.all(promises);
|
|
91
91
|
},
|
package/lib/object-formatting.js
CHANGED
package/lib/pagination.js
CHANGED
|
@@ -30,10 +30,12 @@ const ensureMinValues = ({ start, limit }) => ({
|
|
|
30
30
|
limit: limit === -1 ? limit : Math.max(limit, 1),
|
|
31
31
|
});
|
|
32
32
|
|
|
33
|
-
const ensureMaxValues =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
const ensureMaxValues =
|
|
34
|
+
(maxLimit = -1) =>
|
|
35
|
+
({ start, limit }) => ({
|
|
36
|
+
start,
|
|
37
|
+
limit: withMaxLimit(limit, maxLimit),
|
|
38
|
+
});
|
|
37
39
|
|
|
38
40
|
// Apply maxLimit as the limit when limit is -1
|
|
39
41
|
const withNoLimit = (pagination, maxLimit = -1) => ({
|
package/lib/parse-multipart.js
CHANGED
package/lib/parse-type.js
CHANGED
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const dates = require('date-fns');
|
|
5
5
|
|
|
6
|
-
const timeRegex =
|
|
6
|
+
const timeRegex = /^(2[0-3]|[01][0-9]):([0-5][0-9]):([0-5][0-9])(.[0-9]{1,3})?$/;
|
|
7
7
|
|
|
8
|
-
const parseTime = value => {
|
|
8
|
+
const parseTime = (value) => {
|
|
9
9
|
if (dates.isDate(value)) return dates.format(value, 'HH:mm:ss.SSS');
|
|
10
10
|
|
|
11
11
|
if (typeof value !== 'string') {
|
|
@@ -23,10 +23,10 @@ const parseTime = value => {
|
|
|
23
23
|
return `${hours}:${minutes}:${seconds}.${fractionPart}`;
|
|
24
24
|
};
|
|
25
25
|
|
|
26
|
-
const parseDate = value => {
|
|
26
|
+
const parseDate = (value) => {
|
|
27
27
|
if (dates.isDate(value)) return dates.format(value, 'yyyy-MM-dd');
|
|
28
28
|
try {
|
|
29
|
-
|
|
29
|
+
const date = dates.parseISO(value);
|
|
30
30
|
|
|
31
31
|
if (dates.isValid(date)) return dates.format(date, 'yyyy-MM-dd');
|
|
32
32
|
|
|
@@ -36,7 +36,7 @@ const parseDate = value => {
|
|
|
36
36
|
}
|
|
37
37
|
};
|
|
38
38
|
|
|
39
|
-
const parseDateTimeOrTimestamp = value => {
|
|
39
|
+
const parseDateTimeOrTimestamp = (value) => {
|
|
40
40
|
if (dates.isDate(value)) return value;
|
|
41
41
|
try {
|
|
42
42
|
const date = dates.parseISO(value);
|
package/lib/pipe-async.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
module.exports =
|
|
4
|
-
|
|
3
|
+
module.exports =
|
|
4
|
+
(...methods) =>
|
|
5
|
+
async (data) => {
|
|
6
|
+
let res = data;
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
for (const method of methods) {
|
|
9
|
+
res = await method(res);
|
|
10
|
+
}
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
};
|
|
12
|
+
return res;
|
|
13
|
+
};
|
package/lib/policy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Policies util
|
|
3
3
|
*/
|
|
4
|
+
|
|
4
5
|
'use strict';
|
|
5
6
|
|
|
6
7
|
const _ = require('lodash');
|
|
@@ -9,7 +10,7 @@ const { eq } = require('lodash/fp');
|
|
|
9
10
|
const PLUGIN_PREFIX = 'plugin::';
|
|
10
11
|
const API_PREFIX = 'api::';
|
|
11
12
|
|
|
12
|
-
const parsePolicy = policy => {
|
|
13
|
+
const parsePolicy = (policy) => {
|
|
13
14
|
if (typeof policy === 'string') {
|
|
14
15
|
return { policyName: policy, config: {} };
|
|
15
16
|
}
|
|
@@ -43,7 +44,7 @@ const globalPolicy = ({ method, endpoint, controller, action, plugin }) => {
|
|
|
43
44
|
};
|
|
44
45
|
|
|
45
46
|
const resolvePolicies = (config, { pluginName, apiName } = {}) => {
|
|
46
|
-
return config.map(policyConfig => {
|
|
47
|
+
return config.map((policyConfig) => {
|
|
47
48
|
return {
|
|
48
49
|
handler: getPolicy(policyConfig, { pluginName, apiName }),
|
|
49
50
|
config: policyConfig.config || {},
|
|
@@ -87,10 +88,10 @@ const getPolicy = (policyConfig, { pluginName, apiName } = {}) => {
|
|
|
87
88
|
return policy.handler;
|
|
88
89
|
};
|
|
89
90
|
|
|
90
|
-
const createPolicy = options => {
|
|
91
|
+
const createPolicy = (options) => {
|
|
91
92
|
const { name = 'unnamed', validator, handler } = options;
|
|
92
93
|
|
|
93
|
-
const wrappedValidator = config => {
|
|
94
|
+
const wrappedValidator = (config) => {
|
|
94
95
|
if (validator) {
|
|
95
96
|
try {
|
|
96
97
|
validator(config);
|
package/lib/print-value.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Code copied from the yup library (https://github.com/jquense/yup)
|
|
4
4
|
// https://github.com/jquense/yup/blob/2778b88bdacd5260d593c6468793da2e77daf21f/src/util/printValue.ts
|
|
5
5
|
|
|
6
|
-
const toString = Object.prototype
|
|
6
|
+
const { toString } = Object.prototype;
|
|
7
7
|
const errorToString = Error.prototype.toString;
|
|
8
8
|
const regExpToString = RegExp.prototype.toString;
|
|
9
9
|
const symbolToString = typeof Symbol !== 'undefined' ? Symbol.prototype.toString : () => '';
|
|
@@ -13,34 +13,34 @@ const SYMBOL_REGEXP = /^Symbol\((.*)\)(.*)$/;
|
|
|
13
13
|
function printNumber(val) {
|
|
14
14
|
if (val != +val) return 'NaN';
|
|
15
15
|
const isNegativeZero = val === 0 && 1 / val < 0;
|
|
16
|
-
return isNegativeZero ? '-0' :
|
|
16
|
+
return isNegativeZero ? '-0' : `${val}`;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
function printSimpleValue(val, quoteStrings = false) {
|
|
20
|
-
if (val == null || val === true || val === false) return
|
|
20
|
+
if (val == null || val === true || val === false) return `${val}`;
|
|
21
21
|
|
|
22
22
|
const typeOf = typeof val;
|
|
23
23
|
if (typeOf === 'number') return printNumber(val);
|
|
24
24
|
if (typeOf === 'string') return quoteStrings ? `"${val}"` : val;
|
|
25
|
-
if (typeOf === 'function') return
|
|
25
|
+
if (typeOf === 'function') return `[Function ${val.name || 'anonymous'}]`;
|
|
26
26
|
if (typeOf === 'symbol') return symbolToString.call(val).replace(SYMBOL_REGEXP, 'Symbol($1)');
|
|
27
27
|
|
|
28
28
|
const tag = toString.call(val).slice(8, -1);
|
|
29
|
-
if (tag === 'Date') return isNaN(val.getTime()) ?
|
|
30
|
-
if (tag === 'Error' || val instanceof Error) return
|
|
29
|
+
if (tag === 'Date') return Number.isNaN(val.getTime()) ? `${val}` : val.toISOString(val);
|
|
30
|
+
if (tag === 'Error' || val instanceof Error) return `[${errorToString.call(val)}]`;
|
|
31
31
|
if (tag === 'RegExp') return regExpToString.call(val);
|
|
32
32
|
|
|
33
33
|
return null;
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
function printValue(value, quoteStrings) {
|
|
37
|
-
|
|
37
|
+
const result = printSimpleValue(value, quoteStrings);
|
|
38
38
|
if (result !== null) return result;
|
|
39
39
|
|
|
40
40
|
return JSON.stringify(
|
|
41
41
|
value,
|
|
42
|
-
function(key, value) {
|
|
43
|
-
|
|
42
|
+
function replacer(key, value) {
|
|
43
|
+
const result = printSimpleValue(this[key], quoteStrings);
|
|
44
44
|
if (result !== null) return result;
|
|
45
45
|
return value;
|
|
46
46
|
},
|
package/lib/provider-factory.js
CHANGED
package/lib/relations.js
CHANGED
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const MANY_RELATIONS = ['oneToMany', 'manyToMany'];
|
|
4
4
|
|
|
5
|
-
const getRelationalFields = contentType => {
|
|
6
|
-
return Object.keys(contentType.attributes).filter(attributeName => {
|
|
5
|
+
const getRelationalFields = (contentType) => {
|
|
6
|
+
return Object.keys(contentType.attributes).filter((attributeName) => {
|
|
7
7
|
return contentType.attributes[attributeName].type === 'relation';
|
|
8
8
|
});
|
|
9
9
|
};
|
package/lib/sanitize/index.js
CHANGED
|
@@ -13,7 +13,7 @@ module.exports = {
|
|
|
13
13
|
contentAPI: {
|
|
14
14
|
input(data, schema, { auth } = {}) {
|
|
15
15
|
if (isArray(data)) {
|
|
16
|
-
return Promise.all(data.map(entry => this.input(entry, schema, { auth })));
|
|
16
|
+
return Promise.all(data.map((entry) => this.input(entry, schema, { auth })));
|
|
17
17
|
}
|
|
18
18
|
|
|
19
19
|
const nonWritableAttributes = getNonWritableAttributes(schema);
|
|
@@ -31,14 +31,14 @@ module.exports = {
|
|
|
31
31
|
// Apply sanitizers from registry if exists
|
|
32
32
|
strapi.sanitizers
|
|
33
33
|
.get('content-api.input')
|
|
34
|
-
.forEach(sanitizer => transforms.push(sanitizer(schema)));
|
|
34
|
+
.forEach((sanitizer) => transforms.push(sanitizer(schema)));
|
|
35
35
|
|
|
36
36
|
return pipeAsync(...transforms)(data);
|
|
37
37
|
},
|
|
38
38
|
|
|
39
39
|
output(data, schema, { auth } = {}) {
|
|
40
40
|
if (isArray(data)) {
|
|
41
|
-
return Promise.all(data.map(entry => this.output(entry, schema, { auth })));
|
|
41
|
+
return Promise.all(data.map((entry) => this.output(entry, schema, { auth })));
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
const transforms = [sanitizers.defaultSanitizeOutput(schema)];
|
|
@@ -50,7 +50,7 @@ module.exports = {
|
|
|
50
50
|
// Apply sanitizers from registry if exists
|
|
51
51
|
strapi.sanitizers
|
|
52
52
|
.get('content-api.output')
|
|
53
|
-
.forEach(sanitizer => transforms.push(sanitizer(schema)));
|
|
53
|
+
.forEach((sanitizer) => transforms.push(sanitizer(schema)));
|
|
54
54
|
|
|
55
55
|
return pipeAsync(...transforms)(data);
|
|
56
56
|
},
|
|
@@ -2,68 +2,70 @@
|
|
|
2
2
|
|
|
3
3
|
const { isArray, toPath } = require('lodash/fp');
|
|
4
4
|
|
|
5
|
-
module.exports =
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
module.exports =
|
|
6
|
+
(allowedFields = null) =>
|
|
7
|
+
({ key, path }, { remove }) => {
|
|
8
|
+
// All fields are allowed
|
|
9
|
+
if (allowedFields === null) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
// Ignore invalid formats
|
|
14
|
+
if (!isArray(allowedFields)) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
const containedPaths = getContainedPaths(path);
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
20
|
+
/**
|
|
21
|
+
* Tells if the current path should be kept or not based
|
|
22
|
+
* on the success of the check functions for any of the allowed paths.
|
|
23
|
+
*
|
|
24
|
+
* The check functions are defined as follow:
|
|
25
|
+
*
|
|
26
|
+
* `containedPaths.includes(p)`
|
|
27
|
+
* @example
|
|
28
|
+
* ```js
|
|
29
|
+
* const path = 'foo.bar.field';
|
|
30
|
+
* const p = 'foo.bar';
|
|
31
|
+
* // it should match
|
|
32
|
+
*
|
|
33
|
+
* const path = 'foo.bar.field';
|
|
34
|
+
* const p = 'bar.foo';
|
|
35
|
+
* // it shouldn't match
|
|
36
|
+
*
|
|
37
|
+
* const path = 'foo.bar';
|
|
38
|
+
* const p = 'foo.bar.field';
|
|
39
|
+
* // it should match but isn't handled by this check
|
|
40
|
+
* ```
|
|
41
|
+
*
|
|
42
|
+
* `p.startsWith(`${path}.`)`
|
|
43
|
+
* @example
|
|
44
|
+
* ```js
|
|
45
|
+
* const path = 'foo.bar';
|
|
46
|
+
* const p = 'foo.bar.field';
|
|
47
|
+
* // it should match
|
|
48
|
+
*
|
|
49
|
+
* const path = 'foo.bar.field';
|
|
50
|
+
* const p = 'bar.foo';
|
|
51
|
+
* // it shouldn't match
|
|
52
|
+
*
|
|
53
|
+
* const path = 'foo.bar.field';
|
|
54
|
+
* const p = 'foo.bar';
|
|
55
|
+
* // it should match but isn't handled by this check
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
const isPathAllowed = allowedFields.some(
|
|
59
|
+
(p) => containedPaths.includes(p) || p.startsWith(`${path}.`)
|
|
60
|
+
);
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
62
|
+
if (isPathAllowed) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
63
65
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
};
|
|
66
|
+
// Remove otherwise
|
|
67
|
+
remove(key);
|
|
68
|
+
};
|
|
67
69
|
|
|
68
70
|
/**
|
|
69
71
|
* Retrieve the list of allowed paths based on the given path
|
|
@@ -83,7 +85,7 @@ module.exports = (allowedFields = null) => ({ key, path }, { remove }) => {
|
|
|
83
85
|
* // ['foo', 'foo.bar', 'foo.bar.field']
|
|
84
86
|
* ```
|
|
85
87
|
*/
|
|
86
|
-
const getContainedPaths = path => {
|
|
88
|
+
const getContainedPaths = (path) => {
|
|
87
89
|
const parts = toPath(path);
|
|
88
90
|
|
|
89
91
|
return parts.reduce((acc, value, index, list) => {
|
|
@@ -4,62 +4,64 @@ const ACTIONS_TO_VERIFY = ['find'];
|
|
|
4
4
|
|
|
5
5
|
const { CREATED_BY_ATTRIBUTE, UPDATED_BY_ATTRIBUTE } = require('../../content-types').constants;
|
|
6
6
|
|
|
7
|
-
module.exports =
|
|
8
|
-
|
|
7
|
+
module.exports =
|
|
8
|
+
(auth) =>
|
|
9
|
+
async ({ data, key, attribute, schema }, { remove, set }) => {
|
|
10
|
+
const isRelation = attribute.type === 'relation';
|
|
9
11
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
12
|
+
if (!isRelation) {
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
+
const handleMorphRelation = async () => {
|
|
17
|
+
const newMorphValue = [];
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
19
|
+
for (const element of data[key]) {
|
|
20
|
+
const scopes = ACTIONS_TO_VERIFY.map((action) => `${element.__type}.${action}`);
|
|
21
|
+
const isAllowed = await hasAccessToSomeScopes(scopes, auth);
|
|
20
22
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
if (isAllowed) {
|
|
24
|
+
newMorphValue.push(element);
|
|
25
|
+
}
|
|
23
26
|
}
|
|
24
|
-
}
|
|
25
27
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
28
|
+
// If the new value is empty, remove the relation completely
|
|
29
|
+
if (newMorphValue.length === 0) {
|
|
30
|
+
remove(key);
|
|
31
|
+
} else {
|
|
32
|
+
set(key, newMorphValue);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
33
35
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
+
const handleRegularRelation = async () => {
|
|
37
|
+
const scopes = ACTIONS_TO_VERIFY.map((action) => `${attribute.target}.${action}`);
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
const isAllowed = await hasAccessToSomeScopes(scopes, auth);
|
|
38
40
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
// If the authenticated user don't have access to any of the scopes, then remove the field
|
|
42
|
+
if (!isAllowed) {
|
|
43
|
+
remove(key);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
const isMorphRelation = attribute.relation.toLowerCase().startsWith('morph');
|
|
48
|
+
const isCreatorRelation = [CREATED_BY_ATTRIBUTE, UPDATED_BY_ATTRIBUTE].includes(key);
|
|
47
49
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
50
|
+
// Polymorphic relations
|
|
51
|
+
if (isMorphRelation) {
|
|
52
|
+
await handleMorphRelation();
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
53
55
|
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
// Creator relations
|
|
57
|
+
if (isCreatorRelation && schema.options.populateCreatorFields) {
|
|
58
|
+
// do nothing
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
59
61
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
};
|
|
62
|
+
// Regular relations
|
|
63
|
+
await handleRegularRelation();
|
|
64
|
+
};
|
|
63
65
|
|
|
64
66
|
const hasAccessToSomeScopes = async (scopes, auth) => {
|
|
65
67
|
for (const scope of scopes) {
|
|
@@ -2,30 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
const { isArray } = require('lodash/fp');
|
|
4
4
|
|
|
5
|
-
module.exports =
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
module.exports =
|
|
6
|
+
(restrictedFields = null) =>
|
|
7
|
+
({ key, path }, { remove }) => {
|
|
8
|
+
// Remove all fields
|
|
9
|
+
if (restrictedFields === null) {
|
|
10
|
+
remove(key);
|
|
11
|
+
return;
|
|
12
|
+
}
|
|
11
13
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
14
|
+
// Ignore invalid formats
|
|
15
|
+
if (!isArray(restrictedFields)) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
16
18
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
// Remove if an exact match was found
|
|
20
|
+
if (restrictedFields.includes(path)) {
|
|
21
|
+
remove(key);
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
22
24
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
31
|
-
};
|
|
25
|
+
// Remove nested matches
|
|
26
|
+
const isRestrictedNested = restrictedFields.some((allowedPath) =>
|
|
27
|
+
path.startsWith(`${allowedPath}.`)
|
|
28
|
+
);
|
|
29
|
+
if (isRestrictedNested) {
|
|
30
|
+
remove(key);
|
|
31
|
+
}
|
|
32
|
+
};
|
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
const { assign, assoc } = require('lodash/fp');
|
|
4
4
|
const { CREATED_BY_ATTRIBUTE, UPDATED_BY_ATTRIBUTE } = require('./content-types').constants;
|
|
5
5
|
|
|
6
|
-
module.exports =
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
6
|
+
module.exports =
|
|
7
|
+
({ user, isEdition = false }) =>
|
|
8
|
+
(data) => {
|
|
9
|
+
if (isEdition) {
|
|
10
|
+
return assoc(UPDATED_BY_ATTRIBUTE, user.id, data);
|
|
11
|
+
}
|
|
10
12
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
};
|
|
13
|
+
return assign(data, {
|
|
14
|
+
[CREATED_BY_ATTRIBUTE]: user.id,
|
|
15
|
+
[UPDATED_BY_ATTRIBUTE]: user.id,
|
|
16
|
+
});
|
|
17
|
+
};
|
package/lib/string-formatting.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
|
|
2
3
|
const _ = require('lodash');
|
|
3
4
|
const { trimChars, trimCharsEnd, trimCharsStart } = require('lodash/fp');
|
|
4
5
|
const slugify = require('@sindresorhus/slugify');
|
|
@@ -6,9 +7,9 @@ const { kebabCase } = require('lodash');
|
|
|
6
7
|
|
|
7
8
|
const nameToSlug = (name, options = { separator: '-' }) => slugify(name, options);
|
|
8
9
|
|
|
9
|
-
const nameToCollectionName = name => slugify(name, { separator: '_' });
|
|
10
|
+
const nameToCollectionName = (name) => slugify(name, { separator: '_' });
|
|
10
11
|
|
|
11
|
-
const toRegressedEnumValue = value =>
|
|
12
|
+
const toRegressedEnumValue = (value) =>
|
|
12
13
|
slugify(value, {
|
|
13
14
|
decamelize: false,
|
|
14
15
|
lowercase: false,
|
|
@@ -16,14 +17,14 @@ const toRegressedEnumValue = value =>
|
|
|
16
17
|
});
|
|
17
18
|
|
|
18
19
|
const getCommonBeginning = (...strings) =>
|
|
19
|
-
_.takeWhile(strings[0], (char, index) => strings.every(string => string[index] === char)).join(
|
|
20
|
+
_.takeWhile(strings[0], (char, index) => strings.every((string) => string[index] === char)).join(
|
|
20
21
|
''
|
|
21
22
|
);
|
|
22
23
|
|
|
23
24
|
const getCommonPath = (...paths) => {
|
|
24
|
-
const [segments, ...otherSegments] = paths.map(it => _.split(it, '/'));
|
|
25
|
+
const [segments, ...otherSegments] = paths.map((it) => _.split(it, '/'));
|
|
25
26
|
return _.join(
|
|
26
|
-
_.takeWhile(segments, (str, index) => otherSegments.every(it => it[index] === str)),
|
|
27
|
+
_.takeWhile(segments, (str, index) => otherSegments.every((it) => it[index] === str)),
|
|
27
28
|
'/'
|
|
28
29
|
);
|
|
29
30
|
};
|
|
@@ -42,9 +43,9 @@ const escapeQuery = (query, charsToEscape, escapeChar = '\\') => {
|
|
|
42
43
|
|
|
43
44
|
const stringIncludes = (arr, val) => arr.map(String).includes(String(val));
|
|
44
45
|
const stringEquals = (a, b) => String(a) === String(b);
|
|
45
|
-
const isCamelCase = value => /^[a-z][a-zA-Z0-9]+$/.test(value);
|
|
46
|
-
const isKebabCase = value => /^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/.test(value);
|
|
47
|
-
const startsWithANumber = value => /^[0-9]/.test(value);
|
|
46
|
+
const isCamelCase = (value) => /^[a-z][a-zA-Z0-9]+$/.test(value);
|
|
47
|
+
const isKebabCase = (value) => /^([a-z][a-z0-9]*)(-[a-z0-9]+)*$/.test(value);
|
|
48
|
+
const startsWithANumber = (value) => /^[0-9]/.test(value);
|
|
48
49
|
|
|
49
50
|
const joinBy = (joint, ...args) => {
|
|
50
51
|
const trim = trimChars(joint);
|
|
@@ -59,7 +60,7 @@ const joinBy = (joint, ...args) => {
|
|
|
59
60
|
}, '');
|
|
60
61
|
};
|
|
61
62
|
|
|
62
|
-
const toKebabCase = value => kebabCase(value);
|
|
63
|
+
const toKebabCase = (value) => kebabCase(value);
|
|
63
64
|
|
|
64
65
|
module.exports = {
|
|
65
66
|
nameToSlug,
|
|
@@ -19,7 +19,7 @@ const templateConfiguration = (obj, configPath = '') => {
|
|
|
19
19
|
!excludeConfigPaths.includes(configPath.substr(1)) &&
|
|
20
20
|
obj[key].match(regex) !== null
|
|
21
21
|
) {
|
|
22
|
-
// eslint-disable-next-line prefer-template
|
|
22
|
+
// eslint-disable-next-line prefer-template, no-eval
|
|
23
23
|
acc[key] = eval('`' + obj[key] + '`');
|
|
24
24
|
} else {
|
|
25
25
|
acc[key] = obj[key];
|
package/lib/traverse-entity.js
CHANGED
|
@@ -46,7 +46,7 @@ const traverseEntity = async (visitor, options, entity) => {
|
|
|
46
46
|
if (isRelation) {
|
|
47
47
|
const isMorphRelation = attribute.relation.toLowerCase().startsWith('morph');
|
|
48
48
|
|
|
49
|
-
const traverseTarget = entry => {
|
|
49
|
+
const traverseTarget = (entry) => {
|
|
50
50
|
// Handle polymorphic relationships
|
|
51
51
|
const targetSchemaUID = isMorphRelation ? entry.__type : attribute.target;
|
|
52
52
|
const targetSchema = strapi.getModel(targetSchemaUID);
|
|
@@ -63,7 +63,7 @@ const traverseEntity = async (visitor, options, entity) => {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
if (isMedia) {
|
|
66
|
-
const traverseTarget = entry => {
|
|
66
|
+
const traverseTarget = (entry) => {
|
|
67
67
|
const targetSchemaUID = 'plugin::upload.file';
|
|
68
68
|
const targetSchema = strapi.getModel(targetSchemaUID);
|
|
69
69
|
|
|
@@ -82,7 +82,7 @@ const traverseEntity = async (visitor, options, entity) => {
|
|
|
82
82
|
const targetSchema = strapi.getModel(attribute.component);
|
|
83
83
|
const traverseOptions = { schema: targetSchema, path: newPath };
|
|
84
84
|
|
|
85
|
-
const traverseComponent = entry => traverseEntity(visitor, traverseOptions, entry);
|
|
85
|
+
const traverseComponent = (entry) => traverseEntity(visitor, traverseOptions, entry);
|
|
86
86
|
|
|
87
87
|
copy[key] = isArray(value)
|
|
88
88
|
? await Promise.all(value.map(traverseComponent))
|
|
@@ -90,7 +90,7 @@ const traverseEntity = async (visitor, options, entity) => {
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
if (isDynamicZone && isArray(value)) {
|
|
93
|
-
const visitDynamicZoneEntry = entry => {
|
|
93
|
+
const visitDynamicZoneEntry = (entry) => {
|
|
94
94
|
const targetSchema = strapi.getModel(entry.__component);
|
|
95
95
|
const traverseOptions = { schema: targetSchema, path: newPath };
|
|
96
96
|
|
package/lib/validators.js
CHANGED
|
@@ -9,34 +9,38 @@ const printValue = require('./print-value');
|
|
|
9
9
|
|
|
10
10
|
const MixedSchemaType = yup.MixedSchema;
|
|
11
11
|
|
|
12
|
-
const isNotNilTest = value => !_.isNil(value);
|
|
12
|
+
const isNotNilTest = (value) => !_.isNil(value);
|
|
13
13
|
|
|
14
14
|
function isNotNill(msg = '${path} must be defined.') {
|
|
15
15
|
return this.test('defined', msg, isNotNilTest);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
const isNotNullTest = value => !_.isNull(value);
|
|
18
|
+
const isNotNullTest = (value) => !_.isNull(value);
|
|
19
19
|
function isNotNull(msg = '${path} cannot be null.') {
|
|
20
20
|
return this.test('defined', msg, isNotNullTest);
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
function isFunction(message = '${path} is not a function') {
|
|
24
|
-
return this.test(
|
|
24
|
+
return this.test(
|
|
25
|
+
'is a function',
|
|
26
|
+
message,
|
|
27
|
+
(value) => _.isUndefined(value) || _.isFunction(value)
|
|
28
|
+
);
|
|
25
29
|
}
|
|
26
30
|
|
|
27
31
|
function isCamelCase(message = '${path} is not in camel case (anExampleOfCamelCase)') {
|
|
28
|
-
return this.test('is in camelCase', message, value => utils.isCamelCase(value));
|
|
32
|
+
return this.test('is in camelCase', message, (value) => utils.isCamelCase(value));
|
|
29
33
|
}
|
|
30
34
|
|
|
31
35
|
function isKebabCase(message = '${path} is not in kebab case (an-example-of-kebab-case)') {
|
|
32
|
-
return this.test('is in kebab-case', message, value => utils.isKebabCase(value));
|
|
36
|
+
return this.test('is in kebab-case', message, (value) => utils.isKebabCase(value));
|
|
33
37
|
}
|
|
34
38
|
|
|
35
39
|
function onlyContainsFunctions(message = '${path} contains values that are not functions') {
|
|
36
40
|
return this.test(
|
|
37
41
|
'only contains functions',
|
|
38
42
|
message,
|
|
39
|
-
value => _.isUndefined(value) || (value && Object.values(value).every(_.isFunction))
|
|
43
|
+
(value) => _.isUndefined(value) || (value && Object.values(value).every(_.isFunction))
|
|
40
44
|
);
|
|
41
45
|
}
|
|
42
46
|
|
|
@@ -65,33 +69,38 @@ const handleYupError = (error, errorMessage) => {
|
|
|
65
69
|
|
|
66
70
|
const defaultValidationParam = { strict: true, abortEarly: false };
|
|
67
71
|
|
|
68
|
-
const validateYupSchema =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
72
|
+
const validateYupSchema =
|
|
73
|
+
(schema, options = {}) =>
|
|
74
|
+
async (body, errorMessage) => {
|
|
75
|
+
try {
|
|
76
|
+
const optionsWithDefaults = defaults(defaultValidationParam, options);
|
|
77
|
+
return await schema.validate(body, optionsWithDefaults);
|
|
78
|
+
} catch (e) {
|
|
79
|
+
handleYupError(e, errorMessage);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const validateYupSchemaSync =
|
|
84
|
+
(schema, options = {}) =>
|
|
85
|
+
(body, errorMessage) => {
|
|
86
|
+
try {
|
|
87
|
+
const optionsWithDefaults = defaults(defaultValidationParam, options);
|
|
88
|
+
return schema.validateSync(body, optionsWithDefaults);
|
|
89
|
+
} catch (e) {
|
|
90
|
+
handleYupError(e, errorMessage);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
85
93
|
|
|
86
94
|
// Temporary fix of this issue : https://github.com/jquense/yup/issues/616
|
|
87
95
|
yup.setLocale({
|
|
88
96
|
mixed: {
|
|
89
97
|
notType({ path, type, value, originalValue }) {
|
|
90
|
-
|
|
91
|
-
|
|
98
|
+
const isCast = originalValue != null && originalValue !== value;
|
|
99
|
+
const msg =
|
|
92
100
|
`${path} must be a \`${type}\` type, ` +
|
|
93
|
-
`but the final value was: \`${printValue(value, true)}
|
|
94
|
-
|
|
101
|
+
`but the final value was: \`${printValue(value, true)}\`${
|
|
102
|
+
isCast ? ` (cast from the value \`${printValue(originalValue, true)}\`).` : '.'
|
|
103
|
+
}`;
|
|
95
104
|
|
|
96
105
|
/* Remove comment that is not supposed to be seen by the enduser
|
|
97
106
|
if (value === null) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@strapi/utils",
|
|
3
|
-
"version": "4.3.
|
|
3
|
+
"version": "4.3.5",
|
|
4
4
|
"description": "Shared utilities for the Strapi packages",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
38
|
"@sindresorhus/slugify": "1.1.0",
|
|
39
|
-
"date-fns": "2.
|
|
39
|
+
"date-fns": "2.29.2",
|
|
40
40
|
"http-errors": "1.8.1",
|
|
41
41
|
"lodash": "4.17.21",
|
|
42
42
|
"yup": "0.32.9"
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"node": ">=14.19.1 <=16.x.x",
|
|
46
46
|
"npm": ">=6.0.0"
|
|
47
47
|
},
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "a52b0535513ff35b4ef46c9daf7be48f2ba71737"
|
|
49
49
|
}
|