ghost 6.54.1 → 6.56.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 (356) hide show
  1. package/components/tryghost-adapter-base-scheduling-0.2.0.tgz +0 -0
  2. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  3. package/components/tryghost-kg-default-nodes-2.1.4.tgz +0 -0
  4. package/components/tryghost-kg-default-transforms-1.3.3.tgz +0 -0
  5. package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
  6. package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
  7. package/components/tryghost-nql-string-0.0.0.tgz +0 -0
  8. package/core/boot.js +3 -6
  9. package/core/built/admin/assets/{Combination-CJG7Flp-.js → Combination-BCm9FL4o.js} +1 -1
  10. package/core/built/admin/assets/{PolarAngleAxis-BGjf7sBu.js → PolarAngleAxis-C4qtHy1B.js} +1 -1
  11. package/core/built/admin/assets/{RadialBarChart-zeFf9mBz.js → RadialBarChart-Bh09Stsj.js} +1 -1
  12. package/core/built/admin/assets/{account-migration-BklF2I_I.js → account-migration-D3O_yVck.js} +1 -1
  13. package/core/built/admin/assets/activitypub/{account-migration-CNovr6W-.js → account-migration-D8FbP-Z-.js} +7 -7
  14. package/core/built/admin/assets/activitypub/activitypub.js +6 -6
  15. package/core/built/admin/assets/activitypub/{avatar-B3DEDB1l.js → avatar-11EA1VSs.js} +2 -2
  16. package/core/built/admin/assets/activitypub/{avatar-flipboard-Cqrg0XMx.js → avatar-flipboard-jfbLG-xe.js} +2 -2
  17. package/core/built/admin/assets/activitypub/{bluesky-sharing-Ceslb1_M.js → bluesky-sharing-f1jTQe2P.js} +16 -16
  18. package/core/built/admin/assets/activitypub/{content-formatters-BpQARh28.js → content-formatters-nmTOPY9M.js} +3 -3
  19. package/core/built/admin/assets/activitypub/{copy-D9UWIAan.js → copy-BCwo9ep3.js} +2 -2
  20. package/core/built/admin/assets/activitypub/{dist-DHAjBcV3.js → dist-BEfX-0aW.js} +12 -12
  21. package/core/built/admin/assets/activitypub/{domain-C1qQvJ8C.js → domain-AaDlZlrA.js} +9 -9
  22. package/core/built/admin/assets/activitypub/{edit-profile-BcH-jEfM.js → edit-profile-DPi8Tl5M.js} +945 -945
  23. package/core/built/admin/assets/activitypub/{explore-AxwBh9dI.js → explore-CxTzjILQ.js} +8 -8
  24. package/core/built/admin/assets/activitypub/{feed-BaOlbh7a.js → feed-DPv5x2MX.js} +33 -33
  25. package/core/built/admin/assets/activitypub/{field-B7mYYQpN.js → field-DbgA8lat.js} +3 -3
  26. package/core/built/admin/assets/activitypub/{hash-BC9Q0N4c.js → hash-BWuZoYkA.js} +2 -2
  27. package/core/built/admin/assets/activitypub/{inbox-DQ4ZnC1m.js → inbox-35VDQ2K2.js} +2 -2
  28. package/core/built/admin/assets/activitypub/{inbox-vXez-5uJ.js → inbox-DSrF_i1O.js} +85 -85
  29. package/core/built/admin/assets/activitypub/{label-Chv5T8bw.js → label-CPjFBbo7.js} +3 -3
  30. package/core/built/admin/assets/activitypub/layout-CTgrezN7.js +1 -0
  31. package/core/built/admin/assets/activitypub/{moderation-DtSM2i1I.js → moderation-BkDC1XVZ.js} +11 -11
  32. package/core/built/admin/assets/activitypub/{note-Dckp4S-2.js → note-xEyH4xT-.js} +7 -7
  33. package/core/built/admin/assets/activitypub/{notifications-CG5T22vg.js → notifications-DClaL3ui.js} +9 -9
  34. package/core/built/admin/assets/activitypub/{preferences-D_799jdl.js → preferences-DpnOa-4Z.js} +20 -20
  35. package/core/built/admin/assets/activitypub/{profile-D2JfXBQl.js → profile-Dvi9mz-6.js} +237 -237
  36. package/core/built/admin/assets/activitypub/{reply-CpdMeAO5.js → reply-Cs9ySd6Z.js} +2 -2
  37. package/core/built/admin/assets/activitypub/{routes-CCHe2dQp.js → routes-DFBeTnfn.js} +1139 -1150
  38. package/core/built/admin/assets/activitypub/{separator-BI1FLsYM.js → separator-C5u_VjzY.js} +3 -3
  39. package/core/built/admin/assets/activitypub/{settings-kx-9Enkz.js → settings-CV2fFu6D.js} +13 -13
  40. package/core/built/admin/assets/activitypub/{site-x4_IjVJ0.js → site-_TgDd_EM.js} +81 -81
  41. package/core/built/admin/assets/activitypub/{step-1-DIDg4aHb.js → step-1-BfnNL2oF.js} +5 -5
  42. package/core/built/admin/assets/activitypub/{step-2-BfGe8BYt.js → step-2-DLndZkO-.js} +6 -6
  43. package/core/built/admin/assets/activitypub/{step-3-mE9spAqw.js → step-3-A-PZTtRE.js} +10 -10
  44. package/core/built/admin/assets/activitypub/{tabs-CyPRQQ1N.js → tabs-BUbz7tea.js} +321 -319
  45. package/core/built/admin/assets/activitypub/{topic-filter-DLHBZT93.js → topic-filter-BdX_xYYT.js} +3 -3
  46. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-VzbN99u_.js → use-activity-pub-queries-EwuRemHh.js} +2 -2
  47. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-CAwXTmqH.js → use-navigate-with-base-path-cy4ACBc4.js} +92 -90
  48. package/core/built/admin/assets/activitypub/{use-reply-chain-data-BAB6fNGt.js → use-reply-chain-data-CyFcGfan.js} +5 -5
  49. package/core/built/admin/assets/activitypub/{x-C8tAnXly.js → x-7zk8mV2p.js} +2 -2
  50. package/core/built/admin/assets/audience-CkBb2LlO.js +1 -0
  51. package/core/built/admin/assets/{automated-emails-of1MvVsv.js → automated-emails-6t2bIRWf.js} +2 -2
  52. package/core/built/admin/assets/{automations-B7-y3dXt.js → automations-DhKeS_U5.js} +1 -1
  53. package/core/built/admin/assets/{automations-DRFjW5fw.js → automations-cMllU545.js} +1 -1
  54. package/core/built/admin/assets/{bluesky-sharing-BbbjWdzs.js → bluesky-sharing-DPGpyD-G.js} +1 -1
  55. package/core/built/admin/assets/{box-BWbhXh21.js → box-By_EgGtm.js} +1 -1
  56. package/core/built/admin/assets/{breadcrumb-B-M9Ip_2.js → breadcrumb-DIfcF5ow.js} +1 -1
  57. package/core/built/admin/assets/build-CsiH26uS.js +1 -0
  58. package/core/built/admin/assets/{card-DGoDJzrn.js → card-aUHX0hyR.js} +1 -1
  59. package/core/built/admin/assets/{chart-BuRT2Y6p.js → chart-BnXyfH11.js} +1 -1
  60. package/core/built/admin/assets/{chart-helpers-xiSsW8r0.js → chart-helpers-DV0FkdcD.js} +1 -1
  61. package/core/built/admin/assets/{checkbox-EhJKHFdZ.js → checkbox-BIl077a0.js} +1 -1
  62. package/core/built/admin/assets/{chunk.361.3cdb584b973959e52ccc.js → chunk.332.05991a101d67f732dec3.js} +16 -12
  63. package/core/built/admin/assets/chunk.524.87b52f4fada529df2c4c.js +25 -0
  64. package/core/built/admin/assets/chunk.582.340bcca873e806cbd4bb.js +27 -0
  65. package/core/built/admin/assets/{code-editor-view-CZtJFGhR.js → code-editor-view-C0aWNwdl.js} +1 -1
  66. package/core/built/admin/assets/comments-qwhIjH3c.js +1 -0
  67. package/core/built/admin/assets/{container-BYz2Ed39.js → container-LiZf3XdY.js} +1 -1
  68. package/core/built/admin/assets/{data-list-16rkJYYi.js → data-list-CupF7bjM.js} +1 -1
  69. package/core/built/admin/assets/{date-range-select-CTlyo8zW.js → date-range-select-dXc-GsVe.js} +1 -1
  70. package/core/built/admin/assets/{disabled-sources-indicator-qs2GfLvl.js → disabled-sources-indicator-DYAXW-6V.js} +1 -1
  71. package/core/built/admin/assets/dist-BUKmD51u.js +1 -0
  72. package/core/built/admin/assets/{dist-2Tnz2Yfq.js → dist-CNlMj7ML.js} +1 -1
  73. package/core/built/admin/assets/{dist-DGzPJ_bO.js → dist-Cb5RSHSg.js} +1 -1
  74. package/core/built/admin/assets/{dist-P_ohUlTi.js → dist-CqO2lWS0.js} +1 -1
  75. package/core/built/admin/assets/{domain-CPNXKVOV.js → domain-B5ogXUBa.js} +1 -1
  76. package/core/built/admin/assets/{dropzone-BmCXnw6J.js → dropzone-BnFG7dDP.js} +1 -1
  77. package/core/built/admin/assets/{edit-profile-SGrJve5M.js → edit-profile-B07jCZy9.js} +1 -1
  78. package/core/built/admin/assets/editor-DDYSOXGN.js +8 -0
  79. package/core/built/admin/assets/{empty-indicator-oX1u1Pkn.js → empty-indicator-WB1WgT9h.js} +1 -1
  80. package/core/built/admin/assets/{explore-CUYLv9G9.js → explore-Ca1y8HW_.js} +1 -1
  81. package/core/built/admin/assets/{feed-CPdJMttL.js → feed-x4iFMBP4.js} +1 -1
  82. package/core/built/admin/assets/field-ChCAabZg.js +1 -0
  83. package/core/built/admin/assets/filter-sources-LdtH1e-T.js +1 -0
  84. package/core/built/admin/assets/{filters-bXkkWro8.js → filters-CLehPo_D.js} +1 -1
  85. package/core/built/admin/assets/filters-CjVKRP8U.js +1 -0
  86. package/core/built/admin/assets/{ghost-05f9a7182ed56903dba05878a5b8422b.js → ghost-7f01131115186d1af84ead76e79646dd.js} +275 -255
  87. package/core/built/admin/assets/{ghost-3063eae29e6c9cc6d6e7131928a27da0.css → ghost-d38c30b866e59f2e06a357b5eb844fe5.css} +1 -1
  88. package/core/built/admin/assets/{ghost-dark-c8d0ab7f515b2010c173e23e9b32ea8a.css → ghost-dark-1bffd5099ba7a749303133efacf8480b.css} +1 -1
  89. package/core/built/admin/assets/gift-link-modal-Buqh2vXh.js +1 -0
  90. package/core/built/admin/assets/{gift-links-nBxJjZiQ.js → gift-links-CsUhefWG.js} +1 -1
  91. package/core/built/admin/assets/growth-DajOBdN0.js +1 -0
  92. package/core/built/admin/assets/growth-Dy2UOSay.js +3 -0
  93. package/core/built/admin/assets/{hooks-BApwwBE0.js → hooks-CixO01lL.js} +1 -1
  94. package/core/built/admin/assets/{inbox-Xx1CdnfO.js → inbox-BUOLDoji.js} +13 -13
  95. package/core/built/admin/assets/index-08n05-rc.js +67 -0
  96. package/core/built/admin/assets/index-DgUzgFvS.css +1 -0
  97. package/core/built/admin/assets/{inline-EMAm3okk.js → inline-Cw-gigDW.js} +1 -1
  98. package/core/built/admin/assets/input-group-uSK3XT7b.js +1 -0
  99. package/core/built/admin/assets/{input-surface-BpAjZXY5.js → input-surface-BdOe4KzO.js} +1 -1
  100. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +9132 -9087
  101. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +121 -121
  102. package/core/built/admin/assets/koenig-lexical/style.css +1 -1
  103. package/core/built/admin/assets/{koenig-lexical-B61IALf5.js → koenig-lexical-BFc4yFed.js} +82 -82
  104. package/core/built/admin/assets/{kpi-card-DCdlyaSS.js → kpi-card-BN-E6WYX.js} +1 -1
  105. package/core/built/admin/assets/{kpi-card-BlZaG1Lo.js → kpi-card-CCt-qeWD.js} +1 -1
  106. package/core/built/admin/assets/{kpi-helpers-DpAuS1Nb.js → kpi-helpers-DlxTbTs4.js} +1 -1
  107. package/core/built/admin/assets/{kpi-tabs-Dme44bQS.js → kpi-tabs-BlcUttpn.js} +1 -1
  108. package/core/built/admin/assets/{label-dSs5aepE.js → label-AVrxwmsz.js} +1 -1
  109. package/core/built/admin/assets/label-picker-DVnpu_IF.js +1 -0
  110. package/core/built/admin/assets/labels-DAIqKed7.js +1 -0
  111. package/core/built/admin/assets/layout-B0YLh7R9.js +1 -0
  112. package/core/built/admin/assets/link-helpers-D9LsDOkG.js +1 -0
  113. package/core/built/admin/assets/{list-page-B6ANtzkd.js → list-page-CdsnQR9N.js} +1 -1
  114. package/core/built/admin/assets/member-custom-fields-C8NpZYza.js +1 -0
  115. package/core/built/admin/assets/member-detail-CIVAdKXf.js +1 -0
  116. package/core/built/admin/assets/members-oLPiSkXD.js +10 -0
  117. package/core/built/admin/assets/modals-CB7v-k3T.js +31 -0
  118. package/core/built/admin/assets/moderation-CQ3urTFE.js +1 -0
  119. package/core/built/admin/assets/newsletter-rbeDX9Wk.js +1 -0
  120. package/core/built/admin/assets/{newsletters-D1jbtV-h.js → newsletters-BXJbJgA-.js} +1 -1
  121. package/core/built/admin/assets/newsletters-vt3Ni1Ov.js +1 -0
  122. package/core/built/admin/assets/note-Dk877BTv.js +1 -0
  123. package/core/built/admin/assets/notifications-CPlLjPxO.js +1 -0
  124. package/core/built/admin/assets/{offers-CrY6nSRR.js → offers-DEniVRD5.js} +1 -1
  125. package/core/built/admin/assets/onboarding-route-DdStOtjy.js +1 -0
  126. package/core/built/admin/assets/overview--4V-kx9c.js +1 -0
  127. package/core/built/admin/assets/overview-DSLv30oz.js +1 -0
  128. package/core/built/admin/assets/{page-header-op0eN36I.js → page-header-pp4dWmOS.js} +1 -1
  129. package/core/built/admin/assets/{pages-BbNWFcGj.js → pages-B4goRPUn.js} +1 -1
  130. package/core/built/admin/assets/{post-analytics-WwRDEnrV.js → post-analytics-BMJrI81R.js} +1 -1
  131. package/core/built/admin/assets/post-analytics-header-pU2m9ZuV.js +1 -0
  132. package/core/built/admin/assets/post-analytics-provider-Btml5vJI.js +1 -0
  133. package/core/built/admin/assets/{post-share-modal-Dx-MEjhI.js → post-share-modal-AcUZkbfL.js} +1 -1
  134. package/core/built/admin/assets/{posts-EWpZIjkr.js → posts-DhXE7b6r.js} +1 -1
  135. package/core/built/admin/assets/preferences-Ru4CRPHC.js +5 -0
  136. package/core/built/admin/assets/profile-BNb1oj7D.js +1 -0
  137. package/core/built/admin/assets/{referrers-BCfFYSHc.js → referrers-pmJT4k3-.js} +1 -1
  138. package/core/built/admin/assets/{router-provider-C1R4AZ2o.js → router-provider-BmEZf6c6.js} +1 -1
  139. package/core/built/admin/assets/select-B0HteHrp.js +1 -0
  140. package/core/built/admin/assets/settings-B1C9Q3wo.js +4 -0
  141. package/core/built/admin/assets/{settings-H6HxFgfI.js → settings-BGgk7oCI.js} +1 -1
  142. package/core/built/admin/assets/{shade-app-B3heOAfz-CGXQwWeO.js → shade-app-B3heOAfz-BhQXxNkT.js} +1 -1
  143. package/core/built/admin/assets/{share-modal-BJCadJyp.js → share-modal-BeuydaBf.js} +1 -1
  144. package/core/built/admin/assets/social-urls-DA2lXrhW.js +8 -0
  145. package/core/built/admin/assets/{sort-button-DyYlfYp7.js → sort-button-Df4t2AL8.js} +1 -1
  146. package/core/built/admin/assets/{source-icon-_ubRDbg_.js → source-icon-ajoaKb41.js} +1 -1
  147. package/core/built/admin/assets/{stack-xiN0eQ6Z.js → stack-CIfOAAVf.js} +1 -1
  148. package/core/built/admin/assets/{stats-BQZMGMvn.js → stats-C_lfxZAw.js} +1 -1
  149. package/core/built/admin/assets/{stats-view-Doei-qCX.js → stats-view-Cxot1iD8.js} +1 -1
  150. package/core/built/admin/assets/{step-1-IKiLBPoC.js → step-1-ogAA4lDr.js} +1 -1
  151. package/core/built/admin/assets/{step-2-jJ41PsDN.js → step-2-C5Xini-7.js} +1 -1
  152. package/core/built/admin/assets/{step-3-CaqFAEqf.js → step-3-CZv1FV81.js} +2 -2
  153. package/core/built/admin/assets/{switch-t-WyENU2.js → switch-CyQcwMAv.js} +1 -1
  154. package/core/built/admin/assets/{table-DxUL78jU.js → table-DFj1wiYv.js} +1 -1
  155. package/core/built/admin/assets/tabs-D4y1gqUd.js +1 -0
  156. package/core/built/admin/assets/{tags-CNujGwkT.js → tags-Dmft-Y4O.js} +1 -1
  157. package/core/built/admin/assets/{text-DwtjwGkh.js → text-DU9Hv33_.js} +1 -1
  158. package/core/built/admin/assets/textarea-d9qEkMOP.js +1 -0
  159. package/core/built/admin/assets/{tiers-BgYLWRJv.js → tiers-DYsa4DCo.js} +1 -1
  160. package/core/built/admin/assets/{toggle-group-BeEpQ69p.js → toggle-group-R2QMFvXW.js} +1 -1
  161. package/core/built/admin/assets/{topic-filter-Bb5MOD-1.js → topic-filter-DGDxDalo.js} +1 -1
  162. package/core/built/admin/assets/{use-analytics-data-Cds6XDjg.js → use-analytics-data-C76v4LIp.js} +1 -1
  163. package/core/built/admin/assets/use-filter-params-WrdhAISv.js +1 -0
  164. package/core/built/admin/assets/{use-growth-stats-D8uf98-G.js → use-growth-stats-aNXJOmQr.js} +1 -1
  165. package/core/built/admin/assets/{use-post-referrers-CRtgyuXB.js → use-post-referrers-CG0SZioj.js} +1 -1
  166. package/core/built/admin/assets/{use-reply-chain-data-DbKYX50v.js → use-reply-chain-data-RLdPKRmS.js} +1 -1
  167. package/core/built/admin/assets/{use-simple-pagination-DC01kpo0.js → use-simple-pagination-DgcF69KI.js} +1 -1
  168. package/core/built/admin/assets/{use-tinybird-query-C7bQRJWw.js → use-tinybird-query-C1R_Kbmy.js} +1 -1
  169. package/core/built/admin/assets/{use-tinybird-token-Az4tam7_.js → use-tinybird-token-C9fPjVM_.js} +1 -1
  170. package/core/built/admin/assets/{users-CcSCQFcQ.js → users-Dkv8NQZe.js} +1 -1
  171. package/core/built/admin/assets/{virtual-list-Dhuh9fGx.js → virtual-list-BmTUzeoF.js} +1 -1
  172. package/core/built/admin/assets/web-BqW0b0_C.js +1 -0
  173. package/core/built/admin/assets/web-Cy5GKMWM.js +2 -0
  174. package/core/built/admin/index.html +18 -18
  175. package/core/frontend/services/data/fetch-data.js +10 -5
  176. package/core/frontend/services/proxy.js +7 -8
  177. package/core/frontend/services/routing/api-adapter.js +115 -0
  178. package/core/frontend/services/routing/api-adapter.ts +157 -0
  179. package/core/frontend/services/routing/collection-router.js +5 -3
  180. package/core/frontend/services/routing/controllers/index.js +1 -1
  181. package/core/frontend/services/routing/controllers/rss.js +26 -31
  182. package/core/frontend/services/routing/controllers/rss.ts +66 -0
  183. package/core/frontend/services/routing/controllers/static.js +8 -7
  184. package/core/frontend/services/routing/parent-router.js +17 -4
  185. package/core/frontend/services/routing/permalink-adapter.js +26 -0
  186. package/core/frontend/services/routing/permalink-adapter.ts +23 -0
  187. package/core/frontend/services/routing/registry.js +59 -33
  188. package/core/frontend/services/routing/router-manager.js +14 -18
  189. package/core/frontend/services/routing/static-routes-router.js +11 -13
  190. package/core/frontend/services/routing/taxonomy-router.js +6 -2
  191. package/core/server/api/endpoints/automation-action-links.js +68 -0
  192. package/core/server/api/endpoints/automation-action-links.ts +46 -0
  193. package/core/server/api/endpoints/index.js +4 -0
  194. package/core/server/api/endpoints/settings.js +0 -2
  195. package/core/server/api/endpoints/utils/serializers/output/utils/url.js +11 -1
  196. package/core/server/data/migrations/versions/6.55/2026-07-27-11-25-44-add-to-hash-to-redirects.js +51 -0
  197. package/core/server/data/migrations/versions/6.55/2026-07-28-12-08-00-remove-routes-hash-from-settings.js +5 -0
  198. package/core/server/data/migrations/versions/6.56/2026-08-03-19-27-51-add-automation-run-step-id-to-automated-email-recipients.js +8 -0
  199. package/core/server/data/migrations/versions/6.56/2026-08-04-14-42-39-restrict-notification-add-permission-to-administrators.js +12 -0
  200. package/core/server/data/schema/commands.js +5 -0
  201. package/core/server/data/schema/default-settings/default-settings.json +0 -4
  202. package/core/server/data/schema/fixtures/fixtures.json +2 -2
  203. package/core/server/data/schema/schema.js +6 -1
  204. package/core/server/models/post.js +10 -1
  205. package/core/server/services/automations/automations-api.js +15 -0
  206. package/core/server/services/automations/automations-api.ts +19 -0
  207. package/core/server/services/automations/automations-repository.ts +20 -0
  208. package/core/server/services/automations/database-automations-repository.js +132 -30
  209. package/core/server/services/automations/database-automations-repository.ts +141 -12
  210. package/core/server/services/automations/database-date.js +10 -0
  211. package/core/server/services/automations/database-date.ts +5 -0
  212. package/core/server/services/automations/get-scheduler-idempotency-key.js +13 -0
  213. package/core/server/services/automations/get-scheduler-idempotency-key.ts +8 -0
  214. package/core/server/services/automations/poll.js +7 -2
  215. package/core/server/services/automations/poll.ts +10 -2
  216. package/core/server/services/automations/scheduler-poll-time.js +40 -0
  217. package/core/server/services/automations/scheduler-poll-time.ts +38 -0
  218. package/core/server/services/automations/service.js +19 -3
  219. package/core/server/services/automations/service.ts +20 -3
  220. package/core/server/services/comments/comments-service-emails.js +10 -7
  221. package/core/server/services/comments/comments-service.js +9 -4
  222. package/core/server/services/email-rendering/partials/email-wrapper.hbs +3 -1
  223. package/core/server/services/email-service/email-service-wrapper.js +6 -1
  224. package/core/server/services/email-service/email-templates/partials/styles.hbs +8 -0
  225. package/core/server/services/email-service/newsletter-email-event-storage.js +41 -10
  226. package/core/server/services/gift-links/CONTEXT.md +9 -0
  227. package/core/server/services/gifts/CONTEXT.md +55 -0
  228. package/core/server/services/gifts/README.md +28 -0
  229. package/core/server/services/gifts/gift-bookshelf-repository.js +65 -45
  230. package/core/server/services/gifts/gift-bookshelf-repository.ts +203 -74
  231. package/core/server/services/gifts/gift-checkout-adapter.js +59 -0
  232. package/core/server/services/gifts/gift-checkout-offer.js +74 -0
  233. package/core/server/services/gifts/gift-checkout-offer.ts +127 -0
  234. package/core/server/services/gifts/gift-codec.js +63 -0
  235. package/core/server/services/gifts/gift-codec.ts +62 -0
  236. package/core/server/services/gifts/gift-controller.js +6 -31
  237. package/core/server/services/gifts/gift-controller.ts +6 -69
  238. package/core/server/services/gifts/gift-email-service.ts +3 -2
  239. package/core/server/services/gifts/gift-schema.js +34 -0
  240. package/core/server/services/gifts/gift-schema.ts +39 -0
  241. package/core/server/services/gifts/gift-service-wrapper.js +13 -11
  242. package/core/server/services/gifts/gift-service.js +265 -52
  243. package/core/server/services/gifts/gift-service.ts +431 -97
  244. package/core/server/services/gifts/gift.ts +16 -39
  245. package/core/server/services/lib/dynamic-redirect-manager.js +12 -3
  246. package/core/server/services/link-redirection/link-redirect-repository.js +41 -4
  247. package/core/server/services/link-redirection/link-redirect.js +2 -0
  248. package/core/server/services/link-redirection/link-redirect.ts +3 -1
  249. package/core/server/services/link-redirection/link-redirects-service.js +38 -2
  250. package/core/server/services/link-tracking/click-event.js +3 -0
  251. package/core/server/services/link-tracking/index.js +38 -6
  252. package/core/server/services/link-tracking/link-click-repository.js +35 -9
  253. package/core/server/services/link-tracking/link-click-tracking-service.js +54 -4
  254. package/core/server/services/member-attribution/outbound-link-tagger.js +3 -1
  255. package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +20 -4
  256. package/core/server/services/member-welcome-emails/service.js +15 -3
  257. package/core/server/services/members/api.js +1 -2
  258. package/core/server/services/members/import-export/csv/formula.js +16 -0
  259. package/core/server/services/members/import-export/csv/formula.ts +14 -0
  260. package/core/server/services/members/import-export/csv/index.js +3 -1
  261. package/core/server/services/members/import-export/csv/index.ts +1 -0
  262. package/core/server/services/members/import-export/import/completion-email.js +22 -3
  263. package/core/server/services/members/import-export/import/completion-email.ts +29 -7
  264. package/core/server/services/members/import-export/import/importer.js +25 -4
  265. package/core/server/services/members/import-export/import/importer.ts +44 -5
  266. package/core/server/services/members/import-export/index.js +18 -1
  267. package/core/server/services/members/import-export/index.ts +32 -3
  268. package/core/server/services/members/members-api/controllers/router-controller.js +38 -36
  269. package/core/server/services/members/members-api/members-api.js +13 -7
  270. package/core/server/services/members/members-api/repositories/event-repository.js +4 -94
  271. package/core/server/services/members/members-api/services/member-bread-service.js +3 -3
  272. package/core/server/services/members/members-api/services/payments-service.js +6 -68
  273. package/core/server/services/members/service.js +8 -1
  274. package/core/server/services/members-custom-fields/schema.js +8 -1
  275. package/core/server/services/members-custom-fields/schema.ts +8 -0
  276. package/core/server/services/members-custom-fields/values-service.js +136 -8
  277. package/core/server/services/members-custom-fields/values-service.ts +152 -10
  278. package/core/server/services/mentions/service.js +5 -1
  279. package/core/server/services/oembed/oembed-service.js +44 -3
  280. package/core/server/services/posts/posts-service.js +11 -0
  281. package/core/server/services/route-settings/dynamic-routing-service.js +21 -52
  282. package/core/server/services/route-settings/index.js +1 -16
  283. package/core/server/services/route-settings/route-settings-parser.js +25 -25
  284. package/core/server/services/route-settings/route-settings-parser.ts +27 -27
  285. package/core/server/services/route-settings/validation-errors.js +68 -4
  286. package/core/server/services/route-settings/validation-errors.ts +79 -4
  287. package/core/server/services/settings/settings-service.js +0 -18
  288. package/core/server/services/stats/posts-stats-service.js +27 -23
  289. package/core/server/services/stripe/services/webhook/charge-refunded-event-service.js +1 -1
  290. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +16 -4
  291. package/core/server/services/stripe/stripe-api.js +10 -0
  292. package/core/server/services/url/url-service-facade.js +1 -1
  293. package/core/server/services/url/url-service-facade.ts +8 -1
  294. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  295. package/core/server/web/gift-preview/controller.js +10 -25
  296. package/core/shared/labs.js +2 -2
  297. package/package.json +8 -7
  298. package/pnpm-lock.yaml +28 -18
  299. package/pnpm-workspace.yaml +6 -4
  300. package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
  301. package/core/built/admin/assets/Growth-BTP1LthE.js +0 -3
  302. package/core/built/admin/assets/Newsletters-CSEX0myc.js +0 -1
  303. package/core/built/admin/assets/Overview-BjACEsJF.js +0 -1
  304. package/core/built/admin/assets/Web-CxxxjCcv.js +0 -2
  305. package/core/built/admin/assets/activitypub/layout-CziktJXo.js +0 -1
  306. package/core/built/admin/assets/audience-X_XsZivI.js +0 -1
  307. package/core/built/admin/assets/chunk.524.8365cd9280405c0f6216.js +0 -25
  308. package/core/built/admin/assets/chunk.582.61d078937e6102769f2c.js +0 -27
  309. package/core/built/admin/assets/comments-CvyXxOyN.js +0 -1
  310. package/core/built/admin/assets/constants-c-JNF9c2.js +0 -1
  311. package/core/built/admin/assets/dist-CjY-f5Z1.js +0 -1
  312. package/core/built/admin/assets/editor-BOG2YPUm.js +0 -8
  313. package/core/built/admin/assets/field-D0XISCK2.js +0 -1
  314. package/core/built/admin/assets/filter-sources-Bieps75g.js +0 -1
  315. package/core/built/admin/assets/filters-CuB-A7ma.js +0 -1
  316. package/core/built/admin/assets/gift-link-modal-su8MsRdg.js +0 -1
  317. package/core/built/admin/assets/growth-Ba6S5FQX.js +0 -1
  318. package/core/built/admin/assets/index-D8PbzuT9.css +0 -1
  319. package/core/built/admin/assets/index-ukW_0Uie.js +0 -67
  320. package/core/built/admin/assets/input-group-CGOXcy4l.js +0 -1
  321. package/core/built/admin/assets/label-picker-Uq91atex.js +0 -1
  322. package/core/built/admin/assets/labels-Cyayfd-e.js +0 -1
  323. package/core/built/admin/assets/layout-WUcz6tu1.js +0 -1
  324. package/core/built/admin/assets/link-helpers-tOlxmuH5.js +0 -1
  325. package/core/built/admin/assets/member-custom-fields-NCzrMuba.js +0 -1
  326. package/core/built/admin/assets/member-detail-DPWDtYnN.js +0 -1
  327. package/core/built/admin/assets/members-B9rO7qr4.js +0 -10
  328. package/core/built/admin/assets/modals-CxRaan1J.js +0 -31
  329. package/core/built/admin/assets/moderation-D8zon60E.js +0 -1
  330. package/core/built/admin/assets/multi-select-combobox-1wYm3nle.js +0 -1
  331. package/core/built/admin/assets/newsletter-DbYMRxRS.js +0 -1
  332. package/core/built/admin/assets/note-PiWPnnrh.js +0 -1
  333. package/core/built/admin/assets/notifications-DEgqqB9G.js +0 -1
  334. package/core/built/admin/assets/nql-BsgFMd93.js +0 -1
  335. package/core/built/admin/assets/onboarding-route-BzF6HeVn.js +0 -1
  336. package/core/built/admin/assets/overview-DixLeIqY.js +0 -1
  337. package/core/built/admin/assets/post-analytics-header-DsMoLx0R.js +0 -1
  338. package/core/built/admin/assets/post-analytics-provider-BJCPLMIb.js +0 -1
  339. package/core/built/admin/assets/preferences-D7-j6A4C.js +0 -5
  340. package/core/built/admin/assets/profile-Dl69QPp0.js +0 -1
  341. package/core/built/admin/assets/select-C1qDZnW4.js +0 -1
  342. package/core/built/admin/assets/settings-BbmC_Iqq.js +0 -4
  343. package/core/built/admin/assets/social-urls-BPL1nADp.js +0 -8
  344. package/core/built/admin/assets/tabs-BQcJQ1ke.js +0 -1
  345. package/core/built/admin/assets/textarea-Bjccvh_E.js +0 -1
  346. package/core/built/admin/assets/use-filter-params-DjXkhO54.js +0 -1
  347. package/core/built/admin/assets/web-LtHNiNBV.js +0 -1
  348. package/core/server/services/gifts/gift-repository.js +0 -2
  349. package/core/server/services/gifts/gift-repository.ts +0 -29
  350. package/core/server/services/route-settings/activation-bridge.js +0 -164
  351. package/core/server/services/route-settings/activation-bridge.ts +0 -210
  352. package/core/server/services/route-settings/default-settings-manager.js +0 -63
  353. package/core/server/services/route-settings/route-settings.js +0 -170
  354. package/core/server/services/route-settings/settings-loader.js +0 -60
  355. package/core/server/services/route-settings/settings-path-manager.js +0 -47
  356. package/core/server/services/route-settings/validate.js +0 -440
