ghost 6.55.0 → 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 (280) hide show
  1. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  2. package/components/tryghost-kg-default-cards-10.3.3.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/built/admin/assets/{Combination-CJG7Flp-.js → Combination-BCm9FL4o.js} +1 -1
  9. package/core/built/admin/assets/{PolarAngleAxis-BGjf7sBu.js → PolarAngleAxis-C4qtHy1B.js} +1 -1
  10. package/core/built/admin/assets/{RadialBarChart-zeFf9mBz.js → RadialBarChart-Bh09Stsj.js} +1 -1
  11. package/core/built/admin/assets/{account-migration-sjHLx5dy.js → account-migration-D3O_yVck.js} +1 -1
  12. package/core/built/admin/assets/activitypub/{account-migration-DoYXtnZ8.js → account-migration-D8FbP-Z-.js} +7 -7
  13. package/core/built/admin/assets/activitypub/activitypub.js +6 -6
  14. package/core/built/admin/assets/activitypub/{avatar-B3DEDB1l.js → avatar-11EA1VSs.js} +2 -2
  15. package/core/built/admin/assets/activitypub/{avatar-flipboard-Cqrg0XMx.js → avatar-flipboard-jfbLG-xe.js} +2 -2
  16. package/core/built/admin/assets/activitypub/{bluesky-sharing-Bxeh6MKW.js → bluesky-sharing-f1jTQe2P.js} +16 -16
  17. package/core/built/admin/assets/activitypub/{content-formatters-BpQARh28.js → content-formatters-nmTOPY9M.js} +3 -3
  18. package/core/built/admin/assets/activitypub/{copy-D9UWIAan.js → copy-BCwo9ep3.js} +2 -2
  19. package/core/built/admin/assets/activitypub/{dist-D8sFfdpC.js → dist-BEfX-0aW.js} +12 -12
  20. package/core/built/admin/assets/activitypub/{domain-CWueFW6q.js → domain-AaDlZlrA.js} +9 -9
  21. package/core/built/admin/assets/activitypub/{edit-profile-BGxPU7N5.js → edit-profile-DPi8Tl5M.js} +945 -945
  22. package/core/built/admin/assets/activitypub/{explore-DBK-u0Ch.js → explore-CxTzjILQ.js} +8 -8
  23. package/core/built/admin/assets/activitypub/{feed-Bcir5RQZ.js → feed-DPv5x2MX.js} +33 -33
  24. package/core/built/admin/assets/activitypub/{field-BV-CIT3d.js → field-DbgA8lat.js} +3 -3
  25. package/core/built/admin/assets/activitypub/{hash-BC9Q0N4c.js → hash-BWuZoYkA.js} +2 -2
  26. package/core/built/admin/assets/activitypub/{inbox-DQ4ZnC1m.js → inbox-35VDQ2K2.js} +2 -2
  27. package/core/built/admin/assets/activitypub/{inbox-yC0nfvzl.js → inbox-DSrF_i1O.js} +85 -85
  28. package/core/built/admin/assets/activitypub/{label-DTRS9TDk.js → label-CPjFBbo7.js} +3 -3
  29. package/core/built/admin/assets/activitypub/layout-CTgrezN7.js +1 -0
  30. package/core/built/admin/assets/activitypub/{moderation-Dwl9t0pb.js → moderation-BkDC1XVZ.js} +11 -11
  31. package/core/built/admin/assets/activitypub/{note-B-zKsZ7r.js → note-xEyH4xT-.js} +7 -7
  32. package/core/built/admin/assets/activitypub/{notifications-DjJZD54t.js → notifications-DClaL3ui.js} +9 -9
  33. package/core/built/admin/assets/activitypub/{preferences-CoU8vs6d.js → preferences-DpnOa-4Z.js} +20 -20
  34. package/core/built/admin/assets/activitypub/{profile-BAsaJekx.js → profile-Dvi9mz-6.js} +237 -237
  35. package/core/built/admin/assets/activitypub/{reply-CpdMeAO5.js → reply-Cs9ySd6Z.js} +2 -2
  36. package/core/built/admin/assets/activitypub/{routes-CLH3OVTP.js → routes-DFBeTnfn.js} +1139 -1150
  37. package/core/built/admin/assets/activitypub/{separator-BI1FLsYM.js → separator-C5u_VjzY.js} +3 -3
  38. package/core/built/admin/assets/activitypub/{settings-DA6pNJ-Q.js → settings-CV2fFu6D.js} +13 -13
  39. package/core/built/admin/assets/activitypub/{site-CQD42Pw5.js → site-_TgDd_EM.js} +81 -81
  40. package/core/built/admin/assets/activitypub/{step-1-DIDg4aHb.js → step-1-BfnNL2oF.js} +5 -5
  41. package/core/built/admin/assets/activitypub/{step-2-BfGe8BYt.js → step-2-DLndZkO-.js} +6 -6
  42. package/core/built/admin/assets/activitypub/{step-3-mE9spAqw.js → step-3-A-PZTtRE.js} +10 -10
  43. package/core/built/admin/assets/activitypub/{tabs-Cg0saow1.js → tabs-BUbz7tea.js} +317 -315
  44. package/core/built/admin/assets/activitypub/{topic-filter-DLHBZT93.js → topic-filter-BdX_xYYT.js} +3 -3
  45. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-VzbN99u_.js → use-activity-pub-queries-EwuRemHh.js} +2 -2
  46. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-CAwXTmqH.js → use-navigate-with-base-path-cy4ACBc4.js} +92 -90
  47. package/core/built/admin/assets/activitypub/{use-reply-chain-data-Br4aOWOh.js → use-reply-chain-data-CyFcGfan.js} +5 -5
  48. package/core/built/admin/assets/activitypub/{x-C8tAnXly.js → x-7zk8mV2p.js} +2 -2
  49. package/core/built/admin/assets/{audience-DjEWNhp3.js → audience-CkBb2LlO.js} +1 -1
  50. package/core/built/admin/assets/{automated-emails-DRy7qY-J.js → automated-emails-6t2bIRWf.js} +1 -1
  51. package/core/built/admin/assets/{automations-Bx3VcFIL.js → automations-cMllU545.js} +1 -1
  52. package/core/built/admin/assets/{bluesky-sharing-ClMd0-Ft.js → bluesky-sharing-DPGpyD-G.js} +1 -1
  53. package/core/built/admin/assets/{box-BWbhXh21.js → box-By_EgGtm.js} +1 -1
  54. package/core/built/admin/assets/{breadcrumb-BD0U2ygg.js → breadcrumb-DIfcF5ow.js} +1 -1
  55. package/core/built/admin/assets/build-CsiH26uS.js +1 -0
  56. package/core/built/admin/assets/{card-DGoDJzrn.js → card-aUHX0hyR.js} +1 -1
  57. package/core/built/admin/assets/{chart-BuRT2Y6p.js → chart-BnXyfH11.js} +1 -1
  58. package/core/built/admin/assets/{chart-helpers-fjQKRVyY.js → chart-helpers-DV0FkdcD.js} +1 -1
  59. package/core/built/admin/assets/{checkbox-CWa28lgO.js → checkbox-BIl077a0.js} +1 -1
  60. package/core/built/admin/assets/{chunk.481.6c7395a49484bb4583aa.js → chunk.332.05991a101d67f732dec3.js} +43 -42
  61. package/core/built/admin/assets/chunk.524.87b52f4fada529df2c4c.js +25 -0
  62. package/core/built/admin/assets/chunk.582.340bcca873e806cbd4bb.js +27 -0
  63. package/core/built/admin/assets/{code-editor-view-CCr0rBWS.js → code-editor-view-C0aWNwdl.js} +1 -1
  64. package/core/built/admin/assets/comments-qwhIjH3c.js +1 -0
  65. package/core/built/admin/assets/{container-BYz2Ed39.js → container-LiZf3XdY.js} +1 -1
  66. package/core/built/admin/assets/{data-list-16rkJYYi.js → data-list-CupF7bjM.js} +1 -1
  67. package/core/built/admin/assets/{date-range-select-BFp58EMk.js → date-range-select-dXc-GsVe.js} +1 -1
  68. package/core/built/admin/assets/{disabled-sources-indicator-CUxMfiPa.js → disabled-sources-indicator-DYAXW-6V.js} +1 -1
  69. package/core/built/admin/assets/dist-BUKmD51u.js +1 -0
  70. package/core/built/admin/assets/{dist-2Tnz2Yfq.js → dist-CNlMj7ML.js} +1 -1
  71. package/core/built/admin/assets/{dist-DGzPJ_bO.js → dist-Cb5RSHSg.js} +1 -1
  72. package/core/built/admin/assets/{dist-P_ohUlTi.js → dist-CqO2lWS0.js} +1 -1
  73. package/core/built/admin/assets/domain-B5ogXUBa.js +1 -0
  74. package/core/built/admin/assets/{dropzone-DlK-K2DQ.js → dropzone-BnFG7dDP.js} +1 -1
  75. package/core/built/admin/assets/{edit-profile-BKT-REJw.js → edit-profile-B07jCZy9.js} +1 -1
  76. package/core/built/admin/assets/editor-DDYSOXGN.js +8 -0
  77. package/core/built/admin/assets/{empty-indicator-oX1u1Pkn.js → empty-indicator-WB1WgT9h.js} +1 -1
  78. package/core/built/admin/assets/{explore-ClUpJZOp.js → explore-Ca1y8HW_.js} +1 -1
  79. package/core/built/admin/assets/{feed-DF92dUMP.js → feed-x4iFMBP4.js} +1 -1
  80. package/core/built/admin/assets/{field-Do7lieBA.js → field-ChCAabZg.js} +1 -1
  81. package/core/built/admin/assets/filter-sources-LdtH1e-T.js +1 -0
  82. package/core/built/admin/assets/filters-CLehPo_D.js +1 -0
  83. package/core/built/admin/assets/filters-CjVKRP8U.js +1 -0
  84. package/core/built/admin/assets/{ghost-c4057474943effc568c5659ccc4909d7.js → ghost-7f01131115186d1af84ead76e79646dd.js} +149 -129
  85. package/core/built/admin/assets/{ghost-a9c788f6f0b1ea6cf0f14ca4b13f4496.css → ghost-d38c30b866e59f2e06a357b5eb844fe5.css} +1 -1
  86. package/core/built/admin/assets/{ghost-dark-db36d795d21c61426837df40af270966.css → ghost-dark-1bffd5099ba7a749303133efacf8480b.css} +1 -1
  87. package/core/built/admin/assets/{gift-link-modal-f9VIMNDv.js → gift-link-modal-Buqh2vXh.js} +1 -1
  88. package/core/built/admin/assets/{growth-B3I6gNLm.js → growth-DajOBdN0.js} +1 -1
  89. package/core/built/admin/assets/{growth-CVw1My4S.js → growth-Dy2UOSay.js} +2 -2
  90. package/core/built/admin/assets/{inbox-CFBSu4Ed.js → inbox-BUOLDoji.js} +12 -12
  91. package/core/built/admin/assets/index-08n05-rc.js +67 -0
  92. package/core/built/admin/assets/index-DgUzgFvS.css +1 -0
  93. package/core/built/admin/assets/{inline-EMAm3okk.js → inline-Cw-gigDW.js} +1 -1
  94. package/core/built/admin/assets/input-group-uSK3XT7b.js +1 -0
  95. package/core/built/admin/assets/{input-surface-BpAjZXY5.js → input-surface-BdOe4KzO.js} +1 -1
  96. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +9535 -9527
  97. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +124 -124
  98. package/core/built/admin/assets/{koenig-lexical-f8vHRYvz.js → koenig-lexical-BFc4yFed.js} +86 -86
  99. package/core/built/admin/assets/{kpi-card-DSap9rAy.js → kpi-card-BN-E6WYX.js} +1 -1
  100. package/core/built/admin/assets/{kpi-card-DU0rn9Cj.js → kpi-card-CCt-qeWD.js} +1 -1
  101. package/core/built/admin/assets/{kpi-helpers-Dz33AV6h.js → kpi-helpers-DlxTbTs4.js} +1 -1
  102. package/core/built/admin/assets/{kpi-tabs-BtTTrKvJ.js → kpi-tabs-BlcUttpn.js} +1 -1
  103. package/core/built/admin/assets/{label-DS8Mi6G5.js → label-AVrxwmsz.js} +1 -1
  104. package/core/built/admin/assets/{label-picker-1DvSuLA1.js → label-picker-DVnpu_IF.js} +1 -1
  105. package/core/built/admin/assets/labels-DAIqKed7.js +1 -0
  106. package/core/built/admin/assets/layout-B0YLh7R9.js +1 -0
  107. package/core/built/admin/assets/link-helpers-D9LsDOkG.js +1 -0
  108. package/core/built/admin/assets/{list-page-B6ANtzkd.js → list-page-CdsnQR9N.js} +1 -1
  109. package/core/built/admin/assets/member-custom-fields-C8NpZYza.js +1 -0
  110. package/core/built/admin/assets/member-detail-CIVAdKXf.js +1 -0
  111. package/core/built/admin/assets/{members-CQpVFJu4.js → members-oLPiSkXD.js} +2 -2
  112. package/core/built/admin/assets/modals-CB7v-k3T.js +31 -0
  113. package/core/built/admin/assets/{moderation-DySbgyUS.js → moderation-CQ3urTFE.js} +1 -1
  114. package/core/built/admin/assets/newsletter-rbeDX9Wk.js +1 -0
  115. package/core/built/admin/assets/newsletters-vt3Ni1Ov.js +1 -0
  116. package/core/built/admin/assets/{note-C8kxrz6w.js → note-Dk877BTv.js} +1 -1
  117. package/core/built/admin/assets/{notifications-CgtSfX7t.js → notifications-CPlLjPxO.js} +1 -1
  118. package/core/built/admin/assets/{onboarding-route-4qtziA9d.js → onboarding-route-DdStOtjy.js} +1 -1
  119. package/core/built/admin/assets/{overview-Bf2sbznq.js → overview--4V-kx9c.js} +1 -1
  120. package/core/built/admin/assets/overview-DSLv30oz.js +1 -0
  121. package/core/built/admin/assets/{page-header-B3Wb9Jqv.js → page-header-pp4dWmOS.js} +1 -1
  122. package/core/built/admin/assets/{post-analytics-We-ko5Cb.js → post-analytics-BMJrI81R.js} +1 -1
  123. package/core/built/admin/assets/post-analytics-header-pU2m9ZuV.js +1 -0
  124. package/core/built/admin/assets/{post-analytics-provider-B6UynTGb.js → post-analytics-provider-Btml5vJI.js} +1 -1
  125. package/core/built/admin/assets/{post-share-modal-DFfO93_0.js → post-share-modal-AcUZkbfL.js} +1 -1
  126. package/core/built/admin/assets/preferences-Ru4CRPHC.js +5 -0
  127. package/core/built/admin/assets/profile-BNb1oj7D.js +1 -0
  128. package/core/built/admin/assets/{router-provider-C1R4AZ2o.js → router-provider-BmEZf6c6.js} +1 -1
  129. package/core/built/admin/assets/select-B0HteHrp.js +1 -0
  130. package/core/built/admin/assets/settings-B1C9Q3wo.js +4 -0
  131. package/core/built/admin/assets/{settings-WXNeWyU1.js → settings-BGgk7oCI.js} +1 -1
  132. package/core/built/admin/assets/{shade-app-B3heOAfz-CGXQwWeO.js → shade-app-B3heOAfz-BhQXxNkT.js} +1 -1
  133. package/core/built/admin/assets/{share-modal-DsgW3wbg.js → share-modal-BeuydaBf.js} +1 -1
  134. package/core/built/admin/assets/social-urls-DA2lXrhW.js +8 -0
  135. package/core/built/admin/assets/{sort-button-Bb_FCl7e.js → sort-button-Df4t2AL8.js} +1 -1
  136. package/core/built/admin/assets/{source-icon-BeaRxUn9.js → source-icon-ajoaKb41.js} +1 -1
  137. package/core/built/admin/assets/{stack-xiN0eQ6Z.js → stack-CIfOAAVf.js} +1 -1
  138. package/core/built/admin/assets/{stats-Bjj_kmJ1.js → stats-C_lfxZAw.js} +1 -1
  139. package/core/built/admin/assets/{stats-view-BxPbW6K1.js → stats-view-Cxot1iD8.js} +1 -1
  140. package/core/built/admin/assets/{step-1-B1e3-1F8.js → step-1-ogAA4lDr.js} +1 -1
  141. package/core/built/admin/assets/{step-2-C5WaeBJo.js → step-2-C5Xini-7.js} +1 -1
  142. package/core/built/admin/assets/{step-3-B46iVPwu.js → step-3-CZv1FV81.js} +2 -2
  143. package/core/built/admin/assets/{switch-z5Y6Uzt6.js → switch-CyQcwMAv.js} +1 -1
  144. package/core/built/admin/assets/{table-CB-4_HBJ.js → table-DFj1wiYv.js} +1 -1
  145. package/core/built/admin/assets/{tabs-CCEgKYzq.js → tabs-D4y1gqUd.js} +1 -1
  146. package/core/built/admin/assets/{tags-Cg2VJGmG.js → tags-Dmft-Y4O.js} +1 -1
  147. package/core/built/admin/assets/{text-DwtjwGkh.js → text-DU9Hv33_.js} +1 -1
  148. package/core/built/admin/assets/textarea-d9qEkMOP.js +1 -0
  149. package/core/built/admin/assets/{toggle-group-b3IlxvhV.js → toggle-group-R2QMFvXW.js} +1 -1
  150. package/core/built/admin/assets/{topic-filter-DAFT87tQ.js → topic-filter-DGDxDalo.js} +1 -1
  151. package/core/built/admin/assets/{use-analytics-data-S0FTd43s.js → use-analytics-data-C76v4LIp.js} +1 -1
  152. package/core/built/admin/assets/use-filter-params-WrdhAISv.js +1 -0
  153. package/core/built/admin/assets/{use-growth-stats-CIaiHOVb.js → use-growth-stats-aNXJOmQr.js} +1 -1
  154. package/core/built/admin/assets/{use-post-referrers-Cq2vBPrA.js → use-post-referrers-CG0SZioj.js} +1 -1
  155. package/core/built/admin/assets/{use-reply-chain-data-DBDRtGu-.js → use-reply-chain-data-RLdPKRmS.js} +1 -1
  156. package/core/built/admin/assets/{use-simple-pagination-CjYNxdwe.js → use-simple-pagination-DgcF69KI.js} +1 -1
  157. package/core/built/admin/assets/{use-tinybird-query-falHAtuK.js → use-tinybird-query-C1R_Kbmy.js} +1 -1
  158. package/core/built/admin/assets/{use-tinybird-token-D7-8ND1y.js → use-tinybird-token-C9fPjVM_.js} +1 -1
  159. package/core/built/admin/assets/{virtual-list-CvnC9zF2.js → virtual-list-BmTUzeoF.js} +1 -1
  160. package/core/built/admin/assets/{web-B_5GZRvH.js → web-BqW0b0_C.js} +1 -1
  161. package/core/built/admin/assets/web-Cy5GKMWM.js +2 -0
  162. package/core/built/admin/index.html +16 -16
  163. package/core/frontend/services/data/fetch-data.js +10 -5
  164. package/core/frontend/services/routing/api-adapter.js +115 -0
  165. package/core/frontend/services/routing/api-adapter.ts +157 -0
  166. package/core/frontend/services/routing/collection-router.js +5 -3
  167. package/core/frontend/services/routing/controllers/index.js +1 -1
  168. package/core/frontend/services/routing/controllers/rss.js +26 -31
  169. package/core/frontend/services/routing/controllers/rss.ts +66 -0
  170. package/core/frontend/services/routing/controllers/static.js +8 -7
  171. package/core/frontend/services/routing/parent-router.js +17 -4
  172. package/core/frontend/services/routing/permalink-adapter.js +26 -0
  173. package/core/frontend/services/routing/permalink-adapter.ts +23 -0
  174. package/core/frontend/services/routing/registry.js +59 -33
  175. package/core/frontend/services/routing/router-manager.js +9 -13
  176. package/core/frontend/services/routing/static-routes-router.js +4 -4
  177. package/core/frontend/services/routing/taxonomy-router.js +6 -2
  178. 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
  179. package/core/server/data/migrations/versions/6.56/2026-08-04-14-42-39-restrict-notification-add-permission-to-administrators.js +12 -0
  180. package/core/server/data/schema/fixtures/fixtures.json +2 -2
  181. package/core/server/data/schema/schema.js +1 -0
  182. package/core/server/models/post.js +10 -1
  183. package/core/server/services/automations/automations-repository.ts +3 -1
  184. package/core/server/services/automations/database-automations-repository.js +33 -14
  185. package/core/server/services/automations/database-automations-repository.ts +41 -15
  186. package/core/server/services/automations/get-scheduler-idempotency-key.js +13 -0
  187. package/core/server/services/automations/get-scheduler-idempotency-key.ts +8 -0
  188. package/core/server/services/automations/poll.js +3 -1
  189. package/core/server/services/automations/poll.ts +4 -1
  190. package/core/server/services/automations/service.js +5 -1
  191. package/core/server/services/automations/service.ts +5 -1
  192. package/core/server/services/gift-links/CONTEXT.md +9 -0
  193. package/core/server/services/gifts/CONTEXT.md +55 -0
  194. package/core/server/services/gifts/README.md +28 -0
  195. package/core/server/services/gifts/gift-bookshelf-repository.js +65 -45
  196. package/core/server/services/gifts/gift-bookshelf-repository.ts +203 -74
  197. package/core/server/services/gifts/gift-checkout-adapter.js +59 -0
  198. package/core/server/services/gifts/gift-checkout-offer.js +74 -0
  199. package/core/server/services/gifts/gift-checkout-offer.ts +127 -0
  200. package/core/server/services/gifts/gift-codec.js +63 -0
  201. package/core/server/services/gifts/gift-codec.ts +62 -0
  202. package/core/server/services/gifts/gift-controller.js +6 -31
  203. package/core/server/services/gifts/gift-controller.ts +6 -69
  204. package/core/server/services/gifts/gift-email-service.ts +3 -2
  205. package/core/server/services/gifts/gift-schema.js +34 -0
  206. package/core/server/services/gifts/gift-schema.ts +39 -0
  207. package/core/server/services/gifts/gift-service-wrapper.js +13 -11
  208. package/core/server/services/gifts/gift-service.js +265 -52
  209. package/core/server/services/gifts/gift-service.ts +431 -97
  210. package/core/server/services/gifts/gift.ts +16 -39
  211. package/core/server/services/link-tracking/link-click-repository.js +6 -1
  212. package/core/server/services/link-tracking/link-click-tracking-service.js +12 -3
  213. package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +4 -3
  214. package/core/server/services/member-welcome-emails/service.js +7 -3
  215. package/core/server/services/members/api.js +1 -2
  216. package/core/server/services/members/import-export/import/importer.ts +3 -2
  217. package/core/server/services/members/import-export/index.js +10 -2
  218. package/core/server/services/members/import-export/index.ts +14 -4
  219. package/core/server/services/members/members-api/controllers/router-controller.js +38 -36
  220. package/core/server/services/members/members-api/members-api.js +13 -7
  221. package/core/server/services/members/members-api/repositories/event-repository.js +4 -94
  222. package/core/server/services/members/members-api/services/member-bread-service.js +3 -3
  223. package/core/server/services/members/members-api/services/payments-service.js +6 -68
  224. package/core/server/services/members-custom-fields/schema.js +8 -1
  225. package/core/server/services/members-custom-fields/schema.ts +8 -0
  226. package/core/server/services/members-custom-fields/values-service.js +118 -2
  227. package/core/server/services/members-custom-fields/values-service.ts +134 -4
  228. package/core/server/services/oembed/oembed-service.js +44 -3
  229. package/core/server/services/posts/posts-service.js +11 -0
  230. package/core/server/services/route-settings/dynamic-routing-service.js +1 -3
  231. package/core/server/services/route-settings/route-settings-parser.js +25 -25
  232. package/core/server/services/route-settings/route-settings-parser.ts +27 -27
  233. package/core/server/services/route-settings/validation-errors.js +68 -4
  234. package/core/server/services/route-settings/validation-errors.ts +79 -4
  235. package/core/server/services/stats/posts-stats-service.js +27 -23
  236. package/core/server/services/stripe/services/webhook/charge-refunded-event-service.js +1 -1
  237. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +16 -4
  238. package/core/server/services/stripe/stripe-api.js +10 -0
  239. package/core/server/web/gift-preview/controller.js +10 -25
  240. package/core/shared/labs.js +2 -2
  241. package/package.json +10 -9
  242. package/pnpm-lock.yaml +28 -18
  243. package/pnpm-workspace.yaml +5 -4
  244. package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
  245. package/core/built/admin/assets/activitypub/layout-qx-bUL1k.js +0 -1
  246. package/core/built/admin/assets/chunk.524.cffd399783a519e404a9.js +0 -25
  247. package/core/built/admin/assets/chunk.582.eebf0d0b2d202bb746a5.js +0 -27
  248. package/core/built/admin/assets/comments-DyUSMUdj.js +0 -1
  249. package/core/built/admin/assets/dist-CjY-f5Z1.js +0 -1
  250. package/core/built/admin/assets/domain-C0bV2UV8.js +0 -1
  251. package/core/built/admin/assets/editor-Dp865O-4.js +0 -8
  252. package/core/built/admin/assets/filter-sources-BV2rnpfA.js +0 -1
  253. package/core/built/admin/assets/filters-CUXJ2Nm9.js +0 -1
  254. package/core/built/admin/assets/filters-DfbIWc_l.js +0 -1
  255. package/core/built/admin/assets/index-9Cw8z3rA.css +0 -1
  256. package/core/built/admin/assets/index-BGG7NckO.js +0 -67
  257. package/core/built/admin/assets/input-group-BSgG_lVw.js +0 -1
  258. package/core/built/admin/assets/labels-CDSUt4J_.js +0 -1
  259. package/core/built/admin/assets/layout-yQEi2qDM.js +0 -1
  260. package/core/built/admin/assets/link-helpers-7EYqvvHs.js +0 -1
  261. package/core/built/admin/assets/member-custom-fields-yOMHyAg9.js +0 -1
  262. package/core/built/admin/assets/member-detail-BAq7gDaa.js +0 -1
  263. package/core/built/admin/assets/modals-ReORvWnu.js +0 -31
  264. package/core/built/admin/assets/newsletter-CJQvtHqN.js +0 -1
  265. package/core/built/admin/assets/newsletters-B3QQXfGT.js +0 -1
  266. package/core/built/admin/assets/nql-BsgFMd93.js +0 -1
  267. package/core/built/admin/assets/overview-CBfePxcr.js +0 -1
  268. package/core/built/admin/assets/post-analytics-header-Bmc9su6q.js +0 -1
  269. package/core/built/admin/assets/preferences-DJPfHEGu.js +0 -5
  270. package/core/built/admin/assets/profile-DqBQp-gT.js +0 -1
  271. package/core/built/admin/assets/select-C5_xEt5b.js +0 -1
  272. package/core/built/admin/assets/settings-BwiVIXau.js +0 -4
  273. package/core/built/admin/assets/social-urls-BQPFEk1_.js +0 -8
  274. package/core/built/admin/assets/textarea-Bjccvh_E.js +0 -1
  275. package/core/built/admin/assets/use-filter-params-Gpoq2E1L.js +0 -1
  276. package/core/built/admin/assets/web-jFYWCKDn.js +0 -2
  277. package/core/server/services/gifts/gift-repository.js +0 -2
  278. package/core/server/services/gifts/gift-repository.ts +0 -29
  279. package/core/server/services/route-settings/activation-bridge.js +0 -174
  280. package/core/server/services/route-settings/activation-bridge.ts +0 -250
