@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.
Files changed (89) hide show
  1. package/README.md +12 -12
  2. package/bin/strapi.js +6 -1
  3. package/lib/Strapi.js +33 -21
  4. package/lib/commands/build.js +1 -1
  5. package/lib/commands/content-types/list.js +3 -5
  6. package/lib/commands/develop.js +8 -10
  7. package/lib/commands/generate-template.js +4 -5
  8. package/lib/commands/hooks/list.js +3 -5
  9. package/lib/commands/middlewares/list.js +3 -5
  10. package/lib/commands/new.js +3 -1
  11. package/lib/commands/policies/list.js +3 -5
  12. package/lib/commands/services/list.js +22 -0
  13. package/lib/commands/watchAdmin.js +4 -4
  14. package/lib/core/app-configuration/index.js +5 -10
  15. package/lib/core/bootstrap.js +2 -2
  16. package/lib/core/domain/module/index.js +3 -1
  17. package/lib/core/domain/module/validation.js +1 -4
  18. package/lib/core/loaders/admin.js +2 -2
  19. package/lib/core/loaders/apis.js +3 -1
  20. package/lib/core/loaders/plugins/get-enabled-plugins.js +25 -9
  21. package/lib/core/loaders/plugins/index.js +21 -7
  22. package/lib/core/loaders/src-index.js +1 -0
  23. package/lib/core/registries/apis.js +2 -16
  24. package/lib/core/registries/content-types.js +50 -6
  25. package/lib/core/registries/controllers.d.ts +7 -0
  26. package/lib/core/registries/controllers.js +74 -3
  27. package/lib/core/registries/hooks.d.ts +20 -0
  28. package/lib/core/registries/hooks.js +57 -7
  29. package/lib/core/registries/middlewares.d.ts +5 -0
  30. package/lib/core/registries/middlewares.js +61 -2
  31. package/lib/core/registries/policies.d.ts +9 -0
  32. package/lib/core/registries/policies.js +57 -6
  33. package/lib/core/registries/services.d.ts +7 -0
  34. package/lib/core/registries/services.js +67 -11
  35. package/lib/core-api/controller/collection-type.js +38 -11
  36. package/lib/core-api/controller/index.d.ts +25 -0
  37. package/lib/core-api/controller/index.js +30 -11
  38. package/lib/core-api/controller/single-type.js +26 -7
  39. package/lib/core-api/routes/index.js +71 -0
  40. package/lib/core-api/service/collection-type.js +8 -12
  41. package/lib/core-api/service/index.d.ts +21 -0
  42. package/lib/core-api/service/index.js +9 -19
  43. package/lib/core-api/service/pagination.js +7 -2
  44. package/lib/core-api/service/single-type.js +12 -11
  45. package/lib/factories.d.ts +48 -0
  46. package/lib/factories.js +84 -0
  47. package/lib/index.d.ts +1 -0
  48. package/lib/index.js +5 -1
  49. package/lib/middlewares/body.js +33 -0
  50. package/lib/middlewares/compression.js +1 -1
  51. package/lib/middlewares/cors.js +3 -2
  52. package/lib/middlewares/errors.js +24 -119
  53. package/lib/middlewares/favicon.js +3 -3
  54. package/lib/middlewares/index.d.ts +2 -1
  55. package/lib/middlewares/index.js +4 -2
  56. package/lib/middlewares/ip.js +1 -1
  57. package/lib/middlewares/logger.js +1 -1
  58. package/lib/middlewares/powered-by.js +4 -2
  59. package/lib/middlewares/public/index.js +4 -7
  60. package/lib/middlewares/query.js +46 -0
  61. package/lib/middlewares/responses.js +2 -2
  62. package/lib/middlewares/security.js +29 -3
  63. package/lib/middlewares/session/index.js +1 -1
  64. package/lib/services/auth/index.js +1 -6
  65. package/lib/services/entity-service/attributes/index.js +31 -0
  66. package/lib/services/entity-service/attributes/transforms.js +20 -0
  67. package/lib/services/entity-service/components.js +2 -3
  68. package/lib/services/entity-service/index.d.ts +1 -1
  69. package/lib/services/entity-service/index.js +83 -27
  70. package/lib/services/entity-service/params.js +37 -87
  71. package/lib/services/entity-validator/index.js +76 -43
  72. package/lib/services/entity-validator/validators.js +129 -43
  73. package/lib/services/errors.js +77 -0
  74. package/lib/services/metrics/index.js +37 -35
  75. package/lib/services/server/compose-endpoint.js +39 -11
  76. package/lib/services/server/content-api.js +1 -1
  77. package/lib/services/server/index.js +4 -9
  78. package/lib/services/server/koa.js +64 -0
  79. package/lib/services/server/middleware.js +8 -1
  80. package/lib/services/server/policy.js +8 -10
  81. package/lib/services/server/register-middlewares.js +7 -2
  82. package/lib/services/server/register-routes.js +1 -3
  83. package/lib/services/server/routing.js +13 -0
  84. package/lib/utils/get-dirs.js +1 -0
  85. package/lib/utils/signals.js +24 -0
  86. package/package.json +22 -17
  87. package/lib/core/app-configuration/load-functions.js +0 -28
  88. package/lib/core-api/index.js +0 -39
  89. package/lib/middlewares/request.js +0 -74
