ghost 5.115.0 → 5.116.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 (341) hide show
  1. package/components/{tryghost-api-framework-5.115.0.tgz → tryghost-api-framework-5.116.0.tgz} +0 -0
  2. package/components/tryghost-constants-5.116.0.tgz +0 -0
  3. package/components/tryghost-custom-fonts-5.116.0.tgz +0 -0
  4. package/components/{tryghost-custom-theme-settings-service-5.115.0.tgz → tryghost-custom-theme-settings-service-5.116.0.tgz} +0 -0
  5. package/components/tryghost-domain-events-5.116.0.tgz +0 -0
  6. package/components/{tryghost-donations-5.115.0.tgz → tryghost-donations-5.116.0.tgz} +0 -0
  7. package/components/tryghost-email-addresses-5.116.0.tgz +0 -0
  8. package/components/tryghost-email-service-5.116.0.tgz +0 -0
  9. package/components/tryghost-email-suppression-list-5.116.0.tgz +0 -0
  10. package/components/tryghost-html-to-plaintext-5.116.0.tgz +0 -0
  11. package/components/tryghost-i18n-5.116.0.tgz +0 -0
  12. package/components/tryghost-job-manager-5.116.0.tgz +0 -0
  13. package/components/tryghost-link-replacer-5.116.0.tgz +0 -0
  14. package/components/tryghost-magic-link-5.116.0.tgz +0 -0
  15. package/components/tryghost-member-attribution-5.116.0.tgz +0 -0
  16. package/components/tryghost-member-events-5.116.0.tgz +0 -0
  17. package/components/tryghost-members-api-5.116.0.tgz +0 -0
  18. package/components/tryghost-members-csv-5.116.0.tgz +0 -0
  19. package/components/{tryghost-members-offers-5.115.0.tgz → tryghost-members-offers-5.116.0.tgz} +0 -0
  20. package/components/{tryghost-milestones-5.115.0.tgz → tryghost-milestones-5.116.0.tgz} +0 -0
  21. package/components/{tryghost-mw-error-handler-5.115.0.tgz → tryghost-mw-error-handler-5.116.0.tgz} +0 -0
  22. package/components/tryghost-mw-vhost-5.116.0.tgz +0 -0
  23. package/components/{tryghost-post-events-5.115.0.tgz → tryghost-post-events-5.116.0.tgz} +0 -0
  24. package/components/{tryghost-post-revisions-5.115.0.tgz → tryghost-post-revisions-5.116.0.tgz} +0 -0
  25. package/components/tryghost-posts-service-5.116.0.tgz +0 -0
  26. package/components/{tryghost-prometheus-metrics-5.115.0.tgz → tryghost-prometheus-metrics-5.116.0.tgz} +0 -0
  27. package/components/tryghost-security-5.116.0.tgz +0 -0
  28. package/components/{tryghost-tiers-5.115.0.tgz → tryghost-tiers-5.116.0.tgz} +0 -0
  29. package/components/tryghost-webmentions-5.116.0.tgz +0 -0
  30. package/content/themes/casper/LICENSE +1 -1
  31. package/content/themes/casper/README.md +1 -1
  32. package/content/themes/source/LICENSE +1 -1
  33. package/content/themes/source/README.md +1 -1
  34. package/content/themes/source/assets/built/screen.css +1 -1
  35. package/content/themes/source/assets/built/screen.css.map +1 -1
  36. package/content/themes/source/assets/css/screen.css +11 -6
  37. package/content/themes/source/partials/feature-image.hbs +2 -2
  38. package/core/boot.js +3 -43
  39. package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +30494 -29403
  40. package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
  41. package/core/built/admin/assets/admin-x-demo/{index-0040480a.mjs → index-a9601514.mjs} +5 -4
  42. package/core/built/admin/assets/admin-x-demo/{modals-fb35c86c.mjs → modals-c1789d04.mjs} +67 -65
  43. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-806ef39c.mjs → CodeEditorView-e9c9deb8.mjs} +2 -2
  44. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
  45. package/core/built/admin/assets/admin-x-settings/{index-376f847c.mjs → index-84580c3a.mjs} +2 -2
  46. package/core/built/admin/assets/admin-x-settings/{index-8fa19303.mjs → index-f744cab7.mjs} +3147 -3123
  47. package/core/built/admin/assets/admin-x-settings/{modals-36775d71.mjs → modals-d9ca60c5.mjs} +1198 -1192
  48. package/core/built/admin/assets/chunk.524.8371443ef8f60db429d0.js +35 -0
  49. package/core/built/admin/assets/chunk.582.f90151775f2e53dd21d9.js +37 -0
  50. package/core/built/admin/assets/{chunk.874.461cb3cf5b6b36915f8c.js → chunk.713.e9027c0cc3c56110f5da.js} +125 -98
  51. package/core/built/admin/assets/{ghost-938b3d9c29e3564a53a22f8c8f82d351.js → ghost-03b64c086f3c60cabc85fe7a7e2b640a.js} +272 -251
  52. package/core/built/admin/assets/ghost-ba58e9822f7384461e926c7e23f04a75.css +1 -0
  53. package/core/built/admin/assets/ghost-dark-f1f29683b14ffa11615b3bba8b6ab92c.css +1 -0
  54. package/core/built/admin/assets/koenig-lexical/index.css +1 -1
  55. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +20563 -20891
  56. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +139 -139
  57. package/core/built/admin/assets/posts/posts.js +5732 -5667
  58. package/core/built/admin/assets/stats/stats.js +75373 -0
  59. package/core/built/admin/assets/{vendor-68a4aa424a179a90f5bbc2b750def576.js → vendor-72026232b36d97babc6320917c16c321.js} +36 -34
  60. package/core/built/admin/index.html +6 -6
  61. package/core/cli/generate-data.js +1 -1
  62. package/core/frontend/helpers/ghost_head.js +8 -1
  63. package/core/frontend/public/ghost-stats.js +55 -2
  64. package/core/frontend/services/assets-minification/AdminAuthAssets.js +2 -1
  65. package/core/frontend/services/assets-minification/CardAssets.js +1 -1
  66. package/core/frontend/services/assets-minification/CommentCountsAssets.js +1 -1
  67. package/core/frontend/services/assets-minification/MemberAttributionAssets.js +1 -1
  68. package/core/frontend/services/assets-minification/Minifier.js +191 -0
  69. package/core/frontend/services/routing/controllers/previews.js +2 -1
  70. package/core/frontend/src/cards/css/cta.css +1 -1
  71. package/core/server/adapters/cache/Redis.js +1 -1
  72. package/core/server/adapters/lib/redis/AdapterCacheRedis.js +287 -0
  73. package/core/server/adapters/lib/redis/redis-store-factory.js +22 -0
  74. package/core/server/api/endpoints/posts.js +9 -3
  75. package/core/server/api/endpoints/previews.js +35 -1
  76. package/core/server/api/endpoints/slugs.js +6 -2
  77. package/core/server/api/endpoints/utils/serializers/output/utils/post-gating.js +6 -9
  78. package/core/server/api/endpoints/utils/validators/input/settings.js +1 -1
  79. package/core/server/data/db/connection.js +2 -0
  80. package/core/server/data/db/index.js +1 -0
  81. package/core/server/data/importer/handlers/ImporterContentFileHandler.js +90 -0
  82. package/core/server/data/importer/import-manager.js +3 -3
  83. package/core/server/data/importer/importers/importer-revue.js +128 -0
  84. package/core/server/data/importer/importers/json-to-html.js +107 -0
  85. package/core/server/data/migrations/utils/tables.js +2 -4
  86. package/core/server/data/seeders/DataGenerator.js +288 -0
  87. package/core/server/data/seeders/importers/BenefitsImporter.js +28 -0
  88. package/core/server/data/seeders/importers/CommentsImporter.js +73 -0
  89. package/core/server/data/seeders/importers/EmailBatchesImporter.js +38 -0
  90. package/core/server/data/seeders/importers/EmailRecipientFailuresImporter.js +67 -0
  91. package/core/server/data/seeders/importers/EmailRecipientsImporter.js +212 -0
  92. package/core/server/data/seeders/importers/EmailsImporter.js +99 -0
  93. package/core/server/data/seeders/importers/LabelsImporter.js +41 -0
  94. package/core/server/data/seeders/importers/MembersClickEventsImporter.js +69 -0
  95. package/core/server/data/seeders/importers/MembersCreatedEventsImporter.js +103 -0
  96. package/core/server/data/seeders/importers/MembersFeedbackImporter.js +45 -0
  97. package/core/server/data/seeders/importers/MembersImporter.js +111 -0
  98. package/core/server/data/seeders/importers/MembersLabelsImporter.js +39 -0
  99. package/core/server/data/seeders/importers/MembersLoginEventsImporter.js +69 -0
  100. package/core/server/data/seeders/importers/MembersNewslettersImporter.js +38 -0
  101. package/core/server/data/seeders/importers/MembersPaidSubscriptionEventsImporter.js +99 -0
  102. package/core/server/data/seeders/importers/MembersProductsImporter.js +42 -0
  103. package/core/server/data/seeders/importers/MembersStatusEventsImporter.js +58 -0
  104. package/core/server/data/seeders/importers/MembersStripeCustomersImporter.js +60 -0
  105. package/core/server/data/seeders/importers/MembersStripeCustomersSubscriptionsImporter.js +259 -0
  106. package/core/server/data/seeders/importers/MembersSubscribeEventsImporter.js +69 -0
  107. package/core/server/data/seeders/importers/MembersSubscriptionCreatedEventsImporter.js +95 -0
  108. package/core/server/data/seeders/importers/NewslettersImporter.js +40 -0
  109. package/core/server/data/seeders/importers/OffersImporter.js +70 -0
  110. package/core/server/data/seeders/importers/PostsAuthorsImporter.js +32 -0
  111. package/core/server/data/seeders/importers/PostsImporter.js +102 -0
  112. package/core/server/data/seeders/importers/PostsProductsImporter.js +35 -0
  113. package/core/server/data/seeders/importers/PostsTagsImporter.js +46 -0
  114. package/core/server/data/seeders/importers/ProductsBenefitsImporter.js +54 -0
  115. package/core/server/data/seeders/importers/ProductsImporter.js +90 -0
  116. package/core/server/data/seeders/importers/RecommendationClickEventsImporter.js +32 -0
  117. package/core/server/data/seeders/importers/RecommendationSubscribeEventsImporter.js +32 -0
  118. package/core/server/data/seeders/importers/RecommendationsImporter.js +34 -0
  119. package/core/server/data/seeders/importers/RedirectsImporter.js +49 -0
  120. package/core/server/data/seeders/importers/RolesUsersImporter.js +42 -0
  121. package/core/server/data/seeders/importers/StripePricesImporter.js +69 -0
  122. package/core/server/data/seeders/importers/StripeProductsImporter.js +34 -0
  123. package/core/server/data/seeders/importers/TableImporter.js +187 -0
  124. package/core/server/data/seeders/importers/TagsImporter.js +41 -0
  125. package/core/server/data/seeders/importers/UsersImporter.js +31 -0
  126. package/core/server/data/seeders/importers/WebMentionsImporter.js +42 -0
  127. package/core/server/data/seeders/importers/index.js +41 -0
  128. package/core/server/data/seeders/utils/JsonImporter.js +39 -0
  129. package/core/server/data/seeders/utils/blog-info.js +3 -0
  130. package/core/server/data/seeders/utils/database-date.js +7 -0
  131. package/core/server/data/seeders/utils/event-generator.js +48 -0
  132. package/core/server/data/seeders/utils/random.js +13 -0
  133. package/core/server/data/seeders/utils/topological-sort.js +33 -0
  134. package/core/server/lib/bootstrap-socket.js +87 -0
  135. package/core/server/lib/package-json/index.js +1 -0
  136. package/core/server/lib/package-json/package-json.js +160 -0
  137. package/core/server/lib/package-json/parse.js +57 -0
  138. package/core/server/models/base/plugins/actions.js +44 -31
  139. package/core/server/models/base/plugins/generate-slug.js +6 -0
  140. package/core/server/notify.js +1 -1
  141. package/core/server/services/activitypub/ActivityPubService.ts +1 -1
  142. package/core/server/services/adapter-manager/AdapterManager.js +161 -0
  143. package/core/server/services/adapter-manager/index.js +1 -1
  144. package/core/server/services/announcement-bar-service/AnnouncementBarSettings.js +54 -0
  145. package/core/server/services/announcement-bar-service/AnnouncementVisibilityValues.js +11 -0
  146. package/core/server/services/announcement-bar-service/index.js +1 -1
  147. package/core/server/services/api-version-compatibility/APIVersionCompatibilityService.js +99 -0
  148. package/core/server/services/api-version-compatibility/VersionNotificationsDataService.js +80 -0
  149. package/core/server/services/api-version-compatibility/extract-api-key.js +57 -0
  150. package/core/server/services/api-version-compatibility/index.js +2 -2
  151. package/core/server/services/api-version-compatibility/mw-api-version-mismatch.js +31 -0
  152. package/core/server/services/audience-feedback/AudienceFeedbackController.js +85 -0
  153. package/core/server/services/audience-feedback/AudienceFeedbackService.js +34 -0
  154. package/core/server/services/audience-feedback/Feedback.js +35 -0
  155. package/core/server/services/audience-feedback/index.js +4 -2
  156. package/core/server/services/auth/session/emails/signin.js +168 -0
  157. package/core/server/services/auth/session/index.js +2 -2
  158. package/core/server/services/auth/session/session-from-token.js +69 -0
  159. package/core/server/services/auth/session/session-service.js +374 -0
  160. package/core/server/services/custom-redirects/index.js +1 -1
  161. package/core/server/services/email-analytics/EmailAnalyticsProviderMailgun.js +62 -0
  162. package/core/server/services/email-analytics/EmailAnalyticsService.js +552 -0
  163. package/core/server/services/email-analytics/EmailAnalyticsServiceWrapper.js +3 -3
  164. package/core/server/services/email-analytics/EventProcessingResult.js +66 -0
  165. package/core/server/services/email-service/EmailServiceWrapper.js +4 -4
  166. package/core/server/services/email-suppression-list/MailgunEmailSuppressionList.js +1 -1
  167. package/core/server/services/email-suppression-list/service.js +1 -1
  168. package/core/server/services/explore-ping/ExplorePingService.js +106 -0
  169. package/core/server/services/explore-ping/index.js +31 -0
  170. package/core/server/services/identity-tokens/IdentityTokenService.js +30 -0
  171. package/core/server/services/identity-tokens/IdentityTokenService.ts +28 -0
  172. package/core/server/services/identity-tokens/IdentityTokenServiceWrapper.js +1 -1
  173. package/core/server/services/invitations/accept.js +5 -2
  174. package/core/server/services/lib/DynamicRedirectManager.js +156 -0
  175. package/core/server/services/lib/EmailContentGenerator.js +54 -0
  176. package/core/server/services/lib/InMemoryRepository.js +62 -0
  177. package/core/server/services/lib/InMemoryRepository.ts +80 -0
  178. package/core/server/services/lib/MailgunClient.js +364 -0
  179. package/core/server/services/link-redirection/LinkRedirect.js +26 -0
  180. package/core/server/services/link-redirection/LinkRedirectRepository.js +7 -7
  181. package/core/server/services/link-redirection/LinkRedirectsService.js +123 -0
  182. package/core/server/services/link-redirection/README.md +151 -0
  183. package/core/server/services/link-redirection/RedirectEvent.js +24 -0
  184. package/core/server/services/link-redirection/index.js +1 -1
  185. package/core/server/services/link-tracking/LinkClickTrackingService.js +1 -1
  186. package/core/server/services/mail/index.js +1 -1
  187. package/core/server/services/mail-events/BookshelfMailEventRepository.js +2 -2
  188. package/core/server/services/mail-events/InMemoryMailEventRepository.js +10 -0
  189. package/core/server/services/mail-events/InMemoryMailEventRepository.ts +8 -0
  190. package/core/server/services/mail-events/MailEvent.js +20 -0
  191. package/core/server/services/mail-events/MailEvent.ts +10 -0
  192. package/core/server/services/mail-events/MailEventRepository.js +2 -0
  193. package/core/server/services/mail-events/MailEventRepository.ts +5 -0
  194. package/core/server/services/mail-events/MailEventService.js +124 -0
  195. package/core/server/services/mail-events/MailEventService.ts +169 -0
  196. package/core/server/services/mail-events/index.js +1 -1
  197. package/core/server/services/mail-events/libraries.d.ts +2 -0
  198. package/core/server/services/members/CaptchaService.js +80 -0
  199. package/core/server/services/members/api.js +1 -1
  200. package/core/server/services/members/importer/MembersCSVImporter.js +464 -0
  201. package/core/server/services/members/importer/MembersCSVImporterStripeUtils.js +194 -0
  202. package/core/server/services/members/importer/email-template.js +182 -0
  203. package/core/server/services/members/importer/index.js +30 -0
  204. package/core/server/services/members/members-ssr.js +333 -0
  205. package/core/server/services/members/service.js +2 -2
  206. package/core/server/services/members-events/LastSeenAtUpdater.js +1 -1
  207. package/core/server/services/offers/service.js +1 -1
  208. package/core/server/services/posts/stats/PostStats.js +13 -0
  209. package/core/server/services/recommendations/RecommendationServiceWrapper.js +8 -8
  210. package/core/server/services/recommendations/service/BookshelfClickEventRepository.js +48 -0
  211. package/core/server/services/recommendations/service/BookshelfClickEventRepository.ts +49 -0
  212. package/core/server/services/recommendations/service/BookshelfRecommendationRepository.js +98 -0
  213. package/core/server/services/recommendations/service/BookshelfRecommendationRepository.ts +117 -0
  214. package/core/server/services/recommendations/service/BookshelfRepository.js +134 -0
  215. package/core/server/services/recommendations/service/BookshelfRepository.ts +196 -0
  216. package/core/server/services/recommendations/service/BookshelfSubscribeEventRepository.js +48 -0
  217. package/core/server/services/recommendations/service/BookshelfSubscribeEventRepository.ts +49 -0
  218. package/core/server/services/recommendations/service/ClickEvent.js +33 -0
  219. package/core/server/services/recommendations/service/ClickEvent.ts +32 -0
  220. package/core/server/services/recommendations/service/InMemoryRecommendationRepository.js +19 -0
  221. package/core/server/services/recommendations/service/InMemoryRecommendationRepository.ts +20 -0
  222. package/core/server/services/recommendations/service/IncomingRecommendationController.js +34 -0
  223. package/core/server/services/recommendations/service/IncomingRecommendationController.ts +51 -0
  224. package/core/server/services/recommendations/service/IncomingRecommendationEmailRenderer.js +25 -0
  225. package/core/server/services/recommendations/service/IncomingRecommendationEmailRenderer.ts +37 -0
  226. package/core/server/services/recommendations/service/IncomingRecommendationService.js +93 -0
  227. package/core/server/services/recommendations/service/IncomingRecommendationService.ts +160 -0
  228. package/core/server/services/recommendations/service/Recommendation.js +140 -0
  229. package/core/server/services/recommendations/service/Recommendation.ts +201 -0
  230. package/core/server/services/recommendations/service/RecommendationController.js +208 -0
  231. package/core/server/services/recommendations/service/RecommendationController.ts +258 -0
  232. package/core/server/services/recommendations/service/RecommendationMetadataService.js +86 -0
  233. package/core/server/services/recommendations/service/RecommendationMetadataService.ts +128 -0
  234. package/core/server/services/recommendations/service/RecommendationRepository.js +2 -0
  235. package/core/server/services/recommendations/service/RecommendationRepository.ts +13 -0
  236. package/core/server/services/recommendations/service/RecommendationService.js +228 -0
  237. package/core/server/services/recommendations/service/RecommendationService.ts +281 -0
  238. package/core/server/services/recommendations/service/SubscribeEvent.js +33 -0
  239. package/core/server/services/recommendations/service/SubscribeEvent.ts +32 -0
  240. package/core/server/services/recommendations/service/UnsafeData.js +183 -0
  241. package/core/server/services/recommendations/service/UnsafeData.ts +217 -0
  242. package/core/server/services/recommendations/service/WellknownService.js +36 -0
  243. package/core/server/services/recommendations/service/WellknownService.ts +47 -0
  244. package/core/server/services/recommendations/service/index.js +31 -0
  245. package/core/server/services/recommendations/service/index.ts +15 -0
  246. package/core/server/services/recommendations/service/libraries.d.ts +5 -0
  247. package/core/server/services/route-settings/SettingsPathManager.js +47 -0
  248. package/core/server/services/route-settings/index.js +1 -1
  249. package/core/server/services/slack-notifications/SlackNotifications.js +211 -0
  250. package/core/server/services/slack-notifications/SlackNotificationsService.js +90 -0
  251. package/core/server/services/slack-notifications/service.js +4 -6
  252. package/core/server/services/stripe/README.md +63 -0
  253. package/core/server/services/stripe/StripeAPI.js +931 -0
  254. package/core/server/services/stripe/StripeMigrations.js +613 -0
  255. package/core/server/services/stripe/StripeService.js +175 -0
  256. package/core/server/services/stripe/WebhookController.js +100 -0
  257. package/core/server/services/stripe/WebhookManager.js +175 -0
  258. package/core/server/services/stripe/events/StripeLiveDisabledEvent.js +23 -0
  259. package/core/server/services/stripe/events/StripeLiveEnabledEvent.js +23 -0
  260. package/core/server/services/stripe/events/index.js +4 -0
  261. package/core/server/services/stripe/service.js +1 -1
  262. package/core/server/services/stripe/services/webhook/CheckoutSessionEventService.js +255 -0
  263. package/core/server/services/stripe/services/webhook/InvoiceEventService.js +70 -0
  264. package/core/server/services/stripe/services/webhook/SubscriptionEventService.js +54 -0
  265. package/core/server/services/themes/loader.js +1 -1
  266. package/core/server/services/themes/to-json.js +1 -1
  267. package/core/server/web/api/endpoints/admin/app.js +1 -21
  268. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  269. package/core/server/web/api/middleware/version-match.js +41 -0
  270. package/core/server/web/shared/middleware/cache-control.js +51 -0
  271. package/core/server/web/shared/middleware/index.js +1 -1
  272. package/core/server/web/well-known.js +1 -1
  273. package/core/shared/labs.js +5 -3
  274. package/core/shared/settings-cache/CacheManager.js +64 -6
  275. package/package.json +98 -146
  276. package/tsconfig.tsbuildinfo +1 -1
  277. package/yarn.lock +1478 -1634
  278. package/components/tryghost-adapter-cache-redis-5.115.0.tgz +0 -0
  279. package/components/tryghost-adapter-manager-5.115.0.tgz +0 -0
  280. package/components/tryghost-announcement-bar-settings-5.115.0.tgz +0 -0
  281. package/components/tryghost-api-version-compatibility-service-5.115.0.tgz +0 -0
  282. package/components/tryghost-audience-feedback-5.115.0.tgz +0 -0
  283. package/components/tryghost-bookshelf-repository-5.115.0.tgz +0 -0
  284. package/components/tryghost-bootstrap-socket-5.115.0.tgz +0 -0
  285. package/components/tryghost-captcha-service-5.115.0.tgz +0 -0
  286. package/components/tryghost-constants-5.115.0.tgz +0 -0
  287. package/components/tryghost-custom-fonts-5.115.0.tgz +0 -0
  288. package/components/tryghost-data-generator-5.115.0.tgz +0 -0
  289. package/components/tryghost-domain-events-5.115.0.tgz +0 -0
  290. package/components/tryghost-email-addresses-5.115.0.tgz +0 -0
  291. package/components/tryghost-email-analytics-provider-mailgun-5.115.0.tgz +0 -0
  292. package/components/tryghost-email-analytics-service-5.115.0.tgz +0 -0
  293. package/components/tryghost-email-content-generator-5.115.0.tgz +0 -0
  294. package/components/tryghost-email-events-5.115.0.tgz +0 -0
  295. package/components/tryghost-email-service-5.115.0.tgz +0 -0
  296. package/components/tryghost-email-suppression-list-5.115.0.tgz +0 -0
  297. package/components/tryghost-express-dynamic-redirects-5.115.0.tgz +0 -0
  298. package/components/tryghost-extract-api-key-5.115.0.tgz +0 -0
  299. package/components/tryghost-ghost-5.115.0.tgz +0 -0
  300. package/components/tryghost-html-to-plaintext-5.115.0.tgz +0 -0
  301. package/components/tryghost-i18n-5.115.0.tgz +0 -0
  302. package/components/tryghost-identity-token-service-5.115.0.tgz +0 -0
  303. package/components/tryghost-importer-handler-content-files-5.115.0.tgz +0 -0
  304. package/components/tryghost-importer-revue-5.115.0.tgz +0 -0
  305. package/components/tryghost-in-memory-repository-5.115.0.tgz +0 -0
  306. package/components/tryghost-job-manager-5.115.0.tgz +0 -0
  307. package/components/tryghost-link-redirects-5.115.0.tgz +0 -0
  308. package/components/tryghost-link-replacer-5.115.0.tgz +0 -0
  309. package/components/tryghost-magic-link-5.115.0.tgz +0 -0
  310. package/components/tryghost-mail-events-5.115.0.tgz +0 -0
  311. package/components/tryghost-mailgun-client-5.115.0.tgz +0 -0
  312. package/components/tryghost-member-attribution-5.115.0.tgz +0 -0
  313. package/components/tryghost-member-events-5.115.0.tgz +0 -0
  314. package/components/tryghost-members-api-5.115.0.tgz +0 -0
  315. package/components/tryghost-members-csv-5.115.0.tgz +0 -0
  316. package/components/tryghost-members-importer-5.115.0.tgz +0 -0
  317. package/components/tryghost-members-payments-5.115.0.tgz +0 -0
  318. package/components/tryghost-members-ssr-5.115.0.tgz +0 -0
  319. package/components/tryghost-members-stripe-service-5.115.0.tgz +0 -0
  320. package/components/tryghost-minifier-5.115.0.tgz +0 -0
  321. package/components/tryghost-mw-api-version-mismatch-5.115.0.tgz +0 -0
  322. package/components/tryghost-mw-cache-control-5.115.0.tgz +0 -0
  323. package/components/tryghost-mw-session-from-token-5.115.0.tgz +0 -0
  324. package/components/tryghost-mw-update-user-last-seen-5.115.0.tgz +0 -0
  325. package/components/tryghost-mw-version-match-5.115.0.tgz +0 -0
  326. package/components/tryghost-mw-vhost-5.115.0.tgz +0 -0
  327. package/components/tryghost-package-json-5.115.0.tgz +0 -0
  328. package/components/tryghost-posts-service-5.115.0.tgz +0 -0
  329. package/components/tryghost-recommendations-5.115.0.tgz +0 -0
  330. package/components/tryghost-referrers-5.115.0.tgz +0 -0
  331. package/components/tryghost-security-5.115.0.tgz +0 -0
  332. package/components/tryghost-session-service-5.115.0.tgz +0 -0
  333. package/components/tryghost-settings-path-manager-5.115.0.tgz +0 -0
  334. package/components/tryghost-slack-notifications-5.115.0.tgz +0 -0
  335. package/components/tryghost-version-notifications-data-service-5.115.0.tgz +0 -0
  336. package/components/tryghost-webmentions-5.115.0.tgz +0 -0
  337. package/core/built/admin/assets/chunk.524.31419fdf6fb3859ecc1e.js +0 -35
  338. package/core/built/admin/assets/chunk.582.08c816d5e4ab766486a7.js +0 -37
  339. package/core/built/admin/assets/ghost-c2a7c4a1b76550c4219adb2ed4124ce0.css +0 -1
  340. package/core/built/admin/assets/ghost-dark-f91e4a479c6d38d94d5d1b14727871dc.css +0 -1
  341. /package/core/built/admin/assets/{chunk.874.461cb3cf5b6b36915f8c.js.LICENSE.txt → chunk.713.e9027c0cc3c56110f5da.js.LICENSE.txt} +0 -0
