ghost 5.32.0 → 5.33.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 (106) hide show
  1. package/components/{tryghost-adapter-manager-5.32.0.tgz → tryghost-adapter-manager-5.33.0.tgz} +0 -0
  2. package/components/{tryghost-api-framework-5.32.0.tgz → tryghost-api-framework-5.33.0.tgz} +0 -0
  3. package/components/{tryghost-api-version-compatibility-service-5.32.0.tgz → tryghost-api-version-compatibility-service-5.33.0.tgz} +0 -0
  4. package/components/tryghost-audience-feedback-5.33.0.tgz +0 -0
  5. package/components/tryghost-bootstrap-socket-5.33.0.tgz +0 -0
  6. package/components/tryghost-constants-5.33.0.tgz +0 -0
  7. package/components/{tryghost-custom-theme-settings-service-5.32.0.tgz → tryghost-custom-theme-settings-service-5.33.0.tgz} +0 -0
  8. package/components/tryghost-data-generator-5.33.0.tgz +0 -0
  9. package/components/{tryghost-domain-events-5.32.0.tgz → tryghost-domain-events-5.33.0.tgz} +0 -0
  10. package/components/tryghost-dynamic-routing-events-5.33.0.tgz +0 -0
  11. package/components/tryghost-email-analytics-provider-mailgun-5.33.0.tgz +0 -0
  12. package/components/{tryghost-email-analytics-service-5.32.0.tgz → tryghost-email-analytics-service-5.33.0.tgz} +0 -0
  13. package/components/tryghost-email-content-generator-5.33.0.tgz +0 -0
  14. package/components/tryghost-email-events-5.33.0.tgz +0 -0
  15. package/components/tryghost-email-service-5.33.0.tgz +0 -0
  16. package/components/{tryghost-email-suppression-list-5.32.0.tgz → tryghost-email-suppression-list-5.33.0.tgz} +0 -0
  17. package/components/{tryghost-express-dynamic-redirects-5.32.0.tgz → tryghost-express-dynamic-redirects-5.33.0.tgz} +0 -0
  18. package/components/{tryghost-extract-api-key-5.32.0.tgz → tryghost-extract-api-key-5.33.0.tgz} +0 -0
  19. package/components/tryghost-html-to-plaintext-5.33.0.tgz +0 -0
  20. package/components/tryghost-i18n-5.33.0.tgz +0 -0
  21. package/components/{tryghost-importer-revue-5.32.0.tgz → tryghost-importer-revue-5.33.0.tgz} +0 -0
  22. package/components/{tryghost-job-manager-5.32.0.tgz → tryghost-job-manager-5.33.0.tgz} +0 -0
  23. package/components/{tryghost-link-redirects-5.32.0.tgz → tryghost-link-redirects-5.33.0.tgz} +0 -0
  24. package/components/tryghost-link-replacer-5.33.0.tgz +0 -0
  25. package/components/{tryghost-link-tracking-5.32.0.tgz → tryghost-link-tracking-5.33.0.tgz} +0 -0
  26. package/components/{tryghost-magic-link-5.32.0.tgz → tryghost-magic-link-5.33.0.tgz} +0 -0
  27. package/components/tryghost-mailgun-client-5.33.0.tgz +0 -0
  28. package/components/{tryghost-member-attribution-5.32.0.tgz → tryghost-member-attribution-5.33.0.tgz} +0 -0
  29. package/components/tryghost-member-events-5.33.0.tgz +0 -0
  30. package/components/{tryghost-members-api-5.32.0.tgz → tryghost-members-api-5.33.0.tgz} +0 -0
  31. package/components/{tryghost-members-csv-5.32.0.tgz → tryghost-members-csv-5.33.0.tgz} +0 -0
  32. package/components/{tryghost-members-events-service-5.32.0.tgz → tryghost-members-events-service-5.33.0.tgz} +0 -0
  33. package/components/{tryghost-members-importer-5.32.0.tgz → tryghost-members-importer-5.33.0.tgz} +0 -0
  34. package/components/{tryghost-members-offers-5.32.0.tgz → tryghost-members-offers-5.33.0.tgz} +0 -0
  35. package/components/{tryghost-members-payments-5.32.0.tgz → tryghost-members-payments-5.33.0.tgz} +0 -0
  36. package/components/{tryghost-members-ssr-5.32.0.tgz → tryghost-members-ssr-5.33.0.tgz} +0 -0
  37. package/components/tryghost-members-stripe-service-5.33.0.tgz +0 -0
  38. package/components/{tryghost-minifier-5.32.0.tgz → tryghost-minifier-5.33.0.tgz} +0 -0
  39. package/components/tryghost-mw-api-version-mismatch-5.33.0.tgz +0 -0
  40. package/components/{tryghost-mw-cache-control-5.32.0.tgz → tryghost-mw-cache-control-5.33.0.tgz} +0 -0
  41. package/components/{tryghost-mw-error-handler-5.32.0.tgz → tryghost-mw-error-handler-5.33.0.tgz} +0 -0
  42. package/components/tryghost-mw-session-from-token-5.33.0.tgz +0 -0
  43. package/components/tryghost-mw-update-user-last-seen-5.33.0.tgz +0 -0
  44. package/components/tryghost-mw-vhost-5.33.0.tgz +0 -0
  45. package/components/tryghost-oembed-service-5.33.0.tgz +0 -0
  46. package/components/{tryghost-package-json-5.32.0.tgz → tryghost-package-json-5.33.0.tgz} +0 -0
  47. package/components/tryghost-referrers-5.33.0.tgz +0 -0
  48. package/components/{tryghost-security-5.32.0.tgz → tryghost-security-5.33.0.tgz} +0 -0
  49. package/components/tryghost-session-service-5.33.0.tgz +0 -0
  50. package/components/{tryghost-settings-path-manager-5.32.0.tgz → tryghost-settings-path-manager-5.33.0.tgz} +0 -0
  51. package/components/tryghost-staff-service-5.33.0.tgz +0 -0
  52. package/components/{tryghost-stats-service-5.32.0.tgz → tryghost-stats-service-5.33.0.tgz} +0 -0
  53. package/components/{tryghost-tiers-5.32.0.tgz → tryghost-tiers-5.33.0.tgz} +0 -0
  54. package/components/{tryghost-update-check-service-5.32.0.tgz → tryghost-update-check-service-5.33.0.tgz} +0 -0
  55. package/components/{tryghost-verification-trigger-5.32.0.tgz → tryghost-verification-trigger-5.33.0.tgz} +0 -0
  56. package/components/{tryghost-version-notifications-data-service-5.32.0.tgz → tryghost-version-notifications-data-service-5.33.0.tgz} +0 -0
  57. package/components/tryghost-webmentions-5.33.0.tgz +0 -0
  58. package/core/built/admin/assets/{chunk.143.ad05239cc363254caed7.js → chunk.143.e158db597c343f4f132e.js} +23 -23
  59. package/core/built/admin/assets/{chunk.178.5260f900f09f859bf8ed.js → chunk.178.eaeb178bc4597cb87699.js} +4 -4
  60. package/core/built/admin/assets/{chunk.507.aa38cb08f25bae1d4e76.js → chunk.79.c3c2c05ea7ff7707fcad.js} +173 -167
  61. package/core/built/admin/assets/{chunk.47.f231a64fe3fbaba23b84.js → chunk.963.e47ead5abeca4cf69fed.js} +1003 -1009
  62. package/core/built/admin/assets/{chunk.47.f231a64fe3fbaba23b84.js.LICENSE.txt → chunk.963.e47ead5abeca4cf69fed.js.LICENSE.txt} +0 -0
  63. package/core/built/admin/assets/{ghost-4571ad33bd158ced8a9f5c13598fdb7f.js → ghost-86f1db1d00d5f0bc4f0cbdb39ff6c977.js} +135 -119
  64. package/core/built/admin/assets/{ghost-dark-fb05eb50e216469c5626356731afa42f.css → ghost-dark-ac0cb221eddc8652a0e7c263ed6513dc.css} +1 -1
  65. package/core/built/admin/assets/{ghost-721a7adc4ca642c88e4ac85e1cb8b385.css → ghost-fb9fb8adbcaf1603ad4006dd2d49e401.css} +1 -1
  66. package/core/built/admin/assets/img/stripe-3af79c77413d7e58d0322c1de5bfdb8e.svg +1 -0
  67. package/core/built/admin/index.html +5 -5
  68. package/core/cli/generate-data.js +3 -1
  69. package/core/frontend/services/sitemap/base-generator.js +16 -1
  70. package/core/frontend/services/sitemap/manager.js +6 -0
  71. package/core/server/services/email-analytics/jobs/index.js +3 -3
  72. package/core/server/services/email-service/wrapper.js +3 -1
  73. package/core/server/services/mentions/BookshelfMentionRepository.js +8 -0
  74. package/core/server/services/mentions/ResourceService.js +45 -0
  75. package/core/server/services/mentions/RoutingService.js +78 -0
  76. package/core/server/services/mentions/service.js +19 -32
  77. package/core/server/services/staff/index.js +4 -1
  78. package/core/server/services/url/Resources.js +33 -0
  79. package/core/server/services/url/Urls.js +3 -0
  80. package/core/server/services/url/config.js +6 -3
  81. package/core/shared/labs.js +1 -0
  82. package/package.json +110 -108
  83. package/yarn.lock +129 -129
  84. package/components/tryghost-audience-feedback-5.32.0.tgz +0 -0
  85. package/components/tryghost-bootstrap-socket-5.32.0.tgz +0 -0
  86. package/components/tryghost-constants-5.32.0.tgz +0 -0
  87. package/components/tryghost-data-generator-5.32.0.tgz +0 -0
  88. package/components/tryghost-email-analytics-provider-mailgun-5.32.0.tgz +0 -0
  89. package/components/tryghost-email-content-generator-5.32.0.tgz +0 -0
  90. package/components/tryghost-email-events-5.32.0.tgz +0 -0
  91. package/components/tryghost-email-service-5.32.0.tgz +0 -0
  92. package/components/tryghost-html-to-plaintext-5.32.0.tgz +0 -0
  93. package/components/tryghost-i18n-5.32.0.tgz +0 -0
  94. package/components/tryghost-link-replacer-5.32.0.tgz +0 -0
  95. package/components/tryghost-mailgun-client-5.32.0.tgz +0 -0
  96. package/components/tryghost-member-events-5.32.0.tgz +0 -0
  97. package/components/tryghost-members-stripe-service-5.32.0.tgz +0 -0
  98. package/components/tryghost-mw-api-version-mismatch-5.32.0.tgz +0 -0
  99. package/components/tryghost-mw-session-from-token-5.32.0.tgz +0 -0
  100. package/components/tryghost-mw-update-user-last-seen-5.32.0.tgz +0 -0
  101. package/components/tryghost-mw-vhost-5.32.0.tgz +0 -0
  102. package/components/tryghost-oembed-service-5.32.0.tgz +0 -0
  103. package/components/tryghost-referrers-5.32.0.tgz +0 -0
  104. package/components/tryghost-session-service-5.32.0.tgz +0 -0
  105. package/components/tryghost-staff-service-5.32.0.tgz +0 -0
  106. package/components/tryghost-webmentions-5.32.0.tgz +0 -0
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 400 400" style="enable-background:new 0 0 400 400" xml:space="preserve"><path style="fill-rule:evenodd;clip-rule:evenodd;fill:#635bff" d="M0 0h400v400H0z"/><path d="M184.4 155.5c0-9.4 7.7-13.1 20.5-13.1 18.4 0 41.6 5.6 60 15.5v-56.8C244.8 93.1 225 90 205 90c-49.1 0-81.7 25.6-81.7 68.4 0 66.7 91.9 56.1 91.9 84.9 0 11.1-9.7 14.7-23.2 14.7-20.1 0-45.7-8.2-66-19.3v57.5c22.5 9.7 45.2 13.8 66 13.8 50.3 0 84.9-24.9 84.9-68.2-.4-72-92.5-59.2-92.5-86.3z" style="fill-rule:evenodd;clip-rule:evenodd;fill:#fff"/></svg>
@@ -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.32%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.33%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" />
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-721a7adc4ca642c88e4ac85e1cb8b385.css" title="light">
40
+ <link integrity="" rel="stylesheet" href="assets/ghost-fb9fb8adbcaf1603ad4006dd2d49e401.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-0441964c34d58f2aacd5a04bbe240243.js"></script>
60
- <script src="assets/chunk.47.f231a64fe3fbaba23b84.js"></script>
61
- <script src="assets/chunk.143.ad05239cc363254caed7.js"></script>
62
- <script src="assets/ghost-4571ad33bd158ced8a9f5c13598fdb7f.js"></script>
60
+ <script src="assets/chunk.963.e47ead5abeca4cf69fed.js"></script>
61
+ <script src="assets/chunk.143.e158db597c343f4f132e.js"></script>
62
+ <script src="assets/ghost-86f1db1d00d5f0bc4f0cbdb39ff6c977.js"></script>
63
63
  </body>