@@ -0,0 +1,74 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveGiftDuration = resolveGiftDuration;
7
+ exports.validateGiftCheckoutOffer = validateGiftCheckoutOffer;
8
+ const errors_1 = __importDefault(require("@tryghost/errors"));
9
+ // Mirrored by the Portal catalogue in apps/portal/src/utils/gift-subscriptions.ts
10
+ // until later customization work makes durations server-provided
11
+ const GIFT_DURATION_CATALOGUE = new Map([
12
+ [1, { cadence: 'month', billingDuration: 1, portalPlan: 'monthly', priceProperty: 'monthlyPrice', multiplier: 1 }],
13
+ [3, { cadence: 'month', billingDuration: 3, portalPlan: 'monthly', priceProperty: 'monthlyPrice', multiplier: 3 }],
14
+ [6, { cadence: 'month', billingDuration: 6, portalPlan: 'monthly', priceProperty: 'monthlyPrice', multiplier: 6 }],
15
+ [12, { cadence: 'year', billingDuration: 1, portalPlan: 'yearly', priceProperty: 'yearlyPrice', multiplier: 1 }]
16
+ ]);
17
+ function invalidGiftOffer(context) {
18
+ return new errors_1.default.BadRequestError({
19
+ message: 'Bad Request.',
20
+ context
21
+ });
22
+ }
23
+ function resolveGiftDuration({ duration, cadence }) {
24
+ let totalMonths = duration;
25
+ if (totalMonths === undefined) {
26
+ if (cadence === 'month') {
27
+ totalMonths = 1;
28
+ }
29
+ else if (cadence === 'year') {
30
+ totalMonths = 12;
31
+ }
32
+ else {
33
+ throw invalidGiftOffer('Expected a supported gift duration or legacy cadence');
34
+ }
35
+ }
36
+ if (typeof totalMonths !== 'number' || !Number.isInteger(totalMonths)) {
37
+ throw invalidGiftOffer(`Unsupported gift duration "${totalMonths}"`);
38
+ }
39
+ const offer = GIFT_DURATION_CATALOGUE.get(totalMonths);
40
+ if (!offer) {
41
+ throw invalidGiftOffer(`Unsupported gift duration "${totalMonths}"`);
42
+ }
43
+ if (cadence !== undefined && cadence !== offer.cadence) {
44
+ throw invalidGiftOffer(`Gift duration "${totalMonths}" conflicts with cadence "${cadence}"`);
45
+ }
46
+ return {
47
+ ...offer,
48
+ totalMonths,
49
+ isCadenceOnly: duration === undefined
50
+ };
51
+ }
52
+ function validateGiftCheckoutOffer({ tier, portalPlans, offer }) {
53
+ if (tier.status !== 'active' || tier.visibility !== 'public' || tier.type !== 'paid') {
54
+ throw invalidGiftOffer('The requested tier is not available for gift purchases');
55
+ }
56
+ // legacy cadence-only requests predate the Portal plan gate, keep them working
57
+ if (!offer.isCadenceOnly && (!Array.isArray(portalPlans) || !portalPlans.includes(offer.portalPlan))) {
58
+ throw invalidGiftOffer(`The ${offer.portalPlan} Portal plan is not available`);
59
+ }
60
+ const unitAmount = tier[offer.priceProperty];
61
+ if (typeof unitAmount !== 'number' || !Number.isSafeInteger(unitAmount) || unitAmount <= 0 || typeof tier.currency !== 'string' || !tier.currency) {
62
+ throw invalidGiftOffer('The requested tier does not have a valid gift price');
63
+ }
64
+ const amount = unitAmount * offer.multiplier;
65
+ if (!Number.isSafeInteger(amount)) {
66
+ throw invalidGiftOffer('The requested gift amount is invalid');
67
+ }
68
+ return {
69
+ cadence: offer.cadence,
70
+ duration: offer.billingDuration,
71
+ totalMonths: offer.totalMonths,
72
+ amount
73
+ };
74
+ }
@@ -0,0 +1,127 @@
1
+ import errors from '@tryghost/errors';
2
+ import type {GiftCadence} from './gift-schema';
3
+
4
+ export type {GiftCadence} from './gift-schema';
5
+
6
+ export interface GiftCheckoutTier {
7
+ status: string;
8
+ visibility: string;
9
+ type: string;
10
+ currency: string | null;
11
+ monthlyPrice: number | null;
12
+ yearlyPrice: number | null;
13
+ }
14
+
15
+ interface GiftDurationDefinition {
16
+ cadence: GiftCadence;
17
+ billingDuration: number;
18
+ portalPlan: 'monthly' | 'yearly';
19
+ priceProperty: 'monthlyPrice' | 'yearlyPrice';
20
+ multiplier: number;
21
+ }
22
+
23
+ export interface ResolvedGiftDuration extends GiftDurationDefinition {
24
+ totalMonths: number;
25
+ isCadenceOnly: boolean;
26
+ }
27
+
28
+ export interface GiftCheckoutPlan {
29
+ cadence: GiftCadence;
30
+ duration: number;
31
+ totalMonths: number;
32
+ amount: number;
33
+ }
34
+
35
+ // Mirrored by the Portal catalogue in apps/portal/src/utils/gift-subscriptions.ts
36
+ // until later customization work makes durations server-provided
37
+ const GIFT_DURATION_CATALOGUE = new Map<number, GiftDurationDefinition>([
38
+ [1, {cadence: 'month', billingDuration: 1, portalPlan: 'monthly', priceProperty: 'monthlyPrice', multiplier: 1}],
39
+ [3, {cadence: 'month', billingDuration: 3, portalPlan: 'monthly', priceProperty: 'monthlyPrice', multiplier: 3}],
40
+ [6, {cadence: 'month', billingDuration: 6, portalPlan: 'monthly', priceProperty: 'monthlyPrice', multiplier: 6}],
41
+ [12, {cadence: 'year', billingDuration: 1, portalPlan: 'yearly', priceProperty: 'yearlyPrice', multiplier: 1}]
42
+ ]);
43
+
44
+ function invalidGiftOffer(context: string) {
45
+ return new errors.BadRequestError({
46
+ message: 'Bad Request.',
47
+ context
48
+ });
49
+ }
50
+
51
+ export function resolveGiftDuration({
52
+ duration,
53
+ cadence
54
+ }: {
55
+ duration?: number;
56
+ cadence?: string;
57
+ }): ResolvedGiftDuration {
58
+ let totalMonths = duration;
59
+
60
+ if (totalMonths === undefined) {
61
+ if (cadence === 'month') {
62
+ totalMonths = 1;
63
+ } else if (cadence === 'year') {
64
+ totalMonths = 12;
65
+ } else {
66
+ throw invalidGiftOffer('Expected a supported gift duration or legacy cadence');
67
+ }
68
+ }
69
+
70
+ if (typeof totalMonths !== 'number' || !Number.isInteger(totalMonths)) {
71
+ throw invalidGiftOffer(`Unsupported gift duration "${totalMonths}"`);
72
+ }
73
+
74
+ const offer = GIFT_DURATION_CATALOGUE.get(totalMonths);
75
+
76
+ if (!offer) {
77
+ throw invalidGiftOffer(`Unsupported gift duration "${totalMonths}"`);
78
+ }
79
+
80
+ if (cadence !== undefined && cadence !== offer.cadence) {
81
+ throw invalidGiftOffer(`Gift duration "${totalMonths}" conflicts with cadence "${cadence}"`);
82
+ }
83
+
84
+ return {
85
+ ...offer,
86
+ totalMonths,
87
+ isCadenceOnly: duration === undefined
88
+ };
89
+ }
90
+
91
+ export function validateGiftCheckoutOffer({
92
+ tier,
93
+ portalPlans,
94
+ offer
95
+ }: {
96
+ tier: GiftCheckoutTier;
97
+ portalPlans: unknown;
98
+ offer: ResolvedGiftDuration;
99
+ }): GiftCheckoutPlan {
100
+ if (tier.status !== 'active' || tier.visibility !== 'public' || tier.type !== 'paid') {
101
+ throw invalidGiftOffer('The requested tier is not available for gift purchases');
102
+ }
103
+
104
+ // legacy cadence-only requests predate the Portal plan gate, keep them working
105
+ if (!offer.isCadenceOnly && (!Array.isArray(portalPlans) || !portalPlans.includes(offer.portalPlan))) {
106
+ throw invalidGiftOffer(`The ${offer.portalPlan} Portal plan is not available`);
107
+ }
108
+
109
+ const unitAmount = tier[offer.priceProperty];
110
+
111
+ if (typeof unitAmount !== 'number' || !Number.isSafeInteger(unitAmount) || unitAmount <= 0 || typeof tier.currency !== 'string' || !tier.currency) {
112
+ throw invalidGiftOffer('The requested tier does not have a valid gift price');
113
+ }
114
+
115
+ const amount = unitAmount * offer.multiplier;
116
+
117
+ if (!Number.isSafeInteger(amount)) {
118
+ throw invalidGiftOffer('The requested gift amount is invalid');
119
+ }
120
+
121
+ return {
122
+ cadence: offer.cadence,
123
+ duration: offer.billingDuration,
124
+ totalMonths: offer.totalMonths,
125
+ amount
126
+ };
127
+ }
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.giftCodec = void 0;
4
+ exports.decodeGiftRow = decodeGiftRow;
5
+ exports.encodeGift = encodeGift;
6
+ const zod_1 = require("zod");
7
+ const gift_1 = require("./gift");
8
+ const gift_schema_1 = require("./gift-schema");
9
+ exports.giftCodec = zod_1.z.codec(gift_schema_1.DbGift, zod_1.z.instanceof(gift_1.Gift), {
10
+ decode: row => new gift_1.Gift({
11
+ token: row.token,
12
+ buyerEmail: row.buyer_email,
13
+ buyerMemberId: row.buyer_member_id,
14
+ redeemerMemberId: row.redeemer_member_id,
15
+ tierId: row.tier_id,
16
+ cadence: row.cadence,
17
+ duration: row.duration,
18
+ currency: row.currency,
19
+ amount: row.amount,
20
+ stripeCheckoutSessionId: row.stripe_checkout_session_id,
21
+ stripePaymentIntentId: row.stripe_payment_intent_id,
22
+ consumesAt: row.consumes_at,
23
+ expiresAt: row.expires_at,
24
+ status: row.status,
25
+ purchasedAt: row.purchased_at,
26
+ redeemedAt: row.redeemed_at,
27
+ consumedAt: row.consumed_at,
28
+ expiredAt: row.expired_at,
29
+ refundedAt: row.refunded_at,
30
+ consumesSoonReminderSentAt: row.consumes_soon_reminder_sent_at
31
+ }),
32
+ encode: gift => ({
33
+ token: gift.token,
34
+ buyer_email: gift.buyerEmail,
35
+ buyer_member_id: gift.buyerMemberId,
36
+ redeemer_member_id: gift.redeemerMemberId,
37
+ tier_id: gift.tierId,
38
+ cadence: gift.cadence,
39
+ duration: gift.duration,
40
+ currency: gift.currency,
41
+ amount: gift.amount,
42
+ stripe_checkout_session_id: gift.stripeCheckoutSessionId,
43
+ stripe_payment_intent_id: gift.stripePaymentIntentId,
44
+ consumes_at: gift.consumesAt,
45
+ expires_at: gift.expiresAt,
46
+ status: gift.status,
47
+ purchased_at: gift.purchasedAt,
48
+ redeemed_at: gift.redeemedAt,
49
+ consumed_at: gift.consumedAt,
50
+ expired_at: gift.expiredAt,
51
+ refunded_at: gift.refundedAt,
52
+ consumes_soon_reminder_sent_at: gift.consumesSoonReminderSentAt
53
+ })
54
+ });
55
+ function decodeGiftRow(input) {
56
+ return exports.giftCodec.parse(input);
57
+ }
58
+ function encodeGift(gift) {
59
+ const row = zod_1.z.encode(exports.giftCodec, gift);
60
+ // DbDate encodes to Date at runtime, but z.encode types the row as DbGift input,
61
+ // whose date fields also accept strings and numbers. Parse to narrow it to GiftRow.
62
+ return gift_schema_1.DbGift.parse(row);
63
+ }
@@ -0,0 +1,62 @@
1
+ import {z} from 'zod';
2
+ import {Gift} from './gift';
3
+ import {DbGift} from './gift-schema';
4
+
5
+ export const giftCodec = z.codec(DbGift, z.instanceof(Gift), {
6
+ decode: row => new Gift({
7
+ token: row.token,
8
+ buyerEmail: row.buyer_email,
9
+ buyerMemberId: row.buyer_member_id,
10
+ redeemerMemberId: row.redeemer_member_id,
11
+ tierId: row.tier_id,
12
+ cadence: row.cadence,
13
+ duration: row.duration,
14
+ currency: row.currency,
15
+ amount: row.amount,
16
+ stripeCheckoutSessionId: row.stripe_checkout_session_id,
17
+ stripePaymentIntentId: row.stripe_payment_intent_id,
18
+ consumesAt: row.consumes_at,
19
+ expiresAt: row.expires_at,
20
+ status: row.status,
21
+ purchasedAt: row.purchased_at,
22
+ redeemedAt: row.redeemed_at,
23
+ consumedAt: row.consumed_at,
24
+ expiredAt: row.expired_at,
25
+ refundedAt: row.refunded_at,
26
+ consumesSoonReminderSentAt: row.consumes_soon_reminder_sent_at
27
+ }),
28
+ encode: gift => ({
29
+ token: gift.token,
30
+ buyer_email: gift.buyerEmail,
31
+ buyer_member_id: gift.buyerMemberId,
32
+ redeemer_member_id: gift.redeemerMemberId,
33
+ tier_id: gift.tierId,
34
+ cadence: gift.cadence,
35
+ duration: gift.duration,
36
+ currency: gift.currency,
37
+ amount: gift.amount,
38
+ stripe_checkout_session_id: gift.stripeCheckoutSessionId,
39
+ stripe_payment_intent_id: gift.stripePaymentIntentId,
40
+ consumes_at: gift.consumesAt,
41
+ expires_at: gift.expiresAt,
42
+ status: gift.status,
43
+ purchased_at: gift.purchasedAt,
44
+ redeemed_at: gift.redeemedAt,
45
+ consumed_at: gift.consumedAt,
46
+ expired_at: gift.expiredAt,
47
+ refunded_at: gift.refundedAt,
48
+ consumes_soon_reminder_sent_at: gift.consumesSoonReminderSentAt
49
+ })
50
+ });
51
+
52
+ export function decodeGiftRow(input: unknown): Gift {
53
+ return giftCodec.parse(input);
54
+ }
55
+
56
+ export function encodeGift(gift: Gift): z.output<typeof DbGift> {
57
+ const row = z.encode(giftCodec, gift);
58
+
59
+ // DbDate encodes to Date at runtime, but z.encode types the row as DbGift input,
60
+ // whose date fields also accept strings and numbers. Parse to narrow it to GiftRow.
61
+ return DbGift.parse(row);
62
+ }
@@ -7,16 +7,13 @@ exports.GiftController = void 0;
7
7
  const errors_1 = __importDefault(require("@tryghost/errors"));
