@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.
- package/LICENSE +22 -0
- package/README.md +144 -0
- package/bin/strapi.js +186 -0
- package/lib/Strapi.js +470 -0
- package/lib/commands/admin-reset.js +51 -0
- package/lib/commands/build.js +56 -0
- package/lib/commands/configurationDump.js +50 -0
- package/lib/commands/configurationRestore.js +169 -0
- package/lib/commands/console.js +26 -0
- package/lib/commands/develop.js +157 -0
- package/lib/commands/generate-template.js +97 -0
- package/lib/commands/install.js +48 -0
- package/lib/commands/new.js +11 -0
- package/lib/commands/start.js +8 -0
- package/lib/commands/uninstall.js +68 -0
- package/lib/commands/watchAdmin.js +45 -0
- package/lib/container.js +45 -0
- package/lib/core/app-configuration/config-loader.js +20 -0
- package/lib/core/app-configuration/index.js +75 -0
- package/lib/core/app-configuration/load-config-file.js +43 -0
- package/lib/core/app-configuration/load-functions.js +28 -0
- package/lib/core/bootstrap.js +60 -0
- package/lib/core/domain/component/index.js +24 -0
- package/lib/core/domain/component/validator.js +29 -0
- package/lib/core/domain/content-type/index.js +140 -0
- package/lib/core/domain/content-type/validator.js +64 -0
- package/lib/core/domain/module/index.js +106 -0
- package/lib/core/domain/module/validation.js +36 -0
- package/lib/core/loaders/admin.js +16 -0
- package/lib/core/loaders/apis.js +157 -0
- package/lib/core/loaders/components.js +41 -0
- package/lib/core/loaders/index.js +11 -0
- package/lib/core/loaders/middlewares.js +86 -0
- package/lib/core/loaders/plugins/get-enabled-plugins.js +100 -0
- package/lib/core/loaders/plugins/index.js +109 -0
- package/lib/core/loaders/policies.js +28 -0
- package/lib/core/loaders/src-index.js +38 -0
- package/lib/core/registries/apis.js +43 -0
- package/lib/core/registries/config.js +21 -0
- package/lib/core/registries/content-types.js +53 -0
- package/lib/core/registries/controllers.js +43 -0
- package/lib/core/registries/hooks.js +37 -0
- package/lib/core/registries/middlewares.js +30 -0
- package/lib/core/registries/modules.js +44 -0
- package/lib/core/registries/plugins.js +28 -0
- package/lib/core/registries/policies.js +38 -0
- package/lib/core/registries/services.js +58 -0
- package/lib/core/utils.js +35 -0
- package/lib/core-api/controller/collection-type.js +84 -0
- package/lib/core-api/controller/index.js +26 -0
- package/lib/core-api/controller/single-type.js +44 -0
- package/lib/core-api/controller/transform.js +97 -0
- package/lib/core-api/index.js +39 -0
- package/lib/core-api/service/collection-type.js +84 -0
- package/lib/core-api/service/index.js +55 -0
- package/lib/core-api/service/pagination.js +125 -0
- package/lib/core-api/service/single-type.js +58 -0
- package/lib/index.d.ts +26 -0
- package/lib/index.js +3 -0
- package/lib/load/filepath-to-prop-path.js +22 -0
- package/lib/load/glob.js +15 -0
- package/lib/load/index.js +9 -0
- package/lib/load/load-files.js +56 -0
- package/lib/load/package-path.js +9 -0
- package/lib/middlewares/cors/index.js +66 -0
- package/lib/middlewares/error/defaults.json +5 -0
- package/lib/middlewares/error/index.js +147 -0
- package/lib/middlewares/favicon/defaults.json +7 -0
- package/lib/middlewares/favicon/index.js +31 -0
- package/lib/middlewares/gzip/defaults.json +6 -0
- package/lib/middlewares/gzip/index.js +19 -0
- package/lib/middlewares/helmet/defaults.json +18 -0
- package/lib/middlewares/helmet/index.js +9 -0
- package/lib/middlewares/index.js +120 -0
- package/lib/middlewares/ip/defaults.json +7 -0
- package/lib/middlewares/ip/index.js +25 -0
- package/lib/middlewares/logger/defaults.json +5 -0
- package/lib/middlewares/logger/index.js +37 -0
- package/lib/middlewares/parser/defaults.json +11 -0
- package/lib/middlewares/parser/index.js +75 -0
- package/lib/middlewares/poweredBy/defaults.json +5 -0
- package/lib/middlewares/poweredBy/index.js +16 -0
- package/lib/middlewares/public/assets/images/group_people_1.png +0 -0
- package/lib/middlewares/public/assets/images/group_people_2.png +0 -0
- package/lib/middlewares/public/assets/images/group_people_3.png +0 -0
- package/lib/middlewares/public/assets/images/logo_login.png +0 -0
- package/lib/middlewares/public/defaults.json +8 -0
- package/lib/middlewares/public/index.html +66 -0
- package/lib/middlewares/public/index.js +130 -0
- package/lib/middlewares/public/serve-static.js +23 -0
- package/lib/middlewares/responseTime/defaults.json +5 -0
- package/lib/middlewares/responseTime/index.js +25 -0
- package/lib/middlewares/responses/defaults.json +5 -0
- package/lib/middlewares/responses/index.js +19 -0
- package/lib/middlewares/router/defaults.json +7 -0
- package/lib/middlewares/router/index.js +97 -0
- package/lib/middlewares/session/defaults.json +18 -0
- package/lib/middlewares/session/index.js +140 -0
- package/lib/migrations/draft-publish.js +57 -0
- package/lib/services/auth/index.js +92 -0
- package/lib/services/core-store.js +145 -0
- package/lib/services/cron.js +54 -0
- package/lib/services/entity-service/components.js +365 -0
- package/lib/services/entity-service/index.d.ts +91 -0
- package/lib/services/entity-service/index.js +244 -0
- package/lib/services/entity-service/params.js +145 -0
- package/lib/services/entity-validator/index.js +187 -0
- package/lib/services/entity-validator/validators.js +123 -0
- package/lib/services/event-hub.js +15 -0
- package/lib/services/fs.js +58 -0
- package/lib/services/metrics/index.js +104 -0
- package/lib/services/metrics/is-truthy.js +9 -0
- package/lib/services/metrics/middleware.js +33 -0
- package/lib/services/metrics/rate-limiter.js +27 -0
- package/lib/services/metrics/sender.js +76 -0
- package/lib/services/metrics/stringify-deep.js +22 -0
- package/lib/services/server/admin-api.js +14 -0
- package/lib/services/server/api.js +32 -0
- package/lib/services/server/compose-endpoint.js +112 -0
- package/lib/services/server/content-api.js +16 -0
- package/lib/services/server/http-server.js +64 -0
- package/lib/services/server/index.js +108 -0
- package/lib/services/server/middleware.js +28 -0
- package/lib/services/server/policy.js +34 -0
- package/lib/services/server/routing.js +107 -0
- package/lib/services/utils/upload-files.js +79 -0
- package/lib/services/webhook-runner.js +155 -0
- package/lib/services/webhook-store.js +91 -0
- package/lib/services/worker-queue.js +58 -0
- package/lib/utils/addSlash.js +10 -0
- package/lib/utils/ee.js +123 -0
- package/lib/utils/get-dirs.js +15 -0
- package/lib/utils/get-prefixed-dependencies.js +7 -0
- package/lib/utils/index.js +11 -0
- package/lib/utils/is-initialized.js +23 -0
- package/lib/utils/open-browser.js +12 -0
- package/lib/utils/resources/key.pub +9 -0
- package/lib/utils/run-checks.js +22 -0
- package/lib/utils/startup-logger.js +90 -0
- package/lib/utils/success.js +31 -0
- package/lib/utils/update-notifier/index.js +97 -0
- package/lib/utils/url-from-segments.js +12 -0
- 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,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
|
+
};
|