ghost 5.34.1 → 5.35.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 (149) hide show
  1. package/components/tryghost-adapter-cache-redis-5.35.0.tgz +0 -0
  2. package/components/{tryghost-adapter-manager-5.34.1.tgz → tryghost-adapter-manager-5.35.0.tgz} +0 -0
  3. package/components/{tryghost-api-framework-5.34.1.tgz → tryghost-api-framework-5.35.0.tgz} +0 -0
  4. package/components/tryghost-api-version-compatibility-service-5.35.0.tgz +0 -0
  5. package/components/tryghost-audience-feedback-5.35.0.tgz +0 -0
  6. package/components/tryghost-bootstrap-socket-5.35.0.tgz +0 -0
  7. package/components/tryghost-constants-5.35.0.tgz +0 -0
  8. package/components/{tryghost-custom-theme-settings-service-5.34.1.tgz → tryghost-custom-theme-settings-service-5.35.0.tgz} +0 -0
  9. package/components/tryghost-data-generator-5.35.0.tgz +0 -0
  10. package/components/tryghost-domain-events-5.35.0.tgz +0 -0
  11. package/components/tryghost-dynamic-routing-events-5.35.0.tgz +0 -0
  12. package/components/tryghost-email-analytics-provider-mailgun-5.35.0.tgz +0 -0
  13. package/components/{tryghost-email-analytics-service-5.34.1.tgz → tryghost-email-analytics-service-5.35.0.tgz} +0 -0
  14. package/components/tryghost-email-content-generator-5.35.0.tgz +0 -0
  15. package/components/tryghost-email-events-5.35.0.tgz +0 -0
  16. package/components/tryghost-email-service-5.35.0.tgz +0 -0
  17. package/components/tryghost-email-suppression-list-5.35.0.tgz +0 -0
  18. package/components/{tryghost-express-dynamic-redirects-5.34.1.tgz → tryghost-express-dynamic-redirects-5.35.0.tgz} +0 -0
  19. package/components/{tryghost-extract-api-key-5.34.1.tgz → tryghost-extract-api-key-5.35.0.tgz} +0 -0
  20. package/components/tryghost-html-to-plaintext-5.35.0.tgz +0 -0
  21. package/components/tryghost-i18n-5.35.0.tgz +0 -0
  22. package/components/{tryghost-importer-revue-5.34.1.tgz → tryghost-importer-revue-5.35.0.tgz} +0 -0
  23. package/components/tryghost-job-manager-5.35.0.tgz +0 -0
  24. package/components/{tryghost-link-redirects-5.34.1.tgz → tryghost-link-redirects-5.35.0.tgz} +0 -0
  25. package/components/tryghost-link-replacer-5.35.0.tgz +0 -0
  26. package/components/tryghost-link-tracking-5.35.0.tgz +0 -0
  27. package/components/{tryghost-magic-link-5.34.1.tgz → tryghost-magic-link-5.35.0.tgz} +0 -0
  28. package/components/tryghost-mailgun-client-5.35.0.tgz +0 -0
  29. package/components/tryghost-member-attribution-5.35.0.tgz +0 -0
  30. package/components/tryghost-member-events-5.35.0.tgz +0 -0
  31. package/components/tryghost-members-api-5.35.0.tgz +0 -0
  32. package/components/tryghost-members-csv-5.35.0.tgz +0 -0
  33. package/components/{tryghost-members-events-service-5.34.1.tgz → tryghost-members-events-service-5.35.0.tgz} +0 -0
  34. package/components/{tryghost-members-importer-5.34.1.tgz → tryghost-members-importer-5.35.0.tgz} +0 -0
  35. package/components/tryghost-members-offers-5.35.0.tgz +0 -0
  36. package/components/tryghost-members-payments-5.35.0.tgz +0 -0
  37. package/components/tryghost-members-ssr-5.35.0.tgz +0 -0
  38. package/components/tryghost-members-stripe-service-5.35.0.tgz +0 -0
  39. package/components/tryghost-milestones-5.35.0.tgz +0 -0
  40. package/components/tryghost-minifier-5.35.0.tgz +0 -0
  41. package/components/tryghost-mw-api-version-mismatch-5.35.0.tgz +0 -0
  42. package/components/tryghost-mw-cache-control-5.35.0.tgz +0 -0
  43. package/components/{tryghost-mw-error-handler-5.34.1.tgz → tryghost-mw-error-handler-5.35.0.tgz} +0 -0
  44. package/components/tryghost-mw-session-from-token-5.35.0.tgz +0 -0
  45. package/components/tryghost-mw-update-user-last-seen-5.35.0.tgz +0 -0
  46. package/components/tryghost-mw-vhost-5.35.0.tgz +0 -0
  47. package/components/tryghost-oembed-service-5.35.0.tgz +0 -0
  48. package/components/{tryghost-package-json-5.34.1.tgz → tryghost-package-json-5.35.0.tgz} +0 -0
  49. package/components/tryghost-public-resource-repository-5.35.0.tgz +0 -0
  50. package/components/tryghost-referrers-5.35.0.tgz +0 -0
  51. package/components/tryghost-security-5.35.0.tgz +0 -0
  52. package/components/tryghost-session-service-5.35.0.tgz +0 -0
  53. package/components/tryghost-settings-path-manager-5.35.0.tgz +0 -0
  54. package/components/tryghost-slack-notifications-5.35.0.tgz +0 -0
  55. package/components/{tryghost-staff-service-5.34.1.tgz → tryghost-staff-service-5.35.0.tgz} +0 -0
  56. package/components/tryghost-stats-service-5.35.0.tgz +0 -0
  57. package/components/tryghost-tiers-5.35.0.tgz +0 -0
  58. package/components/tryghost-update-check-service-5.35.0.tgz +0 -0
  59. package/components/tryghost-verification-trigger-5.35.0.tgz +0 -0
  60. package/components/tryghost-version-notifications-data-service-5.35.0.tgz +0 -0
  61. package/components/tryghost-webmentions-5.35.0.tgz +0 -0
  62. package/core/boot.js +27 -3
  63. package/core/built/admin/assets/{chunk.143.07f5af56ff872bb0e9e4.js → chunk.143.d49ad252968f2ef3966d.js} +5 -5
  64. package/core/built/admin/assets/{chunk.178.2e831ef9072743e38dd1.js → chunk.178.3d45fff87e08a5be5eb8.js} +4 -4
  65. package/core/built/admin/assets/{chunk.616.181e1ad6c33f0bec7a65.js → chunk.502.c4afca88c98edad8b268.js} +1621 -1338
  66. package/core/built/admin/assets/{chunk.616.181e1ad6c33f0bec7a65.js.LICENSE.txt → chunk.502.c4afca88c98edad8b268.js.LICENSE.txt} +43 -0
  67. package/core/built/admin/assets/{ghost-ad40d109653288e74a7cd922341fb33d.js → ghost-4a6ed62455c9e367434183980b3ca3e9.js} +4344 -4322
  68. package/core/built/admin/assets/{vendor-253d6527ca6353855164ef65f896f762.js → vendor-c4684647d4f5213e5dbb6763de430e7e.js} +2741 -2309
  69. package/core/built/admin/index.html +5 -5
  70. package/core/cli/generate-data.js +3 -1
  71. package/core/server/api/endpoints/images.js +13 -6
  72. package/core/server/api/endpoints/pages-public.js +3 -1
  73. package/core/server/api/endpoints/posts-public.js +3 -1
  74. package/core/server/api/endpoints/utils/serializers/output/mappers/mentions.js +2 -1
  75. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +11 -0
  76. package/core/server/data/migrations/versions/5.35/2023-02-13-06-24-add-mentions-verified-column.js +7 -0
  77. package/core/server/data/schema/schema.js +2 -1
  78. package/core/server/lib/request-external.js +1 -0
  79. package/core/server/models/mention.js +2 -1
  80. package/core/server/services/email-service/wrapper.js +2 -1
  81. package/core/server/services/mega/post-email-serializer.js +2 -2
  82. package/core/server/services/mega/template.js +1 -1
  83. package/core/server/services/member-attribution/index.js +8 -4
  84. package/core/server/services/mentions/BookshelfMentionRepository.js +4 -2
  85. package/core/server/services/mentions/WebmentionRequest.js +20 -0
  86. package/core/server/services/mentions/service.js +4 -1
  87. package/core/server/services/mentions-jobs/index.js +1 -0
  88. package/core/server/services/mentions-jobs/job-service.js +48 -0
  89. package/core/server/services/{milestone-emails → milestones}/MilestoneQueries.js +0 -0
  90. package/core/server/services/{milestone-emails → milestones}/index.js +0 -0
  91. package/core/server/services/milestones/service.js +78 -0
  92. package/core/server/services/posts-public/index.js +1 -0
  93. package/core/server/services/posts-public/service.js +31 -0
  94. package/core/server/services/slack-notifications/index.js +1 -0
  95. package/core/server/services/slack-notifications/service.js +60 -0
  96. package/core/server/services/tags-public/service.js +5 -5
  97. package/core/server/services/websockets/index.js +1 -0
  98. package/core/server/services/websockets/service.js +37 -0
  99. package/core/shared/config/defaults.json +2 -2
  100. package/core/shared/labs.js +3 -2
  101. package/package.json +121 -118
  102. package/yarn.lock +307 -102
  103. package/components/tryghost-adapter-cache-redis-5.34.1.tgz +0 -0
  104. package/components/tryghost-api-version-compatibility-service-5.34.1.tgz +0 -0
  105. package/components/tryghost-audience-feedback-5.34.1.tgz +0 -0
  106. package/components/tryghost-bootstrap-socket-5.34.1.tgz +0 -0
  107. package/components/tryghost-constants-5.34.1.tgz +0 -0
  108. package/components/tryghost-data-generator-5.34.1.tgz +0 -0
  109. package/components/tryghost-domain-events-5.34.1.tgz +0 -0
  110. package/components/tryghost-dynamic-routing-events-5.34.1.tgz +0 -0
  111. package/components/tryghost-email-analytics-provider-mailgun-5.34.1.tgz +0 -0
  112. package/components/tryghost-email-content-generator-5.34.1.tgz +0 -0
  113. package/components/tryghost-email-events-5.34.1.tgz +0 -0
  114. package/components/tryghost-email-service-5.34.1.tgz +0 -0
  115. package/components/tryghost-email-suppression-list-5.34.1.tgz +0 -0
  116. package/components/tryghost-html-to-plaintext-5.34.1.tgz +0 -0
  117. package/components/tryghost-i18n-5.34.1.tgz +0 -0
  118. package/components/tryghost-job-manager-5.34.1.tgz +0 -0
  119. package/components/tryghost-link-replacer-5.34.1.tgz +0 -0
  120. package/components/tryghost-link-tracking-5.34.1.tgz +0 -0
  121. package/components/tryghost-mailgun-client-5.34.1.tgz +0 -0
  122. package/components/tryghost-member-attribution-5.34.1.tgz +0 -0
  123. package/components/tryghost-member-events-5.34.1.tgz +0 -0
  124. package/components/tryghost-members-api-5.34.1.tgz +0 -0
  125. package/components/tryghost-members-csv-5.34.1.tgz +0 -0
  126. package/components/tryghost-members-offers-5.34.1.tgz +0 -0
  127. package/components/tryghost-members-payments-5.34.1.tgz +0 -0
  128. package/components/tryghost-members-ssr-5.34.1.tgz +0 -0
  129. package/components/tryghost-members-stripe-service-5.34.1.tgz +0 -0
  130. package/components/tryghost-milestone-emails-5.34.1.tgz +0 -0
  131. package/components/tryghost-minifier-5.34.1.tgz +0 -0
  132. package/components/tryghost-mw-api-version-mismatch-5.34.1.tgz +0 -0
  133. package/components/tryghost-mw-cache-control-5.34.1.tgz +0 -0
  134. package/components/tryghost-mw-session-from-token-5.34.1.tgz +0 -0
  135. package/components/tryghost-mw-update-user-last-seen-5.34.1.tgz +0 -0
  136. package/components/tryghost-mw-vhost-5.34.1.tgz +0 -0
  137. package/components/tryghost-oembed-service-5.34.1.tgz +0 -0
  138. package/components/tryghost-referrers-5.34.1.tgz +0 -0
  139. package/components/tryghost-security-5.34.1.tgz +0 -0
  140. package/components/tryghost-session-service-5.34.1.tgz +0 -0
  141. package/components/tryghost-settings-path-manager-5.34.1.tgz +0 -0
  142. package/components/tryghost-stats-service-5.34.1.tgz +0 -0
  143. package/components/tryghost-tags-public-5.34.1.tgz +0 -0
  144. package/components/tryghost-tiers-5.34.1.tgz +0 -0
  145. package/components/tryghost-update-check-service-5.34.1.tgz +0 -0
  146. package/components/tryghost-verification-trigger-5.34.1.tgz +0 -0
  147. package/components/tryghost-version-notifications-data-service-5.34.1.tgz +0 -0
  148. package/components/tryghost-webmentions-5.34.1.tgz +0 -0
  149. package/core/server/services/milestone-emails/service.js +0 -58
