ghost 5.76.1 → 5.77.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 (166) hide show
  1. package/components/tryghost-adapter-cache-memory-ttl-5.77.0.tgz +0 -0
  2. package/components/tryghost-adapter-cache-redis-5.77.0.tgz +0 -0
  3. package/components/{tryghost-adapter-manager-5.76.1.tgz → tryghost-adapter-manager-5.77.0.tgz} +0 -0
  4. package/components/tryghost-announcement-bar-settings-5.77.0.tgz +0 -0
  5. package/components/tryghost-api-framework-5.77.0.tgz +0 -0
  6. package/components/tryghost-api-version-compatibility-service-5.77.0.tgz +0 -0
  7. package/components/tryghost-audience-feedback-5.77.0.tgz +0 -0
  8. package/components/tryghost-bookshelf-repository-5.77.0.tgz +0 -0
  9. package/components/tryghost-bootstrap-socket-5.77.0.tgz +0 -0
  10. package/components/tryghost-collections-5.77.0.tgz +0 -0
  11. package/components/tryghost-constants-5.77.0.tgz +0 -0
  12. package/components/tryghost-custom-theme-settings-service-5.77.0.tgz +0 -0
  13. package/components/{tryghost-data-generator-5.76.1.tgz → tryghost-data-generator-5.77.0.tgz} +0 -0
  14. package/components/{tryghost-domain-events-5.76.1.tgz → tryghost-domain-events-5.77.0.tgz} +0 -0
  15. package/components/tryghost-donations-5.77.0.tgz +0 -0
  16. package/components/tryghost-dynamic-routing-events-5.77.0.tgz +0 -0
  17. package/components/tryghost-email-addresses-5.77.0.tgz +0 -0
  18. package/components/{tryghost-email-analytics-provider-mailgun-5.76.1.tgz → tryghost-email-analytics-provider-mailgun-5.77.0.tgz} +0 -0
  19. package/components/tryghost-email-analytics-service-5.77.0.tgz +0 -0
  20. package/components/tryghost-email-content-generator-5.77.0.tgz +0 -0
  21. package/components/{tryghost-email-events-5.76.1.tgz → tryghost-email-events-5.77.0.tgz} +0 -0
  22. package/components/tryghost-email-service-5.77.0.tgz +0 -0
  23. package/components/tryghost-email-suppression-list-5.77.0.tgz +0 -0
  24. package/components/{tryghost-express-dynamic-redirects-5.76.1.tgz → tryghost-express-dynamic-redirects-5.77.0.tgz} +0 -0
  25. package/components/{tryghost-external-media-inliner-5.76.1.tgz → tryghost-external-media-inliner-5.77.0.tgz} +0 -0
  26. package/components/tryghost-extract-api-key-5.77.0.tgz +0 -0
  27. package/components/tryghost-html-to-plaintext-5.77.0.tgz +0 -0
  28. package/components/tryghost-i18n-5.77.0.tgz +0 -0
  29. package/components/{tryghost-importer-handler-content-files-5.76.1.tgz → tryghost-importer-handler-content-files-5.77.0.tgz} +0 -0
  30. package/components/tryghost-importer-revue-5.77.0.tgz +0 -0
  31. package/components/tryghost-in-memory-repository-5.77.0.tgz +0 -0
  32. package/components/{tryghost-job-manager-5.76.1.tgz → tryghost-job-manager-5.77.0.tgz} +0 -0
  33. package/components/tryghost-link-redirects-5.77.0.tgz +0 -0
  34. package/components/tryghost-link-replacer-5.77.0.tgz +0 -0
  35. package/components/tryghost-link-tracking-5.77.0.tgz +0 -0
  36. package/components/{tryghost-magic-link-5.76.1.tgz → tryghost-magic-link-5.77.0.tgz} +0 -0
  37. package/components/{tryghost-mail-events-5.76.1.tgz → tryghost-mail-events-5.77.0.tgz} +0 -0
  38. package/components/{tryghost-mailgun-client-5.76.1.tgz → tryghost-mailgun-client-5.77.0.tgz} +0 -0
  39. package/components/{tryghost-member-attribution-5.76.1.tgz → tryghost-member-attribution-5.77.0.tgz} +0 -0
  40. package/components/tryghost-member-events-5.77.0.tgz +0 -0
  41. package/components/tryghost-members-api-5.77.0.tgz +0 -0
  42. package/components/tryghost-members-csv-5.77.0.tgz +0 -0
  43. package/components/{tryghost-members-events-service-5.76.1.tgz → tryghost-members-events-service-5.77.0.tgz} +0 -0
  44. package/components/tryghost-members-importer-5.77.0.tgz +0 -0
  45. package/components/{tryghost-members-offers-5.76.1.tgz → tryghost-members-offers-5.77.0.tgz} +0 -0
  46. package/components/{tryghost-members-payments-5.76.1.tgz → tryghost-members-payments-5.77.0.tgz} +0 -0
  47. package/components/{tryghost-members-ssr-5.76.1.tgz → tryghost-members-ssr-5.77.0.tgz} +0 -0
  48. package/components/{tryghost-members-stripe-service-5.76.1.tgz → tryghost-members-stripe-service-5.77.0.tgz} +0 -0
  49. package/components/tryghost-mentions-email-report-5.77.0.tgz +0 -0
  50. package/components/tryghost-milestones-5.77.0.tgz +0 -0
  51. package/components/tryghost-minifier-5.77.0.tgz +0 -0
  52. package/components/tryghost-model-to-domain-event-interceptor-5.77.0.tgz +0 -0
  53. package/components/tryghost-mw-api-version-mismatch-5.77.0.tgz +0 -0
  54. package/components/tryghost-mw-cache-control-5.77.0.tgz +0 -0
  55. package/components/{tryghost-mw-error-handler-5.76.1.tgz → tryghost-mw-error-handler-5.77.0.tgz} +0 -0
  56. package/components/tryghost-mw-session-from-token-5.77.0.tgz +0 -0
  57. package/components/tryghost-mw-update-user-last-seen-5.77.0.tgz +0 -0
  58. package/components/{tryghost-mw-version-match-5.76.1.tgz → tryghost-mw-version-match-5.77.0.tgz} +0 -0
  59. package/components/tryghost-mw-vhost-5.77.0.tgz +0 -0
  60. package/components/tryghost-nql-filter-expansions-5.77.0.tgz +0 -0
  61. package/components/tryghost-oembed-service-5.77.0.tgz +0 -0
  62. package/components/{tryghost-package-json-5.76.1.tgz → tryghost-package-json-5.77.0.tgz} +0 -0
  63. package/components/tryghost-post-events-5.77.0.tgz +0 -0
  64. package/components/tryghost-post-revisions-5.77.0.tgz +0 -0
  65. package/components/tryghost-posts-service-5.77.0.tgz +0 -0
  66. package/components/tryghost-recommendations-5.77.0.tgz +0 -0
  67. package/components/tryghost-referrers-5.77.0.tgz +0 -0
  68. package/components/{tryghost-security-5.76.1.tgz → tryghost-security-5.77.0.tgz} +0 -0
  69. package/components/{tryghost-session-service-5.76.1.tgz → tryghost-session-service-5.77.0.tgz} +0 -0
  70. package/components/tryghost-settings-path-manager-5.77.0.tgz +0 -0
  71. package/components/tryghost-slack-notifications-5.77.0.tgz +0 -0
  72. package/components/{tryghost-staff-service-5.76.1.tgz → tryghost-staff-service-5.77.0.tgz} +0 -0
  73. package/components/tryghost-stats-service-5.77.0.tgz +0 -0
  74. package/components/tryghost-tiers-5.77.0.tgz +0 -0
  75. package/components/{tryghost-update-check-service-5.76.1.tgz → tryghost-update-check-service-5.77.0.tgz} +0 -0
  76. package/components/{tryghost-verification-trigger-5.76.1.tgz → tryghost-verification-trigger-5.77.0.tgz} +0 -0
  77. package/components/tryghost-version-notifications-data-service-5.77.0.tgz +0 -0
  78. package/components/tryghost-webmentions-5.77.0.tgz +0 -0
  79. package/core/boot.js +3 -1
  80. package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
  81. package/core/built/admin/assets/admin-x-demo/{index-3d0e9e6b.mjs → index-eee22c84.mjs} +348 -346
  82. package/core/built/admin/assets/admin-x-demo/{modals-f20fa267.mjs → modals-e4b12fba.mjs} +38 -38
  83. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-05a0f378.mjs → CodeEditorView-c3b34635.mjs} +2 -2
  84. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
  85. package/core/built/admin/assets/admin-x-settings/{index-6cc60dce.mjs → index-28680861.mjs} +2 -2
  86. package/core/built/admin/assets/admin-x-settings/{index-518e7d26.mjs → index-8808e054.mjs} +2 -2
  87. package/core/built/admin/assets/admin-x-settings/{index-1c923713.mjs → index-99bd65a2.mjs} +2579 -2583
  88. package/core/built/admin/assets/admin-x-settings/{modals-516e0c98.mjs → modals-f491bd44.mjs} +5248 -5253
  89. package/core/built/admin/assets/{chunk.143.16780ac5d51ee6a46eb2.js → chunk.143.16228699a88898175515.js} +5 -5
  90. package/core/built/admin/assets/{chunk.178.19c31dac4249a6e158b6.js → chunk.178.09e01be47e7daa486981.js} +4 -4
  91. package/core/built/admin/assets/{chunk.664.0bd9e83a6b03130112ae.js → chunk.664.99d0951b803d7ec00d8b.js} +2 -2
  92. package/core/built/admin/assets/{ghost-971615b195b524bf97f678a350865195.js → ghost-90accaf45b85c7bc55ca781472d27e8e.js} +74 -91
  93. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +8 -8
  94. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +4 -4
  95. package/core/built/admin/index.html +4 -4
  96. package/core/frontend/helpers/excerpt.js +3 -2
  97. package/core/frontend/helpers/get.js +38 -22
  98. package/core/frontend/services/routing/controllers/previews.js +3 -2
  99. package/core/server/api/endpoints/newsletters.js +1 -2
  100. package/core/server/api/endpoints/stats.js +40 -5
  101. package/core/server/api/endpoints/utils/serializers/output/mappers/newsletters.js +12 -0
  102. package/core/server/services/explore/ExploreService.js +1 -1
  103. package/core/server/services/newsletters/NewslettersService.js +22 -3
  104. package/core/server/services/posts-public/service.js +4 -20
  105. package/core/server/services/segment/index.js +0 -14
  106. package/core/server/services/stats/service.js +26 -3
  107. package/core/server/services/tags-public/service.js +4 -19
  108. package/core/shared/labs.js +1 -1
  109. package/core/shared/sentry.js +28 -1
  110. package/package.json +150 -152
  111. package/yarn.lock +607 -1542
  112. package/components/tryghost-adapter-cache-memory-ttl-5.76.1.tgz +0 -0
  113. package/components/tryghost-adapter-cache-redis-5.76.1.tgz +0 -0
  114. package/components/tryghost-announcement-bar-settings-5.76.1.tgz +0 -0
  115. package/components/tryghost-api-framework-5.76.1.tgz +0 -0
  116. package/components/tryghost-api-version-compatibility-service-5.76.1.tgz +0 -0
  117. package/components/tryghost-audience-feedback-5.76.1.tgz +0 -0
  118. package/components/tryghost-bookshelf-repository-5.76.1.tgz +0 -0
  119. package/components/tryghost-bootstrap-socket-5.76.1.tgz +0 -0
  120. package/components/tryghost-collections-5.76.1.tgz +0 -0
  121. package/components/tryghost-constants-5.76.1.tgz +0 -0
  122. package/components/tryghost-custom-theme-settings-service-5.76.1.tgz +0 -0
  123. package/components/tryghost-donations-5.76.1.tgz +0 -0
  124. package/components/tryghost-dynamic-routing-events-5.76.1.tgz +0 -0
  125. package/components/tryghost-email-addresses-5.76.1.tgz +0 -0
  126. package/components/tryghost-email-analytics-service-5.76.1.tgz +0 -0
  127. package/components/tryghost-email-content-generator-5.76.1.tgz +0 -0
  128. package/components/tryghost-email-service-5.76.1.tgz +0 -0
  129. package/components/tryghost-email-suppression-list-5.76.1.tgz +0 -0
  130. package/components/tryghost-event-aware-cache-wrapper-5.76.1.tgz +0 -0
  131. package/components/tryghost-extract-api-key-5.76.1.tgz +0 -0
  132. package/components/tryghost-html-to-plaintext-5.76.1.tgz +0 -0
  133. package/components/tryghost-i18n-5.76.1.tgz +0 -0
  134. package/components/tryghost-importer-revue-5.76.1.tgz +0 -0
  135. package/components/tryghost-in-memory-repository-5.76.1.tgz +0 -0
  136. package/components/tryghost-link-redirects-5.76.1.tgz +0 -0
  137. package/components/tryghost-link-replacer-5.76.1.tgz +0 -0
  138. package/components/tryghost-link-tracking-5.76.1.tgz +0 -0
  139. package/components/tryghost-member-events-5.76.1.tgz +0 -0
  140. package/components/tryghost-members-api-5.76.1.tgz +0 -0
  141. package/components/tryghost-members-csv-5.76.1.tgz +0 -0
  142. package/components/tryghost-members-importer-5.76.1.tgz +0 -0
  143. package/components/tryghost-mentions-email-report-5.76.1.tgz +0 -0
  144. package/components/tryghost-milestones-5.76.1.tgz +0 -0
  145. package/components/tryghost-minifier-5.76.1.tgz +0 -0
  146. package/components/tryghost-model-to-domain-event-interceptor-5.76.1.tgz +0 -0
  147. package/components/tryghost-mw-api-version-mismatch-5.76.1.tgz +0 -0
  148. package/components/tryghost-mw-cache-control-5.76.1.tgz +0 -0
  149. package/components/tryghost-mw-session-from-token-5.76.1.tgz +0 -0
  150. package/components/tryghost-mw-update-user-last-seen-5.76.1.tgz +0 -0
  151. package/components/tryghost-mw-vhost-5.76.1.tgz +0 -0
  152. package/components/tryghost-nql-filter-expansions-5.76.1.tgz +0 -0
  153. package/components/tryghost-oembed-service-5.76.1.tgz +0 -0
  154. package/components/tryghost-post-events-5.76.1.tgz +0 -0
  155. package/components/tryghost-post-revisions-5.76.1.tgz +0 -0
  156. package/components/tryghost-posts-service-5.76.1.tgz +0 -0
  157. package/components/tryghost-recommendations-5.76.1.tgz +0 -0
  158. package/components/tryghost-referrers-5.76.1.tgz +0 -0
  159. package/components/tryghost-settings-path-manager-5.76.1.tgz +0 -0
  160. package/components/tryghost-slack-notifications-5.76.1.tgz +0 -0
  161. package/components/tryghost-stats-service-5.76.1.tgz +0 -0
  162. package/components/tryghost-tiers-5.76.1.tgz +0 -0
  163. package/components/tryghost-version-notifications-data-service-5.76.1.tgz +0 -0
  164. package/components/tryghost-webmentions-5.76.1.tgz +0 -0
  165. package/core/server/services/segment/ModelEventsAnalytics.js +0 -108
  166. /package/core/built/admin/assets/{chunk.664.0bd9e83a6b03130112ae.js.LICENSE.txt → chunk.664.99d0951b803d7ec00d8b.js.LICENSE.txt} +0 -0
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%225.76%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22ember-websockets%22%3A%7B%22socketIO%22%3Atrue%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%22c5245f84d0%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%22ace375b522%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%222814ff3e02%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.77%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22ember-websockets%22%3A%7B%22socketIO%22%3Atrue%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%22a9a58f16a8%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%2233d2c60ad0%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%22d07813452e%22%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -57,8 +57,8 @@
57
57
  <div id="ember-basic-dropdown-wormhole"></div>
