@strapi/strapi 4.0.0-beta.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 (143) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +144 -0
  3. package/bin/strapi.js +186 -0
  4. package/lib/Strapi.js +470 -0
  5. package/lib/commands/admin-reset.js +51 -0
  6. package/lib/commands/build.js +56 -0
  7. package/lib/commands/configurationDump.js +50 -0
  8. package/lib/commands/configurationRestore.js +169 -0
  9. package/lib/commands/console.js +26 -0
  10. package/lib/commands/develop.js +157 -0
  11. package/lib/commands/generate-template.js +97 -0
  12. package/lib/commands/install.js +48 -0
  13. package/lib/commands/new.js +11 -0
  14. package/lib/commands/start.js +8 -0
  15. package/lib/commands/uninstall.js +68 -0
  16. package/lib/commands/watchAdmin.js +45 -0
  17. package/lib/container.js +45 -0
  18. package/lib/core/app-configuration/config-loader.js +20 -0
  19. package/lib/core/app-configuration/index.js +75 -0
  20. package/lib/core/app-configuration/load-config-file.js +43 -0
  21. package/lib/core/app-configuration/load-functions.js +28 -0
  22. package/lib/core/bootstrap.js +60 -0
  23. package/lib/core/domain/component/index.js +24 -0
  24. package/lib/core/domain/component/validator.js +29 -0
  25. package/lib/core/domain/content-type/index.js +140 -0
  26. package/lib/core/domain/content-type/validator.js +64 -0
  27. package/lib/core/domain/module/index.js +106 -0
  28. package/lib/core/domain/module/validation.js +36 -0
  29. package/lib/core/loaders/admin.js +16 -0
  30. package/lib/core/loaders/apis.js +157 -0
  31. package/lib/core/loaders/components.js +41 -0
  32. package/lib/core/loaders/index.js +11 -0
  33. package/lib/core/loaders/middlewares.js +86 -0
  34. package/lib/core/loaders/plugins/get-enabled-plugins.js +100 -0
  35. package/lib/core/loaders/plugins/index.js +109 -0
  36. package/lib/core/loaders/policies.js +28 -0
  37. package/lib/core/loaders/src-index.js +38 -0
  38. package/lib/core/registries/apis.js +43 -0
  39. package/lib/core/registries/config.js +21 -0
  40. package/lib/core/registries/content-types.js +53 -0
  41. package/lib/core/registries/controllers.js +43 -0
  42. package/lib/core/registries/hooks.js +37 -0
  43. package/lib/core/registries/middlewares.js +30 -0
  44. package/lib/core/registries/modules.js +44 -0
  45. package/lib/core/registries/plugins.js +28 -0
  46. package/lib/core/registries/policies.js +38 -0
  47. package/lib/core/registries/services.js +58 -0
  48. package/lib/core/utils.js +35 -0
  49. package/lib/core-api/controller/collection-type.js +84 -0
  50. package/lib/core-api/controller/index.js +26 -0
  51. package/lib/core-api/controller/single-type.js +44 -0
  52. package/lib/core-api/controller/transform.js +97 -0
  53. package/lib/core-api/index.js +39 -0
  54. package/lib/core-api/service/collection-type.js +84 -0
  55. package/lib/core-api/service/index.js +55 -0
  56. package/lib/core-api/service/pagination.js +125 -0
  57. package/lib/core-api/service/single-type.js +58 -0
  58. package/lib/index.d.ts +26 -0
  59. package/lib/index.js +3 -0
  60. package/lib/load/filepath-to-prop-path.js +22 -0
  61. package/lib/load/glob.js +15 -0
  62. package/lib/load/index.js +9 -0
  63. package/lib/load/load-files.js +56 -0
  64. package/lib/load/package-path.js +9 -0
  65. package/lib/middlewares/cors/index.js +66 -0
  66. package/lib/middlewares/error/defaults.json +5 -0
  67. package/lib/middlewares/error/index.js +147 -0
  68. package/lib/middlewares/favicon/defaults.json +7 -0
  69. package/lib/middlewares/favicon/index.js +31 -0
  70. package/lib/middlewares/gzip/defaults.json +6 -0
  71. package/lib/middlewares/gzip/index.js +19 -0
  72. package/lib/middlewares/helmet/defaults.json +18 -0
  73. package/lib/middlewares/helmet/index.js +9 -0
  74. package/lib/middlewares/index.js +120 -0
  75. package/lib/middlewares/ip/defaults.json +7 -0
  76. package/lib/middlewares/ip/index.js +25 -0
  77. package/lib/middlewares/logger/defaults.json +5 -0
  78. package/lib/middlewares/logger/index.js +37 -0
  79. package/lib/middlewares/parser/defaults.json +11 -0
  80. package/lib/middlewares/parser/index.js +75 -0
  81. package/lib/middlewares/poweredBy/defaults.json +5 -0
  82. package/lib/middlewares/poweredBy/index.js +16 -0
  83. package/lib/middlewares/public/assets/images/group_people_1.png +0 -0
  84. package/lib/middlewares/public/assets/images/group_people_2.png +0 -0
  85. package/lib/middlewares/public/assets/images/group_people_3.png +0 -0
  86. package/lib/middlewares/public/assets/images/logo_login.png +0 -0
  87. package/lib/middlewares/public/defaults.json +8 -0
  88. package/lib/middlewares/public/index.html +66 -0
  89. package/lib/middlewares/public/index.js +130 -0
  90. package/lib/middlewares/public/serve-static.js +23 -0
  91. package/lib/middlewares/responseTime/defaults.json +5 -0
  92. package/lib/middlewares/responseTime/index.js +25 -0
  93. package/lib/middlewares/responses/defaults.json +5 -0
  94. package/lib/middlewares/responses/index.js +19 -0
  95. package/lib/middlewares/router/defaults.json +7 -0
  96. package/lib/middlewares/router/index.js +97 -0
  97. package/lib/middlewares/session/defaults.json +18 -0
  98. package/lib/middlewares/session/index.js +140 -0
  99. package/lib/migrations/draft-publish.js +57 -0
  100. package/lib/services/auth/index.js +92 -0
  101. package/lib/services/core-store.js +145 -0
  102. package/lib/services/cron.js +54 -0
  103. package/lib/services/entity-service/components.js +365 -0
  104. package/lib/services/entity-service/index.d.ts +91 -0
  105. package/lib/services/entity-service/index.js +244 -0
  106. package/lib/services/entity-service/params.js +145 -0
  107. package/lib/services/entity-validator/index.js +187 -0
  108. package/lib/services/entity-validator/validators.js +123 -0
  109. package/lib/services/event-hub.js +15 -0
  110. package/lib/services/fs.js +58 -0
  111. package/lib/services/metrics/index.js +104 -0
  112. package/lib/services/metrics/is-truthy.js +9 -0
  113. package/lib/services/metrics/middleware.js +33 -0
  114. package/lib/services/metrics/rate-limiter.js +27 -0
  115. package/lib/services/metrics/sender.js +76 -0
  116. package/lib/services/metrics/stringify-deep.js +22 -0
  117. package/lib/services/server/admin-api.js +14 -0
  118. package/lib/services/server/api.js +32 -0
  119. package/lib/services/server/compose-endpoint.js +112 -0
  120. package/lib/services/server/content-api.js +16 -0
  121. package/lib/services/server/http-server.js +64 -0
  122. package/lib/services/server/index.js +108 -0
  123. package/lib/services/server/middleware.js +28 -0
  124. package/lib/services/server/policy.js +34 -0
  125. package/lib/services/server/routing.js +107 -0
  126. package/lib/services/utils/upload-files.js +79 -0
  127. package/lib/services/webhook-runner.js +155 -0
  128. package/lib/services/webhook-store.js +91 -0
  129. package/lib/services/worker-queue.js +58 -0
  130. package/lib/utils/addSlash.js +10 -0
  131. package/lib/utils/ee.js +123 -0
  132. package/lib/utils/get-dirs.js +15 -0
  133. package/lib/utils/get-prefixed-dependencies.js +7 -0
  134. package/lib/utils/index.js +11 -0
  135. package/lib/utils/is-initialized.js +23 -0
  136. package/lib/utils/open-browser.js +12 -0
  137. package/lib/utils/resources/key.pub +9 -0
  138. package/lib/utils/run-checks.js +22 -0
  139. package/lib/utils/startup-logger.js +90 -0
  140. package/lib/utils/success.js +31 -0
  141. package/lib/utils/update-notifier/index.js +97 -0
  142. package/lib/utils/url-from-segments.js +12 -0
  143. package/package.json +133 -0
