@akemona-org/strapi 3.7.0

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.
Files changed (125) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +163 -0
  3. package/bin/strapi.js +239 -0
  4. package/index.d.ts +13 -0
  5. package/lib/Strapi.js +498 -0
  6. package/lib/commands/admin-reset.js +51 -0
  7. package/lib/commands/build.js +47 -0
  8. package/lib/commands/configurationDump.js +50 -0
  9. package/lib/commands/configurationRestore.js +160 -0
  10. package/lib/commands/console.js +26 -0
  11. package/lib/commands/develop.js +155 -0
  12. package/lib/commands/generate-template.js +97 -0
  13. package/lib/commands/generate.js +66 -0
  14. package/lib/commands/install.js +48 -0
  15. package/lib/commands/new.js +11 -0
  16. package/lib/commands/start.js +8 -0
  17. package/lib/commands/uninstall.js +68 -0
  18. package/lib/commands/watchAdmin.js +35 -0
  19. package/lib/core/app-configuration/config-loader.js +56 -0
  20. package/lib/core/app-configuration/config-provider.js +28 -0
  21. package/lib/core/app-configuration/index.js +99 -0
  22. package/lib/core/bootstrap.js +166 -0
  23. package/lib/core/fs.js +52 -0
  24. package/lib/core/index.js +21 -0
  25. package/lib/core/load-admin.js +36 -0
  26. package/lib/core/load-apis.js +22 -0
  27. package/lib/core/load-components.js +43 -0
  28. package/lib/core/load-extensions.js +71 -0
  29. package/lib/core/load-functions.js +21 -0
  30. package/lib/core/load-hooks.js +117 -0
  31. package/lib/core/load-middlewares.js +130 -0
  32. package/lib/core/load-modules.js +61 -0
  33. package/lib/core/load-plugins.js +68 -0
  34. package/lib/core/load-policies.js +36 -0
  35. package/lib/core/walk.js +27 -0
  36. package/lib/core-api/controller.js +158 -0
  37. package/lib/core-api/index.js +33 -0
  38. package/lib/core-api/service/collection-type.js +122 -0
  39. package/lib/core-api/service/index.js +81 -0
  40. package/lib/core-api/service/single-type.js +68 -0
  41. package/lib/hooks/index.js +97 -0
  42. package/lib/index.js +3 -0
  43. package/lib/load/check-reserved-filename.js +18 -0
  44. package/lib/load/filepath-to-prop-path.js +22 -0
  45. package/lib/load/glob.js +15 -0
  46. package/lib/load/index.js +9 -0
  47. package/lib/load/load-config-files.js +22 -0
  48. package/lib/load/load-files.js +56 -0
  49. package/lib/load/package-path.js +9 -0
  50. package/lib/load/require-file-parse.js +15 -0
  51. package/lib/middlewares/boom/defaults.json +5 -0
  52. package/lib/middlewares/boom/index.js +147 -0
  53. package/lib/middlewares/cors/index.js +66 -0
  54. package/lib/middlewares/cron/defaults.json +5 -0
  55. package/lib/middlewares/cron/index.js +43 -0
  56. package/lib/middlewares/csp/defaults.json +5 -0
  57. package/lib/middlewares/csp/index.js +26 -0
  58. package/lib/middlewares/favicon/defaults.json +7 -0
  59. package/lib/middlewares/favicon/index.js +35 -0
  60. package/lib/middlewares/gzip/defaults.json +6 -0
  61. package/lib/middlewares/gzip/index.js +19 -0
  62. package/lib/middlewares/hsts/defaults.json +7 -0
  63. package/lib/middlewares/hsts/index.js +30 -0
  64. package/lib/middlewares/index.js +120 -0
  65. package/lib/middlewares/ip/defaults.json +7 -0
  66. package/lib/middlewares/ip/index.js +25 -0
  67. package/lib/middlewares/language/defaults.json +9 -0
  68. package/lib/middlewares/language/index.js +40 -0
  69. package/lib/middlewares/logger/defaults.json +8 -0
  70. package/lib/middlewares/logger/index.js +63 -0
  71. package/lib/middlewares/p3p/defaults.json +6 -0
  72. package/lib/middlewares/p3p/index.js +29 -0
  73. package/lib/middlewares/parser/defaults.json +10 -0
  74. package/lib/middlewares/parser/index.js +71 -0
  75. package/lib/middlewares/poweredBy/defaults.json +5 -0
  76. package/lib/middlewares/poweredBy/index.js +16 -0
  77. package/lib/middlewares/public/assets/images/group_people_1.png +0 -0
  78. package/lib/middlewares/public/assets/images/group_people_2.png +0 -0
  79. package/lib/middlewares/public/assets/images/group_people_3.png +0 -0
  80. package/lib/middlewares/public/assets/images/logo_login.png +0 -0
  81. package/lib/middlewares/public/defaults.json +8 -0
  82. package/lib/middlewares/public/index.html +66 -0
  83. package/lib/middlewares/public/index.js +98 -0
  84. package/lib/middlewares/public/serve-static.js +23 -0
  85. package/lib/middlewares/responseTime/defaults.json +5 -0
  86. package/lib/middlewares/responseTime/index.js +25 -0
  87. package/lib/middlewares/responses/defaults.json +5 -0
  88. package/lib/middlewares/responses/index.js +18 -0
  89. package/lib/middlewares/router/defaults.json +7 -0
  90. package/lib/middlewares/router/index.js +64 -0
  91. package/lib/middlewares/router/utils/composeEndpoint.js +25 -0
  92. package/lib/middlewares/router/utils/routerChecker.js +92 -0
  93. package/lib/middlewares/session/defaults.json +18 -0
  94. package/lib/middlewares/session/index.js +140 -0
  95. package/lib/middlewares/xframe/defaults.json +6 -0
  96. package/lib/middlewares/xframe/index.js +33 -0
  97. package/lib/middlewares/xss/defaults.json +6 -0
  98. package/lib/middlewares/xss/index.js +30 -0
  99. package/lib/services/core-store.js +144 -0
  100. package/lib/services/entity-service.js +260 -0
  101. package/lib/services/entity-validator/index.js +199 -0
  102. package/lib/services/entity-validator/validators.js +125 -0
  103. package/lib/services/event-hub.js +15 -0
  104. package/lib/services/metrics/index.js +103 -0
  105. package/lib/services/metrics/is-truthy.js +9 -0
  106. package/lib/services/metrics/middleware.js +33 -0
  107. package/lib/services/metrics/rate-limiter.js +27 -0
  108. package/lib/services/metrics/sender.js +76 -0
  109. package/lib/services/metrics/stringify-deep.js +22 -0
  110. package/lib/services/utils/upload-files.js +70 -0
  111. package/lib/services/webhook-runner.js +159 -0
  112. package/lib/services/webhook-store.js +97 -0
  113. package/lib/services/worker-queue.js +58 -0
  114. package/lib/utils/addSlash.js +10 -0
  115. package/lib/utils/ee.js +123 -0
  116. package/lib/utils/get-prefixed-dependencies.js +7 -0
  117. package/lib/utils/index.js +25 -0
  118. package/lib/utils/openBrowser.js +145 -0
  119. package/lib/utils/resources/key.pub +9 -0
  120. package/lib/utils/resources/openChrome.applescript +83 -0
  121. package/lib/utils/run-checks.js +37 -0
  122. package/lib/utils/success.js +31 -0
  123. package/lib/utils/update-notifier/index.js +96 -0
  124. package/lib/utils/url-from-segments.js +13 -0
  125. package/package.json +143 -0
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ const convert = require('koa-convert');
4
+ const { xssProtection } = require('koa-lusca');
5
+
6
+ module.exports = strapi => {
7
+ return {
8
+ initialize() {
9
+ const defaults = require('./defaults.json');
10
+
11
+ strapi.app.use(async (ctx, next) => {
12
+ if (ctx.request.admin) {
13
+ return await convert(
14
+ xssProtection({
15
+ enabled: true,
16
+ mode: defaults.xss.mode,
17
+ })
18
+ )(ctx, next);
19
+ }
20
+
21
+ const xssConfig = strapi.config.get('middleware.settings.xss');
22
+ if (xssConfig.enabled) {
23
+ return await convert(xssProtection(xssConfig))(ctx, next);
24
+ }
25
+
26
+ await next();
27
+ });
28
+ },
29
+ };
30
+ };
@@ -0,0 +1,144 @@
1
+ 'use strict';
2
+
3
+ const coreStoreModel = config => ({
4
+ connection: config.get('database.defaultConnection'),
5
+ uid: 'strapi::core-store',
6
+ info: {
7
+ name: 'core_store',
8
+ description: '',
9
+ },
10
+ pluginOptions: {
11
+ 'content-manager': {
12
+ visible: false,
13
+ },
14
+ 'content-type-builder': {
15
+ visible: false,
16
+ },
17
+ },
18
+ attributes: {
19
+ key: {
20
+ type: 'string',
21
+ },
22
+ value: {
23
+ type: 'text',
24
+ },
25
+ type: {
26
+ type: 'string',
27
+ },
28
+ environment: {
29
+ type: 'string',
30
+ },
31
+ tag: {
32
+ type: 'string',
33
+ },
34
+ },
35
+ globalId: 'StrapiConfigs',
36
+ collectionName: 'core_store',
37
+ });
38
+
39
+ const createCoreStore = ({ environment: defaultEnv, db }) => {
40
+ return (source = {}) => {
41
+ async function get(params = {}) {
42
+ const { key, environment = defaultEnv, type = 'core', name = '', tag = '' } = Object.assign(
43
+ {},
44
+ source,
45
+ params
46
+ );
47
+
48
+ const prefix = `${type}${name ? `_${name}` : ''}`;
49
+
50
+ const where = {
51
+ key: `${prefix}_${key}`,
52
+ environment,
53
+ tag,
54
+ };
55
+
56
+ const data = await db.query('core_store').findOne(where);
57
+
58
+ if (!data) {
59
+ return null;
60
+ }
61
+
62
+ if (
63
+ data.type === 'object' ||
64
+ data.type === 'array' ||
65
+ data.type === 'boolean' ||
66
+ data.type === 'string'
67
+ ) {
68
+ try {
69
+ return JSON.parse(data.value);
70
+ } catch (err) {
71
+ return new Date(data.value);
72
+ }
73
+ } else if (data.type === 'number') {
74
+ return parseFloat(data.value);
75
+ } else {
76
+ return null;
77
+ }
78
+ }
79
+
80
+ async function set(params = {}) {
81
+ const { key, value, environment = defaultEnv, type, name, tag = '' } = Object.assign(
82
+ {},
83
+ source,
84
+ params
85
+ );
86
+
87
+ const prefix = `${type}${name ? `_${name}` : ''}`;
88
+
89
+ const where = {
90
+ key: `${prefix}_${key}`,
91
+ environment,
92
+ tag,
93
+ };
94
+
95
+ const data = await db.query('core_store').findOne(where);
96
+
97
+ if (data) {
98
+ Object.assign(data, {
99
+ value: JSON.stringify(value) || value.toString(),
100
+ type: (typeof value).toString(),
101
+ });
102
+
103
+ await db.query('core_store').update({ id: data.id }, data);
104
+ } else {
105
+ const data = Object.assign({}, where, {
106
+ value: JSON.stringify(value) || value.toString(),
107
+ type: (typeof value).toString(),
108
+ tag,
109
+ });
110
+
111
+ await db.query('core_store').create(data);
112
+ }
113
+ }
114
+
115
+ async function deleteFn(params = {}) {
116
+ const { key, environment = defaultEnv, type, name, tag = '' } = Object.assign(
117
+ {},
118
+ source,
119
+ params
120
+ );
121
+
122
+ const prefix = `${type}${name ? `_${name}` : ''}`;
123
+
124
+ const where = {
125
+ key: `${prefix}_${key}`,
126
+ environment,
127
+ tag,
128
+ };
129
+
130
+ await db.query('core_store').delete(where);
131
+ }
132
+
133
+ return {
134
+ get,
135
+ set,
136
+ delete: deleteFn,
137
+ };
138
+ };
139
+ };
140
+
141
+ module.exports = {
142
+ coreStoreModel,
143
+ createCoreStore,
144
+ };
@@ -0,0 +1,260 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+ const delegate = require('delegates');
5
+
6
+ const {
7
+ sanitizeEntity,
8
+ webhook: webhookUtils,
9
+ contentTypes: contentTypesUtils,
10
+ } = require('@akemona-org/strapi-utils');
11
+ const uploadFiles = require('./utils/upload-files');
12
+
13
+ // TODO: those should be strapi events used by the webhooks not the other way arround
14
+ const { ENTRY_CREATE, ENTRY_UPDATE, ENTRY_DELETE } = webhookUtils.webhookEvents;
15
+
16
+ module.exports = (ctx) => {
17
+ const implementation = createDefaultImplementation(ctx);
18
+
19
+ const service = {
20
+ implementation,
21
+ decorate(decorator) {
22
+ if (typeof decorator !== 'function') {
23
+ throw new Error(`Decorator must be a function, received ${typeof decorator}`);
24
+ }
25
+
26
+ this.implementation = Object.assign({}, this.implementation, decorator(this.implementation));
27
+ return this;
28
+ },
29
+ };
30
+
31
+ const delegator = delegate(service, 'implementation');
32
+
33
+ // delegate every method in implementation
34
+ Object.keys(service.implementation).forEach((key) => delegator.method(key));
35
+
36
+ return service;
37
+ };
38
+
39
+ const createDefaultImplementation = ({ db, eventHub, entityValidator }) => ({
40
+ /**
41
+ * expose some utils so the end users can use them
42
+ */
43
+ uploadFiles,
44
+
45
+ /**
46
+ * Returns default opt
47
+ * it is async so decorators can do async processing
48
+ * @param {object} params - query params to extend
49
+ * @param {object=} ctx - Query context
50
+ * @param {object} ctx.model - Model that is being used
51
+ */
52
+ async wrapOptions(options = {}) {
53
+ return options;
54
+ },
55
+
56
+ /**
57
+ * Returns a list of entries
58
+ * @param {object} opts - Query options object (params, data, files, populate)
59
+ * @param {object} ctx - Query context
60
+ * @param {object} ctx.model - Model that is being used
61
+ */
62
+ async find(opts, { model }) {
63
+ const { params, populate } = await this.wrapOptions(opts, { model, action: 'find' });
64
+
65
+ const { kind } = db.getModel(model);
66
+
67
+ // return first element and ignore filters
68
+ if (kind === 'singleType') {
69
+ const results = await db.query(model).find({ ...params, _limit: 1 }, populate);
70
+ return _.first(results) || null;
71
+ }
72
+
73
+ return db.query(model).find(params, populate);
74
+ },
75
+
76
+ /**
77
+ * Returns a paginated list of entries
78
+ * @param {object} opts - Query options object (params, data, files, populate)
79
+ * @param {object} ctx - Query context
80
+ * @param {object} ctx.model - Model that is being used
81
+ */
82
+ async findPage(opts, { model }) {
83
+ const { params, populate } = await this.wrapOptions(opts, { model, action: 'findPage' });
84
+
85
+ return db.query(model).findPage(params, populate);
86
+ },
87
+
88
+ /**
89
+ * Returns a list of entries with relation counters
90
+ * @param {object} opts - Query options object (params, data, files, populate)
91
+ * @param {object} ctx - Query context
92
+ * @param {object} ctx.model - Model that is being used
93
+ */
94
+ async findWithRelationCounts(opts, { model }) {
95
+ const { params, populate } = await this.wrapOptions(opts, {
96
+ model,
97
+ action: 'findWithRelationCounts',
98
+ });
99
+
100
+ return db.query(model).findWithRelationCounts(params, populate);
101
+ },
102
+
103
+ /**
104
+ * Returns one entry
105
+ * @param {object} opts - Query options object (params, data, files, populate)
106
+ * @param {object} ctx - Query context
107
+ * @param {object} ctx.model - Model that is being used
108
+ */
109
+ async findOne(opts, { model }) {
110
+ const { params, populate } = await this.wrapOptions(opts, { model, action: 'findOne' });
111
+
112
+ return db.query(model).findOne(params, populate);
113
+ },
114
+
115
+ /**
116
+ * Returns a count of entries
117
+ * @param {object} opts - Query options object (params, data, files, populate)
118
+ * @param {object} ctx - Query context
119
+ * @param {object} ctx.model - Model that is being used
120
+ */
121
+ async count(opts, { model }) {
122
+ const { params } = await this.wrapOptions(opts, { model, action: 'count' });
123
+
124
+ return db.query(model).count(params);
125
+ },
126
+
127
+ /**
128
+ * Creates & returns a new entry
129
+ * @param {object} opts - Query options object (params, data, files, populate)
130
+ * @param {object} ctx - Query context
131
+ * @param {object} ctx.model - Model that is being used
132
+ */
133
+ async create(opts, { model }) {
134
+ const { data, files } = await this.wrapOptions(opts, { model, action: 'create' });
135
+
136
+ const modelDef = db.getModel(model);
137
+
138
+ const isDraft = contentTypesUtils.isDraft(data, modelDef);
139
+
140
+ const validData = await entityValidator.validateEntityCreation(modelDef, data, { isDraft });
141
+
142
+ let entry = await db.query(model).create(validData);
143
+
144
+ if (files && Object.keys(files).length > 0) {
145
+ await this.uploadFiles(entry, files, { model });
146
+ entry = await this.findOne({ params: { id: entry.id } }, { model });
147
+ }
148
+
149
+ eventHub.emit(ENTRY_CREATE, {
150
+ model: modelDef.modelName,
151
+ entry: sanitizeEntity(entry, { model: modelDef }),
152
+ });
153
+
154
+ return entry;
155
+ },
156
+
157
+ /**
158
+ * Updates & returns an existing entry
159
+ * @param {object} opts - Query options object (params, data, files, populate)
160
+ * @param {object} ctx - Query context
161
+ * @param {object} ctx.model - Model that is being used
162
+ */
163
+ async update(opts, { model }) {
164
+ const { params, data, files } = await this.wrapOptions(opts, { model, action: 'update' });
165
+
166
+ const modelDef = db.getModel(model);
167
+ const existingEntry = await db.query(model).findOne(params);
168
+
169
+ const isDraft = contentTypesUtils.isDraft(existingEntry, modelDef);
170
+
171
+ const validData = await entityValidator.validateEntityUpdate(modelDef, data, {
172
+ isDraft,
173
+ });
174
+
175
+ let entry = await db.query(model).update(params, validData);
176
+
177
+ if (files && Object.keys(files).length > 0) {
178
+ await this.uploadFiles(entry, files, { model });
179
+ entry = await this.findOne({ params: { id: entry.id } }, { model });
180
+ }
181
+
182
+ eventHub.emit(ENTRY_UPDATE, {
183
+ model: modelDef.modelName,
184
+ entry: sanitizeEntity(entry, { model: modelDef }),
185
+ });
186
+
187
+ return entry;
188
+ },
189
+
190
+ /**
191
+ * Deletes & returns the entry that was deleted
192
+ * @param {object} opts - Query options object (params, data, files, populate)
193
+ * @param {object} ctx - Query context
194
+ * @param {object} ctx.model - Model that is being used
195
+ */
196
+ async delete(opts, { model }) {
197
+ const { params } = await this.wrapOptions(opts, { model, action: 'delete' });
198
+
199
+ const entry = await db.query(model).delete(params);
200
+
201
+ const modelDef = db.getModel(model);
202
+ eventHub.emit(ENTRY_DELETE, {
203
+ model: modelDef.modelName,
204
+ entry: sanitizeEntity(entry, { model: modelDef }),
205
+ });
206
+
207
+ return entry;
208
+ },
209
+
210
+ /**
211
+ * Returns a list of matching entries
212
+ * @param {object} opts - Query options object (params, data, files, populate)
213
+ * @param {object} ctx - Query context
214
+ * @param {object} ctx.model - Model that is being used
215
+ */
216
+ async search(opts, { model }) {
217
+ const { params, populate } = await this.wrapOptions(opts, { model, action: 'search' });
218
+
219
+ return db.query(model).search(params, populate);
220
+ },
221
+
222
+ /**
223
+ * Returns a list of matching entries with relations counters
224
+ * @param {object} opts - Query options object (params, data, files, populate)
225
+ * @param {object} ctx - Query context
226
+ * @param {object} ctx.model - Model that is being used
227
+ */
228
+ async searchWithRelationCounts(opts, { model }) {
229
+ const { params, populate } = await this.wrapOptions(opts, {
230
+ model,
231
+ action: 'searchWithRelationCounts',
232
+ });
233
+
234
+ return db.query(model).searchWithRelationCounts(params, populate);
235
+ },
236
+
237
+ /**
238
+ * Returns a paginated list of matching entries
239
+ * @param {object} opts - Query options object (params, data, files, populate)
240
+ * @param {object} ctx - Query context
241
+ * @param {object} ctx.model - Model that is being used
242
+ */
243
+ async searchPage(opts, { model }) {
244
+ const { params, populate } = await this.wrapOptions(opts, { model, action: 'searchPage' });
245
+
246
+ return db.query(model).searchPage(params, populate);
247
+ },
248
+
249
+ /**
250
+ * Promise to count searched records
251
+ * @param {object} opts - Query options object (params, data, files, populate)
252
+ * @param {object} ctx - Query context
253
+ * @param {object} ctx.model - Model that is being used
254
+ */
255
+ async countSearch(opts, { model }) {
256
+ const { params } = await this.wrapOptions(opts, { model, action: 'countSearch' });
257
+
258
+ return db.query(model).countSearch(params);
259
+ },
260
+ });
@@ -0,0 +1,199 @@
1
+ /**
2
+ * Entity validator
3
+ * Module that will validate input data for entity creation or edition
4
+ */
5
+ 'use strict';
6
+
7
+ const { has, assoc, prop } = require('lodash/fp');
8
+ const strapiUtils = require('@akemona-org/strapi-utils');
9
+ const validators = require('./validators');
10
+
11
+ const { yup, formatYupErrors } = strapiUtils;
12
+ const { isMediaAttribute, isScalarAttribute, getWritableAttributes } = strapiUtils.contentTypes;
13
+
14
+ const addMinMax = (attr, validator, data) => {
15
+ if (Number.isInteger(attr.min) && (attr.required || (Array.isArray(data) && data.length > 0))) {
16
+ validator = validator.min(attr.min);
17
+ }
18
+ if (Number.isInteger(attr.max)) {
19
+ validator = validator.max(attr.max);
20
+ }
21
+ return validator;
22
+ };
23
+
24
+ const addRequiredValidation = (createOrUpdate) => (required, validator) => {
25
+ if (required) {
26
+ if (createOrUpdate === 'creation') {
27
+ validator = validator.notNil();
28
+ } else if (createOrUpdate === 'update') {
29
+ validator = validator.notNull();
30
+ }
31
+ } else {
32
+ validator = validator.nullable();
33
+ }
34
+ return validator;
35
+ };
36
+
37
+ const addDefault = (createOrUpdate) => (attr, validator) => {
38
+ if (createOrUpdate === 'creation') {
39
+ if (
40
+ ((attr.type === 'component' && attr.repeatable) || attr.type === 'dynamiczone') &&
41
+ !attr.required
42
+ ) {
43
+ validator = validator.default([]);
44
+ } else {
45
+ validator = validator.default(attr.default);
46
+ }
47
+ } else {
48
+ validator = validator.default(undefined);
49
+ }
50
+
51
+ return validator;
52
+ };
53
+
54
+ const preventCast = (validator) => validator.transform((val, originalVal) => originalVal);
55
+
56
+ const createComponentValidator =
57
+ (createOrUpdate) =>
58
+ (attr, data, { isDraft }) => {
59
+ let validator;
60
+
61
+ const [model] = strapi.db.getModelsByAttribute(attr);
62
+ if (!model) {
63
+ throw new Error('Validation failed: Model not found');
64
+ }
65
+
66
+ if (prop('repeatable', attr) === true) {
67
+ validator = yup
68
+ .array()
69
+ .of(
70
+ yup.lazy((item) =>
71
+ createModelValidator(createOrUpdate)(model, item, { isDraft }).notNull()
72
+ )
73
+ );
74
+ validator = addRequiredValidation(createOrUpdate)(true, validator);
75
+ validator = addMinMax(attr, validator, data);
76
+ } else {
77
+ validator = createModelValidator(createOrUpdate)(model, data, { isDraft });
78
+ validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
79
+ }
80
+
81
+ return validator;
82
+ };
83
+
84
+ const createDzValidator =
85
+ (createOrUpdate) =>
86
+ (attr, data, { isDraft }) => {
87
+ let validator;
88
+
89
+ validator = yup.array().of(
90
+ yup.lazy((item) => {
91
+ const model = strapi.getModel(prop('__component', item));
92
+ const schema = yup
93
+ .object()
94
+ .shape({
95
+ __component: yup.string().required().oneOf(Object.keys(strapi.components)),
96
+ })
97
+ .notNull();
98
+
99
+ return model
100
+ ? schema.concat(createModelValidator(createOrUpdate)(model, item, { isDraft }))
101
+ : schema;
102
+ })
103
+ );
104
+ validator = addRequiredValidation(createOrUpdate)(true, validator);
105
+ validator = addMinMax(attr, validator, data);
106
+
107
+ return validator;
108
+ };
109
+
110
+ const createRelationValidator =
111
+ (createOrUpdate) =>
112
+ (attr, data, { isDraft }) => {
113
+ let validator;
114
+
115
+ if (Array.isArray(data)) {
116
+ validator = yup.array().of(yup.mixed());
117
+ } else {
118
+ validator = yup.mixed();
119
+ }
120
+ validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
121
+
122
+ return validator;
123
+ };
124
+
125
+ const createScalarAttributeValidator =
126
+ (createOrUpdate) =>
127
+ (attr, { isDraft }) => {
128
+ let validator;
129
+
130
+ if (has(attr.type, validators)) {
131
+ validator = validators[attr.type](attr, { isDraft });
132
+ } else {
133
+ // No validators specified - fall back to mixed
134
+ validator = yup.mixed();
135
+ }
136
+
137
+ validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
138
+
139
+ return validator;
140
+ };
141
+
142
+ const createAttributeValidator =
143
+ (createOrUpdate) =>
144
+ (attr, data, { isDraft }) => {
145
+ let validator;
146
+ if (isMediaAttribute(attr)) {
147
+ validator = yup.mixed();
148
+ } else if (isScalarAttribute(attr)) {
149
+ validator = createScalarAttributeValidator(createOrUpdate)(attr, { isDraft });
150
+ } else {
151
+ if (attr.type === 'component') {
152
+ validator = createComponentValidator(createOrUpdate)(attr, data, { isDraft });
153
+ } else if (attr.type === 'dynamiczone') {
154
+ validator = createDzValidator(createOrUpdate)(attr, data, { isDraft });
155
+ } else {
156
+ validator = createRelationValidator(createOrUpdate)(attr, data, { isDraft });
157
+ }
158
+
159
+ validator = preventCast(validator);
160
+ }
161
+
162
+ validator = addDefault(createOrUpdate)(attr, validator);
163
+
164
+ return validator;
165
+ };
166
+
167
+ const createModelValidator =
168
+ (createOrUpdate) =>
169
+ (model, data, { isDraft }) => {
170
+ const writableAttributes = model ? getWritableAttributes(model) : [];
171
+
172
+ const schema = writableAttributes.reduce((validators, attributeName) => {
173
+ const validator = createAttributeValidator(createOrUpdate)(
174
+ model.attributes[attributeName],
175
+ prop(attributeName, data),
176
+ { isDraft }
177
+ );
178
+
179
+ return assoc(attributeName, validator)(validators);
180
+ }, {});
181
+
182
+ return yup.object().shape(schema);
183
+ };
184
+
185
+ const createValidateEntity =
186
+ (createOrUpdate) =>
187
+ async (model, data, { isDraft = false } = {}) => {
188
+ try {
189
+ const validator = createModelValidator(createOrUpdate)(model, data, { isDraft }).required();
190
+ return await validator.validate(data, { abortEarly: false });
191
+ } catch (e) {
192
+ throw strapi.errors.badRequest('ValidationError', { errors: formatYupErrors(e) });
193
+ }
194
+ };
195
+
196
+ module.exports = {
197
+ validateEntityCreation: createValidateEntity('creation'),
198
+ validateEntityUpdate: createValidateEntity('update'),
199
+ };