@@ -0,0 +1,364 @@
1
+ const _ = require('lodash');
2
+ const debug = require('@tryghost/debug');
3
+ const logging = require('@tryghost/logging');
4
+ const metrics = require('@tryghost/metrics');
5
+ const errors = require('@tryghost/errors');
6
+
7
+ module.exports = class MailgunClient {
8
+ #config;
9
+ #settings;
10
+
11
+ static DEFAULT_BATCH_SIZE = 1000;
12
+
13
+ constructor({config, settings}) {
14
+ this.#config = config;
15
+ this.#settings = settings;
16
+ }
17
+
18
+ /**
19
+ * Creates the data payload and sends to Mailgun
20
+ *
21
+ * @param {Object} message
22
+ * @param {Object} recipientData
23
+ * @param {Array<Object>} replacements
24
+ *
25
+ * recipientData format:
26
+ * {
27
+ * 'test@example.com': {
28
+ * name: 'Test User',
29
+ * unsubscribe_url: 'https://example.com/unsub/me',
30
+ * list_unsubscribe: 'https://example.com/unsub/me'
31
+ * }
32
+ * }
33
+ */
34
+ async send(message, recipientData, replacements) {
35
+ const mailgunInstance = this.getInstance();
36
+ if (!mailgunInstance) {
37
+ logging.warn(`Mailgun is not configured`);
38
+ return null;
39
+ }
40
+
41
+ const batchSize = this.getBatchSize();
42
+ if (Object.keys(recipientData).length > batchSize) {
43
+ throw new errors.IncorrectUsageError({
44
+ message: `Mailgun only supports sending to ${batchSize} recipients at a time`
45
+ });
46
+ }
47
+
48
+ let messageData = {};
49
+
50
+ let startTime;
51
+ try {
52
+ const bulkEmailConfig = this.#config.get('bulkEmail');
53
+ const messageContent = _.pick(message, 'subject', 'html', 'plaintext');
54
+
55
+ // update content to use Mailgun variable syntax for replacements
56
+ replacements.forEach((replacement) => {
57
+ messageContent[replacement.format] = messageContent[replacement.format].replace(
58
+ replacement.regexp,
59
+ `%recipient.${replacement.id}%`
60
+ );
61
+ });
62
+
63
+ messageData = {
64
+ to: Object.keys(recipientData),
65
+ from: message.from,
66
+ 'h:Reply-To': message.replyTo || message.reply_to,
67
+ subject: messageContent.subject,
68
+ html: messageContent.html,
69
+ text: messageContent.plaintext,
70
+ 'recipient-variables': JSON.stringify(recipientData)
71
+ };
72
+
73
+ // Do we have a custom List-Unsubscribe header set?
74
+ // (we need a variable for this, as this is a per-email setting)
75
+ if (Object.keys(recipientData)[0] && recipientData[Object.keys(recipientData)[0]].list_unsubscribe) {
76
+ messageData['h:List-Unsubscribe'] = '<%recipient.list_unsubscribe%>, <%tag_unsubscribe_email%>';
77
+ messageData['h:List-Unsubscribe-Post'] = 'List-Unsubscribe=One-Click';
78
+ }
79
+
80
+ // add a reference to the original email record for easier mapping of mailgun event -> email
81
+ if (message.id) {
82
+ messageData['v:email-id'] = message.id;
83
+ }
84
+
85
+ const tags = ['bulk-email', 'ghost-email'];
86
+ if (bulkEmailConfig?.mailgun?.tag) {
87
+ tags.push(bulkEmailConfig.mailgun.tag);
88
+ }
89
+ messageData['o:tag'] = tags;
90
+
91
+ if (bulkEmailConfig?.mailgun?.testmode) {
92
+ messageData['o:testmode'] = true;
93
+ }
94
+
95
+ // enable tracking if turned on for this email
96
+ if (message.track_opens) {
97
+ messageData['o:tracking-opens'] = true;
98
+ }
99
+
100
+ // set the delivery time if specified
101
+ if (message.deliveryTime && message.deliveryTime instanceof Date) {
102
+ messageData['o:deliverytime'] = message.deliveryTime.toUTCString();
103
+ }
104
+
105
+ const mailgunConfig = this.#getConfig();
106
+ startTime = Date.now();
107
+ const response = await mailgunInstance.messages.create(mailgunConfig.domain, messageData);
108
+ metrics.metric('mailgun-send-mail', {
109
+ value: Date.now() - startTime,
110
+ statusCode: 200
111
+ });
112
+
113
+ return {
114
+ id: response.id
115
+ };
116
+ } catch (error) {
117
+ logging.error(error);
118
+ metrics.metric('mailgun-send-mail', {
119
+ value: Date.now() - startTime,
120
+ statusCode: error.status
121
+ });
122
+ return Promise.reject({error, messageData});
123
+ }
124
+ }
125
+
126
+ /**
127
+ * @param {import('mailgun.js').default} mailgunInstance
128
+ * @param {Object} mailgunConfig
129
+ * @param {Object} mailgunOptions
130
+ */
131
+ async getEventsFromMailgun(mailgunInstance, mailgunConfig, mailgunOptions) {
132
+ const startTime = Date.now();
133
+ try {
134
+ const page = await mailgunInstance.events.get(mailgunConfig.domain, mailgunOptions);
135
+ metrics.metric('mailgun-get-events', {
136
+ value: Date.now() - startTime,
137
+ statusCode: 200
138
+ });
139
+ return page;
140
+ } catch (error) {
141
+ metrics.metric('mailgun-get-events', {
142
+ value: Date.now() - startTime,
143
+ statusCode: error.status
144
+ });
145
+ throw error;
146
+ }
147
+ }
148
+
149
+ /**
150
+ * Fetches events from Mailgun
151
+ * @param {Object} mailgunOptions
152
+ * @param {Function} batchHandler
153
+ * @param {Object} options
154
+ * @param {Number} options.maxEvents Not a strict maximum. We stop fetching after we reached the maximum AND received at least one event after begin (not equal) to prevent deadlocks.
155
+ * @returns {Promise<void>}
156
+ */
157
+ async fetchEvents(mailgunOptions, batchHandler, {maxEvents = Infinity} = {}) {
158
+ const mailgunInstance = this.getInstance();
159
+ if (!mailgunInstance) {
160
+ logging.warn(`Mailgun is not configured`);
161
+ return;
162
+ }
163
+
164
+ debug(`[MailgunClient fetchEvents]: starting fetching first events page`);
165
+ const mailgunConfig = this.#getConfig();
166
+ const startDate = new Date();
167
+ const overallStartTime = Date.now();
168
+
169
+ let batchCount = 0;
170
+ let totalBatchTime = 0;
171
+
172
+ try {
173
+ let page = await this.getEventsFromMailgun(mailgunInstance, mailgunConfig, mailgunOptions);
174
+
175
+ // By limiting the processed events to ones created before this job started we cancel early ready for the next job run.
176
+ // Avoids chance of events being missed in long job runs due to mailgun's eventual-consistency creating events outside of our 30min sliding re-check window
177
+ let events = (page?.items?.map(this.normalizeEvent) || []).filter(e => !!e && e.timestamp <= startDate);
178
+ debug(`[MailgunClient fetchEvents]: finished fetching first page with ${events.length} events`);
179
+
180
+ let eventCount = 0;
181
+ const beginTimestamp = mailgunOptions.begin ? Math.ceil(mailgunOptions.begin * 1000) : undefined; // ceil here if we have rounding errors
182
+
183
+ while (events.length !== 0) {
184
+ const batchStartTime = Date.now();
185
+ await batchHandler(events);
186
+ const batchEndTime = Date.now();
187
+ const batchDuration = batchEndTime - batchStartTime;
188
+
189
+ batchCount += 1;
190
+ totalBatchTime += batchDuration;
191
+
192
+ eventCount += events.length;
193
+
194
+ if (eventCount >= maxEvents && (!beginTimestamp || !events[events.length - 1].timestamp || (events[events.length - 1].timestamp.getTime() > beginTimestamp))) {
195
+ break;
196
+ }
197
+
198
+ const nextPageId = page.pages.next.page;
199
+ debug(`[MailgunClient fetchEvents]: starting fetching next page ${nextPageId}`);
200
+ page = await this.getEventsFromMailgun(mailgunInstance, mailgunConfig, {
201
+ page: nextPageId,
202
+ ...mailgunOptions
203
+ });
204
+
205
+ // We need to cap events at the time we started fetching them (see comment above)
206
+ events = (page?.items?.map(this.normalizeEvent) || []).filter(e => !!e && e.timestamp <= startDate);
207
+ debug(`[MailgunClient fetchEvents]: finished fetching next page with ${events.length} events`);
208
+ }
209
+
210
+ const overallEndTime = Date.now();
211
+ const totalDuration = overallEndTime - overallStartTime;
212
+ const averageBatchTime = batchCount > 0 ? totalBatchTime / batchCount : 0;
213
+
214
+ logging.info(`[MailgunClient fetchEvents]: Processed ${batchCount} batches in ${(totalDuration / 1000).toFixed(2)}s. Average batch time: ${(averageBatchTime / 1000).toFixed(2)}s`);
215
+ } catch (error) {
216
+ logging.error(error);
217
+ throw error;
218
+ }
219
+ }
220
+
221
+ async removeSuppression(type, email) {
222
+ if (!this.isConfigured()) {
223
+ return false;
224
+ }
225
+ const instance = this.getInstance();
226
+ const config = this.#getConfig();
227
+
228
+ try {
229
+ await instance.suppressions.destroy(
230
+ config.domain,
231
+ type,
232
+ email
233
+ );
234
+ return true;
235
+ } catch (err) {
236
+ logging.error(err);
237
+ return false;
238
+ }
239
+ }
240
+
241
+ async removeBounce(email) {
242
+ return this.removeSuppression('bounces', email);
243
+ }
244
+
245
+ async removeComplaint(email) {
246
+ return this.removeSuppression('complaints', email);
247
+ }
248
+
249
+ async removeUnsubscribe(email) {
250
+ return this.removeSuppression('unsubscribes', email);
251
+ }
252
+
253
+ normalizeEvent(event) {
254
+ const providerId = event?.message?.headers['message-id'];
255
+
256
+ if (!providerId && !(event['user-variables'] && event['user-variables']['email-id'])) {
257
+ logging.error('Received invalid event from Mailgun');
258
+ logging.error(event);
259
+ return null;
260
+ }
261
+
262
+ return {
263
+ id: event.id,
264
+ type: event.event,
265
+ severity: event.severity,
266
+ recipientEmail: event.recipient,
267
+ emailId: event['user-variables'] && event['user-variables']['email-id'],
268
+ providerId: providerId,
269
+ timestamp: new Date(event.timestamp * 1000),
270
+
271
+ error: event['delivery-status'] && (typeof (event['delivery-status'].message || event['delivery-status'].description) === 'string') ? {
272
+ code: event['delivery-status'].code,
273
+ message: (event['delivery-status'].message || event['delivery-status'].description).substring(0, 2000),
274
+ enhancedCode: event['delivery-status']['enhanced-code']?.toString()?.substring(0, 50) ?? null
275
+ } : null
276
+ };
277
+ }
278
+
279
+ #getConfig() {
280
+ const bulkEmailConfig = this.#config.get('bulkEmail');
281
+ const bulkEmailSetting = {
282
+ apiKey: this.#settings.get('mailgun_api_key'),
283
+ domain: this.#settings.get('mailgun_domain'),
284
+ baseUrl: this.#settings.get('mailgun_base_url')
285
+ };
286
+
287
+ const hasMailgunConfig = !!(bulkEmailConfig?.mailgun);
288
+ const hasMailgunSetting = !!(bulkEmailSetting && bulkEmailSetting.apiKey && bulkEmailSetting.baseUrl && bulkEmailSetting.domain);
289
+
290
+ if (!hasMailgunConfig && !hasMailgunSetting) {
291
+ return null;
292
+ }
293
+
294
+ const mailgunConfig = hasMailgunConfig ? bulkEmailConfig.mailgun : bulkEmailSetting;
295
+ return mailgunConfig;
296
+ }
297
+
298
+ /**
299
+ * Returns an instance of the Mailgun client based upon the config or settings values
300
+ *
301
+ * We don't cache the instance so we can always get a fresh one based upon changed settings
302
+ * or config values over time
303
+ *
304
+ * Note: if the credentials are not configure, this method returns `null` and it is down to the
305
+ * consumer to act upon this/log this out
306
+ *
307
+ * @returns {import('mailgun.js')|null} the Mailgun client instance
308
+ */
309
+ getInstance() {
310
+ const mailgunConfig = this.#getConfig();
311
+ if (!mailgunConfig) {
312
+ return null;
313
+ }
314
+
315
+ const formData = require('form-data');
316
+ const Mailgun = require('mailgun.js');
317
+
318
+ const baseUrl = new URL(mailgunConfig.baseUrl);
319
+ const mailgun = new Mailgun(formData);
320
+
321
+ return mailgun.client({
322
+ username: 'api',
323
+ key: mailgunConfig.apiKey,
324
+ url: baseUrl.origin,
325
+ timeout: 60000
326
+ });
327
+ }
328
+
329
+ /**
330
+ * Returns whether the Mailgun instance is configured via config/settings
331
+ *
332
+ * @returns {boolean}
333
+ */
334
+ isConfigured() {
335
+ const instance = this.getInstance();
336
+ return !!instance;
337
+ }
338
+
339
+ /**
340
+ * Returns configured batch size
341
+ *
342
+ * @returns {number}
343
+ */
344
+ getBatchSize() {
345
+ return this.#config.get('bulkEmail')?.batchSize ?? this.DEFAULT_BATCH_SIZE;
346
+ }
347
+
348
+ /**
349
+ * Returns the configured target delivery window in seconds
350
+ * Ghost will attempt to deliver emails evenly distributed over this window
351
+ *
352
+ * Defaults to 0 (no delay) if not set
353
+ *
354
+ * @returns {number}
355
+ */
356
+ getTargetDeliveryWindow() {
357
+ const targetDeliveryWindow = this.#config.get('bulkEmail')?.targetDeliveryWindow;
358
+ // If targetDeliveryWindow is not set or is not a positive integer, return 0
359
+ if (targetDeliveryWindow === undefined || !Number.isInteger(parseInt(targetDeliveryWindow)) || parseInt(targetDeliveryWindow) < 0) {
360
+ return 0;
361
+ }
362
+ return parseInt(targetDeliveryWindow);
363
+ }
364
+ };
@@ -0,0 +1,26 @@
1
+ const ObjectID = require('bson-objectid').default;
2
+
3
+ module.exports = class LinkRedirect {
4
+ /** @type {ObjectID} */
5
+ link_id;
6
+ /** @type {URL} */
7
+ from;
8
+ /** @type {URL} */
9
+ to;
10
+ /** @type {boolean} */
11
+ edited;
12
+
13
+ constructor(data) {
14
+ if (!data.id) {
15
+ this.link_id = new ObjectID();
16
+ }
17
+
18
+ if (typeof data.id === 'string') {
19
+ this.link_id = ObjectID.createFromHexString(data.id);
20
+ }
21
+
22
+ this.from = data.from;
23
+ this.to = data.to;
24
+ this.edited = !!data.edited;
25
+ }
26
+ };
@@ -1,4 +1,4 @@
1
- const LinkRedirect = require('@tryghost/link-redirects').LinkRedirect;
1
+ const LinkRedirect = require('./LinkRedirect');
2
2
  const ObjectID = require('bson-objectid').default;
