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,258 @@
1
+ /* eslint-disable @typescript-eslint/no-explicit-any */
2
+ import errors from '@tryghost/errors';
3
+ import {AddRecommendation, Recommendation, RecommendationPlain} from './Recommendation';
4
+ import {RecommendationService} from './RecommendationService';
5
+ import {UnsafeData} from './UnsafeData';
6
+ import {OrderOption} from './BookshelfRepository';
7
+
8
+ type Frame = {
9
+ data: unknown,
10
+ options: unknown,
11
+ user: unknown,
12
+ };
13
+
14
+ const RecommendationIncludesMap = {
15
+ 'count.clicks': 'clickCount' as const,
16
+ 'count.subscribers': 'subscriberCount' as const
17
+ };
18
+
19
+ const RecommendationOrderMap = {
20
+ title: 'title' as const,
21
+ description: 'description' as const,
22
+ excerpt: 'excerpt' as const,
23
+ one_click_subscribe: 'oneClickSubscribe' as const,
24
+ created_at: 'createdAt' as const,
25
+ updated_at: 'updatedAt' as const,
26
+ 'count.clicks': 'clickCount' as const,
27
+ 'count.subscribers': 'subscriberCount' as const
28
+ };
29
+
30
+ export class RecommendationController {
31
+ service: RecommendationService;
32
+
33
+ constructor(deps: {service: RecommendationService}) {
34
+ this.service = deps.service;
35
+ }
36
+
37
+ async read(frame: Frame) {
38
+ const options = new UnsafeData(frame.options);
39
+ const id = options.key('id').string;
40
+
41
+ const recommendation = await this.service.readRecommendation(id);
42
+
43
+ return this.#serialize(
44
+ [recommendation]
45
+ );
46
+ }
47
+
48
+ async add(frame: Frame) {
49
+ const data = new UnsafeData(frame.data);
50
+ const recommendation = data.key('recommendations').index(0);
51
+ const plain: AddRecommendation = {
52
+ title: recommendation.key('title').string,
53
+ url: recommendation.key('url').url,
54
+
55
+ // Optional fields
56
+ oneClickSubscribe: recommendation.optionalKey('one_click_subscribe')?.boolean ?? false,
57
+ description: recommendation.optionalKey('description')?.nullable.string ?? null,
58
+ excerpt: recommendation.optionalKey('excerpt')?.nullable.string ?? null,
59
+ featuredImage: recommendation.optionalKey('featured_image')?.nullable.url ?? null,
60
+ favicon: recommendation.optionalKey('favicon')?.nullable.url ?? null
61
+ };
62
+
63
+ return this.#serialize(
64
+ [await this.service.addRecommendation(plain)]
65
+ );
66
+ }
67
+
68
+ /**
69
+ * Given a recommendation URL, returns either an existing recommendation with that url and updated metadata,
70
+ * or the metadata from that URL as if it would create a new one (without creating a new one)
71
+ *
72
+ * This can be used in the frontend when creating a new recommendation (duplication checking + showing a preview before saving)
73
+ */
74
+ async check(frame: Frame) {
75
+ const data = new UnsafeData(frame.data);
76
+ const recommendation = data.key('recommendations').index(0);
77
+ const url = recommendation.key('url').url;
78
+
79
+ return this.#serialize(
80
+ [await this.service.checkRecommendation(url)]
81
+ );
82
+ }
83
+
84
+ async edit(frame: Frame) {
85
+ const options = new UnsafeData(frame.options);
86
+ const data = new UnsafeData(frame.data);
87
+ const recommendation = data.key('recommendations').index(0);
88
+
89
+ const id = options.key('id').string;
90
+ const plain: Partial<RecommendationPlain> = {
91
+ title: recommendation.optionalKey('title')?.string,
92
+ url: recommendation.optionalKey('url')?.url,
93
+ oneClickSubscribe: recommendation.optionalKey('one_click_subscribe')?.boolean,
94
+ description: recommendation.optionalKey('description')?.nullable.string,
95
+ excerpt: recommendation.optionalKey('excerpt')?.nullable.string,
96
+ featuredImage: recommendation.optionalKey('featured_image')?.nullable.url,
97
+ favicon: recommendation.optionalKey('favicon')?.nullable.url
98
+ };
99
+
100
+ return this.#serialize(
101
+ [await this.service.editRecommendation(id, plain)]
102
+ );
103
+ }
104
+
105
+ async destroy(frame: Frame) {
106
+ const options = new UnsafeData(frame.options);
107
+ const id = options.key('id').string;
108
+ await this.service.deleteRecommendation(id);
109
+ }
110
+
111
+ #stringToOrder(str?: string) {
112
+ if (!str) {
113
+ // Default order
114
+ return [
115
+ {
116
+ field: 'createdAt' as const,
117
+ direction: 'desc' as const
118
+ }
119
+ ];
120
+ }
121
+
122
+ const parts = str.split(',');
123
+ const order: OrderOption<Recommendation> = [];
124
+ for (const [index, part] of parts.entries()) {
125
+ const trimmed = part.trim();
126
+ const fieldData = new UnsafeData(trimmed.split(' ')[0].trim(), {field: ['order', index.toString(), 'field']});
127
+ const directionData = new UnsafeData(trimmed.split(' ')[1]?.trim() ?? 'desc', {field: ['order', index.toString(), 'direction']});
128
+
129
+ const validatedField = fieldData.enum(
130
+ Object.keys(RecommendationOrderMap) as (keyof typeof RecommendationOrderMap)[]
131
+ );
132
+ const direction = directionData.enum(['asc' as const, 'desc' as const]);
133
+
134
+ // Convert 'count.' and camelCase to snake_case
135
+ const field = RecommendationOrderMap[validatedField];
136
+ order.push({
137
+ field,
138
+ direction
139
+ });
140
+ }
141
+
142
+ return order;
143
+ }
144
+
145
+ async browse(frame: Frame) {
146
+ const options = new UnsafeData(frame.options);
147
+
148
+ const page = options.optionalKey('page')?.integer ?? 1;
149
+ const limit = options.optionalKey('limit')?.integer ?? 5;
150
+ const include = options.optionalKey('withRelated')?.array.map((item) => {
151
+ return RecommendationIncludesMap[item.enum(
152
+ Object.keys(RecommendationIncludesMap) as (keyof typeof RecommendationIncludesMap)[]
153
+ )];
154
+ }) ?? [];
155
+ const filter = options.optionalKey('filter')?.string;
156
+
157
+ const orderOption = options.optionalKey('order')?.string;
158
+ const order = this.#stringToOrder(orderOption);
159
+
160
+ const count = await this.service.countRecommendations({});
161
+ const recommendations = (await this.service.listRecommendations({page, limit, filter, include, order}));
162
+
163
+ return this.#serialize(
164
+ recommendations,
165
+ {
166
+ pagination: this.#serializePagination({page, limit, count})
167
+ }
168
+ );
169
+ }
170
+
171
+ async trackClicked(frame: Frame) {
172
+ const member = this.#optionalAuthMember(frame);
173
+ const options = new UnsafeData(frame.options);
174
+ const id = options.key('id').string;
175
+
176
+ await this.service.trackClicked({
177
+ id,
178
+ memberId: member?.id
179
+ });
180
+ }
181
+ async trackSubscribed(frame: Frame) {
182
+ const member = this.#authMember(frame);
183
+ const options = new UnsafeData(frame.options);
184
+ const id = options.key('id').string;
185
+
186
+ await this.service.trackSubscribed({
187
+ id,
188
+ memberId: member.id
189
+ });
190
+ }
191
+
192
+ #authMember(frame: Frame): {id: string} {
193
+ const options = new UnsafeData(frame.options);
194
+ const memberId = options.key('context').optionalKey('member')?.nullable.key('id').string;
195
+ if (!memberId) {
196
+ // This is an internal server error because authentication should happen outside this service.
197
+ throw new errors.UnauthorizedError({
198
+ message: 'Member not found'
199
+ });
200
+ }
201
+ return {
202
+ id: memberId
203
+ };
204
+ }
205
+
206
+ #optionalAuthMember(frame: Frame): {id: string}|null {
207
+ try {
208
+ const member = this.#authMember(frame);
209
+ return member;
210
+ } catch (e) {
211
+ if (e instanceof errors.UnauthorizedError) {
212
+ // This is fine, this is not required
213
+ } else {
214
+ throw e;
215
+ }
216
+ }
217
+ return null;
218
+ }
219
+
220
+ #serialize(recommendations: Partial<RecommendationPlain>[], meta?: any) {
221
+ return {
222
+ data: recommendations.map((entity) => {
223
+ const d = {
224
+ id: entity.id ?? null,
225
+ title: entity.title ?? null,
226
+ description: entity.description ?? null,
227
+ excerpt: entity.excerpt ?? null,
228
+ featured_image: entity.featuredImage?.toString() ?? null,
229
+ favicon: entity.favicon?.toString() ?? null,
230
+ url: entity.url?.toString() ?? null,
231
+ one_click_subscribe: entity.oneClickSubscribe ?? null,
232
+ created_at: entity.createdAt?.toISOString() ?? null,
233
+ updated_at: entity.updatedAt?.toISOString() ?? null,
234
+ count: entity.clickCount !== undefined || entity.subscriberCount !== undefined ? {
235
+ clicks: entity.clickCount,
236
+ subscribers: entity.subscriberCount
237
+ } : undefined
238
+ };
239
+
240
+ return d;
241
+ }),
242
+ meta
243
+ };
244
+ }
245
+
246
+ #serializePagination({page, limit, count}: {page: number, limit: number, count: number}) {
247
+ const pages = Math.ceil(count / limit);
248
+
249
+ return {
250
+ page,
251
+ limit,
252
+ total: count,
253
+ pages,
254
+ prev: page > 1 ? page - 1 : null,
255
+ next: page < pages ? page + 1 : null
256
+ };
257
+ }
258
+ }
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ /* eslint-disable ghost/filenames/match-exported-class */
3
+ Object.defineProperty(exports, "__esModule", { value: true });
4
+ exports.RecommendationMetadataService = void 0;
5
+ class RecommendationMetadataService {
6
+ #oembedService;
7
+ #externalRequest;
8
+ constructor(dependencies) {
9
+ this.#oembedService = dependencies.oembedService;
10
+ this.#externalRequest = dependencies.externalRequest;
11
+ }
12
+ async #fetchJSON(url, options) {
13
+ // Even though we have throwHttpErrors: false, we still need to catch DNS errors
14
+ // that can arise from externalRequest, otherwise we'll return a HTTP 500 to the user
15
+ try {
16
+ // default content type is application/x-www-form-encoded which is what we need for the webmentions spec
17
+ const response = await this.#externalRequest.get(url.toString(), {
18
+ throwHttpErrors: false,
19
+ maxRedirects: 10,
20
+ followRedirect: true,
21
+ timeout: 15000,
22
+ retry: {
23
+ // Only retry on network issues, or specific HTTP status codes
24
+ limit: 3
25
+ },
26
+ ...options
27
+ });
28
+ if (response.statusCode >= 200 && response.statusCode < 300) {
29
+ try {
30
+ return JSON.parse(response.body);
31
+ }
32
+ catch (e) {
33
+ return undefined;
34
+ }
35
+ }
36
+ }
37
+ catch (e) {
38
+ return undefined;
39
+ }
40
+ }
41
+ #castUrl(url) {
42
+ if (!url) {
43
+ return null;
44
+ }
45
+ try {
46
+ return new URL(url);
47
+ }
48
+ catch (e) {
49
+ return null;
50
+ }
51
+ }
52
+ async fetch(url, options = { timeout: 5000 }) {
53
+ // Make sure url path ends with a slash (urls should be resolved relative to the path)
54
+ if (!url.pathname.endsWith('/')) {
55
+ url.pathname += '/';
56
+ }
57
+ // 1. Check if it is a Ghost site
58
+ let ghostSiteData = await this.#fetchJSON(new URL('members/api/site', url), options);
59
+ if (!ghostSiteData && url.pathname !== '' && url.pathname !== '/') {
60
+ // Try root relative URL
61
+ ghostSiteData = await this.#fetchJSON(new URL('members/api/site', url.origin), options);
62
+ }
63
+ if (ghostSiteData && typeof ghostSiteData === 'object' && ghostSiteData.site && typeof ghostSiteData.site === 'object') {
64
+ // Check if the Ghost site returns allow_external_signup, otherwise it is an old Ghost version that returns unreliable data
65
+ if (typeof ghostSiteData.site.allow_external_signup === 'boolean') {
66
+ return {
67
+ title: ghostSiteData.site.title || null,
68
+ excerpt: ghostSiteData.site.description || null,
69
+ featuredImage: this.#castUrl(ghostSiteData.site.cover_image),
70
+ favicon: this.#castUrl(ghostSiteData.site.icon || ghostSiteData.site.logo),
71
+ oneClickSubscribe: !!ghostSiteData.site.allow_external_signup
72
+ };
73
+ }
74
+ }
75
+ // Use the oembed service to fetch metadata
76
+ const oembed = await this.#oembedService.fetchOembedDataFromUrl(url.toString(), 'mention');
77
+ return {
78
+ title: oembed?.metadata?.title || null,
79
+ excerpt: oembed?.metadata?.description || null,
80
+ featuredImage: this.#castUrl(oembed?.metadata?.thumbnail),
81
+ favicon: this.#castUrl(oembed?.metadata?.icon),
82
+ oneClickSubscribe: false
83
+ };
84
+ }
85
+ }
86
+ exports.RecommendationMetadataService = RecommendationMetadataService;
@@ -0,0 +1,128 @@
1
+ /* eslint-disable ghost/filenames/match-exported-class */
2
+
3
+ type OembedMetadata<Type extends string> = {
4
+ version: '1.0',
5
+ type: Type,
6
+ url: string,
7
+ metadata: {
8
+ title: string|null,
9
+ description: string|null,
10
+ publisher: string|null,
11
+ author: string|null,
12
+ thumbnail: string|null,
13
+ icon: string|null
14
+ },
15
+ body?: Type extends 'mention' ? string : unknown,
16
+ contentType?: Type extends 'mention' ? string : unknown
17
+ }
18
+
19
+ type OEmbedService = {
20
+ fetchOembedDataFromUrl<Type extends string>(url: string, type: Type, options?: {timeout?: number}): Promise<OembedMetadata<Type>>
21
+ }
22
+
23
+ type ExternalRequest = {
24
+ get(url: string, options: object): Promise<{statusCode: number, body: string}>
25
+ }
26
+
27
+ export type RecommendationMetadata = {
28
+ title: string|null,
29
+ excerpt: string|null,
30
+ featuredImage: URL|null,
31
+ favicon: URL|null,
32
+ oneClickSubscribe: boolean
33
+ }
34
+
35
+ export class RecommendationMetadataService {
36
+ #oembedService: OEmbedService;
37
+ #externalRequest: ExternalRequest;
38
+
39
+ constructor(dependencies: {oembedService: OEmbedService, externalRequest: ExternalRequest}) {
40
+ this.#oembedService = dependencies.oembedService;
41
+ this.#externalRequest = dependencies.externalRequest;
42
+ }
43
+
44
+ async #fetchJSON(url: URL, options?: {timeout?: number}) {
45
+ // Even though we have throwHttpErrors: false, we still need to catch DNS errors
46
+ // that can arise from externalRequest, otherwise we'll return a HTTP 500 to the user
47
+ try {
48
+ // default content type is application/x-www-form-encoded which is what we need for the webmentions spec
49
+ const response = await this.#externalRequest.get(url.toString(), {
50
+ throwHttpErrors: false,
51
+ maxRedirects: 10,
52
+ followRedirect: true,
53
+ timeout: 15000,
54
+ retry: {
55
+ // Only retry on network issues, or specific HTTP status codes
56
+ limit: 3
57
+ },
58
+ ...options
59
+ });
60
+
61
+ if (response.statusCode >= 200 && response.statusCode < 300) {
62
+ try {
63
+ return JSON.parse(response.body);
64
+ } catch (e) {
65
+ return undefined;
66
+ }
67
+ }
68
+ } catch (e) {
69
+ return undefined;
70
+ }
71
+ }
72
+
73
+ #castUrl(url: string|null|undefined): URL|null {
74
+ if (!url) {
75
+ return null;
76
+ }
77
+ try {
78
+ return new URL(url);
79
+ } catch (e) {
80
+ return null;
81
+ }
82
+ }
83
+
84
+ async fetch(url: URL, options: {timeout: number} = {timeout: 5000}): Promise<RecommendationMetadata> {
85
+ // Make sure url path ends with a slash (urls should be resolved relative to the path)
86
+ if (!url.pathname.endsWith('/')) {
87
+ url.pathname += '/';
88
+ }
89
+
90
+ // 1. Check if it is a Ghost site
91
+ let ghostSiteData = await this.#fetchJSON(
92
+ new URL('members/api/site', url),
93
+ options
94
+ );
95
+
96
+ if (!ghostSiteData && url.pathname !== '' && url.pathname !== '/') {
97
+ // Try root relative URL
98
+ ghostSiteData = await this.#fetchJSON(
99
+ new URL('members/api/site', url.origin),
100
+ options
101
+ );
102
+ }
103
+
104
+ if (ghostSiteData && typeof ghostSiteData === 'object' && ghostSiteData.site && typeof ghostSiteData.site === 'object') {
105
+ // Check if the Ghost site returns allow_external_signup, otherwise it is an old Ghost version that returns unreliable data
106
+ if (typeof ghostSiteData.site.allow_external_signup === 'boolean') {
107
+ return {
108
+ title: ghostSiteData.site.title || null,
109
+ excerpt: ghostSiteData.site.description || null,
110
+ featuredImage: this.#castUrl(ghostSiteData.site.cover_image),
111
+ favicon: this.#castUrl(ghostSiteData.site.icon || ghostSiteData.site.logo),
112
+ oneClickSubscribe: !!ghostSiteData.site.allow_external_signup
113
+ };
114
+ }
115
+ }
116
+
117
+ // Use the oembed service to fetch metadata
118
+ const oembed = await this.#oembedService.fetchOembedDataFromUrl(url.toString(), 'mention');
119
+
120
+ return {
121
+ title: oembed?.metadata?.title || null,
122
+ excerpt: oembed?.metadata?.description || null,
123
+ featuredImage: this.#castUrl(oembed?.metadata?.thumbnail),
124
+ favicon: this.#castUrl(oembed?.metadata?.icon),
125
+ oneClickSubscribe: false
126
+ };
127
+ }
128
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,13 @@
1
+ import {AllOptions} from './BookshelfRepository';
2
+ import {Recommendation} from './Recommendation';
3
+
4
+ export interface RecommendationRepository {
5
+ save(entity: Recommendation): Promise<void>;
6
+ getById(id: string): Promise<Recommendation | null>;
7
+ getByUrl(url: URL): Promise<Recommendation|null>;
8
+ getAll(options: Omit<AllOptions<Recommendation>, 'page'|'limit'>): Promise<Recommendation[]>;
9
+ getPage(options: AllOptions<Recommendation> & Required<Pick<AllOptions<Recommendation>, 'page'|'limit'>>): Promise<Recommendation[]>;
10
+ getCount(options: {
11
+ filter?: string;
12
+ }): Promise<number>;
13
+ }