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.
Files changed (220) hide show
  1. package/components/tryghost-activitypub-5.100.0.tgz +0 -0
  2. package/components/tryghost-adapter-cache-memory-ttl-5.100.0.tgz +0 -0
  3. package/components/tryghost-adapter-cache-redis-5.100.0.tgz +0 -0
  4. package/components/tryghost-adapter-manager-5.100.0.tgz +0 -0
  5. package/components/tryghost-announcement-bar-settings-5.100.0.tgz +0 -0
  6. package/components/{tryghost-api-framework-5.98.1.tgz → tryghost-api-framework-5.100.0.tgz} +0 -0
  7. package/components/tryghost-api-version-compatibility-service-5.100.0.tgz +0 -0
  8. package/components/tryghost-audience-feedback-5.100.0.tgz +0 -0
  9. package/components/tryghost-bookshelf-repository-5.100.0.tgz +0 -0
  10. package/components/tryghost-bootstrap-socket-5.100.0.tgz +0 -0
  11. package/components/tryghost-collections-5.100.0.tgz +0 -0
  12. package/components/tryghost-constants-5.100.0.tgz +0 -0
  13. package/components/tryghost-custom-fonts-5.100.0.tgz +0 -0
  14. package/components/tryghost-custom-theme-settings-service-5.100.0.tgz +0 -0
  15. package/components/{tryghost-data-generator-5.98.1.tgz → tryghost-data-generator-5.100.0.tgz} +0 -0
  16. package/components/tryghost-domain-events-5.100.0.tgz +0 -0
  17. package/components/tryghost-donations-5.100.0.tgz +0 -0
  18. package/components/tryghost-dynamic-routing-events-5.100.0.tgz +0 -0
  19. package/components/tryghost-email-addresses-5.100.0.tgz +0 -0
  20. package/components/tryghost-email-analytics-provider-mailgun-5.100.0.tgz +0 -0
  21. package/components/tryghost-email-analytics-service-5.100.0.tgz +0 -0
  22. package/components/tryghost-email-content-generator-5.100.0.tgz +0 -0
  23. package/components/tryghost-email-events-5.100.0.tgz +0 -0
  24. package/components/tryghost-email-service-5.100.0.tgz +0 -0
  25. package/components/tryghost-email-suppression-list-5.100.0.tgz +0 -0
  26. package/components/tryghost-express-dynamic-redirects-5.100.0.tgz +0 -0
  27. package/components/tryghost-external-media-inliner-5.100.0.tgz +0 -0
  28. package/components/tryghost-extract-api-key-5.100.0.tgz +0 -0
  29. package/components/{tryghost-ghost-5.98.1.tgz → tryghost-ghost-5.100.0.tgz} +0 -0
  30. package/components/tryghost-html-to-plaintext-5.100.0.tgz +0 -0
  31. package/components/tryghost-i18n-5.100.0.tgz +0 -0
  32. package/components/tryghost-identity-token-service-5.100.0.tgz +0 -0
  33. package/components/tryghost-importer-handler-content-files-5.100.0.tgz +0 -0
  34. package/components/tryghost-importer-revue-5.100.0.tgz +0 -0
  35. package/components/tryghost-in-memory-repository-5.100.0.tgz +0 -0
  36. package/components/tryghost-job-manager-5.100.0.tgz +0 -0
  37. package/components/tryghost-link-redirects-5.100.0.tgz +0 -0
  38. package/components/tryghost-link-replacer-5.100.0.tgz +0 -0
  39. package/components/tryghost-link-tracking-5.100.0.tgz +0 -0
  40. package/components/tryghost-magic-link-5.100.0.tgz +0 -0
  41. package/components/tryghost-mail-events-5.100.0.tgz +0 -0
  42. package/components/tryghost-mailgun-client-5.100.0.tgz +0 -0
  43. package/components/tryghost-member-attribution-5.100.0.tgz +0 -0
  44. package/components/tryghost-member-events-5.100.0.tgz +0 -0
  45. package/components/{tryghost-members-api-5.98.1.tgz → tryghost-members-api-5.100.0.tgz} +0 -0
  46. package/components/tryghost-members-csv-5.100.0.tgz +0 -0
  47. package/components/tryghost-members-events-service-5.100.0.tgz +0 -0
  48. package/components/{tryghost-members-importer-5.98.1.tgz → tryghost-members-importer-5.100.0.tgz} +0 -0
  49. package/components/tryghost-members-offers-5.100.0.tgz +0 -0
  50. package/components/tryghost-members-payments-5.100.0.tgz +0 -0
  51. package/components/tryghost-members-ssr-5.100.0.tgz +0 -0
  52. package/components/tryghost-members-stripe-service-5.100.0.tgz +0 -0
  53. package/components/tryghost-mentions-email-report-5.100.0.tgz +0 -0
  54. package/components/tryghost-milestones-5.100.0.tgz +0 -0
  55. package/components/tryghost-minifier-5.100.0.tgz +0 -0
  56. package/components/tryghost-model-to-domain-event-interceptor-5.100.0.tgz +0 -0
  57. package/components/tryghost-mw-api-version-mismatch-5.100.0.tgz +0 -0
  58. package/components/tryghost-mw-cache-control-5.100.0.tgz +0 -0
  59. package/components/tryghost-mw-error-handler-5.100.0.tgz +0 -0
  60. package/components/tryghost-mw-session-from-token-5.100.0.tgz +0 -0
  61. package/components/tryghost-mw-update-user-last-seen-5.100.0.tgz +0 -0
  62. package/components/tryghost-mw-version-match-5.100.0.tgz +0 -0
  63. package/components/tryghost-mw-vhost-5.100.0.tgz +0 -0
  64. package/components/tryghost-nql-filter-expansions-5.100.0.tgz +0 -0
  65. package/components/tryghost-oembed-service-5.100.0.tgz +0 -0
  66. package/components/tryghost-package-json-5.100.0.tgz +0 -0
  67. package/components/tryghost-post-events-5.100.0.tgz +0 -0
  68. package/components/tryghost-post-revisions-5.100.0.tgz +0 -0
  69. package/components/tryghost-posts-service-5.100.0.tgz +0 -0
  70. package/components/tryghost-prometheus-metrics-5.100.0.tgz +0 -0
  71. package/components/tryghost-recommendations-5.100.0.tgz +0 -0
  72. package/components/tryghost-referrers-5.100.0.tgz +0 -0
  73. package/components/tryghost-security-5.100.0.tgz +0 -0
  74. package/components/tryghost-session-service-5.100.0.tgz +0 -0
  75. package/components/tryghost-settings-path-manager-5.100.0.tgz +0 -0
  76. package/components/tryghost-slack-notifications-5.100.0.tgz +0 -0
  77. package/components/tryghost-staff-service-5.100.0.tgz +0 -0
  78. package/components/tryghost-stats-service-5.100.0.tgz +0 -0
  79. package/components/tryghost-tiers-5.100.0.tgz +0 -0
  80. package/components/tryghost-update-check-service-5.100.0.tgz +0 -0
  81. package/components/tryghost-verification-trigger-5.100.0.tgz +0 -0
  82. package/components/tryghost-version-notifications-data-service-5.100.0.tgz +0 -0
  83. package/components/tryghost-webmentions-5.100.0.tgz +0 -0
  84. package/core/boot.js +16 -5
  85. package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +2 -2
  86. package/core/built/admin/assets/admin-x-activitypub/{index-d0087e93.mjs → index-ab50c736.mjs} +3782 -3675
  87. package/core/built/admin/assets/admin-x-activitypub/{modals-aaaeb2ed.mjs → modals-2e6f9c05.mjs} +2 -2
  88. package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +2 -2
  89. package/core/built/admin/assets/admin-x-demo/{index-ec1c4705.mjs → index-ff63fc71.mjs} +11 -10
  90. package/core/built/admin/assets/admin-x-demo/{modals-3bfb50e8.mjs → modals-c5353ca0.mjs} +2 -2
  91. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-72579105.mjs → CodeEditorView-26137e6e.mjs} +2 -2
  92. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +3 -3
  93. package/core/built/admin/assets/admin-x-settings/{index-c8b38805.mjs → index-cdf09c6d.mjs} +2 -2
  94. package/core/built/admin/assets/admin-x-settings/{index-253cc6ec.mjs → index-f6338b55.mjs} +598 -587
  95. package/core/built/admin/assets/admin-x-settings/{modals-e1b17636.mjs → modals-15249255.mjs} +5659 -5625
  96. package/core/built/admin/assets/{chunk.524.61222a4a218eee1d5708.js → chunk.524.0982f6745ca0f52945b1.js} +5 -5
  97. package/core/built/admin/assets/{chunk.582.2b6d8d14e037cb327afb.js → chunk.582.d97692c812575ab051a9.js} +7 -7
  98. package/core/built/admin/assets/{ghost-84fc705038c0ea946e4e5048ccb1fea5.css → ghost-5be0a663eecbd5db9c3612b90050fa66.css} +1 -1
  99. package/core/built/admin/assets/{ghost-7a6db87f125f7ea41beb7a88d500e8dc.js → ghost-7ab268fc7cd7884eef525145d5fbb501.js} +22 -19
  100. package/core/built/admin/assets/{ghost-dark-4781c0dc5c36ec188a1ba04d893dbe47.css → ghost-dark-562f74b99cec55f89d4c6cc82ab5ad51.css} +1 -1
  101. package/core/built/admin/assets/koenig-lexical/index.css +1 -1
  102. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +13236 -13223
  103. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +134 -134
  104. package/core/built/admin/index.html +4 -4
  105. package/core/frontend/helpers/ghost_head.js +61 -52
  106. package/core/frontend/src/cards/css/signup.css +1 -0
  107. package/core/server/api/endpoints/comments-members.js +0 -1
  108. package/core/server/api/endpoints/identities.js +5 -31
  109. package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +9 -2
  110. package/core/server/api/endpoints/utils/serializers/output/mappers/index.js +1 -0
  111. package/core/server/api/endpoints/utils/serializers/output/mappers/oembed.js +5 -0
  112. package/core/server/api/endpoints/utils/serializers/output/oembed.js +7 -0
  113. package/core/server/data/migrations/versions/5.100/2024-10-31-15-27-42-add-jobs-queue-columns.js +14 -0
  114. package/core/server/data/migrations/versions/5.100/2024-11-05-14-48-08-add-comments-in-reply-to-id.js +10 -0
  115. package/core/server/data/migrations/versions/5.100/2024-11-06-04-45-15-add-activitypub-integration.js +40 -0
  116. package/core/server/data/migrations/versions/5.55/2023-07-10-05-16-55-add-built-in-collection-posts.js +1 -1
  117. package/core/server/data/migrations/versions/5.65/2023-09-22-06-42-55-repopulate-built-in-featured-collection-posts.js +1 -1
  118. package/core/server/data/migrations/versions/5.89/2024-07-30-19-51-06-backfill-offer-redemptions.js +1 -1
  119. package/core/server/data/schema/fixtures/fixtures.json +7 -0
  120. package/core/server/data/schema/schema.js +4 -1
  121. package/core/server/models/comment.js +17 -33
  122. package/core/server/services/activitypub/ActivityPubServiceWrapper.js +42 -0
  123. package/core/server/services/activitypub/index.js +1 -0
  124. package/core/server/services/comments/CommentsController.js +3 -1
  125. package/core/server/services/comments/CommentsService.js +24 -2
  126. package/core/server/services/comments/CommentsServiceEmails.js +7 -2
  127. package/core/server/services/email-analytics/EmailAnalyticsServiceWrapper.js +18 -2
  128. package/core/server/services/email-analytics/jobs/update-member-email-analytics/index.js +13 -0
  129. package/core/server/services/email-service/EmailServiceWrapper.js +24 -2
  130. package/core/server/services/i18n.js +1 -1
  131. package/core/server/services/identity-tokens/IdentityTokenServiceWrapper.js +28 -0
  132. package/core/server/services/identity-tokens/index.js +1 -0
  133. package/core/server/services/jobs/job-service.js +3 -2
  134. package/core/server/services/mentions-jobs/job-service.js +2 -2
  135. package/core/server/services/oembed/TwitterOEmbedProvider.js +8 -0
  136. package/core/server/services/oembed/service.js +2 -1
  137. package/core/server/services/offers/OfferBookshelfRepository.js +3 -1
  138. package/core/shared/config/defaults.json +3 -3
  139. package/core/shared/config/env/config.testing.json +0 -3
  140. package/core/shared/labs.js +1 -1
  141. package/core/shared/prometheus-client.js +8 -31
  142. package/core/shared/settings-cache/CacheManager.js +9 -0
  143. package/package.json +157 -154
  144. package/yarn.lock +274 -113
  145. package/components/tryghost-adapter-cache-memory-ttl-5.98.1.tgz +0 -0
  146. package/components/tryghost-adapter-cache-redis-5.98.1.tgz +0 -0
  147. package/components/tryghost-adapter-manager-5.98.1.tgz +0 -0
  148. package/components/tryghost-announcement-bar-settings-5.98.1.tgz +0 -0
  149. package/components/tryghost-api-version-compatibility-service-5.98.1.tgz +0 -0
  150. package/components/tryghost-audience-feedback-5.98.1.tgz +0 -0
  151. package/components/tryghost-bookshelf-repository-5.98.1.tgz +0 -0
  152. package/components/tryghost-bootstrap-socket-5.98.1.tgz +0 -0
  153. package/components/tryghost-collections-5.98.1.tgz +0 -0
  154. package/components/tryghost-constants-5.98.1.tgz +0 -0
  155. package/components/tryghost-custom-fonts-5.98.1.tgz +0 -0
  156. package/components/tryghost-custom-theme-settings-service-5.98.1.tgz +0 -0
  157. package/components/tryghost-domain-events-5.98.1.tgz +0 -0
  158. package/components/tryghost-donations-5.98.1.tgz +0 -0
  159. package/components/tryghost-dynamic-routing-events-5.98.1.tgz +0 -0
  160. package/components/tryghost-email-addresses-5.98.1.tgz +0 -0
  161. package/components/tryghost-email-analytics-provider-mailgun-5.98.1.tgz +0 -0
  162. package/components/tryghost-email-analytics-service-5.98.1.tgz +0 -0
  163. package/components/tryghost-email-content-generator-5.98.1.tgz +0 -0
  164. package/components/tryghost-email-events-5.98.1.tgz +0 -0
  165. package/components/tryghost-email-service-5.98.1.tgz +0 -0
  166. package/components/tryghost-email-suppression-list-5.98.1.tgz +0 -0
  167. package/components/tryghost-express-dynamic-redirects-5.98.1.tgz +0 -0
  168. package/components/tryghost-external-media-inliner-5.98.1.tgz +0 -0
  169. package/components/tryghost-extract-api-key-5.98.1.tgz +0 -0
  170. package/components/tryghost-html-to-plaintext-5.98.1.tgz +0 -0
  171. package/components/tryghost-i18n-5.98.1.tgz +0 -0
  172. package/components/tryghost-importer-handler-content-files-5.98.1.tgz +0 -0
  173. package/components/tryghost-importer-revue-5.98.1.tgz +0 -0
  174. package/components/tryghost-in-memory-repository-5.98.1.tgz +0 -0
  175. package/components/tryghost-job-manager-5.98.1.tgz +0 -0
  176. package/components/tryghost-link-redirects-5.98.1.tgz +0 -0
  177. package/components/tryghost-link-replacer-5.98.1.tgz +0 -0
  178. package/components/tryghost-link-tracking-5.98.1.tgz +0 -0
  179. package/components/tryghost-magic-link-5.98.1.tgz +0 -0
  180. package/components/tryghost-mail-events-5.98.1.tgz +0 -0
  181. package/components/tryghost-mailgun-client-5.98.1.tgz +0 -0
  182. package/components/tryghost-member-attribution-5.98.1.tgz +0 -0
  183. package/components/tryghost-member-events-5.98.1.tgz +0 -0
  184. package/components/tryghost-members-csv-5.98.1.tgz +0 -0
  185. package/components/tryghost-members-events-service-5.98.1.tgz +0 -0
  186. package/components/tryghost-members-offers-5.98.1.tgz +0 -0
  187. package/components/tryghost-members-payments-5.98.1.tgz +0 -0
  188. package/components/tryghost-members-ssr-5.98.1.tgz +0 -0
  189. package/components/tryghost-members-stripe-service-5.98.1.tgz +0 -0
  190. package/components/tryghost-mentions-email-report-5.98.1.tgz +0 -0
  191. package/components/tryghost-metrics-server-5.98.1.tgz +0 -0
  192. package/components/tryghost-milestones-5.98.1.tgz +0 -0
  193. package/components/tryghost-minifier-5.98.1.tgz +0 -0
  194. package/components/tryghost-model-to-domain-event-interceptor-5.98.1.tgz +0 -0
  195. package/components/tryghost-mw-api-version-mismatch-5.98.1.tgz +0 -0
  196. package/components/tryghost-mw-cache-control-5.98.1.tgz +0 -0
  197. package/components/tryghost-mw-error-handler-5.98.1.tgz +0 -0
  198. package/components/tryghost-mw-session-from-token-5.98.1.tgz +0 -0
  199. package/components/tryghost-mw-update-user-last-seen-5.98.1.tgz +0 -0
  200. package/components/tryghost-mw-version-match-5.98.1.tgz +0 -0
  201. package/components/tryghost-mw-vhost-5.98.1.tgz +0 -0
  202. package/components/tryghost-nql-filter-expansions-5.98.1.tgz +0 -0
  203. package/components/tryghost-oembed-service-5.98.1.tgz +0 -0
  204. package/components/tryghost-package-json-5.98.1.tgz +0 -0
  205. package/components/tryghost-post-events-5.98.1.tgz +0 -0
  206. package/components/tryghost-post-revisions-5.98.1.tgz +0 -0
  207. package/components/tryghost-posts-service-5.98.1.tgz +0 -0
  208. package/components/tryghost-recommendations-5.98.1.tgz +0 -0
  209. package/components/tryghost-referrers-5.98.1.tgz +0 -0
  210. package/components/tryghost-security-5.98.1.tgz +0 -0
  211. package/components/tryghost-session-service-5.98.1.tgz +0 -0
  212. package/components/tryghost-settings-path-manager-5.98.1.tgz +0 -0
  213. package/components/tryghost-slack-notifications-5.98.1.tgz +0 -0
  214. package/components/tryghost-staff-service-5.98.1.tgz +0 -0
  215. package/components/tryghost-stats-service-5.98.1.tgz +0 -0
  216. package/components/tryghost-tiers-5.98.1.tgz +0 -0
  217. package/components/tryghost-update-check-service-5.98.1.tgz +0 -0
  218. package/components/tryghost-verification-trigger-5.98.1.tgz +0 -0
  219. package/components/tryghost-version-notifications-data-service-5.98.1.tgz +0 -0
  220. 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