64
64
  </html>
@@ -1,5 +1,6 @@
1
1
  const Command = require('./command');
2
2
  const DataGenerator = require('@tryghost/data-generator');
3
+ const config = require('../shared/config');
3
4
 
4
5
  module.exports = class REPL extends Command {
5
6
  setup() {
@@ -39,7 +40,8 @@ module.exports = class REPL extends Command {
39
40
  fatal: this.fatal,
40
41
  debug: this.debug
41
42
  },
42
- modelQuantities: {}
43
+ modelQuantities: {},
44
+ baseUrl: config.getSiteUrl()
43
45
  });
44
46
  try {
45
47
  await dataGenerator.importData();
@@ -55,7 +55,7 @@ class BaseSiteMapGenerator {
55
55
  // Generate full xml
56
56
  let sitemapXml = localUtils.getDeclarations() + xml(data);
57
57
 
58
- // Perform url transformatons
58
+ // Perform url transformations
59
59
  // - Necessary because sitemap data is supplied by the router which
60
60
  // uses knex directly bypassing model-layer attribute transforms
61
61
  sitemapXml = urlUtils.transformReadyToAbsolute(sitemapXml);
@@ -63,6 +63,15 @@ class BaseSiteMapGenerator {
63
63
  return sitemapXml;
64
64
  }
65
65
 
66
+ updateURL(datum) {
67
+ const url = this.nodeLookup[datum.id]?.url[0].loc;
68
+
69
+ if (url) {
70
+ this.removeUrl(url, datum);
71
+ this.addUrl(url, datum);
72
+ }
73
+ }
74
+
66
75
  addUrl(url, datum) {
67
76
  const node = this.createUrlNodeFromDatum(url, datum);
68
77
 
@@ -100,6 +109,12 @@ class BaseSiteMapGenerator {
100
109
  }
101
110
  }
102
111
 
112
+ /**
113
+ *
114
+ * @param {String} url
115
+ * @param {Object} datum
116
+ * @returns
117
+ */
103
118
  createUrlNodeFromDatum(url, datum) {
104
119
  let node;
105
120
  let imgNode;
@@ -1,3 +1,5 @@
1
+ const DomainEvents = require('@tryghost/domain-events');
2
+ const {URLResourceUpdatedEvent} = require('@tryghost/dynamic-routing-events');
1
3
  const IndexMapGenerator = require('./index-generator');
2
4
  const PagesMapGenerator = require('./page-generator');
3
5
  const PostsMapGenerator = require('./post-generator');
@@ -29,6 +31,10 @@ class SiteMapManager {
29
31
  }
30
32
  });
31
33
 
34
+ DomainEvents.subscribe(URLResourceUpdatedEvent, (event) => {
35
+ this[event.data.resourceType].updateURL(event.data);
36
+ });
37
+
32
38
  events.on('url.added', (obj) => {
33
39
  this[obj.resource.config.type].addUrl(obj.url.absolute, obj.resource.data);
34
40
  });
@@ -7,7 +7,7 @@ const jobsService = require('../../jobs');
7
7
  let hasScheduled = false;
8
8
 
9
9
  module.exports = {
10
- async scheduleRecurringJobs() {
10
+ async scheduleRecurringJobs(skipEmailCheck = false) {
11
11
  if (
12
12
  !hasScheduled &&
13
13
  config.get('emailAnalytics') &&
@@ -17,10 +17,10 @@ module.exports = {
17
17
  // Don't register email analytics job if we have no emails,
18
18
  // processor usage from many sites spinning up threads can be high.
19
19
  // Mega service will re-run this scheduling task when an email is sent
20
- const emailCount = await models.Email
20
+ const emailCount = skipEmailCheck ? 1 : (await models.Email
21
21
  .where('created_at', '>', moment.utc().subtract(30, 'days').toDate())
22
22
  .where('status', '<>', 'failed')
23
- .count();
23
+ .count());
24
24
 
25
25
  if (emailCount > 0) {
26
26
  // use a random seconds value to avoid spikes to external APIs on the minute
@@ -35,6 +35,7 @@ class EmailServiceWrapper {
35
35
  const linkTracking = require('../link-tracking');
36
36
  const audienceFeedback = require('../audience-feedback');
37
37
  const storageUtils = require('../../adapters/storage/utils');
38
+ const emailAnalyticsJobs = require('../email-analytics/jobs');
38
39
 
39
40
  // capture errors from mailgun client and log them in sentry
40
41
  const errorHandler = (error) => {
@@ -103,7 +104,8 @@ class EmailServiceWrapper {
103
104
  emailSegmenter,
104
105
  limitService,
105
106
  membersRepository,
106
- verificationTrigger: membersService.verificationTrigger
107
+ verificationTrigger: membersService.verificationTrigger,
108
+ emailAnalyticsJobs
107
109
  });
108
110
 
109
111
  this.controller = new EmailController(this.service, {
@@ -21,12 +21,17 @@ module.exports = class BookshelfMentionRepository {
21
21
  /** @type {Object} */
22
22
  #MentionModel;
23
23
 
24
+ /** @type {import('@tryghost/domain-events')} */
25
+ #DomainEvents;
26
+
24
27
  /**
25
28
  * @param {object} deps
26
29
  * @param {object} deps.MentionModel Bookshelf Model
30
+ * @param {import('@tryghost/domain-events')} deps.DomainEvents
27
31
  */
28
32
  constructor(deps) {
29
33
  this.#MentionModel = deps.MentionModel;
34
+ this.#DomainEvents = deps.DomainEvents;
30
35
  }
31
36
 
32
37
  #modelToMention(model) {
@@ -113,5 +118,8 @@ module.exports = class BookshelfMentionRepository {
113
118
  id: data.id
114
119
  });
115
120
  }
121
+ for (const event of mention.events) {
122
+ this.#DomainEvents.dispatch(event);
123
+ }
116
124
  }
117
125
  };
@@ -0,0 +1,45 @@
1
+ const ObjectID = require('bson-objectid').default;
2
+
3
+ /**
4
+ * @typedef {import('@tryghost/webmentions/lib/MentionsAPI').IResourceService} IResourceService
5
+ */
6
+
7
+ /**
8
+ * @implements {IResourceService}
9
+ */
10
+ module.exports = class ResourceService {
11
+ /** @type {import('@tryghost/url-utils/lib/url-utils')} */
12
+ #urlUtils;
13
+
14
+ /** @type {import('../url')} */
15
+ #urlService;
16
+
17
+ /**
18
+ * @param {object} deps
19
+ * @param {import('@tryghost/url-utils/lib/url-utils')} deps.urlUtils
20
+ * @param {import('../url')} deps.urlService
21
+ */
22
+ constructor(deps) {
23
+ this.#urlUtils = deps.urlUtils;
24
+ this.#urlService = deps.urlService;
25
+ }
26
+
27
+ /**
28
+ * @param {URL} url
29
+ * @returns {Promise<import('@tryghost/webmentions/lib/MentionsAPI').ResourceResult>}
30
+ */
31
+ async getByURL(url) {
32
+ const path = this.#urlUtils.absoluteToRelative(url.href, {withoutSubdirectory: true});
33
+ const resource = this.#urlService.getResource(path);
34
+ if (resource?.config?.type === 'posts') {
35
+ return {
36
+ type: 'post',
37
+ id: ObjectID.createFromHexString(resource.data.id)
38
+ };
39
+ }
40
+ return {
41
+ type: null,
42
+ id: null
43
+ };
44
+ }
45
+ };
@@ -0,0 +1,78 @@
1
+ const logging = require('@tryghost/logging');
2
+
3
+ /**
4
+ * @typedef {import('@tryghost/webmentions/lib/MentionsAPI').IRoutingService} IRoutingService
5
+ * @typedef {import('@tryghost/webmentions/lib/MentionsAPI').IResourceService} IResourceService
6
+ */
7
+
8
+ /**
9
+ * @typedef {object} IUrlUtils
10
+ * @prop {() => string} getSiteUrl
11
+ * @prop {() => string} getSubdir
12
+ */
13
+
14
+ /**
15
+ * @implements {IRoutingService}
16
+ */
17
+ module.exports = class RoutingService {
18
+ /** @typedef {URL} */
19
+ #siteUrl;
20
+
21
+ /** @typedef {IResourceService} */
22
+ #resourceService;
23
+
24
+ /** @typedef {import('got')} */
25
+ #externalRequest;
26
+
27
+ /**
28
+ * @param {object} deps
29
+ * @param {URL} deps.siteUrl
30
+ * @param {IResourceService} deps.resourceService
31
+ * @param {import('got')} deps.externalRequest;
32
+ */
33
+ constructor(deps) {
34
+ this.#siteUrl = deps.siteUrl;
35
+ this.#resourceService = deps.resourceService;
36
+ this.#externalRequest = deps.externalRequest;
37
+ }
38
+
39
+ /**
40
+ * @param {URL} url
41
+ */
42
+ async pageExists(url) {
43
+ if (this.#siteUrl.origin !== url.origin) {
44
+ return false;
45
+ }
46
+ const subdir = removeTrailingSlash(this.#siteUrl.pathname);
47
+ if (subdir && !url.pathname.startsWith(subdir)) {
48
+ return false;
49
+ }
50
+
51
+ const resource = await this.#resourceService.getByURL(url);
52
+
53
+ if (resource?.type !== null) {
54
+ return true;
55
+ }
56
+
57
+ try {
58
+ const response = await this.#externalRequest.head(url, {
59
+ followRedirect: false
60
+ });
61
+ if (response.statusCode < 400 && response.statusCode > 199) {
62
+ return true;
63
+ } else {
64
+ return false;
65
+ }
66
+ } catch (err) {
67
+ logging.error(err);
68
+ return false;
69
+ }
70
+ }
71
+ };
72
+
73
+ /**
74
+ * @param {string} str
75
+ */
76
+ function removeTrailingSlash(str) {
77
+ return str.replace(/\/$/, '');
78
+ }
@@ -1,4 +1,3 @@
1
- const ObjectID = require('bson-objectid').default;
2
1
  const MentionController = require('./MentionController');
3
2
  const WebmentionMetadata = require('./WebmentionMetadata');
4
3
  const {
@@ -7,6 +6,8 @@ const {
7
6
  MentionDiscoveryService
8
7
  } = require('@tryghost/webmentions');
9
8
  const BookshelfMentionRepository = require('./BookshelfMentionRepository');
9
+ const ResourceService = require('./ResourceService');
10
+ const RoutingService = require('./RoutingService');
10
11
  const models = require('../../models');
11
12
  const events = require('../../lib/common/events');
12
13
  const externalRequest = require('../../../server/lib/request-external.js');
@@ -14,53 +15,39 @@ const urlUtils = require('../../../shared/url-utils');
14
15
  const outputSerializerUrlUtil = require('../../../server/api/endpoints/utils/serializers/output/utils/url');
15
16
  const labs = require('../../../shared/labs');
16
17
  const urlService = require('../url');
18
+ const DomainEvents = require('@tryghost/domain-events');
17
19
 
18
20
  function getPostUrl(post) {
19
21
  const jsonModel = {};
20
22
  outputSerializerUrlUtil.forPost(post.id, jsonModel, {options: {}});
21
23
  return jsonModel.url;
22
24
  }
25
+
23
26
  module.exports = {
24
27
  controller: new MentionController(),
25
28
  async init() {
26
29
  const repository = new BookshelfMentionRepository({
27
- MentionModel: models.Mention
30
+ MentionModel: models.Mention,
31
+ DomainEvents
28
32
  });
29
33
  const webmentionMetadata = new WebmentionMetadata();
30
34
  const discoveryService = new MentionDiscoveryService({externalRequest});
35
+ const resourceService = new ResourceService({
36
+ urlUtils,
37
+ urlService
38
+ });
39
+
40
+ const routingService = new RoutingService({
41
+ siteUrl: new URL(urlUtils.getSiteUrl()),
42
+ resourceService,
43
+ externalRequest
44
+ });
45
+
31
46
  const api = new MentionsAPI({
32
47
  repository,
33
48
  webmentionMetadata,
34
- resourceService: {
35
- async getByURL(url) {
36
- const path = urlUtils.absoluteToRelative(url.href, {withoutSubdirectory: true});
37
- const resource = urlService.getResource(path);
38
- if (resource?.config?.type === 'posts') {
39
- return {
40
- type: 'post',
41
- id: ObjectID.createFromHexString(resource.data.id)
42
- };
43
- }
44
- return {
45
- type: null,
46
- id: null
47
- };
48
- }
49
- },
50
- routingService: {
51
- async pageExists(url) {
52
- const siteUrl = new URL(urlUtils.getSiteUrl());
53
- if (siteUrl.origin !== url.origin) {
54
- return false;
55
- }
56
- const subdir = urlUtils.getSubdir();
57
- if (subdir && !url.pathname.startsWith(subdir)) {
58
- return false;
59
- }
60
-
61
- return true;
62
- }
63
- }
49
+ resourceService,
50
+ routingService
64
51
  });
65
52
 
66
53
  this.controller.init({api});
@@ -1,4 +1,6 @@
1
1
  const DomainEvents = require('@tryghost/domain-events');
2
+ const labs = require('../../../shared/labs');
3
+
2
4
  class StaffServiceWrapper {
3
5
  init() {
4
6
  if (this.api) {
@@ -23,7 +25,8 @@ class StaffServiceWrapper {
23
25
  settingsHelpers,
24
26
  settingsCache,
25
27
  urlUtils,
26
- DomainEvents
28
+ DomainEvents,
29
+ labs
27
30
  });
28
31
 
29
32
  this.api.subscribeEvents();
@@ -1,5 +1,7 @@
1
1
  const _ = require('lodash');
2
2
  const debug = require('@tryghost/debug')('services:url:resources');
3
+ const DomainEvents = require('@tryghost/domain-events');
4
+ const {URLResourceUpdatedEvent} = require('@tryghost/dynamic-routing-events');
3
5
  const Resource = require('./Resource');
4
6
  const config = require('../../../shared/config');
5
7
  const models = require('../../models');
@@ -272,6 +274,21 @@ class Resources {
272
274
  }
273
275
  }
274
276
 
277
+ /**
278
+ *
279
+ * @param {Object} model resource model
280
+ * @returns
281
+ */
282
+ _containsRoutingAffectingChanges(model, ignoredProperties) {
283
+ if (model._changed && Object.keys(model._changed).length) {
284
+ return _.difference(Object.keys(model._changed), ignoredProperties).length !== 0;
285
+ }
286
+
287
+ // NOTE: returning true here as "_changed" property might not be available on attached/detached events
288
+ // assuming there were route affecting changes by default
289
+ return true;
290
+ }
291
+
275
292
  /**
276
293
  * @description Listener for "model updated" event.
277
294
  *
@@ -297,6 +314,22 @@ class Resources {
297
314
 
298
315
  const resourceConfig = _.find(this.resourcesConfig, {type: type});
299
316
 
317
+ // NOTE: check if any of the route-related fields were changed and only proceed if so
318
+ const ignoredProperties = [...resourceConfig.modelOptions.exclude, 'updated_at'];
319
+ if (!this._containsRoutingAffectingChanges(model, ignoredProperties)) {
320
+ const cachedResource = this.getByIdAndType(type, model.id);
321
+
322
+ if (cachedResource && model._changed && Object.keys(model._changed).includes('updated_at')) {
323
+ DomainEvents.dispatch(URLResourceUpdatedEvent.create(Object.assign(cachedResource.data, {
324
+ resourceType: cachedResource.config.type,
325
+ updated_at: model._changed.updated_at
326
+ })));
327
+ }
328
+
329
+ debug('skipping _onResourceUpdated because only non-route-related properties changed');
330
+ return false;
331
+ }
332
+
300
333
  // NOTE: synchronous handling for post and pages so that their URL is available without a delay
301
334
  // for more context and future improvements check https://github.com/TryGhost/Ghost/issues/10360
302
335
  if (['posts', 'pages'].includes(type)) {
@@ -32,6 +32,9 @@ class Urls {
32
32
  /**
33
33
  * @description Add a url to the system.
34
34
  * @param {Object} options
35
+ * @param {import('./Resource')} options.resource - instance of the Resource class
36
+ * @param {string} options.generatorId
37
+ * @param {string} options.url
35
38
  */
36
39
  add(options) {
37
40
  const url = options.url;
@@ -31,7 +31,8 @@ module.exports = [
31
31
  'twitter_title',
32
32
  'twitter_description',
33
33
  'custom_template',
34
- 'locale'
34
+ 'locale',
35
+ 'tiers'
35
36
  ],
36
37
  withRelated: ['tags', 'authors'],
37
38
  withRelatedPrimary: {
@@ -79,7 +80,8 @@ module.exports = [
79
80
  'tags',
80
81
  'authors',
81
82
  'primary_tag',
82
- 'primary_author'
83
+ 'primary_author',
84
+ 'tiers'
83
85
  ],
84
86
  filter: 'status:published+type:page'
85
87
  },
@@ -126,7 +128,8 @@ module.exports = [
126
128
  'accessibility',
127
129
  'meta_title',
128
130
  'meta_description',
129
- 'tour'
131
+ 'tour',
132
+ 'last_seen'
130
133
  ],
131
134
  filter: 'visibility:public',
132
135
  shouldHavePosts: {
@@ -35,6 +35,7 @@ const ALPHA_FEATURES = [
35
35
  'beforeAfterCard',
36
36
  'lexicalEditor',
37
37
  'webmentions',
38
+ 'webmentionEmail',
38
39
  'outboundLinkTagging'
39
40
  ];
40
41