@strapi/strapi 4.0.0-next.9 → 4.0.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 (140) hide show
  1. package/README.md +12 -12
  2. package/bin/strapi.js +32 -5
  3. package/lib/Strapi.js +140 -72
  4. package/lib/commands/build.js +16 -6
  5. package/lib/commands/console.js +1 -1
  6. package/lib/commands/content-types/list.js +22 -0
  7. package/lib/commands/develop.js +14 -15
  8. package/lib/commands/generate-template.js +4 -5
  9. package/lib/commands/hooks/list.js +22 -0
  10. package/lib/commands/middlewares/list.js +22 -0
  11. package/lib/commands/new.js +3 -1
  12. package/lib/commands/policies/list.js +22 -0
  13. package/lib/commands/routes/list.js +28 -0
  14. package/lib/commands/services/list.js +22 -0
  15. package/lib/commands/watchAdmin.js +18 -9
  16. package/lib/core/app-configuration/index.js +3 -19
  17. package/lib/core/bootstrap.js +3 -34
  18. package/lib/core/domain/content-type/index.js +3 -7
  19. package/lib/core/domain/module/index.js +8 -6
  20. package/lib/core/domain/module/validation.js +1 -4
  21. package/lib/core/loaders/admin.js +2 -2
  22. package/lib/core/loaders/apis.js +7 -7
  23. package/lib/core/loaders/components.js +3 -5
  24. package/lib/core/loaders/index.js +1 -0
  25. package/lib/core/loaders/middlewares.js +23 -123
  26. package/lib/core/loaders/plugins/get-enabled-plugins.js +48 -14
  27. package/lib/core/loaders/plugins/index.js +30 -14
  28. package/lib/core/loaders/policies.js +1 -1
  29. package/lib/core/loaders/src-index.js +39 -0
  30. package/lib/core/registries/apis.js +2 -16
  31. package/lib/core/registries/content-types.js +50 -6
  32. package/lib/core/registries/controllers.d.ts +7 -0
  33. package/lib/core/registries/controllers.js +74 -3
  34. package/lib/core/registries/hooks.d.ts +20 -0
  35. package/lib/core/registries/hooks.js +87 -0
  36. package/lib/core/registries/middlewares.d.ts +5 -0
  37. package/lib/core/registries/middlewares.js +61 -2
  38. package/lib/core/registries/modules.js +3 -3
  39. package/lib/core/registries/plugins.js +2 -2
  40. package/lib/core/registries/policies.d.ts +9 -0
  41. package/lib/core/registries/policies.js +57 -6
  42. package/lib/core/registries/services.d.ts +7 -0
  43. package/lib/core/registries/services.js +71 -15
  44. package/lib/core-api/controller/collection-type.js +38 -11
  45. package/lib/core-api/controller/index.d.ts +25 -0
  46. package/lib/core-api/controller/index.js +30 -11
  47. package/lib/core-api/controller/single-type.js +26 -7
  48. package/lib/core-api/controller/transform.js +28 -3
  49. package/lib/core-api/routes/index.js +71 -0
  50. package/lib/core-api/service/collection-type.js +22 -27
  51. package/lib/core-api/service/index.d.ts +21 -0
  52. package/lib/core-api/service/index.js +9 -19
  53. package/lib/core-api/service/pagination.js +7 -2
  54. package/lib/core-api/service/single-type.js +17 -20
  55. package/lib/factories.d.ts +48 -0
  56. package/lib/factories.js +84 -0
  57. package/lib/index.d.ts +10 -31
  58. package/lib/index.js +5 -1
  59. package/lib/middlewares/body.js +33 -0
  60. package/lib/middlewares/compression.js +8 -0
  61. package/lib/middlewares/cors.js +58 -0
  62. package/lib/middlewares/errors.js +40 -0
  63. package/lib/middlewares/favicon.js +19 -0
  64. package/lib/middlewares/index.d.ts +5 -0
  65. package/lib/middlewares/index.js +30 -116
  66. package/lib/middlewares/ip.js +8 -0
  67. package/lib/middlewares/logger.js +27 -0
  68. package/lib/middlewares/powered-by.js +20 -0
  69. package/lib/middlewares/public/index.js +98 -73
  70. package/lib/middlewares/query.js +46 -0
  71. package/lib/middlewares/response-time.js +15 -0
  72. package/lib/middlewares/responses.js +19 -0
  73. package/lib/middlewares/security.js +51 -0
  74. package/lib/middlewares/session/index.js +6 -6
  75. package/lib/migrations/draft-publish.js +57 -0
  76. package/lib/services/auth/index.js +87 -0
  77. package/lib/services/core-store.js +64 -49
  78. package/lib/services/cron.js +54 -0
  79. package/lib/services/entity-service/attributes/index.js +31 -0
  80. package/lib/services/entity-service/attributes/transforms.js +20 -0
  81. package/lib/services/entity-service/components.js +39 -15
  82. package/lib/services/entity-service/index.d.ts +91 -0
  83. package/lib/services/entity-service/index.js +118 -60
  84. package/lib/services/entity-service/params.js +48 -81
  85. package/lib/services/entity-validator/index.js +76 -43
  86. package/lib/services/entity-validator/validators.js +129 -43
  87. package/lib/services/errors.js +77 -0
  88. package/lib/services/fs.js +1 -1
  89. package/lib/services/metrics/index.js +38 -36
  90. package/lib/services/server/admin-api.js +14 -0
  91. package/lib/services/server/api.js +36 -0
  92. package/lib/services/server/compose-endpoint.js +141 -0
  93. package/lib/services/server/content-api.js +16 -0
  94. package/lib/{server.js → services/server/http-server.js} +0 -0
  95. package/lib/services/server/index.js +127 -0
  96. package/lib/services/server/koa.js +64 -0
  97. package/lib/services/server/middleware.js +122 -0
  98. package/lib/services/server/policy.js +32 -0
  99. package/lib/services/server/register-middlewares.js +110 -0
  100. package/lib/services/server/register-routes.js +106 -0
  101. package/lib/services/server/routing.js +120 -0
  102. package/lib/services/webhook-runner.js +1 -1
  103. package/lib/utils/ee.js +3 -3
  104. package/lib/utils/get-dirs.js +17 -0
  105. package/lib/utils/index.js +2 -0
  106. package/lib/utils/signals.js +24 -0
  107. package/lib/utils/update-notifier/index.js +2 -1
  108. package/package.json +93 -93
  109. package/lib/core/app-configuration/load-functions.js +0 -28
  110. package/lib/core-api/index.js +0 -39
  111. package/lib/middlewares/boom/defaults.json +0 -5
  112. package/lib/middlewares/boom/index.js +0 -147
  113. package/lib/middlewares/cors/index.js +0 -66
  114. package/lib/middlewares/cron/defaults.json +0 -5
  115. package/lib/middlewares/cron/index.js +0 -43
  116. package/lib/middlewares/favicon/defaults.json +0 -7
  117. package/lib/middlewares/favicon/index.js +0 -32
  118. package/lib/middlewares/gzip/defaults.json +0 -6
  119. package/lib/middlewares/gzip/index.js +0 -19
  120. package/lib/middlewares/helmet/defaults.json +0 -18
  121. package/lib/middlewares/helmet/index.js +0 -9
  122. package/lib/middlewares/ip/defaults.json +0 -7
  123. package/lib/middlewares/ip/index.js +0 -25
  124. package/lib/middlewares/language/defaults.json +0 -9
  125. package/lib/middlewares/language/index.js +0 -40
  126. package/lib/middlewares/logger/defaults.json +0 -5
  127. package/lib/middlewares/logger/index.js +0 -37
  128. package/lib/middlewares/parser/defaults.json +0 -11
  129. package/lib/middlewares/parser/index.js +0 -72
  130. package/lib/middlewares/poweredBy/defaults.json +0 -5
  131. package/lib/middlewares/poweredBy/index.js +0 -16
  132. package/lib/middlewares/public/defaults.json +0 -8
  133. package/lib/middlewares/responseTime/defaults.json +0 -5
  134. package/lib/middlewares/responseTime/index.js +0 -25
  135. package/lib/middlewares/responses/defaults.json +0 -5
  136. package/lib/middlewares/responses/index.js +0 -18
  137. package/lib/middlewares/router/defaults.json +0 -7
  138. package/lib/middlewares/router/index.js +0 -72
  139. package/lib/middlewares/router/utils/compose-endpoint.js +0 -169
  140. package/lib/utils/get-prefixed-dependencies.js +0 -7