3
3
  const debug = require('@tryghost/debug')('LinkRedirectRepository');
4
4
 
@@ -17,7 +17,7 @@ module.exports = class LinkRedirectRepository {
17
17
  * @param {object} deps.LinkRedirect - Bookshelf Model
18
18
  * @param {object} deps.urlUtils
19
19
  * @param {object} deps.cacheAdapter - Cache Adapter instance, or null if cache is disabled
20
- * @param {object} deps.EventRegistry
20
+ * @param {object} deps.EventRegistry
21
21
  */
22
22
  constructor(deps) {
23
23
  debug('Creating LinkRedirectRepository');
@@ -61,7 +61,7 @@ module.exports = class LinkRedirectRepository {
61
61
 
62
62
  /**
63
63
  * Trim the leading slash from a URL path
64
- * @param {string} url
64
+ * @param {string} url
65
65
  * @returns {string} url without leading slash
66
66
  */
67
67
  #trimLeadingSlash(url) {
@@ -70,14 +70,14 @@ module.exports = class LinkRedirectRepository {
70
70
 
71
71
  /**
72
72
  * Returns a LinkRedirect object from a model
73
- * @param {object} model - Bookshelf model instance
73
+ * @param {object} model - Bookshelf model instance
74
74
  * @returns {InstanceType<LinkRedirect>} LinkRedirect
75
75
  */
76
76
  fromModel(model) {
77
77
  // Store if link has been edited
78
78
  // Note: in some edge cases updated_at is set directly after created_at, sometimes with a second difference, so we need to check for that
79
79
  const edited = model.get('updated_at')?.getTime() > (model.get('created_at')?.getTime() + 1000);
80
-
80
+
81
81
  return new LinkRedirect({
82
82
  id: model.id,
83
83
  from: new URL(this.#trimLeadingSlash(model.get('from')), this.#urlUtils.urlFor('home', true)),
@@ -106,7 +106,7 @@ module.exports = class LinkRedirectRepository {
106
106
 
107
107
  /**
108
108
  * Serialize a LinkRedirect object to a plain object (e.g. for caching)
109
- * @param {InstanceType<LinkRedirect>} linkRedirect
109
+ * @param {InstanceType<LinkRedirect>} linkRedirect
110
110
  * @returns {object} - serialized LinkRedirect
111
111
  */
112
112
  #serialize(linkRedirect) {
@@ -120,7 +120,7 @@ module.exports = class LinkRedirectRepository {
120
120
 
121
121
  /**
122
122
  * Get all LinkRedirects from the DB, with optional filters
123
- * @param {object} options - options passed directly to LinkRedirect.findAll
123
+ * @param {object} options - options passed directly to LinkRedirect.findAll
124
124
  * @returns {Promise<InstanceType<LinkRedirect>[]>} array of LinkRedirects
125
125
  */
126
126
  async getAll(options) {
@@ -0,0 +1,123 @@
1
+ const crypto = require('crypto');
2
+ const DomainEvents = require('@tryghost/domain-events');
3
+ const RedirectEvent = require('./RedirectEvent');
4
+ const LinkRedirect = require('./LinkRedirect');
5
+
6
+ /**
7
+ * @typedef {object} ILinkRedirectRepository
8
+ * @prop {(url: URL) => Promise<LinkRedirect|undefined>} getByURL
9
+ * @prop {({filter: string}) => Promise<LinkRedirect[]>} getAll
10
+ * @prop {({filter: string}) => Promise<String[]>} getFilteredIds
11
+ * @prop {(linkRedirect: LinkRedirect) => Promise<void>} save
12
+ */
13
+
14
+ class LinkRedirectsService {
15
+ /** @type ILinkRedirectRepository */
16
+ #linkRedirectRepository;
17
+ /** @type URL */
18
+ #baseURL;
19
+
20
+ /** @type String */
21
+ #redirectURLPrefix = 'r/';
22
+
23
+ /**
24
+ * @param {object} deps
25
+ * @param {ILinkRedirectRepository} deps.linkRedirectRepository
26
+ * @param {object} deps.config
27
+ * @param {URL} deps.config.baseURL
28
+ */
29
+ constructor(deps) {
30
+ this.#linkRedirectRepository = deps.linkRedirectRepository;
31
+ if (!deps.config.baseURL.pathname.endsWith('/')) {
32
+ this.#baseURL = new URL(deps.config.baseURL);
33
+ this.#baseURL.pathname += '/';
34
+ } else {
35
+ this.#baseURL = deps.config.baseURL;
36
+ }
37
+ this.handleRequest = this.handleRequest.bind(this);
38
+ }
39
+
40
+ /**
41
+ * Get a unique URL with slug for creating unique redirects
42
+ *
43
+ * @returns {Promise<URL>}
44
+ */
45
+ async getSlugUrl() {
46
+ let url;
47
+ while (!url || await this.#linkRedirectRepository.getByURL(url)) {
48
+ const slug = crypto.randomBytes(4).toString('hex');
49
+ url = new URL(`${this.#redirectURLPrefix}${slug}`, this.#baseURL);
50
+ }
51
+ return url;
52
+ }
53
+
54
+ /**
55
+ * @param {Object} options
56
+ *
57
+ * @returns {Promise<String[]>}
58
+ */
59
+ async getFilteredIds(options) {
60
+ return await this.#linkRedirectRepository.getFilteredIds(options);
61
+ }
62
+
63
+ /**
64
+ * @param {URL} from
65
+ * @param {URL} to
66
+ *
67
+ * @returns {Promise<LinkRedirect>}
68
+ */
69
+ async addRedirect(from, to) {
70
+ const link = new LinkRedirect({
71
+ from,
72
+ to
73
+ });
74
+
75
+ await this.#linkRedirectRepository.save(link);
76
+
77
+ return link;
78
+ }
79
+
80
+ /**
81
+ * @param {import('express').Request} req
82
+ * @param {import('express').Response} res
83
+ * @param {import('express').NextFunction} next
84
+ *
85
+ * @returns {Promise<void>}
86
+ */
87
+ async handleRequest(req, res, next) {
88
+ try {
89
+ // skip handling if original url doesn't match the prefix
90
+ const fullURLWithRedirectPrefix = `${this.#baseURL.pathname}${this.#redirectURLPrefix}`;
91
+ // @NOTE: below is equivalent to doing:
92
+ // router.get('/r/'), (req, res) ...
93
+ // To make it cleaner we should rework it to:
94
+ // linkRedirects.service.handleRequest(router);
95
+ // and mount routes on top like for example sitemapHandler does
96
+ // Cleanup issue: https://github.com/TryGhost/Toolbox/issues/516
97
+ if (!req.originalUrl.startsWith(fullURLWithRedirectPrefix)) {
98
+ return next();
99
+ }
100
+
101
+ const url = new URL(req.originalUrl, this.#baseURL);
102
+ const link = await this.#linkRedirectRepository.getByURL(url);
103
+
104
+ if (!link) {
105
+ return next();
106
+ }
107
+
108
+ const event = RedirectEvent.create({
109
+ url,
110
+ link
111
+ });
112
+
113
+ DomainEvents.dispatch(event);
114
+
115
+ res.setHeader('X-Robots-Tag', 'noindex, nofollow');
116
+ return res.redirect(link.to.href);
117
+ } catch (e) {
118
+ return next(e);
119
+ }
120
+ }
121
+ }
122
+
123
+ module.exports = LinkRedirectsService;