ghost 5.98.1 → 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.98.1.tgz → 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.98.1.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.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.98.1.tgz → 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.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.98.1.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.98.1.tgz → 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-d0087e93.mjs → index-ab50c736.mjs} +3782 -3675
- package/core/built/admin/assets/admin-x-activitypub/{modals-aaaeb2ed.mjs → modals-2e6f9c05.mjs} +2 -2
- package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +2 -2
- package/core/built/admin/assets/admin-x-demo/{index-ec1c4705.mjs → index-ff63fc71.mjs} +11 -10
- package/core/built/admin/assets/admin-x-demo/{modals-3bfb50e8.mjs → modals-c5353ca0.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{CodeEditorView-72579105.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-c8b38805.mjs → index-cdf09c6d.mjs} +2 -2
- package/core/built/admin/assets/admin-x-settings/{index-253cc6ec.mjs → index-f6338b55.mjs} +598 -587
- package/core/built/admin/assets/admin-x-settings/{modals-e1b17636.mjs → modals-15249255.mjs} +5659 -5625
- package/core/built/admin/assets/{chunk.524.61222a4a218eee1d5708.js → chunk.524.0982f6745ca0f52945b1.js} +5 -5
- package/core/built/admin/assets/{chunk.582.2b6d8d14e037cb327afb.js → chunk.582.d97692c812575ab051a9.js} +7 -7
- package/core/built/admin/assets/{ghost-84fc705038c0ea946e4e5048ccb1fea5.css → ghost-5be0a663eecbd5db9c3612b90050fa66.css} +1 -1
- package/core/built/admin/assets/{ghost-7a6db87f125f7ea41beb7a88d500e8dc.js → ghost-7ab268fc7cd7884eef525145d5fbb501.js} +22 -19
- package/core/built/admin/assets/{ghost-dark-4781c0dc5c36ec188a1ba04d893dbe47.css → ghost-dark-562f74b99cec55f89d4c6cc82ab5ad51.css} +1 -1
- package/core/built/admin/assets/koenig-lexical/index.css +1 -1
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +13236 -13223
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +134 -134
- package/core/built/admin/index.html +4 -4
- package/core/frontend/helpers/ghost_head.js +61 -52
- package/core/frontend/src/cards/css/signup.css +1 -0
- 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/api/endpoints/utils/serializers/output/mappers/index.js +1 -0
- package/core/server/api/endpoints/utils/serializers/output/mappers/oembed.js +5 -0
- package/core/server/api/endpoints/utils/serializers/output/oembed.js +7 -0
- 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 +24 -2
- package/core/server/services/i18n.js +1 -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/oembed/TwitterOEmbedProvider.js +8 -0
- package/core/server/services/oembed/service.js +2 -1
- package/core/server/services/offers/OfferBookshelfRepository.js +3 -1
- package/core/shared/config/defaults.json +3 -3
- 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/core/shared/settings-cache/CacheManager.js +9 -0
- package/package.json +157 -154
- package/yarn.lock +274 -113
- package/components/tryghost-adapter-cache-memory-ttl-5.98.1.tgz +0 -0
- package/components/tryghost-adapter-cache-redis-5.98.1.tgz +0 -0
- package/components/tryghost-adapter-manager-5.98.1.tgz +0 -0
- package/components/tryghost-announcement-bar-settings-5.98.1.tgz +0 -0
- package/components/tryghost-api-version-compatibility-service-5.98.1.tgz +0 -0
- package/components/tryghost-audience-feedback-5.98.1.tgz +0 -0
- package/components/tryghost-bookshelf-repository-5.98.1.tgz +0 -0
- package/components/tryghost-bootstrap-socket-5.98.1.tgz +0 -0
- package/components/tryghost-collections-5.98.1.tgz +0 -0
- package/components/tryghost-constants-5.98.1.tgz +0 -0
- package/components/tryghost-custom-fonts-5.98.1.tgz +0 -0
- package/components/tryghost-custom-theme-settings-service-5.98.1.tgz +0 -0
- package/components/tryghost-domain-events-5.98.1.tgz +0 -0
- package/components/tryghost-donations-5.98.1.tgz +0 -0
- package/components/tryghost-dynamic-routing-events-5.98.1.tgz +0 -0
- package/components/tryghost-email-addresses-5.98.1.tgz +0 -0
- package/components/tryghost-email-analytics-provider-mailgun-5.98.1.tgz +0 -0
- package/components/tryghost-email-analytics-service-5.98.1.tgz +0 -0
- package/components/tryghost-email-content-generator-5.98.1.tgz +0 -0
- package/components/tryghost-email-events-5.98.1.tgz +0 -0
- package/components/tryghost-email-service-5.98.1.tgz +0 -0
- package/components/tryghost-email-suppression-list-5.98.1.tgz +0 -0
- package/components/tryghost-express-dynamic-redirects-5.98.1.tgz +0 -0
- package/components/tryghost-external-media-inliner-5.98.1.tgz +0 -0
- package/components/tryghost-extract-api-key-5.98.1.tgz +0 -0
- package/components/tryghost-html-to-plaintext-5.98.1.tgz +0 -0
- package/components/tryghost-i18n-5.98.1.tgz +0 -0
- package/components/tryghost-importer-handler-content-files-5.98.1.tgz +0 -0
- package/components/tryghost-importer-revue-5.98.1.tgz +0 -0
- package/components/tryghost-in-memory-repository-5.98.1.tgz +0 -0
- package/components/tryghost-job-manager-5.98.1.tgz +0 -0
- package/components/tryghost-link-redirects-5.98.1.tgz +0 -0
- package/components/tryghost-link-replacer-5.98.1.tgz +0 -0
- package/components/tryghost-link-tracking-5.98.1.tgz +0 -0
- package/components/tryghost-magic-link-5.98.1.tgz +0 -0
- package/components/tryghost-mail-events-5.98.1.tgz +0 -0
- package/components/tryghost-mailgun-client-5.98.1.tgz +0 -0
- package/components/tryghost-member-attribution-5.98.1.tgz +0 -0
- package/components/tryghost-member-events-5.98.1.tgz +0 -0
- package/components/tryghost-members-csv-5.98.1.tgz +0 -0
- package/components/tryghost-members-events-service-5.98.1.tgz +0 -0
- package/components/tryghost-members-offers-5.98.1.tgz +0 -0
- package/components/tryghost-members-payments-5.98.1.tgz +0 -0
- package/components/tryghost-members-ssr-5.98.1.tgz +0 -0
- package/components/tryghost-members-stripe-service-5.98.1.tgz +0 -0
- package/components/tryghost-mentions-email-report-5.98.1.tgz +0 -0
- package/components/tryghost-metrics-server-5.98.1.tgz +0 -0
- package/components/tryghost-milestones-5.98.1.tgz +0 -0
- package/components/tryghost-minifier-5.98.1.tgz +0 -0
- package/components/tryghost-model-to-domain-event-interceptor-5.98.1.tgz +0 -0
- package/components/tryghost-mw-api-version-mismatch-5.98.1.tgz +0 -0
- package/components/tryghost-mw-cache-control-5.98.1.tgz +0 -0
- package/components/tryghost-mw-error-handler-5.98.1.tgz +0 -0
- package/components/tryghost-mw-session-from-token-5.98.1.tgz +0 -0
- package/components/tryghost-mw-update-user-last-seen-5.98.1.tgz +0 -0
- package/components/tryghost-mw-version-match-5.98.1.tgz +0 -0
- package/components/tryghost-mw-vhost-5.98.1.tgz +0 -0
- package/components/tryghost-nql-filter-expansions-5.98.1.tgz +0 -0
- package/components/tryghost-oembed-service-5.98.1.tgz +0 -0
- package/components/tryghost-package-json-5.98.1.tgz +0 -0
- package/components/tryghost-post-events-5.98.1.tgz +0 -0
- package/components/tryghost-post-revisions-5.98.1.tgz +0 -0
- package/components/tryghost-posts-service-5.98.1.tgz +0 -0
- package/components/tryghost-recommendations-5.98.1.tgz +0 -0
- package/components/tryghost-referrers-5.98.1.tgz +0 -0
- package/components/tryghost-security-5.98.1.tgz +0 -0
- package/components/tryghost-session-service-5.98.1.tgz +0 -0
- package/components/tryghost-settings-path-manager-5.98.1.tgz +0 -0
- package/components/tryghost-slack-notifications-5.98.1.tgz +0 -0
- package/components/tryghost-staff-service-5.98.1.tgz +0 -0
- package/components/tryghost-stats-service-5.98.1.tgz +0 -0
- package/components/tryghost-tiers-5.98.1.tgz +0 -0
- package/components/tryghost-update-check-service-5.98.1.tgz +0 -0
- package/components/tryghost-verification-trigger-5.98.1.tgz +0 -0
- package/components/tryghost-version-notifications-data-service-5.98.1.tgz +0 -0
- package/components/tryghost-webmentions-5.98.1.tgz +0 -0
|
@@ -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) {
|
|
@@ -27,7 +29,7 @@ class EmailServiceWrapper {
|
|
|
27
29
|
const limitService = require('../limits');
|
|
28
30
|
const labs = require('../../../shared/labs');
|
|
29
31
|
const emailAddressService = require('../email-address');
|
|
30
|
-
|
|
32
|
+
const i18nLib = require('@tryghost/i18n');
|
|
31
33
|
const mobiledocLib = require('../../lib/mobiledoc');
|
|
32
34
|
const lexicalLib = require('../../lib/lexical');
|
|
33
35
|
const urlUtils = require('../../../shared/url-utils');
|
|
@@ -49,6 +51,25 @@ class EmailServiceWrapper {
|
|
|
49
51
|
const mailgunClient = new MailgunClient({
|
|
50
52
|
config: configService, settings: settingsCache
|
|
51
53
|
});
|
|
54
|
+
const i18nLanguage = labs.isSet('i18n') ? settingsCache.get('locale') || 'en' : 'en';
|
|
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
|
+
});
|
|
52
73
|
|
|
53
74
|
const mailgunEmailProvider = new MailgunEmailProvider({
|
|
54
75
|
mailgunClient,
|
|
@@ -73,7 +94,8 @@ class EmailServiceWrapper {
|
|
|
73
94
|
outboundLinkTagger: memberAttribution.outboundLinkTagger,
|
|
74
95
|
emailAddressService: emailAddressService.service,
|
|
75
96
|
labs,
|
|
76
|
-
models: {Post}
|
|
97
|
+
models: {Post},
|
|
98
|
+
t: i18n.t
|
|
77
99
|
});
|
|
78
100
|
|
|
79
101
|
const sendingService = new SendingService({
|
|
@@ -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');
|
|
@@ -8,6 +8,7 @@ const logging = require('@tryghost/logging');
|
|
|
8
8
|
const models = require('../../models');
|
|
9
9
|
const sentry = require('../../../shared/sentry');
|
|
10
10
|
const domainEvents = require('@tryghost/domain-events');
|
|
11
|
+
const config = require('../../../shared/config');
|
|
11
12
|
|
|
12
13
|
const errorHandler = (error, workerMeta) => {
|
|
13
14
|
logging.info(`Capturing error for worker during execution of job: ${workerMeta.name}`);
|
|
@@ -24,7 +25,7 @@ const workerMessageHandler = ({name, message}) => {
|
|
|
24
25
|
const initTestMode = () => {
|
|
25
26
|
// Output job queue length every 5 seconds
|
|
26
27
|
setInterval(() => {
|
|
27
|
-
logging.warn(`${jobManager.
|
|
28
|
+
logging.warn(`${jobManager.inlineQueue.length()} jobs in the queue. Idle: ${jobManager.inlineQueue.idle()}`);
|
|
28
29
|
|
|
29
30
|
const runningScheduledjobs = Object.keys(jobManager.bree.workers);
|
|
30
31
|
if (Object.keys(jobManager.bree.workers).length) {
|
|
@@ -42,7 +43,7 @@ const initTestMode = () => {
|
|
|
42
43
|
}, 5000);
|
|
43
44
|
};
|
|
44
45
|
|
|
45
|
-
const jobManager = new JobManager({errorHandler, workerMessageHandler, JobModel: models.Job, domainEvents});
|
|
46
|
+
const jobManager = new JobManager({errorHandler, workerMessageHandler, JobModel: models.Job, domainEvents, config});
|
|
46
47
|
|
|
47
48
|
module.exports = jobManager;
|
|
48
49
|
module.exports.initTestMode = initTestMode;
|
|
@@ -24,7 +24,7 @@ const workerMessageHandler = ({name, message}) => {
|
|
|
24
24
|
const initTestMode = () => {
|
|
25
25
|
// Output job queue length every 5 seconds
|
|
26
26
|
setInterval(() => {
|
|
27
|
-
logging.warn(`${jobManager.
|
|
27
|
+
logging.warn(`${jobManager.inlineQueue.length()} jobs in the queue. Idle: ${jobManager.inlineQueue.idle()}`);
|
|
28
28
|
|
|
29
29
|
const runningScheduledjobs = Object.keys(jobManager.bree.workers);
|
|
30
30
|
if (Object.keys(jobManager.bree.workers).length) {
|
|
@@ -42,7 +42,7 @@ const initTestMode = () => {
|
|
|
42
42
|
}, 5000);
|
|
43
43
|
};
|
|
44
44
|
|
|
45
|
-
const jobManager = new JobManager({errorHandler, workerMessageHandler, JobModel: models.Job, domainEvents});
|
|
45
|
+
const jobManager = new JobManager({errorHandler, workerMessageHandler, JobModel: models.Job, domainEvents, isDuplicate: true});
|
|
46
46
|
|
|
47
47
|
module.exports = jobManager;
|
|
48
48
|
module.exports.initTestMode = initTestMode;
|
|
@@ -71,6 +71,14 @@ class TwitterOEmbedProvider {
|
|
|
71
71
|
oembedData.tweet_data = body.data;
|
|
72
72
|
oembedData.tweet_data.includes = body.includes;
|
|
73
73
|
} catch (err) {
|
|
74
|
+
if (err.response?.body) {
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(err.response.body);
|
|
77
|
+
err.context = parsed;
|
|
78
|
+
} catch (e) {
|
|
79
|
+
err.context = err.response.body;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
74
82
|
logging.error(err);
|
|
75
83
|
}
|
|
76
84
|
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
const config = require('../../../shared/config');
|
|
2
|
+
const storage = require('../../adapters/storage');
|
|
2
3
|
const externalRequest = require('../../lib/request-external');
|
|
3
4
|
|
|
4
5
|
const OEmbed = require('@tryghost/oembed-service');
|
|
5
|
-
const oembed = new OEmbed({config, externalRequest});
|
|
6
|
+
const oembed = new OEmbed({config, externalRequest, storage});
|
|
6
7
|
|
|
7
8
|
const NFT = require('./NFTOEmbedProvider');
|
|
8
9
|
const nft = new NFT({
|
|
@@ -107,6 +107,8 @@ class OfferBookshelfRepository {
|
|
|
107
107
|
});
|
|
108
108
|
|
|
109
109
|
try {
|
|
110
|
+
const lastRedeemedObject = lastRedeemed.toJSON();
|
|
111
|
+
|
|
110
112
|
return await Offer.create({
|
|
111
113
|
id: json.id,
|
|
112
114
|
name: json.name,
|
|
@@ -126,7 +128,7 @@ class OfferBookshelfRepository {
|
|
|
126
128
|
name: json.product.name
|
|
127
129
|
},
|
|
128
130
|
created_at: json.created_at,
|
|
129
|
-
last_redeemed:
|
|
131
|
+
last_redeemed: lastRedeemedObject.length > 0 ? lastRedeemedObject[0].created_at : null
|
|
130
132
|
}, null);
|
|
131
133
|
} catch (err) {
|
|
132
134
|
logger.error(err);
|
|
@@ -197,12 +197,12 @@
|
|
|
197
197
|
},
|
|
198
198
|
"portal": {
|
|
199
199
|
"url": "https://cdn.jsdelivr.net/ghost/portal@~{version}/umd/portal.min.js",
|
|
200
|
-
"version": "2.
|
|
200
|
+
"version": "2.46"
|
|
201
201
|
},
|
|
202
202
|
"sodoSearch": {
|
|
203
203
|
"url": "https://cdn.jsdelivr.net/ghost/sodo-search@~{version}/umd/sodo-search.min.js",
|
|
204
204
|
"styles": "https://cdn.jsdelivr.net/ghost/sodo-search@~{version}/umd/main.css",
|
|
205
|
-
"version": "1.
|
|
205
|
+
"version": "1.5"
|
|
206
206
|
},
|
|
207
207
|
"announcementBar": {
|
|
208
208
|
"url": "https://cdn.jsdelivr.net/ghost/announcement-bar@~{version}/umd/announcement-bar.min.js",
|
|
@@ -210,7 +210,7 @@
|
|
|
210
210
|
},
|
|
211
211
|
"comments": {
|
|
212
212
|
"url": "https://cdn.jsdelivr.net/ghost/comments-ui@~{version}/umd/comments-ui.min.js",
|
|
213
|
-
"version": "0.
|
|
213
|
+
"version": "0.21"
|
|
214
214
|
},
|
|
215
215
|
"signupForm": {
|
|
216
216
|
"url": "https://cdn.jsdelivr.net/ghost/signup-form@~{version}/umd/signup-form.min.js",
|
package/core/shared/labs.js
CHANGED
|
@@ -16,6 +16,7 @@ const messages = {
|
|
|
16
16
|
const GA_FEATURES = [
|
|
17
17
|
'audienceFeedback',
|
|
18
18
|
'collections',
|
|
19
|
+
'i18n',
|
|
19
20
|
'themeErrorsNotification',
|
|
20
21
|
'outboundLinkTagging',
|
|
21
22
|
'announcementBar',
|
|
@@ -26,7 +27,6 @@ const GA_FEATURES = [
|
|
|
26
27
|
// input for the "labs" setting value
|
|
27
28
|
const BETA_FEATURES = [
|
|
28
29
|
'additionalPaymentMethods',
|
|
29
|
-
'i18n',
|
|
30
30
|
'activitypub',
|
|
31
31
|
'stripeAutomaticTax',
|
|
32
32
|
'webmentions',
|
|
@@ -1,36 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
this.client = require('prom-client');
|
|
4
|
-
this.prefix = 'ghost_';
|
|
5
|
-
this.collectDefaultMetrics();
|
|
6
|
-
}
|
|
1
|
+
const {PrometheusClient} = require('@tryghost/prometheus-metrics');
|
|
2
|
+
const config = require('./config');
|
|
7
3
|
|
|
8
|
-
|
|
9
|
-
this.client.collectDefaultMetrics({prefix: this.prefix});
|
|
10
|
-
}
|
|
4
|
+
let prometheusClient;
|
|
11
5
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
res.status(500).end(err.message);
|
|
18
|
-
}
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
async getMetrics() {
|
|
22
|
-
return this.client.register.metrics();
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
getRegistry() {
|
|
26
|
-
return this.client.register;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
getContentType() {
|
|
30
|
-
return this.getRegistry().contentType;
|
|
31
|
-
}
|
|
6
|
+
if (!prometheusClient) {
|
|
7
|
+
const pushgatewayConfig = config.get('prometheus:pushgateway');
|
|
8
|
+
const prometheusConfig = {pushgateway: pushgatewayConfig};
|
|
9
|
+
prometheusClient = new PrometheusClient(prometheusConfig);
|
|
10
|
+
prometheusClient.init();
|
|
32
11
|
}
|
|
33
12
|
|
|
34
|
-
// Create a singleton instance and export it as the default export
|
|
35
|
-
const prometheusClient = new PrometheusClient();
|
|
36
13
|
module.exports = prometheusClient;
|
|
@@ -64,6 +64,10 @@ class CacheManager {
|
|
|
64
64
|
return cacheEntry;
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
// TODO: I think we should be a little smarter here and deserialize the value based on the type
|
|
68
|
+
// rather than trying to parse everything as JSON, which is very slow when we do it hundreds
|
|
69
|
+
// of times per request.
|
|
70
|
+
|
|
67
71
|
// Default behavior is to try to resolve the value and return that
|
|
68
72
|
try {
|
|
69
73
|
// CASE: handle literal false
|
|
@@ -71,6 +75,11 @@ class CacheManager {
|
|
|
71
75
|
return false;
|
|
72
76
|
}
|
|
73
77
|
|
|
78
|
+
// CASE: hotpath early return for strings which are already strings
|
|
79
|
+
if (cacheEntry.type === 'string' && typeof cacheEntry.value === 'string') {
|
|
80
|
+
return cacheEntry.value || null;
|
|
81
|
+
}
|
|
82
|
+
|
|
74
83
|
// CASE: if a string contains a number e.g. "1", JSON.parse will auto convert into integer
|
|
75
84
|
if (!isNaN(Number(cacheEntry.value))) {
|
|
76
85
|
return cacheEntry.value || null;
|