@@ -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 (updatedAttribute.value === null) {
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
+ };
@@ -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.server.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());
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
- const { toLower, castArray, trim, prop } = require('lodash/fp');
3
+ const { has, toLower, castArray, trim, prop, isNil } = require('lodash/fp');
4
+ const { UnauthorizedError, ForbiddenError } = require('@strapi/utils').errors;
4
5
 
5
6
  const compose = require('koa-compose');
6
7
  const { resolveRouteMiddlewares } = require('./middleware');
@@ -31,8 +32,6 @@ const createAuthorizeMiddleware = strapi => async (ctx, next) => {
31
32
 
32
33
  return next();
33
34
  } catch (error) {
34
- const { UnauthorizedError, ForbiddenError } = authService.errors;
35
-
36
35
  if (error instanceof UnauthorizedError) {
37
36
  return ctx.unauthorized();
38
37
  }
@@ -49,6 +48,14 @@ const createAuthenticateMiddleware = strapi => async (ctx, next) => {
49
48
  return strapi.container.get('auth').authenticate(ctx, next);
50
49
  };
51
50
 
51
+ const returnBodyMiddleware = async (ctx, next) => {
52
+ const values = await next();
53
+
54
+ if (isNil(ctx.body) && !isNil(values)) {
55
+ ctx.body = values;
56
+ }
57
+ };
58
+
52
59
  module.exports = strapi => {
53
60
  const authenticate = createAuthenticateMiddleware(strapi);
54
61
  const authorize = createAuthorizeMiddleware(strapi);
@@ -69,39 +76,54 @@ module.exports = strapi => {
69
76
  authorize,
70
77
  ...policies,
71
78
  ...middlewares,
79
+ returnBodyMiddleware,
72
80
  ...castArray(action),
73
81
  ]);
74
82
 
75
83
  router[method](path, routeHandler);
76
84
  } catch (error) {
77
- throw new Error(`Error creating endpoint ${route.method} ${route.path}: ${error.message}`);
85
+ error.message = `Error creating endpoint ${route.method} ${route.path}: ${error.message}`;
86
+ throw error;
78
87
  }
79
88
  };
80
89
  };
81
90
 
