@akemona-org/strapi 3.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +163 -0
- package/bin/strapi.js +239 -0
- package/index.d.ts +13 -0
- package/lib/Strapi.js +498 -0
- package/lib/commands/admin-reset.js +51 -0
- package/lib/commands/build.js +47 -0
- package/lib/commands/configurationDump.js +50 -0
- package/lib/commands/configurationRestore.js +160 -0
- package/lib/commands/console.js +26 -0
- package/lib/commands/develop.js +155 -0
- package/lib/commands/generate-template.js +97 -0
- package/lib/commands/generate.js +66 -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 +35 -0
- package/lib/core/app-configuration/config-loader.js +56 -0
- package/lib/core/app-configuration/config-provider.js +28 -0
- package/lib/core/app-configuration/index.js +99 -0
- package/lib/core/bootstrap.js +166 -0
- package/lib/core/fs.js +52 -0
- package/lib/core/index.js +21 -0
- package/lib/core/load-admin.js +36 -0
- package/lib/core/load-apis.js +22 -0
- package/lib/core/load-components.js +43 -0
- package/lib/core/load-extensions.js +71 -0
- package/lib/core/load-functions.js +21 -0
- package/lib/core/load-hooks.js +117 -0
- package/lib/core/load-middlewares.js +130 -0
- package/lib/core/load-modules.js +61 -0
- package/lib/core/load-plugins.js +68 -0
- package/lib/core/load-policies.js +36 -0
- package/lib/core/walk.js +27 -0
- package/lib/core-api/controller.js +158 -0
- package/lib/core-api/index.js +33 -0
- package/lib/core-api/service/collection-type.js +122 -0
- package/lib/core-api/service/index.js +81 -0
- package/lib/core-api/service/single-type.js +68 -0
- package/lib/hooks/index.js +97 -0
- package/lib/index.js +3 -0
- package/lib/load/check-reserved-filename.js +18 -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-config-files.js +22 -0
- package/lib/load/load-files.js +56 -0
- package/lib/load/package-path.js +9 -0
- package/lib/load/require-file-parse.js +15 -0
- package/lib/middlewares/boom/defaults.json +5 -0
- package/lib/middlewares/boom/index.js +147 -0
- package/lib/middlewares/cors/index.js +66 -0
- package/lib/middlewares/cron/defaults.json +5 -0
- package/lib/middlewares/cron/index.js +43 -0
- package/lib/middlewares/csp/defaults.json +5 -0
- package/lib/middlewares/csp/index.js +26 -0
- package/lib/middlewares/favicon/defaults.json +7 -0
- package/lib/middlewares/favicon/index.js +35 -0
- package/lib/middlewares/gzip/defaults.json +6 -0
- package/lib/middlewares/gzip/index.js +19 -0
- package/lib/middlewares/hsts/defaults.json +7 -0
- package/lib/middlewares/hsts/index.js +30 -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/language/defaults.json +9 -0
- package/lib/middlewares/language/index.js +40 -0
- package/lib/middlewares/logger/defaults.json +8 -0
- package/lib/middlewares/logger/index.js +63 -0
- package/lib/middlewares/p3p/defaults.json +6 -0
- package/lib/middlewares/p3p/index.js +29 -0
- package/lib/middlewares/parser/defaults.json +10 -0
- package/lib/middlewares/parser/index.js +71 -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 +98 -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 +18 -0
- package/lib/middlewares/router/defaults.json +7 -0
- package/lib/middlewares/router/index.js +64 -0
- package/lib/middlewares/router/utils/composeEndpoint.js +25 -0
- package/lib/middlewares/router/utils/routerChecker.js +92 -0
- package/lib/middlewares/session/defaults.json +18 -0
- package/lib/middlewares/session/index.js +140 -0
- package/lib/middlewares/xframe/defaults.json +6 -0
- package/lib/middlewares/xframe/index.js +33 -0
- package/lib/middlewares/xss/defaults.json +6 -0
- package/lib/middlewares/xss/index.js +30 -0
- package/lib/services/core-store.js +144 -0
- package/lib/services/entity-service.js +260 -0
- package/lib/services/entity-validator/index.js +199 -0
- package/lib/services/entity-validator/validators.js +125 -0
- package/lib/services/event-hub.js +15 -0
- package/lib/services/metrics/index.js +103 -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/utils/upload-files.js +70 -0
- package/lib/services/webhook-runner.js +159 -0
- package/lib/services/webhook-store.js +97 -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-prefixed-dependencies.js +7 -0
- package/lib/utils/index.js +25 -0
- package/lib/utils/openBrowser.js +145 -0
- package/lib/utils/resources/key.pub +9 -0
- package/lib/utils/resources/openChrome.applescript +83 -0
- package/lib/utils/run-checks.js +37 -0
- package/lib/utils/success.js +31 -0
- package/lib/utils/update-notifier/index.js +96 -0
- package/lib/utils/url-from-segments.js +13 -0
- package/package.json +143 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
const { yup } = require('@akemona-org/strapi-utils');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Utility function to compose validators
|
|
9
|
+
*/
|
|
10
|
+
const composeValidators =
|
|
11
|
+
(...fns) =>
|
|
12
|
+
(attr, { isDraft }) => {
|
|
13
|
+
return fns.reduce((validator, fn) => {
|
|
14
|
+
return fn(attr, validator, { isDraft });
|
|
15
|
+
}, yup.mixed());
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/* Validator utils */
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Adds minLength validator
|
|
22
|
+
* @param {Object} attribute model attribute
|
|
23
|
+
* @param {Object} validator yup validator
|
|
24
|
+
*/
|
|
25
|
+
const addMinLengthValidator = ({ minLength }, validator, { isDraft }) =>
|
|
26
|
+
_.isInteger(minLength) && !isDraft ? validator.min(minLength) : validator;
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Adds maxLength validator
|
|
30
|
+
* @param {Object} attribute model attribute
|
|
31
|
+
* @param {Object} validator yup validator
|
|
32
|
+
*/
|
|
33
|
+
const addMaxLengthValidator = ({ maxLength }, validator) =>
|
|
34
|
+
_.isInteger(maxLength) ? validator.max(maxLength) : validator;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Adds min integer validator
|
|
38
|
+
* @param {Object} attribute model attribute
|
|
39
|
+
* @param {Object} validator yup validator
|
|
40
|
+
*/
|
|
41
|
+
const addMinIntegerValidator = ({ min }, validator) =>
|
|
42
|
+
_.isNumber(min) ? validator.min(_.toInteger(min)) : validator;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Adds max integer validator
|
|
46
|
+
* @param {Object} attribute model attribute
|
|
47
|
+
* @param {Object} validator yup validator
|
|
48
|
+
*/
|
|
49
|
+
const addMaxIntegerValidator = ({ max }, validator) =>
|
|
50
|
+
_.isNumber(max) ? validator.max(_.toInteger(max)) : validator;
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Adds min float/decimal validator
|
|
54
|
+
* @param {Object} attribute model attribute
|
|
55
|
+
* @param {Object} validator yup validator
|
|
56
|
+
*/
|
|
57
|
+
const addMinFloatValidator = ({ min }, validator) =>
|
|
58
|
+
_.isNumber(min) ? validator.min(min) : validator;
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Adds max float/decimal validator
|
|
62
|
+
* @param {Object} attribute model attribute
|
|
63
|
+
* @param {Object} validator yup validator
|
|
64
|
+
*/
|
|
65
|
+
const addMaxFloatValidator = ({ max }, validator) =>
|
|
66
|
+
_.isNumber(max) ? validator.max(max) : validator;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Adds regex validator
|
|
70
|
+
* @param {Object} attribute model attribute
|
|
71
|
+
* @param {Object} validator yup validator
|
|
72
|
+
*/
|
|
73
|
+
const addStringRegexValidator = ({ regex }, validator) =>
|
|
74
|
+
_.isUndefined(regex) ? validator : validator.matches(new RegExp(regex));
|
|
75
|
+
|
|
76
|
+
/* Type validators */
|
|
77
|
+
|
|
78
|
+
const stringValidator = composeValidators(
|
|
79
|
+
() => yup.string().transform((val, originalVal) => originalVal),
|
|
80
|
+
addMinLengthValidator,
|
|
81
|
+
addMaxLengthValidator,
|
|
82
|
+
addStringRegexValidator
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
const emailValidator = composeValidators(stringValidator, (attr, validator) => validator.email());
|
|
86
|
+
|
|
87
|
+
const uidValidator = composeValidators(stringValidator, (attr, validator) =>
|
|
88
|
+
validator.matches(new RegExp('^[A-Za-z0-9-_.~]*$'))
|
|
89
|
+
);
|
|
90
|
+
|
|
91
|
+
const enumerationValidator = (attr) => {
|
|
92
|
+
return yup.string().oneOf((Array.isArray(attr.enum) ? attr.enum : [attr.enum]).concat(null));
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const integerValidator = composeValidators(
|
|
96
|
+
() => yup.number().integer(),
|
|
97
|
+
addMinIntegerValidator,
|
|
98
|
+
addMaxIntegerValidator
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const floatValidator = composeValidators(
|
|
102
|
+
() => yup.number(),
|
|
103
|
+
addMinFloatValidator,
|
|
104
|
+
addMaxFloatValidator
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
module.exports = {
|
|
108
|
+
string: stringValidator,
|
|
109
|
+
text: stringValidator,
|
|
110
|
+
richtext: stringValidator,
|
|
111
|
+
password: stringValidator,
|
|
112
|
+
email: emailValidator,
|
|
113
|
+
enumeration: enumerationValidator,
|
|
114
|
+
boolean: () => yup.boolean(),
|
|
115
|
+
uid: uidValidator,
|
|
116
|
+
json: () => yup.mixed(),
|
|
117
|
+
integer: integerValidator,
|
|
118
|
+
biginteger: () => yup.mixed(),
|
|
119
|
+
float: floatValidator,
|
|
120
|
+
decimal: floatValidator,
|
|
121
|
+
date: () => yup.mixed(),
|
|
122
|
+
time: () => yup.mixed(),
|
|
123
|
+
datetime: () => yup.mixed(),
|
|
124
|
+
timestamp: () => yup.mixed(),
|
|
125
|
+
};
|
|
@@ -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,103 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Strapi telemetry package.
|
|
4
|
+
* You can learn more at https://strapi.akemona.com/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;
|
|
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.app.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 => hash(`${strapi.config.info.name}${strapi.config.info.description}`);
|
|
87
|
+
|
|
88
|
+
const hashDep = strapi => {
|
|
89
|
+
const depStr = JSON.stringify(strapi.config.info.dependencies);
|
|
90
|
+
const readmePath = path.join(strapi.dir, 'README.md');
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
if (fs.existsSync(readmePath)) {
|
|
94
|
+
return hash(`${depStr}${fs.readFileSync(readmePath)}`);
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
return hash(`${depStr}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return hash(`${depStr}`);
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
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
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const os = require('os');
|
|
4
|
+
const _ = require('lodash');
|
|
5
|
+
const isDocker = require('is-docker');
|
|
6
|
+
const { machineIdSync } = require('node-machine-id');
|
|
7
|
+
const fetch = require('node-fetch');
|
|
8
|
+
const ciEnv = require('ci-info');
|
|
9
|
+
const ee = require('../../utils/ee');
|
|
10
|
+
const stringifyDeep = require('./stringify-deep');
|
|
11
|
+
|
|
12
|
+
const defaultQueryOpts = {
|
|
13
|
+
timeout: 1000,
|
|
14
|
+
headers: { 'Content-Type': 'application/json' },
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const ANALYTICS_URI = 'https://analytics.strapi.io';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Add properties from the package.json strapi key in the metadata
|
|
21
|
+
* @param {object} metadata
|
|
22
|
+
*/
|
|
23
|
+
const addPackageJsonStrapiMetadata = (metadata, strapi) => {
|
|
24
|
+
const { packageJsonStrapi = {} } = strapi.config;
|
|
25
|
+
|
|
26
|
+
_.defaults(metadata, packageJsonStrapi);
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Create a send function for event with all the necessary metadatas
|
|
31
|
+
* @param {Object} strapi strapi app
|
|
32
|
+
* @returns {Function} (event, payload) -> Promise{boolean}
|
|
33
|
+
*/
|
|
34
|
+
module.exports = strapi => {
|
|
35
|
+
const { uuid } = strapi.config;
|
|
36
|
+
const deviceId = machineIdSync();
|
|
37
|
+
const isEE = strapi.EE === true && ee.isEE === true;
|
|
38
|
+
|
|
39
|
+
const anonymous_metadata = {
|
|
40
|
+
environment: strapi.config.environment,
|
|
41
|
+
os: os.type(),
|
|
42
|
+
osPlatform: os.platform(),
|
|
43
|
+
osRelease: os.release(),
|
|
44
|
+
nodeVersion: process.version,
|
|
45
|
+
docker: process.env.DOCKER || isDocker(),
|
|
46
|
+
isCI: ciEnv.isCI,
|
|
47
|
+
version: strapi.config.info.strapi,
|
|
48
|
+
strapiVersion: strapi.config.info.strapi,
|
|
49
|
+
projectType: isEE ? 'Enterprise' : 'Community',
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
addPackageJsonStrapiMetadata(anonymous_metadata, strapi);
|
|
53
|
+
|
|
54
|
+
return async (event, payload = {}, opts = {}) => {
|
|
55
|
+
const reqParams = {
|
|
56
|
+
method: 'POST',
|
|
57
|
+
body: JSON.stringify({
|
|
58
|
+
event,
|
|
59
|
+
uuid,
|
|
60
|
+
deviceId,
|
|
61
|
+
properties: stringifyDeep({
|
|
62
|
+
...payload,
|
|
63
|
+
...anonymous_metadata,
|
|
64
|
+
}),
|
|
65
|
+
}),
|
|
66
|
+
..._.merge({}, defaultQueryOpts, opts),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const res = await fetch(`${ANALYTICS_URI}/track`, reqParams);
|
|
71
|
+
return res.ok;
|
|
72
|
+
} catch (err) {
|
|
73
|
+
return false;
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { map, mapValues, isObject, isArray, toString } = require('lodash/fp');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Stringify all non object valutes before send them
|
|
7
|
+
* @param {object} obj
|
|
8
|
+
* @returns {object}
|
|
9
|
+
*/
|
|
10
|
+
const stringifyDeep = value => {
|
|
11
|
+
if (isArray(value)) {
|
|
12
|
+
return map(stringifyDeep, value);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
if (isObject(value)) {
|
|
16
|
+
return mapValues(stringifyDeep, value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
return toString(value);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
module.exports = stringifyDeep;
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const _ = require('lodash');
|
|
4
|
+
|
|
5
|
+
module.exports = async (entry, files, { model, source }) => {
|
|
6
|
+
const entity = strapi.getModel(model, source);
|
|
7
|
+
|
|
8
|
+
if (!_.has(strapi.plugins, 'upload')) return entry;
|
|
9
|
+
|
|
10
|
+
const uploadService = strapi.plugins.upload.services.upload;
|
|
11
|
+
|
|
12
|
+
const findModelFromUploadPath = path => {
|
|
13
|
+
if (path.length === 0) return { model, source };
|
|
14
|
+
|
|
15
|
+
let currentPath = [];
|
|
16
|
+
let tmpModel = entity;
|
|
17
|
+
let modelName = model;
|
|
18
|
+
let sourceName;
|
|
19
|
+
|
|
20
|
+
for (let i = 0; i < path.length; i++) {
|
|
21
|
+
if (!tmpModel) return {};
|
|
22
|
+
const part = path[i];
|
|
23
|
+
const attr = tmpModel.attributes[part];
|
|
24
|
+
|
|
25
|
+
currentPath.push(part);
|
|
26
|
+
|
|
27
|
+
// ignore array indexes => handled in the dynamic zone section
|
|
28
|
+
if (_.isFinite(_.toNumber(path[i]))) {
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (!attr) return {};
|
|
33
|
+
|
|
34
|
+
if (attr.type === 'component') {
|
|
35
|
+
modelName = attr.component;
|
|
36
|
+
tmpModel = strapi.components[attr.component];
|
|
37
|
+
} else if (attr.type === 'dynamiczone') {
|
|
38
|
+
const entryIdx = path[i + 1]; // get component index
|
|
39
|
+
const value = _.get(entry, [...currentPath, entryIdx]);
|
|
40
|
+
|
|
41
|
+
if (!value) return {};
|
|
42
|
+
|
|
43
|
+
modelName = value.__component; // get component type
|
|
44
|
+
tmpModel = strapi.components[modelName];
|
|
45
|
+
} else if (_.has(attr, 'model') || _.has(attr, 'collection')) {
|
|
46
|
+
sourceName = attr.plugin;
|
|
47
|
+
modelName = attr.model || attr.collection;
|
|
48
|
+
tmpModel = strapi.getModel(attr.model || attr.collection, source);
|
|
49
|
+
} else {
|
|
50
|
+
return {};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return { model: modelName, source: sourceName };
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const doUpload = async (key, files) => {
|
|
58
|
+
const parts = key.split('.');
|
|
59
|
+
const [path, field] = [_.initial(parts), _.last(parts)];
|
|
60
|
+
|
|
61
|
+
const { model, source } = findModelFromUploadPath(path);
|
|
62
|
+
|
|
63
|
+
if (model) {
|
|
64
|
+
const id = _.get(entry, path.concat('id'));
|
|
65
|
+
return uploadService.uploadToEntity({ id, model, field }, files, source);
|
|
66
|
+
}
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
await Promise.all(Object.keys(files).map(key => doUpload(key, files[key])));
|
|
70
|
+
};
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The event hub is Strapi's event control center.
|
|
3
|
+
*/
|
|
4
|
+
'use strict';
|
|
5
|
+
|
|
6
|
+
const debug = require('debug')('strapi:webhook');
|
|
7
|
+
const _ = require('lodash');
|
|
8
|
+
const fetch = require('node-fetch');
|
|
9
|
+
|
|
10
|
+
const WorkerQueue = require('./worker-queue');
|
|
11
|
+
|
|
12
|
+
const defaultConfiguration = {
|
|
13
|
+
defaultHeaders: {},
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
class WebhookRunner {
|
|
17
|
+
constructor({ eventHub, logger, configuration = {} }) {
|
|
18
|
+
debug('Initialized webhook runer');
|
|
19
|
+
this.eventHub = eventHub;
|
|
20
|
+
this.logger = logger;
|
|
21
|
+
this.webhooksMap = new Map();
|
|
22
|
+
this.listeners = new Map();
|
|
23
|
+
|
|
24
|
+
if (typeof configuration !== 'object') {
|
|
25
|
+
throw new Error(
|
|
26
|
+
'Invalid configuration provided to the webhookRunner.\nCheck your server.json -> webhooks configuration'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
this.config = _.merge(defaultConfiguration, configuration);
|
|
31
|
+
|
|
32
|
+
this.queue = new WorkerQueue({ logger, concurency: 5 });
|
|
33
|
+
this.queue.subscribe(this.executeListener.bind(this));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
deleteListener(event) {
|
|
37
|
+
debug(`Deleting listener for event '${event}'`);
|
|
38
|
+
if (this.listeners.has(event)) {
|
|
39
|
+
const fn = this.listeners.get(event);
|
|
40
|
+
|
|
41
|
+
this.eventHub.off(event, fn);
|
|
42
|
+
this.listeners.delete(event);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
createListener(event) {
|
|
47
|
+
debug(`Creating listener for event '${event}'`);
|
|
48
|
+
if (this.listeners.has(event)) {
|
|
49
|
+
this.logger.error(
|
|
50
|
+
`The webhook runner is already listening for the event '${event}'. Did you mean to call .register() ?`
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const listen = info => {
|
|
55
|
+
this.queue.enqueue({ event, info });
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
this.listeners.set(event, listen);
|
|
59
|
+
this.eventHub.on(event, listen);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
async executeListener({ event, info }) {
|
|
63
|
+
debug(`Executing webhook for event '${event}'`);
|
|
64
|
+
const webhooks = this.webhooksMap.get(event) || [];
|
|
65
|
+
const activeWebhooks = webhooks.filter(
|
|
66
|
+
webhook => webhook.isEnabled === true
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
for (const webhook of activeWebhooks) {
|
|
70
|
+
await this.run(webhook, event, info).catch(error => {
|
|
71
|
+
this.logger.error('Error running webhook');
|
|
72
|
+
this.logger.error(error);
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
run(webhook, event, info = {}) {
|
|
78
|
+
const { url, headers } = webhook;
|
|
79
|
+
|
|
80
|
+
return fetch(url, {
|
|
81
|
+
method: 'post',
|
|
82
|
+
body: JSON.stringify({
|
|
83
|
+
event,
|
|
84
|
+
created_at: new Date(),
|
|
85
|
+
...info,
|
|
86
|
+
}),
|
|
87
|
+
headers: {
|
|
88
|
+
...this.config.defaultHeaders,
|
|
89
|
+
...headers,
|
|
90
|
+
'X-Strapi-Event': event,
|
|
91
|
+
'Content-Type': 'application/json',
|
|
92
|
+
},
|
|
93
|
+
timeout: 10000,
|
|
94
|
+
})
|
|
95
|
+
.then(async res => {
|
|
96
|
+
if (res.ok) {
|
|
97
|
+
return {
|
|
98
|
+
statusCode: res.status,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
statusCode: res.status,
|
|
104
|
+
message: await res.text(),
|
|
105
|
+
};
|
|
106
|
+
})
|
|
107
|
+
.catch(err => {
|
|
108
|
+
return {
|
|
109
|
+
statusCode: 500,
|
|
110
|
+
message: err.message,
|
|
111
|
+
};
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
add(webhook) {
|
|
116
|
+
debug(`Registering webhook '${webhook.id}'`);
|
|
117
|
+
const { events } = webhook;
|
|
118
|
+
|
|
119
|
+
events.forEach(event => {
|
|
120
|
+
if (this.webhooksMap.has(event)) {
|
|
121
|
+
this.webhooksMap.get(event).push(webhook);
|
|
122
|
+
} else {
|
|
123
|
+
this.webhooksMap.set(event, [webhook]);
|
|
124
|
+
this.createListener(event);
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
update(webhook) {
|
|
130
|
+
debug(`Refreshing webhook '${webhook.id}'`);
|
|
131
|
+
this.remove(webhook);
|
|
132
|
+
this.add(webhook);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
remove(webhook) {
|
|
136
|
+
debug(`Unregistering webhook '${webhook.id}'`);
|
|
137
|
+
|
|
138
|
+
this.webhooksMap.forEach((webhooks, event) => {
|
|
139
|
+
const filteredWebhooks = webhooks.filter(
|
|
140
|
+
value => value.id !== webhook.id
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
// Cleanup hanging listeners
|
|
144
|
+
if (filteredWebhooks.length === 0) {
|
|
145
|
+
this.webhooksMap.delete(event);
|
|
146
|
+
this.deleteListener(event);
|
|
147
|
+
} else {
|
|
148
|
+
this.webhooksMap.set(event, filteredWebhooks);
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Expose a factory function instead of the class
|
|
156
|
+
*/
|
|
157
|
+
module.exports = function createWebhookRunner(opts) {
|
|
158
|
+
return new WebhookRunner(opts);
|
|
159
|
+
};
|