@@ -0,0 +1,59 @@
1
+ const logging = require('@tryghost/logging');
2
+
3
+ /**
4
+ * Stripe and Bookshelf adapter used only by the gifts module.
5
+ *
6
+ * The module interface deals in stable member/customer IDs. Stripe customer
7
+ * objects and Bookshelf models stay on this side of the seam.
8
+ */
9
+ module.exports = class GiftCheckoutAdapter {
10
+ constructor({StripeCustomerModel, getStripeApi}) {
11
+ this.StripeCustomerModel = StripeCustomerModel;
12
+ this.getStripeApi = getStripeApi;
13
+ }
14
+
15
+ async getCustomerId({memberId, email, name}) {
16
+ if (!memberId) {
17
+ return null;
18
+ }
19
+
20
+ const rows = await this.StripeCustomerModel.where({
21
+ member_id: memberId
22
+ }).query().select('customer_id');
23
+
24
+ for (const row of rows) {
25
+ try {
26
+ const customer = await this.getStripeApi().getCustomer(row.customer_id);
27
+ if (!customer.deleted) {
28
+ return customer.id;
29
+ }
30
+ } catch (err) {
31
+ logging.warn(err);
32
+ }
33
+ }
34
+
35
+ const customer = await this.getStripeApi().createCustomer({
36
+ email,
37
+ name
38
+ });
39
+
40
+ await this.StripeCustomerModel.add({
41
+ member_id: memberId,
42
+ customer_id: customer.id,
43
+ email: customer.email,
44
+ name: customer.name
45
+ });
46
+
47
+ return customer.id;
48
+ }
49
+
50
+ async createSession(data) {
51
+ const {customerId, ...sessionData} = data;
52
+ const session = await this.getStripeApi().createGiftCheckoutSession({
53
+ ...sessionData,
54
+ customer: customerId ? {id: customerId} : null
55
+ });
56
+
57
+ return session.url;
58
+ }
59
+ };
@@ -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>;