82
91
  const getController = (name, { pluginName, apiName }, strapi) => {
92
+ let ctrl;
93
+
83
94
  if (pluginName) {
84
95
  if (pluginName === 'admin') {
85
- return strapi.controller(`admin::${name}`);
96
+ ctrl = strapi.controller(`admin::${name}`);
97
+ } else {
98
+ ctrl = strapi.plugin(pluginName).controller(name);
86
99
  }
87
-
88
- return strapi.plugin(pluginName).controller(name);
89
100
  } else if (apiName) {
90
- return strapi.controller(`api::${apiName}.${name}`);
101
+ ctrl = strapi.controller(`api::${apiName}.${name}`);
91
102
  }
92
103
 
93
- return strapi.controller(name);
104
+ if (!ctrl) {
105
+ return strapi.controller(name);
106
+ }
107
+
108
+ return ctrl;
109
+ };
110
+
111
+ const extractHandlerParts = name => {
112
+ const controllerName = name.slice(0, name.lastIndexOf('.'));
113
+ const actionName = name.slice(name.lastIndexOf('.') + 1);
114
+
115
+ return { controllerName, actionName };
94
116
  };
95
117
 
96
118
  const getAction = (route, strapi) => {
97
119
  const { handler, info = {} } = route;
98
- const { pluginName, apiName } = info;
120
+ const { pluginName, apiName, type } = info;
99
121
 
100
122
  if (Array.isArray(handler) || typeof handler === 'function') {
101
123
  return handler;
102
124
  }
103
125
 
104
- const [controllerName, actionName] = trim(handler).split('.');
126
+ const { controllerName, actionName } = extractHandlerParts(trim(handler));
105
127
 
106
128
  const controller = getController(controllerName, { pluginName, apiName }, strapi);
107
129
 
@@ -109,5 +131,11 @@ const getAction = (route, strapi) => {
109
131
  throw new Error(`Handler not found "${handler}"`);
110
132
  }
111
133
 
134
+ if (has(Symbol.for('__type__'), controller[actionName])) {
135
+ controller[actionName][Symbol.for('__type__')].push(type);
136
+ } else {
137
+ controller[actionName][Symbol.for('__type__')] = [type];
138
+ }
139
+
112
140
  return controller[actionName].bind(controller);
113
141
  };
@@ -4,7 +4,7 @@ const { createAPI } = require('./api');
4
4
 
