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,191 @@
1
+ /**
2
+ * ```
3
+ * const Minifier = require('./Minifier');
4
+ * const minifier = new Minifier({
5
+ * src: 'my/src/path',
6
+ * dest: 'my/dest/path'
7
+ * });
8
+ *
9
+ * minifier.minify({
10
+ * 'some.css': '*.css',
11
+ * 'then.js': '!(other).js'
12
+ * });
13
+ * ```
14
+ *
15
+ * - Minfier constructor requires a src and a dest
16
+ * - minify() function takes an object with destination file as the key and source glob as the value
17
+ * - globs can be anything tiny-glob supports
18
+ * - destination files must end with .css or .js
19
+ * - src files will be minified according to their destination file extension
20
+ */
21
+
22
+ const errors = require('@tryghost/errors');
23
+ const debug = require('@tryghost/debug')('minifier');
24
+ const tpl = require('@tryghost/tpl');
25
+ const glob = require('tiny-glob');
26
+ const path = require('path');
27
+ const fs = require('fs').promises;
28
+ const isWin = process.platform === 'win32';
29
+
30
+ const messages = {
31
+ badDestination: {
32
+ message: 'Unexpected destination {dest}',
33
+ context: 'Minifier expected a destination that ended in .css or .js'
34
+ },
35
+ badSource: {
36
+ message: 'Unable to read source files {src}',
37
+ context: 'Minifier was unable to locate or read the source files'
38
+ },
39
+ missingConstructorOption: {
40
+ message: 'Minifier missing {opt} option',
41
+ context: 'new Minifier({}) requires a {opt} option'
42
+ },
43
+ globalHelp: 'Refer to the README for Minifier for how to use this module'
44
+ };
45
+
46
+ // public API for minify hooks
47
+ class Minifier {
48
+ constructor({src, dest}) {
49
+ if (!src) {
50
+ throw new errors.IncorrectUsageError({
51
+ message: tpl(messages.missingConstructorOption.message, {opt: 'src'}),
52
+ context: tpl(messages.missingConstructorOption.context, {opt: 'src'}),
53
+ help: tpl(messages.globalHelp)
54
+ });
55
+ }
56
+ if (!dest) {
57
+ throw new errors.IncorrectUsageError({
58
+ message: tpl(messages.missingConstructorOption.message, {opt: 'dest'}),
59
+ context: tpl(messages.missingConstructorOption.context, {opt: 'dest'}),
60
+ help: tpl(messages.globalHelp)
61
+ });
62
+ }
63
+ this.srcPath = src;
64
+ this.destPath = dest;
65
+ }
66
+
67
+ getFullSrc(src) {
68
+ return path.join(this.srcPath, src);
69
+ }
70
+
71
+ getFullDest(dest) {
72
+ return path.join(this.destPath, dest);
73
+ }
74
+
75
+ async minifyCSS(contents) {
76
+ const csso = require('csso');
77
+ const result = await csso.minify(contents);
78
+ if (result && result.css) {
79
+ return result.css;
80
+ }
81
+ return null;
82
+ }
83
+
84
+ async minifyJS(contents) {
85
+ const terser = require('terser');
86
+ const result = await terser.minify(contents);
87
+ if (result && result.code) {
88
+ return result.code;
89
+ }
90
+ return null;
91
+ }
92
+
93
+ async getMatchingFiles(src) {
94
+ let fullSrc = this.getFullSrc(src);
95
+ if (isWin) {
96
+ fullSrc = fullSrc.replace(/\\/g,'/');
97
+ }
98
+ return await glob(fullSrc);
99
+ }
100
+
101
+ async readFiles(files) {
102
+ let mergedFiles = '';
103
+ for (const file of files) {
104
+ const contents = await fs.readFile(file, 'utf8');
105
+ mergedFiles += contents;
106
+ }
107
+
108
+ return mergedFiles;
109
+ }
110
+
111
+ async getSrcFileContents(src) {
112
+ try {
113
+ const files = await this.getMatchingFiles(src);
114
+
115
+ if (files) {
116
+ return await this.readFiles(files);
117
+ }
118
+ } catch (error) {
119
+ throw new errors.IncorrectUsageError({
120
+ message: tpl(messages.badSource.message, {src}),
121
+ context: tpl(messages.badSource.context),
122
+ help: tpl(messages.globalHelp)
123
+ });
124
+ }
125
+ }
126
+
127
+ async writeFile(contents, dest) {
128
+ if (contents) {
129
+ let writePath = this.getFullDest(dest);
130
+ // Ensure the output folder exists
131
+ await fs.mkdir(this.destPath, {recursive: true});
132
+ // Create the file
133
+ await fs.writeFile(writePath, contents);
134
+ return writePath;
135
+ }
136
+ }
137
+
138
+ /**
139
+ * Minify files
140
+ *
141
+ * @param {Object} globs An object in the form of
142
+ * ```js
143
+ * {
144
+ * 'destination1.js': 'glob/*.js',
145
+ * 'destination2.js': 'glob2/*.js'
146
+ * }
147
+ * ```
148
+ * @param {Object} [options]
149
+ * @param {Object} [options.replacements] Key value pairs that should get replaced in the content before minifying
150
+ * @returns {Promise<string[]>} List of minified files (keys of globs)
151
+ */
152
+ async minify(globs, options) {
153
+ debug('Begin', globs);
154
+ const destinations = Object.keys(globs);
155
+ const minifiedFiles = [];
156
+
157
+ for (const dest of destinations) {
158
+ const src = globs[dest];
159
+ let contents = await this.getSrcFileContents(src);
160
+
161
+ if (options?.replacements) {
162
+ for (const key of Object.keys(options.replacements)) {
163
+ contents = contents.replace(key, options.replacements[key]);
164
+ }
165
+ }
166
+ let minifiedContents;
167
+
168
+ if (dest.endsWith('.css')) {
169
+ minifiedContents = await this.minifyCSS(contents);
170
+ } else if (dest.endsWith('.js')) {
171
+ minifiedContents = await this.minifyJS(contents);
172
+ } else {
173
+ throw new errors.IncorrectUsageError({
174
+ message: tpl(messages.badDestination.message, {dest}),
175
+ context: tpl(messages.badDestination.context),
176
+ help: tpl(messages.globalHelp)
177
+ });
178
+ }
179
+
180
+ const result = await this.writeFile(minifiedContents, dest);
181
+ if (result) {
182
+ minifiedFiles.push(dest);
183
+ }
184
+ }
185
+
186
+ debug('End');
187
+ return minifiedFiles;
188
+ }
189
+ }
190
+
191
+ module.exports = Minifier;
@@ -19,7 +19,8 @@ module.exports = function previewController(req, res, next) {
19
19
  const params = {
20
20
  uuid: req.params.uuid,
21
21
  status: 'all',
22
- include: 'authors,tags,tiers'
22
+ include: 'authors,tags,tiers',
23
+ member_status: req.query?.member_status
23
24
  };
24
25
 
25
26
  return api[res.routerOptions.query.controller]
@@ -95,7 +95,7 @@
95
95
  .kg-cta-content {
96
96
  display: flex;
97
97
  padding: 1.5em;
98
- gap: 1.5em 1.5em 1.7em;
98
+ gap: 1.5em;
99
99
  }
100
100
 
101
101
  @media (max-width: 600px) {
@@ -1,4 +1,4 @@
1
1
  /* eslint-disable ghost/filenames/match-regex */
2
- const RedisCache = require('@tryghost/adapter-cache-redis');
2
+ const RedisCache = require('../lib/redis/AdapterCacheRedis');
3
3
 
4
4
  module.exports = RedisCache;
@@ -0,0 +1,287 @@
1
+ const BaseCacheAdapter = require('@tryghost/adapter-base-cache');
2
+ const logging = require('@tryghost/logging');
3
+ const metrics = require('@tryghost/metrics');
4
+ const debug = require('@tryghost/debug')('redis-cache');
5
+ const cacheManager = require('cache-manager');
6
+ const redisStoreFactory = require('./redis-store-factory');
7
+ const calculateSlot = require('cluster-key-slot');
8
+
9
+ class AdapterCacheRedis extends BaseCacheAdapter {
10
+ /**
11
+ *
12
+ * @param {Object} config
13
+ * @param {Object} [config.cache] - caching instance compatible with cache-manager's redis store
14
+ * @param {String} [config.host] - redis host used in case no cache instance provided
15
+ * @param {Number} [config.port] - redis port used in case no cache instance provided
16
+ * @param {String} [config.password] - redis password used in case no cache instance provided
17
+ * @param {Object} [config.clusterConfig] - redis cluster config used in case no cache instance provided
18
+ * @param {Object} [config.storeConfig] - extra redis client config used in case no cache instance provided
19
+ * @param {Number} [config.ttl] - default cached value Time To Live (expiration) in *seconds*
20
+ * @param {Number} [config.getTimeoutMilliseconds] - default timeout for cache get operations in *milliseconds*
21
+ * @param {Number} [config.refreshAheadFactor] - 0-1 number to use to determine how old (as a percentage of ttl) an entry should be before refreshing it
22
+ * @param {String} [config.keyPrefix] - prefix to use when building a unique cache key, e.g.: 'some_id:image-sizes:'
23
+ * @param {Boolean} [config.reuseConnection] - specifies if the redis store/connection should be reused within the process
24
+ */
25
+ constructor(config) {
26
+ super();
27
+
28
+ this.cache = config.cache;
29
+
30
+ if (!this.cache) {
31
+ // @NOTE: this condition can be avoided if we add merging of nested options
32
+ // to adapter configuration. Than adding adapter-specific {clusterConfig: {options: {ttl: XXX}}}
33
+ // will be enough to set ttl for redis cluster.
34
+ if (config.ttl && config.clusterConfig) {
35
+ if (!config.clusterConfig.options) {
36
+ config.clusterConfig.options = {};
37
+ }
38
+
39
+ config.clusterConfig.options.ttl = config.ttl;
40
+ }
41
+
42
+ const storeOptions = {
43
+ ttl: config.ttl,
44
+ host: config.host,
45
+ port: config.port,
46
+ username: config.username,
47
+ password: config.password,
48
+ retryStrategy: () => {
49
+ return (config.storeConfig.retryConnectSeconds || 10) * 1000;
50
+ },
51
+ ...config.storeConfig,
52
+ clusterConfig: config.clusterConfig
53
+ };
54
+ const store = redisStoreFactory.getRedisStore(storeOptions, config.reuseConnection);
55
+
56
+ this.cache = cacheManager.caching({
57
+ store: store,
58
+ ...storeOptions
59
+ });
60
+ }
61
+
62
+ this.ttl = config.ttl;
63
+ this.refreshAheadFactor = config.refreshAheadFactor || 0;
64
+ this.getTimeoutMilliseconds = config.getTimeoutMilliseconds || null;
65
+ this.currentlyExecutingBackgroundRefreshes = new Set();
66
+ this.keyPrefix = config.keyPrefix;
67
+ this._keysPattern = config.keyPrefix ? `${config.keyPrefix}*` : '';
68
+ this.redisClient = this.cache.store.getClient();
69
+ this.redisClient.on('error', this.handleRedisError);
70
+ }
71
+
72
+ handleRedisError(error) {
73
+ logging.error(error);
74
+ }
75
+
76
+ #getPrimaryRedisNode() {
77
+ debug('getPrimaryRedisNode');
78
+ if (this.redisClient.constructor.name !== 'Cluster') {
79
+ return this.redisClient;
80
+ }
81
+ const slot = calculateSlot(this.keyPrefix);
82
+ const [ip, port] = this.redisClient.slots[slot][0].split(':');
83
+ for (const node of this.redisClient.nodes()) {
84
+ if (node.options.host === ip && node.options.port === parseInt(port)) {
85
+ return node;
86
+ }
87
+ }
88
+ return null;
89
+ }
90
+
91
+ #scanNodeForKeys(node) {
92
+ debug(`scanNodeForKeys matching ${this._keysPattern}`);
93
+ return new Promise((resolve, reject) => {
94
+ const stream = node.scanStream({match: this._keysPattern, count: 100});
95
+ let keys = [];
96
+ stream.on('data', (resultKeys) => {
97
+ keys = keys.concat(resultKeys);
98
+ });
99
+ stream.on('error', (e) => {
100
+ reject(e);
101
+ });
102
+ stream.on('end', () => {
103
+ resolve(keys);
104
+ });
105
+ });
106
+ }
107
+
108
+ async #getKeys() {
109
+ debug('#getKeys');
110
+ const primaryNode = this.#getPrimaryRedisNode();
111
+ if (primaryNode === null) {
112
+ return [];
113
+ }
114
+ return await this.#scanNodeForKeys(primaryNode);
115
+ }
116
+
117
+ /**
118
+ * This is a recommended way to build cache key prefixes from
119
+ * the cache-manager package. Might be a good contribution to make
120
+ * in the package itself (https://github.com/node-cache-manager/node-cache-manager/issues/158)
121
+ * @param {string} key
122
+ * @returns {string}
123
+ */
124
+ _buildKey(key) {
125
+ if (this.keyPrefix) {
126
+ return `${this.keyPrefix}${key}`;
127
+ }
128
+
129
+ return key;
130
+ }
131
+
132
+ /**
133
+ * This is a method to remove the key prefix from any raw key returned from redis.
134
+ * @param {string} key
135
+ * @returns {string}
136
+ */
137
+ _removeKeyPrefix(key) {
138
+ return key.slice(this.keyPrefix.length);
139
+ }
140
+
141
+ /**
142
+ *
143
+ * @param {String} internalKey
144
+ */
145
+ async shouldRefresh(internalKey) {
146
+ if (this.refreshAheadFactor === 0) {
147
+ debug(`shouldRefresh ${internalKey}: false - refreshAheadFactor = 0`);
148
+ return false;
149
+ }
150
+ if (this.refreshAheadFactor === 1) {
151
+ debug(`shouldRefresh ${internalKey}: true - refreshAheadFactor = 1`);
152
+ return true;
153
+ }
154
+ try {
155
+ const ttlRemainingForEntry = await this.cache.ttl(internalKey);
156
+ const shouldRefresh = ttlRemainingForEntry < this.refreshAheadFactor * this.ttl;
157
+ debug(`shouldRefresh ${internalKey}: ${shouldRefresh} - TTL remaining = ${ttlRemainingForEntry}`);
158
+ return shouldRefresh;
159
+ } catch (err) {
160
+ logging.error(err);
161
+ return false;
162
+ }
163
+ }
164
+
165
+ /**
166
+ * Returns the specified key from the cache if it exists, otherwise returns null
167
+ * - If getTimeoutMilliseconds is set, the method will return a promise that resolves with the value or null if the timeout is exceeded
168
+ *
169
+ * @param {string} key
170
+ */
171
+ async _get(key) {
172
+ if (typeof this.getTimeoutMilliseconds !== 'number') {
173
+ return this.cache.get(key);
174
+ } else {
175
+ return new Promise((resolve) => {
176
+ const timer = setTimeout(() => {
177
+ debug('get', key, 'timeout');
178
+ resolve(null);
179
+ }, this.getTimeoutMilliseconds);
180
+
181
+ this.cache.get(key).then((result) => {
182
+ clearTimeout(timer);
183
+ resolve(result);
184
+ });
185
+ });
186
+ }
187
+ }
188
+
189
+ /**
190
+ *
191
+ * @param {string} key
192
+ * @param {() => Promise<any>} [fetchData] An optional function to fetch the data, which will be used in the case of a cache MISS or a background refresh
193
+ */
194
+ async get(key, fetchData) {
195
+ const internalKey = this._buildKey(key);
196
+ try {
197
+ const result = await this._get(internalKey);
198
+ debug(`get ${internalKey}: Cache ${result ? 'HIT' : 'MISS'}`);
199
+ if (!fetchData) {
200
+ return result;
201
+ }
202
+ if (result) {
203
+ const shouldRefresh = await this.shouldRefresh(internalKey);
204
+ const isRefreshing = this.currentlyExecutingBackgroundRefreshes.has(internalKey);
205
+ if (isRefreshing) {
206
+ debug(`Background refresh already happening for ${internalKey}`);
207
+ }
208
+ if (!isRefreshing && shouldRefresh) {
209
+ debug(`Doing background refresh for ${internalKey}`);
210
+ this.currentlyExecutingBackgroundRefreshes.add(internalKey);
211
+ fetchData().then(async (data) => {
212
+ await this.set(key, data); // We don't use `internalKey` here because `set` handles it
213
+ this.currentlyExecutingBackgroundRefreshes.delete(internalKey);
214
+ }).catch((error) => {
215
+ this.currentlyExecutingBackgroundRefreshes.delete(internalKey);
216
+ logging.error({
217
+ message: 'There was an error refreshing cache data in the background',
218
+ error: error
219
+ });
220
+ });
221
+ }
222
+ return result;
223
+ } else {
224
+ const data = await fetchData();
225
+ await this.set(key, data); // We don't use `internalKey` here because `set` handles it
226
+ return data;
227
+ }
228
+ } catch (err) {
229
+ logging.error(err);
230
+ }
231
+ }
232
+
233
+ /**
234
+ *
235
+ * @param {String} key
236
+ * @param {*} value
237
+ */
238
+ async set(key, value) {
239
+ const internalKey = this._buildKey(key);
240
+ debug('set', internalKey);
241
+ try {
242
+ return await this.cache.set(internalKey, value);
243
+ } catch (err) {
244
+ logging.error(err);
245
+ }
246
+ }
247
+
248
+ /**
249
+ * Reset the cache by deleting everything from redis
250
+ */
251
+ async reset() {
252
+ debug('reset');
253
+ try {
254
+ const t0 = performance.now();
255
+ logging.debug(`[RedisAdapter] Clearing cache: scanning for keys matching ${this._keysPattern}`);
256
+ const keys = await this.#getKeys();
257
+ logging.debug(`[RedisAdapter] Clearing cache: found ${keys.length} keys matching ${this._keysPattern} in ${(performance.now() - t0).toFixed(1)}ms`);
258
+ metrics.metric('cache-reset-scan', (performance.now() - t0).toFixed(1));
259
+ const t1 = performance.now();
260
+ for (const key of keys) {
261
+ await this.cache.del(key);
262
+ }
263
+ logging.debug(`[RedisAdapter] Clearing cache: deleted ${keys.length} keys matching ${this._keysPattern} in ${(performance.now() - t1).toFixed(1)}ms`);
264
+ metrics.metric('cache-reset-delete', (performance.now() - t1).toFixed(1));
265
+ metrics.metric('cache-reset', (performance.now() - t0).toFixed(1));
266
+ metrics.metric('cache-reset-key-count', keys.length);
267
+ } catch (err) {
268
+ logging.error(err);
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Helper method to assist "getAll" type of operations
274
+ * @returns {Promise<Array<String>>} all keys present in the cache
275
+ */
276
+ async keys() {
277
+ try {
278
+ return (await this.#getKeys()).map((key) => {
279
+ return this._removeKeyPrefix(key);
280
+ });
281
+ } catch (err) {
282
+ logging.error(err);
283
+ }
284
+ }
285
+ }
286
+
287
+ module.exports = AdapterCacheRedis;
@@ -0,0 +1,22 @@
1
+ const defaultCacheManager = require('cache-manager-ioredis');
2
+ let redisStoreSingletonInstance;
3
+
4
+ /**
5
+ *
6
+ * @param {object} [storeOptions] options to pass to the Redis store instance
7
+ * @param {boolean} [reuseConnection] specifies if the Redis store/connection should be reused within the process
8
+ * @param {object} [CacheManager] CacheManager constructor to instantiate, defaults to cache-manager-ioredis
9
+ */
10
+ const getRedisStore = (storeOptions, reuseConnection = true, CacheManager = defaultCacheManager) => {
11
+ if (storeOptions && reuseConnection) {
12
+ if (!redisStoreSingletonInstance) {
13
+ redisStoreSingletonInstance = CacheManager.create(storeOptions);
14
+ }
15
+
16
+ return redisStoreSingletonInstance;
17
+ } else {
18
+ return CacheManager;
19
+ }
20
+ };
21
+
22
+ module.exports.getRedisStore = getRedisStore;
@@ -30,10 +30,16 @@ function getCacheHeaderFromEventString(event, dto) {
30
30
  return true;
31
31
  }