8
8
  class GiftController {
9
9
  service;
10
- tiersService;
11
- constructor({ service, tiersService }) {
10
+ constructor({ service }) {
12
11
  this.service = service;
13
- this.tiersService = tiersService;
14
12
  }
15
13
  async getRedeemable(frame) {
16
14
  const token = frame.data.token;
17
15
  const memberStatus = frame.options?.context?.member?.status ?? null;
18
- const gift = await this.service.getRedeemable(token, memberStatus);
19
- return this.serializeGift(gift);
16
+ return this.service.getRedeemable({ token, memberStatus });
20
17
  }
21
18
  async redeem(frame) {
22
19
  const token = frame.data.token;
@@ -26,32 +23,10 @@ class GiftController {
26
23
  message: 'Member authentication required.'
27
24
  });
28
25
  }
29
- const gift = await this.service.redeem(token, member.id);
30
- return this.serializeGift(gift);
31
- }
32
- async serializeGift(gift) {
33
- const tier = await this.tiersService.api.read(gift.tierId);
34
- if (!tier) {
35
- throw new errors_1.default.InternalServerError({
36
- message: `Tier ${gift.tierId} not found for gift: ${gift.token}`
37
- });
38
- }
39
- const tierJSON = tier.toJSON();
40
- return {
41
- token: gift.token,
42
- cadence: gift.cadence,
43
- duration: gift.duration,
44
- currency: gift.currency,
45
- amount: gift.amount,
46
- expires_at: gift.expiresAt,
47
- consumes_at: gift.consumesAt,
48
- tier: {
49
- id: tierJSON.id,
50
- name: tierJSON.name,
51
- description: tierJSON.description,
52
- benefits: tierJSON.benefits
53
- }
54
- };
26
+ return this.service.redeem({
27
+ token,
28
+ memberId: member.id
29
+ });
55
30
  }