@@ -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.34%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%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.35%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" />
@@ -56,9 +56,9 @@
56
56
 
57
57
  <div id="ember-basic-dropdown-wormhole"></div>
58
58
 
59
- <script src="assets/vendor-253d6527ca6353855164ef65f896f762.js"></script>
60
- <script src="assets/chunk.616.181e1ad6c33f0bec7a65.js"></script>
61
- <script src="assets/chunk.143.07f5af56ff872bb0e9e4.js"></script>
62
- <script src="assets/ghost-ad40d109653288e74a7cd922341fb33d.js"></script>
59
+ <script src="assets/vendor-c4684647d4f5213e5dbb6763de430e7e.js"></script>
60
+ <script src="assets/chunk.502.c4afca88c98edad8b268.js"></script>
61
+ <script src="assets/chunk.143.d49ad252968f2ef3966d.js"></script>
62
+ <script src="assets/ghost-4a6ed62455c9e367434183980b3ca3e9.js"></script>
63
63
  </body>
64
64
  </html>
@@ -9,6 +9,7 @@ module.exports = class DataGeneratorCommand extends Command {
9
9
  this.argument('--scale', {type: 'string', defaultValue: 'small', desc: 'Scale of the data to generate. `small` for a quick run, `large` for more content'});
10
10
  this.argument('--single-table', {type: 'string', desc: 'Import a single table'});
11
11
  this.argument('--quantity', {type: 'number', desc: 'When importing a single table, the quantity to import'});
12
+ this.argument('--clear-database', {type: 'boolean', defaultValue: false, desc: 'Clear all entries in the database before importing'});
12
13
  }