32
32
  if (event === 'scheduled_updated' || event === 'draft_updated') {
33
+ const baseUrl = urlUtils.urlFor({
34
+ relativeUrl: urlUtils.urlJoin('/p', dto.uuid, '/')
35
+ });
33
36
  return {
34
- value: urlUtils.urlFor({
35
- relativeUrl: urlUtils.urlJoin('/p', dto.uuid, '/')
36
- })
37
+ value: [
38
+ baseUrl,
39
+ `${baseUrl}?member_status=anonymous`,
40
+ `${baseUrl}?member_status=free`,
41
+ `${baseUrl}?member_status=paid`
42
+ ].join(', ')
37
43
  };
38
44
  }
39
45
  }
@@ -2,11 +2,39 @@ const tpl = require('@tryghost/tpl');
2
2
  const errors = require('@tryghost/errors');
3
3
  const models = require('../../models');
4
4
  const ALLOWED_INCLUDES = ['authors', 'tags'];
5
+ const ALLOWED_MEMBER_STATUSES = ['anonymous', 'free', 'paid'];
5
6
 
6
7
  const messages = {
7
8
  postNotFound: 'Post not found.'
8
9
  };
9
10
 
11
+ // Simulate serving content as different member states by setting the minimal
12
+ // member context needed for content gating to function
13
+ const _addMemberContextToFrame = (frame) => {
14
+ if (!frame?.options?.member_status) {
15
+ return;
16
+ }
17
+
18
+ // only set apiType when given a member_status to preserve backwards compatibility
19
+ // where we used to serve "Admin API" content with no gating for all previews
20
+ frame.apiType = 'content';
21
+
22
+ frame.original ??= {};
23
+ frame.original.context ??= {};
24
+
25
+ if (frame.options?.member_status === 'free') {
26
+ frame.original.context.member = {
27
+ status: 'free'
28
+ };
29
+ }
30
+
31
+ if (frame.options?.member_status === 'paid') {
32
+ frame.original.context.member = {
33
+ status: 'paid'
34
+ };
35
+ }
36
+ };
37
+
10
38
  /** @type {import('@tryghost/api-framework').Controller} */
