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
@@ -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
  }
@@ -7,9 +7,12 @@ exports.GiftService = void 0;
7
7
  const node_crypto_1 = __importDefault(require("node:crypto"));
8
8
  const errors_1 = __importDefault(require("@tryghost/errors"));
9
9
  const logging_1 = __importDefault(require("@tryghost/logging"));
10
+ const zod_1 = require("zod");
10
11
  const gift_1 = require("./gift");
12
+ const gift_schema_1 = require("./gift-schema");
11
13
  const tpl_1 = __importDefault(require("@tryghost/tpl"));
12
14
  const constants_1 = require("./constants");
15
+ const gift_checkout_offer_1 = require("./gift-checkout-offer");
13
16
  const MS_PER_DAY = 24 * 60 * 60 * 1000;
14
17
  const GIFT_REMINDER_LEAD_MS = constants_1.GIFT_REMINDER_LEAD_DAYS * MS_PER_DAY;
15
18
  const GIFT_REMINDER_FLOOR_MS = constants_1.GIFT_REMINDER_FLOOR_DAYS * MS_PER_DAY;
@@ -26,11 +29,119 @@ const errorMessages = {
26
29
  giftMissingConsumesAt: 'This gift is missing a "consumes at" date.',
27
30
  giftMemberAlreadyHasGift: 'Member already has a different active gift attached.'
28
31
  };