5
5
  const createContentAPI = strapi => {
6
6
  const opts = {
7
- prefix: strapi.config.get('api.prefix', '/api'),
7
+ prefix: strapi.config.get('api.rest.prefix', '/api'),
8
8
  type: 'content-api',
9
9
  };
10
10
 
@@ -1,6 +1,5 @@
1
1
  'use strict';
2
2
 
3
- const Koa = require('koa');
4
3
  const Router = require('@koa/router');
5
4
 
6
5
  const { createHTTPServer } = require('./http-server');
@@ -9,6 +8,7 @@ const { createAdminAPI } = require('./admin-api');
9
8
  const { createContentAPI } = require('./content-api');
10
9
  const registerAllRoutes = require('./register-routes');
11
10
  const registerApplicationMiddlewares = require('./register-middlewares');
11
+ const createKoaApp = require('./koa');
12
12
 
13
13
  const healthCheck = async ctx => {
14
14
  ctx.set('strapi', 'You are so French!');
@@ -28,14 +28,9 @@ const healthCheck = async ctx => {
28
28
  * @returns {Server}
29
29
  */
30
30
  const createServer = strapi => {
31
- const app = new Koa({
32
- proxy: strapi.config.get('server.proxy'),
33
- });
34
-
35
- const router = new Router({
36
- // FIXME: this prefix can break the admin if not specified in the admin url
37
- prefix: strapi.config.get('middleware.settings.router.prefix', ''),
38
- });
31
+ const app = createKoaApp({ proxy: strapi.config.get('server.proxy') });
32
+
33
+ const router = new Router();
39
34
 
40
35
  const routeManager = createRouteManager(strapi);
41
36
 
@@ -0,0 +1,64 @@
1
+ 'use strict';
2
+
3
+ const { isNil, camelCase } = require('lodash/fp');
4
+ const Koa = require('koa');
5
+ const createError = require('http-errors');
6
+ const delegate = require('delegates');
7
+ var statuses = require('statuses');
8
+ const { formatHttpError } = require('../errors');
9
+
10
+ const addCustomMethods = app => {
11
+ const delegator = delegate(app.context, 'response');
12
+
13
+ /* errors */
14
+ statuses.codes
15
+ .filter(code => code >= 400 && code < 600)
16
+ .forEach(code => {
17
+ const name = statuses(code);
18
+ const camelCasedName = camelCase(name);
19
+ app.response[camelCasedName] = function(message, details = {}) {
20
+ const httpError = createError(code, message, { details });
21
+ const { status, body } = formatHttpError(httpError);
22
+ this.status = status;
23
+ this.body = body;
24
+ };
25
+ delegator.method(camelCasedName);
26
+ });
27
+
28
+ /* send, created, deleted */
29
+ app.response.send = function(data, status = 200) {
30
+ this.status = status;
31
+ this.body = data;
32
+ };
33
+
34
+ app.response.created = function(data) {
35
+ this.status = 201;
36
+ this.body = data;
37
+ };
38
+
39
+ app.response.deleted = function(data) {
40
+ if (isNil(data)) {
41
+ this.status = 204;
42
+ } else {
43
+ this.status = 200;
44
+ this.body = data;
45
+ }
46
+ };
47
+
48
+ delegator
49
+ .method('send')
50
+ .method('created')
51
+ .method('deleted');
52
+
53
+ return app;
54
+ };
55
+
56
+ const createKoaApp = ({ proxy }) => {
57
+ const app = new Koa({ proxy });
58
+
59
+ addCustomMethods(app);
60
+
61
+ return app;
62
+ };
63
+
64
+ module.exports = createKoaApp;
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const path = require('path');
4
- const { propOr, isArray } = require('lodash/fp');
4
+ const { propOr, isArray, isNil } = require('lodash/fp');
5
5
 
6
6
  const getMiddlewareConfig = propOr([], 'config.middlewares');
7
7
 
@@ -81,6 +81,13 @@ const resolveMiddlewares = (config, strapi) => {
81
81
  );
82
82
  }
83
83
 
84
+ middlewares.forEach(middleware => {
85
+ // NOTE: we replace null middlewares by a dumb one to avoid having to filter later on
86
+ if (isNil(middleware.handler)) {
87
+ middleware.handler = (_, next) => next();
88
+ }
89
+ });
90
+
84
91
  return middlewares;
85
92
  };
86
93
 
@@ -1,32 +1,30 @@
1
1
  'use strict';
2
2
 
3
3
  const { propOr } = require('lodash/fp');
4
- const policy = require('@strapi/utils/lib/policy');
5
-
6
- const { bodyPolicy } = policy;
4
+ const { ForbiddenError } = require('@strapi/utils').errors;
5
+ const { policy: policyUtils } = require('@strapi/utils');
7
6
 
8
7
  const getPoliciesConfig = propOr([], 'config.policies');
9
8
 
10
9
  const resolvePolicies = route => {
11
- const { pluginName, apiName } = route.info || {};
12
10
  const policiesConfig = getPoliciesConfig(route);
11
+ const resolvedPolicies = policyUtils.resolve(policiesConfig, route.info);
13
12
 
14
13
  const policiesMiddleware = async (ctx, next) => {
15
- const context = policy.createPolicyContext('koa', ctx);
14
+ const context = policyUtils.createPolicyContext('koa', ctx);
16
15
 
17
- for (const policyName of policiesConfig) {
18
- const resolvedPolicy = await policy.get(policyName, { pluginName, apiName });
19
- const result = await resolvedPolicy({ ctx: context, strapi });
16
+ for (const { handler, config } of resolvedPolicies) {
17
+ const result = await handler(context, config, { strapi });
20
18
 
21
19
  if (![true, undefined].includes(result)) {
22
- throw new Error('Policies failed.');
20
+ throw new ForbiddenError('Policies failed.');
23
21
  }
24
22
  }
25
23
 
26
24
  await next();
27
25
  };
28
26
 
29
- return [policiesMiddleware, bodyPolicy];
27
+ return [policiesMiddleware];
30
28
  };
31
29
 
32
30
  module.exports = {