11
39
  const controller = {
12
40
  docName: 'previews',
@@ -17,7 +45,8 @@ const controller = {
17
45
  },
18
46
  permissions: true,
19
47
  options: [
20
- 'include'
48
+ 'include',
49
+ 'member_status'
21
50
  ],
22
51
  data: [
23
52
  'uuid'
@@ -26,6 +55,9 @@ const controller = {
26
55
  options: {
27
56
  include: {
28
57
  values: ALLOWED_INCLUDES
58
+ },
59
+ member_status: {
60
+ values: ALLOWED_MEMBER_STATUSES
29
61
  }
30
62
  },
31
63
  data: {
@@ -35,6 +67,8 @@ const controller = {
35
67
  }
36
68
  },
37
69
  query(frame) {
70
+ _addMemberContextToFrame(frame);
71
+
38
72
  return models.Post.findOne(Object.assign({status: 'all'}, frame.data), frame.options)
39
73
  .then((model) => {
40
74
  if (!model) {
@@ -21,7 +21,8 @@ const controller = {
21
21
  },
22
22
  options: [
23
23
  'include',
24
- 'type'
24
+ 'type',
25
+ 'id'
25
26
  ],
26
27
  data: [
27
28
  'name'
@@ -32,6 +33,9 @@ const controller = {
32
33
  type: {
33
34
  required: true,
34
35
  values: Object.keys(allowedTypes)
36
+ },
37
+ id: {
38
+ required: false
35
39
  }
36
40
  },
37
41
  data: {
@@ -41,7 +45,7 @@ const controller = {
41
45
  }
42
46
  },
43
47
  query(frame) {
44
- return models.Base.Model.generateSlug(allowedTypes[frame.options.type], frame.data.name, {status: 'all'})
48
+ return models.Base.Model.generateSlug(allowedTypes[frame.options.type], frame.data.name, {status: 'all', modelId: frame.options.id})
45
49
  .then((slug) => {
46
50
  if (!slug) {
47
51
  return Promise.reject(new errors.InternalServerError({
@@ -8,7 +8,7 @@ const {PERMIT_ACCESS} = membersService.contentGating;
8
8
  const HAS_GATED_BLOCKS_REGEX = /<!--\s*kg-gated-block:begin/;
9
9
  // Match gated block comments
10
10
  // e.g. <!--kg-gated-block:begin nonMember:true memberSegment:"status:free"-->...gated content<!--kg-gated-block:end-->
11
- const GATED_BLOCK_REGEX = /<!--\s*kg-gated-block:begin\s+([^\n]+?)\s*-->\s*([\s\S]*?)\s*<!--\s*kg-gated-block:end\s*-->/g;
11
+ const GATED_BLOCK_REGEX = /<!--kg-gated-block:begin ([^\n]+?)\s*-->([\s\S]*?)<!--kg-gated-block:end-->/g;
12
12
  // Match the key-value pairs (with optional quotes around the value) in the gated-block:begin comment
13
13
  const GATED_BLOCK_PARAM_REGEX = /\b(?<key>\w+):["']?(?<value>[^"'\s]+)["']?/g;
14
14
 
@@ -70,15 +70,14 @@ const stripGatedBlocks = function (html, member) {
70
70
  });
71
71
  };
72
72
 
73
- function _updatePlaintext(attrs) {
73
+ function _updateTextAttrs(attrs) {
74
74
  if (attrs.html) {
75
75
  attrs.plaintext = htmlToPlaintext.excerpt(attrs.html);
76
76
  }
77
- }
78
77
 
79
- function _updateExcerpt(attrs) {
80
78
  if (!attrs.custom_excerpt && attrs.excerpt) {
81
- attrs.excerpt = htmlToPlaintext.excerpt(attrs.html).substring(0, 500);
79
+ const plaintext = attrs.plaintext || htmlToPlaintext.excerpt(attrs.html);
80
+ attrs.excerpt = plaintext.substring(0, 500);
82
81
  }
83
82
  }
84
83
 
@@ -96,8 +95,7 @@ const forPost = (attrs, frame) => {
96
95
 
97
96
  if (paywallIndex !== -1) {
98
97
  attrs.html = attrs.html.slice(0, paywallIndex);
99
- _updatePlaintext(attrs);
100
- _updateExcerpt(attrs);
98
+ _updateTextAttrs(attrs);
101
99
  } else {
102
100
  ['plaintext', 'html', 'excerpt'].forEach((field) => {
103
101
  if (attrs[field] !== undefined) {
@@ -111,8 +109,7 @@ const forPost = (attrs, frame) => {
111
109
  const hasGatedBlocks = HAS_GATED_BLOCKS_REGEX.test(attrs.html);
112
110
  if (hasGatedBlocks) {
113
111
  attrs.html = module.exports.stripGatedBlocks(attrs.html, frame.original.context.member);
114
- _updatePlaintext(attrs);
115
- _updateExcerpt(attrs);
112
+ _updateTextAttrs(attrs);
116
113
  }
117
114
  }
118
115