@@ -0,0 +1,145 @@
1
+ 'use strict';
2
+
3
+ const { pick, pipe, isNil } = require('lodash/fp');
4
+
5
+ const {
6
+ convertSortQueryParams,
7
+ convertLimitQueryParams,
8
+ convertStartQueryParams,
9
+ convertPopulateQueryParams,
10
+ convertFiltersQueryParams,
11
+ convertFieldsQueryParams,
12
+ } = require('@strapi/utils/lib/convert-query-params');
13
+
14
+ const { contentTypes: contentTypesUtils } = require('@strapi/utils');
15
+
16
+ const { PUBLISHED_AT_ATTRIBUTE } = contentTypesUtils.constants;
17
+
18
+ // TODO: to remove once the front is migrated
19
+ const convertOldQuery = params => {
20
+ const query = {};
21
+
22
+ Object.keys(params).forEach(key => {
23
+ if (key.startsWith('_')) {
24
+ query[key.slice(1)] = params[key];
25
+ } else {
26
+ query[key] = params[key];
27
+ }
28
+ });
29
+
30
+ return query;
31
+ };
32
+
33
+ const transformCommonParams = (params = {}) => {
34
+ const { _q, sort, filters, _where, fields, populate, ...query } = params;
35
+
36
+ if (_q) {
37
+ query._q = _q;
38
+ }
39
+
40
+ if (sort) {
41
+ query.orderBy = convertSortQueryParams(sort);
42
+ }
43
+
44
+ if (filters) {
45
+ query.where = convertFiltersQueryParams(filters);
46
+ }
47
+
48
+ if (_where) {
49
+ query.where = {
50
+ $and: [_where].concat(query.where || []),
51
+ };
52
+ }
53
+
54
+ if (fields) {
55
+ query.select = convertFieldsQueryParams(fields);
56
+ }
57
+
58
+ if (populate) {
59
+ query.populate = convertPopulateQueryParams(populate);
60
+ }
61
+
62
+ return { ...convertOldQuery(query), ...query };
63
+ };
64
+
65
+ const transformPaginationParams = (params = {}) => {
66
+ const { page, pageSize, start, limit, ...query } = params;
67
+
68
+ const isPagePagination = !isNil(page) || !isNil(pageSize);
69
+ const isOffsetPagination = !isNil(start) || !isNil(limit);
70
+
71
+ if (isPagePagination && isOffsetPagination) {
72
+ throw new Error(
73
+ 'Invalid pagination attributes. You cannot use page and offset pagination in the same query'
74
+ );
75
+ }
76
+
77
+ if (page) {
78
+ query.page = Number(page);
79
+ }
80
+
81
+ if (pageSize) {
82
+ query.pageSize = Number(pageSize);
83
+ }
84
+
85
+ if (start) {
86
+ query.offset = convertStartQueryParams(start);
87
+ }
88
+
89
+ if (limit) {
90
+ query.limit = convertLimitQueryParams(limit);
91
+ }
92
+
93
+ return { ...convertOldQuery(query), ...query };
94
+ };
95
+
96
+ const transformPublicationStateParams = uid => (params = {}) => {
97
+ const contentType = strapi.getModel(uid);
98
+
99
+ if (!contentType) {
100
+ return params;
101
+ }
102
+
103
+ const { publicationState, ...query } = params;
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
+ }
121
+ }
122
+
123
+ return { ...convertOldQuery(query), ...query };
124
+ };
125
+
126
+ const pickSelectionParams = pick(['fields', 'populate']);
127
+
128
+ const transformParamsToQuery = (uid, params) => {
129
+ return pipe(
130
+ // _q, _where, filters, etc...
131
+ transformCommonParams,
132
+ // page, pageSize, start, limit
133
+ transformPaginationParams,
134
+ // publicationState
135
+ transformPublicationStateParams(uid)
136
+ )(params);
137
+ };
138
+
139
+ module.exports = {
140
+ transformCommonParams,
141
+ transformPublicationStateParams,
142
+ transformPaginationParams,
143
+ transformParamsToQuery,
144
+ pickSelectionParams,
145
+ };
@@ -0,0 +1,187 @@
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('@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 = createOrUpdate => (attr, data, { isDraft }) => {
57
+ let validator;
58
+
59
+ const model = strapi.getModel(attr.component);
60
+ if (!model) {
61
+ throw new Error('Validation failed: Model not found');
62
+ }
63
+
64
+ if (prop('repeatable', attr) === true) {
65
+ validator = yup
66
+ .array()
67
+ .of(
68
+ yup.lazy(item => createModelValidator(createOrUpdate)(model, item, { isDraft }).notNull())
69
+ );
70
+ validator = addRequiredValidation(createOrUpdate)(true, validator);
71
+ validator = addMinMax(attr, validator, data);
72
+ } else {
73
+ validator = createModelValidator(createOrUpdate)(model, data, { isDraft });
74
+ validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
75
+ }
76
+
77
+ return validator;
78
+ };
79
+
80
+ const createDzValidator = createOrUpdate => (attr, data, { isDraft }) => {
81
+ let validator;
82
+
83
+ validator = yup.array().of(
84
+ yup.lazy(item => {
85
+ const model = strapi.getModel(prop('__component', item));
86
+ const schema = yup
87
+ .object()
88
+ .shape({
89
+ __component: yup
90
+ .string()
91
+ .required()
92
+ .oneOf(Object.keys(strapi.components)),
93
+ })
94
+ .notNull();
95
+
96
+ return model
97
+ ? schema.concat(createModelValidator(createOrUpdate)(model, item, { isDraft }))
98
+ : schema;
99
+ })
100
+ );
101
+ validator = addRequiredValidation(createOrUpdate)(true, validator);
102
+ validator = addMinMax(attr, validator, data);
103
+
104
+ return validator;
105
+ };
106
+
107
+ const createRelationValidator = createOrUpdate => (attr, data, { isDraft }) => {
108
+ let validator;
109
+
110
+ if (Array.isArray(data)) {
111
+ validator = yup.array().of(yup.mixed());
112
+ } else {
113
+ validator = yup.mixed();
114
+ }
115
+ validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
116
+
117
+ return validator;
118
+ };
119
+
120
+ const createScalarAttributeValidator = createOrUpdate => (attr, { isDraft }) => {
121
+ let validator;
122
+
123
+ if (has(attr.type, validators)) {
124
+ validator = validators[attr.type](attr, { isDraft });
125
+ } else {
126
+ // No validators specified - fall back to mixed
127
+ validator = yup.mixed();
128
+ }
129
+
130
+ validator = addRequiredValidation(createOrUpdate)(!isDraft && attr.required, validator);
131
+
132
+ return validator;
133
+ };
134
+
135
+ const createAttributeValidator = createOrUpdate => (attr, data, { isDraft }) => {
136
+ let validator;
137
+
138
+ if (isMediaAttribute(attr)) {
139
+ validator = yup.mixed();
140
+ } else if (isScalarAttribute(attr)) {
141
+ validator = createScalarAttributeValidator(createOrUpdate)(attr, { isDraft });
142
+ } 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 });
147
+ } else {
148
+ validator = createRelationValidator(createOrUpdate)(attr, data, { isDraft });
149
+ }
150
+
151
+ validator = preventCast(validator);
152
+ }
153
+
154
+ validator = addDefault(createOrUpdate)(attr, validator);
155
+
156
+ return validator;
157
+ };
158
+
159
+ const createModelValidator = createOrUpdate => (model, data, { isDraft }) => {
160
+ const writableAttributes = model ? getWritableAttributes(model) : [];
161
+
162
+ const schema = writableAttributes.reduce((validators, attributeName) => {
163
+ const validator = createAttributeValidator(createOrUpdate)(
164
+ model.attributes[attributeName],
165
+ prop(attributeName, data),
166
+ { isDraft }
167
+ );
168
+
169
+ return assoc(attributeName, validator)(validators);
170
+ }, {});
171
+
172
+ return yup.object().shape(schema);
173
+ };
174
+
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) });
181
+ }
182
+ };
183
+
184
+ module.exports = {
185
+ validateEntityCreation: createValidateEntity('creation'),
186
+ validateEntityUpdate: createValidateEntity('update'),
187
+ };
@@ -0,0 +1,123 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+
5
+ const { yup } = require('@strapi/utils');
6
+
7
+ /**
8
+ * Utility function to compose validators
9
+ */
10
+ const composeValidators = (...fns) => (attr, { isDraft }) => {
11
+ return fns.reduce((validator, fn) => {
12
+ return fn(attr, validator, { isDraft });
13
+ }, yup.mixed());
14
+ };
15
+
16
+ /* Validator utils */
17
+
18
+ /**
19
+ * Adds minLength validator
20
+ * @param {Object} attribute model attribute
21
+ * @param {Object} validator yup validator
22
+ */
23
+ const addMinLengthValidator = ({ minLength }, validator, { isDraft }) =>
24
+ _.isInteger(minLength) && !isDraft ? validator.min(minLength) : validator;
25
+
26
+ /**
27
+ * Adds maxLength validator
28
+ * @param {Object} attribute model attribute
29
+ * @param {Object} validator yup validator
30
+ */
31
+ const addMaxLengthValidator = ({ maxLength }, validator) =>
32
+ _.isInteger(maxLength) ? validator.max(maxLength) : validator;
33
+
34
+ /**
35
+ * Adds min integer validator
36
+ * @param {Object} attribute model attribute
37
+ * @param {Object} validator yup validator
38
+ */
39
+ const addMinIntegerValidator = ({ min }, validator) =>
40
+ _.isNumber(min) ? validator.min(_.toInteger(min)) : validator;
41
+
42
+ /**
43
+ * Adds max integer validator
44
+ * @param {Object} attribute model attribute
45
+ * @param {Object} validator yup validator
46
+ */
47
+ const addMaxIntegerValidator = ({ max }, validator) =>
48
+ _.isNumber(max) ? validator.max(_.toInteger(max)) : validator;
49
+
50
+ /**
51
+ * Adds min float/decimal validator
52
+ * @param {Object} attribute model attribute
53
+ * @param {Object} validator yup validator
54
+ */
55
+ const addMinFloatValidator = ({ min }, validator) =>
56
+ _.isNumber(min) ? validator.min(min) : validator;
57
+
58
+ /**
59
+ * Adds max float/decimal validator
60
+ * @param {Object} attribute model attribute
61
+ * @param {Object} validator yup validator
62
+ */
63
+ const addMaxFloatValidator = ({ max }, validator) =>
64
+ _.isNumber(max) ? validator.max(max) : validator;
65
+
66
+ /**
67
+ * Adds regex validator
68
+ * @param {Object} attribute model attribute
69
+ * @param {Object} validator yup validator
70
+ */
71
+ const addStringRegexValidator = ({ regex }, validator) =>
72
+ _.isUndefined(regex) ? validator : validator.matches(new RegExp(regex));
73
+
74
+ /* Type validators */
75
+
76
+ const stringValidator = composeValidators(
77
+ () => yup.string().transform((val, originalVal) => originalVal),
78
+ addMinLengthValidator,
79
+ addMaxLengthValidator,
80
+ addStringRegexValidator
81
+ );
82
+
83
+ const emailValidator = composeValidators(stringValidator, (attr, validator) => validator.email());
84
+
85
+ const uidValidator = composeValidators(stringValidator, (attr, validator) =>
86
+ validator.matches(new RegExp('^[A-Za-z0-9-_.~]*$'))
87
+ );
88
+
89
+ const enumerationValidator = attr => {
90
+ return yup.string().oneOf((Array.isArray(attr.enum) ? attr.enum : [attr.enum]).concat(null));
91
+ };
92
+
93
+ const integerValidator = composeValidators(
94
+ () => yup.number().integer(),
95
+ addMinIntegerValidator,
96
+ addMaxIntegerValidator
97
+ );
98
+
99
+ const floatValidator = composeValidators(
100
+ () => yup.number(),
101
+ addMinFloatValidator,
102
+ addMaxFloatValidator
103
+ );
104
+
105
+ module.exports = {
106
+ string: stringValidator,
107
+ text: stringValidator,
108
+ richtext: stringValidator,
109
+ password: stringValidator,
110
+ email: emailValidator,
111
+ enumeration: enumerationValidator,
112
+ boolean: () => yup.boolean(),
113
+ uid: uidValidator,
114
+ json: () => yup.mixed(),
115
+ integer: integerValidator,
116
+ biginteger: () => yup.mixed(),
117
+ float: floatValidator,
118
+ decimal: floatValidator,
119
+ date: () => yup.mixed(),
120
+ time: () => yup.mixed(),
121
+ datetime: () => yup.mixed(),
122
+ timestamp: () => yup.mixed(),
123
+ };
@@ -0,0 +1,15 @@
1
+ /**
2
+ * The event hub is Strapi's event control center.
3
+ */
4
+ 'use strict';
5
+
6
+ const EventEmitter = require('events');
7
+
8
+ class EventHub extends EventEmitter {}
9
+
10
+ /**
11
+ * Expose a factory function instead of the class
12
+ */
13
+ module.exports = function createEventHub(opts) {
14
+ return new EventHub(opts);
15
+ };
@@ -0,0 +1,58 @@
1
+ 'use strict';
2
+
3
+ const path = require('path');
4
+ const fse = require('fs-extra');
5
+
6
+ /**
7
+ * create strapi fs layer
8
+ */
9
+ module.exports = strapi => {
10
+ function normalizePath(optPath) {
11
+ const filePath = Array.isArray(optPath) ? optPath.join('/') : optPath;
12
+
13
+ const normalizedPath = path.normalize(filePath).replace(/^\/?(\.\/|\.\.\/)+/, '');
14
+
15
+ return path.join(strapi.dirs.root, normalizedPath);
16
+ }
17
+
18
+ const strapiFS = {
19
+ /**
20
+ * Writes a file in a strapi app
21
+ * @param {Array|string} optPath - file path
22
+ * @param {string} data - content
23
+ */
24
+ writeAppFile(optPath, data) {
25
+ const writePath = normalizePath(optPath);
26
+ return fse.ensureFile(writePath).then(() => fse.writeFile(writePath, data));
27
+ },
28
+
29
+ /**
30
+ * Writes a file in a plugin extensions folder
31
+ * @param {string} plugin - plugin name
32
+ * @param {Array|string} optPath - path to file
33
+ * @param {string} data - content
34
+ */
35
+ writePluginFile(plugin, optPath, data) {
36
+ const newPath = ['extensions', plugin].concat(optPath).join('/');
37
+ return strapiFS.writeAppFile(newPath, data);
38
+ },
39
+
40
+ /**
41
+ * Removes a file in strapi app
42
+ */
43
+ removeAppFile(optPath) {
44
+ const removePath = normalizePath(optPath);
45
+ return fse.remove(removePath);
46
+ },
47
+
48
+ /**
49
+ * Appends a file in strapi app
50
+ */
51
+ appendFile(optPath, data) {
52
+ const writePath = normalizePath(optPath);
53
+ return fse.appendFileSync(writePath, data);
54
+ },
55
+ };
56
+
57
+ return strapiFS;
58
+ };
@@ -0,0 +1,104 @@
1
+ 'use strict';
2
+ /**
3
+ * Strapi telemetry package.
4
+ * You can learn more at https://strapi.io/documentation/developer-docs/latest/getting-started/usage-information.html
5
+ */
6
+
7
+ const crypto = require('crypto');
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+ const { scheduleJob } = require('node-schedule');
11
+
12
+ const ee = require('../../utils/ee');
13
+ const wrapWithRateLimit = require('./rate-limiter');
14
+ const createSender = require('./sender');
15
+ const createMiddleware = require('./middleware');
16
+ const isTruthy = require('./is-truthy');
17
+
18
+ const LIMITED_EVENTS = [
19
+ 'didSaveMediaWithAlternativeText',
20
+ 'didSaveMediaWithCaption',
21
+ 'didDisableResponsiveDimensions',
22
+ 'didEnableResponsiveDimensions',
23
+ ];
24
+
25
+ const createTelemetryInstance = strapi => {
26
+ const uuid = strapi.config.get('uuid');
27
+ const isDisabled = !uuid || isTruthy(process.env.STRAPI_TELEMETRY_DISABLED);
28
+
29
+ const crons = [];
30
+ const sender = createSender(strapi);
31
+ const sendEvent = wrapWithRateLimit(sender, { limitedEvents: LIMITED_EVENTS });
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
+ }
39
+
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' },
56
+ }
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
+ destroy() {
70
+ // clear open handles
71
+ crons.forEach(cron => cron.cancel());
72
+ },
73
+ async send(event, payload) {
74
+ if (isDisabled) return true;
75
+ return sendEvent(event, payload);
76
+ },
77
+ };
78
+ };
79
+
80
+ const hash = str =>
81
+ crypto
82
+ .createHash('sha256')
83
+ .update(str)
84
+ .digest('hex');
85
+
86
+ const hashProject = strapi =>
87
+ hash(`${strapi.config.get('info.name')}${strapi.config.get('info.description')}`);
88
+
89
+ const hashDep = strapi => {
90
+ const depStr = JSON.stringify(strapi.config.info.dependencies);
91
+ const readmePath = path.join(strapi.dirs.root, 'README.md');
92
+
93
+ try {
94
+ if (fs.existsSync(readmePath)) {
95
+ return hash(`${depStr}${fs.readFileSync(readmePath)}`);
96
+ }
97
+ } catch (err) {
98
+ return hash(`${depStr}`);
99
+ }
100
+
101
+ return hash(`${depStr}`);
102
+ };
103
+
104
+ module.exports = createTelemetryInstance;
@@ -0,0 +1,9 @@
1
+ 'use strict';
2
+
3
+ const _ = require('lodash');
4
+
5
+ const isTruthy = val => {
6
+ return [1, true].includes(val) || ['true', '1'].includes(_.toLower(val));
7
+ };
8
+
9
+ module.exports = isTruthy;
@@ -0,0 +1,33 @@
1
+ 'use strict';
2
+
3
+ const createMiddleware = ({ sendEvent }) => {
4
+ const _state = {
5
+ currentDay: null,
6
+ counter: 0,
7
+ };
8
+
9
+ return async (ctx, next) => {
10
+ const { url, method } = ctx.request;
11
+
12
+ if (!url.includes('.') && ['GET', 'PUT', 'POST', 'DELETE'].includes(method)) {
13
+ const dayOfMonth = new Date().getDate();
14
+
15
+ if (dayOfMonth !== _state.currentDay) {
16
+ _state.currentDay = dayOfMonth;
17
+ _state.counter = 0;
18
+ }
19
+
20
+ // Send max. 1000 events per day.
21
+ if (_state.counter < 1000) {
22
+ sendEvent('didReceiveRequest', { url: ctx.request.url });
23
+
24
+ // Increase counter.
25
+ _state.counter++;
26
+ }
27
+ }
28
+
29
+ await next();
30
+ };
31
+ };
32
+
33
+ module.exports = createMiddleware;
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * @param events a list of events that need to be limited
5
+ */
6
+ module.exports = (sender, { limitedEvents = [] } = {}) => {
7
+ let currentDay = new Date().getDate();
8
+ const eventCache = new Map();
9
+
10
+ return async (event, ...args) => {
11
+ if (!limitedEvents.includes(event)) {
12
+ return sender(event, ...args);
13
+ }
14
+
15
+ if (new Date().getDate() !== currentDay) {
16
+ eventCache.clear();
17
+ currentDay = new Date().getDate();
18
+ }
19
+
20
+ if (eventCache.has(event)) {
21
+ return false;
22
+ }
23
+
24
+ eventCache.set(event, true);
25
+ return sender(event, ...args);
26
+ };
27
+ };