58
58
 
59
59
  <script src="assets/vendor-6b83da0c6e6fede335947219c59e1543.js"></script>
60
- <script src="assets/chunk.664.0bd9e83a6b03130112ae.js"></script>
61
- <script src="assets/chunk.143.16780ac5d51ee6a46eb2.js"></script>
62
- <script src="assets/ghost-971615b195b524bf97f678a350865195.js"></script>
60
+ <script src="assets/chunk.664.99d0951b803d7ec00d8b.js"></script>
61
+ <script src="assets/chunk.143.16228699a88898175515.js"></script>
62
+ <script src="assets/ghost-90accaf45b85c7bc55ca781472d27e8e.js"></script>
63
63
  </body>
64
64
  </html>
@@ -22,7 +22,7 @@ module.exports = function excerpt(options) {
22
22
  } else {
23
23
  excerptText = '';
24
24
  }
25
-
25
+
26
26
  excerptText = _.escape(excerptText);
27
27
 
28
28
  truncateOptions = _.reduce(truncateOptions, (_truncateOptions, value, key) => {
@@ -32,8 +32,9 @@ module.exports = function excerpt(options) {
32
32
  return _truncateOptions;
33
33
  }, {});
34
34
 
35
+ // For custom excerpts, make sure we truncate them only based on length
35
36
  if (!_.isEmpty(this.custom_excerpt)) {
36
- truncateOptions.characters = this.custom_excerpt.length;
37
+ truncateOptions.characters = excerptText.length; // length is expanded by use of escaped characters
37
38
  if (truncateOptions.words) {
38
39
  delete truncateOptions.words;
39
40
  }
@@ -7,6 +7,7 @@ const {hbs} = require('../services/handlebars');
7
7
  const logging = require('@tryghost/logging');
8
8
  const errors = require('@tryghost/errors');
9
9
  const tpl = require('@tryghost/tpl');
10
+ const Sentry = require('@sentry/node');
10
11
 
11
12
  const _ = require('lodash');
12
13
  const jsonpath = require('jsonpath');
@@ -206,32 +207,47 @@ module.exports = async function get(resource, options) {
206
207
 
207
208
  // Parse the options we're going to pass to the API
208
209
  apiOptions = parseOptions(ghostGlobals, this, apiOptions);
210
+ let apiOptionsString = Object.entries(apiOptions)
211
+ .map(([key, value]) => ` ${key}="${value}"`)
212
+ .join('');
209
213
  apiOptions.context = {member: data.member};
210
-
211
214
  try {
212
- const response = await makeAPICall(resource, controllerName, action, apiOptions);
213
-
214
- // prepare data properties for use with handlebars
215
- if (response[resource] && response[resource].length) {
216
- response[resource].forEach(prepareContextResource);
217
- }
218
-
219
- // used for logging details of slow requests
220
- returnedRowsCount = response[resource] && response[resource].length;
221
-
222
- // block params allows the theme developer to name the data using something like
223
- // `{{#get "posts" as |result pageInfo|}}`
224
- const blockParams = [response[resource]];
225
- if (response.meta && response.meta.pagination) {
226
- response.pagination = response.meta.pagination;
227
- blockParams.push(response.meta.pagination);
228
- }
215
+ const spanName = `{{#get "${resource}"${apiOptionsString}}} ${data.member ? 'member' : 'public'}`;
216
+ const result = await Sentry.startSpan({
217
+ op: 'frontend.helpers.get',
218
+ name: spanName,
219
+ tags: {
220
+ resource,
221
+ ...apiOptions,
222
+ context: data.member ? 'member' : 'public'
223
+ }
224
+ }, async (span) => {
225
+ const response = await makeAPICall(resource, controllerName, action, apiOptions);
229
226
 
230
- // Call the main template function
231
- return options.fn(response, {
232
- data: data,
233
- blockParams: blockParams
227
+ // prepare data properties for use with handlebars
228
+ if (response[resource] && response[resource].length) {
229
+ response[resource].forEach(prepareContextResource);
230
+ }
231
+
232
+ // used for logging details of slow requests
233
+ returnedRowsCount = response[resource] && response[resource].length;
234
+ span?.setTag('returnedRows', returnedRowsCount);
235
+
236
+ // block params allows the theme developer to name the data using something like
237
+ // `{{#get "posts" as |result pageInfo|}}`
238
+ const blockParams = [response[resource]];
239
+ if (response.meta && response.meta.pagination) {
240
+ response.pagination = response.meta.pagination;
241
+ blockParams.push(response.meta.pagination);
242
+ }
243
+
244
+ // Call the main template function
245
+ return options.fn(response, {
246
+ data: data,
247
+ blockParams: blockParams
248
+ });
234
249
  });
250
+ return result;
235
251
  } catch (error) {
236
252
  logging.error(error);
237
253
  data.error = error.message;
@@ -49,12 +49,13 @@ module.exports = function previewController(req, res, next) {
49
49
  return next();
50
50
  }
51
51
 
52
+ // published content should only resolve to /:slug - /p/:uuid is for drafts only in lieu of an actual preview api
52
53
  if (post.status === 'published') {
53
54
  return urlUtils.redirect301(res, routerManager.getUrlByResourceId(post.id, {withSubdirectory: true}));
54
55
  }
55
56
 
56
- // published content should only resolve to /:slug or /email/:uuid - /p/:uuid is for drafts only in lieu of an actual preview api
57
- if (post.status !== 'published' && post.email_only === true) {
57
+ // once an email-only post has been sent it shouldn't be available via /p/ to avoid leaking members-only content
58
+ if (post.status === 'sent') {
58
59
  return urlUtils.redirect301(res, urlUtils.urlJoin('/email', post.uuid, '/'));
59
60
  }
60
61
 
@@ -1,4 +1,3 @@
1
- const models = require('../../models');
2
1
  const allowedIncludes = ['count.posts', 'count.members', 'count.active_members'];
3
2
 
4
3
  const newslettersService = require('../../services/newsletters');
@@ -27,7 +26,7 @@ module.exports = {
27
26
  },
28
27
  permissions: true,
29
28
  query(frame) {
30
- return models.Newsletter.findPage(frame.options);
29
+ return newslettersService.browse(frame.options);
31
30
  }
32
31
  },
33
32
 
@@ -10,8 +10,14 @@ module.exports = {
10
10
  docName: 'members',
11
11
  method: 'browse'
12
12
  },
13
+ cache: statsService.cache,
14
+ generateCacheKeyData() {
15
+ return {
16
+ method: 'memberCountHistory'
17
+ };
18
+ },
13
19
  async query() {
14
- return await statsService.getMemberCountHistory();
20
+ return await statsService.api.getMemberCountHistory();
15
21
  }
16
22
  },
17
23
  mrr: {
@@ -22,8 +28,14 @@ module.exports = {
22
28
  docName: 'members',
23
29
  method: 'browse'
24
30
  },
31
+ cache: statsService.cache,
32
+ generateCacheKeyData() {
33
+ return {
34
+ method: 'mrr'
35
+ };
36
+ },
25
37
  async query() {
26
- return await statsService.getMRRHistory();
38
+ return await statsService.api.getMRRHistory();
27
39
  }
28
40
  },
29
41
  subscriptions: {
@@ -34,8 +46,15 @@ module.exports = {
34
46
  docName: 'members',
35
47
  method: 'browse'
36
48
  },
49
+ cache: statsService.cache,
50
+ generateCacheKeyData() {
51
+ return {
52
+ method: 'subscriptions'
53
+
54
+ };
55
+ },
37
56
  async query() {
38
- return await statsService.getSubscriptionCountHistory();
57
+ return await statsService.api.getSubscriptionCountHistory();
39
58
  }
40
59
  },
41
60
  postReferrers: {
@@ -49,8 +68,18 @@ module.exports = {
49
68
  docName: 'posts',
50
69
  method: 'browse'
51
70
  },
71
+ cache: statsService.cache,
72
+ generateCacheKeyData(frame) {
73
+ return {
74
+ method: 'postReferrers',
75
+ data: {
76
+ id: frame.data.id
77
+ }
78
+
79
+ };
80
+ },
52
81
  async query(frame) {
53
- return await statsService.getPostReferrers(frame.data.id);
82
+ return await statsService.api.getPostReferrers(frame.data.id);
54
83
  }
55
84
  },
56
85
  referrersHistory: {
@@ -64,8 +93,14 @@ module.exports = {
64
93
  docName: 'posts',
65
94
  method: 'browse'
66
95
  },
96
+ cache: statsService.cache,
97
+ generateCacheKeyData() {
98
+ return {
99
+ method: 'referrersHistory'
100
+ };
101
+ },
67
102
  async query() {
68
- return await statsService.getReferrersHistory();
103
+ return await statsService.api.getReferrersHistory();
69
104
  }
70
105
  }
71
106
  };
@@ -1,4 +1,5 @@
1
1
  const utils = require('../../../index');
2
+ const emailAddressService = require('../../../../../../services/email-address');
2
3
 
3
4
  module.exports = (model, frame) => {
4
5
  const jsonModel = model.toJSON(frame.options);
@@ -19,6 +20,17 @@ module.exports = (model, frame) => {
19
20
  };
20
21
 
21
22
  return serialized;
23
+ } else {
24
+ if (jsonModel.sender_email && jsonModel.sender_reply_to === 'newsletter') {
25
+ // If sender_email is not allowed, we'll return it as the sender_reply_to instead, so we display the current situation correctly in the frontend
26
+ // If one of the properties was changed, we need to reset sender_email in case it was not changed but is invalid in the database
27
+ // which can happen after a config change (= auto correcting behaviour)
28
+ const validated = emailAddressService.service.validate(jsonModel.sender_email, 'from');
29
+ if (!validated.allowed) {
30
+ jsonModel.sender_reply_to = jsonModel.sender_email;
31
+ jsonModel.sender_email = null;
32
+ }
33
+ }
22
34
  }
23
35
 
24
36
  return jsonModel;
@@ -24,7 +24,7 @@ module.exports = class ExploreService {
24
24
  */
25
25
  async fetchData() {
26
26
  const totalMembers = await this.MembersService.stats.getTotalMembers();
27
- const mrrStats = await this.StatsService.getMRRHistory();
27
+ const mrrStats = await this.StatsService.api.getMRRHistory();
28
28
 
29
29
  const {description, icon, title, url, accent_color: accentColor, locale} = this.PublicConfigService.site;
30
30
 
@@ -92,7 +92,7 @@ class NewslettersService {
92
92
  * @public
93
93
  * @param {Object} options data (id, uuid, slug...)
94
94
  * @param {Object} [options] options
95
- * @returns {Promise<object>} JSONified Newsletter models
95
+ * @returns {Promise<object>}
96
96
  */
97
97
  async read(data, options = {}) {
98
98
  const newsletter = await this.NewsletterModel.findOne(data, options);
@@ -108,11 +108,14 @@ class NewslettersService {
108
108
  /**
109
109
  * @public
110
110
  * @param {Object} [options] options
111
- * @returns {Promise<object>} JSONified Newsletter models
111
+ * @returns {Promise<object>}
112
112
  */
113
113
  async browse(options = {}) {
114
- let newsletters = await this.NewsletterModel.findAll(options);
114
+ return await this.NewsletterModel.findPage(options);
115
+ }
115
116
 
117
+ async getAll(options = {}) {
118
+ const newsletters = await this.NewsletterModel.findAll(options);
116
119
  return newsletters.toJSON();
117
120
  }
118
121
 
@@ -298,6 +301,10 @@ class NewslettersService {
298
301
  }
299
302
 
300
303
  if (validated.verificationEmailRequired) {
304
+ if (type === 'replyTo' && email === newsletter.get('sender_email')) {
305
+ // This is some custom behaviour that allows swapping sender_email to sender_reply_to without requiring validation again
306
+ continue;
307
+ }
301
308
  delete cleanedAttrs[property];
302
309
  emailsToVerify.push({email, property});
303
310
  }
@@ -311,6 +318,18 @@ class NewslettersService {
311
318
  }
312
319
  }
313
320
 
321
+ // If one of the properties was changed, we need to reset sender_email in case it was not changed but is invalid in the database
322
+ // which can happen after a config change (= auto correcting behaviour)
323
+ const didChangeReplyTo = newsletter && attrs.sender_reply_to !== undefined && newsletter.get('sender_reply_to') !== attrs.sender_reply_to;
324
+ const didChangeSenderEmail = newsletter && (attrs.sender_email !== undefined && newsletter.get('sender_email') !== attrs.sender_email);
325
+ if (didChangeReplyTo && !didChangeSenderEmail && newsletter.get('sender_email')) {
326
+ const validated = this.emailAddressService.service.validate(newsletter.get('sender_email'), 'from');
327
+ if (!validated.allowed) {
328
+ logging.info(`Resetting sender_email for newsletter ${newsletter.id} because it became invalid`);
329
+ cleanedAttrs.sender_email = null;
330
+ }
331
+ }
332
+
314
333
  return {cleanedAttrs, emailsToVerify};
315
334
  }
316
335
 
@@ -8,34 +8,18 @@ class PostsPublicServiceWrapper {
8
8
  // Wire up all the dependencies
9
9
  const adapterManager = require('../adapter-manager');
10
10
  const config = require('../../../shared/config');
11
- const EventAwareCacheWrapper = require('@tryghost/event-aware-cache-wrapper');
12
11
  const EventRegistry = require('../../lib/common/events');
13
12
 
14
13
  let postsCache;
15
14
  if (config.get('hostSettings:postsPublicCache:enabled')) {
16
- const cache = adapterManager.getAdapter('cache:postsPublic');
17
- postsCache = new EventAwareCacheWrapper({
18
- cache: cache,
19
- resetEvents: ['site.changed'],
20
- eventRegistry: EventRegistry
15
+ postsCache = adapterManager.getAdapter('cache:postsPublic');
16
+ EventRegistry.on('site.changed', () => {
17
+ postsCache.reset();
21
18
  });
22
19
  }
23
20
 
24
- let cache;
25
- if (postsCache) {
26
- // @NOTE: exposing cache through getter and setter to not loose the context of "this"
27
- cache = {
28
- get() {
29
- return postsCache.get(...arguments);
30
- },
31
- set() {
32
- return postsCache.set(...arguments);
33
- }
34
- };
35
- }
36
-
37
21
  this.api = {
38
- cache: cache
22
+ cache: postsCache
39
23
  };
40
24
  }
41
25
  }
@@ -3,9 +3,7 @@ const config = require('../../../shared/config');
3
3
  const sentry = require('../../../shared/sentry');
4
4
  const logging = require('@tryghost/logging');
5
5
  const DomainEvents = require('@tryghost/domain-events');
6
- const events = require('../../lib/common/events');
7
6
 
8
- const ModelEventsAnalytics = require('./ModelEventsAnalytics');
9
7
  const DomainEventsAnalytics = require('./DomainEventsAnalytics');
10
8
 
11
9
  module.exports.init = function () {
@@ -22,18 +20,6 @@ module.exports.init = function () {
22
20
  logging
23
21
  });
24
22
 
25
- const modelEventsAnalytics = new ModelEventsAnalytics({
26
- analytics,
27
- trackDefaults,
28
- prefix,
29
- exceptionHandler: sentry,
30
- events,
31
- logging
32
- });
33
-
34
- // Listen to model events
35
- modelEventsAnalytics.subscribeToEvents();
36
-
37
23
  // Listen to domain events
38
24
  subscribeToDomainEvents.subscribeToEvents();
39
25
  };
@@ -1,4 +1,27 @@
1
- const StatsService = require('@tryghost/stats-service');
2
- const db = require('../../data/db');
1
+ class StatsServiceWrapper {
2
+ constructor() {
3
+ this.api = null;
4
+ this.cache = null;
5
+ }
3
6
 
4
- module.exports = StatsService.create({knex: db.knex});
7
+ async init() {
8
+ if (this.api) {
9
+ // Already done
10
+ return;
11
+ }
12
+
13
+ const StatsService = require('@tryghost/stats-service');
14
+ const db = require('../../data/db');
15
+
16
+ this.api = StatsService.create({knex: db.knex});
17
+
18
+ const adapterManager = require('../adapter-manager');
19
+ const config = require('../../../shared/config');
20
+
21
+ if (config.get('hostSettings:statsCache:enabled')) {
22
+ this.cache = adapterManager.getAdapter('cache:stats');
23
+ }
24
+ }
25
+ }
26
+
27
+ module.exports = new StatsServiceWrapper();
@@ -8,33 +8,18 @@ class TagsPublicServiceWrapper {
8
8
  // Wire up all the dependencies
9
9
  const adapterManager = require('../adapter-manager');
10
10
  const config = require('../../../shared/config');
11
- const EventAwareCacheWrapper = require('@tryghost/event-aware-cache-wrapper');
12
11
  const EventRegistry = require('../../lib/common/events');
13
12
 
14
13
  let tagsCache;
15
14
  if (config.get('hostSettings:tagsPublicCache:enabled')) {
16
- let tagsPublicCache = adapterManager.getAdapter('cache:tagsPublic');
17
- tagsCache = new EventAwareCacheWrapper({
18
- cache: tagsPublicCache,
19
- resetEvents: ['site.changed'],
20
- eventRegistry: EventRegistry
15
+ tagsCache = adapterManager.getAdapter('cache:tagsPublic');
16
+ EventRegistry.on('site.changed', () => {
17
+ tagsCache.reset();
21
18
  });
22
19
  }
23
20
 
24
- let cache;
25
- if (tagsCache) {
26
- // @NOTE: exposing cache through getter and setter to not loose the context of "this"
27
- cache = {
28
- get() {
29
- return tagsCache.get(...arguments);
30
- },
31
- set() {
32
- return tagsCache.set(...arguments);
33
- }
34
- };
35
- }
36
21
  this.api = {
37
- cache: cache
22
+ cache: tagsCache
38
23
  };
39
24
  }
40
25
  }
@@ -44,7 +44,7 @@ const ALPHA_FEATURES = [
44
44
  'tipsAndDonations',
45
45
  'importMemberTier',
46
46
  'lexicalIndicators',
47
- 'adminXOffers',
47
+ // 'adminXOffers',
48
48
  'filterEmailDisabled',
49
49
  'adminXDemo',
50
50
  'newEmailAddresses',
@@ -57,6 +57,31 @@ const beforeSend = function (event, hint) {
57
57
  }
58
58
  };
59
59
 
60
+ const ALLOWED_HTTP_TRANSACTIONS = [
61
+ '/ghost/api', // any Ghost API call
62
+ '/members/api', // any Members API call
63
+ '/:slug', // any frontend post/page
64
+ '/author', // any frontend author page
65
+ '/tag' // any frontend tag page
66
+ ].map((path) => {
67
+ // Sentry names HTTP transactions like: "<HTTP_METHOD> <PATH>" i.e. "GET /ghost/api/content/settings"
68
+ // Match any of the paths above with any HTTP method, and also the homepage "GET /"
69
+ return new RegExp(`^(GET|POST|PUT|DELETE)\\s(?<path>${path}\\/.+|\\/$)`);
70
+ });
71
+
72
+ const beforeSendTransaction = function (event) {
73
+ // Drop transactions that are not in the allowed list
74
+ for (const transaction of ALLOWED_HTTP_TRANSACTIONS) {
75
+ const match = event.transaction.match(transaction);
76
+
77
+ if (match?.groups?.path) {
78
+ return event;
79
+ }
80
+ }
81
+
82
+ return null;
83
+ };
84
+
60
85
  if (sentryConfig && !sentryConfig.disabled) {
61
86
  const Sentry = require('@sentry/node');
62
87
  const version = require('@tryghost/version').full;
@@ -72,7 +97,8 @@ if (sentryConfig && !sentryConfig.disabled) {
72
97
  environment: environment,
73
98
  maxValueLength: 1000,
74
99
  integrations: [],
75
- beforeSend
100
+ beforeSend,
101
+ beforeSendTransaction
76
102
  };
77
103
 
78
104
  // Enable tracing if sentry.tracing.enabled is true
@@ -117,6 +143,7 @@ if (sentryConfig && !sentryConfig.disabled) {
117
143
  captureException: Sentry.captureException,
118
144
  captureMessage: Sentry.captureMessage,
119
145
  beforeSend: beforeSend,
146
+ beforeSendTransaction: beforeSendTransaction,
120
147
  initQueryTracing: (knex) => {
121
148
  if (sentryConfig.tracing?.enabled === true) {
122
149
  const integration = new SentryKnexTracingIntegration(knex);