ghost 5.90.1 → 5.91.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/components/tryghost-adapter-cache-memory-ttl-5.91.0.tgz +0 -0
- package/components/{tryghost-adapter-cache-redis-5.90.1.tgz → tryghost-adapter-cache-redis-5.91.0.tgz} +0 -0
- package/components/{tryghost-adapter-manager-5.90.1.tgz → tryghost-adapter-manager-5.91.0.tgz} +0 -0
- package/components/tryghost-announcement-bar-settings-5.91.0.tgz +0 -0
- package/components/{tryghost-api-framework-5.90.1.tgz → tryghost-api-framework-5.91.0.tgz} +0 -0
- package/components/{tryghost-api-version-compatibility-service-5.90.1.tgz → tryghost-api-version-compatibility-service-5.91.0.tgz} +0 -0
- package/components/{tryghost-audience-feedback-5.90.1.tgz → tryghost-audience-feedback-5.91.0.tgz} +0 -0
- package/components/{tryghost-bookshelf-repository-5.90.1.tgz → tryghost-bookshelf-repository-5.91.0.tgz} +0 -0
- package/components/{tryghost-bootstrap-socket-5.90.1.tgz → tryghost-bootstrap-socket-5.91.0.tgz} +0 -0
- package/components/tryghost-collections-5.91.0.tgz +0 -0
- package/components/tryghost-constants-5.91.0.tgz +0 -0
- package/components/{tryghost-custom-theme-settings-service-5.90.1.tgz → tryghost-custom-theme-settings-service-5.91.0.tgz} +0 -0
- package/components/{tryghost-data-generator-5.90.1.tgz → tryghost-data-generator-5.91.0.tgz} +0 -0
- package/components/tryghost-domain-events-5.91.0.tgz +0 -0
- package/components/tryghost-donations-5.91.0.tgz +0 -0
- package/components/{tryghost-dynamic-routing-events-5.90.1.tgz → tryghost-dynamic-routing-events-5.91.0.tgz} +0 -0
- package/components/tryghost-email-addresses-5.91.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.91.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.91.0.tgz +0 -0
- package/components/{tryghost-email-content-generator-5.90.1.tgz → tryghost-email-content-generator-5.91.0.tgz} +0 -0
- package/components/{tryghost-email-events-5.90.1.tgz → tryghost-email-events-5.91.0.tgz} +0 -0
- package/components/{tryghost-email-service-5.90.1.tgz → tryghost-email-service-5.91.0.tgz} +0 -0
- package/components/tryghost-email-suppression-list-5.91.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.91.0.tgz +0 -0
- package/components/tryghost-external-media-inliner-5.91.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.91.0.tgz +0 -0
- package/components/tryghost-ghost-5.91.0.tgz +0 -0
- package/components/{tryghost-html-to-plaintext-5.90.1.tgz → tryghost-html-to-plaintext-5.91.0.tgz} +0 -0
- package/components/tryghost-i18n-5.91.0.tgz +0 -0
- package/components/{tryghost-importer-handler-content-files-5.90.1.tgz → tryghost-importer-handler-content-files-5.91.0.tgz} +0 -0
- package/components/{tryghost-importer-revue-5.90.1.tgz → tryghost-importer-revue-5.91.0.tgz} +0 -0
- package/components/tryghost-in-memory-repository-5.91.0.tgz +0 -0
- package/components/{tryghost-job-manager-5.90.1.tgz → tryghost-job-manager-5.91.0.tgz} +0 -0
- package/components/{tryghost-link-redirects-5.90.1.tgz → tryghost-link-redirects-5.91.0.tgz} +0 -0
- package/components/tryghost-link-replacer-5.91.0.tgz +0 -0
- package/components/{tryghost-link-tracking-5.90.1.tgz → tryghost-link-tracking-5.91.0.tgz} +0 -0
- package/components/{tryghost-magic-link-5.90.1.tgz → tryghost-magic-link-5.91.0.tgz} +0 -0
- package/components/{tryghost-mail-events-5.90.1.tgz → tryghost-mail-events-5.91.0.tgz} +0 -0
- package/components/tryghost-mailgun-client-5.91.0.tgz +0 -0
- package/components/{tryghost-member-attribution-5.90.1.tgz → tryghost-member-attribution-5.91.0.tgz} +0 -0
- package/components/tryghost-member-events-5.91.0.tgz +0 -0
- package/components/{tryghost-members-api-5.90.1.tgz → tryghost-members-api-5.91.0.tgz} +0 -0
- package/components/tryghost-members-csv-5.91.0.tgz +0 -0
- package/components/{tryghost-members-events-service-5.90.1.tgz → tryghost-members-events-service-5.91.0.tgz} +0 -0
- package/components/{tryghost-members-importer-5.90.1.tgz → tryghost-members-importer-5.91.0.tgz} +0 -0
- package/components/tryghost-members-offers-5.91.0.tgz +0 -0
- package/components/tryghost-members-payments-5.91.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.91.0.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.91.0.tgz +0 -0
- package/components/{tryghost-mentions-email-report-5.90.1.tgz → tryghost-mentions-email-report-5.91.0.tgz} +0 -0
- package/components/tryghost-milestones-5.91.0.tgz +0 -0
- package/components/{tryghost-minifier-5.90.1.tgz → tryghost-minifier-5.91.0.tgz} +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.91.0.tgz +0 -0
- package/components/{tryghost-mw-api-version-mismatch-5.90.1.tgz → tryghost-mw-api-version-mismatch-5.91.0.tgz} +0 -0
- package/components/tryghost-mw-cache-control-5.91.0.tgz +0 -0
- package/components/tryghost-mw-error-handler-5.91.0.tgz +0 -0
- package/components/{tryghost-mw-session-from-token-5.90.1.tgz → tryghost-mw-session-from-token-5.91.0.tgz} +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.91.0.tgz +0 -0
- package/components/{tryghost-mw-version-match-5.90.1.tgz → tryghost-mw-version-match-5.91.0.tgz} +0 -0
- package/components/tryghost-mw-vhost-5.91.0.tgz +0 -0
- package/components/{tryghost-nql-filter-expansions-5.90.1.tgz → tryghost-nql-filter-expansions-5.91.0.tgz} +0 -0
- package/components/tryghost-oembed-service-5.91.0.tgz +0 -0
- package/components/{tryghost-package-json-5.90.1.tgz → tryghost-package-json-5.91.0.tgz} +0 -0
- package/components/{tryghost-post-events-5.90.1.tgz → tryghost-post-events-5.91.0.tgz} +0 -0
- package/components/{tryghost-post-revisions-5.90.1.tgz → tryghost-post-revisions-5.91.0.tgz} +0 -0
- package/components/{tryghost-posts-service-5.90.1.tgz → tryghost-posts-service-5.91.0.tgz} +0 -0
- package/components/tryghost-recommendations-5.91.0.tgz +0 -0
- package/components/tryghost-referrers-5.91.0.tgz +0 -0
- package/components/{tryghost-security-5.90.1.tgz → tryghost-security-5.91.0.tgz} +0 -0
- package/components/{tryghost-session-service-5.90.1.tgz → tryghost-session-service-5.91.0.tgz} +0 -0
- package/components/{tryghost-settings-path-manager-5.90.1.tgz → tryghost-settings-path-manager-5.91.0.tgz} +0 -0
- package/components/tryghost-slack-notifications-5.91.0.tgz +0 -0
- package/components/tryghost-staff-service-5.91.0.tgz +0 -0
- package/components/{tryghost-stats-service-5.90.1.tgz → tryghost-stats-service-5.91.0.tgz} +0 -0
- package/components/{tryghost-tiers-5.90.1.tgz → tryghost-tiers-5.91.0.tgz} +0 -0
- package/components/tryghost-update-check-service-5.91.0.tgz +0 -0
- package/components/tryghost-verification-trigger-5.91.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.91.0.tgz +0 -0
- package/components/tryghost-webmentions-5.91.0.tgz +0 -0
- package/core/boot.js +7 -1
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +2 -2
- package/core/built/admin/assets/admin-x-activitypub/index-963ff2fa.mjs +21434 -0
- package/core/built/admin/assets/admin-x-activitypub/modals-15352d7e.mjs +965 -0
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +2 -2
- package/core/built/admin/assets/admin-x-demo/{index-9379b3eb.mjs → index-b39b02bd.mjs} +1324 -1316
- package/core/built/admin/assets/admin-x-demo/{modals-88dc270f.mjs → modals-11aeb750.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-821d5b2c.mjs → CodeEditorView-bd41d0f7.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-29d842f9.mjs → index-4d32cd29.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-1c47835f.mjs → index-c7e5604c.mjs} +2409 -2409
- package/core/built/admin/assets/admin-x-settings/{modals-7c030639.mjs → modals-2347814d.mjs} +3 -3
- package/core/built/admin/assets/{chunk.42.eae83e03ed76c8e8c57a.js → chunk.217.2bd17b550359ea58831b.js} +17381 -8760
- package/core/built/admin/assets/{chunk.42.eae83e03ed76c8e8c57a.js.LICENSE.txt → chunk.217.2bd17b550359ea58831b.js.LICENSE.txt} +20 -0
- package/core/built/admin/assets/{chunk.524.0e32f793dedc25f9612b.js → chunk.524.be369dd173f309718bc6.js} +5 -5
- package/core/built/admin/assets/{chunk.582.55524aaa3b9579c9bf28.js → chunk.582.429ee51b198b4083ba8f.js} +6 -6
- package/core/built/admin/assets/ghost-dark-b27530aaada81101d641ff1ca5f5df6b.css +1 -0
- package/core/built/admin/assets/{ghost-e733775ba236c045dbcdaaa39231c6d8.js → ghost-e45879c99efb00163363eb86b1df74ff.js} +123 -101
- package/core/built/admin/assets/ghost-fb39dfeedba081be5e8f58578e26dd3a.css +1 -0
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +5233 -5221
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +96 -96
- package/core/built/admin/index.html +5 -5
- package/core/frontend/helpers/ghost_head.js +19 -0
- package/core/frontend/services/routing/RouterManager.js +7 -7
- package/core/server/api/endpoints/members.js +6 -0
- package/core/server/api/endpoints/utils/serializers/output/config.js +2 -1
- package/core/server/data/migrations/versions/5.91/2024-08-28-05-28-22-add-donation-message-column-to-donation-payment-events.js +9 -0
- package/core/server/data/schema/schema.js +2 -1
- package/core/server/services/email-analytics/EmailAnalyticsServiceWrapper.js +8 -37
- package/core/server/services/email-analytics/lib/queries.js +21 -117
- package/core/server/services/public-config/config.js +3 -5
- package/core/server/services/recommendations/RecommendationServiceWrapper.js +6 -1
- package/core/server/web/api/endpoints/admin/app.js +1 -1
- package/core/shared/config/defaults.json +1 -1
- package/core/shared/labs.js +1 -3
- package/package.json +153 -153
- package/yarn.lock +100 -77
- package/components/tryghost-adapter-cache-memory-ttl-5.90.1.tgz +0 -0
- package/components/tryghost-announcement-bar-settings-5.90.1.tgz +0 -0
- package/components/tryghost-collections-5.90.1.tgz +0 -0
- package/components/tryghost-constants-5.90.1.tgz +0 -0
- package/components/tryghost-domain-events-5.90.1.tgz +0 -0
- package/components/tryghost-donations-5.90.1.tgz +0 -0
- package/components/tryghost-email-addresses-5.90.1.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.90.1.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.90.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.90.1.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.90.1.tgz +0 -0
- package/components/tryghost-external-media-inliner-5.90.1.tgz +0 -0
- package/components/tryghost-extract-api-key-5.90.1.tgz +0 -0
- package/components/tryghost-ghost-5.90.1.tgz +0 -0
- package/components/tryghost-i18n-5.90.1.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.90.1.tgz +0 -0
- package/components/tryghost-link-replacer-5.90.1.tgz +0 -0
- package/components/tryghost-mailgun-client-5.90.1.tgz +0 -0
- package/components/tryghost-member-events-5.90.1.tgz +0 -0
- package/components/tryghost-members-csv-5.90.1.tgz +0 -0
- package/components/tryghost-members-offers-5.90.1.tgz +0 -0
- package/components/tryghost-members-payments-5.90.1.tgz +0 -0
- package/components/tryghost-members-ssr-5.90.1.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.90.1.tgz +0 -0
- package/components/tryghost-milestones-5.90.1.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.90.1.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.90.1.tgz +0 -0
- package/components/tryghost-mw-error-handler-5.90.1.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.90.1.tgz +0 -0
- package/components/tryghost-mw-vhost-5.90.1.tgz +0 -0
- package/components/tryghost-oembed-service-5.90.1.tgz +0 -0
- package/components/tryghost-recommendations-5.90.1.tgz +0 -0
- package/components/tryghost-referrers-5.90.1.tgz +0 -0
- package/components/tryghost-slack-notifications-5.90.1.tgz +0 -0
- package/components/tryghost-staff-service-5.90.1.tgz +0 -0
- package/components/tryghost-update-check-service-5.90.1.tgz +0 -0
- package/components/tryghost-verification-trigger-5.90.1.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.90.1.tgz +0 -0
- package/components/tryghost-webmentions-5.90.1.tgz +0 -0
- package/core/built/admin/assets/admin-x-activitypub/index-79b91eda.mjs +0 -18741
- package/core/built/admin/assets/admin-x-activitypub/modals-25d834a0.mjs +0 -1017
- package/core/built/admin/assets/ghost-dark-cedf65ab1ef5082419d66cc99a151071.css +0 -1
- package/core/built/admin/assets/ghost-e452bcc2710cf5a8372a7f7202661191.css +0 -1
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
<title>Ghost Admin</title>
|
|
9
9
|
|
|
10
10
|
|
|
11
|
-
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.
|
|
11
|
+
<meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.91%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%227a9605cc5a%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%22c118746aa4%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%22d02cf0ca62%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%22e4ce5aa74b%22%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
</style>
|
|
38
38
|
|
|
39
39
|
<link integrity="" rel="stylesheet" href="assets/vendor-0ede59da8efb5e28fa929557f7ff7154.css">
|
|
40
|
-
<link integrity="" rel="stylesheet" href="assets/ghost-
|
|
40
|
+
<link integrity="" rel="stylesheet" href="assets/ghost-fb39dfeedba081be5e8f58578e26dd3a.css" title="light">
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
</head>
|
|
@@ -57,8 +57,8 @@
|
|
|
57
57
|
<div id="ember-basic-dropdown-wormhole"></div>
|
|
58
58
|
|
|
59
59
|
<script src="assets/vendor-bff75d127fcf4a75c8a83b473bdb308f.js"></script>
|
|
60
|
-
<script src="assets/chunk.
|
|
61
|
-
<script src="assets/chunk.524.
|
|
62
|
-
<script src="assets/ghost-
|
|
60
|
+
<script src="assets/chunk.217.2bd17b550359ea58831b.js"></script>
|
|
61
|
+
<script src="assets/chunk.524.be369dd173f309718bc6.js"></script>
|
|
62
|
+
<script src="assets/ghost-e45879c99efb00163363eb86b1df74ff.js"></script>
|
|
63
63
|
</body>
|
|
64
64
|
</html>
|
|
@@ -141,6 +141,21 @@ function getWebmentionDiscoveryLink() {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
|
|
144
|
+
function getTinybirdTrackerScript(dataRoot) {
|
|
145
|
+
const scriptUrl = config.get('tinybird:tracker:scriptUrl');
|
|
146
|
+
const endpoint = config.get('tinybird:tracker:endpoint');
|
|
147
|
+
const token = config.get('tinybird:tracker:token');
|
|
148
|
+
|
|
149
|
+
const tbParams = _.map({
|
|
150
|
+
site_uuid: config.get('tinybird:tracker:id'),
|
|
151
|
+
post_uuid: dataRoot.post?.uuid,
|
|
152
|
+
member_uuid: dataRoot.member?.uuid,
|
|
153
|
+
member_status: dataRoot.member?.status
|
|
154
|
+
}, (value, key) => `tb_${key}="${value}"`).join(' ');
|
|
155
|
+
|
|
156
|
+
return `<script defer src="${scriptUrl}" data-host="${endpoint}" data-token="${token}" ${tbParams}></script>`;
|
|
157
|
+
}
|
|
158
|
+
|
|
144
159
|
/**
|
|
145
160
|
* **NOTE**
|
|
146
161
|
* Express adds `_locals`, see https://github.com/expressjs/express/blob/4.15.4/lib/response.js#L962.
|
|
@@ -319,6 +334,10 @@ module.exports = async function ghost_head(options) { // eslint-disable-line cam
|
|
|
319
334
|
if (!_.isEmpty(tagCodeInjection)) {
|
|
320
335
|
head.push(tagCodeInjection);
|
|
321
336
|
}
|
|
337
|
+
|
|
338
|
+
if (config.get('tinybird') && config.get('tinybird:tracker') && config.get('tinybird:tracker:scriptUrl')) {
|
|
339
|
+
head.push(getTinybirdTrackerScript(dataRoot));
|
|
340
|
+
}
|
|
322
341
|
}
|
|
323
342
|
|
|
324
343
|
debug('end');
|
|
@@ -126,13 +126,6 @@ class RouterManager {
|
|
|
126
126
|
this.registry.setRouter(staticRoutesRouter.identifier, staticRoutesRouter);
|
|
127
127
|
});
|
|
128
128
|
|
|
129
|
-
_.each(routerSettings.taxonomies, (value, key) => {
|
|
130
|
-
const taxonomyRouter = new TaxonomyRouter(key, value, RESOURCE_CONFIG, this.routerCreated.bind(this));
|
|
131
|
-
this.siteRouter.mountRouter(taxonomyRouter.router());
|
|
132
|
-
|
|
133
|
-
this.registry.setRouter(taxonomyRouter.identifier, taxonomyRouter);
|
|
134
|
-
});
|
|
135
|
-
|
|
136
129
|
_.each(routerSettings.collections, (value, key) => {
|
|
137
130
|
const collectionRouter = new CollectionRouter(key, value, RESOURCE_CONFIG, this.routerCreated.bind(this));
|
|
138
131
|
this.siteRouter.mountRouter(collectionRouter.router());
|
|
@@ -144,6 +137,13 @@ class RouterManager {
|
|
|
144
137
|
|
|
145
138
|
this.registry.setRouter('staticPagesRouter', staticPagesRouter);
|
|
146
139
|
|
|
140
|
+
_.each(routerSettings.taxonomies, (value, key) => {
|
|
141
|
+
const taxonomyRouter = new TaxonomyRouter(key, value, RESOURCE_CONFIG, this.routerCreated.bind(this));
|
|
142
|
+
this.siteRouter.mountRouter(taxonomyRouter.router());
|
|
143
|
+
|
|
144
|
+
this.registry.setRouter(taxonomyRouter.identifier, taxonomyRouter);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
147
|
const appRouter = new ParentRouter('AppsRouter');
|
|
148
148
|
this.siteRouter.mountRouter(appRouter.router());
|
|
149
149
|
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// as it is a getter and may change during runtime.
|
|
3
3
|
const moment = require('moment-timezone');
|
|
4
4
|
const errors = require('@tryghost/errors');
|
|
5
|
+
const logging = require('@tryghost/logging');
|
|
5
6
|
const models = require('../../models');
|
|
6
7
|
const membersService = require('../../services/members');
|
|
7
8
|
|
|
@@ -11,6 +12,7 @@ const _ = require('lodash');
|
|
|
11
12
|
|
|
12
13
|
const messages = {
|
|
13
14
|
memberNotFound: 'Member not found.',
|
|
15
|
+
notSendingWelcomeEmail: 'Email verification required, welcome email is disabled',
|
|
14
16
|
memberAlreadyExists: {
|
|
15
17
|
message: 'Member already exists',
|
|
16
18
|
context: 'Attempting to {action} member with existing email address.'
|
|
@@ -115,6 +117,10 @@ const controller = {
|
|
|
115
117
|
},
|
|
116
118
|
permissions: true,
|
|
117
119
|
async query(frame) {
|
|
120
|
+
if (await membersService.verificationTrigger.checkVerificationRequired()) {
|
|
121
|
+
logging.warn(tpl(messages.notSendingWelcomeEmail));
|
|
122
|
+
frame.options.send_email = false;
|
|
123
|
+
}
|
|
118
124
|
const member = await membersService.api.memberBREADService.add(frame.data.members[0], frame.options);
|
|
119
125
|
|
|
120
126
|
return member;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// For information on writing migrations, see https://www.notion.so/ghost/Database-migrations-eb5b78c435d741d2b34a582d57c24253
|
|
2
|
+
|
|
3
|
+
const {createAddColumnMigration} = require('../../utils');
|
|
4
|
+
|
|
5
|
+
module.exports = createAddColumnMigration('donation_payment_events', 'donation_message', {
|
|
6
|
+
type: 'string',
|
|
7
|
+
maxlength: 255, // as per stripe limitation for custom fields https://docs.stripe.com/payments/checkout/custom-fields
|
|
8
|
+
nullable: true
|
|
9
|
+
});
|
|
@@ -766,7 +766,8 @@ module.exports = {
|
|
|
766
766
|
referrer_source: {type: 'string', maxlength: 191, nullable: true},
|
|
767
767
|
referrer_medium: {type: 'string', maxlength: 191, nullable: true},
|
|
768
768
|
referrer_url: {type: 'string', maxlength: 2000, nullable: true},
|
|
769
|
-
created_at: {type: 'dateTime', nullable: false}
|
|
769
|
+
created_at: {type: 'dateTime', nullable: false},
|
|
770
|
+
donation_message: {type: 'string', maxlength: 255, nullable: true} // https://docs.stripe.com/payments/checkout/custom-fields
|
|
770
771
|
},
|
|
771
772
|
stripe_products: {
|
|
772
773
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
@@ -57,22 +57,11 @@ class EmailAnalyticsServiceWrapper {
|
|
|
57
57
|
});
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
async
|
|
61
|
-
logging.info('[EmailAnalytics] Fetch latest
|
|
60
|
+
async fetchLatest({maxEvents} = {maxEvents: Infinity}) {
|
|
61
|
+
logging.info('[EmailAnalytics] Fetch latest started');
|
|
62
62
|
|
|
63
63
|
const fetchStartDate = new Date();
|
|
64
|
-
const totalEvents = await this.service.
|
|
65
|
-
const fetchEndDate = new Date();
|
|
66
|
-
|
|
67
|
-
logging.info(`[EmailAnalytics] Fetched ${totalEvents} events and aggregated stats in ${fetchEndDate.getTime() - fetchStartDate.getTime()}ms (latest opens)`);
|
|
68
|
-
return totalEvents;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
async fetchLatestNonOpenedEvents({maxEvents} = {maxEvents: Infinity}) {
|
|
72
|
-
logging.info('[EmailAnalytics] Fetch latest non-opened events started');
|
|
73
|
-
|
|
74
|
-
const fetchStartDate = new Date();
|
|
75
|
-
const totalEvents = await this.service.fetchLatestNonOpenedEvents({maxEvents});
|
|
64
|
+
const totalEvents = await this.service.fetchLatest({maxEvents});
|
|
76
65
|
const fetchEndDate = new Date();
|
|
77
66
|
|
|
78
67
|
logging.info(`[EmailAnalytics] Fetched ${totalEvents} events and aggregated stats in ${fetchEndDate.getTime() - fetchStartDate.getTime()}ms (latest)`);
|
|
@@ -80,7 +69,7 @@ class EmailAnalyticsServiceWrapper {
|
|
|
80
69
|
}
|
|
81
70
|
|
|
82
71
|
async fetchMissing({maxEvents} = {maxEvents: Infinity}) {
|
|
83
|
-
logging.info('[EmailAnalytics] Fetch missing
|
|
72
|
+
logging.info('[EmailAnalytics] Fetch missing started');
|
|
84
73
|
|
|
85
74
|
const fetchStartDate = new Date();
|
|
86
75
|
const totalEvents = await this.service.fetchMissing({maxEvents});
|
|
@@ -94,7 +83,7 @@ class EmailAnalyticsServiceWrapper {
|
|
|
94
83
|
if (maxEvents < 300) {
|
|
95
84
|
return 0;
|
|
96
85
|
}
|
|
97
|
-
logging.info('[EmailAnalytics] Fetch scheduled
|
|
86
|
+
logging.info('[EmailAnalytics] Fetch scheduled started');
|
|
98
87
|
|
|
99
88
|
const fetchStartDate = new Date();
|
|
100
89
|
const totalEvents = await this.service.fetchScheduled({maxEvents});
|
|
@@ -111,31 +100,13 @@ class EmailAnalyticsServiceWrapper {
|
|
|
111
100
|
}
|
|
112
101
|
this.fetching = true;
|
|
113
102
|
|
|
114
|
-
// NOTE: Data shows we can process ~7500 events per minute on Pro; this can vary locally
|
|
115
103
|
try {
|
|
116
|
-
|
|
117
|
-
await this.
|
|
118
|
-
|
|
119
|
-
// Set limits on how much we fetch without checkings for opened events. During surge events (following newsletter send)
|
|
120
|
-
// we want to make sure we don't spend too much time collecting delivery data.
|
|
121
|
-
const c1 = await this.fetchLatestNonOpenedEvents({maxEvents: 20000});
|
|
122
|
-
if (c1 > 15000) {
|
|
123
|
-
this.fetching = false;
|
|
124
|
-
logging.info('[EmailAnalytics] Restarting fetch due to high event count');
|
|
125
|
-
this.startFetch();
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
const c2 = await this.fetchMissing({maxEvents: 20000});
|
|
129
|
-
if ((c1 + c2) > 15000) {
|
|
130
|
-
this.fetching = false;
|
|
131
|
-
logging.info('[EmailAnalytics] Restarting fetch due to high event count');
|
|
132
|
-
this.startFetch();
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
104
|
+
const c1 = await this.fetchLatest({maxEvents: Infinity});
|
|
105
|
+
const c2 = await this.fetchMissing({maxEvents: Infinity});
|
|
135
106
|
|
|
136
107
|
// Only fetch scheduled if we didn't fetch a lot of normal events
|
|
137
108
|
await this.fetchScheduled({maxEvents: 20000 - c1 - c2});
|
|
138
|
-
|
|
109
|
+
|
|
139
110
|
this.fetching = false;
|
|
140
111
|
} catch (e) {
|
|
141
112
|
logging.error(e, 'Error while fetching email analytics');
|
|
@@ -1,14 +1,9 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const debug = require('@tryghost/debug')('services:email-analytics');
|
|
3
3
|
const db = require('../../../data/db');
|
|
4
|
-
const logging = require('@tryghost/logging');
|
|
5
|
-
const {default: ObjectID} = require('bson-objectid');
|
|
6
4
|
|
|
7
5
|
const MIN_EMAIL_COUNT_FOR_OPEN_RATE = 5;
|
|
8
6
|
|
|
9
|
-
/** @typedef {'email-analytics-latest-opened'|'email-analytics-latest-others'|'email-analytics-missing'|'email-analytics-scheduled'} EmailAnalyticsJobName */
|
|
10
|
-
/** @typedef {'delivered'|'opened'|'failed'} EmailAnalyticsEvent */
|
|
11
|
-
|
|
12
7
|
module.exports = {
|
|
13
8
|
async shouldFetchStats() {
|
|
14
9
|
// don't fetch stats from Mailgun if we haven't sent any emails
|
|
@@ -16,124 +11,33 @@ module.exports = {
|
|
|
16
11
|
return emailCount && emailCount.count > 0;
|
|
17
12
|
},
|
|
18
13
|
|
|
19
|
-
|
|
20
|
-
* Retrieves the timestamp of the last seen event for the specified email analytics events.
|
|
21
|
-
* @param {EmailAnalyticsJobName} jobName - The name of the job to update.
|
|
22
|
-
* @param {EmailAnalyticsEvent[]} [events=['delivered', 'opened', 'failed']] - The email analytics events to consider.
|
|
23
|
-
* @returns {Promise<Date|null>} The timestamp of the last seen event, or null if no events are found.
|
|
24
|
-
*/
|
|
25
|
-
async getLastEventTimestamp(jobName, events = ['delivered', 'opened', 'failed']) {
|
|
14
|
+
async getLastSeenEventTimestamp() {
|
|
26
15
|
const startDate = new Date();
|
|
27
|
-
|
|
28
|
-
let maxOpenedAt;
|
|
29
|
-
let maxDeliveredAt;
|
|
30
|
-
let maxFailedAt;
|
|
31
|
-
|
|
32
|
-
const jobData = await db.knex('jobs').select('finished_at', 'started_at').where('name', jobName).first();
|
|
33
|
-
|
|
34
|
-
if (jobData) {
|
|
35
|
-
debug(`Using job data for ${jobName}`);
|
|
36
|
-
const lastJobTimestamp = jobData.finished_at || jobData.started_at;
|
|
37
|
-
maxOpenedAt = events.includes('opened') ? lastJobTimestamp : null;
|
|
38
|
-
maxDeliveredAt = events.includes('delivered') ? lastJobTimestamp : null;
|
|
39
|
-
maxFailedAt = events.includes('failed') ? lastJobTimestamp : null;
|
|
40
|
-
} else {
|
|
41
|
-
debug(`Job data not found for ${jobName}, using email_recipients data`);
|
|
42
|
-
logging.info(`Job data not found for ${jobName}, using email_recipients data`);
|
|
43
|
-
if (events.includes('opened')) {
|
|
44
|
-
maxOpenedAt = (await db.knex('email_recipients').select(db.knex.raw('MAX(opened_at) as maxOpenedAt')).first()).maxOpenedAt;
|
|
45
|
-
}
|
|
46
|
-
if (events.includes('delivered')) {
|
|
47
|
-
maxDeliveredAt = (await db.knex('email_recipients').select(db.knex.raw('MAX(delivered_at) as maxDeliveredAt')).first()).maxDeliveredAt;
|
|
48
|
-
}
|
|
49
|
-
if (events.includes('failed')) {
|
|
50
|
-
maxFailedAt = (await db.knex('email_recipients').select(db.knex.raw('MAX(failed_at) as maxFailedAt')).first()).maxFailedAt;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// Insert a new job row if it doesn't exist
|
|
54
|
-
await db.knex('jobs').insert({
|
|
55
|
-
id: new ObjectID().toHexString(),
|
|
56
|
-
name: jobName,
|
|
57
|
-
started_at: new Date(),
|
|
58
|
-
created_at: new Date(),
|
|
59
|
-
status: 'started'
|
|
60
|
-
}).onConflict('name').ignore();
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// Convert string dates to Date objects for SQLite compatibility
|
|
64
|
-
[maxOpenedAt, maxDeliveredAt, maxFailedAt] = [maxOpenedAt, maxDeliveredAt, maxFailedAt].map(date => (
|
|
65
|
-
date && !(date instanceof Date) ? new Date(date) : date
|
|
66
|
-
));
|
|
67
16
|
|
|
68
|
-
|
|
69
|
-
|
|
17
|
+
// three separate queries is much faster than using max/greatest (with coalesce to handle nulls) across columns
|
|
18
|
+
let {maxDeliveredAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(delivered_at) as maxDeliveredAt')).first() || {};
|
|
19
|
+
let {maxOpenedAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(opened_at) as maxOpenedAt')).first() || {};
|
|
20
|
+
let {maxFailedAt} = await db.knex('email_recipients').select(db.knex.raw('MAX(failed_at) as maxFailedAt')).first() || {};
|
|
70
21
|
|
|
71
|
-
|
|
72
|
-
|
|
22
|
+
if (maxDeliveredAt && !(maxDeliveredAt instanceof Date)) {
|
|
23
|
+
// SQLite returns a string instead of a Date
|
|
24
|
+
maxDeliveredAt = new Date(maxDeliveredAt);
|
|
25
|
+
}
|
|
73
26
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
* @param {'completed'|'started'} field - The field to update.
|
|
78
|
-
* @param {Date} date - The timestamp of the last seen event.
|
|
79
|
-
* @returns {Promise<void>}
|
|
80
|
-
* @description
|
|
81
|
-
* Updates the `finished_at` or `started_at` column of the specified job in the `jobs` table with the provided timestamp.
|
|
82
|
-
* This is used to keep track of the last time the job was run to avoid expensive queries following reboot.
|
|
83
|
-
*/
|
|
84
|
-
async setJobTimestamp(jobName, field, date) {
|
|
85
|
-
// Convert string dates to Date objects for SQLite compatibility
|
|
86
|
-
try {
|
|
87
|
-
debug(`Setting ${field} timestamp for job ${jobName} to ${date}`);
|
|
88
|
-
const updateField = field === 'completed' ? 'finished_at' : 'started_at';
|
|
89
|
-
const status = field === 'completed' ? 'finished' : 'started';
|
|
90
|
-
const result = await db.knex('jobs').update({[updateField]: date, updated_at: new Date(), status: status}).where('name', jobName);
|
|
91
|
-
if (result === 0) {
|
|
92
|
-
await db.knex('jobs').insert({
|
|
93
|
-
id: new ObjectID().toHexString(),
|
|
94
|
-
name: jobName,
|
|
95
|
-
[updateField]: date,
|
|
96
|
-
updated_at: date,
|
|
97
|
-
status: status
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
} catch (err) {
|
|
101
|
-
debug(`Error setting ${field} timestamp for job ${jobName}: ${err.message}`);
|
|
27
|
+
if (maxOpenedAt && !(maxOpenedAt instanceof Date)) {
|
|
28
|
+
// SQLite returns a string instead of a Date
|
|
29
|
+
maxOpenedAt = new Date(maxOpenedAt);
|
|
102
30
|
}
|
|
103
|
-
},
|
|
104
31
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
* @param {'started'|'finished'|'failed'} status - The new status of the job.
|
|
109
|
-
* @returns {Promise<void>}
|
|
110
|
-
* @description
|
|
111
|
-
* Updates the `status` column of the specified job in the `jobs` table with the provided status.
|
|
112
|
-
* This is used to keep track of the current state of the job.
|
|
113
|
-
*/
|
|
114
|
-
async setJobStatus(jobName, status) {
|
|
115
|
-
debug(`Setting status for job ${jobName} to ${status}`);
|
|
116
|
-
try {
|
|
117
|
-
const result = await db.knex('jobs')
|
|
118
|
-
.update({
|
|
119
|
-
status: status,
|
|
120
|
-
updated_at: new Date()
|
|
121
|
-
})
|
|
122
|
-
.where('name', jobName);
|
|
123
|
-
|
|
124
|
-
if (result === 0) {
|
|
125
|
-
await db.knex('jobs').insert({
|
|
126
|
-
id: new ObjectID().toHexString(),
|
|
127
|
-
name: jobName,
|
|
128
|
-
status: status,
|
|
129
|
-
created_at: new Date(),
|
|
130
|
-
updated_at: new Date()
|
|
131
|
-
});
|
|
132
|
-
}
|
|
133
|
-
} catch (err) {
|
|
134
|
-
debug(`Error setting status for job ${jobName}: ${err.message}`);
|
|
135
|
-
throw err;
|
|
32
|
+
if (maxFailedAt && !(maxFailedAt instanceof Date)) {
|
|
33
|
+
// SQLite returns a string instead of a Date
|
|
34
|
+
maxFailedAt = new Date(maxFailedAt);
|
|
136
35
|
}
|
|
36
|
+
|
|
37
|
+
const lastSeenEventTimestamp = _.max([maxDeliveredAt, maxOpenedAt, maxFailedAt]);
|
|
38
|
+
debug(`getLastSeenEventTimestamp: finished in ${Date.now() - startDate}ms`);
|
|
39
|
+
|
|
40
|
+
return lastSeenEventTimestamp;
|
|
137
41
|
},
|
|
138
42
|
|
|
139
43
|
async aggregateEmailStats(emailId) {
|
|
@@ -174,4 +78,4 @@ module.exports = {
|
|
|
174
78
|
.update(updateQuery)
|
|
175
79
|
.where('id', memberId);
|
|
176
80
|
}
|
|
177
|
-
};
|
|
81
|
+
};
|
|
@@ -19,15 +19,13 @@ module.exports = function getConfigProperties() {
|
|
|
19
19
|
emailAnalytics: config.get('emailAnalytics'),
|
|
20
20
|
hostSettings: config.get('hostSettings'),
|
|
21
21
|
tenor: config.get('tenor'),
|
|
22
|
-
editor: config.get('editor'),
|
|
23
22
|
pintura: config.get('pintura'),
|
|
24
|
-
adminX: config.get('adminX'),
|
|
25
23
|
signupForm: config.get('signupForm')
|
|
26
24
|
};
|
|
27
25
|
|
|
28
|
-
|
|
29
|
-
if (
|
|
30
|
-
configProperties.
|
|
26
|
+
// WIP tinybird stats feature - it's entirely config driven instead of using an alpha flag for now
|
|
27
|
+
if (config.get('tinybird') && config.get('tinybird:stats')) {
|
|
28
|
+
configProperties.stats = config.get('tinybird:stats');
|
|
31
29
|
}
|
|
32
30
|
|
|
33
31
|
return configProperties;
|
|
@@ -39,11 +39,16 @@ class RecommendationServiceWrapper {
|
|
|
39
39
|
incomingRecommendationService;
|
|
40
40
|
|
|
41
41
|
init() {
|
|
42
|
+
const config = require('../../../shared/config');
|
|
43
|
+
if (config.get('services:recommendations:enabled') === false) {
|
|
44
|
+
logging.info('[Recommendations] Service is disabled via config');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
if (this.repository) {
|
|
43
49
|
return;
|
|
44
50
|
}
|
|
45
51
|
|
|
46
|
-
const config = require('../../../shared/config');
|
|
47
52
|
const urlUtils = require('../../../shared/url-utils');
|
|
48
53
|
const models = require('../../models');
|
|
49
54
|
const sentry = require('../../../shared/sentry');
|
|
@@ -39,7 +39,7 @@ module.exports = function setupApiApp() {
|
|
|
39
39
|
apiApp.use(routes());
|
|
40
40
|
|
|
41
41
|
apiApp.use(async function nestApp(req, res, next) {
|
|
42
|
-
if (labs.isSet('NestPlayground')) {
|
|
42
|
+
if (process.env.GHOST_ENABLE_NEST_FRAMEWORK && labs.isSet('NestPlayground')) {
|
|
43
43
|
const originalExpressApp = req.app;
|
|
44
44
|
const app = await GhostNestApp.getApp();
|
|
45
45
|
|
|
@@ -182,7 +182,7 @@
|
|
|
182
182
|
},
|
|
183
183
|
"portal": {
|
|
184
184
|
"url": "https://cdn.jsdelivr.net/ghost/portal@~{version}/umd/portal.min.js",
|
|
185
|
-
"version": "2.
|
|
185
|
+
"version": "2.42"
|
|
186
186
|
},
|
|
187
187
|
"sodoSearch": {
|
|
188
188
|
"url": "https://cdn.jsdelivr.net/ghost/sodo-search@~{version}/umd/sodo-search.min.js",
|
package/core/shared/labs.js
CHANGED