@@ -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, formatYupErrors } = strapiUtils;
11
+ const { yup, validateYupSchema } = strapiUtils;
12
12
  const { isMediaAttribute, isScalarAttribute, getWritableAttributes } = strapiUtils.contentTypes;
13
+ const { ValidationError } = strapiUtils.errors;
13
14
 
14
- const addMinMax = (attr, validator, data) => {
15
- if (Number.isInteger(attr.min) && (attr.required || (Array.isArray(data) && data.length > 0))) {
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 => (required, validator) => {
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 => (attr, validator) => {
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, data, { isDraft }) => {
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 => createModelValidator(createOrUpdate)(model, item, { isDraft }).notNull())
72
+ yup.lazy(item =>
73
+ createModelValidator(createOrUpdate)({ model, data: item }, { isDraft }).notNull()
74
+ )
69
75
  );
70
- validator = addRequiredValidation(createOrUpdate)(true, validator);
71
- validator = addMinMax(attr, validator, data);
76
+ validator = addRequiredValidation(createOrUpdate)(validator, { attr: { required: true } });
77
+ validator = addMinMax(validator, { attr, updatedAttribute });
72
78
  } else {
73
- validator = createModelValidator(createOrUpdate)(model, data, { isDraft });
74
- validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
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, data, { isDraft }) => {
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)(true, validator);
102
- validator = addMinMax(attr, validator, data);
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, data, { isDraft }) => {
115
+ const createRelationValidator = createOrUpdate => ({ attr, updatedAttribute }, { isDraft }) => {
108
116
  let validator;
109
117
 
110
- if (Array.isArray(data)) {
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
- validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
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 => (attr, { isDraft }) => {
131
+ const createScalarAttributeValidator = createOrUpdate => (metas, options) => {
121
132
  let validator;
122
133
 
123
- if (has(attr.type, validators)) {
124
- validator = validators[attr.type](attr, { isDraft });
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)(!isDraft && attr.required, validator);
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 => (attr, data, { isDraft }) => {
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)(attr, { isDraft });
153
+ } else if (isScalarAttribute(metas.attr)) {
154
+ validator = createScalarAttributeValidator(createOrUpdate)(metas, options);
142
155
  } else {
143
- if (attr.type === 'component') {
144
- validator = createComponentValidator(createOrUpdate)(attr, data, { isDraft });
145
- } else if (attr.type === 'dynamiczone') {
146
- validator = createDzValidator(createOrUpdate)(attr, data, { isDraft });
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)(attr, data, { isDraft });
161
+ validator = createRelationValidator(createOrUpdate)(metas, options);
149
162
  }
150
163
 
151
164
  validator = preventCast(validator);
152
165
  }
153
166
 
154
- validator = addDefault(createOrUpdate)(attr, validator);
167
+ validator = addDefault(createOrUpdate)(validator, metas);
155
168
 
156
169
  return validator;
157
170
  };
158
171
 
159
- const createModelValidator = createOrUpdate => (model, data, { isDraft }) => {
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
- model.attributes[attributeName],
165
- prop(attributeName, data),
166
- { isDraft }
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 (model, data, { isDraft = false } = {}) => {
176
- try {
177
- const validator = createModelValidator(createOrUpdate)(model, data, { isDraft }).required();
178
- return await validator.validate(data, { abortEarly: false });
179
- } catch (e) {
180
- throw strapi.errors.badRequest('ValidationError', { errors: formatYupErrors(e) });
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 = {
@@ -4,72 +4,155 @@ const _ = require('lodash');
4
4
 
5
5
  const { yup } = require('@strapi/utils');
6
6
 
7
+ /**
8
+ * @type {import('yup').StringSchema} StringSchema
9
+ * @type {import('yup').NumberSchema} NumberSchema
10
+ * @type {import('yup').AnySchema} AnySchema
11
+ */
12
+
7
13
  /**
8
14
  * Utility function to compose validators
9
15
  */
10
- const composeValidators = (...fns) => (attr, { isDraft }) => {
11
- return fns.reduce((validator, fn) => {
12
- return fn(attr, validator, { isDraft });
13
- }, yup.mixed());
16
+ const composeValidators = (...fns) => (...args) => {
17
+ let validator = yup.mixed();
18
+
19
+ // if we receive a schema then use it as base schema for nested composition
20
+ if (yup.isSchema(args[0])) {
21
+ validator = args[0];
22
+ args = args.slice(1);
23
+ }
24
+
25
+ return fns.reduce((validator, fn) => fn(validator, ...args), validator);
14
26
  };
15
27
 
16
28
  /* Validator utils */
17
29
 
18
30
  /**
19
31
  * Adds minLength validator
20
- * @param {Object} attribute model attribute
21
- * @param {Object} validator yup validator
32
+ * @param {StringSchema} validator yup validator
33
+ * @param {Object} metas
34
+ * @param {{ minLength: Number }} metas.attr model attribute
35
+ * @param {Object} options
36
+ * @param {boolean} options.isDraft
37
+ *
38
+ * @returns {StringSchema}
22
39
  */
23
- const addMinLengthValidator = ({ minLength }, validator, { isDraft }) =>
24
- _.isInteger(minLength) && !isDraft ? validator.min(minLength) : validator;
40
+ const addMinLengthValidator = (validator, { attr }, { isDraft }) =>
41
+ _.isInteger(attr.minLength) && !isDraft ? validator.min(attr.minLength) : validator;
25
42
 
26
43
  /**
27
44
  * Adds maxLength validator
28
- * @param {Object} attribute model attribute
29
- * @param {Object} validator yup validator
45
+ * @param {StringSchema} validator yup validator
46
+ * @param {Object} metas
47
+ * @param {{ maxLength: Number }} metas.attr model attribute
48
+ *
49
+ * @returns {StringSchema}
30
50
  */
31
- const addMaxLengthValidator = ({ maxLength }, validator) =>
32
- _.isInteger(maxLength) ? validator.max(maxLength) : validator;
51
+ const addMaxLengthValidator = (validator, { attr }) =>
52
+ _.isInteger(attr.maxLength) ? validator.max(attr.maxLength) : validator;
33
53
 
34
54
  /**
35
55
  * Adds min integer validator
36
- * @param {Object} attribute model attribute
37
- * @param {Object} validator yup validator
56
+ * @param {NumberSchema} validator yup validator
57
+ * @param {Object} metas
58
+ * @param {{ min: Number }} metas.attr model attribute
59
+ *
60
+ * @returns {NumberSchema}
38
61
  */
39
- const addMinIntegerValidator = ({ min }, validator) =>
40
- _.isNumber(min) ? validator.min(_.toInteger(min)) : validator;
62
+ const addMinIntegerValidator = (validator, { attr }) =>
63
+ _.isNumber(attr.min) ? validator.min(_.toInteger(attr.min)) : validator;
41
64
 
42
65
  /**
43
66
  * Adds max integer validator
44
- * @param {Object} attribute model attribute
45
- * @param {Object} validator yup validator
67
+ * @param {NumberSchema} validator yup validator
68
+ * @param {Object} metas
69
+ * @param {{ max: Number }} metas.attr model attribute
70
+ *
71
+ * @returns {NumberSchema}
46
72
  */
47
- const addMaxIntegerValidator = ({ max }, validator) =>
48
- _.isNumber(max) ? validator.max(_.toInteger(max)) : validator;
73
+ const addMaxIntegerValidator = (validator, { attr }) =>
74
+ _.isNumber(attr.max) ? validator.max(_.toInteger(attr.max)) : validator;
49
75
 
50
76
  /**
51
77
  * Adds min float/decimal validator
52
- * @param {Object} attribute model attribute
53
- * @param {Object} validator yup validator
78
+ * @param {NumberSchema} validator yup validator
79
+ * @param {Object} metas
80
+ * @param {{ min: Number }} metas.attr model attribute
81
+ *
82
+ * @returns {NumberSchema}
54
83
  */
55
- const addMinFloatValidator = ({ min }, validator) =>
56
- _.isNumber(min) ? validator.min(min) : validator;
84
+ const addMinFloatValidator = (validator, { attr }) =>
85
+ _.isNumber(attr.min) ? validator.min(attr.min) : validator;
57
86
 
58
87
  /**
59
88
  * Adds max float/decimal validator
60
- * @param {Object} attribute model attribute
61
- * @param {Object} validator yup validator
89
+ * @param {NumberSchema} validator yup validator
90
+ * @param {Object} metas model attribute
91
+ * @param {{ max: Number }} metas.attr
92
+ *
93
+ * @returns {NumberSchema}
62
94
  */
63
- const addMaxFloatValidator = ({ max }, validator) =>
64
- _.isNumber(max) ? validator.max(max) : validator;
95
+ const addMaxFloatValidator = (validator, { attr }) =>
96
+ _.isNumber(attr.max) ? validator.max(attr.max) : validator;
65
97
 
66
98
  /**
67
99
  * Adds regex validator
68
- * @param {Object} attribute model attribute
69
- * @param {Object} validator yup validator
100
+ * @param {StringSchema} validator yup validator
101
+ * @param {Object} metas model attribute
102
+ * @param {{ regex: RegExp }} metas.attr
103
+ *
104
+ * @returns {StringSchema}
70
105
  */
71
- const addStringRegexValidator = ({ regex }, validator) =>
72
- _.isUndefined(regex) ? validator : validator.matches(new RegExp(regex));
106
+ const addStringRegexValidator = (validator, { attr }) =>
107
+ _.isUndefined(attr.regex) ? validator : validator.matches(new RegExp(attr.regex));
108
+
109
+ /**
110
+ *
111
+ * @param {AnySchema} validator
112
+ * @param {Object} metas
113
+ * @param {{ unique: Boolean, type: String }} metas.attr
114
+ * @param {{ uid: String }} metas.model
115
+ * @param {{ name: String, value: any }} metas.updatedAttribute
116
+ * @param {Object} metas.entity
117
+ *
118
+ * @returns {AnySchema}
119
+ */
120
+ const addUniqueValidator = (validator, { attr, model, updatedAttribute, entity }) => {
121
+ if (!attr.unique && attr.type !== 'uid') {
122
+ return validator;
123
+ }
124
+
125
+ return validator.test('unique', 'This attribute must be unique', async value => {
126
+ /**
127
+ * If the attribute value is `null` we want to skip the unique validation.
128
+ * Otherwise it'll only accept a single `null` entry in the database.
129
+ */
130
+ if (_.isNil(updatedAttribute.value)) {
131
+ return true;
132
+ }
133
+
134
+ /**
135
+ * If the attribute is unchanged we skip the unique verification. This will
136
+ * prevent the validator to be triggered in case the user activated the
137
+ * unique constraint after already creating multiple entries with
138
+ * the same attribute value for that field.
139
+ */
140
+ if (entity && updatedAttribute.value === entity[updatedAttribute.name]) {
141
+ return true;
142
+ }
143
+
144
+ let whereParams = entity
145
+ ? { $and: [{ [updatedAttribute.name]: value }, { $not: { id: entity.id } }] }
146
+ : { [updatedAttribute.name]: value };
147
+
148
+ const record = await strapi.db.query(model.uid).findOne({
149
+ select: ['id'],
150
+ where: whereParams,
151
+ });
152
+
153
+ return !record;
154
+ });
155
+ };
73
156
 
74
157
  /* Type validators */
75
158
 
@@ -77,29 +160,32 @@ const stringValidator = composeValidators(
77
160
  () => yup.string().transform((val, originalVal) => originalVal),
78
161
  addMinLengthValidator,
79
162
  addMaxLengthValidator,
80
- addStringRegexValidator
163
+ addStringRegexValidator,
164
+ addUniqueValidator
81
165
  );
82
166
 
83
- const emailValidator = composeValidators(stringValidator, (attr, validator) => validator.email());
167
+ const emailValidator = composeValidators(stringValidator, validator => validator.email());
84
168
 
85
- const uidValidator = composeValidators(stringValidator, (attr, validator) =>
169
+ const uidValidator = composeValidators(stringValidator, validator =>
86
170
  validator.matches(new RegExp('^[A-Za-z0-9-_.~]*$'))
87
171
  );
88
172
 
89
- const enumerationValidator = attr => {
173
+ const enumerationValidator = ({ attr }) => {
90
174
  return yup.string().oneOf((Array.isArray(attr.enum) ? attr.enum : [attr.enum]).concat(null));
91
175
  };
92
176
 
93
177
  const integerValidator = composeValidators(
94
178
  () => yup.number().integer(),
95
179
  addMinIntegerValidator,
96
- addMaxIntegerValidator
180
+ addMaxIntegerValidator,
181
+ addUniqueValidator
97
182
  );
98
183
 
99
184
  const floatValidator = composeValidators(
100
185
  () => yup.number(),
101
186
  addMinFloatValidator,
102
- addMaxFloatValidator
187
+ addMaxFloatValidator,
188
+ addUniqueValidator
103
189
  );
104
190
 
105
191
  module.exports = {
@@ -113,11 +199,11 @@ module.exports = {
113
199
  uid: uidValidator,
114
200
  json: () => yup.mixed(),
115
201
  integer: integerValidator,
116
- biginteger: () => yup.mixed(),
202
+ biginteger: composeValidators(addUniqueValidator),
117
203
  float: floatValidator,
118
204
  decimal: floatValidator,
119
- date: () => yup.mixed(),
120
- time: () => yup.mixed(),
121
- datetime: () => yup.mixed(),
122
- timestamp: () => yup.mixed(),
205
+ date: composeValidators(addUniqueValidator),
206
+ time: composeValidators(addUniqueValidator),
207
+ datetime: composeValidators(addUniqueValidator),
208
+ timestamp: composeValidators(addUniqueValidator),
123
209
  };
@@ -0,0 +1,77 @@
1
+ 'use strict';
2
+
3
+ const createError = require('http-errors');
4
+ const {
5
+ NotFoundError,
6
+ UnauthorizedError,
7
+ ForbiddenError,
8
+ PayloadTooLargeError,
9
+ } = require('@strapi/utils').errors;
10
+
11
+ const mapErrorsAndStatus = [
12
+ {
13
+ classError: UnauthorizedError,
14
+ status: 401,
15
+ },
16
+ {
17
+ classError: ForbiddenError,
18
+ status: 403,
19
+ },
20
+ {
21
+ classError: NotFoundError,
22
+ status: 404,
23
+ },
24
+ {
25
+ classError: PayloadTooLargeError,
26
+ status: 413,
27
+ },
28
+ ];
29
+
30
+ const formatApplicationError = error => {
31
+ const errorAndStatus = mapErrorsAndStatus.find(pair => error instanceof pair.classError);
32
+ const status = errorAndStatus ? errorAndStatus.status : 400;
33
+
34
+ return {
35
+ status,
36
+ body: {
37
+ data: null,
38
+ error: {
39
+ status,
40
+ name: error.name,
41
+ message: error.message,
42
+ details: error.details,
43
+ },
44
+ },
45
+ };
46
+ };
47
+
48
+ const formatHttpError = error => {
49
+ return {
50
+ status: error.status,
51
+ body: {
52
+ data: null,
53
+ error: {
54
+ status: error.status,
55
+ name: error.name,
56
+ message: error.message,
57
+ details: error.details,
58
+ },
59
+ },
60
+ };
61
+ };
62
+
63
+ const formatInternalError = error => {
64
+ const httpError = createError(error);
65
+
66
+ if (httpError.expose) {
67
+ return formatHttpError(httpError);
68
+ }
69
+
70
+ return formatHttpError(createError(httpError.status || 500));
71
+ };
72
+
73
+ module.exports = {
74
+ formatApplicationError,
75
+ formatHttpError,
76
+ formatInternalError,
77
+ };
@@ -12,7 +12,7 @@ module.exports = strapi => {
12
12
 
13
13
  const normalizedPath = path.normalize(filePath).replace(/^\/?(\.\/|\.\.\/)+/, '');
14
14
 
15
- return path.join(strapi.dir, normalizedPath);
15
+ return path.join(strapi.dirs.root, normalizedPath);
16
16
  }
17
17
 
18
18
  const strapiFS = {
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
  /**
3
3
  * Strapi telemetry package.
4
- * You can learn more at https://strapi.io/documentation/developer-docs/latest/getting-started/usage-information.html
4
+ * You can learn more at https://docs.strapi.io/developer-docs/latest/getting-started/usage-information.html
5
5
  */
6
6
 
7
7
  const crypto = require('crypto');
@@ -30,42 +30,44 @@ const createTelemetryInstance = strapi => {
30
30
  const sender = createSender(strapi);
31
31
  const sendEvent = wrapWithRateLimit(sender, { limitedEvents: LIMITED_EVENTS });
32
32
 
33
- if (!isDisabled) {
34
- const pingCron = scheduleJob('0 0 12 * * *', () => sendEvent('ping'));
35
- crons.push(pingCron);
36
-
37
- strapi.app.use(createMiddleware({ sendEvent }));
38
- }
33
+ return {
34
+ register() {
35
+ if (!isDisabled) {
36
+ const pingCron = scheduleJob('0 0 12 * * *', () => sendEvent('ping'));
37
+ crons.push(pingCron);
39
38
 
40
- if (strapi.EE === true && ee.isEE === true) {
41
- const pingDisabled =
42
- isTruthy(process.env.STRAPI_LICENSE_PING_DISABLED) && ee.licenseInfo.type === 'gold';
43
-
44
- const sendLicenseCheck = () => {
45
- return sendEvent(
46
- 'didCheckLicense',
47
- {
48
- licenseInfo: {
49
- ...ee.licenseInfo,
50
- projectHash: hashProject(strapi),
51
- dependencyHash: hashDep(strapi),
52
- },
53
- },
54
- {
55
- headers: { 'x-strapi-project': 'enterprise' },
39
+ strapi.server.use(createMiddleware({ sendEvent }));
40
+ }
41
+ },
42
+ bootstrap() {
43
+ if (strapi.EE === true && ee.isEE === true) {
44
+ const pingDisabled =
45
+ isTruthy(process.env.STRAPI_LICENSE_PING_DISABLED) && ee.licenseInfo.type === 'gold';
46
+
47
+ const sendLicenseCheck = () => {
48
+ return sendEvent(
49
+ 'didCheckLicense',
50
+ {
51
+ licenseInfo: {
52
+ ...ee.licenseInfo,
53
+ projectHash: hashProject(strapi),
54
+ dependencyHash: hashDep(strapi),
55
+ },
56
+ },
57
+ {
58
+ headers: { 'x-strapi-project': 'enterprise' },
59
+ }
60
+ );
61
+ };
62
+
63
+ if (!pingDisabled) {
64
+ const licenseCron = scheduleJob('0 0 0 * * 7', () => sendLicenseCheck());
65
+ crons.push(licenseCron);
66
+
67
+ sendLicenseCheck();
56
68
  }
57
- );
58
- };
59
-
60
- if (!pingDisabled) {
61
- const licenseCron = scheduleJob('0 0 0 * * 7', () => sendLicenseCheck());
62
- crons.push(licenseCron);
63
-
64
- sendLicenseCheck();
65
- }
66
- }
67
-
68
- return {
69
+ }
70
+ },
69
71
  destroy() {
70
72
  // clear open handles
71
73
  crons.forEach(cron => cron.cancel());
@@ -88,7 +90,7 @@ const hashProject = strapi =>
88
90
 
89
91
  const hashDep = strapi => {
90
92
  const depStr = JSON.stringify(strapi.config.info.dependencies);
91
- const readmePath = path.join(strapi.dir, 'README.md');
93
+ const readmePath = path.join(strapi.dirs.root, 'README.md');
92
94
 
93
95
  try {
94
96
  if (fs.existsSync(readmePath)) {
@@ -0,0 +1,14 @@
1
+ 'use strict';
2
+
3
+ const { createAPI } = require('./api');
4
+
5
+ const createAdminAPI = strapi => {
6
+ const opts = {
7
+ prefix: '', // '/admin';
8
+ type: 'admin',
9
+ };
10
+
11
+ return createAPI(strapi, opts);
12
+ };
13
+
14
+ module.exports = { createAdminAPI };