56
31
  }
57
32
  exports.GiftController = GiftController;
@@ -1,5 +1,4 @@
1
1
  import errors from '@tryghost/errors';
2
- import {Gift, type GiftCadence} from './gift';
3
2
  import {GiftService} from './gift-service';
4
3
 
5
4
  type Frame = {
@@ -16,57 +15,22 @@ type Frame = {
16
15
  };
17
16
  };
18
17
 
19
- type SerializedTier = {
20
- id: string;
21
- name: string;
22
- description: string | null;
23
- benefits: string[];
24
- };
25
-
26
- type Tier = {
27
- name: string;
28
- toJSON(): SerializedTier;
29
- };
30
-
31
- type GiftDTO = {
32
- token: string;
33
- cadence: GiftCadence;
34
- duration: number;
35
- currency: string;
36
- amount: number;
37
- expires_at: Date;
38
- consumes_at: Date | null;
39
- tier: SerializedTier;
40
- };
41
-
42
- type TiersService = {
43
- api: {
44
- read(idString: string): Promise<Tier | null>;
45
- };
46
- };
47
-
48
18
  export class GiftController {
49
19
  private readonly service: GiftService;
50
- private readonly tiersService: TiersService;
51
20
 
52
21
  constructor({
53
- service,
54
- tiersService
22
+ service
55
23
  }: {
56
24
  service: GiftService;
57
- tiersService: TiersService;
58
25
  }) {
59
26
  this.service = service;
60
- this.tiersService = tiersService;
61
27
  }
62
28
 
63
29
  async getRedeemable(frame: Frame) {
64
30
  const token = frame.data.token;
65
31
  const memberStatus = frame.options?.context?.member?.status ?? null;
66
32
 
67
- const gift = await this.service.getRedeemable(token, memberStatus);
68
-
69
- return this.serializeGift(gift);
33
+ return this.service.getRedeemable({token, memberStatus});
70
34
  }
71
35
 
72
36
  async redeem(frame: Frame) {
@@ -79,36 +43,9 @@ export class GiftController {
79
43
  });
80
44
  }