32
+ const GiftPurchaseDataSchema = zod_1.z.object({
33
+ token: zod_1.z.string().min(1),
34
+ buyerEmail: zod_1.z.string().min(1),
35
+ stripeCustomerId: zod_1.z.string().min(1).nullable(),
36
+ tierId: zod_1.z.string().min(1),
37
+ cadence: gift_schema_1.GiftCadenceSchema,
38
+ duration: zod_1.z.number().int().positive(),
39
+ currency: zod_1.z.string().min(1),
40
+ amount: zod_1.z.number().int().nonnegative(),
41
+ stripeCheckoutSessionId: zod_1.z.string().min(1),
42
+ stripePaymentIntentId: zod_1.z.string().min(1)
43
+ });
29
44
  class GiftService {
30
45
  deps;
31
46
  constructor(deps) {
32
47
  this.deps = deps;
33
48
  }
49
+ async startCheckout(input) {
50
+ if (input.offerId) {
51
+ throw new errors_1.default.BadRequestError({
52
+ message: 'Bad Request.',
53
+ context: 'Offers cannot be applied to gift subscriptions'
54
+ });
55
+ }
56
+ if (!input.tierId) {
57
+ throw new errors_1.default.BadRequestError({
58
+ message: 'Bad Request.',
59
+ context: 'Expected offerId or tierId, received none'
60
+ });
61
+ }
62
+ let resolvedDuration = null;
63
+ let cadence;
64
+ if (this.deps.labsService.isSet('giftSubCustomization')) {
65
+ resolvedDuration = (0, gift_checkout_offer_1.resolveGiftDuration)(input);
66
+ cadence = resolvedDuration.cadence;
67
+ }
68
+ else {
69
+ if (input.cadence !== 'month' && input.cadence !== 'year') {
70
+ const receivedCadence = input.cadence ? `"${input.cadence}"` : input.cadence;
71
+ throw new errors_1.default.BadRequestError({
72
+ message: 'Bad Request.',
73
+ context: `Expected cadence to be "month" or "year", received ${receivedCadence}`
74
+ });
75
+ }
76
+ cadence = input.cadence;
77
+ }
78
+ let tier;
79
+ try {
80
+ tier = await this.deps.tiersService.api.read(input.tierId);
81
+ }
82
+ catch (err) {
83
+ logging_1.default.error(err);
84
+ tier = null;
85
+ }
86
+ if (!tier) {
87
+ throw new errors_1.default.BadRequestError({
88
+ message: 'This tier does not exist.',
89
+ context: `Tier with id "${input.tierId}" not found`
90
+ });
91
+ }
92
+ if (tier.status === 'archived') {
93
+ throw new errors_1.default.NoPermissionError({
94
+ message: 'This tier is archived.'
95
+ });
96
+ }
97
+ let duration = 1;
98
+ let totalMonths;
99
+ let amount = tier.getPrice(cadence);
100
+ if (resolvedDuration) {
101
+ const plan = (0, gift_checkout_offer_1.validateGiftCheckoutOffer)({
102
+ tier,
103
+ portalPlans: this.deps.settingsCache.get('portal_plans'),
104
+ offer: resolvedDuration
105
+ });
106
+ cadence = plan.cadence;
107
+ duration = plan.duration;
108
+ totalMonths = plan.totalMonths;
109
+ amount = plan.amount;
110
+ }
111
+ const tierId = typeof tier.id === 'string' ? tier.id : tier.id.toHexString();
112
+ const token = this.generateToken();
113
+ const successUrl = new URL(input.successUrl);
114
+ successUrl.searchParams.set('stripe', 'gift-purchase-success');
115
+ successUrl.searchParams.set('gift_token', token);
116
+ successUrl.searchParams.set('gift_tier', tierId);
117
+ successUrl.searchParams.set('gift_cadence', cadence);
118
+ if (totalMonths !== undefined) {
119
+ successUrl.searchParams.set('gift_duration', String(totalMonths));
120
+ }
121
+ const customerId = input.buyer.isAuthenticated
122
+ ? await this.deps.checkoutAdapter.getCustomerId(input.buyer)
123
+ : null;
124
+ const url = await this.deps.checkoutAdapter.createSession({
125
+ amount,
126
+ currency: tier.currency.toLowerCase(),
127
+ tierName: tier.name,
128
+ cadence,
129
+ duration,
130
+ metadata: {
131
+ ...input.metadata,
132
+ ghost_gift: 'true',
133
+ gift_token: token,
134
+ tier_id: tierId,
135
+ cadence,
136
+ duration: String(duration)
137
+ },
138
+ successUrl: successUrl.toString(),
139
+ cancelUrl: input.cancelUrl,
140
+ customerId,
141
+ customerEmail: customerId ? null : input.buyer.email
142
+ });
143
+ return { url };
144
+ }
34
145
  generateToken() {
35
146
  /**
36
147
  * Combinations: 62^12 ≈ 3.23 × 10^21 (~3.23 sextillion)
@@ -43,11 +154,17 @@ class GiftService {
43
154
  }
44
155
  return token;
45
156
  }
46
- async recordPurchase(data) {
47
- const duration = Number.parseInt(data.duration);
48
- if (Number.isNaN(duration)) {
49
- throw new errors_1.default.ValidationError({ message: `Invalid gift duration: ${data.duration}` });
157
+ async completePurchase(input) {
158
+ const parsed = GiftPurchaseDataSchema.safeParse(input);
159
+ if (!parsed.success) {
160
+ const issue = parsed.error.issues[0];
161
+ throw new errors_1.default.ValidationError({
162
+ message: 'Invalid gift purchase data.',
163
+ property: issue.path.join('.'),
164
+ context: issue.message
165
+ });
50
166
  }
167
+ const data = parsed.data;
51
168
  if (await this.deps.giftRepository.existsByCheckoutSessionId(data.stripeCheckoutSessionId)) {
52
169
  return false;
53
170
  }
@@ -60,7 +177,7 @@ class GiftService {
60
177
  buyerMemberId: member?.id ?? null,
61
178
  tierId: data.tierId,
62
179
  cadence: data.cadence,
63
- duration,
180
+ duration: data.duration,
64
181
  currency: data.currency,
65
182
  amount: data.amount,
66
183
  stripeCheckoutSessionId: data.stripeCheckoutSessionId,
@@ -80,7 +197,7 @@ class GiftService {
80
197
  currency: data.currency,
81
198
  tierName: tier.name,
82
199
  cadence: data.cadence,
83
- duration
200
+ duration: data.duration
84
201
  });
85
202
  }
86
203
  catch (err) {
@@ -92,7 +209,7 @@ class GiftService {
92
209
  token: data.token,
93
210
  tierName: tier.name,
94
211
  cadence: data.cadence,
95
- duration,
212
+ duration: data.duration,
96
213
  expiresAt: gift.expiresAt
97
214
  });
98
215
  }
@@ -101,10 +218,7 @@ class GiftService {
101
218
  }
102
219
  return true;
103
220
  }
104
- async getByToken(token) {
105
- return this.deps.giftRepository.getByToken(token);
106
- }
107
- async assertRedeemable(gift, memberStatus) {
221
+ assertRedeemable(gift, memberStatus) {
108
222
  const redeemableCheck = gift.checkRedeemable(memberStatus);
109
223
  if (!redeemableCheck.redeemable) {
110
224
  switch (redeemableCheck.reason) {
@@ -143,45 +257,25 @@ class GiftService {
143
257
  }
144
258
  return gift;
145
259
  }
146
- async getRedeemable(token, memberStatus) {
147
- const gift = await this.deps.giftRepository.getByToken(token);
260
+ async getRedeemable(input) {
261
+ const gift = await this.deps.giftRepository.getByToken(input.token);
148
262
  if (!gift) {
149
263
  throw new errors_1.default.NotFoundError({ message: (0, tpl_1.default)(errorMessages.giftNotFound) });
150
264
  }
151
- await this.assertRedeemable(gift, memberStatus);
152
- return gift;
265
+ this.assertRedeemable(gift, input.memberStatus);
266
+ return this.serializeRedemption(gift);
153
267
  }
154
- async redeem(token, memberId, options = {}) {
268
+ async redeem(input) {
155
269
  const run = async (transacting) => {
156
- const member = await this.deps.memberRepository.get({ id: memberId }, { transacting, forUpdate: true });
157
- if (!member) {
158
- throw new errors_1.default.NotFoundError({ message: `Member not found: ${memberId}` });
159
- }
160
- const gift = await this.deps.giftRepository.getByToken(token, { transacting, forUpdate: true });
161
- if (!gift) {
162
- throw new errors_1.default.NotFoundError({ message: (0, tpl_1.default)(errorMessages.giftNotFound) });
163
- }
164
- if (options.newMember) {
165
- await this.assertRedeemable(gift, null);
166
- }
167
- else {
168
- await this.assertRedeemable(gift, member.get('status'));
169
- }
170
- const redeemed = gift.redeem({ memberId });
171
- await this.deps.memberRepository.update({
172
- products: [{
173
- id: redeemed.tierId,
174
- expiry_at: redeemed.consumesAt
175
- }],
176
- status: 'gift'
177
- }, { id: memberId, transacting });
178
- await this.deps.giftRepository.update(redeemed, { transacting });
179
- // Gift members receive the paid welcome email, as they receive access to paid content
180
- await this.deps.memberRepository.triggerMemberSignupAutomation(memberId, member.get('email'), 'paid', { transacting });
181
- return { redeemed, member };
270
+ const { redeemed, member } = await this.redeemGift(input.token, input.memberId, {
271
+ transacting,
272
+ newMember: input.newMember
273
+ });
274
+ const redemption = await this.serializeRedemption(redeemed);
275
+ return { redeemed, member, redemption };
182
276
  };
183
- const { redeemed, member } = options.transacting
184
- ? await run(options.transacting)
277
+ const { redeemed, member, redemption } = input.transacting
278
+ ? await run(input.transacting)
185
279
  : await this.deps.giftRepository.transaction(run);
186
280
  const notify = async () => {
187
281
  try {
@@ -204,14 +298,43 @@ class GiftService {
204
298
  }
205
299
  await this.deps.giftReminderScheduler.scheduleFor(redeemed);
206
300
  };
207
- if (options.transacting) {
301
+ if (input.transacting) {
208
302
  // Only notify once the transaction has finished
209
- options.transacting.executionPromise.then(notify, () => { });
303
+ input.transacting.executionPromise.then(notify, () => { });
210
304
  }
211
305
  else {
212
306
  await notify();
213
307
  }
214
- return redeemed;
308
+ return redemption;
309
+ }
310
+ async redeemGift(token, memberId, options) {
311
+ const { transacting } = options;
312
+ const member = await this.deps.memberRepository.get({ id: memberId }, { transacting, forUpdate: true });
313
+ if (!member) {
314
+ throw new errors_1.default.NotFoundError({ message: `Member not found: ${memberId}` });
315
+ }
316
+ const gift = await this.deps.giftRepository.getByToken(token, { transacting, forUpdate: true });
317
+ if (!gift) {
318
+ throw new errors_1.default.NotFoundError({ message: (0, tpl_1.default)(errorMessages.giftNotFound) });
319
+ }
320
+ if (options.newMember) {
321
+ this.assertRedeemable(gift, null);
322
+ }
323
+ else {
324
+ this.assertRedeemable(gift, member.get('status'));
325
+ }
326
+ const redeemed = gift.redeem({ memberId });
327
+ await this.deps.memberRepository.update({
328
+ products: [{
329
+ id: redeemed.tierId,
330
+ expiry_at: redeemed.consumesAt
331
+ }],
332
+ status: 'gift'
333
+ }, { id: memberId, transacting });
334
+ await this.deps.giftRepository.update(redeemed, { transacting });
335
+ // Gift members receive the paid welcome email, as they receive access to paid content
336
+ await this.deps.memberRepository.triggerMemberSignupAutomation(memberId, member.get('email'), 'paid', { transacting });
337
+ return { redeemed, member };
215
338
  }
216
339
  async getActiveByMember(memberId, options = {}) {
217
340
  if (!memberId) {
@@ -232,7 +355,66 @@ class GiftService {
232
355
  const diffDays = Math.ceil((gift.consumesAt.getTime() - now.getTime()) / MS_PER_DAY);
233
356
  return Math.max(0, diffDays);
234
357
  }
235
- async reassignRedeemer(giftId, memberId, options = {}) {
358
+ async preparePaidContinuation({ memberId, memberStatus }) {
359
+ if (memberStatus !== 'gift') {
360
+ throw new errors_1.default.BadRequestError({
361
+ message: 'Bad Request.',
362
+ context: 'Member does not have an active gift subscription'
363
+ });
364
+ }
365
+ const gift = await this.getActiveByMember(memberId);
366
+ if (!gift) {
367
+ throw new errors_1.default.BadRequestError({
368
+ message: 'Bad Request.',
369
+ context: 'No active gift subscription found for member'
370
+ });
371
+ }
372
+ const remainingDays = this.getRemainingActiveDays(gift);
373
+ return {
374
+ tierId: gift.tierId,
375
+ cadence: gift.cadence,
376
+ trialDays: remainingDays > 0 ? Math.min(remainingDays, 730) : null
377
+ };
378
+ }
379
+ async getMemberPresentations(memberIds) {
380
+ const gifts = await this.getActiveByMembers(memberIds);
381
+ const presentations = new Map();
382
+ for (const [memberId, gift] of gifts) {
383
+ presentations.set(memberId, {
384
+ cadence: gift.cadence,
385
+ currency: gift.currency,
386
+ amount: gift.amount
387
+ });
388
+ }
389
+ return presentations;
390
+ }
391
+ async getPreview(token) {
392
+ const gift = await this.deps.giftRepository.getByToken(token);
393
+ if (!gift) {
394
+ return null;
395
+ }
396
+ const tier = await this.deps.tiersService.api.read(gift.tierId);
397
+ if (!tier) {
398
+ throw new errors_1.default.NotFoundError({ message: `Tier not found for gift: ${gift.token}` });
399
+ }
400
+ const tierJSON = tier.toJSON();
401
+ return {
402
+ cadence: gift.cadence,
403
+ duration: gift.duration,
404
+ tier: {
405
+ id: tierJSON.id,
406
+ name: tierJSON.name
407
+ }
408
+ };
409
+ }
410
+ browsePurchaseEvents(options, filter) {
411
+ return this.deps.giftRepository.browsePurchaseEvents(options, filter);
412
+ }
413
+ browseRedemptionEvents(options, filter) {
414
+ return this.deps.giftRepository.browseRedemptionEvents(options, filter);
415
+ }
416
+ async reassignRedeemer(input) {
417
+ const { giftId, memberId } = input;
236
418
  const run = async (transacting) => {
237
419
  const gift = await this.deps.giftRepository.getById(giftId, { transacting, forUpdate: true });
238
420
  if (!gift) {
@@ -284,11 +466,11 @@ class GiftService {
284
466
  await this.deps.giftRepository.update(reassignedGift, { transacting });
285
467
  return reassignedGift;
286
468
  };
287
- return options.transacting
288
- ? run(options.transacting)
289
- : this.deps.giftRepository.transaction(run);
469
+ await (input.transacting
470
+ ? run(input.transacting)
471
+ : this.deps.giftRepository.transaction(run));
290
472
  }
291
- async refund(paymentIntentId) {
473
+ async handlePaymentRefund({ paymentIntentId }) {
292
474
  const gift = await this.deps.giftRepository.getByPaymentIntentId(paymentIntentId);
293
475
  if (!gift) {
294
476
  return false;
@@ -311,6 +493,13 @@ class GiftService {
311
493
  });
312
494
  return true;
313
495
  }
496
+ async handlePaidSubscriptionActivation(memberId) {
497
+ const gift = await this.getActiveByMember(memberId);
498
+ if (!gift) {
499
+ return false;
500
+ }
501
+ return Boolean(await this.consume(gift.token));
502
+ }
314
503
  async consume(token, options = {}) {
315
504
  const run = async (transacting) => {
316
505
  // Fetch with a row lock to prevent race conditions under concurrency
@@ -465,5 +654,29 @@ class GiftService {
465
654
  });
466
655
  return true;
467
656
  }
657
+ async serializeRedemption(gift) {
658
+ const tier = await this.deps.tiersService.api.read(gift.tierId);
659
+ if (!tier) {
660
+ throw new errors_1.default.InternalServerError({
661
+ message: `Tier ${gift.tierId} not found for gift: ${gift.token}`
662
+ });
663
+ }
664
+ const tierJSON = tier.toJSON();
665
+ return {
666
+ token: gift.token,
667
+ cadence: gift.cadence,
668
+ duration: gift.duration,
669
+ currency: gift.currency,
670
+ amount: gift.amount,
671
+ expires_at: gift.expiresAt,
672
+ consumes_at: gift.consumesAt,
673
+ tier: {
674
+ id: tierJSON.id,
675
+ name: tierJSON.name,
676
+ description: tierJSON.description,
677
+ benefits: tierJSON.benefits
678
+ }
679
+ };
680
+ }
468
681
  }
469
682
  exports.GiftService = GiftService;