ghost 5.99.0 → 5.100.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-activitypub-5.100.0.tgz +0 -0
- package/components/tryghost-adapter-cache-memory-ttl-5.100.0.tgz +0 -0
- package/components/tryghost-adapter-cache-redis-5.100.0.tgz +0 -0
- package/components/tryghost-adapter-manager-5.100.0.tgz +0 -0
- package/components/tryghost-announcement-bar-settings-5.100.0.tgz +0 -0
- package/components/tryghost-api-framework-5.100.0.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.100.0.tgz +0 -0
- package/components/tryghost-audience-feedback-5.100.0.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.100.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.100.0.tgz +0 -0
- package/components/tryghost-collections-5.100.0.tgz +0 -0
- package/components/tryghost-constants-5.100.0.tgz +0 -0
- package/components/tryghost-custom-fonts-5.100.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.100.0.tgz +0 -0
- package/components/{tryghost-data-generator-5.99.0.tgz → tryghost-data-generator-5.100.0.tgz} +0 -0
- package/components/tryghost-domain-events-5.100.0.tgz +0 -0
- package/components/tryghost-donations-5.100.0.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.100.0.tgz +0 -0
- package/components/tryghost-email-addresses-5.100.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.100.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.100.0.tgz +0 -0
- package/components/tryghost-email-content-generator-5.100.0.tgz +0 -0
- package/components/tryghost-email-events-5.100.0.tgz +0 -0
- package/components/{tryghost-email-service-5.99.0.tgz → tryghost-email-service-5.100.0.tgz} +0 -0
- package/components/tryghost-email-suppression-list-5.100.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.100.0.tgz +0 -0
- package/components/tryghost-external-media-inliner-5.100.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.100.0.tgz +0 -0
- package/components/tryghost-ghost-5.100.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.100.0.tgz +0 -0
- package/components/tryghost-i18n-5.100.0.tgz +0 -0
- package/components/tryghost-identity-token-service-5.100.0.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.100.0.tgz +0 -0
- package/components/tryghost-importer-revue-5.100.0.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.100.0.tgz +0 -0
- package/components/tryghost-job-manager-5.100.0.tgz +0 -0
- package/components/tryghost-link-redirects-5.100.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.100.0.tgz +0 -0
- package/components/tryghost-link-tracking-5.100.0.tgz +0 -0
- package/components/{tryghost-magic-link-5.99.0.tgz → tryghost-magic-link-5.100.0.tgz} +0 -0
- package/components/tryghost-mail-events-5.100.0.tgz +0 -0
- package/components/tryghost-mailgun-client-5.100.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.100.0.tgz +0 -0
- package/components/tryghost-member-events-5.100.0.tgz +0 -0
- package/components/{tryghost-members-api-5.99.0.tgz → tryghost-members-api-5.100.0.tgz} +0 -0
- package/components/tryghost-members-csv-5.100.0.tgz +0 -0
- package/components/tryghost-members-events-service-5.100.0.tgz +0 -0
- package/components/tryghost-members-importer-5.100.0.tgz +0 -0
- package/components/tryghost-members-offers-5.100.0.tgz +0 -0
- package/components/tryghost-members-payments-5.100.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.100.0.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.100.0.tgz +0 -0
- package/components/tryghost-mentions-email-report-5.100.0.tgz +0 -0
- package/components/tryghost-milestones-5.100.0.tgz +0 -0
- package/components/tryghost-minifier-5.100.0.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.100.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.100.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.100.0.tgz +0 -0
- package/components/tryghost-mw-error-handler-5.100.0.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.100.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.100.0.tgz +0 -0
- package/components/tryghost-mw-version-match-5.100.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.100.0.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.100.0.tgz +0 -0
- package/components/tryghost-oembed-service-5.100.0.tgz +0 -0
- package/components/tryghost-package-json-5.100.0.tgz +0 -0
- package/components/tryghost-post-events-5.100.0.tgz +0 -0
- package/components/tryghost-post-revisions-5.100.0.tgz +0 -0
- package/components/tryghost-posts-service-5.100.0.tgz +0 -0
- package/components/tryghost-prometheus-metrics-5.100.0.tgz +0 -0
- package/components/tryghost-recommendations-5.100.0.tgz +0 -0
- package/components/tryghost-referrers-5.100.0.tgz +0 -0
- package/components/tryghost-security-5.100.0.tgz +0 -0
- package/components/tryghost-session-service-5.100.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.100.0.tgz +0 -0
- package/components/tryghost-slack-notifications-5.100.0.tgz +0 -0
- package/components/tryghost-staff-service-5.100.0.tgz +0 -0
- package/components/tryghost-stats-service-5.100.0.tgz +0 -0
- package/components/tryghost-tiers-5.100.0.tgz +0 -0
- package/components/tryghost-update-check-service-5.100.0.tgz +0 -0
- package/components/tryghost-verification-trigger-5.100.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.100.0.tgz +0 -0
- package/components/tryghost-webmentions-5.100.0.tgz +0 -0
- package/core/boot.js +16 -5
- package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +2 -2
- package/core/built/admin/assets/admin-x-activitypub/{index-696e3897.mjs → index-ab50c736.mjs} +147 -141
- package/core/built/admin/assets/admin-x-activitypub/{modals-dc93ce65.mjs → modals-2e6f9c05.mjs} +2 -2
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-e6f6e0b3.mjs → CodeEditorView-26137e6e.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +3 -3
- package/core/built/admin/assets/admin-x-settings/{index-2a9ec06c.mjs → index-cdf09c6d.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-8ede56c6.mjs → index-f6338b55.mjs} +579 -571
- package/core/built/admin/assets/admin-x-settings/{modals-e3497e68.mjs → modals-15249255.mjs} +5657 -5622
- package/core/built/admin/assets/{chunk.524.85082593cb84282804e5.js → chunk.524.0982f6745ca0f52945b1.js} +5 -5
- package/core/built/admin/assets/{chunk.582.a756882ecb6d421c862c.js → chunk.582.d97692c812575ab051a9.js} +6 -6
- package/core/built/admin/assets/{ghost-fefa2e99b7dc389dfd6579049cdd2066.js → ghost-7ab268fc7cd7884eef525145d5fbb501.js} +13 -13
- package/core/built/admin/index.html +3 -3
- package/core/frontend/helpers/ghost_head.js +2 -1
- package/core/server/api/endpoints/comments-members.js +0 -1
- package/core/server/api/endpoints/identities.js +5 -31
- package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +9 -2
- package/core/server/data/migrations/versions/5.100/2024-10-31-15-27-42-add-jobs-queue-columns.js +14 -0
- package/core/server/data/migrations/versions/5.100/2024-11-05-14-48-08-add-comments-in-reply-to-id.js +10 -0
- package/core/server/data/migrations/versions/5.100/2024-11-06-04-45-15-add-activitypub-integration.js +40 -0
- package/core/server/data/migrations/versions/5.55/2023-07-10-05-16-55-add-built-in-collection-posts.js +1 -1
- package/core/server/data/migrations/versions/5.65/2023-09-22-06-42-55-repopulate-built-in-featured-collection-posts.js +1 -1
- package/core/server/data/migrations/versions/5.89/2024-07-30-19-51-06-backfill-offer-redemptions.js +1 -1
- package/core/server/data/schema/fixtures/fixtures.json +7 -0
- package/core/server/data/schema/schema.js +4 -1
- package/core/server/models/comment.js +17 -33
- package/core/server/services/activitypub/ActivityPubServiceWrapper.js +42 -0
- package/core/server/services/activitypub/index.js +1 -0
- package/core/server/services/comments/CommentsController.js +3 -1
- package/core/server/services/comments/CommentsService.js +24 -2
- package/core/server/services/comments/CommentsServiceEmails.js +7 -2
- package/core/server/services/email-analytics/EmailAnalyticsServiceWrapper.js +18 -2
- package/core/server/services/email-analytics/jobs/update-member-email-analytics/index.js +13 -0
- package/core/server/services/email-service/EmailServiceWrapper.js +20 -1
- package/core/server/services/identity-tokens/IdentityTokenServiceWrapper.js +28 -0
- package/core/server/services/identity-tokens/index.js +1 -0
- package/core/server/services/jobs/job-service.js +3 -2
- package/core/server/services/mentions-jobs/job-service.js +2 -2
- package/core/server/services/offers/OfferBookshelfRepository.js +3 -1
- package/core/shared/config/defaults.json +1 -1
- package/core/shared/config/env/config.testing.json +0 -3
- package/core/shared/labs.js +1 -1
- package/core/shared/prometheus-client.js +8 -31
- package/package.json +156 -153
- package/yarn.lock +176 -26
- package/components/tryghost-adapter-cache-memory-ttl-5.99.0.tgz +0 -0
- package/components/tryghost-adapter-cache-redis-5.99.0.tgz +0 -0
- package/components/tryghost-adapter-manager-5.99.0.tgz +0 -0
- package/components/tryghost-announcement-bar-settings-5.99.0.tgz +0 -0
- package/components/tryghost-api-framework-5.99.0.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.99.0.tgz +0 -0
- package/components/tryghost-audience-feedback-5.99.0.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.99.0.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.99.0.tgz +0 -0
- package/components/tryghost-collections-5.99.0.tgz +0 -0
- package/components/tryghost-constants-5.99.0.tgz +0 -0
- package/components/tryghost-custom-fonts-5.99.0.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.99.0.tgz +0 -0
- package/components/tryghost-domain-events-5.99.0.tgz +0 -0
- package/components/tryghost-donations-5.99.0.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.99.0.tgz +0 -0
- package/components/tryghost-email-addresses-5.99.0.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.99.0.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.99.0.tgz +0 -0
- package/components/tryghost-email-content-generator-5.99.0.tgz +0 -0
- package/components/tryghost-email-events-5.99.0.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.99.0.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.99.0.tgz +0 -0
- package/components/tryghost-external-media-inliner-5.99.0.tgz +0 -0
- package/components/tryghost-extract-api-key-5.99.0.tgz +0 -0
- package/components/tryghost-ghost-5.99.0.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.99.0.tgz +0 -0
- package/components/tryghost-i18n-5.99.0.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.99.0.tgz +0 -0
- package/components/tryghost-importer-revue-5.99.0.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.99.0.tgz +0 -0
- package/components/tryghost-job-manager-5.99.0.tgz +0 -0
- package/components/tryghost-link-redirects-5.99.0.tgz +0 -0
- package/components/tryghost-link-replacer-5.99.0.tgz +0 -0
- package/components/tryghost-link-tracking-5.99.0.tgz +0 -0
- package/components/tryghost-mail-events-5.99.0.tgz +0 -0
- package/components/tryghost-mailgun-client-5.99.0.tgz +0 -0
- package/components/tryghost-member-attribution-5.99.0.tgz +0 -0
- package/components/tryghost-member-events-5.99.0.tgz +0 -0
- package/components/tryghost-members-csv-5.99.0.tgz +0 -0
- package/components/tryghost-members-events-service-5.99.0.tgz +0 -0
- package/components/tryghost-members-importer-5.99.0.tgz +0 -0
- package/components/tryghost-members-offers-5.99.0.tgz +0 -0
- package/components/tryghost-members-payments-5.99.0.tgz +0 -0
- package/components/tryghost-members-ssr-5.99.0.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.99.0.tgz +0 -0
- package/components/tryghost-mentions-email-report-5.99.0.tgz +0 -0
- package/components/tryghost-metrics-server-5.99.0.tgz +0 -0
- package/components/tryghost-milestones-5.99.0.tgz +0 -0
- package/components/tryghost-minifier-5.99.0.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.99.0.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.99.0.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.99.0.tgz +0 -0
- package/components/tryghost-mw-error-handler-5.99.0.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.99.0.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.99.0.tgz +0 -0
- package/components/tryghost-mw-version-match-5.99.0.tgz +0 -0
- package/components/tryghost-mw-vhost-5.99.0.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.99.0.tgz +0 -0
- package/components/tryghost-oembed-service-5.99.0.tgz +0 -0
- package/components/tryghost-package-json-5.99.0.tgz +0 -0
- package/components/tryghost-post-events-5.99.0.tgz +0 -0
- package/components/tryghost-post-revisions-5.99.0.tgz +0 -0
- package/components/tryghost-posts-service-5.99.0.tgz +0 -0
- package/components/tryghost-recommendations-5.99.0.tgz +0 -0
- package/components/tryghost-referrers-5.99.0.tgz +0 -0
- package/components/tryghost-security-5.99.0.tgz +0 -0
- package/components/tryghost-session-service-5.99.0.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.99.0.tgz +0 -0
- package/components/tryghost-slack-notifications-5.99.0.tgz +0 -0
- package/components/tryghost-staff-service-5.99.0.tgz +0 -0
- package/components/tryghost-stats-service-5.99.0.tgz +0 -0
- package/components/tryghost-tiers-5.99.0.tgz +0 -0
- package/components/tryghost-update-check-service-5.99.0.tgz +0 -0
- package/components/tryghost-verification-trigger-5.99.0.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.99.0.tgz +0 -0
- package/components/tryghost-webmentions-5.99.0.tgz +0 -0
|
@@ -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.100%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%22a724a31dfe%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%227b54e3e101%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%221b2f79bda3%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%22d3e0f0e348%22%2C%22adminXActivitypubCustomUrl%22%3A%22https%3A%2F%2Fcdn.jsdelivr.net%2Fghost%2Fadmin-x-activitypub%400%2Fdist%2Fadmin-x-activitypub.js%22%7D" />
|
|
12
12
|
|
|
13
13
|
<meta name="HandheldFriendly" content="True" />
|
|
14
14
|
<meta name="MobileOptimized" content="320" />
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
|
|
59
59
|
<script src="assets/vendor-88cd9f5cf5eba65221e2d2a636531eaa.js"></script>
|
|
60
60
|
<script src="assets/chunk.874.5eb920d19e75683234c2.js"></script>
|
|
61
|
-
<script src="assets/chunk.524.
|
|
62
|
-
<script src="assets/ghost-
|
|
61
|
+
<script src="assets/chunk.524.0982f6745ca0f52945b1.js"></script>
|
|
62
|
+
<script src="assets/ghost-7ab268fc7cd7884eef525145d5fbb501.js"></script>
|
|
63
63
|
</body>
|
|
64
64
|
</html>
|
|
@@ -62,7 +62,8 @@ function getMembersHelper(data, frontendKey, excludeList) {
|
|
|
62
62
|
i18n: labs.isSet('i18n'),
|
|
63
63
|
ghost: urlUtils.getSiteUrl(),
|
|
64
64
|
key: frontendKey,
|
|
65
|
-
api: urlUtils.urlFor('api', {type: 'content'}, true)
|
|
65
|
+
api: urlUtils.urlFor('api', {type: 'content'}, true),
|
|
66
|
+
locale: settingsCache.get('locale') || 'en'
|
|
66
67
|
};
|
|
67
68
|
if (colorString) {
|
|
68
69
|
attributes['accent-color'] = colorString;
|
|
@@ -1,28 +1,4 @@
|
|
|
1
1
|
const logging = require('@tryghost/logging');
|
|
2
|
-
const settings = require('../../../shared/settings-cache');
|
|
3
|
-
const urlUtils = require('../../../shared/url-utils');
|
|
4
|
-
const jwt = require('jsonwebtoken');
|
|
5
|
-
const jose = require('node-jose');
|
|
6
|
-
const issuer = urlUtils.urlFor('admin', true);
|
|
7
|
-
|
|
8
|
-
const dangerousPrivateKey = settings.get('ghost_private_key');
|
|
9
|
-
const keyStore = jose.JWK.createKeyStore();
|
|
10
|
-
const keyStoreReady = keyStore.add(dangerousPrivateKey, 'pem');
|
|
11
|
-
|
|
12
|
-
const getKeyID = async () => {
|
|
13
|
-
const key = await keyStoreReady;
|
|
14
|
-
return key.kid;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
const sign = async (claims, options = {}) => {
|
|
18
|
-
const kid = await getKeyID();
|
|
19
|
-
return jwt.sign(claims, dangerousPrivateKey, Object.assign({
|
|
20
|
-
issuer,
|
|
21
|
-
expiresIn: '5m',
|
|
22
|
-
algorithm: 'RS256',
|
|
23
|
-
keyid: kid
|
|
24
|
-
}, options));
|
|
25
|
-
};
|
|
26
2
|
|
|
27
3
|
/** @type {import('@tryghost/api-framework').Controller} */
|
|
28
4
|
const controller = {
|
|
@@ -33,6 +9,8 @@ const controller = {
|
|
|
33
9
|
},
|
|
34
10
|
permissions: true,
|
|
35
11
|
async query(frame) {
|
|
12
|
+
const IdentityTokenService = require('../../services/identity-tokens');
|
|
13
|
+
|
|
36
14
|
let role = null;
|
|
37
15
|
try {
|
|
38
16
|
await frame.user.load(['roles']);
|
|
@@ -40,13 +18,9 @@ const controller = {
|
|
|
40
18
|
} catch (err) {
|
|
41
19
|
logging.warn('Could not load role for identity');
|
|
42
20
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
if (typeof role === 'string') {
|
|
47
|
-
claims.role = role;
|
|
48
|
-
}
|
|
49
|
-
const token = await sign(claims);
|
|
21
|
+
|
|
22
|
+
const token = await IdentityTokenService.instance.getTokenForUser(frame.user.get('email'), role);
|
|
23
|
+
|
|
50
24
|
return {token};
|
|
51
25
|
}
|
|
52
26
|
}
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const utils = require('../../..');
|
|
3
3
|
const url = require('../utils/url');
|
|
4
|
+
const htmlToPlaintext = require('@tryghost/html-to-plaintext');
|
|
4
5
|
|
|
5
6
|
const commentFields = [
|
|
6
7
|
'id',
|
|
8
|
+
'in_reply_to_id',
|
|
9
|
+
'in_reply_to_snippet',
|
|
7
10
|
'status',
|
|
8
11
|
'html',
|
|
9
12
|
'created_at',
|
|
@@ -42,6 +45,10 @@ const countFields = [
|
|
|
42
45
|
const commentMapper = (model, frame) => {
|
|
43
46
|
const jsonModel = model.toJSON ? model.toJSON(frame.options) : model;
|
|
44
47
|
|
|
48
|
+
if (jsonModel.inReplyTo && jsonModel.inReplyTo.status === 'published') {
|
|
49
|
+
jsonModel.in_reply_to_snippet = htmlToPlaintext.commentSnippet(jsonModel.inReplyTo.html);
|
|
50
|
+
}
|
|
51
|
+
|
|
45
52
|
const response = _.pick(jsonModel, commentFields);
|
|
46
53
|
|
|
47
54
|
if (jsonModel.member) {
|
|
@@ -59,7 +66,7 @@ const commentMapper = (model, frame) => {
|
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
if (jsonModel.post) {
|
|
62
|
-
// We could use the post mapper here, but we need less field + don't need
|
|
69
|
+
// We could use the post mapper here, but we need less field + don't need all the async behavior support
|
|
63
70
|
url.forPost(jsonModel.post.id, jsonModel.post, frame);
|
|
64
71
|
response.post = _.pick(jsonModel.post, postFields);
|
|
65
72
|
}
|
|
@@ -77,7 +84,7 @@ const commentMapper = (model, frame) => {
|
|
|
77
84
|
response.html = null;
|
|
78
85
|
}
|
|
79
86
|
}
|
|
80
|
-
|
|
87
|
+
|
|
81
88
|
return response;
|
|
82
89
|
};
|
|
83
90
|
|
package/core/server/data/migrations/versions/5.100/2024-10-31-15-27-42-add-jobs-queue-columns.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const {combineNonTransactionalMigrations, createAddColumnMigration} = require('../../utils');
|
|
2
|
+
|
|
3
|
+
module.exports = combineNonTransactionalMigrations(
|
|
4
|
+
createAddColumnMigration('jobs', 'metadata', {
|
|
5
|
+
type: 'string',
|
|
6
|
+
maxlength: 2000,
|
|
7
|
+
nullable: true
|
|
8
|
+
}),
|
|
9
|
+
createAddColumnMigration('jobs', 'queue_entry', {
|
|
10
|
+
type: 'integer',
|
|
11
|
+
nullable: true,
|
|
12
|
+
unsigned: true
|
|
13
|
+
})
|
|
14
|
+
);
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
const {createAddColumnMigration} = require('../../utils');
|
|
2
|
+
|
|
3
|
+
module.exports = createAddColumnMigration('comments', 'in_reply_to_id', {
|
|
4
|
+
type: 'string',
|
|
5
|
+
maxlength: 24,
|
|
6
|
+
nullable: true,
|
|
7
|
+
unique: false,
|
|
8
|
+
references: 'comments.id',
|
|
9
|
+
setNullDelete: true
|
|
10
|
+
});
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
const logging = require('@tryghost/logging');
|
|
2
|
+
const {createTransactionalMigration, meta} = require('../../utils');
|
|
3
|
+
const ObjectID = require('bson-objectid').default;
|
|
4
|
+
|
|
5
|
+
module.exports = createTransactionalMigration(
|
|
6
|
+
async function up(knex) {
|
|
7
|
+
logging.info('Adding Ghost ActivityPub integration');
|
|
8
|
+
const existing = await knex
|
|
9
|
+
.select('id')
|
|
10
|
+
.from('integrations')
|
|
11
|
+
.where('slug', '=', 'ghost-activitypub')
|
|
12
|
+
.andWhere('type', '=', 'internal')
|
|
13
|
+
.first();
|
|
14
|
+
|
|
15
|
+
if (existing) {
|
|
16
|
+
logging.warn('Found existing Ghost ActivityPub integration');
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
await knex
|
|
21
|
+
.insert({
|
|
22
|
+
id: (new ObjectID).toHexString(),
|
|
23
|
+
type: 'internal',
|
|
24
|
+
slug: 'ghost-activitypub',
|
|
25
|
+
name: 'Ghost ActivityPub',
|
|
26
|
+
description: 'Internal Integration for ActivityPub',
|
|
27
|
+
created_at: knex.raw('current_timestamp'),
|
|
28
|
+
created_by: meta.MIGRATION_USER
|
|
29
|
+
})
|
|
30
|
+
.into('integrations');
|
|
31
|
+
},
|
|
32
|
+
async function down(knex) {
|
|
33
|
+
logging.info('Removing Ghost ActivityPub integration');
|
|
34
|
+
await knex
|
|
35
|
+
.del()
|
|
36
|
+
.from('integrations')
|
|
37
|
+
.where('slug', '=', 'ghost-activitypub')
|
|
38
|
+
.andWhere('type', '=', 'internal');
|
|
39
|
+
}
|
|
40
|
+
);
|
|
@@ -14,7 +14,7 @@ const insertPostCollections = async (knex, collectionId, postIds) => {
|
|
|
14
14
|
};
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
await knex.batchInsert('collections_posts', collectionPosts,
|
|
17
|
+
await knex.batchInsert('collections_posts', collectionPosts, 100);
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
module.exports = createTransactionalMigration(
|
|
@@ -14,7 +14,7 @@ const insertPostCollections = async (knex, collectionId, postIds) => {
|
|
|
14
14
|
};
|
|
15
15
|
});
|
|
16
16
|
|
|
17
|
-
await knex.batchInsert('collections_posts', collectionPosts,
|
|
17
|
+
await knex.batchInsert('collections_posts', collectionPosts, 100);
|
|
18
18
|
};
|
|
19
19
|
|
|
20
20
|
module.exports = createTransactionalMigration(
|
package/core/server/data/migrations/versions/5.89/2024-07-30-19-51-06-backfill-offer-redemptions.js
CHANGED
|
@@ -50,7 +50,7 @@ module.exports = createTransactionalMigration(
|
|
|
50
50
|
};
|
|
51
51
|
});
|
|
52
52
|
// Batch insert rows into the offer_redemptions table
|
|
53
|
-
await knex.batchInsert('offer_redemptions', offerRedemptions,
|
|
53
|
+
await knex.batchInsert('offer_redemptions', offerRedemptions, 100);
|
|
54
54
|
} else {
|
|
55
55
|
logging.info('No offer redemptions to backfill');
|
|
56
56
|
}
|
|
@@ -814,6 +814,13 @@
|
|
|
814
814
|
"description": "Internal Content API integration for Admin access",
|
|
815
815
|
"type": "core",
|
|
816
816
|
"api_keys": [{"type": "content"}]
|
|
817
|
+
},
|
|
818
|
+
{
|
|
819
|
+
"slug": "ghost-activitypub",
|
|
820
|
+
"name": "Ghost ActivityPub",
|
|
821
|
+
"description": "Internal Integration for ActivityPub",
|
|
822
|
+
"type": "internal",
|
|
823
|
+
"api_keys": []
|
|
817
824
|
}
|
|
818
825
|
]
|
|
819
826
|
}
|
|
@@ -958,6 +958,7 @@ module.exports = {
|
|
|
958
958
|
post_id: {type: 'string', maxlength: 24, nullable: false, unique: false, references: 'posts.id', cascadeDelete: true},
|
|
959
959
|
member_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'members.id', setNullDelete: true},
|
|
960
960
|
parent_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'comments.id', cascadeDelete: true},
|
|
961
|
+
in_reply_to_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'comments.id', setNullDelete: true},
|
|
961
962
|
status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'published', validations: {isIn: [['published', 'hidden', 'deleted']]}},
|
|
962
963
|
html: {type: 'text', maxlength: 1000000000, fieldtype: 'long', nullable: true},
|
|
963
964
|
edited_at: {type: 'dateTime', nullable: true},
|
|
@@ -985,7 +986,9 @@ module.exports = {
|
|
|
985
986
|
started_at: {type: 'dateTime', nullable: true},
|
|
986
987
|
finished_at: {type: 'dateTime', nullable: true},
|
|
987
988
|
created_at: {type: 'dateTime', nullable: false},
|
|
988
|
-
updated_at: {type: 'dateTime', nullable: true}
|
|
989
|
+
updated_at: {type: 'dateTime', nullable: true},
|
|
990
|
+
metadata: {type: 'string', maxlength: 2000, nullable: true},
|
|
991
|
+
queue_entry: {type: 'integer', nullable: true, unsigned: true}
|
|
989
992
|
},
|
|
990
993
|
redirects: {
|
|
991
994
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
@@ -46,6 +46,10 @@ const Comment = ghostBookshelf.Model.extend({
|
|
|
46
46
|
return this.belongsTo('Comment', 'parent_id');
|
|
47
47
|
},
|
|
48
48
|
|
|
49
|
+
inReplyTo() {
|
|
50
|
+
return this.belongsTo('Comment', 'in_reply_to_id');
|
|
51
|
+
},
|
|
52
|
+
|
|
49
53
|
likes() {
|
|
50
54
|
return this.hasMany('CommentLike', 'comment_id');
|
|
51
55
|
},
|
|
@@ -94,6 +98,12 @@ const Comment = ghostBookshelf.Model.extend({
|
|
|
94
98
|
}
|
|
95
99
|
},
|
|
96
100
|
|
|
101
|
+
orderAttributes: function orderAttributes() {
|
|
102
|
+
let keys = ghostBookshelf.Model.prototype.orderAttributes.call(this, arguments);
|
|
103
|
+
keys.push('count__likes');
|
|
104
|
+
return keys;
|
|
105
|
+
},
|
|
106
|
+
|
|
97
107
|
onCreated: function onCreated(model, options) {
|
|
98
108
|
ghostBookshelf.Model.prototype.onCreated.apply(this, arguments);
|
|
99
109
|
|
|
@@ -175,25 +185,26 @@ const Comment = ghostBookshelf.Model.extend({
|
|
|
175
185
|
/**
|
|
176
186
|
* We have to ensure consistency. If you listen on model events (e.g. `member.added`), you can expect that you always
|
|
177
187
|
* receive all fields including relations. Otherwise you can't rely on a consistent flow. And we want to avoid
|
|
178
|
-
* that event listeners have to re-fetch a resource.
|
|
179
|
-
* and updating resources. We won't return the relations by default for now.
|
|
188
|
+
* that event listeners have to re-fetch a resource.
|
|
180
189
|
*/
|
|
181
190
|
defaultRelations: function defaultRelations(methodName, options) {
|
|
182
|
-
// @
|
|
191
|
+
// @TODO: the default relations are not working for 'add' when we add it below
|
|
192
|
+
// this is because bookshelf does not automatically call `fetch` after adding so
|
|
193
|
+
// our bookshelf eager-load plugin doesn't use the `withRelated` options
|
|
183
194
|
if (['findAll', 'findPage', 'edit', 'findOne', 'destroy'].indexOf(methodName) !== -1) {
|
|
184
195
|
if (!options.withRelated || options.withRelated.length === 0) {
|
|
185
196
|
if (options.parentId) {
|
|
186
197
|
// Do not include replies for replies
|
|
187
198
|
options.withRelated = [
|
|
188
199
|
// Relations
|
|
189
|
-
'member', 'count.likes', 'count.liked'
|
|
200
|
+
'inReplyTo', 'member', 'count.likes', 'count.liked'
|
|
190
201
|
];
|
|
191
202
|
} else {
|
|
192
203
|
options.withRelated = [
|
|
193
204
|
// Relations
|
|
194
|
-
'member', 'count.replies', 'count.likes', 'count.liked',
|
|
205
|
+
'member', 'inReplyTo', 'count.replies', 'count.likes', 'count.liked',
|
|
195
206
|
// Replies (limited to 3)
|
|
196
|
-
'replies', 'replies.member' , 'replies.count.likes', 'replies.count.liked'
|
|
207
|
+
'replies', 'replies.member', 'replies.inReplyTo', 'replies.count.likes', 'replies.count.liked'
|
|
197
208
|
];
|
|
198
209
|
}
|
|
199
210
|
}
|
|
@@ -202,47 +213,20 @@ const Comment = ghostBookshelf.Model.extend({
|
|
|
202
213
|
return options;
|
|
203
214
|
},
|
|
204
215
|
|
|
205
|
-
async findMostLikedComment(options = {}) {
|
|
206
|
-
let query = ghostBookshelf.knex('comments')
|
|
207
|
-
.select('comments.*')
|
|
208
|
-
.count('comment_likes.id as count__likes') // Counting likes for sorting
|
|
209
|
-
.leftJoin('comment_likes', 'comments.id', 'comment_likes.comment_id')
|
|
210
|
-
.groupBy('comments.id') // Group by comment ID to aggregate likes count
|
|
211
|
-
.orderBy('count__likes', 'desc') // Order by likes in descending order (most likes first)
|
|
212
|
-
.limit(1); // Limit to just 1 result
|
|
213
|
-
// Execute the query and get the result
|
|
214
|
-
const result = await query.first(); // Fetch the single top comment
|
|
215
|
-
const id = result && result.id;
|
|
216
|
-
// Fetch the comment model by ID
|
|
217
|
-
return this.findOne({id}, options);
|
|
218
|
-
},
|
|
219
|
-
|
|
220
216
|
async findPage(options) {
|
|
221
217
|
const {withRelated} = this.defaultRelations('findPage', options);
|
|
222
|
-
|
|
223
218
|
const relationsToLoadIndividually = [
|
|
224
219
|
'replies',
|
|
225
220
|
'replies.member',
|
|
226
221
|
'replies.count.likes',
|
|
227
222
|
'replies.count.liked'
|
|
228
223
|
].filter(relation => withRelated.includes(relation));
|
|
229
|
-
|
|
230
224
|
const result = await ghostBookshelf.Model.findPage.call(this, options);
|
|
231
225
|
|
|
232
226
|
for (const model of result.data) {
|
|
233
227
|
await model.load(relationsToLoadIndividually, _.omit(options, 'withRelated'));
|
|
234
228
|
}
|
|
235
229
|
|
|
236
|
-
// if options.order === 'best', we findMostLikedComment
|
|
237
|
-
// then we remove it from the result set and add it as the first element
|
|
238
|
-
if (options.order === 'best' && options.page === '1') {
|
|
239
|
-
const mostLikedComment = await this.findMostLikedComment(options);
|
|
240
|
-
if (mostLikedComment) {
|
|
241
|
-
result.data = result.data.filter(comment => comment.id !== mostLikedComment.id);
|
|
242
|
-
result.data.unshift(mostLikedComment);
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
|
|
246
230
|
return result;
|
|
247
231
|
},
|
|
248
232
|
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const {ActivityPubService} = require('@tryghost/activitypub');
|
|
2
|
+
|
|
3
|
+
module.exports = class ActivityPubServiceWrapper {
|
|
4
|
+
/** @type ActivityPubService */
|
|
5
|
+
static instance;
|
|
6
|
+
|
|
7
|
+
static async init() {
|
|
8
|
+
if (ActivityPubServiceWrapper.instance) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
const labs = require('../../../shared/labs');
|
|
12
|
+
|
|
13
|
+
if (!labs.isSet('ActivityPub')) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const urlUtils = require('../../../shared/url-utils');
|
|
18
|
+
const siteUrl = new URL(urlUtils.getSiteUrl());
|
|
19
|
+
|
|
20
|
+
const db = require('../../data/db');
|
|
21
|
+
const knex = db.knex;
|
|
22
|
+
|
|
23
|
+
const logging = require('@tryghost/logging');
|
|
24
|
+
|
|
25
|
+
const IdentityTokenServiceWrapper = require('../identity-tokens');
|
|
26
|
+
|
|
27
|
+
if (!IdentityTokenServiceWrapper.instance) {
|
|
28
|
+
logging.error(`IdentityTokenService needs to be initialised before ActivityPubService`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
ActivityPubServiceWrapper.instance = new ActivityPubService(
|
|
32
|
+
knex,
|
|
33
|
+
siteUrl,
|
|
34
|
+
logging,
|
|
35
|
+
IdentityTokenServiceWrapper.instance
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
if (labs.isSet('ActivityPub') && IdentityTokenServiceWrapper.instance) {
|
|
39
|
+
await ActivityPubServiceWrapper.instance.initialiseWebhooks();
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./ActivityPubServiceWrapper');
|
|
@@ -51,7 +51,8 @@ module.exports = class CommentsController {
|
|
|
51
51
|
frame.options.filter = `post_id:${frame.options.post_id}`;
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
|
-
|
|
54
|
+
|
|
55
|
+
return await this.service.getComments(frame.options);
|
|
55
56
|
}
|
|
56
57
|
|
|
57
58
|
/**
|
|
@@ -114,6 +115,7 @@ module.exports = class CommentsController {
|
|
|
114
115
|
if (data.parent_id) {
|
|
115
116
|
result = await this.service.replyToComment(
|
|
116
117
|
data.parent_id,
|
|
118
|
+
data.in_reply_to_id,
|
|
117
119
|
frame.options.context.member.id,
|
|
118
120
|
data.html,
|
|
119
121
|
frame.options
|
|
@@ -83,7 +83,10 @@ class CommentsService {
|
|
|
83
83
|
await this.emails.notifyPostAuthors(comment);
|
|
84
84
|
|
|
85
85
|
if (comment.get('parent_id')) {
|
|
86
|
-
await this.emails.notifyParentCommentAuthor(comment);
|
|
86
|
+
await this.emails.notifyParentCommentAuthor(comment, {type: 'parent'});
|
|
87
|
+
}
|
|
88
|
+
if (comment.get('in_reply_to_id')) {
|
|
89
|
+
await this.emails.notifyParentCommentAuthor(comment, {type: 'in_reply_to'});
|
|
87
90
|
}
|
|
88
91
|
}
|
|
89
92
|
|
|
@@ -253,11 +256,12 @@ class CommentsService {
|
|
|
253
256
|
|
|
254
257
|
/**
|
|
255
258
|
* @param {string} parent - The ID of the Comment to reply to
|
|
259
|
+
* @param {string} inReplyTo - The ID of the Reply to reply to
|
|
256
260
|
* @param {string} member - The ID of the Member to comment as
|
|
257
261
|
* @param {string} comment - The HTML content of the Comment
|
|
258
262
|
* @param {any} options
|
|
259
263
|
*/
|
|
260
|
-
async replyToComment(parent, member, comment, options) {
|
|
264
|
+
async replyToComment(parent, inReplyTo, member, comment, options) {
|
|
261
265
|
this.checkEnabled();
|
|
262
266
|
const memberModel = await this.models.Member.findOne({
|
|
263
267
|
id: member
|
|
@@ -281,6 +285,7 @@ class CommentsService {
|
|
|
281
285
|
message: tpl(messages.replyToReply)
|
|
282
286
|
});
|
|
283
287
|
}
|
|
288
|
+
|
|
284
289
|
const postModel = await this.models.Post.findOne({
|
|
285
290
|
id: parentComment.get('post_id')
|
|
286
291
|
}, {
|
|
@@ -291,10 +296,27 @@ class CommentsService {
|
|
|
291
296
|
|
|
292
297
|
this.checkPostAccess(postModel, memberModel);
|
|
293
298
|
|
|
299
|
+
let inReplyToComment;
|
|
300
|
+
if (parent && inReplyTo) {
|
|
301
|
+
inReplyToComment = await this.getCommentByID(inReplyTo, options);
|
|
302
|
+
|
|
303
|
+
// we only allow references to published comments to avoid leaking
|
|
304
|
+
// hidden data via the snippet included in API responses
|
|
305
|
+
if (inReplyToComment && inReplyToComment.get('status') !== 'published') {
|
|
306
|
+
inReplyToComment = null;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// we don't allow in_reply_to references across different parents
|
|
310
|
+
if (inReplyToComment && inReplyToComment.get('parent_id') !== parent) {
|
|
311
|
+
inReplyToComment = null;
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
294
315
|
const model = await this.models.Comment.add({
|
|
295
316
|
post_id: parentComment.get('post_id'),
|
|
296
317
|
member_id: member,
|
|
297
318
|
parent_id: parentComment.id,
|
|
319
|
+
in_reply_to_id: inReplyToComment && inReplyToComment.get('id'),
|
|
298
320
|
html: comment,
|
|
299
321
|
status: 'published'
|
|
300
322
|
}, options);
|
|
@@ -60,8 +60,13 @@ class CommentsServiceEmails {
|
|
|
60
60
|
}
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
async notifyParentCommentAuthor(reply) {
|
|
64
|
-
|
|
63
|
+
async notifyParentCommentAuthor(reply, {type = 'parent'} = {}) {
|
|
64
|
+
let parent;
|
|
65
|
+
if (type === 'in_reply_to') {
|
|
66
|
+
parent = await this.models.Comment.findOne({id: reply.get('in_reply_to_id')});
|
|
67
|
+
} else {
|
|
68
|
+
parent = await this.models.Comment.findOne({id: reply.get('parent_id')});
|
|
69
|
+
}
|
|
65
70
|
const parentMember = parent.related('member');
|
|
66
71
|
|
|
67
72
|
if (parent?.get('status') !== 'published' || !parentMember.get('enable_comment_notifications')) {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
const logging = require('@tryghost/logging');
|
|
2
|
+
const JobManager = require('../../services/jobs');
|
|
3
|
+
const path = require('path');
|
|
2
4
|
|
|
3
5
|
class EmailAnalyticsServiceWrapper {
|
|
4
6
|
init() {
|
|
@@ -11,7 +13,7 @@ class EmailAnalyticsServiceWrapper {
|
|
|
11
13
|
const MailgunProvider = require('@tryghost/email-analytics-provider-mailgun');
|
|
12
14
|
const {EmailRecipientFailure, EmailSpamComplaintEvent, Email} = require('../../models');
|
|
13
15
|
const StartEmailAnalyticsJobEvent = require('./events/StartEmailAnalyticsJobEvent');
|
|
14
|
-
|
|
16
|
+
const {MemberEmailAnalyticsUpdateEvent} = require('@tryghost/member-events');
|
|
15
17
|
const domainEvents = require('@tryghost/domain-events');
|
|
16
18
|
const config = require('../../../shared/config');
|
|
17
19
|
const settings = require('../../../shared/settings-cache');
|
|
@@ -47,7 +49,8 @@ class EmailAnalyticsServiceWrapper {
|
|
|
47
49
|
providers: [
|
|
48
50
|
new MailgunProvider({config, settings})
|
|
49
51
|
],
|
|
50
|
-
queries
|
|
52
|
+
queries,
|
|
53
|
+
domainEvents
|
|
51
54
|
});
|
|
52
55
|
|
|
53
56
|
// We currently cannot trigger a non-offloaded job from the job manager
|
|
@@ -55,6 +58,19 @@ class EmailAnalyticsServiceWrapper {
|
|
|
55
58
|
domainEvents.subscribe(StartEmailAnalyticsJobEvent, async () => {
|
|
56
59
|
await this.startFetch();
|
|
57
60
|
});
|
|
61
|
+
|
|
62
|
+
domainEvents.subscribe(MemberEmailAnalyticsUpdateEvent, async (event) => {
|
|
63
|
+
const memberId = event.data.memberId;
|
|
64
|
+
await JobManager.addQueuedJob({
|
|
65
|
+
name: `update-member-email-analytics-${memberId}`,
|
|
66
|
+
metadata: {
|
|
67
|
+
job: path.resolve(__dirname, 'jobs/update-member-email-analytics'),
|
|
68
|
+
data: {
|
|
69
|
+
memberId
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
});
|
|
58
74
|
}
|
|
59
75
|
|
|
60
76
|
async fetchLatestOpenedEvents({maxEvents} = {maxEvents: Infinity}) {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const queries = require('../../lib/queries');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Updates email analytics for a specific member
|
|
5
|
+
*
|
|
6
|
+
* @param {Object} options - The options object
|
|
7
|
+
* @param {string} options.memberId - The ID of the member to update analytics for
|
|
8
|
+
* @returns {Promise<Object>} The result of the aggregation query (1/0)
|
|
9
|
+
*/
|
|
10
|
+
module.exports = async function updateMemberEmailAnalytics({memberId}) {
|
|
11
|
+
const result = await queries.aggregateMemberStats(memberId);
|
|
12
|
+
return result;
|
|
13
|
+
};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
const debug = require('@tryghost/debug')('i18n');
|
|
1
2
|
const logging = require('@tryghost/logging');
|
|
2
3
|
const url = require('../../api/endpoints/utils/serializers/output/utils/url');
|
|
4
|
+
const events = require('../../lib/common/events');
|
|
3
5
|
|
|
4
6
|
class EmailServiceWrapper {
|
|
5
7
|
getPostUrl(post) {
|
|
@@ -49,8 +51,25 @@ class EmailServiceWrapper {
|
|
|
49
51
|
const mailgunClient = new MailgunClient({
|
|
50
52
|
config: configService, settings: settingsCache
|
|
51
53
|
});
|
|
52
|
-
const i18nLanguage = settingsCache.get('locale') || 'en';
|
|
54
|
+
const i18nLanguage = labs.isSet('i18n') ? settingsCache.get('locale') || 'en' : 'en';
|
|
53
55
|
const i18n = i18nLib(i18nLanguage, 'newsletter');
|
|
56
|
+
|
|
57
|
+
events.on('settings.labs.edited', () => {
|
|
58
|
+
if (labs.isSet('i18n')) {
|
|
59
|
+
debug('labs i18n enabled, updating i18n to', settingsCache.get('locale'));
|
|
60
|
+
i18n.changeLanguage(settingsCache.get('locale'));
|
|
61
|
+
} else {
|
|
62
|
+
debug('labs i18n disabled, updating i18n to en');
|
|
63
|
+
i18n.changeLanguage('en');
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
events.on('settings.locale.edited', (model) => {
|
|
68
|
+
if (labs.isSet('i18n')) {
|
|
69
|
+
debug('locale changed, updating i18n to', model.get('value'));
|
|
70
|
+
i18n.changeLanguage(model.get('value'));
|
|
71
|
+
}
|
|
72
|
+
});
|
|
54
73
|
|
|
55
74
|
const mailgunEmailProvider = new MailgunEmailProvider({
|
|
56
75
|
mailgunClient,
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const {IdentityTokenService} = require('@tryghost/identity-token-service');
|
|
2
|
+
|
|
3
|
+
module.exports = class IdentityTokenServiceWrapper {
|
|
4
|
+
/** @type IdentityTokenService */
|
|
5
|
+
static instance;
|
|
6
|
+
|
|
7
|
+
static async init() {
|
|
8
|
+
if (IdentityTokenServiceWrapper.instance) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const urlUtils = require('../../../shared/url-utils');
|
|
13
|
+
const issuer = urlUtils.urlFor('admin', true);
|
|
14
|
+
|
|
15
|
+
const settings = require('../../../shared/settings-cache');
|
|
16
|
+
const jose = require('node-jose');
|
|
17
|
+
|
|
18
|
+
const privateKey = settings.get('ghost_private_key');
|
|
19
|
+
const keyStore = jose.JWK.createKeyStore();
|
|
20
|
+
const key = await keyStore.add(privateKey, 'pem');
|
|
21
|
+
|
|
22
|
+
IdentityTokenServiceWrapper.instance = new IdentityTokenService(
|
|
23
|
+
privateKey,
|
|
24
|
+
issuer,
|
|
25
|
+
key.kid
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
module.exports = require('./IdentityTokenServiceWrapper');
|