13
14
 
14
15
  initializeContext(context) {
@@ -55,7 +56,8 @@ module.exports = class DataGeneratorCommand extends Command {
55
56
  debug: this.debug
56
57
  },
57
58
  modelQuantities,
58
- baseUrl: config.getSiteUrl()
59
+ baseUrl: config.getSiteUrl(),
60
+ clearDatabase: argv['clear-database']
59
61
  });
60
62
  try {
61
63
  if (argv['single-table']) {
@@ -37,13 +37,20 @@ module.exports = {
37
37
  ...frame.file,
38
38
  path: out
39
39
  });
40
- const processedImagePath = store.urlToPath(processedImageUrl);
41
40
 
42
- // Get the path and name of the processed image
43
- // We want to store the original image on the same name + _o
44
- // So we need to wait for the first store to finish before generating the name of the original image
45
- const processedImageName = path.basename(processedImagePath);
46
- const processedImageDir = path.dirname(processedImagePath);
41
+ let processedImageName = path.basename(processedImageUrl);
42
+ let processedImageDir = undefined;
43
+
44
+ if (store.urlToPath) {
45
+ // Currently urlToPath is not part of StorageBase, so not all storage provider have implemented it
46
+ const processedImagePath = store.urlToPath(processedImageUrl);
47
+
48
+ // Get the path and name of the processed image
49
+ // We want to store the original image on the same name + _o
50
+ // So we need to wait for the first store to finish before generating the name of the original image
51
+ processedImageName = path.basename(processedImagePath);
52
+ processedImageDir = path.dirname(processedImagePath);
53
+ }
47
54
 
48
55
  // Store the original image
49
56
  await store.save({
@@ -1,6 +1,8 @@
1
1
  const tpl = require('@tryghost/tpl');
2
2
  const errors = require('@tryghost/errors');
3
3
  const models = require('../../models');
4
+ const postsPublicService = require('../../services/posts-public');
5
+
4
6
  const ALLOWED_INCLUDES = ['tags', 'authors', 'tiers'];
5
7
 
6
8
  const messages = {
@@ -34,7 +36,7 @@ module.exports = {
34
36
  },
35
37
  permissions: true,
36
38
  query(frame) {
37
- return models.Post.findPage(frame.options);
39
+ return postsPublicService.api.browse(frame.options);
38
40
  }
39
41
  },
40
42
 
@@ -1,6 +1,8 @@
1
1
  const models = require('../../models');
2
2
  const tpl = require('@tryghost/tpl');
3
3
  const errors = require('@tryghost/errors');
4
+ const postsPublicService = require('../../services/posts-public');
5
+
4
6
  const allowedIncludes = ['tags', 'authors', 'tiers', 'sentiment'];
5
7
 
6
8
  const messages = {
@@ -34,7 +36,7 @@ module.exports = {
34
36
  },
35
37
  permissions: true,
36
38
  query(frame) {
37
- return models.Post.findPage(frame.options);
39
+ return postsPublicService.api.browse(frame.options);
38
40
  }
39
41
  },
40
42
 
@@ -13,6 +13,7 @@ module.exports = (model) => {
13
13
  source_excerpt: json.sourceExcerpt,
14
14
  source_author: json.sourceAuthor,
15
15
  source_favicon: json.sourceFavicon,
16
- source_featured_image: json.sourceFeaturedImage
16
+ source_featured_image: json.sourceFeaturedImage,
17
+ verified: json.verified
17
18
  };
18
19
  };
@@ -18,6 +18,8 @@ const getPostServiceInstance = require('../../../../../../services/posts/posts-s
18
18
  const postsService = getPostServiceInstance();
19
19
 
20
20
  const commentsService = require('../../../../../../services/comments');
21
+ const memberAttribution = require('../../../../../../services/member-attribution');
22
+ const labs = require('../../../../../../../shared/labs');
21
23
 
22
24
  module.exports = async (model, frame, options = {}) => {
23
25
  const {tiers: tiersData} = options || {};
@@ -65,6 +67,15 @@ module.exports = async (model, frame, options = {}) => {
65
67
  } else {
66
68
  jsonModel.comments = false;
67
69
  }
70
+
71
+ // Add outbound link tags
72
+ if (labs.isSet('outboundLinkTagging')) {
73
+ // Only add it in the flag! Without the flag we only add it to emails.
74
+ if (jsonModel.html) {
75
+ // Only set if HTML was requested
76
+ jsonModel.html = await memberAttribution.outboundLinkTagger.addToHtml(jsonModel.html);
77
+ }
78
+ }
68
79
  }
69
80
 
70
81
  // Transforms post/page metadata to flat structure
@@ -0,0 +1,7 @@
1
+ const {createAddColumnMigration} = require('../../utils');
2
+
3
+ module.exports = createAddColumnMigration('mentions', 'verified', {
4
+ type: 'boolean',
5
+ nullable: false,
6
+ defaultTo: false
7
+ });
@@ -995,6 +995,7 @@ module.exports = {
995
995
  resource_type: {type: 'string', maxlength: 50, nullable: true},
996
996
  created_at: {type: 'dateTime', nullable: false},
997
997
  payload: {type: 'text', maxlength: 65535, nullable: true},
998
- deleted: {type: 'boolean', nullable: false, defaultTo: false}
998
+ deleted: {type: 'boolean', nullable: false, defaultTo: false},
999
+ verified: {type: 'boolean', nullable: false, defaultTo: false}
999
1000
  }
1000
1001
  };
@@ -42,6 +42,7 @@ const externalRequest = got.extend({
42
42
  headers: {
43
43
  'user-agent': 'Ghost(https://github.com/TryGhost/Ghost)'
44
44
  },
45
+ timeout: 10000, // default is no timeout
45
46
  hooks: {
46
47
  init: [(options) => {
47
48
  if (!options.hostname || !validator.isURL(options.hostname)) {
@@ -3,7 +3,8 @@ const ghostBookshelf = require('./base');
3
3
  const Mention = ghostBookshelf.Model.extend({
4
4
  tableName: 'mentions',
5
5
  defaults: {
6
- deleted: false
6
+ deleted: false,
7
+ verified: false
7
8
  },
8
9
  enforcedFilters() {
9
10
  return 'deleted:false';
@@ -67,7 +67,8 @@ class EmailServiceWrapper {
67
67
  linkReplacer,
68
68
  linkTracking,
69
69
  memberAttributionService: memberAttribution.service,
70
- audienceFeedbackService: audienceFeedback.service
70
+ audienceFeedbackService: audienceFeedback.service,
71
+ outboundLinkTagger: memberAttribution.outboundLinkTagger
71
72
  });
72
73
 
73
74
  const sendingService = new SendingService({
@@ -400,13 +400,13 @@ const PostEmailSerializer = {
400
400
 
401
401
  if (isSite) {
402
402
  // Add newsletter name as ref to the URL
403
- url = memberAttribution.service.addOutboundLinkTagging(url, newsletter);
403
+ url = memberAttribution.outboundLinkTagger.addToUrl(url, newsletter);
404
404
 
405
405
  // Only add post attribution to our own site (because external sites could/should not process this information)
406
406
  url = memberAttribution.service.addPostAttributionTracking(url, post);
407
407
  } else {
408
408
  // Add email source attribution without the newsletter name
409
- url = memberAttribution.service.addOutboundLinkTagging(url);
409
+ url = memberAttribution.outboundLinkTagger.addToUrl(url);
410
410
  }
411
411
 
412
412
  // Add link click tracking
@@ -1041,7 +1041,7 @@ a[data-flickr-embed] img {
1041
1041
  }
1042
1042
 
1043
1043
  table.body .kg-bookmark-card {
1044
- width: 90vw !important;
1044
+ width: 90vw;
1045
1045
  }
1046
1046
 
1047
1047
  table.body .kg-bookmark-thumbnail {
@@ -12,7 +12,7 @@ class MemberAttributionServiceWrapper {
12
12
 
13
13
  // Wire up all the dependencies
14
14
  const {
15
- MemberAttributionService, UrlTranslator, ReferrerTranslator, AttributionBuilder
15
+ MemberAttributionService, UrlTranslator, ReferrerTranslator, AttributionBuilder, OutboundLinkTagger
16
16
  } = require('@tryghost/member-attribution');
17
17
  const models = require('../../models');
18
18
 
@@ -33,6 +33,12 @@ class MemberAttributionServiceWrapper {
33
33
 
34
34
  this.attributionBuilder = new AttributionBuilder({urlTranslator, referrerTranslator});
35
35
 
36
+ this.outboundLinkTagger = new OutboundLinkTagger({
37
+ isEnabled: () => !labs.isSet('outboundLinkTagging') || !!settingsCache.get('outbound_link_tagging'),
38
+ getSiteTitle: () => settingsCache.get('title'),
39
+ urlUtils
40
+ });
41
+
36
42
  // Expose the service
37
43
  this.service = new MemberAttributionService({
38
44
  models: {
@@ -41,9 +47,7 @@ class MemberAttributionServiceWrapper {
41
47
  Integration: models.Integration
42
48
  },
43
49
  attributionBuilder: this.attributionBuilder,
44
- getTrackingEnabled: () => !!settingsCache.get('members_track_sources'),
45
- getOutboundLinkTaggingEnabled: () => !labs.isSet('outboundLinkTagging') || !!settingsCache.get('outbound_link_tagging'),
46
- getSiteTitle: () => settingsCache.get('title')
50
+ getTrackingEnabled: () => !!settingsCache.get('members_track_sources')
47
51
  });
48
52
  }
49
53
  }
@@ -54,7 +54,8 @@ module.exports = class BookshelfMentionRepository {
54
54
  sourceAuthor: model.get('source_author'),
55
55
  sourceExcerpt: model.get('source_excerpt'),
56
56
  sourceFavicon: model.get('source_favicon'),
57
- sourceFeaturedImage: model.get('source_featured_image')
57
+ sourceFeaturedImage: model.get('source_featured_image'),
58
+ verified: model.get('verified')
58
59
  });
59
60
  }
60
61
 
@@ -107,7 +108,8 @@ module.exports = class BookshelfMentionRepository {
107
108
  resource_id: mention.resourceId?.toHexString(),
108
109
  resource_type: mention.resourceId ? 'post' : null,
109
110
  payload: mention.payload ? JSON.stringify(mention.payload) : null,
110
- deleted: Mention.isDeleted(mention)
111
+ deleted: Mention.isDeleted(mention),
112
+ verified: mention.verified
111
113
  };
112
114
 
113
115
  const existing = await this.#MentionModel.findOne({id: data.id}, {require: false});
@@ -0,0 +1,20 @@
1
+ const logging = require('@tryghost/logging');
2
+ const oembedService = require('../oembed');
3
+
4
+ module.exports = class WebmentionRequest {
5
+ /**
6
+ * @param {URL} url
7
+ * @returns {Promise<{html: string}>}
8
+ */
9
+ async fetch(url) {
10
+ try {
11
+ const data = await oembedService.fetchPageHtml(url.href);
12
+ return {
13
+ html: data.body
14
+ };
15
+ } catch (err) {
16
+ logging.warn(err);
17
+ return null;
18
+ }
19
+ }
20
+ };
@@ -1,5 +1,6 @@
1
1
  const MentionController = require('./MentionController');
2
2
  const WebmentionMetadata = require('./WebmentionMetadata');
3
+ const WebmentionRequest = require('./WebmentionRequest');
3
4
  const {
4
5
  MentionsAPI,
5
6
  MentionSendingService,
@@ -16,7 +17,7 @@ const outputSerializerUrlUtil = require('../../../server/api/endpoints/utils/ser
16
17
  const urlService = require('../url');
17
18
  const settingsCache = require('../../../shared/settings-cache');
18
19
  const DomainEvents = require('@tryghost/domain-events');
19
- const jobsService = require('../jobs');
20
+ const jobsService = require('../mentions-jobs');
20
21
 
21
22
  function getPostUrl(post) {
22
23
  const jsonModel = {};
@@ -32,6 +33,7 @@ module.exports = {
32
33
  DomainEvents
33
34
  });
34
35
  const webmentionMetadata = new WebmentionMetadata();
36
+ const webmentionRequest = new WebmentionRequest();
35
37
  const discoveryService = new MentionDiscoveryService({externalRequest});
36
38
  const resourceService = new ResourceService({
37
39
  urlUtils,
@@ -47,6 +49,7 @@ module.exports = {
47
49
  const api = new MentionsAPI({
48
50
  repository,
49
51
  webmentionMetadata,
52
+ webmentionRequest,
50
53
  resourceService,
51
54
  routingService
52
55
  });
@@ -0,0 +1 @@
1
+ module.exports = require('./job-service');
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Minimal wrapper around our external lib
3
+ * Intended for passing any Ghost internals such as logging and config
4
+ */
5
+
6
+ const JobManager = require('@tryghost/job-manager');
7
+ const logging = require('@tryghost/logging');
8
+ const models = require('../../models');
9
+ const sentry = require('../../../shared/sentry');
10
+ const domainEvents = require('@tryghost/domain-events');
11
+
12
+ const errorHandler = (error, workerMeta) => {
13
+ logging.info(`Capturing error for worker during execution of job: ${workerMeta.name}`);
14
+ logging.error(error);
15
+ sentry.captureException(error);
16
+ };
17
+
18
+ const workerMessageHandler = ({name, message}) => {
19
+ if (typeof message === 'string') {
20
+ logging.info(`Worker for job ${name} sent a message: ${message}`);
21
+ }
22
+ };
23
+
24
+ const initTestMode = () => {
25
+ // Output job queue length every 5 seconds
26
+ setInterval(() => {
27
+ logging.warn(`${jobManager.queue.length()} jobs in the queue. Idle: ${jobManager.queue.idle()}`);
28
+
29
+ const runningScheduledjobs = Object.keys(jobManager.bree.workers);
30
+ if (Object.keys(jobManager.bree.workers).length) {
31
+ logging.warn(`${Object.keys(jobManager.bree.workers).length} jobs running: ${runningScheduledjobs}`);
32
+ }
33
+
34
+ const scheduledJobs = Object.keys(jobManager.bree.intervals);
35
+ if (Object.keys(jobManager.bree.intervals).length) {
36
+ logging.warn(`${Object.keys(jobManager.bree.intervals).length} scheduled jobs: ${scheduledJobs}`);
37
+ }
38
+
39
+ if (runningScheduledjobs.length === 0 && scheduledJobs.length === 0) {
40
+ logging.warn('No scheduled or running jobs');
41
+ }
42
+ }, 5000);
43
+ };
44
+
45
+ const jobManager = new JobManager({errorHandler, workerMessageHandler, JobModel: models.Job, domainEvents});
46
+
47
+ module.exports = jobManager;
48
+ module.exports.initTestMode = initTestMode;
@@ -0,0 +1,78 @@
1
+ const DomainEvents = require('@tryghost/domain-events');
2
+
3
+ const getStripeLiveEnabled = () => {
4
+ const settingsCache = require('../../../shared/settings-cache');
5
+ const stripeConnect = settingsCache.get('stripe_connect_publishable_key');
6
+ const stripeKey = settingsCache.get('stripe_publishable_key');
7
+
8
+ const stripeLiveRegex = /pk_live_/;
9
+
10
+ if (stripeConnect && stripeConnect.match(stripeLiveRegex)) {
11
+ return true;
12
+ } else if (stripeKey && stripeKey.match(stripeLiveRegex)) {
13
+ return true;
14
+ }
15
+
16
+ return false;
17
+ };
18
+
19
+ module.exports = {
20
+ /** @type {import('@tryghost/milestones/lib/MilestonesService')} */
21
+ api: null,
22
+
23
+ /**
24
+ * @returns {Promise<void>}
25
+ */
26
+ async init() {
27
+ if (!this.api) {
28
+ const db = require('../../data/db');
29
+ const MilestoneQueries = require('./MilestoneQueries');
30
+
31
+ const {
32
+ MilestonesService,
33
+ InMemoryMilestoneRepository
34
+ } = require('@tryghost/milestones');
35
+ const config = require('../../../shared/config');
36
+ const milestonesConfig = config.get('milestones');
37
+
38
+ const repository = new InMemoryMilestoneRepository({DomainEvents});
39
+ const queries = new MilestoneQueries({db});
40
+
41
+ this.api = new MilestonesService({
42
+ repository,
43
+ milestonesConfig, // avoid using getters and pass as JSON
44
+ queries
45
+ });
46
+ }
47
+ },
48
+
49
+ /**
50
+ * @returns {Promise<object>}
51
+ */
52
+ async run() {
53
+ const labs = require('../../../shared/labs');
54
+
55
+ if (labs.isSet('milestoneEmails')) {
56
+ const members = await this.api.checkMilestones('members');
57
+ let arr;
58
+ const stripeLiveEnabled = getStripeLiveEnabled();
59
+
60
+ if (stripeLiveEnabled) {
61
+ arr = await this.api.checkMilestones('arr');
62
+ }
63
+
64
+ return {
65
+ members,
66
+ arr
67
+ };
68
+ }
69
+ },
70
+
71
+ /**
72
+ * @returns {Promise<object>}
73
+ */
74
+ async initAndRun() {
75
+ await this.init();
76
+ return await this.run();
77
+ }
78
+ };
@@ -0,0 +1 @@
1
+ module.exports = require('./service');
@@ -0,0 +1,31 @@
1
+ class PostsPublicServiceWrapper {
2
+ async init() {
3
+ if (this.api) {
4
+ // Already done
5
+ return;
6
+ }
7
+
8
+ // Wire up all the dependencies
9
+ const {Post} = require('../../models');
10
+ const adapterManager = require('../adapter-manager');
11
+ const config = require('../../../shared/config');
12
+
13
+ let postsCache;
14
+ if (config.get('hostSettings:postsPublicCache:enabled')) {
15
+ postsCache = adapterManager.getAdapter('cache:postsPublic');
16
+ }
17
+
18
+ const {PublicResourcesRepository} = require('@tryghost/public-resource-repository');
19
+
20
+ this.postsRepository = new PublicResourcesRepository({
21
+ Model: Post,
22
+ cache: postsCache
23
+ });
24
+
25
+ this.api = {
26
+ browse: this.postsRepository.getAll.bind(this.postsRepository)
27
+ };
28
+ }
29
+ }
30
+
31
+ module.exports = new PostsPublicServiceWrapper();
@@ -0,0 +1 @@
1
+ module.exports = require('./service');
@@ -0,0 +1,60 @@
1
+ const DomainEvents = require('@tryghost/domain-events');
2
+ const config = require('../../../shared/config');
3
+ const labs = require('../../../shared/labs');
4
+ const logging = require('@tryghost/logging');
5
+
6
+ class SlackNotificationsServiceWrapper {
7
+ /** @type {import('@tryghost/slack-notifications/lib/SlackNotificationsService')} */
8
+ #api;
9
+
10
+ /**
11
+ *
12
+ * @param {object} deps
13
+ * @param {string} deps.siteUrl
14
+ * @param {boolean} deps.isEnabled
15
+ * @param {URL} deps.webhookUrl
16
+ *
17
+ * @returns {import('@tryghost/slack-notifications/lib/SlackNotificationsService')}
18
+ */
19
+ static create({siteUrl, isEnabled, webhookUrl}) {
20
+ const {
21
+ SlackNotificationsService,
22
+ SlackNotifications
23
+ } = require('@tryghost/slack-notifications');
24
+
25
+ const slackNotifications = new SlackNotifications({
26
+ webhookUrl,
27
+ siteUrl,
28
+ logging
29
+ });
30
+
31
+ return new SlackNotificationsService({
32
+ DomainEvents,
33
+ logging,
34
+ config: {
35
+ isEnabled,
36
+ webhookUrl
37
+ },
38
+ slackNotifications
39
+ });
40
+ }
41
+
42
+ init() {
43
+ if (this.#api) {
44
+ // Prevent creating duplicate DomainEvents subscribers
45
+ return;
46
+ }
47
+
48
+ const hostSettings = config.get('hostSettings');
49
+ const urlUtils = require('../../../shared/url-utils');
50
+ const siteUrl = urlUtils.getSiteUrl();
51
+ const isEnabled = labs.isSet('milestoneEmails') && hostSettings?.milestones?.enabled && hostSettings?.milestones?.url;
52
+ const webhookUrl = hostSettings?.milestones?.url;
53
+
54
+ this.#api = SlackNotificationsServiceWrapper.create({siteUrl, isEnabled, webhookUrl});
55
+
56
+ this.#api.subscribeEvents();
57
+ }
58
+ }
59
+
60
+ module.exports = new SlackNotificationsServiceWrapper();
@@ -6,7 +6,7 @@ class TagsPublicServiceWrapper {
6
6
  }
7
7
 
8
8
  // Wire up all the dependencies
9
- const models = require('../../models');
9
+ const {TagPublic} = require('../../models');
10
10
  const adapterManager = require('../adapter-manager');
11
11
  const config = require('../../../shared/config');
12
12
 
@@ -15,15 +15,15 @@ class TagsPublicServiceWrapper {
15
15
  tagsCache = adapterManager.getAdapter('cache:tagsPublic');
16
16
  }
17
17
 
18
- const {TagsPublicRepository} = require('@tryghost/tags-public');
18
+ const {PublicResourcesRepository} = require('@tryghost/public-resource-repository');
19
19
 
20
- this.linkRedirectRepository = new TagsPublicRepository({
21
- Tag: models.TagPublic,
20
+ this.tagsPublicRepository = new PublicResourcesRepository({
21
+ Model: TagPublic,
22
22
  cache: tagsCache
23
23
  });
24
24
 
25
25
  this.api = {
26
- browse: this.linkRedirectRepository.getAll.bind(this.linkRedirectRepository)
26
+ browse: this.tagsPublicRepository.getAll.bind(this.tagsPublicRepository)
27
27
  };
28
28
  }
29
29
  }
@@ -0,0 +1 @@
1
+ module.exports = require('./service');
@@ -0,0 +1,37 @@
1
+ const {Server} = require('socket.io');
2
+ const debug = require('@tryghost/debug')('websockets');
3
+ const logging = require('@tryghost/logging');
4
+
5
+ const labs = require('../../../shared/labs');
6
+
7
+ module.exports = {
8
+ async init(ghostServer) {
9
+ debug(`[Websockets] Is labs set? ${labs.isSet('websockets')}`);
10
+
11
+ if (labs.isSet('websockets')) {
12
+ logging.info(`Starting websockets service`);
13
+
14
+ const io = new Server(ghostServer.httpServer);
15
+ let count = 0;
16
+
17
+ io.on(`connection`, (socket) => {
18
+ debug(`[Websockets] Client connected`);
19
+ // on connect, send current value
20
+ socket.emit('addCount', count);
21
+ // listen to to changes in value from client
22
+ socket.on('addCount', () => {
23
+ count = count + 1;
24
+ debug(`[Websockets] received addCount from client, count is now ${count}`);
25
+ socket.broadcast.emit('addCount', count);
26
+ });
27
+ });
28
+
29
+ ghostServer.registerCleanupTask(async () => {
30
+ logging.warn(`Stopping websockets service`);
31
+ await new Promise((resolve) => {
32
+ io.close(resolve);
33
+ });
34
+ });
35
+ }
36
+ }
37
+ };
@@ -210,9 +210,9 @@
210
210
  "arr": [
211
211
  {
212
212
  "currency": "usd",
213
- "values": [1000, 10000, 50000, 100000, 250000, 500000, 1000000]
213
+ "values": [100, 1000, 10000, 50000, 100000, 250000, 500000, 1000000]
214
214
  }
215
215
  ],
216
- "members": [100, 1000, 10000, 50000, 100000, 250000, 500000, 1000000]
216
+ "members": [100, 1000, 10000, 25000, 50000, 100000, 250000, 500000, 1000000]
217
217
  }
218
218
  }