ghost 5.37.0 → 5.38.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 (154) hide show
  1. package/components/{tryghost-adapter-cache-memory-ttl-5.37.0.tgz → tryghost-adapter-cache-memory-ttl-5.38.0.tgz} +0 -0
  2. package/components/tryghost-adapter-cache-redis-5.38.0.tgz +0 -0
  3. package/components/{tryghost-adapter-manager-5.37.0.tgz → tryghost-adapter-manager-5.38.0.tgz} +0 -0
  4. package/components/{tryghost-api-framework-5.37.0.tgz → tryghost-api-framework-5.38.0.tgz} +0 -0
  5. package/components/{tryghost-api-version-compatibility-service-5.37.0.tgz → tryghost-api-version-compatibility-service-5.38.0.tgz} +0 -0
  6. package/components/tryghost-audience-feedback-5.38.0.tgz +0 -0
  7. package/components/tryghost-bootstrap-socket-5.38.0.tgz +0 -0
  8. package/components/{tryghost-constants-5.37.0.tgz → tryghost-constants-5.38.0.tgz} +0 -0
  9. package/components/tryghost-custom-theme-settings-service-5.38.0.tgz +0 -0
  10. package/components/{tryghost-data-generator-5.37.0.tgz → tryghost-data-generator-5.38.0.tgz} +0 -0
  11. package/components/tryghost-domain-events-5.38.0.tgz +0 -0
  12. package/components/tryghost-dynamic-routing-events-5.38.0.tgz +0 -0
  13. package/components/tryghost-email-analytics-provider-mailgun-5.38.0.tgz +0 -0
  14. package/components/{tryghost-email-analytics-service-5.37.0.tgz → tryghost-email-analytics-service-5.38.0.tgz} +0 -0
  15. package/components/tryghost-email-content-generator-5.38.0.tgz +0 -0
  16. package/components/tryghost-email-events-5.38.0.tgz +0 -0
  17. package/components/tryghost-email-service-5.38.0.tgz +0 -0
  18. package/components/{tryghost-email-suppression-list-5.37.0.tgz → tryghost-email-suppression-list-5.38.0.tgz} +0 -0
  19. package/components/tryghost-event-aware-cache-wrapper-5.38.0.tgz +0 -0
  20. package/components/{tryghost-express-dynamic-redirects-5.37.0.tgz → tryghost-express-dynamic-redirects-5.38.0.tgz} +0 -0
  21. package/components/tryghost-external-media-inliner-5.38.0.tgz +0 -0
  22. package/components/tryghost-extract-api-key-5.38.0.tgz +0 -0
  23. package/components/tryghost-html-to-plaintext-5.38.0.tgz +0 -0
  24. package/components/tryghost-i18n-5.38.0.tgz +0 -0
  25. package/components/{tryghost-importer-handler-content-files-5.37.0.tgz → tryghost-importer-handler-content-files-5.38.0.tgz} +0 -0
  26. package/components/tryghost-importer-revue-5.38.0.tgz +0 -0
  27. package/components/tryghost-job-manager-5.38.0.tgz +0 -0
  28. package/components/tryghost-link-redirects-5.38.0.tgz +0 -0
  29. package/components/tryghost-link-replacer-5.38.0.tgz +0 -0
  30. package/components/{tryghost-link-tracking-5.37.0.tgz → tryghost-link-tracking-5.38.0.tgz} +0 -0
  31. package/components/{tryghost-magic-link-5.37.0.tgz → tryghost-magic-link-5.38.0.tgz} +0 -0
  32. package/components/tryghost-mailgun-client-5.38.0.tgz +0 -0
  33. package/components/tryghost-member-attribution-5.38.0.tgz +0 -0
  34. package/components/tryghost-member-events-5.38.0.tgz +0 -0
  35. package/components/tryghost-members-api-5.38.0.tgz +0 -0
  36. package/components/tryghost-members-csv-5.38.0.tgz +0 -0
  37. package/components/{tryghost-members-events-service-5.37.0.tgz → tryghost-members-events-service-5.38.0.tgz} +0 -0
  38. package/components/{tryghost-members-importer-5.37.0.tgz → tryghost-members-importer-5.38.0.tgz} +0 -0
  39. package/components/tryghost-members-offers-5.38.0.tgz +0 -0
  40. package/components/tryghost-members-payments-5.38.0.tgz +0 -0
  41. package/components/tryghost-members-ssr-5.38.0.tgz +0 -0
  42. package/components/{tryghost-members-stripe-service-5.37.0.tgz → tryghost-members-stripe-service-5.38.0.tgz} +0 -0
  43. package/components/tryghost-milestones-5.38.0.tgz +0 -0
  44. package/components/tryghost-minifier-5.38.0.tgz +0 -0
  45. package/components/tryghost-mw-api-version-mismatch-5.38.0.tgz +0 -0
  46. package/components/{tryghost-mw-cache-control-5.37.0.tgz → tryghost-mw-cache-control-5.38.0.tgz} +0 -0
  47. package/components/tryghost-mw-error-handler-5.38.0.tgz +0 -0
  48. package/components/tryghost-mw-session-from-token-5.38.0.tgz +0 -0
  49. package/components/tryghost-mw-update-user-last-seen-5.38.0.tgz +0 -0
  50. package/components/tryghost-mw-version-match-5.38.0.tgz +0 -0
  51. package/components/tryghost-mw-vhost-5.38.0.tgz +0 -0
  52. package/components/tryghost-oembed-service-5.38.0.tgz +0 -0
  53. package/components/tryghost-package-json-5.38.0.tgz +0 -0
  54. package/components/{tryghost-referrers-5.37.0.tgz → tryghost-referrers-5.38.0.tgz} +0 -0
  55. package/components/tryghost-security-5.38.0.tgz +0 -0
  56. package/components/tryghost-session-service-5.38.0.tgz +0 -0
  57. package/components/tryghost-settings-path-manager-5.38.0.tgz +0 -0
  58. package/components/tryghost-slack-notifications-5.38.0.tgz +0 -0
  59. package/components/tryghost-staff-service-5.38.0.tgz +0 -0
  60. package/components/tryghost-stats-service-5.38.0.tgz +0 -0
  61. package/components/tryghost-tiers-5.38.0.tgz +0 -0
  62. package/components/{tryghost-update-check-service-5.37.0.tgz → tryghost-update-check-service-5.38.0.tgz} +0 -0
  63. package/components/tryghost-verification-trigger-5.38.0.tgz +0 -0
  64. package/components/{tryghost-version-notifications-data-service-5.37.0.tgz → tryghost-version-notifications-data-service-5.38.0.tgz} +0 -0
  65. package/components/tryghost-webmentions-5.38.0.tgz +0 -0
  66. package/core/boot.js +11 -4
  67. package/core/built/admin/assets/{chunk.143.27cd10a38f877e715b35.js → chunk.143.c6802c882a911797ce4f.js} +6 -6
  68. package/core/built/admin/assets/{chunk.178.dd6cf17fb0986acf19d6.js → chunk.178.09faefd4027fcba4113d.js} +4 -4
  69. package/core/built/admin/assets/{chunk.652.bb618bc5abf23bed4e87.js → chunk.220.9ca2950240aba3fced21.js} +1836 -1774
  70. package/core/built/admin/assets/{chunk.79.4a959c324df25480b90e.js → chunk.79.acb7dd01e1c785f4920c.js} +12 -11
  71. package/core/built/admin/assets/{ghost-2948791640be026b987b88f89034bc85.js → ghost-35103ff053c43f1dfa7f35821c3c2412.js} +29 -29
  72. package/core/built/admin/assets/{ghost-efbe4dcc249d119a955b038aae5c980d.css → ghost-a9307c9cfe26a4bc621e02cd3bae421a.css} +1 -1
  73. package/core/built/admin/assets/{ghost-dark-6ea4b338f17a43c204b7c1e207b90cd7.css → ghost-dark-f309cf445255344e4861a95ecb8f1920.css} +1 -1
  74. package/core/built/admin/assets/vendor-b982e3bf1020bff77b2a3c44d5f59e55.js +269 -269
  75. package/core/built/admin/index.html +5 -5
  76. package/core/frontend/helpers/ghost_head.js +4 -1
  77. package/core/frontend/services/routing/StaticPagesRouter.js +1 -1
  78. package/core/frontend/services/sitemap/base-generator.js +5 -1
  79. package/core/server/adapters/storage/LocalImagesStorage.js +1 -1
  80. package/core/server/api/endpoints/email-previews.js +2 -43
  81. package/core/server/api/endpoints/emails.js +1 -22
  82. package/core/server/api/endpoints/utils/serializers/output/mappers/emails.js +14 -8
  83. package/core/server/data/importer/import-manager.js +8 -1
  84. package/core/server/data/migrations/versions/4.9/05-fix-missed-mobiledoc-url-transforms.js +1 -1
  85. package/core/server/lib/common/events.js +16 -23
  86. package/core/server/models/base/plugins/relations.js +5 -3
  87. package/core/server/models/index.js +5 -0
  88. package/core/server/services/comments/emails.js +2 -2
  89. package/core/server/services/email-service/wrapper.js +2 -0
  90. package/core/server/services/link-tracking/LinkClickRepository.js +1 -1
  91. package/core/server/services/media-inliner/service.js +49 -3
  92. package/core/server/services/mentions/service.js +6 -1
  93. package/core/server/services/posts/posts-service.js +3 -14
  94. package/core/server/services/staff/index.js +2 -0
  95. package/core/server/services/url/Urls.js +10 -2
  96. package/core/shared/labs.js +0 -1
  97. package/package.json +138 -138
  98. package/yarn.lock +267 -259
  99. package/components/tryghost-adapter-cache-redis-5.37.0.tgz +0 -0
  100. package/components/tryghost-audience-feedback-5.37.0.tgz +0 -0
  101. package/components/tryghost-bootstrap-socket-5.37.0.tgz +0 -0
  102. package/components/tryghost-custom-theme-settings-service-5.37.0.tgz +0 -0
  103. package/components/tryghost-domain-events-5.37.0.tgz +0 -0
  104. package/components/tryghost-dynamic-routing-events-5.37.0.tgz +0 -0
  105. package/components/tryghost-email-analytics-provider-mailgun-5.37.0.tgz +0 -0
  106. package/components/tryghost-email-content-generator-5.37.0.tgz +0 -0
  107. package/components/tryghost-email-events-5.37.0.tgz +0 -0
  108. package/components/tryghost-email-service-5.37.0.tgz +0 -0
  109. package/components/tryghost-event-aware-cache-wrapper-5.37.0.tgz +0 -0
  110. package/components/tryghost-external-media-inliner-5.37.0.tgz +0 -0
  111. package/components/tryghost-extract-api-key-5.37.0.tgz +0 -0
  112. package/components/tryghost-html-to-plaintext-5.37.0.tgz +0 -0
  113. package/components/tryghost-i18n-5.37.0.tgz +0 -0
  114. package/components/tryghost-importer-revue-5.37.0.tgz +0 -0
  115. package/components/tryghost-job-manager-5.37.0.tgz +0 -0
  116. package/components/tryghost-link-redirects-5.37.0.tgz +0 -0
  117. package/components/tryghost-link-replacer-5.37.0.tgz +0 -0
  118. package/components/tryghost-mailgun-client-5.37.0.tgz +0 -0
  119. package/components/tryghost-member-attribution-5.37.0.tgz +0 -0
  120. package/components/tryghost-member-events-5.37.0.tgz +0 -0
  121. package/components/tryghost-members-api-5.37.0.tgz +0 -0
  122. package/components/tryghost-members-csv-5.37.0.tgz +0 -0
  123. package/components/tryghost-members-offers-5.37.0.tgz +0 -0
  124. package/components/tryghost-members-payments-5.37.0.tgz +0 -0
  125. package/components/tryghost-members-ssr-5.37.0.tgz +0 -0
  126. package/components/tryghost-milestones-5.37.0.tgz +0 -0
  127. package/components/tryghost-minifier-5.37.0.tgz +0 -0
  128. package/components/tryghost-mw-api-version-mismatch-5.37.0.tgz +0 -0
  129. package/components/tryghost-mw-error-handler-5.37.0.tgz +0 -0
  130. package/components/tryghost-mw-session-from-token-5.37.0.tgz +0 -0
  131. package/components/tryghost-mw-update-user-last-seen-5.37.0.tgz +0 -0
  132. package/components/tryghost-mw-version-match-5.37.0.tgz +0 -0
  133. package/components/tryghost-mw-vhost-5.37.0.tgz +0 -0
  134. package/components/tryghost-oembed-service-5.37.0.tgz +0 -0
  135. package/components/tryghost-package-json-5.37.0.tgz +0 -0
  136. package/components/tryghost-security-5.37.0.tgz +0 -0
  137. package/components/tryghost-session-service-5.37.0.tgz +0 -0
  138. package/components/tryghost-settings-path-manager-5.37.0.tgz +0 -0
  139. package/components/tryghost-slack-notifications-5.37.0.tgz +0 -0
  140. package/components/tryghost-staff-service-5.37.0.tgz +0 -0
  141. package/components/tryghost-stats-service-5.37.0.tgz +0 -0
  142. package/components/tryghost-tiers-5.37.0.tgz +0 -0
  143. package/components/tryghost-verification-trigger-5.37.0.tgz +0 -0
  144. package/components/tryghost-webmentions-5.37.0.tgz +0 -0
  145. package/core/server/services/bulk-email/bulk-email-processor.js +0 -289
  146. package/core/server/services/bulk-email/index.js +0 -1
  147. package/core/server/services/mega/email-preview.js +0 -54
  148. package/core/server/services/mega/feedback-buttons.js +0 -66
  149. package/core/server/services/mega/index.js +0 -14
  150. package/core/server/services/mega/mega.js +0 -626
  151. package/core/server/services/mega/post-email-serializer.js +0 -559
  152. package/core/server/services/mega/segment-parser.js +0 -20
  153. package/core/server/services/mega/template.js +0 -1319
  154. /package/core/built/admin/assets/{chunk.652.bb618bc5abf23bed4e87.js.LICENSE.txt → chunk.220.9ca2950240aba3fced21.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%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.37%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%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%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.38%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%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-3e6947aa681f0fb82b193090e520dc73.css">
40
- <link integrity="" rel="stylesheet" href="assets/ghost-efbe4dcc249d119a955b038aae5c980d.css" title="light">
40
+ <link integrity="" rel="stylesheet" href="assets/ghost-a9307c9cfe26a4bc621e02cd3bae421a.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-b982e3bf1020bff77b2a3c44d5f59e55.js"></script>
60
- <script src="assets/chunk.652.bb618bc5abf23bed4e87.js"></script>
61
- <script src="assets/chunk.143.27cd10a38f877e715b35.js"></script>
62
- <script src="assets/ghost-2948791640be026b987b88f89034bc85.js"></script>
60
+ <script src="assets/chunk.220.9ca2950240aba3fced21.js"></script>
61
+ <script src="assets/chunk.143.c6802c882a911797ce4f.js"></script>
62
+ <script src="assets/ghost-35103ff053c43f1dfa7f35821c3c2412.js"></script>
63
63
  </body>
64
64
  </html>
@@ -64,7 +64,10 @@ function getMembersHelper(data, frontendKey) {
64
64
  let membersHelper = `<script defer src="${scriptUrl}" ${dataAttributes} crossorigin="anonymous"></script>`;
65
65
  membersHelper += (`<style id="gh-members-styles">${templateStyles}</style>`);
66
66
  if (settingsCache.get('paid_members_enabled')) {
67
- membersHelper += '<script async src="https://js.stripe.com/v3/"></script>';
67
+ // disable fraud detection for e2e tests to reduce waiting time
68
+ const isFraudSignalsEnabled = process.env.NODE_ENV === 'testing-browser' ? '?advancedFraudSignals=false' : '';
69
+
70
+ membersHelper += `<script async src="https://js.stripe.com/v3/${isFraudSignalsEnabled}"></script>`;
68
71
  }
69
72
  return membersHelper;
70
73
  }
@@ -52,7 +52,7 @@ class StaticPagesRouter extends ParentRouter {
52
52
  }
53
53
 
54
54
  /**
55
- * @description Prepare context for futher middleware/controllers.
55
+ * @description Prepare context for further middleware/controllers.
56
56
  * @param {Object} req
57
57
  * @param {Object} res
58
58
  * @param {Function} next
@@ -22,6 +22,10 @@ class BaseSiteMapGenerator {
22
22
  this.maxPerPage = 50000;
23
23
  }
24
24
 
25
+ hasCanonicalUrl(datum) {
26
+ return Boolean(datum?.canonical_url);
27
+ }
28
+
25
29
  generateXmlFromNodes(page) {
26
30
  // Get a mapping of node to timestamp
27
31
  let nodesToProcess = _.map(this.nodeLookup, (node, id) => {
@@ -75,7 +79,7 @@ class BaseSiteMapGenerator {
75
79
  addUrl(url, datum) {
76
80
  const node = this.createUrlNodeFromDatum(url, datum);
77
81
 
78
- if (node) {
82
+ if (node && !this.hasCanonicalUrl(datum)) {
79
83
  this.updateLastModified(datum);
80
84
  this.updateLookups(datum, node);
81
85
  // force regeneration of xml
@@ -27,7 +27,7 @@ class LocalImagesStorage extends LocalStorageBase {
27
27
  /**
28
28
  * Saves a buffer in the targetPath
29
29
  * @param {Buffer} buffer is an instance of Buffer
30
- * @param {String} targetPath path to which the buffer should be written
30
+ * @param {String} targetPath relative path NOT including storage path to which the buffer should be written
31
31
  * @returns {Promise<String>} a URL to retrieve the data
32
32
  */
33
33
  async saveRaw(buffer, targetPath) {
@@ -1,14 +1,4 @@
1
- const models = require('../../models');
2
- const tpl = require('@tryghost/tpl');
3
- const errors = require('@tryghost/errors');
4
- const mega = require('../../services/mega');
5
1
  const emailService = require('../../services/email-service');
6
- const labs = require('../../../shared/labs');
7
- const messages = {
8
- postNotFound: 'Post not found.'
9
- };
10
-
11
- const emailPreview = new mega.EmailPreview();
12
2
 
13
3
  module.exports = {
14
4
  docName: 'email_previews',
@@ -30,25 +20,7 @@ module.exports = {
30
20
  ],
31
21
  permissions: true,
32
22
  async query(frame) {
33
- if (labs.isSet('emailStability')) {
34
- return await emailService.controller.previewEmail(frame);
35
- }
36
-
37
- const options = Object.assign(frame.options, {formats: 'html,plaintext', withRelated: ['authors', 'posts_meta']});
38
- const data = Object.assign(frame.data, {status: 'all'});
39
-
40
- const model = await models.Post.findOne(data, options);
41
-
42
- if (!model) {
43
- throw new errors.NotFoundError({
44
- message: tpl(messages.postNotFound)
45
- });
46
- }
47
-
48
- return emailPreview.generateEmailContent(model, {
49
- newsletter: frame.options.newsletter,
50
- memberSegment: frame.options.memberSegment
51
- });
23
+ return await emailService.controller.previewEmail(frame);
52
24
  }
53
25
  },
54
26
  sendTestEmail: {
@@ -66,20 +38,7 @@ module.exports = {
66
38
  },
67
39
  permissions: true,
68
40
  async query(frame) {
69
- if (labs.isSet('emailStability')) {
70
- return await emailService.controller.sendTestEmail(frame);
71
- }
72
-
73
- const options = Object.assign(frame.options, {status: 'all'});
74
- let model = await models.Post.findOne(options, {withRelated: ['authors']});
75
-
76
- if (!model) {
77
- throw new errors.NotFoundError({
78
- message: tpl(messages.postNotFound)
79
- });
80
- }
81
- const {emails = [], memberSegment, newsletter = ''} = frame.data;
82
- return await mega.mega.sendTestEmail(model, emails, memberSegment, newsletter);
41
+ return await emailService.controller.sendTestEmail(frame);
83
42
  }
84
43
  }
85
44
  };
@@ -1,9 +1,7 @@
1
1
  const models = require('../../models');
2
2
  const tpl = require('@tryghost/tpl');
3
3
  const errors = require('@tryghost/errors');
4
- const megaService = require('../../services/mega');
5
4
  const emailService = require('../../services/email-service');
6
- const labs = require('../../../shared/labs');
7
5
  const emailAnalytics = require('../../services/email-analytics');
8
6
 
9
7
  const messages = {
@@ -63,27 +61,8 @@ module.exports = {
63
61
  'id'
64
62
  ],
65
63
  permissions: true,
66
- // (complexity removed with new labs flag)
67
- // eslint-disable-next-line ghost/ghost-custom/max-api-complexity
68
64
  async query(frame) {
69
- if (labs.isSet('emailStability')) {
70
- return await emailService.controller.retryFailedEmail(frame);
71
- }
72
-
73
- const model = await models.Email.findOne(frame.data, frame.options);
74
- if (!model) {
75
- throw new errors.NotFoundError({
76
- message: tpl(messages.emailNotFound)
77
- });
78
- }
79
-
80
- if (model.get('status') !== 'failed') {
81
- throw new errors.IncorrectUsageError({
82
- message: tpl(messages.retryNotAllowed)
83
- });
84
- }
85
-
86
- return await megaService.mega.retryFailedEmail(model);
65
+ return await emailService.controller.retryFailedEmail(frame);
87
66
  }
88
67
  },
89
68
 
@@ -1,19 +1,22 @@
1
- const mega = require('../../../../../../services/mega');
2
1
  const labs = require('../../../../../../../shared/labs');
3
2
  const config = require('../../../../../../../shared/config');
3
+ const emailService = require('../../../../../../services/email-service');
4
4
 
5
5
  module.exports = (model, frame) => {
6
6
  const jsonModel = model.toJSON ? model.toJSON(frame.options) : model;
7
7
 
8
8
  // Ensure we're not outputting unwanted replacement strings when viewing email contents
9
9
  // TODO: extract this to a utility, it's duplicated in the email-preview API controller
10
- const replacements = mega.postEmailSerializer.parseReplacements(jsonModel);
11
- replacements.forEach((replacement) => {
12
- jsonModel[replacement.format] = jsonModel[replacement.format].replace(
13
- replacement.regexp,
14
- replacement.fallback || ''
15
- );
16
- });
10
+ if (jsonModel.html) {
11
+ const replacements = emailService.renderer.buildReplacementDefinitions({html: jsonModel.html, newsletterUuid: 'preview'});
12
+ const exampleMember = emailService.service.getDefaultExampleMember();
13
+
14
+ jsonModel.html = emailService.service.replaceDefinitions(jsonModel.html, replacements, exampleMember);
15
+
16
+ if (jsonModel.plaintext) {
17
+ jsonModel.plaintext = emailService.service.replaceDefinitions(jsonModel.plaintext, replacements, exampleMember);
18
+ }
19
+ }
17
20
 
18
21
  if (!labs.isSet('emailErrors') && !!(config.get('bulkEmail') && config.get('bulkEmail').mailgun)) {
19
22
  if (jsonModel.status === 'failed') {
@@ -21,5 +24,8 @@ module.exports = (model, frame) => {
21
24
  }
22
25
  }
23
26
 
27
+ // Removed loaded post relation if set
28
+ delete jsonModel.post;
29
+
24
30
  return jsonModel;
25
31
  };
@@ -301,7 +301,14 @@ class ImportManager {
301
301
  const baseDir = this.getBaseDirectory(zipDirectory);
302
302
 
303
303
  for (const handler of this.handlers) {
304
- const files = this.getFilesFromZip(handler, zipDirectory);
304
+ let files = [];
305
+ if (handler.directories?.length > 0) {
306
+ for (const dir of handler.directories) {
307
+ files.push(...this.getFilesFromZip(handler, path.join(zipDirectory, (baseDir || ''), dir)));
308
+ }
309
+ } else {
310
+ files.push(...this.getFilesFromZip(handler, zipDirectory));
311
+ }
305
312
 
306
313
  debug('handler', handler.type, files);
307
314
 
@@ -4,7 +4,7 @@ const htmlToPlaintext = require('@tryghost/html-to-plaintext');
4
4
  const mobiledocLib = require('../../../../lib/mobiledoc');
5
5
  const {createTransactionalMigration} = require('../../utils');
6
6
 
7
- // in Ghost versions 4.6.1-4.8.4 the 4.0 migration that transfored URLs had a bug
7
+ // in Ghost versions 4.6.1-4.8.4 the 4.0 migration that transformed URLs had a bug
8
8
  // that meant urls inside cards in mobiledoc content was not being transformed
9
9
  //
10
10
  // if the migrations table indicates an upgrade was made from 3.x to 4.6-4.8 then
@@ -12,29 +12,22 @@
12
12
  */
13
13
 
14
14
  const events = require('events');
15
- const util = require('util');
16
- let EventRegistry;
17
- let EventRegistryInstance;
18
15
 
19
- EventRegistry = function () {
20
- events.EventEmitter.call(this);
21
- };
16
+ class EventRegistry extends events.EventEmitter {
17
+ /**
18
+ * This is method is semi-hack to make sure listeners are only registered once
19
+ * during the lifetime of the process. And example problem it solves is
20
+ * registering duplicate listeners between Ghost instance reboots when running tests.
21
+ * @param {String} eventName
22
+ * @param {String} listenerName named function name registered as a listener for the event
23
+ * @returns {Boolean}
24
+ */
25
+ hasRegisteredListener(eventName, listenerName) {
26
+ return !!(this.listeners(eventName).find(listener => (listener.name === listenerName)));
27
+ }
28
+ }
22
29
 
23
- util.inherits(EventRegistry, events.EventEmitter);
30
+ const eventRegistryInstance = new EventRegistry();
31
+ eventRegistryInstance.setMaxListeners(100);
24
32
 
25
- /**
26
- * This is method is semi-hack to make sure listeners are only registered once
27
- * during the lifetime of the process. And example problem it solves is
28
- * registering duplicate listeners between Ghost instance reboots when running tests.
29
- * @param {String} eventName
30
- * @param {String} listenerName named function name registered as a listener for the event
31
- * @returns {Boolean}
32
- */
33
- EventRegistry.prototype.hasRegisteredListener = function (eventName, listenerName) {
34
- return !!(this.listeners(eventName).find(listener => (listener.name === listenerName)));
35
- };
36
-
37
- EventRegistryInstance = new EventRegistry();
38
- EventRegistryInstance.setMaxListeners(100);
39
-
40
- module.exports = EventRegistryInstance;
33
+ module.exports = eventRegistryInstance;
@@ -7,7 +7,7 @@ module.exports = function (Bookshelf) {
7
7
  * Return a relation, and load it if it hasn't been loaded already (or force a refresh with the forceRefresh option).
8
8
  * refs https://github.com/TryGhost/Team/issues/1626
9
9
  * @param {string} name Name of the relation to load
10
- * @param {Object} [options] Options to pass to the fetch when not yet loaded (or when force refreshing)
10
+ * @param {Object} [options] Options to pass to the fetch when not yet loaded (or when force refreshing)
11
11
  * @param {boolean} [options.forceRefresh] If true, the relation will be fetched again even if it has already been loaded.
12
12
  * @returns {Promise<import('bookshelf').Model|import('bookshelf').Collection|null>}
13
13
  */
@@ -23,8 +23,10 @@ module.exports = function (Bookshelf) {
23
23
  // Not yet loaded, or force refresh
24
24
  // Note that we don't use .refresh on the relation on options.forceRefresh
25
25
  // Because the relation can also be a collection, which doesn't have a refresh method
26
- this.relations[name] = this[name]();
27
- return this.relations[name].fetch(options);
26
+ const instance = this[name]();
27
+ await instance.fetch(options);
28
+ this.relations[name] = instance;
29
+ return instance;
28
30
  }
29
31
  });
30
32
  };
@@ -3,6 +3,7 @@
3
3
  */
4
4
 
5
5
  const _ = require('lodash');
6
+ const debug = require('@tryghost/debug')('models');
6
7
  const glob = require('glob');
7
8
 
8
9
  // enable event listeners
@@ -14,12 +15,16 @@ require('./base/listeners');
14
15
  exports = module.exports;
15
16
 
16
17
  function init() {
18
+ const baseNow = Date.now();
17
19
  exports.Base = require('./base');
20
+ debug(`${Date.now() - baseNow}ms - Base.js require`);
18
21
 
19
22
  let modelsFiles = glob.sync('!(index).js', {cwd: __dirname});
20
23
  modelsFiles.forEach((model) => {
21
24
  const name = model.replace(/.js$/, '');
25
+ const modelNow = Date.now();
22
26
  _.extend(exports, require('./' + name));
27
+ debug(`${Date.now() - modelNow}ms - ${model} require`);
23
28
  });
24
29
  }
25
30
 
@@ -2,7 +2,7 @@ const {promises: fs} = require('fs');
2
2
  const path = require('path');
3
3
  const moment = require('moment');
4
4
  const htmlToPlaintext = require('@tryghost/html-to-plaintext');
5
- const postEmailSerializer = require('../mega/post-email-serializer');
5
+ const emailService = require('../email-service');
6
6
 
7
7
  class CommentsServiceEmails {
8
8
  constructor({config, logging, models, mailer, settingsCache, settingsHelpers, urlService, urlUtils}) {
@@ -95,7 +95,7 @@ class CommentsServiceEmails {
95
95
  accentColor: this.settingsCache.get('accent_color'),
96
96
  fromEmail: this.notificationFromAddress,
97
97
  toEmail: to,
98
- profileUrl: postEmailSerializer.createUnsubscribeUrl(member.get('uuid'), {comments: true})
98
+ profileUrl: emailService.renderer.createUnsubscribeUrl(member.get('uuid'), {comments: true})
99
99
  };
100
100
 
101
101
  const {html, text} = await this.renderEmailTemplate('new-comment-reply', templateData);
@@ -94,6 +94,8 @@ class EmailServiceWrapper {
94
94
  sentry
95
95
  });
96
96
 
97
+ this.renderer = emailRenderer;
98
+
97
99
  this.service = new EmailService({
98
100
  batchSendingService,
99
101
  sendingService,
@@ -56,7 +56,7 @@ module.exports = class LinkClickRepository {
56
56
  }
57
57
 
58
58
  const model = await this.#MemberLinkClickEventModel.add({
59
- // Only store the parthname (no support for variable query strings)
59
+ // Only store the pathname (no support for variable query strings)
60
60
  redirect_id: linkClick.link_id.toHexString(),
61
61
  member_id: member.id
62
62
  }, {});
@@ -1,12 +1,58 @@
1
1
  module.exports = {
2
2
  async init() {
3
3
  const debug = require('@tryghost/debug')('mediaInliner');
4
+ const MediaInliner = require('@tryghost/external-media-inliner');
5
+ const models = require('../../models');
6
+ const jobsService = require('../jobs');
7
+
8
+ const mediaStorage = require('../../adapters/storage').getStorage('media');
9
+ const imageStorage = require('../../adapters/storage').getStorage('images');
10
+ const fileStorage = require('../../adapters/storage').getStorage('files');
11
+
12
+ const config = require('../../../shared/config');
13
+
14
+ const mediaInliner = new MediaInliner({
15
+ PostModel: models.Post,
16
+ TagModel: models.Tag,
17
+ UserModel: models.User,
18
+ PostMetaModel: models.PostsMeta,
19
+ getMediaStorage: (extension) => {
20
+ if (config.get('uploads').images.extensions.includes(extension)) {
21
+ return imageStorage;
22
+ } else if (config.get('uploads').media.extensions.includes(extension)) {
23
+ return mediaStorage;
24
+ } else if (config.get('uploads').files.extensions.includes(extension)) {
25
+ return fileStorage;
26
+ } else {
27
+ return null;
28
+ }
29
+ }
30
+ });
4
31
 
5
32
  this.api = {
6
- // @NOTE: the inlining should become an offloaded job
7
- // startMediaInliner: mediaInliner.inlineMedia
8
- startMediaInliner: (domains) => {
33
+
34
+ startMediaInliner: async (domains) => {
35
+ if (!domains || !domains.length) {
36
+ // default domains to inline from if none are provided
37
+ domains = [
38
+ 'https://s3.amazonaws.com/revue',
39
+ 'https://substackcdn.com'
40
+ ];
41
+ }
42
+
9
43
  debug('[Inliner] Starting media inlining job for domains: ', domains);
44
+
45
+ // @NOTE: the job is "inline" (aka non-offloaded into a thread), because usecases are currently
46
+ // limited to migrational, so there is no expectations for site's availability etc.
47
+ await jobsService.addJob({
48
+ name: 'external-media-inliner',
49
+ job: (data) => {
50
+ return mediaInliner.inline(data.domains);
51
+ },
52
+ data: {domains},
53
+ offloaded: false
54
+ });
55
+
10
56
  return {
11
57
  status: 'success'
12
58
  };
@@ -26,7 +26,12 @@ function getPostUrl(post) {
26
26
 
27
27
  module.exports = {
28
28
  controller: new MentionController(),
29
+ didInit: false,
29
30
  async init() {
31
+ if (this.didInit) {
32
+ return;
33
+ }
34
+ this.didInit = true;
30
35
  const repository = new BookshelfMentionRepository({
31
36
  MentionModel: models.Mention,
32
37
  DomainEvents
@@ -97,6 +102,6 @@ module.exports = {
97
102
  }
98
103
  }
99
104
  });
100
- sendingService.listen(events);
105
+ sendingService.listen(events);
101
106
  }
102
107
  };
@@ -8,8 +8,7 @@ const messages = {
8
8
  };
9
9
 
10
10
  class PostsService {
11
- constructor({mega, urlUtils, models, isSet, stats, emailService}) {
12
- this.mega = mega;
11
+ constructor({urlUtils, models, isSet, stats, emailService}) {
13
12
  this.urlUtils = urlUtils;
14
13
  this.models = models;
15
14
  this.isSet = isSet;
@@ -45,17 +44,9 @@ class PostsService {
45
44
  let email;
46
45
 
47
46
  if (!postEmail) {
48
- if (this.isSet('emailStability')) {
49
- email = await this.emailService.createEmail(model);
50
- } else {
51
- email = await this.mega.addEmail(model, frame.options);
52
- }
47
+ email = await this.emailService.createEmail(model);
53
48
  } else if (postEmail && postEmail.get('status') === 'failed') {
54
- if (this.isSet('emailStability')) {
55
- email = await this.emailService.retryEmail(postEmail);
56
- } else {
57
- email = await this.mega.retryFailedEmail(postEmail);
58
- }
49
+ email = await this.emailService.retryEmail(postEmail);
59
50
  }
60
51
  if (email) {
61
52
  model.set('email', email);
@@ -130,7 +121,6 @@ class PostsService {
130
121
  */
131
122
  const getPostServiceInstance = () => {
132
123
  const urlUtils = require('../../../shared/url-utils');
133
- const {mega} = require('../mega');
134
124
  const labs = require('../../../shared/labs');
135
125
  const models = require('../../models');
136
126
  const PostStats = require('./stats/post-stats');
@@ -139,7 +129,6 @@ const getPostServiceInstance = () => {
139
129
  const postStats = new PostStats();
140
130
 
141
131
  return new PostsService({
142
- mega: mega,
143
132
  urlUtils: urlUtils,
144
133
  models: models,
145
134
  isSet: flag => labs.isSet(flag), // don't use bind, that breaks test subbing of labs
@@ -12,6 +12,7 @@ class StaffServiceWrapper {
12
12
 
13
13
  const logging = require('@tryghost/logging');
14
14
  const models = require('../../models');
15
+ const memberAttribution = require('../member-attribution');
15
16
  const {GhostMailer} = require('../mail');
16
17
  const mailer = new GhostMailer();
17
18
  const settingsCache = require('../../../shared/settings-cache');
@@ -26,6 +27,7 @@ class StaffServiceWrapper {
26
27
  settingsCache,
27
28
  urlUtils,
28
29
  DomainEvents,
30
+ memberAttributionService: memberAttribution.service,
29
31
  labs
30
32
  });
31
33
 
@@ -44,10 +44,18 @@ class Urls {
44
44
  debug('cache', url);
45
45
 
46
46
  if (this.urls[resource.data.id]) {
47
- logging.error(new errors.InternalServerError({
47
+ const error = new errors.InternalServerError({
48
48
  message: 'This should not happen.',
49
49
  code: 'URLSERVICE_RESOURCE_DUPLICATE'
50
- }));
50
+ });
51
+ if (process.env.NODE_ENV.startsWith('test')) {
52
+ logging.warn({
53
+ message: 'Duplicate URL',
54
+ err: error
55
+ });
56
+ } else {
57
+ logging.error(error);
58
+ }
51
59
 
52
60
  this.removeResourceId(resource.data.id);
53
61
  }
@@ -20,7 +20,6 @@ const GA_FEATURES = [
20
20
  'memberAttribution',
21
21
  'audienceFeedback',
22
22
  'themeErrorsNotification',
23
- 'emailStability',
24
23
  'emailErrors',
25
24
  'outboundLinkTagging'
26
25
  ];