81
45
 
82
- const gift = await this.service.redeem(token, member.id);
83
-
84
- return this.serializeGift(gift);
85
- }
86
-
87
- private async serializeGift(gift: Gift): Promise<GiftDTO> {
88
- const tier = await this.tiersService.api.read(gift.tierId);
89
-
90
- if (!tier) {
91
- throw new errors.InternalServerError({
92
- message: `Tier ${gift.tierId} not found for gift: ${gift.token}`
93
- });
94
- }
95
-
96
- const tierJSON = tier.toJSON();
97
-
98
- return {
99
- token: gift.token,
100
- cadence: gift.cadence,
101
- duration: gift.duration,
102
- currency: gift.currency,
103
- amount: gift.amount,
104
- expires_at: gift.expiresAt,
105
- consumes_at: gift.consumesAt,
106
- tier: {
107
- id: tierJSON.id,
108
- name: tierJSON.name,
109
- description: tierJSON.description,
110
- benefits: tierJSON.benefits
111
- }
112
- };
46
+ return this.service.redeem({
47
+ token,
48
+ memberId: member.id
49
+ });
113
50
  }
114
51
  }
@@ -1,4 +1,5 @@
1
1
  import {GiftEmailRenderer, Translate} from './gift-email-renderer';
2
+ import type {GiftCadence} from './gift-schema';
2
3
 
