ghost 5.90.1 → 5.91.0

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