- return this.service.getComments(frame.options);
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
- const parent = await this.models.Comment.findOne({id: reply.get('parent_id')});
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({
@@ -33,4 +33,4 @@ module.exports.init = function () {
33
33
  i18nInstance.changeLanguage(model.get('value'));
34
34
  }
35
35
  });
36
- };
36
+ };
@@ -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.queue.length()} jobs in the queue. Idle: ${jobManager.queue.idle()}`);
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.queue.length()} jobs in the queue. Idle: ${jobManager.queue.idle()}`);
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: lastRedeemed.toJSON().length > 0 ? lastRedeemed.toJSON()[0].created_at : null
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.45"
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.4"
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.20"
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",
@@ -11,9 +11,6 @@
11
11
  "server": {
12
12
  "port": 2369
13
13
  },
14
- "metrics_server": {
15
- "port": 9417
16
- },
17
14
  "logging": {
18
15
  "level": "error"
19
16
  },
@@ -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
- class PrometheusClient {
2
- constructor() {
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
- collectDefaultMetrics() {
9
- this.client.collectDefaultMetrics({prefix: this.prefix});
10
- }
4
+ let prometheusClient;
11
5
 
12
- async handleMetricsRequest(req, res) {
13
- try {
14
- res.set('Content-Type', this.getContentType());
15
- res.end(await this.getMetrics());
16
- } catch (err) {
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;