@strapi/utils 4.0.0-next.9 → 4.0.3
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/lib/build-query.js +1 -1
- package/lib/config.js +6 -3
- package/lib/content-types.js +21 -9
- package/lib/convert-query-params.js +48 -29
- package/lib/errors.js +82 -0
- package/lib/format-yup-error.js +20 -0
- package/lib/hooks.js +5 -3
- package/lib/index.js +14 -7
- package/lib/pagination.js +100 -0
- package/lib/parse-multipart.js +3 -3
- package/lib/pipe-async.js +11 -0
- package/lib/policy.js +66 -51
- package/lib/print-value.js +51 -0
- package/lib/sanitize/index.js +51 -0
- package/lib/sanitize/sanitizers.js +26 -0
- package/lib/sanitize/visitors/allowed-fields.js +92 -0
- package/lib/sanitize/visitors/index.js +9 -0
- package/lib/sanitize/visitors/remove-password.js +7 -0
- package/lib/sanitize/visitors/remove-private.js +11 -0
- package/lib/sanitize/visitors/remove-restricted-relations.js +67 -0
- package/lib/sanitize/visitors/restricted-fields.js +31 -0
- package/lib/traverse-entity.js +100 -0
- package/lib/validators.js +45 -16
- package/package.json +25 -25
- package/lib/sanitize-entity.js +0 -172
package/lib/build-query.js
CHANGED
|
@@ -60,7 +60,7 @@ const getAssociationFromFieldKey = ({ model, field }) => {
|
|
|
60
60
|
const castInput = ({ type, value, operator }) => {
|
|
61
61
|
return Array.isArray(value)
|
|
62
62
|
? value.map(val => castValue({ type, operator, value: val }))
|
|
63
|
-
: castValue({ type, operator, value
|
|
63
|
+
: castValue({ type, operator, value });
|
|
64
64
|
};
|
|
65
65
|
|
|
66
66
|
/**
|
package/lib/config.js
CHANGED
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
4
|
const { getCommonPath } = require('./string-formatting');
|
|
5
5
|
|
|
6
|
-
const getConfigUrls = (
|
|
6
|
+
const getConfigUrls = (config, forAdminBuild = false) => {
|
|
7
|
+
const serverConfig = config.get('server');
|
|
8
|
+
const adminConfig = config.get('admin');
|
|
9
|
+
|
|
7
10
|
// Defines serverUrl value
|
|
8
11
|
let serverUrl = _.get(serverConfig, 'url', '');
|
|
9
12
|
serverUrl = _.trim(serverUrl, '/ ');
|
|
@@ -23,7 +26,7 @@ const getConfigUrls = (serverConfig, forAdminBuild = false) => {
|
|
|
23
26
|
}
|
|
24
27
|
|
|
25
28
|
// Defines adminUrl value
|
|
26
|
-
let adminUrl = _.get(
|
|
29
|
+
let adminUrl = _.get(adminConfig, 'url', '/admin');
|
|
27
30
|
adminUrl = _.trim(adminUrl, '/ ');
|
|
28
31
|
if (typeof adminUrl !== 'string') {
|
|
29
32
|
throw new Error('Invalid admin url config. Make sure the url is a non-empty string.');
|
|
@@ -60,7 +63,7 @@ const getConfigUrls = (serverConfig, forAdminBuild = false) => {
|
|
|
60
63
|
};
|
|
61
64
|
|
|
62
65
|
const getAbsoluteUrl = adminOrServer => (config, forAdminBuild = false) => {
|
|
63
|
-
const { serverUrl, adminUrl } = getConfigUrls(config
|
|
66
|
+
const { serverUrl, adminUrl } = getConfigUrls(config, forAdminBuild);
|
|
64
67
|
let url = adminOrServer === 'server' ? serverUrl : adminUrl;
|
|
65
68
|
|
|
66
69
|
if (url.startsWith('http')) {
|
package/lib/content-types.js
CHANGED
|
@@ -1,17 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const _ = require('lodash');
|
|
4
|
+
const { has } = require('lodash/fp');
|
|
4
5
|
|
|
5
6
|
const SINGLE_TYPE = 'singleType';
|
|
6
7
|
const COLLECTION_TYPE = 'collectionType';
|
|
7
8
|
|
|
8
9
|
const ID_ATTRIBUTE = 'id';
|
|
9
|
-
const PUBLISHED_AT_ATTRIBUTE = '
|
|
10
|
-
const CREATED_BY_ATTRIBUTE = '
|
|
11
|
-
const UPDATED_BY_ATTRIBUTE = '
|
|
10
|
+
const PUBLISHED_AT_ATTRIBUTE = 'publishedAt';
|
|
11
|
+
const CREATED_BY_ATTRIBUTE = 'createdBy';
|
|
12
|
+
const UPDATED_BY_ATTRIBUTE = 'updatedBy';
|
|
12
13
|
|
|
13
|
-
const CREATED_AT_ATTRIBUTE = '
|
|
14
|
-
const UPDATED_AT_ATTRIBUTE = '
|
|
14
|
+
const CREATED_AT_ATTRIBUTE = 'createdAt';
|
|
15
|
+
const UPDATED_AT_ATTRIBUTE = 'updatedAt';
|
|
15
16
|
|
|
16
17
|
const DP_PUB_STATE_LIVE = 'live';
|
|
17
18
|
const DP_PUB_STATE_PREVIEW = 'preview';
|
|
@@ -31,8 +32,18 @@ const constants = {
|
|
|
31
32
|
COLLECTION_TYPE,
|
|
32
33
|
};
|
|
33
34
|
|
|
34
|
-
const getTimestamps =
|
|
35
|
-
|
|
35
|
+
const getTimestamps = model => {
|
|
36
|
+
const attributes = [];
|
|
37
|
+
|
|
38
|
+
if (has(CREATED_AT_ATTRIBUTE, model.attributes)) {
|
|
39
|
+
attributes.push(CREATED_AT_ATTRIBUTE);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (has(UPDATED_AT_ATTRIBUTE, model.attributes)) {
|
|
43
|
+
attributes.push(UPDATED_AT_ATTRIBUTE);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return attributes;
|
|
36
47
|
};
|
|
37
48
|
|
|
38
49
|
const getNonWritableAttributes = (model = {}) => {
|
|
@@ -42,7 +53,7 @@ const getNonWritableAttributes = (model = {}) => {
|
|
|
42
53
|
[]
|
|
43
54
|
);
|
|
44
55
|
|
|
45
|
-
return _.uniq([ID_ATTRIBUTE, ...getTimestamps(), ...nonWritableAttributes]);
|
|
56
|
+
return _.uniq([ID_ATTRIBUTE, ...getTimestamps(model), ...nonWritableAttributes]);
|
|
46
57
|
};
|
|
47
58
|
|
|
48
59
|
const getWritableAttributes = (model = {}) => {
|
|
@@ -60,7 +71,7 @@ const getNonVisibleAttributes = model => {
|
|
|
60
71
|
[]
|
|
61
72
|
);
|
|
62
73
|
|
|
63
|
-
return _.uniq([ID_ATTRIBUTE, ...getTimestamps(), ...nonVisibleAttributes]);
|
|
74
|
+
return _.uniq([ID_ATTRIBUTE, ...getTimestamps(model), ...nonVisibleAttributes]);
|
|
64
75
|
};
|
|
65
76
|
|
|
66
77
|
const getVisibleAttributes = model => {
|
|
@@ -137,6 +148,7 @@ module.exports = {
|
|
|
137
148
|
isWritableAttribute,
|
|
138
149
|
getNonVisibleAttributes,
|
|
139
150
|
getVisibleAttributes,
|
|
151
|
+
getTimestamps,
|
|
140
152
|
isVisibleAttribute,
|
|
141
153
|
hasDraftAndPublish,
|
|
142
154
|
isDraft,
|
|
@@ -2,12 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Converts the standard Strapi REST query params to a more usable format for querying
|
|
5
|
-
* You can read more here: https://strapi.io/
|
|
5
|
+
* You can read more here: https://docs.strapi.io/developer-docs/latest/developer-resources/database-apis-reference/rest-api.html#filters
|
|
6
6
|
*/
|
|
7
|
-
|
|
7
|
+
const { has } = require('lodash/fp');
|
|
8
8
|
const _ = require('lodash');
|
|
9
|
+
const parseType = require('./parse-type');
|
|
10
|
+
const contentTypesUtils = require('./content-types');
|
|
9
11
|
|
|
10
|
-
const
|
|
12
|
+
const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
|
|
11
13
|
|
|
12
14
|
class InvalidOrderError extends Error {
|
|
13
15
|
constructor() {
|
|
@@ -29,6 +31,10 @@ const validateOrder = order => {
|
|
|
29
31
|
}
|
|
30
32
|
};
|
|
31
33
|
|
|
34
|
+
const convertCountQueryParams = countQuery => {
|
|
35
|
+
return parseType({ type: 'boolean', value: countQuery });
|
|
36
|
+
};
|
|
37
|
+
|
|
32
38
|
/**
|
|
33
39
|
* Sort query parser
|
|
34
40
|
* @param {string} sortQuery - ex: id:asc,price:desc
|
|
@@ -104,6 +110,8 @@ const convertLimitQueryParams = limitQuery => {
|
|
|
104
110
|
throw new Error(`convertLimitQueryParams expected a positive integer got ${limitAsANumber}`);
|
|
105
111
|
}
|
|
106
112
|
|
|
113
|
+
if (limitAsANumber === -1) return null;
|
|
114
|
+
|
|
107
115
|
return limitAsANumber;
|
|
108
116
|
};
|
|
109
117
|
|
|
@@ -127,13 +135,15 @@ const convertPopulateQueryParams = (populate, depth = 0) => {
|
|
|
127
135
|
|
|
128
136
|
if (Array.isArray(populate)) {
|
|
129
137
|
// map convert
|
|
130
|
-
return
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
return _.uniq(
|
|
139
|
+
populate.flatMap(value => {
|
|
140
|
+
if (typeof value !== 'string') {
|
|
141
|
+
throw new InvalidPopulateError();
|
|
142
|
+
}
|
|
134
143
|
|
|
135
|
-
|
|
136
|
-
|
|
144
|
+
return value.split(',').map(value => _.trim(value));
|
|
145
|
+
})
|
|
146
|
+
);
|
|
137
147
|
}
|
|
138
148
|
|
|
139
149
|
if (_.isPlainObject(populate)) {
|
|
@@ -141,6 +151,7 @@ const convertPopulateQueryParams = (populate, depth = 0) => {
|
|
|
141
151
|
for (const key in populate) {
|
|
142
152
|
transformedPopulate[key] = convertNestedPopulate(populate[key]);
|
|
143
153
|
}
|
|
154
|
+
|
|
144
155
|
return transformedPopulate;
|
|
145
156
|
}
|
|
146
157
|
|
|
@@ -161,7 +172,7 @@ const convertNestedPopulate = subPopulate => {
|
|
|
161
172
|
}
|
|
162
173
|
|
|
163
174
|
// TODO: We will need to consider a way to add limitation / pagination
|
|
164
|
-
const { sort, filters, fields, populate } = subPopulate;
|
|
175
|
+
const { sort, filters, fields, populate, count } = subPopulate;
|
|
165
176
|
|
|
166
177
|
const query = {};
|
|
167
178
|
|
|
@@ -181,6 +192,10 @@ const convertNestedPopulate = subPopulate => {
|
|
|
181
192
|
query.populate = convertPopulateQueryParams(populate);
|
|
182
193
|
}
|
|
183
194
|
|
|
195
|
+
if (count) {
|
|
196
|
+
query.count = convertCountQueryParams(count);
|
|
197
|
+
}
|
|
198
|
+
|
|
184
199
|
return query;
|
|
185
200
|
};
|
|
186
201
|
|
|
@@ -203,25 +218,30 @@ const convertFieldsQueryParams = (fields, depth = 0) => {
|
|
|
203
218
|
throw new Error('Invalid fields parameter. Expected a string or an array of strings');
|
|
204
219
|
};
|
|
205
220
|
|
|
206
|
-
// NOTE: We could validate the parameters are on existing / non private attributes
|
|
207
221
|
const convertFiltersQueryParams = filters => filters;
|
|
208
222
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
223
|
+
const convertPublicationStateParams = (type, params = {}, query = {}) => {
|
|
224
|
+
if (!type) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
const { publicationState } = params;
|
|
229
|
+
|
|
230
|
+
if (!_.isNil(publicationState)) {
|
|
231
|
+
if (!contentTypesUtils.constants.DP_PUB_STATES.includes(publicationState)) {
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Invalid publicationState. Expected one of 'preview','live' received: ${publicationState}.`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// NOTE: this is the query layer filters not the entity service filters
|
|
238
|
+
query.filters = ({ meta }) => {
|
|
239
|
+
if (publicationState === 'live' && has(PUBLISHED_AT_ATTRIBUTE, meta.attributes)) {
|
|
240
|
+
return { [PUBLISHED_AT_ATTRIBUTE]: { $notNull: true } };
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
};
|
|
225
245
|
|
|
226
246
|
module.exports = {
|
|
227
247
|
convertSortQueryParams,
|
|
@@ -230,6 +250,5 @@ module.exports = {
|
|
|
230
250
|
convertPopulateQueryParams,
|
|
231
251
|
convertFiltersQueryParams,
|
|
232
252
|
convertFieldsQueryParams,
|
|
233
|
-
|
|
234
|
-
QUERY_OPERATORS,
|
|
253
|
+
convertPublicationStateParams,
|
|
235
254
|
};
|
package/lib/errors.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { HttpError } = require('http-errors');
|
|
4
|
+
const { formatYupErrors } = require('./format-yup-error');
|
|
5
|
+
|
|
6
|
+
/* ApplicationError */
|
|
7
|
+
class ApplicationError extends Error {
|
|
8
|
+
constructor(message, details = {}) {
|
|
9
|
+
super();
|
|
10
|
+
this.name = 'ApplicationError';
|
|
11
|
+
this.message = message || 'An application error occured';
|
|
12
|
+
this.details = details;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
class ValidationError extends ApplicationError {
|
|
17
|
+
constructor(message, details) {
|
|
18
|
+
super(message, details);
|
|
19
|
+
this.name = 'ValidationError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
class YupValidationError extends ValidationError {
|
|
24
|
+
constructor(yupError, message) {
|
|
25
|
+
super();
|
|
26
|
+
const { errors, message: yupMessage } = formatYupErrors(yupError);
|
|
27
|
+
this.message = message || yupMessage;
|
|
28
|
+
this.details = { errors };
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
class PaginationError extends ApplicationError {
|
|
33
|
+
constructor(message, details) {
|
|
34
|
+
super(message, details);
|
|
35
|
+
this.name = 'PaginationError';
|
|
36
|
+
this.message = message || 'Invalid pagination';
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
class NotFoundError extends ApplicationError {
|
|
41
|
+
constructor(message, details) {
|
|
42
|
+
super(message, details);
|
|
43
|
+
this.name = 'NotFoundError';
|
|
44
|
+
this.message = message || 'Entity not found';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class ForbiddenError extends ApplicationError {
|
|
49
|
+
constructor(message, details) {
|
|
50
|
+
super(message, details);
|
|
51
|
+
this.name = 'ForbiddenError';
|
|
52
|
+
this.message = message || 'Forbidden access';
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
class PayloadTooLargeError extends ApplicationError {
|
|
57
|
+
constructor(message, details) {
|
|
58
|
+
super(message, details);
|
|
59
|
+
this.name = 'PayloadTooLargeError';
|
|
60
|
+
this.message = message || 'Entity too large';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
class UnauthorizedError extends ApplicationError {
|
|
65
|
+
constructor(message, details) {
|
|
66
|
+
super(message, details);
|
|
67
|
+
this.name = 'UnauthorizedError';
|
|
68
|
+
this.message = message || 'Unauthorized';
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
module.exports = {
|
|
73
|
+
HttpError,
|
|
74
|
+
ApplicationError,
|
|
75
|
+
ValidationError,
|
|
76
|
+
YupValidationError,
|
|
77
|
+
PaginationError,
|
|
78
|
+
NotFoundError,
|
|
79
|
+
ForbiddenError,
|
|
80
|
+
PayloadTooLargeError,
|
|
81
|
+
UnauthorizedError,
|
|
82
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { isEmpty, toPath } = require('lodash/fp');
|
|
4
|
+
|
|
5
|
+
const formatYupInnerError = yupError => ({
|
|
6
|
+
path: toPath(yupError.path),
|
|
7
|
+
message: yupError.message,
|
|
8
|
+
name: yupError.name,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const formatYupErrors = yupError => ({
|
|
12
|
+
errors: isEmpty(yupError.inner)
|
|
13
|
+
? [formatYupInnerError(yupError)]
|
|
14
|
+
: yupError.inner.map(formatYupInnerError),
|
|
15
|
+
message: yupError.message,
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
module.exports = {
|
|
19
|
+
formatYupErrors,
|
|
20
|
+
};
|
package/lib/hooks.js
CHANGED
|
@@ -24,12 +24,14 @@ const createHook = () => {
|
|
|
24
24
|
return state.handlers;
|
|
25
25
|
},
|
|
26
26
|
|
|
27
|
-
register
|
|
27
|
+
register(handler) {
|
|
28
28
|
state.handlers.push(handler);
|
|
29
|
+
return this;
|
|
29
30
|
},
|
|
30
31
|
|
|
31
|
-
delete
|
|
32
|
+
delete(handler) {
|
|
32
33
|
state.handlers = remove(eq(handler), state.handlers);
|
|
34
|
+
return this;
|
|
33
35
|
},
|
|
34
36
|
|
|
35
37
|
call() {
|
|
@@ -80,7 +82,7 @@ const createAsyncSeriesWaterfallHook = () => ({
|
|
|
80
82
|
const createAsyncParallelHook = () => ({
|
|
81
83
|
...createHook(),
|
|
82
84
|
|
|
83
|
-
call(context) {
|
|
85
|
+
async call(context) {
|
|
84
86
|
const promises = this.handlers.map(handler => handler(cloneDeep(context)));
|
|
85
87
|
|
|
86
88
|
return Promise.all(promises);
|
package/lib/index.js
CHANGED
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
* Export shared utilities
|
|
5
5
|
*/
|
|
6
6
|
const { buildQuery, hasDeepFilters } = require('./build-query');
|
|
7
|
-
const { VALID_REST_OPERATORS, QUERY_OPERATORS } = require('./convert-query-params');
|
|
8
7
|
const parseMultipartData = require('./parse-multipart');
|
|
9
|
-
const sanitizeEntity = require('./sanitize-entity');
|
|
10
8
|
const parseType = require('./parse-type');
|
|
11
9
|
const policy = require('./policy');
|
|
12
10
|
const templateConfiguration = require('./template-configuration');
|
|
13
|
-
const { yup,
|
|
11
|
+
const { yup, handleYupError, validateYupSchema, validateYupSchemaSync } = require('./validators');
|
|
12
|
+
const errors = require('./errors');
|
|
14
13
|
const {
|
|
15
14
|
nameToSlug,
|
|
16
15
|
nameToCollectionName,
|
|
@@ -31,18 +30,21 @@ const relations = require('./relations');
|
|
|
31
30
|
const setCreatorFields = require('./set-creator-fields');
|
|
32
31
|
const hooks = require('./hooks');
|
|
33
32
|
const providerFactory = require('./provider-factory');
|
|
33
|
+
const pagination = require('./pagination');
|
|
34
|
+
const sanitize = require('./sanitize');
|
|
35
|
+
const traverseEntity = require('./traverse-entity');
|
|
36
|
+
const pipeAsync = require('./pipe-async');
|
|
34
37
|
|
|
35
38
|
module.exports = {
|
|
36
39
|
yup,
|
|
37
|
-
|
|
40
|
+
handleYupError,
|
|
38
41
|
policy,
|
|
39
42
|
templateConfiguration,
|
|
40
|
-
VALID_REST_OPERATORS,
|
|
41
|
-
QUERY_OPERATORS,
|
|
42
43
|
buildQuery,
|
|
43
44
|
hasDeepFilters,
|
|
44
45
|
parseMultipartData,
|
|
45
|
-
|
|
46
|
+
sanitize,
|
|
47
|
+
traverseEntity,
|
|
46
48
|
parseType,
|
|
47
49
|
nameToSlug,
|
|
48
50
|
nameToCollectionName,
|
|
@@ -64,4 +66,9 @@ module.exports = {
|
|
|
64
66
|
setCreatorFields,
|
|
65
67
|
hooks,
|
|
66
68
|
providerFactory,
|
|
69
|
+
pagination,
|
|
70
|
+
pipeAsync,
|
|
71
|
+
errors,
|
|
72
|
+
validateYupSchema,
|
|
73
|
+
validateYupSchemaSync,
|
|
67
74
|
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { merge, pipe, omit, isNil } = require('lodash/fp');
|
|
4
|
+
const { PaginationError } = require('./errors');
|
|
5
|
+
|
|
6
|
+
const STRAPI_DEFAULTS = {
|
|
7
|
+
offset: {
|
|
8
|
+
start: 0,
|
|
9
|
+
limit: 10,
|
|
10
|
+
},
|
|
11
|
+
page: {
|
|
12
|
+
page: 1,
|
|
13
|
+
pageSize: 10,
|
|
14
|
+
},
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const paginationAttributes = ['start', 'limit', 'page', 'pageSize'];
|
|
18
|
+
|
|
19
|
+
const withMaxLimit = (limit, maxLimit = -1) => {
|
|
20
|
+
if (maxLimit === -1 || limit < maxLimit) {
|
|
21
|
+
return limit;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
return maxLimit;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Ensure minimum page & pageSize values (page >= 1, pageSize >= 0, start >= 0, limit >= 0)
|
|
28
|
+
const ensureMinValues = ({ start, limit }) => ({
|
|
29
|
+
start: Math.max(start, 0),
|
|
30
|
+
limit: limit === -1 ? limit : Math.max(limit, 1),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const ensureMaxValues = (maxLimit = -1) => ({ start, limit }) => ({
|
|
34
|
+
start,
|
|
35
|
+
limit: withMaxLimit(limit, maxLimit),
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
// Apply maxLimit as the limit when limit is -1
|
|
39
|
+
const withNoLimit = (pagination, maxLimit = -1) => ({
|
|
40
|
+
...pagination,
|
|
41
|
+
limit: pagination.limit === -1 ? maxLimit : pagination.limit,
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const withDefaultPagination = (args, { defaults = {}, maxLimit = -1 } = {}) => {
|
|
45
|
+
const defaultValues = merge(STRAPI_DEFAULTS, defaults);
|
|
46
|
+
|
|
47
|
+
const usePagePagination = !isNil(args.page) || !isNil(args.pageSize);
|
|
48
|
+
const useOffsetPagination = !isNil(args.start) || !isNil(args.limit);
|
|
49
|
+
|
|
50
|
+
const ensureValidValues = pipe(
|
|
51
|
+
ensureMinValues,
|
|
52
|
+
ensureMaxValues(maxLimit)
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
// If there is no pagination attribute, don't modify the payload
|
|
56
|
+
if (!usePagePagination && !useOffsetPagination) {
|
|
57
|
+
return merge(args, ensureValidValues(defaultValues.offset));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// If there is page & offset pagination attributes, throw an error
|
|
61
|
+
if (usePagePagination && useOffsetPagination) {
|
|
62
|
+
throw new PaginationError('Cannot use both page & offset pagination in the same query');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const pagination = {};
|
|
66
|
+
|
|
67
|
+
// Start / Limit
|
|
68
|
+
if (useOffsetPagination) {
|
|
69
|
+
const { start, limit } = merge(defaultValues.offset, args);
|
|
70
|
+
|
|
71
|
+
Object.assign(pagination, { start, limit });
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Page / PageSize
|
|
75
|
+
if (usePagePagination) {
|
|
76
|
+
const { page, pageSize } = merge(defaultValues.page, {
|
|
77
|
+
...args,
|
|
78
|
+
pageSize: Math.max(1, args.pageSize),
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
Object.assign(pagination, {
|
|
82
|
+
start: (page - 1) * pageSize,
|
|
83
|
+
limit: pageSize,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Handle -1 limit
|
|
88
|
+
Object.assign(pagination, withNoLimit(pagination, maxLimit));
|
|
89
|
+
|
|
90
|
+
const replacePaginationAttributes = pipe(
|
|
91
|
+
// Remove pagination attributes
|
|
92
|
+
omit(paginationAttributes),
|
|
93
|
+
// Merge the object with the new pagination + ensure minimum & maximum values
|
|
94
|
+
merge(ensureValidValues(pagination))
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
return replacePaginationAttributes(args);
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
module.exports = { withDefaultPagination };
|
package/lib/parse-multipart.js
CHANGED
|
@@ -10,7 +10,7 @@ module.exports = ctx => {
|
|
|
10
10
|
const { body = {}, files = {} } = ctx.request;
|
|
11
11
|
|
|
12
12
|
if (!body.data) {
|
|
13
|
-
|
|
13
|
+
return ctx.badRequest(
|
|
14
14
|
`When using multipart/form-data you need to provide your data in a JSON 'data' field.`
|
|
15
15
|
);
|
|
16
16
|
}
|
|
@@ -19,14 +19,14 @@ module.exports = ctx => {
|
|
|
19
19
|
try {
|
|
20
20
|
data = JSON.parse(body.data);
|
|
21
21
|
} catch (error) {
|
|
22
|
-
|
|
22
|
+
return ctx.badRequest(`Invalid 'data' field. 'data' should be a valid JSON.`);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
const filesToUpload = Object.keys(files).reduce((acc, key) => {
|
|
26
26
|
const fullPath = _.toPath(key);
|
|
27
27
|
|
|
28
28
|
if (fullPath.length <= 1 || fullPath[0] !== 'files') {
|
|
29
|
-
|
|
29
|
+
return ctx.badRequest(
|
|
30
30
|
`When using multipart/form-data you need to provide your files by prefixing them with the 'files'.
|
|
31
31
|
For example, when a media file is named "avatar", make sure the form key name is "files.avatar"`
|
|
32
32
|
);
|