3
4
  const DEFAULT_DATE_LOCALE = 'en-gb';
4
5
 
@@ -29,7 +30,7 @@ interface PurchaseConfirmationData {
29
30
  buyerEmail: string;
30
31
  token: string;
31
32
  tierName: string;
32
- cadence: 'month' | 'year';
33
+ cadence: GiftCadence;
33
34
  duration: number;
34
35
  expiresAt: Date;
35
36
  }
@@ -69,7 +70,7 @@ export class GiftEmailService {
69
70
  }
70
71
  }
71
72
 
72
- private getCadenceLabel(cadence: 'month' | 'year', duration: number): string {
73
+ private getCadenceLabel(cadence: GiftCadence, duration: number): string {
73
74
  if (duration === 1) {
74
75
  return cadence === 'year' ? this.t('one-year') : this.t('one-month');
75
76
  }
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DbGift = exports.GiftStatusSchema = exports.GiftCadenceSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const db_date_1 = require("../../lib/db-date");
6
+ exports.GiftCadenceSchema = zod_1.z.enum(['month', 'year']);
7
+ exports.GiftStatusSchema = zod_1.z.enum(['purchased', 'redeemed', 'consumed', 'expired', 'refunded']);
8
+ /**
9
+ * The persisted gift row. Bookshelf remains the persistence implementation, but
10
+ * values read through it are still runtime data and are validated before they
11
+ * become trusted gift-domain values.
12
+ */
13
+ exports.DbGift = zod_1.z.object({
14
+ token: zod_1.z.string(),
15
+ buyer_email: zod_1.z.string(),
16
+ buyer_member_id: zod_1.z.string().nullable(),
17
+ redeemer_member_id: zod_1.z.string().nullable(),
18
+ tier_id: zod_1.z.string(),
19
+ cadence: exports.GiftCadenceSchema,
20
+ duration: zod_1.z.number().int().nonnegative(),
21
+ currency: zod_1.z.string(),
22
+ amount: zod_1.z.number().int().nonnegative(),
23
+ stripe_checkout_session_id: zod_1.z.string(),
24
+ stripe_payment_intent_id: zod_1.z.string(),
25
+ consumes_at: db_date_1.DbDate.nullable(),
26
+ expires_at: db_date_1.DbDate,
27
+ status: exports.GiftStatusSchema,
28
+ purchased_at: db_date_1.DbDate,
29
+ redeemed_at: db_date_1.DbDate.nullable(),
30
+ consumed_at: db_date_1.DbDate.nullable(),
31
+ expired_at: db_date_1.DbDate.nullable(),
32
+ refunded_at: db_date_1.DbDate.nullable(),
33
+ consumes_soon_reminder_sent_at: db_date_1.DbDate.nullable().default(null)
34
+ });
@@ -0,0 +1,39 @@
1
+ import {z} from 'zod';
2
+ import {DbDate} from '../../lib/db-date';
3
+ import type {CamelKeys} from '../../lib/case-keys';
4
+
5
+ export const GiftCadenceSchema = z.enum(['month', 'year']);
6
+ export const GiftStatusSchema = z.enum(['purchased', 'redeemed', 'consumed', 'expired', 'refunded']);
7
+ export type GiftCadence = z.infer<typeof GiftCadenceSchema>;
8
+ export type GiftStatus = z.infer<typeof GiftStatusSchema>;
9
+
10
+ /**
11
+ * The persisted gift row. Bookshelf remains the persistence implementation, but
12
+ * values read through it are still runtime data and are validated before they
13
+ * become trusted gift-domain values.
14
+ */
15
+ export const DbGift = z.object({
16
+ token: z.string(),
17
+ buyer_email: z.string(),
18
+ buyer_member_id: z.string().nullable(),
19
+ redeemer_member_id: z.string().nullable(),
20
+ tier_id: z.string(),
21
+ cadence: GiftCadenceSchema,
22
+ duration: z.number().int().nonnegative(),
23
+ currency: z.string(),
24
+ amount: z.number().int().nonnegative(),
25
+ stripe_checkout_session_id: z.string(),
26
+ stripe_payment_intent_id: z.string(),
27
+ consumes_at: DbDate.nullable(),
28
+ expires_at: DbDate,
29
+ status: GiftStatusSchema,
30
+ purchased_at: DbDate,
31
+ redeemed_at: DbDate.nullable(),
32
+ consumed_at: DbDate.nullable(),
33
+ expired_at: DbDate.nullable(),
34
+ refunded_at: DbDate.nullable(),
35
+ consumes_soon_reminder_sent_at: DbDate.nullable().default(null)
36
+ });
37
+
38
+ export type GiftRow = z.output<typeof DbGift>;
39
+ export type GiftData = CamelKeys<GiftRow>;
@@ -23,12 +23,13 @@ class GiftServiceWrapper {
23
23
  return;
24
24
  }
25
25
 
26
- const {Gift: GiftModel} = require('../../models');
26
+ const {Gift: GiftModel, MemberStripeCustomer: StripeCustomerModel} = require('../../models');
27
27
  const {GiftBookshelfRepository} = require('./gift-bookshelf-repository');
28
28
  const {GiftService} = require('./gift-service');
29
29
  const {GiftReminderScheduler} = require('./gift-reminder-scheduler');
30
30
  const {GiftEmailService} = require('./gift-email-service');
31
31
  const {GiftController} = require('./gift-controller');
32
+ const GiftCheckoutAdapter = require('./gift-checkout-adapter');
32
33
  const membersService = require('../members');
33
34
  const tiersService = require('../tiers');
34
35
  const staffService = require('../staff');
@@ -41,6 +42,7 @@ class GiftServiceWrapper {
41
42
 
42
43
  const {GhostMailer} = require('../mail');
43
44
  const settingsCache = require('../../../shared/settings-cache');
45
+ const labsService = require('../../../shared/labs');
44
46
  const urlUtils = require('../../../shared/url-utils').default;
45
47
  const settingsHelpers = require('../settings-helpers');
46
48
  const EmailAddressParser = require('../email-address/email-address-parser');
@@ -50,6 +52,10 @@ class GiftServiceWrapper {
50
52
  const repository = new GiftBookshelfRepository({
51
53
  GiftModel
52
54
  });
55
+ const checkoutAdapter = new GiftCheckoutAdapter({
56
+ StripeCustomerModel,
57
+ getStripeApi: () => require('../stripe').api
58
+ });
53
59
 
54
60
  const giftEmailService = new GiftEmailService({
55
61
  mailer: new GhostMailer(),
@@ -77,23 +83,19 @@ class GiftServiceWrapper {
77
83
  get staffServiceEmails() {
78
84
  return staffService.api.emails;
79
85
  },
80
- giftReminderScheduler
86
+ giftReminderScheduler,
87
+ checkoutAdapter,
88
+ labsService,
89
+ settingsCache
81
90
  });
82
91
 
83
92
  this.controller = new GiftController({
84
- service: this.service,
85
- tiersService
93
+ service: this.service
86
94
  });
87
95
 
88
96
  DomainEvents.subscribe(SubscriptionActivatedEvent, async (event) => {
89
97
  try {
90
- const gift = await this.service.getActiveByMember(event.data.memberId);
91
-
92
- if (!gift) {
93
- return;
94
- }
95
-
96
- await this.service.consume(gift.token);
98
+ await this.service.handlePaidSubscriptionActivation(event.data.memberId);
97
99
  } catch (err) {
98
100
  logging.error(err, 'Failed to consume gift on paid subscription activation');
99
101
  }