@strapi/strapi 4.0.0-beta.2 → 4.0.0-beta.20
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 +12 -12
- package/bin/strapi.js +6 -1
- package/lib/Strapi.js +33 -21
- package/lib/commands/build.js +1 -1
- package/lib/commands/content-types/list.js +3 -5
- package/lib/commands/develop.js +8 -10
- package/lib/commands/generate-template.js +4 -5
- package/lib/commands/hooks/list.js +3 -5
- package/lib/commands/middlewares/list.js +3 -5
- package/lib/commands/new.js +3 -1
- package/lib/commands/policies/list.js +3 -5
- package/lib/commands/services/list.js +22 -0
- package/lib/commands/watchAdmin.js +4 -4
- package/lib/core/app-configuration/index.js +5 -10
- package/lib/core/bootstrap.js +2 -2
- package/lib/core/domain/module/index.js +3 -1
- package/lib/core/domain/module/validation.js +1 -4
- package/lib/core/loaders/admin.js +2 -2
- package/lib/core/loaders/apis.js +3 -1
- package/lib/core/loaders/plugins/get-enabled-plugins.js +25 -9
- package/lib/core/loaders/plugins/index.js +21 -7
- package/lib/core/loaders/src-index.js +1 -0
- package/lib/core/registries/apis.js +2 -16
- package/lib/core/registries/content-types.js +50 -6
- package/lib/core/registries/controllers.d.ts +7 -0
- package/lib/core/registries/controllers.js +74 -3
- package/lib/core/registries/hooks.d.ts +20 -0
- package/lib/core/registries/hooks.js +57 -7
- package/lib/core/registries/middlewares.d.ts +5 -0
- package/lib/core/registries/middlewares.js +61 -2
- package/lib/core/registries/policies.d.ts +9 -0
- package/lib/core/registries/policies.js +57 -6
- package/lib/core/registries/services.d.ts +7 -0
- package/lib/core/registries/services.js +67 -11
- package/lib/core-api/controller/collection-type.js +38 -11
- package/lib/core-api/controller/index.d.ts +25 -0
- package/lib/core-api/controller/index.js +30 -11
- package/lib/core-api/controller/single-type.js +26 -7
- package/lib/core-api/routes/index.js +71 -0
- package/lib/core-api/service/collection-type.js +8 -12
- package/lib/core-api/service/index.d.ts +21 -0
- package/lib/core-api/service/index.js +9 -19
- package/lib/core-api/service/pagination.js +7 -2
- package/lib/core-api/service/single-type.js +12 -11
- package/lib/factories.d.ts +48 -0
- package/lib/factories.js +84 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +5 -1
- package/lib/middlewares/body.js +33 -0
- package/lib/middlewares/compression.js +1 -1
- package/lib/middlewares/cors.js +3 -2
- package/lib/middlewares/errors.js +24 -119
- package/lib/middlewares/favicon.js +3 -3
- package/lib/middlewares/index.d.ts +2 -1
- package/lib/middlewares/index.js +4 -2
- package/lib/middlewares/ip.js +1 -1
- package/lib/middlewares/logger.js +1 -1
- package/lib/middlewares/powered-by.js +4 -2
- package/lib/middlewares/public/index.js +4 -7
- package/lib/middlewares/query.js +46 -0
- package/lib/middlewares/responses.js +2 -2
- package/lib/middlewares/security.js +29 -3
- package/lib/middlewares/session/index.js +1 -1
- package/lib/services/auth/index.js +1 -6
- package/lib/services/entity-service/attributes/index.js +31 -0
- package/lib/services/entity-service/attributes/transforms.js +20 -0
- package/lib/services/entity-service/components.js +2 -3
- package/lib/services/entity-service/index.d.ts +1 -1
- package/lib/services/entity-service/index.js +83 -27
- package/lib/services/entity-service/params.js +37 -87
- package/lib/services/entity-validator/index.js +76 -43
- package/lib/services/entity-validator/validators.js +129 -43
- package/lib/services/errors.js +77 -0
- package/lib/services/metrics/index.js +37 -35
- package/lib/services/server/compose-endpoint.js +39 -11
- package/lib/services/server/content-api.js +1 -1
- package/lib/services/server/index.js +4 -9
- package/lib/services/server/koa.js +64 -0
- package/lib/services/server/middleware.js +8 -1
- package/lib/services/server/policy.js +8 -10
- package/lib/services/server/register-middlewares.js +7 -2
- package/lib/services/server/register-routes.js +1 -3
- package/lib/services/server/routing.js +13 -0
- package/lib/utils/get-dirs.js +1 -0
- package/lib/utils/signals.js +24 -0
- package/package.json +22 -17
- package/lib/core/app-configuration/load-functions.js +0 -28
- package/lib/core-api/index.js +0 -39
- package/lib/middlewares/request.js +0 -74
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { getOr, toNumber, isString, isBuffer } = require('lodash/fp');
|
|
4
|
+
const bcrypt = require('bcryptjs');
|
|
5
|
+
|
|
6
|
+
const transforms = {
|
|
7
|
+
password(value, context) {
|
|
8
|
+
const { attribute } = context;
|
|
9
|
+
|
|
10
|
+
if (!isString(value) && !isBuffer(value)) {
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const rounds = toNumber(getOr(10, 'encryption.rounds', attribute));
|
|
15
|
+
|
|
16
|
+
return bcrypt.hashSync(value, rounds);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
module.exports = transforms;
|
|
@@ -4,6 +4,7 @@ const _ = require('lodash');
|
|
|
4
4
|
const { has, prop, omit, toString } = require('lodash/fp');
|
|
5
5
|
|
|
6
6
|
const { contentTypes: contentTypesUtils } = require('@strapi/utils');
|
|
7
|
+
const { ApplicationError } = require('@strapi/utils').errors;
|
|
7
8
|
|
|
8
9
|
const omitComponentData = (contentType, data) => {
|
|
9
10
|
const { attributes } = contentType;
|
|
@@ -208,11 +209,9 @@ const deleteOldComponents = async (
|
|
|
208
209
|
|
|
209
210
|
idsToKeep.forEach(id => {
|
|
210
211
|
if (!allIds.includes(id)) {
|
|
211
|
-
|
|
212
|
+
throw new ApplicationError(
|
|
212
213
|
`Some of the provided components in ${attributeName} are not related to the entity`
|
|
213
214
|
);
|
|
214
|
-
err.status = 400;
|
|
215
|
-
throw err;
|
|
216
215
|
}
|
|
217
216
|
});
|
|
218
217
|
|
|
@@ -35,7 +35,7 @@ type Params<T> = {
|
|
|
35
35
|
files?: any;
|
|
36
36
|
};
|
|
37
37
|
|
|
38
|
-
interface EntityService {
|
|
38
|
+
export interface EntityService {
|
|
39
39
|
uploadFiles<K extends keyof AllTypes, T extends AllTypes[K]>(uid: K, entity, files);
|
|
40
40
|
wrapParams<K extends keyof AllTypes, T extends AllTypes[K]>(
|
|
41
41
|
params: Params<T>,
|
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const _ = require('lodash');
|
|
3
4
|
const delegate = require('delegates');
|
|
4
|
-
const { pipe } = require('lodash/fp');
|
|
5
|
-
|
|
6
5
|
const {
|
|
7
|
-
|
|
6
|
+
InvalidTimeError,
|
|
7
|
+
InvalidDateError,
|
|
8
|
+
InvalidDateTimeError,
|
|
9
|
+
} = require('@strapi/database').errors;
|
|
10
|
+
const {
|
|
8
11
|
webhook: webhookUtils,
|
|
9
12
|
contentTypes: contentTypesUtils,
|
|
13
|
+
sanitize,
|
|
10
14
|
} = require('@strapi/utils');
|
|
15
|
+
const { ValidationError } = require('@strapi/utils').errors;
|
|
11
16
|
const uploadFiles = require('../utils/upload-files');
|
|
12
17
|
|
|
13
18
|
const {
|
|
@@ -16,16 +21,22 @@ const {
|
|
|
16
21
|
updateComponents,
|
|
17
22
|
deleteComponents,
|
|
18
23
|
} = require('./components');
|
|
19
|
-
const {
|
|
20
|
-
|
|
21
|
-
transformPaginationParams,
|
|
22
|
-
transformParamsToQuery,
|
|
23
|
-
pickSelectionParams,
|
|
24
|
-
} = require('./params');
|
|
24
|
+
const { transformParamsToQuery, pickSelectionParams } = require('./params');
|
|
25
|
+
const { applyTransforms } = require('./attributes');
|
|
25
26
|
|
|
26
27
|
// TODO: those should be strapi events used by the webhooks not the other way arround
|
|
27
28
|
const { ENTRY_CREATE, ENTRY_UPDATE, ENTRY_DELETE } = webhookUtils.webhookEvents;
|
|
28
29
|
|
|
30
|
+
const databaseErrorsToTransform = [InvalidTimeError, InvalidDateTimeError, InvalidDateError];
|
|
31
|
+
|
|
32
|
+
const creationPipeline = (data, context) => {
|
|
33
|
+
return applyTransforms(data, context);
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const updatePipeline = (data, context) => {
|
|
37
|
+
return applyTransforms(data, context);
|
|
38
|
+
};
|
|
39
|
+
|
|
29
40
|
module.exports = ctx => {
|
|
30
41
|
const implementation = createDefaultImplementation(ctx);
|
|
31
42
|
|
|
@@ -46,6 +57,28 @@ module.exports = ctx => {
|
|
|
46
57
|
// delegate every method in implementation
|
|
47
58
|
Object.keys(service.implementation).forEach(key => delegator.method(key));
|
|
48
59
|
|
|
60
|
+
// wrap methods to handle Database Errors
|
|
61
|
+
service.decorate(oldService => {
|
|
62
|
+
const newService = _.mapValues(
|
|
63
|
+
oldService,
|
|
64
|
+
(method, methodName) =>
|
|
65
|
+
async function(...args) {
|
|
66
|
+
try {
|
|
67
|
+
return await oldService[methodName].call(this, ...args);
|
|
68
|
+
} catch (error) {
|
|
69
|
+
if (
|
|
70
|
+
databaseErrorsToTransform.some(errorToTransform => error instanceof errorToTransform)
|
|
71
|
+
) {
|
|
72
|
+
throw new ValidationError(error.message);
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
return newService;
|
|
80
|
+
});
|
|
81
|
+
|
|
49
82
|
return service;
|
|
50
83
|
};
|
|
51
84
|
|
|
@@ -59,12 +92,13 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
59
92
|
return options;
|
|
60
93
|
},
|
|
61
94
|
|
|
62
|
-
emitEvent(uid, event, entity) {
|
|
95
|
+
async emitEvent(uid, event, entity) {
|
|
63
96
|
const model = strapi.getModel(uid);
|
|
97
|
+
const sanitizedEntity = await sanitize.sanitizers.defaultSanitizeOutput(model, entity);
|
|
64
98
|
|
|
65
99
|
eventHub.emit(event, {
|
|
66
100
|
model: model.modelName,
|
|
67
|
-
entry:
|
|
101
|
+
entry: sanitizedEntity,
|
|
68
102
|
});
|
|
69
103
|
},
|
|
70
104
|
|
|
@@ -137,7 +171,9 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
137
171
|
|
|
138
172
|
let entity = await db.query(uid).create({
|
|
139
173
|
...query,
|
|
140
|
-
data: Object.assign(omitComponentData(model, validData), componentData),
|
|
174
|
+
data: creationPipeline(Object.assign(omitComponentData(model, validData), componentData), {
|
|
175
|
+
contentType: model,
|
|
176
|
+
}),
|
|
141
177
|
});
|
|
142
178
|
|
|
143
179
|
// TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
|
|
@@ -147,7 +183,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
147
183
|
entity = await this.findOne(uid, entity.id, wrappedParams);
|
|
148
184
|
}
|
|
149
185
|
|
|
150
|
-
this.emitEvent(uid, ENTRY_CREATE, entity);
|
|
186
|
+
await this.emitEvent(uid, ENTRY_CREATE, entity);
|
|
151
187
|
|
|
152
188
|
return entity;
|
|
153
189
|
},
|
|
@@ -166,9 +202,14 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
166
202
|
|
|
167
203
|
const isDraft = contentTypesUtils.isDraft(entityToUpdate, model);
|
|
168
204
|
|
|
169
|
-
const validData = await entityValidator.validateEntityUpdate(
|
|
170
|
-
|
|
171
|
-
|
|
205
|
+
const validData = await entityValidator.validateEntityUpdate(
|
|
206
|
+
model,
|
|
207
|
+
data,
|
|
208
|
+
{
|
|
209
|
+
isDraft,
|
|
210
|
+
},
|
|
211
|
+
entityToUpdate
|
|
212
|
+
);
|
|
172
213
|
|
|
173
214
|
const query = transformParamsToQuery(uid, pickSelectionParams(wrappedParams));
|
|
174
215
|
|
|
@@ -178,7 +219,9 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
178
219
|
let entity = await db.query(uid).update({
|
|
179
220
|
...query,
|
|
180
221
|
where: { id: entityId },
|
|
181
|
-
data: Object.assign(omitComponentData(model, validData), componentData),
|
|
222
|
+
data: updatePipeline(Object.assign(omitComponentData(model, validData), componentData), {
|
|
223
|
+
contentType: model,
|
|
224
|
+
}),
|
|
182
225
|
});
|
|
183
226
|
|
|
184
227
|
// TODO: upload the files then set the links in the entity like with compo to avoid making too many queries
|
|
@@ -188,7 +231,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
188
231
|
entity = await this.findOne(uid, entity.id, wrappedParams);
|
|
189
232
|
}
|
|
190
233
|
|
|
191
|
-
this.emitEvent(uid, ENTRY_UPDATE, entity);
|
|
234
|
+
await this.emitEvent(uid, ENTRY_UPDATE, entity);
|
|
192
235
|
|
|
193
236
|
return entity;
|
|
194
237
|
},
|
|
@@ -211,7 +254,7 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
211
254
|
await deleteComponents(uid, entityToDelete);
|
|
212
255
|
await db.query(uid).delete({ where: { id: entityToDelete.id } });
|
|
213
256
|
|
|
214
|
-
this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
|
|
257
|
+
await this.emitEvent(uid, ENTRY_DELETE, entityToDelete);
|
|
215
258
|
|
|
216
259
|
return entityToDelete;
|
|
217
260
|
},
|
|
@@ -226,18 +269,31 @@ const createDefaultImplementation = ({ strapi, db, eventHub, entityValidator })
|
|
|
226
269
|
return db.query(uid).deleteMany(query);
|
|
227
270
|
},
|
|
228
271
|
|
|
229
|
-
load(uid, entity, field, params) {
|
|
272
|
+
load(uid, entity, field, params = {}) {
|
|
230
273
|
const { attributes } = strapi.getModel(uid);
|
|
231
274
|
|
|
232
275
|
const attribute = attributes[field];
|
|
233
276
|
|
|
234
|
-
const loadParams =
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
277
|
+
const loadParams = {};
|
|
278
|
+
|
|
279
|
+
switch (attribute.type) {
|
|
280
|
+
case 'relation': {
|
|
281
|
+
Object.assign(loadParams, transformParamsToQuery(attribute.target, params));
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
case 'component': {
|
|
285
|
+
Object.assign(loadParams, transformParamsToQuery(attribute.component, params));
|
|
286
|
+
break;
|
|
287
|
+
}
|
|
288
|
+
case 'dynamiczone': {
|
|
289
|
+
Object.assign(loadParams, transformParamsToQuery(null, params));
|
|
290
|
+
break;
|
|
291
|
+
}
|
|
292
|
+
case 'media': {
|
|
293
|
+
Object.assign(loadParams, transformParamsToQuery('plugin::upload.file', params));
|
|
294
|
+
break;
|
|
295
|
+
}
|
|
296
|
+
}
|
|
241
297
|
|
|
242
298
|
return db.query(uid).load(entity, field, loadParams);
|
|
243
299
|
},
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { pick,
|
|
3
|
+
const { pick, isNil, toNumber, isInteger } = require('lodash/fp');
|
|
4
|
+
const { PaginationError } = require('@strapi/utils').errors;
|
|
4
5
|
|
|
5
6
|
const {
|
|
6
7
|
convertSortQueryParams,
|
|
@@ -9,137 +10,86 @@ const {
|
|
|
9
10
|
convertPopulateQueryParams,
|
|
10
11
|
convertFiltersQueryParams,
|
|
11
12
|
convertFieldsQueryParams,
|
|
13
|
+
convertPublicationStateParams,
|
|
12
14
|
} = require('@strapi/utils/lib/convert-query-params');
|
|
13
15
|
|
|
14
|
-
const
|
|
16
|
+
const pickSelectionParams = pick(['fields', 'populate']);
|
|
15
17
|
|
|
16
|
-
const
|
|
18
|
+
const transformParamsToQuery = (uid, params) => {
|
|
19
|
+
// NOTE: can be a CT, a Compo or nothing in the case of polymorphism (DZ & morph relations)
|
|
20
|
+
const type = strapi.getModel(uid);
|
|
17
21
|
|
|
18
|
-
// TODO: to remove once the front is migrated
|
|
19
|
-
const convertOldQuery = params => {
|
|
20
22
|
const query = {};
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
if (key.startsWith('_')) {
|
|
24
|
-
query[key.slice(1)] = params[key];
|
|
25
|
-
} else {
|
|
26
|
-
query[key] = params[key];
|
|
27
|
-
}
|
|
28
|
-
});
|
|
24
|
+
const { _q, sort, filters, fields, populate, page, pageSize, start, limit } = params;
|
|
29
25
|
|
|
30
|
-
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
const transformCommonParams = (params = {}) => {
|
|
34
|
-
const { _q, sort, filters, _where, fields, populate, ...query } = params;
|
|
35
|
-
|
|
36
|
-
if (_q) {
|
|
26
|
+
if (!isNil(_q)) {
|
|
37
27
|
query._q = _q;
|
|
38
28
|
}
|
|
39
29
|
|
|
40
|
-
if (sort) {
|
|
30
|
+
if (!isNil(sort)) {
|
|
41
31
|
query.orderBy = convertSortQueryParams(sort);
|
|
42
32
|
}
|
|
43
33
|
|
|
44
|
-
if (filters) {
|
|
34
|
+
if (!isNil(filters)) {
|
|
45
35
|
query.where = convertFiltersQueryParams(filters);
|
|
46
36
|
}
|
|
47
37
|
|
|
48
|
-
if (
|
|
49
|
-
query.where = {
|
|
50
|
-
$and: [_where].concat(query.where || []),
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
if (fields) {
|
|
38
|
+
if (!isNil(fields)) {
|
|
55
39
|
query.select = convertFieldsQueryParams(fields);
|
|
56
40
|
}
|
|
57
41
|
|
|
58
|
-
if (populate) {
|
|
42
|
+
if (!isNil(populate)) {
|
|
59
43
|
query.populate = convertPopulateQueryParams(populate);
|
|
60
44
|
}
|
|
61
45
|
|
|
62
|
-
return { ...convertOldQuery(query), ...query };
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
const transformPaginationParams = (params = {}) => {
|
|
66
|
-
const { page, pageSize, start, limit, ...query } = params;
|
|
67
|
-
|
|
68
46
|
const isPagePagination = !isNil(page) || !isNil(pageSize);
|
|
69
47
|
const isOffsetPagination = !isNil(start) || !isNil(limit);
|
|
70
48
|
|
|
71
49
|
if (isPagePagination && isOffsetPagination) {
|
|
72
|
-
throw new
|
|
50
|
+
throw new PaginationError(
|
|
73
51
|
'Invalid pagination attributes. You cannot use page and offset pagination in the same query'
|
|
74
52
|
);
|
|
75
53
|
}
|
|
76
54
|
|
|
77
|
-
if (page) {
|
|
78
|
-
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (pageSize) {
|
|
82
|
-
query.pageSize = Number(pageSize);
|
|
83
|
-
}
|
|
55
|
+
if (!isNil(page)) {
|
|
56
|
+
const pageVal = toNumber(page);
|
|
84
57
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
58
|
+
if (!isInteger(pageVal) || pageVal <= 0) {
|
|
59
|
+
throw new PaginationError(
|
|
60
|
+
`Invalid 'page' parameter. Expected an integer > 0, received: ${page}`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
88
63
|
|
|
89
|
-
|
|
90
|
-
query.limit = convertLimitQueryParams(limit);
|
|
64
|
+
query.page = pageVal;
|
|
91
65
|
}
|
|
92
66
|
|
|
93
|
-
|
|
94
|
-
|
|
67
|
+
if (!isNil(pageSize)) {
|
|
68
|
+
const pageSizeVal = toNumber(pageSize);
|
|
95
69
|
|
|
96
|
-
|
|
97
|
-
|
|
70
|
+
if (!isInteger(pageSizeVal) || pageSizeVal <= 0) {
|
|
71
|
+
throw new PaginationError(
|
|
72
|
+
`Invalid 'pageSize' parameter. Expected an integer > 0, received: ${page}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
98
75
|
|
|
99
|
-
|
|
100
|
-
return params;
|
|
76
|
+
query.pageSize = pageSizeVal;
|
|
101
77
|
}
|
|
102
78
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
if (publicationState && contentTypesUtils.hasDraftAndPublish(contentType)) {
|
|
106
|
-
const { publicationState = 'live' } = params;
|
|
107
|
-
|
|
108
|
-
const liveClause = {
|
|
109
|
-
[PUBLISHED_AT_ATTRIBUTE]: {
|
|
110
|
-
$notNull: true,
|
|
111
|
-
},
|
|
112
|
-
};
|
|
113
|
-
|
|
114
|
-
if (publicationState === 'live') {
|
|
115
|
-
query.where = {
|
|
116
|
-
$and: [liveClause].concat(query.where || []),
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
// TODO: propagate nested publicationState filter somehow
|
|
120
|
-
}
|
|
79
|
+
if (!isNil(start)) {
|
|
80
|
+
query.offset = convertStartQueryParams(start);
|
|
121
81
|
}
|
|
122
82
|
|
|
123
|
-
|
|
124
|
-
|
|
83
|
+
if (!isNil(limit)) {
|
|
84
|
+
query.limit = convertLimitQueryParams(limit);
|
|
85
|
+
}
|
|
125
86
|
|
|
126
|
-
|
|
87
|
+
convertPublicationStateParams(type, params, query);
|
|
127
88
|
|
|
128
|
-
|
|
129
|
-
return pipe(
|
|
130
|
-
// _q, _where, filters, etc...
|
|
131
|
-
transformCommonParams,
|
|
132
|
-
// page, pageSize, start, limit
|
|
133
|
-
transformPaginationParams,
|
|
134
|
-
// publicationState
|
|
135
|
-
transformPublicationStateParams(uid)
|
|
136
|
-
)(params);
|
|
89
|
+
return query;
|
|
137
90
|
};
|
|
138
91
|
|
|
139
92
|
module.exports = {
|
|
140
|
-
transformCommonParams,
|
|
141
|
-
transformPublicationStateParams,
|
|
142
|
-
transformPaginationParams,
|
|
143
93
|
transformParamsToQuery,
|
|
144
94
|
pickSelectionParams,
|
|
145
95
|
};
|
|
@@ -4,15 +4,19 @@
|
|
|
4
4
|
*/
|
|
5
5
|
'use strict';
|
|
6
6
|
|
|
7
|
-
const { has, assoc, prop } = require('lodash/fp');
|
|
7
|
+
const { has, assoc, prop, isObject } = require('lodash/fp');
|
|
8
8
|
const strapiUtils = require('@strapi/utils');
|
|
9
9
|
const validators = require('./validators');
|
|
10
10
|
|
|
11
|
-
const { yup,
|
|
11
|
+
const { yup, validateYupSchema } = strapiUtils;
|
|
12
12
|
const { isMediaAttribute, isScalarAttribute, getWritableAttributes } = strapiUtils.contentTypes;
|
|
13
|
+
const { ValidationError } = strapiUtils.errors;
|
|
13
14
|
|
|
14
|
-
const addMinMax = (
|
|
15
|
-
if (
|
|
15
|
+
const addMinMax = (validator, { attr, updatedAttribute }) => {
|
|
16
|
+
if (
|
|
17
|
+
Number.isInteger(attr.min) &&
|
|
18
|
+
(attr.required || (Array.isArray(updatedAttribute.value) && updatedAttribute.value.length > 0))
|
|
19
|
+
) {
|
|
16
20
|
validator = validator.min(attr.min);
|
|
17
21
|
}
|
|
18
22
|
if (Number.isInteger(attr.max)) {
|
|
@@ -21,7 +25,7 @@ const addMinMax = (attr, validator, data) => {
|
|
|
21
25
|
return validator;
|
|
22
26
|
};
|
|
23
27
|
|
|
24
|
-
const addRequiredValidation = createOrUpdate => (
|
|
28
|
+
const addRequiredValidation = createOrUpdate => (validator, { attr: { required } }) => {
|
|
25
29
|
if (required) {
|
|
26
30
|
if (createOrUpdate === 'creation') {
|
|
27
31
|
validator = validator.notNil();
|
|
@@ -34,7 +38,7 @@ const addRequiredValidation = createOrUpdate => (required, validator) => {
|
|
|
34
38
|
return validator;
|
|
35
39
|
};
|
|
36
40
|
|
|
37
|
-
const addDefault = createOrUpdate => (
|
|
41
|
+
const addDefault = createOrUpdate => (validator, { attr }) => {
|
|
38
42
|
if (createOrUpdate === 'creation') {
|
|
39
43
|
if (
|
|
40
44
|
((attr.type === 'component' && attr.repeatable) || attr.type === 'dynamiczone') &&
|
|
@@ -53,7 +57,7 @@ const addDefault = createOrUpdate => (attr, validator) => {
|
|
|
53
57
|
|
|
54
58
|
const preventCast = validator => validator.transform((val, originalVal) => originalVal);
|
|
55
59
|
|
|
56
|
-
const createComponentValidator = createOrUpdate => (attr,
|
|
60
|
+
const createComponentValidator = createOrUpdate => ({ attr, updatedAttribute }, { isDraft }) => {
|
|
57
61
|
let validator;
|
|
58
62
|
|
|
59
63
|
const model = strapi.getModel(attr.component);
|
|
@@ -65,19 +69,23 @@ const createComponentValidator = createOrUpdate => (attr, data, { isDraft }) =>
|
|
|
65
69
|
validator = yup
|
|
66
70
|
.array()
|
|
67
71
|
.of(
|
|
68
|
-
yup.lazy(item =>
|
|
72
|
+
yup.lazy(item =>
|
|
73
|
+
createModelValidator(createOrUpdate)({ model, data: item }, { isDraft }).notNull()
|
|
74
|
+
)
|
|
69
75
|
);
|
|
70
|
-
validator = addRequiredValidation(createOrUpdate)(
|
|
71
|
-
validator = addMinMax(
|
|
76
|
+
validator = addRequiredValidation(createOrUpdate)(validator, { attr: { required: true } });
|
|
77
|
+
validator = addMinMax(validator, { attr, updatedAttribute });
|
|
72
78
|
} else {
|
|
73
|
-
validator = createModelValidator(createOrUpdate)(model,
|
|
74
|
-
validator = addRequiredValidation(createOrUpdate)(
|
|
79
|
+
validator = createModelValidator(createOrUpdate)({ model, updatedAttribute }, { isDraft });
|
|
80
|
+
validator = addRequiredValidation(createOrUpdate)(validator, {
|
|
81
|
+
attr: { required: !isDraft && attr.required },
|
|
82
|
+
});
|
|
75
83
|
}
|
|
76
84
|
|
|
77
85
|
return validator;
|
|
78
86
|
};
|
|
79
87
|
|
|
80
|
-
const createDzValidator = createOrUpdate => (attr,
|
|
88
|
+
const createDzValidator = createOrUpdate => ({ attr, updatedAttribute }, { isDraft }) => {
|
|
81
89
|
let validator;
|
|
82
90
|
|
|
83
91
|
validator = yup.array().of(
|
|
@@ -94,76 +102,85 @@ const createDzValidator = createOrUpdate => (attr, data, { isDraft }) => {
|
|
|
94
102
|
.notNull();
|
|
95
103
|
|
|
96
104
|
return model
|
|
97
|
-
? schema.concat(createModelValidator(createOrUpdate)(model, item, { isDraft }))
|
|
105
|
+
? schema.concat(createModelValidator(createOrUpdate)({ model, data: item }, { isDraft }))
|
|
98
106
|
: schema;
|
|
99
107
|
})
|
|
100
108
|
);
|
|
101
|
-
validator = addRequiredValidation(createOrUpdate)(
|
|
102
|
-
validator = addMinMax(
|
|
109
|
+
validator = addRequiredValidation(createOrUpdate)(validator, { attr: { required: true } });
|
|
110
|
+
validator = addMinMax(validator, { attr, updatedAttribute });
|
|
103
111
|
|
|
104
112
|
return validator;
|
|
105
113
|
};
|
|
106
114
|
|
|
107
|
-
const createRelationValidator = createOrUpdate => (attr,
|
|
115
|
+
const createRelationValidator = createOrUpdate => ({ attr, updatedAttribute }, { isDraft }) => {
|
|
108
116
|
let validator;
|
|
109
117
|
|
|
110
|
-
if (Array.isArray(
|
|
118
|
+
if (Array.isArray(updatedAttribute.value)) {
|
|
111
119
|
validator = yup.array().of(yup.mixed());
|
|
112
120
|
} else {
|
|
113
121
|
validator = yup.mixed();
|
|
114
122
|
}
|
|
115
|
-
|
|
123
|
+
|
|
124
|
+
validator = addRequiredValidation(createOrUpdate)(validator, {
|
|
125
|
+
attr: { required: !isDraft && attr.required },
|
|
126
|
+
});
|
|
116
127
|
|
|
117
128
|
return validator;
|
|
118
129
|
};
|
|
119
130
|
|
|
120
|
-
const createScalarAttributeValidator = createOrUpdate => (
|
|
131
|
+
const createScalarAttributeValidator = createOrUpdate => (metas, options) => {
|
|
121
132
|
let validator;
|
|
122
133
|
|
|
123
|
-
if (has(attr.type, validators)) {
|
|
124
|
-
validator = validators[attr.type](
|
|
134
|
+
if (has(metas.attr.type, validators)) {
|
|
135
|
+
validator = validators[metas.attr.type](metas, options);
|
|
125
136
|
} else {
|
|
126
137
|
// No validators specified - fall back to mixed
|
|
127
138
|
validator = yup.mixed();
|
|
128
139
|
}
|
|
129
140
|
|
|
130
|
-
validator = addRequiredValidation(createOrUpdate)(
|
|
141
|
+
validator = addRequiredValidation(createOrUpdate)(validator, {
|
|
142
|
+
attr: { required: !options.isDraft && metas.attr.required },
|
|
143
|
+
});
|
|
131
144
|
|
|
132
145
|
return validator;
|
|
133
146
|
};
|
|
134
147
|
|
|
135
|
-
const createAttributeValidator = createOrUpdate => (
|
|
148
|
+
const createAttributeValidator = createOrUpdate => (metas, options) => {
|
|
136
149
|
let validator;
|
|
137
150
|
|
|
138
|
-
if (isMediaAttribute(attr)) {
|
|
151
|
+
if (isMediaAttribute(metas.attr)) {
|
|
139
152
|
validator = yup.mixed();
|
|
140
|
-
} else if (isScalarAttribute(attr)) {
|
|
141
|
-
validator = createScalarAttributeValidator(createOrUpdate)(
|
|
153
|
+
} else if (isScalarAttribute(metas.attr)) {
|
|
154
|
+
validator = createScalarAttributeValidator(createOrUpdate)(metas, options);
|
|
142
155
|
} else {
|
|
143
|
-
if (attr.type === 'component') {
|
|
144
|
-
validator = createComponentValidator(createOrUpdate)(
|
|
145
|
-
} else if (attr.type === 'dynamiczone') {
|
|
146
|
-
validator = createDzValidator(createOrUpdate)(
|
|
156
|
+
if (metas.attr.type === 'component') {
|
|
157
|
+
validator = createComponentValidator(createOrUpdate)(metas, options);
|
|
158
|
+
} else if (metas.attr.type === 'dynamiczone') {
|
|
159
|
+
validator = createDzValidator(createOrUpdate)(metas, options);
|
|
147
160
|
} else {
|
|
148
|
-
validator = createRelationValidator(createOrUpdate)(
|
|
161
|
+
validator = createRelationValidator(createOrUpdate)(metas, options);
|
|
149
162
|
}
|
|
150
163
|
|
|
151
164
|
validator = preventCast(validator);
|
|
152
165
|
}
|
|
153
166
|
|
|
154
|
-
validator = addDefault(createOrUpdate)(
|
|
167
|
+
validator = addDefault(createOrUpdate)(validator, metas);
|
|
155
168
|
|
|
156
169
|
return validator;
|
|
157
170
|
};
|
|
158
171
|
|
|
159
|
-
const createModelValidator = createOrUpdate => (model, data,
|
|
172
|
+
const createModelValidator = createOrUpdate => ({ model, data, entity }, options) => {
|
|
160
173
|
const writableAttributes = model ? getWritableAttributes(model) : [];
|
|
161
174
|
|
|
162
175
|
const schema = writableAttributes.reduce((validators, attributeName) => {
|
|
163
176
|
const validator = createAttributeValidator(createOrUpdate)(
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
177
|
+
{
|
|
178
|
+
attr: model.attributes[attributeName],
|
|
179
|
+
updatedAttribute: { name: attributeName, value: prop(attributeName, data) },
|
|
180
|
+
model,
|
|
181
|
+
entity,
|
|
182
|
+
},
|
|
183
|
+
options
|
|
167
184
|
);
|
|
168
185
|
|
|
169
186
|
return assoc(attributeName, validator)(validators);
|
|
@@ -172,13 +189,29 @@ const createModelValidator = createOrUpdate => (model, data, { isDraft }) => {
|
|
|
172
189
|
return yup.object().shape(schema);
|
|
173
190
|
};
|
|
174
191
|
|
|
175
|
-
const createValidateEntity = createOrUpdate => async (
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
192
|
+
const createValidateEntity = createOrUpdate => async (
|
|
193
|
+
model,
|
|
194
|
+
data,
|
|
195
|
+
{ isDraft = false } = {},
|
|
196
|
+
entity = null
|
|
197
|
+
) => {
|
|
198
|
+
if (!isObject(data)) {
|
|
199
|
+
const { displayName } = model.info;
|
|
200
|
+
|
|
201
|
+
throw new ValidationError(
|
|
202
|
+
`Invalid payload submitted for the ${createOrUpdate} of an entity of type ${displayName}. Expected an object, but got ${typeof data}`
|
|
203
|
+
);
|
|
181
204
|
}
|
|
205
|
+
|
|
206
|
+
const validator = createModelValidator(createOrUpdate)(
|
|
207
|
+
{
|
|
208
|
+
model,
|
|
209
|
+
data,
|
|
210
|
+
entity,
|
|
211
|
+
},
|
|
212
|
+
{ isDraft }
|
|
213
|
+
).required();
|
|
214
|
+
return validateYupSchema(validator, { strict: false, abortEarly: false })(data);
|
|
182
215
|
};
|
|
183
216
|
|
|
184
217
|
module.exports = {
|