ghost 6.55.0 → 6.57.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 (483) hide show
  1. package/bin/validate-adapters.js +74 -0
  2. package/bin/validate-adapters.ts +87 -0
  3. package/bin/warm-compile-cache.sh +60 -0
  4. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  5. package/components/tryghost-kg-default-cards-10.3.3.tgz +0 -0
  6. package/components/tryghost-kg-default-nodes-2.1.4.tgz +0 -0
  7. package/components/tryghost-kg-default-transforms-1.3.3.tgz +0 -0
  8. package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
  9. package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
  10. package/components/tryghost-nql-string-0.0.0.tgz +0 -0
  11. package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
  12. package/core/app.js +1 -1
  13. package/core/boot.js +21 -25
  14. package/core/bridge.js +10 -12
  15. package/core/built/admin/assets/Combination-BDvCdg1t.js +41 -0
  16. package/core/built/admin/assets/{PolarAngleAxis-BGjf7sBu.js → PolarAngleAxis-C4qtHy1B.js} +1 -1
  17. package/core/built/admin/assets/{RadialBarChart-zeFf9mBz.js → RadialBarChart-Bh09Stsj.js} +1 -1
  18. package/core/built/admin/assets/about-C7O-AgRZ.js +1 -0
  19. package/core/built/admin/assets/{account-migration-sjHLx5dy.js → account-migration-Cv_ol1ko.js} +1 -1
  20. package/core/built/admin/assets/action-list-lGzZLHc8.js +1 -0
  21. package/core/built/admin/assets/activitypub/{account-migration-DoYXtnZ8.js → account-migration-DHFbePl6.js} +7 -7
  22. package/core/built/admin/assets/activitypub/activitypub.js +6 -6
  23. package/core/built/admin/assets/activitypub/{avatar-B3DEDB1l.js → avatar-CT4tS2_S.js} +2 -2
  24. package/core/built/admin/assets/activitypub/{avatar-flipboard-Cqrg0XMx.js → avatar-flipboard-ByYWKipo.js} +2 -2
  25. package/core/built/admin/assets/activitypub/{bluesky-sharing-Bxeh6MKW.js → bluesky-sharing-srVnSHDz.js} +16 -16
  26. package/core/built/admin/assets/activitypub/{content-formatters-BpQARh28.js → content-formatters-C2sos5Ht.js} +3 -3
  27. package/core/built/admin/assets/activitypub/{copy-D9UWIAan.js → copy-B1UBTOoZ.js} +2 -2
  28. package/core/built/admin/assets/activitypub/{dist-D8sFfdpC.js → dist-MPsb2WZ1.js} +12 -12
  29. package/core/built/admin/assets/activitypub/{domain-CWueFW6q.js → domain-DzcM9Dzo.js} +9 -9
  30. package/core/built/admin/assets/activitypub/{edit-profile-BGxPU7N5.js → edit-profile-CsQ_4xAk.js} +945 -945
  31. package/core/built/admin/assets/activitypub/{explore-DBK-u0Ch.js → explore-CJe0DZBK.js} +8 -8
  32. package/core/built/admin/assets/activitypub/{feed-Bcir5RQZ.js → feed-CKV3qWON.js} +33 -33
  33. package/core/built/admin/assets/activitypub/{field-BV-CIT3d.js → field-DckHfDJf.js} +3 -3
  34. package/core/built/admin/assets/activitypub/{hash-BC9Q0N4c.js → hash-Dlp8RM55.js} +2 -2
  35. package/core/built/admin/assets/activitypub/{inbox-DQ4ZnC1m.js → inbox-CsPy4R0M.js} +2 -2
  36. package/core/built/admin/assets/activitypub/{inbox-yC0nfvzl.js → inbox-DAsjzUUe.js} +85 -85
  37. package/core/built/admin/assets/activitypub/{label-DTRS9TDk.js → label-DWyo9E5T.js} +3 -3
  38. package/core/built/admin/assets/activitypub/layout-DM_3pqsU.js +1 -0
  39. package/core/built/admin/assets/activitypub/{moderation-Dwl9t0pb.js → moderation-B0Inc03M.js} +11 -11
  40. package/core/built/admin/assets/activitypub/{note-B-zKsZ7r.js → note-CSE54lER.js} +7 -7
  41. package/core/built/admin/assets/activitypub/{notifications-DjJZD54t.js → notifications-RSwbKqdh.js} +9 -9
  42. package/core/built/admin/assets/activitypub/{preferences-CoU8vs6d.js → preferences-CE231n2_.js} +20 -20
  43. package/core/built/admin/assets/activitypub/{profile-BAsaJekx.js → profile-aaQ1j1Eo.js} +237 -237
  44. package/core/built/admin/assets/activitypub/{reply-CpdMeAO5.js → reply-Cjb0Vs3m.js} +2 -2
  45. package/core/built/admin/assets/activitypub/{routes-CLH3OVTP.js → routes-Djb0mc9k.js} +1139 -1150
  46. package/core/built/admin/assets/activitypub/{separator-BI1FLsYM.js → separator-D8pROI5n.js} +3 -3
  47. package/core/built/admin/assets/activitypub/{settings-DA6pNJ-Q.js → settings-DMc32IQl.js} +13 -13
  48. package/core/built/admin/assets/activitypub/{site-CQD42Pw5.js → site-DVIcsohX.js} +81 -81
  49. package/core/built/admin/assets/activitypub/{step-1-DIDg4aHb.js → step-1-9Yey9usF.js} +5 -5
  50. package/core/built/admin/assets/activitypub/{step-2-BfGe8BYt.js → step-2-DdYmKRXV.js} +6 -6
  51. package/core/built/admin/assets/activitypub/{step-3-mE9spAqw.js → step-3-hdSkcjZU.js} +10 -10
  52. package/core/built/admin/assets/activitypub/{tabs-Cg0saow1.js → tabs-CUqseYzQ.js} +317 -315
  53. package/core/built/admin/assets/activitypub/{topic-filter-DLHBZT93.js → topic-filter-Coni6OXM.js} +3 -3
  54. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-VzbN99u_.js → use-activity-pub-queries-MeHTPMQv.js} +2 -2
  55. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-CAwXTmqH.js → use-navigate-with-base-path-B046AM38.js} +115 -96
  56. package/core/built/admin/assets/activitypub/{use-reply-chain-data-Br4aOWOh.js → use-reply-chain-data-1AO-Xob6.js} +5 -5
  57. package/core/built/admin/assets/activitypub/{x-C8tAnXly.js → x-BRfRYd16.js} +2 -2
  58. package/core/built/admin/assets/add-integration-modal-m9Dv6f77.js +1 -0
  59. package/core/built/admin/assets/add-newsletter-modal-CPSVZL2H.js +1 -0
  60. package/core/built/admin/assets/add-recommendation-modal-Ci9Z5yat.js +1 -0
  61. package/core/built/admin/assets/analytics-DqSCVV8U.js +1 -0
  62. package/core/built/admin/assets/announcement-bar-modal-DU3avV7d.js +1 -0
  63. package/core/built/admin/assets/api-keys-BK0LylyK.js +1 -0
  64. package/core/built/admin/assets/api-keys-_iCT4dxr.js +1 -0
  65. package/core/built/admin/assets/{audience-DjEWNhp3.js → audience-DTJtXXWY.js} +1 -1
  66. package/core/built/admin/assets/automated-emails-PayPCT8L.js +1 -0
  67. package/core/built/admin/assets/{automations-DhKeS_U5.js → automations-162q5HjL.js} +1 -1
  68. package/core/built/admin/assets/{automations-Bx3VcFIL.js → automations-dtQ_MRZ7.js} +1 -1
  69. package/core/built/admin/assets/{bluesky-sharing-ClMd0-Ft.js → bluesky-sharing-B8Es3AuV.js} +1 -1
  70. package/core/built/admin/assets/{box-BWbhXh21.js → box-By_EgGtm.js} +1 -1
  71. package/core/built/admin/assets/brand-icon-09YqFZjV.js +1 -0
  72. package/core/built/admin/assets/{breadcrumb-BD0U2ygg.js → breadcrumb-tg5zL--z.js} +1 -1
  73. package/core/built/admin/assets/build-CsiH26uS.js +1 -0
  74. package/core/built/admin/assets/{card-DGoDJzrn.js → card-aUHX0hyR.js} +1 -1
  75. package/core/built/admin/assets/{chart-BuRT2Y6p.js → chart-BnXyfH11.js} +1 -1
  76. package/core/built/admin/assets/{chart-helpers-fjQKRVyY.js → chart-helpers-CZElTGYL.js} +1 -1
  77. package/core/built/admin/assets/checkbox-Cyo-x7mC.js +1 -0
  78. package/core/built/admin/assets/chunk.524.cd5498dd4e195f679266.js +25 -0
  79. package/core/built/admin/assets/chunk.582.6f92ec1f03734bf2ea3f.js +27 -0
  80. package/core/built/admin/assets/{chunk.481.6c7395a49484bb4583aa.js → chunk.873.b8fc05a8ab877fb819ed.js} +62 -60
  81. package/core/built/admin/assets/code-editor-view-Dq1rQ4tS.js +1 -0
  82. package/core/built/admin/assets/color-picker-field-BonBgxhU.js +1 -0
  83. package/core/built/admin/assets/color-utils-BsrwXuDz.js +1 -0
  84. package/core/built/admin/assets/combobox-uX5r_paI.js +1 -0
  85. package/core/built/admin/assets/command-DxwBO2pF.js +1 -0
  86. package/core/built/admin/assets/comments-Dn-RO-nU.js +1 -0
  87. package/core/built/admin/assets/components-CwL6a6As-B0E0-NBJ.js +1 -0
  88. package/core/built/admin/assets/confirmation-modal-B6D6mk6R.js +1 -0
  89. package/core/built/admin/assets/{container-BYz2Ed39.js → container-LiZf3XdY.js} +1 -1
  90. package/core/built/admin/assets/content-api-modal-BxQ-7T6u.js +1 -0
  91. package/core/built/admin/assets/copy-field-BAxL4Zqn.js +1 -0
  92. package/core/built/admin/assets/currency-Dy4Tc2R2.js +1 -0
  93. package/core/built/admin/assets/custom-integration-modal---d_l7lc.js +1 -0
  94. package/core/built/admin/assets/{data-list-16rkJYYi.js → data-list-CupF7bjM.js} +1 -1
  95. package/core/built/admin/assets/{date-range-select-BFp58EMk.js → date-range-select-PB2n1HZf.js} +1 -1
  96. package/core/built/admin/assets/debounce-Dg2fW5Rg.js +3 -0
  97. package/core/built/admin/assets/design-and-theme-modal-DYRT207a.js +23 -0
  98. package/core/built/admin/assets/dirty-confirm-dialog-BnDJ39ao.js +1 -0
  99. package/core/built/admin/assets/disabled-sources-indicator-iMHrkyV3.js +1 -0
  100. package/core/built/admin/assets/dist-B2PvRwLn.js +1 -0
  101. package/core/built/admin/assets/{dist-DGzPJ_bO.js → dist-Cb5RSHSg.js} +1 -1
  102. package/core/built/admin/assets/{dist-P_ohUlTi.js → dist-CqO2lWS02.js} +1 -1
  103. package/core/built/admin/assets/{dist-pHiOxyxa.js → dist-D5vhMdXf.js} +1 -1
  104. package/core/built/admin/assets/dist-DlCyLrOC.js +1 -0
  105. package/core/built/admin/assets/dist-WeOIvSYM.js +23 -0
  106. package/core/built/admin/assets/{dist-DyViJwIy.js → dist-cFzKLG7y.js} +3 -3
  107. package/core/built/admin/assets/dist-j1xxxnY8.js +1 -0
  108. package/core/built/admin/assets/dist-myQaY0ka.js +1 -0
  109. package/core/built/admin/assets/domain-BR8FIdjD.js +1 -0
  110. package/core/built/admin/assets/{dropzone-DlK-K2DQ.js → dropzone-hNxmUvwD.js} +1 -1
  111. package/core/built/admin/assets/{edit-profile-BKT-REJw.js → edit-profile-CENrkY0l.js} +1 -1
  112. package/core/built/admin/assets/editor-B18D0lm8.js +8 -0
  113. package/core/built/admin/assets/embed-signup-form-modal-BsCYUMvQ.js +9 -0
  114. package/core/built/admin/assets/{empty-indicator-oX1u1Pkn.js → empty-indicator-WB1WgT9h.js} +1 -1
  115. package/core/built/admin/assets/{explore-ClUpJZOp.js → explore-BmrnljWw.js} +1 -1
  116. package/core/built/admin/assets/{feed-DF92dUMP.js → feed-Dy4li6i8.js} +1 -1
  117. package/core/built/admin/assets/{field-Do7lieBA.js → field-Dh_lwk13.js} +1 -1
  118. package/core/built/admin/assets/filter-sources-D2zDuJH5.js +1 -0
  119. package/core/built/admin/assets/filters-3JBQcNtJ.js +1 -0
  120. package/core/built/admin/assets/filters-CFt_BDe4.js +1 -0
  121. package/core/built/admin/assets/first-promoter-modal-D6Rjxugw.js +1 -0
  122. package/core/built/admin/assets/format-url-C-t8G6Qd.js +1 -0
  123. package/core/built/admin/assets/{ghost-a9c788f6f0b1ea6cf0f14ca4b13f4496.css → ghost-d38c30b866e59f2e06a357b5eb844fe5.css} +1 -1
  124. package/core/built/admin/assets/{ghost-c4057474943effc568c5659ccc4909d7.js → ghost-d683fbeb945b13155ea669caa5d8db41.js} +420 -398
  125. package/core/built/admin/assets/{ghost-dark-db36d795d21c61426837df40af270966.css → ghost-dark-1bffd5099ba7a749303133efacf8480b.css} +1 -1
  126. package/core/built/admin/assets/{gift-link-modal-f9VIMNDv.js → gift-link-modal-zMtlJhLQ.js} +1 -1
  127. package/core/built/admin/assets/{gift-links-CsUhefWG.js → gift-links-CVuqLhsZ.js} +1 -1
  128. package/core/built/admin/assets/global-data-provider-B949LDXJ.js +1 -0
  129. package/core/built/admin/assets/grid-CUciBfjf.js +1 -0
  130. package/core/built/admin/assets/{growth-CVw1My4S.js → growth-C38Xtp8D.js} +2 -2
  131. package/core/built/admin/assets/{growth-B3I6gNLm.js → growth-CFws67_i.js} +1 -1
  132. package/core/built/admin/assets/history-modal-ILAOkbxH.js +1 -0
  133. package/core/built/admin/assets/{hooks-CixO01lL.js → hooks-D6EE7BIt.js} +2 -2
  134. package/core/built/admin/assets/html-field-9a9Id_2q.js +1 -0
  135. package/core/built/admin/assets/iframe-buffering-e8vH4-Qn.js +1 -0
  136. package/core/built/admin/assets/images-CrQKVc9Q.js +1 -0
  137. package/core/built/admin/assets/{inbox-CFBSu4Ed.js → inbox-BrSRzyvz.js} +6 -6
  138. package/core/built/admin/assets/index-BHy2DZX-.js +67 -0
  139. package/core/built/admin/assets/index-Ds2iUoF6.css +1 -0
  140. package/core/built/admin/assets/index.es-B9tvf-t4.js +1 -0
  141. package/core/built/admin/assets/{inline-EMAm3okk.js → inline-Cw-gigDW.js} +1 -1
  142. package/core/built/admin/assets/input-group-6vEPvX9S.js +1 -0
  143. package/core/built/admin/assets/{input-surface-BpAjZXY5.js → input-surface-BdOe4KzO.js} +1 -1
  144. package/core/built/admin/assets/integration-header-B4DyXV06.js +1 -0
  145. package/core/built/admin/assets/integrations-BT6_iJvu.js +1 -0
  146. package/core/built/admin/assets/invite-user-modal-5xDCEw90.js +1 -0
  147. package/core/built/admin/assets/isEmail-BOfC647y.js +1 -0
  148. package/core/built/admin/assets/koenig-editor-base-BWLEB9Vf.js +1 -0
  149. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +9535 -9527
  150. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +124 -124
  151. package/core/built/admin/assets/{koenig-lexical-f8vHRYvz.js → koenig-lexical-Cj62r_y9.js} +86 -86
  152. package/core/built/admin/assets/{kpi-card-DSap9rAy.js → kpi-card-DOUnkZcN.js} +1 -1
  153. package/core/built/admin/assets/{kpi-card-DU0rn9Cj.js → kpi-card-IHDo8Q56.js} +1 -1
  154. package/core/built/admin/assets/{kpi-helpers-Dz33AV6h.js → kpi-helpers-CjUdECAK.js} +1 -1
  155. package/core/built/admin/assets/{kpi-tabs-BtTTrKvJ.js → kpi-tabs-X3DkOYbI.js} +1 -1
  156. package/core/built/admin/assets/{label-DS8Mi6G5.js → label-Dq6QaOuS.js} +1 -1
  157. package/core/built/admin/assets/label-picker-8YlhS2U3.js +1 -0
  158. package/core/built/admin/assets/labels-DAPjKPy2.js +1 -0
  159. package/core/built/admin/assets/layout-TMj4P-vv.js +1 -0
  160. package/core/built/admin/assets/limit-modal-BRPsit_B.js +1 -0
  161. package/core/built/admin/assets/link-helpers-Bzkk68GD.js +1 -0
  162. package/core/built/admin/assets/{list-page-B6ANtzkd.js → list-page-CdsnQR9N.js} +1 -1
  163. package/core/built/admin/assets/member-custom-fields-CmvsuuV_.js +1 -0
  164. package/core/built/admin/assets/member-detail-DCvRrkmJ.js +1 -0
  165. package/core/built/admin/assets/members-DxbSKngD.js +11 -0
  166. package/core/built/admin/assets/modal-page-BMTzFHVO.js +1 -0
  167. package/core/built/admin/assets/{moderation-DySbgyUS.js → moderation-Da5lhhWh.js} +1 -1
  168. package/core/built/admin/assets/multi-select-combobox-CYdkH7KQ.js +1 -0
  169. package/core/built/admin/assets/navigation-modal-BIoTwtMy.js +1 -0
  170. package/core/built/admin/assets/newsletter-BptlVoJm.js +1 -0
  171. package/core/built/admin/assets/newsletter-detail-modal-BIvxqE0N.js +1 -0
  172. package/core/built/admin/assets/newsletter-emails-Bf8IZLsP.js +1 -0
  173. package/core/built/admin/assets/newsletters-BgrIZk0V.js +1 -0
  174. package/core/built/admin/assets/{newsletters-BXJbJgA-.js → newsletters-Du0lzskS.js} +1 -1
  175. package/core/built/admin/assets/{note-C8kxrz6w.js → note-DYcLOmDb.js} +1 -1
  176. package/core/built/admin/assets/{notifications-CgtSfX7t.js → notifications-B2SPFIV9.js} +1 -1
  177. package/core/built/admin/assets/{offers-DEniVRD5.js → offers-Um6ab71O.js} +1 -1
  178. package/core/built/admin/assets/offers-container-modal-BZnZITMw.js +1 -0
  179. package/core/built/admin/assets/{onboarding-route-4qtziA9d.js → onboarding-route-DgdfxkQh.js} +1 -1
  180. package/core/built/admin/assets/overview-BYQJXSog.js +1 -0
  181. package/core/built/admin/assets/overview-Dt2_mR6U.js +1 -0
  182. package/core/built/admin/assets/{page-header-B3Wb9Jqv.js → page-header-CpmxJj5B.js} +1 -1
  183. package/core/built/admin/assets/{pages-B4goRPUn.js → pages-BVMnpI2O.js} +1 -1
  184. package/core/built/admin/assets/pintura-modal-B4MuBuuO.js +1 -0
  185. package/core/built/admin/assets/portal-frame-BF0NBIji.js +1 -0
  186. package/core/built/admin/assets/portal-modal-DMVbm0oy.js +1 -0
  187. package/core/built/admin/assets/{post-analytics-We-ko5Cb.js → post-analytics-CXWBOGyP.js} +1 -1
  188. package/core/built/admin/assets/post-analytics-header-ChzjXVPt.js +1 -0
  189. package/core/built/admin/assets/{post-analytics-provider-B6UynTGb.js → post-analytics-provider-Cgw4U_fw.js} +1 -1
  190. package/core/built/admin/assets/{post-share-modal-DFfO93_0.js → post-share-modal-B9Vmk_XM.js} +1 -1
  191. package/core/built/admin/assets/{posts-DhXE7b6r.js → posts-CxG7v0-M.js} +1 -1
  192. package/core/built/admin/assets/preferences-BfzJmnsd.js +5 -0
  193. package/core/built/admin/assets/preview-chrome-BOVO6_PE.js +1 -0
  194. package/core/built/admin/assets/preview-modal-Dw_HtD6S.js +1 -0
  195. package/core/built/admin/assets/profile-BT5vtv9b.js +1 -0
  196. package/core/built/admin/assets/radio-group-I7ou7PoJ.js +1 -0
  197. package/core/built/admin/assets/{referrers-pmJT4k3-.js → referrers-B9QDAfYm.js} +1 -1
  198. package/core/built/admin/assets/{router-provider-C1R4AZ2o.js → router-provider-DSQfprxH.js} +1 -1
  199. package/core/built/admin/assets/select-D9oU1IoM.js +1 -0
  200. package/core/built/admin/assets/settings-CfZFbyu_.js +4 -0
  201. package/core/built/admin/assets/{settings-WXNeWyU1.js → settings-CpvHWix7.js} +1 -1
  202. package/core/built/admin/assets/settings-breadcrumbs-K0USP2nw.js +1 -0
  203. package/core/built/admin/assets/{shade-app-B3heOAfz-CGXQwWeO.js → shade-app-B3heOAfz-BhQXxNkT.js} +1 -1
  204. package/core/built/admin/assets/{share-modal-DsgW3wbg.js → share-modal-ClIyPzc5.js} +1 -1
  205. package/core/built/admin/assets/slack-modal-1XXqOfiw.js +1 -0
  206. package/core/built/admin/assets/social-urls-CHQ9cTAK.js +1 -0
  207. package/core/built/admin/assets/{sort-button-Bb_FCl7e.js → sort-button-CdBsm40v.js} +1 -1
  208. package/core/built/admin/assets/{source-icon-BeaRxUn9.js → source-icon-CEYmVLXR.js} +1 -1
  209. package/core/built/admin/assets/{stack-xiN0eQ6Z.js → stack-CIfOAAVf.js} +1 -1
  210. package/core/built/admin/assets/{stats-Bjj_kmJ1.js → stats-D8nZCTy5.js} +1 -1
  211. package/core/built/admin/assets/{stats-view-BxPbW6K1.js → stats-view-CugFVf0g.js} +1 -1
  212. package/core/built/admin/assets/{step-1-B1e3-1F8.js → step-1-DOYjrORn.js} +1 -1
  213. package/core/built/admin/assets/{step-2-C5WaeBJo.js → step-2-vA6RsarH.js} +1 -1
  214. package/core/built/admin/assets/{step-3-B46iVPwu.js → step-3-BtIBxMId.js} +2 -2
  215. package/core/built/admin/assets/stripe-button-UFP4QRXd.js +1 -0
  216. package/core/built/admin/assets/stripe-connect-modal-C_L3uM6n.js +1 -0
  217. package/core/built/admin/assets/switch-CE2NuMn-.js +1 -0
  218. package/core/built/admin/assets/{table-CB-4_HBJ.js → table-TMCgCQK7.js} +1 -1
  219. package/core/built/admin/assets/tabs-Dd6oOKbv.js +1 -0
  220. package/core/built/admin/assets/tag-detail-D3l-UJPR.js +1 -0
  221. package/core/built/admin/assets/tags-BwCgmr5v.js +1 -0
  222. package/core/built/admin/assets/tags-WdKQGWR8.js +1 -0
  223. package/core/built/admin/assets/testimonials-modal-CqWp-xXJ.js +1 -0
  224. package/core/built/admin/assets/{text-DwtjwGkh.js → text-DU9Hv33_.js} +1 -1
  225. package/core/built/admin/assets/textarea-d9qEkMOP.js +1 -0
  226. package/core/built/admin/assets/tier-detail-modal-BkcR2u0A.js +1 -0
  227. package/core/built/admin/assets/{tiers-DYsa4DCo.js → tiers-Da6kxHQP.js} +1 -1
  228. package/core/built/admin/assets/toggle-group-Guorw93z.js +1 -0
  229. package/core/built/admin/assets/{topic-filter-DAFT87tQ.js → topic-filter-CbgoWIHn.js} +1 -1
  230. package/core/built/admin/assets/transistor-modal-DuNnnpal.js +1 -0
  231. package/core/built/admin/assets/unsplash-modal-y2bVwTHH.js +1 -0
  232. package/core/built/admin/assets/{use-analytics-data-S0FTd43s.js → use-analytics-data-B-b4wNSC.js} +1 -1
  233. package/core/built/admin/assets/use-check-theme-limit-error-m09J38Mq.js +2 -0
  234. package/core/built/admin/assets/use-feature-flag-ud0Qz8vt.js +1 -0
  235. package/core/built/admin/assets/{use-filter-params-Gpoq2E1L.js → use-filter-params-BnbBDc7T.js} +1 -1
  236. package/core/built/admin/assets/use-filterable-api-iMpDZBxD.js +1 -0
  237. package/core/built/admin/assets/use-form-vRjI0Kwk.js +1 -0
  238. package/core/built/admin/assets/{use-growth-stats-CIaiHOVb.js → use-growth-stats-DPU5rt3W.js} +1 -1
  239. package/core/built/admin/assets/use-hash-link-navigation-guard-B9j6HIrV.js +1 -0
  240. package/core/built/admin/assets/use-limiter-BkN3E4gw.js +2 -0
  241. package/core/built/admin/assets/use-pintura-config-C_z8HTG2.js +1 -0
  242. package/core/built/admin/assets/use-pintura-editor-DNgbkhGp.js +1 -0
  243. package/core/built/admin/assets/{use-post-referrers-Cq2vBPrA.js → use-post-referrers-C2mXyrp-.js} +1 -1
  244. package/core/built/admin/assets/use-query-params-DHhhu8wi.js +1 -0
  245. package/core/built/admin/assets/{use-reply-chain-data-DBDRtGu-.js → use-reply-chain-data-DaK8q2qQ.js} +1 -1
  246. package/core/built/admin/assets/use-setting-group-YlYcoNyP.js +1 -0
  247. package/core/built/admin/assets/use-settings-navigation-Bf1g1rEh.js +1 -0
  248. package/core/built/admin/assets/{use-simple-pagination-CjYNxdwe.js → use-simple-pagination-D9ckKo5-.js} +1 -1
  249. package/core/built/admin/assets/{use-tinybird-query-falHAtuK.js → use-tinybird-query-GSa32hVw.js} +1 -1
  250. package/core/built/admin/assets/{use-tinybird-token-D7-8ND1y.js → use-tinybird-token-CKLOZFsw.js} +1 -1
  251. package/core/built/admin/assets/use-url-input-z-p0Kewm.js +5 -0
  252. package/core/built/admin/assets/user-detail-modal-BMxOA4Sv.js +1 -0
  253. package/core/built/admin/assets/{users-Dkv8NQZe.js → users-B6hIds6n.js} +1 -1
  254. package/core/built/admin/assets/validator-Cr0sMKuW.js +7 -0
  255. package/core/built/admin/assets/{virtual-list-CvnC9zF2.js → virtual-list-RHeEL05L.js} +1 -1
  256. package/core/built/admin/assets/{web-jFYWCKDn.js → web-Bw2QMO5u.js} +2 -2
  257. package/core/built/admin/assets/web-xJtcwMy4.js +1 -0
  258. package/core/built/admin/assets/zapier-modal-BP2f4-vY.js +1 -0
  259. package/core/built/admin/index.html +20 -18
  260. package/core/frontend/helpers/authors.js +1 -1
  261. package/core/frontend/helpers/link.js +3 -8
  262. package/core/frontend/helpers/tags.js +1 -1
  263. package/core/frontend/meta/author-url.js +2 -2
  264. package/core/frontend/meta/url.js +4 -4
  265. package/core/frontend/services/data/fetch-data.js +10 -5
  266. package/core/frontend/services/proxy.js +1 -1
  267. package/core/frontend/services/routing/api-adapter.js +115 -0
  268. package/core/frontend/services/routing/api-adapter.ts +157 -0
  269. package/core/frontend/services/routing/collection-router.js +5 -3
  270. package/core/frontend/services/routing/controllers/index.js +1 -1
  271. package/core/frontend/services/routing/controllers/rss.js +26 -31
  272. package/core/frontend/services/routing/controllers/rss.ts +66 -0
  273. package/core/frontend/services/routing/controllers/static.js +8 -7
  274. package/core/frontend/services/routing/events.js +20 -6
  275. package/core/frontend/services/routing/parent-router.js +17 -4
  276. package/core/frontend/services/routing/permalink-adapter.js +13 -0
  277. package/core/frontend/services/routing/permalink-adapter.ts +10 -0
  278. package/core/frontend/services/routing/registry.js +59 -33
  279. package/core/frontend/services/routing/router-manager.js +24 -22
  280. package/core/frontend/services/routing/static-routes-router.js +4 -4
  281. package/core/frontend/services/routing/taxonomy-router.js +6 -2
  282. package/core/frontend/services/sitemap/base-site-map-generator.js +1 -28
  283. package/core/frontend/services/sitemap/site-map-manager.js +22 -57
  284. package/core/server/adapters/route-settings/utils.js +10 -2
  285. package/core/server/adapters/route-settings/utils.ts +12 -2
  286. package/core/server/api/endpoints/email-post.js +1 -1
  287. package/core/server/api/endpoints/previews.js +1 -1
  288. package/core/server/api/endpoints/search-index-public.js +3 -3
  289. package/core/server/api/endpoints/search-index.js +4 -4
  290. package/core/server/api/endpoints/utils/serializers/input/authors.js +2 -2
  291. package/core/server/api/endpoints/utils/serializers/input/comments.js +1 -1
  292. package/core/server/api/endpoints/utils/serializers/input/pages.js +3 -3
  293. package/core/server/api/endpoints/utils/serializers/input/posts.js +3 -3
  294. package/core/server/api/endpoints/utils/serializers/input/tags.js +1 -1
  295. package/core/server/api/endpoints/utils/serializers/input/users.js +2 -2
  296. package/core/server/api/endpoints/utils/serializers/input/utils/url.js +31 -13
  297. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +2 -2
  298. package/core/server/api/endpoints/utils/serializers/output/mappers/tags.js +2 -2
  299. package/core/server/api/endpoints/utils/serializers/output/mappers/users.js +8 -0
  300. package/core/server/api/endpoints/utils/serializers/output/utils/url.js +18 -11
  301. 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
  302. package/core/server/data/migrations/versions/6.56/2026-08-04-14-42-39-restrict-notification-add-permission-to-administrators.js +12 -0
  303. package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +74 -0
  304. package/core/server/data/migrations/versions/6.57/2026-08-04-18-54-14-reset-automation-email-analytics.js +25 -0
  305. package/core/server/data/schema/commands.js +21 -5
  306. package/core/server/data/schema/fixtures/fixtures.json +2 -2
  307. package/core/server/data/schema/schema.js +25 -5
  308. package/core/server/lib/common/to-plain.ts +1 -1
  309. package/core/server/models/post.js +12 -3
  310. package/core/server/services/adapter-manager/adapter-manager.js +1 -45
  311. package/core/server/services/adapter-manager/adapter-manager.ts +2 -48
  312. package/core/server/services/adapter-manager/adapter-paths.js +24 -0
  313. package/core/server/services/adapter-manager/adapter-paths.ts +21 -0
  314. package/core/server/services/adapter-manager/base-classes.js +22 -0
  315. package/core/server/services/adapter-manager/base-classes.ts +22 -0
  316. package/core/server/services/adapter-manager/index.js +4 -27
  317. package/core/server/services/adapter-manager/index.ts +4 -31
  318. package/core/server/services/adapter-manager/utils.js +50 -0
  319. package/core/server/services/adapter-manager/utils.ts +57 -0
  320. package/core/server/services/audience-feedback/audience-feedback-controller.js +1 -1
  321. package/core/server/services/audience-feedback/audience-feedback-service.js +2 -2
  322. package/core/server/services/automations/automations-repository.ts +3 -1
  323. package/core/server/services/automations/database-automations-repository.js +33 -14
  324. package/core/server/services/automations/database-automations-repository.ts +41 -15
  325. package/core/server/services/automations/get-scheduler-idempotency-key.js +13 -0
  326. package/core/server/services/automations/get-scheduler-idempotency-key.ts +8 -0
  327. package/core/server/services/automations/poll.js +3 -1
  328. package/core/server/services/automations/poll.ts +4 -1
  329. package/core/server/services/automations/service.js +7 -3
  330. package/core/server/services/automations/service.ts +7 -3
  331. package/core/server/services/comments/comments-service-emails.js +1 -1
  332. package/core/server/services/comments/comments-service.js +1 -1
  333. package/core/server/services/email-analytics/email-analytics-service-wrapper.js +11 -2
  334. package/core/server/services/email-analytics/email-analytics-service.js +1 -1
  335. package/core/server/services/email-service/email-service-wrapper.js +2 -1
  336. package/core/server/services/email-service/mailgun-email-provider.js +15 -0
  337. package/core/server/services/gift-links/CONTEXT.md +9 -0
  338. package/core/server/services/gifts/CONTEXT.md +55 -0
  339. package/core/server/services/gifts/README.md +28 -0
  340. package/core/server/services/gifts/gift-bookshelf-repository.js +65 -45
  341. package/core/server/services/gifts/gift-bookshelf-repository.ts +203 -74
  342. package/core/server/services/gifts/gift-checkout-adapter.js +59 -0
  343. package/core/server/services/gifts/gift-checkout-offer.js +74 -0
  344. package/core/server/services/gifts/gift-checkout-offer.ts +127 -0
  345. package/core/server/services/gifts/gift-codec.js +63 -0
  346. package/core/server/services/gifts/gift-codec.ts +62 -0
  347. package/core/server/services/gifts/gift-controller.js +6 -31
  348. package/core/server/services/gifts/gift-controller.ts +6 -69
  349. package/core/server/services/gifts/gift-email-service.ts +3 -2
  350. package/core/server/services/gifts/gift-schema.js +34 -0
  351. package/core/server/services/gifts/gift-schema.ts +39 -0
  352. package/core/server/services/gifts/gift-service-wrapper.js +13 -11
  353. package/core/server/services/gifts/gift-service.js +265 -52
  354. package/core/server/services/gifts/gift-service.ts +431 -97
  355. package/core/server/services/gifts/gift.ts +16 -39
  356. package/core/server/services/indexnow-ping/indexnow-ping-service.js +1 -1
  357. package/core/server/services/indexnow-ping/indexnow-ping-service.ts +2 -4
  358. package/core/server/services/lib/mailgun-client.js +3 -3
  359. package/core/server/services/link-tracking/link-click-repository.js +6 -1
  360. package/core/server/services/link-tracking/link-click-tracking-service.js +12 -3
  361. package/core/server/services/media-inliner/external-media-inliner.js +12 -8
  362. package/core/server/services/member-attribution/url-translator.js +5 -7
  363. package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +4 -3
  364. package/core/server/services/member-welcome-emails/service.js +79 -15
  365. package/core/server/services/members/api.js +2 -3
  366. package/core/server/services/members/import-export/import/completion-email.js +8 -4
  367. package/core/server/services/members/import-export/import/completion-email.ts +9 -4
  368. package/core/server/services/members/import-export/import/importer.js +25 -4
  369. package/core/server/services/members/import-export/import/importer.ts +27 -6
  370. package/core/server/services/members/import-export/import/row.ts +5 -1
  371. package/core/server/services/members/import-export/index.js +6 -2
  372. package/core/server/services/members/import-export/index.ts +10 -4
  373. package/core/server/services/members/members-api/controllers/router-controller.js +38 -36
  374. package/core/server/services/members/members-api/members-api.js +13 -7
  375. package/core/server/services/members/members-api/repositories/event-repository.js +4 -94
  376. package/core/server/services/members/members-api/services/member-bread-service.js +3 -3
  377. package/core/server/services/members/members-api/services/payments-service.js +6 -68
  378. package/core/server/services/members-custom-fields/queries.js +4 -13
  379. package/core/server/services/members-custom-fields/queries.ts +4 -13
  380. package/core/server/services/members-custom-fields/schema.js +19 -21
  381. package/core/server/services/members-custom-fields/schema.ts +18 -20
  382. package/core/server/services/members-custom-fields/storage.js +94 -27
  383. package/core/server/services/members-custom-fields/storage.ts +114 -39
  384. package/core/server/services/members-custom-fields/values-service.js +98 -103
  385. package/core/server/services/members-custom-fields/values-service.ts +116 -114
  386. package/core/server/services/mentions/resource-service.js +1 -1
  387. package/core/server/services/mentions/service.js +2 -2
  388. package/core/server/services/oembed/oembed-service.js +44 -3
  389. package/core/server/services/posts/posts-service.js +11 -0
  390. package/core/server/services/route-settings/dynamic-routing-service.js +10 -62
  391. package/core/server/services/route-settings/route-settings-parser.js +25 -25
  392. package/core/server/services/route-settings/route-settings-parser.ts +27 -27
  393. package/core/server/services/route-settings/validation-errors.js +68 -4
  394. package/core/server/services/route-settings/validation-errors.ts +79 -4
  395. package/core/server/services/slack-ping/slack-ping-service.js +2 -2
  396. package/core/server/services/slack-ping/slack-ping-service.ts +3 -5
  397. package/core/server/services/stats/content-stats-service.js +3 -3
  398. package/core/server/services/stats/posts-stats-service.js +29 -25
  399. package/core/server/services/stripe/services/webhook/charge-refunded-event-service.js +1 -1
  400. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +16 -4
  401. package/core/server/services/stripe/stripe-api.js +10 -0
  402. package/core/server/services/url/config.js +90 -135
  403. package/core/server/services/url/index.js +6 -49
  404. package/core/server/services/url/lazy-find-resource.js +14 -11
  405. package/core/server/services/url/lazy-find-resource.ts +14 -11
  406. package/core/server/services/url/lazy-url-service.js +174 -94
  407. package/core/server/services/url/lazy-url-service.ts +233 -97
  408. package/core/server/services/url/routable-resources.js +62 -71
  409. package/core/server/services/url/router-filter.js +12 -4
  410. package/core/server/services/url/router-filter.ts +12 -4
  411. package/core/server/services/webhooks/index.js +1 -1
  412. package/core/server/services/webhooks/serialize.js +2 -1
  413. package/core/server/web/gift-preview/controller.js +10 -25
  414. package/core/shared/config/env/config.testing-mysql.json +1 -2
  415. package/core/shared/config/env/config.testing.json +1 -2
  416. package/core/shared/events/index.js +1 -2
  417. package/core/shared/labs.js +4 -4
  418. package/package.json +10 -10
  419. package/pnpm-lock.yaml +27 -20
  420. package/pnpm-workspace.yaml +3 -2
  421. package/tsconfig.json +1 -0
  422. package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
  423. package/core/built/admin/assets/Combination-CJG7Flp-.js +0 -41
  424. package/core/built/admin/assets/activitypub/layout-qx-bUL1k.js +0 -1
  425. package/core/built/admin/assets/automated-emails-DRy7qY-J.js +0 -7
  426. package/core/built/admin/assets/checkbox-CWa28lgO.js +0 -1
  427. package/core/built/admin/assets/chunk.524.cffd399783a519e404a9.js +0 -25
  428. package/core/built/admin/assets/chunk.582.eebf0d0b2d202bb746a5.js +0 -27
  429. package/core/built/admin/assets/code-editor-view-CCr0rBWS.js +0 -1
  430. package/core/built/admin/assets/comments-DyUSMUdj.js +0 -1
  431. package/core/built/admin/assets/disabled-sources-indicator-CUxMfiPa.js +0 -1
  432. package/core/built/admin/assets/dist-2Tnz2Yfq.js +0 -1
  433. package/core/built/admin/assets/dist-CjY-f5Z1.js +0 -1
  434. package/core/built/admin/assets/dist-DDY6GRVz.js +0 -1
  435. package/core/built/admin/assets/dist-DG78yroX.js +0 -23
  436. package/core/built/admin/assets/domain-C0bV2UV8.js +0 -1
  437. package/core/built/admin/assets/editor-Dp865O-4.js +0 -8
  438. package/core/built/admin/assets/esm-CFX93H5M.js +0 -2
  439. package/core/built/admin/assets/filter-sources-BV2rnpfA.js +0 -1
  440. package/core/built/admin/assets/filters-CUXJ2Nm9.js +0 -1
  441. package/core/built/admin/assets/filters-DfbIWc_l.js +0 -1
  442. package/core/built/admin/assets/index-9Cw8z3rA.css +0 -1
  443. package/core/built/admin/assets/index-BGG7NckO.js +0 -67
  444. package/core/built/admin/assets/input-group-BSgG_lVw.js +0 -1
  445. package/core/built/admin/assets/label-picker-1DvSuLA1.js +0 -1
  446. package/core/built/admin/assets/labels-CDSUt4J_.js +0 -1
  447. package/core/built/admin/assets/layout-yQEi2qDM.js +0 -1
  448. package/core/built/admin/assets/link-helpers-7EYqvvHs.js +0 -1
  449. package/core/built/admin/assets/member-custom-fields-yOMHyAg9.js +0 -1
  450. package/core/built/admin/assets/member-detail-BAq7gDaa.js +0 -1
  451. package/core/built/admin/assets/members-CQpVFJu4.js +0 -10
  452. package/core/built/admin/assets/modals-ReORvWnu.js +0 -31
  453. package/core/built/admin/assets/newsletter-CJQvtHqN.js +0 -1
  454. package/core/built/admin/assets/newsletters-B3QQXfGT.js +0 -1
  455. package/core/built/admin/assets/nql-BsgFMd93.js +0 -1
  456. package/core/built/admin/assets/overview-Bf2sbznq.js +0 -1
  457. package/core/built/admin/assets/overview-CBfePxcr.js +0 -1
  458. package/core/built/admin/assets/post-analytics-header-Bmc9su6q.js +0 -1
  459. package/core/built/admin/assets/preferences-DJPfHEGu.js +0 -5
  460. package/core/built/admin/assets/profile-DqBQp-gT.js +0 -1
  461. package/core/built/admin/assets/select-C5_xEt5b.js +0 -1
  462. package/core/built/admin/assets/settings-BwiVIXau.js +0 -4
  463. package/core/built/admin/assets/social-urls-BQPFEk1_.js +0 -8
  464. package/core/built/admin/assets/switch-z5Y6Uzt6.js +0 -1
  465. package/core/built/admin/assets/tabs-CCEgKYzq.js +0 -1
  466. package/core/built/admin/assets/tags-Cg2VJGmG.js +0 -1
  467. package/core/built/admin/assets/textarea-Bjccvh_E.js +0 -1
  468. package/core/built/admin/assets/toggle-group-b3IlxvhV.js +0 -1
  469. package/core/built/admin/assets/web-B_5GZRvH.js +0 -1
  470. package/core/server/services/gifts/gift-repository.js +0 -2
  471. package/core/server/services/gifts/gift-repository.ts +0 -29
  472. package/core/server/services/route-settings/activation-bridge.js +0 -174
  473. package/core/server/services/route-settings/activation-bridge.ts +0 -250
  474. package/core/server/services/url/local-file-cache.js +0 -75
  475. package/core/server/services/url/queue.js +0 -260
  476. package/core/server/services/url/resource.js +0 -102
  477. package/core/server/services/url/resources.js +0 -480
  478. package/core/server/services/url/url-generator.js +0 -276
  479. package/core/server/services/url/url-service-facade.js +0 -366
  480. package/core/server/services/url/url-service-facade.ts +0 -519
  481. package/core/server/services/url/url-service.js +0 -387
  482. package/core/server/services/url/urls.js +0 -140
  483. package/core/shared/events/url-resource-updated-event.js +0 -33
@@ -0,0 +1,55 @@
1
+ # Gift Subscriptions
2
+
3
+ Gift Subscriptions covers prepaid, fixed-duration membership access intended to be shared and claimed. Its language spans gift purchase, gift redemption, gift expiration, gift consumption, and gift continuation into a paid subscription.
4
+
5
+ ## Language
6
+
7
+ ### Roles
8
+
9
+ **Buyer**:
10
+ The person who pays for a gift subscription. A buyer need not be a member and may also become the redeemer if eligible.
11
+ _Avoid_: Purchaser, recipient, redeemer
12
+
13
+ **Redeemer**:
14
+ The member who claims a gift subscription and receives its access. The redeemer is determined at redemption, not purchase.
15
+ _Avoid_: Recipient, buyer
16
+
17
+ ### Purchase and claim
18
+
19
+ **Gift subscription**:
20
+ A prepaid, non-renewing entitlement to one membership tier for a fixed period. It begins when claimed rather than when purchased.
21
+ _Avoid_: Gift membership, recurring subscription
22
+
23
+ **Gift purchase**:
24
+ A one-time purchase that creates a gift subscription for the buyer to share.
25
+ _Avoid_: Gift checkout
26
+
27
+ **Redemption link**:
28
+ A single-use, time-limited link through which a purchased gift subscription can be claimed.
29
+ _Avoid_: Gift Link
30
+
31
+ **Gift redemption**:
32
+ The act of claiming an eligible gift subscription. The member who claims it becomes the redeemer and gifted access begins at that point.
33
+ _Avoid_: Gift activation
34
+
35
+ ### Time and lifecycle
36
+
37
+ **Gift duration**:
38
+ The total length of gifted access, measured from redemption.
39
+ _Avoid_: Gift cadence, billing period
40
+
41
+ **Gift cadence**:
42
+ The monthly or yearly membership price basis used to value a gift subscription. It does not make the gift recurring.
43
+ _Avoid_: Gift duration, renewal interval
44
+
45
+ **Gift expiration**:
46
+ The end of the claim window for a purchased gift subscription that was never redeemed.
47
+ _Avoid_: Gift consumption
48
+
49
+ **Gift consumption**:
50
+ The end of a redeemed gift subscription's role in providing access, either when its duration finishes or a paid subscription takes over.
51
+ _Avoid_: Gift expiration, cancellation
52
+
53
+ **Gift continuation**:
54
+ Starting a paid subscription from gifted access while carrying the remaining gifted time forward before billing begins.
55
+ _Avoid_: Gift renewal
@@ -0,0 +1,28 @@
1
+ # Gifts
2
+
3
+ This module owns the gift-subscription journey. Its interface is
4
+ capability-oriented; callers do not receive `Gift` entities, Bookshelf models,
5
+ transactions, or Stripe objects.
6
+
7
+ ## Interface
8
+
9
+ - `startCheckout(input)` owns the `giftSubCustomization` flag, duration
10
+ catalogue, Portal plan gate, tier/cadence validation, authoritative price,
11
+ token, success parameters, metadata, customer, and one-time payment.
12
+ - `preparePaidContinuation(input)` validates an active gift and returns the
13
+ stable tier/cadence/trial decision consumed by the normal subscription
14
+ checkout path.
15
+ - `getRedeemable(input)` and `redeem(input)` return the public redemption DTO.
16
+ - `completePurchase(input)` and `handlePaymentRefund(input)` accept normalized
17
+ payment outcomes.
18
+ - `handlePaidSubscriptionActivation(memberId)` consumes gift time when a paid
19
+ subscription takes over.
20
+ - `getMemberPresentations(memberIds)`, `getPreview(token)`,
21
+ `browsePurchaseEvents(...)`, and `browseRedemptionEvents(...)` expose
22
+ stable read models.
23
+ - `processReminders()`, `processConsumed()`, and `processExpired()` own due
24
+ lifecycle work; scheduler and HTTP triggers remain adapters.
25
+ - `reassignRedeemer(...)` is the import capability.
26
+
27
+ The `Gift`, repository, Bookshelf query, Stripe checkout, email, scheduling,
28
+ and notification collaborators are internal adapters.
@@ -5,7 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.GiftBookshelfRepository = void 0;
7
7
  const errors_1 = __importDefault(require("@tryghost/errors"));
8
- const gift_1 = require("./gift");
8
+ const mongo_utils_1 = require("@tryghost/mongo-utils");
9
+ const gift_codec_1 = require("./gift-codec");
9
10
  class GiftBookshelfRepository {
10
11
  model;
11
12
  constructor({ GiftModel }) {
@@ -60,6 +61,26 @@ class GiftBookshelfRepository {
60
61
  }
61
62
  return map;
62
63
  }
64
+ browsePurchaseEvents(options = {}, filter) {
65
+ return this.browseEvents({
66
+ options,
67
+ filter,
68
+ type: 'gift_purchase_event',
69
+ relation: 'buyer',
70
+ memberIdColumn: 'buyer_member_id',
71
+ dateColumn: 'purchased_at'
72
+ });
73
+ }
74
+ browseRedemptionEvents(options = {}, filter) {
75
+ return this.browseEvents({
76
+ options,
77
+ filter,
78
+ type: 'gift_redemption_event',
79
+ relation: 'redeemer',
80
+ memberIdColumn: 'redeemer_member_id',
81
+ dateColumn: 'redeemed_at'
82
+ });
83
+ }
63
84
  async findPendingConsumption() {
64
85
  const now = new Date();
65
86
  const collection = await this.model.findAll({
@@ -111,53 +132,52 @@ class GiftBookshelfRepository {
111
132
  return await this.model.transaction(callback);
112
133
  }
113
134
  toRow(gift) {
114
- return {
115
- token: gift.token,
116
- buyer_email: gift.buyerEmail,
117
- buyer_member_id: gift.buyerMemberId,
118
- redeemer_member_id: gift.redeemerMemberId,
119
- tier_id: gift.tierId,
120
- cadence: gift.cadence,
121
- duration: gift.duration,
122
- currency: gift.currency,
123
- amount: gift.amount,
124
- stripe_checkout_session_id: gift.stripeCheckoutSessionId,
125
- stripe_payment_intent_id: gift.stripePaymentIntentId,
126
- consumes_at: gift.consumesAt,
127
- expires_at: gift.expiresAt,
128
- status: gift.status,
129
- purchased_at: gift.purchasedAt,
130
- redeemed_at: gift.redeemedAt,
131
- consumed_at: gift.consumedAt,
132
- expired_at: gift.expiredAt,
133
- refunded_at: gift.refundedAt,
134
- consumes_soon_reminder_sent_at: gift.consumesSoonReminderSentAt
135
- };
135
+ return (0, gift_codec_1.encodeGift)(gift);
136
136
  }
137
137
  toGift(model) {
138
- const json = model.toJSON();
139
- return new gift_1.Gift({
140
- token: json.token,
141
- buyerEmail: json.buyer_email,
142
- buyerMemberId: json.buyer_member_id,
143
- redeemerMemberId: json.redeemer_member_id,
144
- tierId: json.tier_id,
145
- cadence: json.cadence,
146
- duration: json.duration,
147
- currency: json.currency,
148
- amount: json.amount,
149
- stripeCheckoutSessionId: json.stripe_checkout_session_id,
150
- stripePaymentIntentId: json.stripe_payment_intent_id,
151
- consumesAt: json.consumes_at,
152
- expiresAt: json.expires_at,
153
- status: json.status,
154
- purchasedAt: json.purchased_at,
155
- redeemedAt: json.redeemed_at,
156
- consumedAt: json.consumed_at,
157
- expiredAt: json.expired_at,
158
- refundedAt: json.refunded_at,
159
- consumesSoonReminderSentAt: json.consumes_soon_reminder_sent_at ?? null
138
+ return (0, gift_codec_1.decodeGiftRow)(model.toJSON());
139
+ }
140
+ async browseEvents({ options, filter, type, relation, memberIdColumn, dateColumn }) {
141
+ const replaceCustomFilter = (existingFilter) => (0, mongo_utils_1.replaceFilters)(existingFilter, {
142
+ custom: filter
160
143
  });
144
+ const queryOptions = {
145
+ ...options,
146
+ withRelated: [relation, 'tier'],
147
+ filter: `${memberIdColumn}:-null+custom:true`,
148
+ useBasicCount: true,
149
+ mongoTransformer: (0, mongo_utils_1.chainTransformers)(replaceCustomFilter, ...(0, mongo_utils_1.mapKeys)({
150
+ 'data.created_at': dateColumn,
151
+ 'data.member_id': memberIdColumn
152
+ }))
153
+ };
154
+ if (typeof queryOptions.order === 'string') {
155
+ queryOptions.order = queryOptions.order.replace(/created_at/g, dateColumn);
156
+ }
157
+ if (!this.model.findPage) {
158
+ throw new errors_1.default.InternalServerError({ message: 'Gift model does not support paginated event queries.' });
159
+ }
160
+ const { data: models, meta } = await this.model.findPage(queryOptions);
161
+ return {
162
+ data: models.map((model) => {
163
+ const json = model.toJSON(queryOptions);
164
+ return {
165
+ type,
166
+ data: {
167
+ id: json.id,
168
+ member: json[relation] || null,
169
+ member_id: json[memberIdColumn],
170
+ tier_name: json.tier?.name,
171
+ cadence: json.cadence,
172
+ duration: json.duration,
173
+ amount: json.amount,
174
+ currency: json.currency,
175
+ created_at: json[dateColumn]
176
+ }
177
+ };
178
+ }),
179
+ meta
180
+ };
161
181
  }
162
182
  }
163
183
  exports.GiftBookshelfRepository = GiftBookshelfRepository;
@@ -1,10 +1,100 @@
1
1
  import errors from '@tryghost/errors';
2
- import {Gift, type GiftCadence, type GiftStatus} from './gift';
3
- import type {FindPendingReminderOptions, GiftRepository, RepositoryTransactionOptions} from './gift-repository';
2
+ import {chainTransformers, mapKeys, replaceFilters} from '@tryghost/mongo-utils';
3
+ import type {Knex} from 'knex';
4
+ import {Gift} from './gift';
5
+ import {decodeGiftRow, encodeGift} from './gift-codec';
6
+ import type {GiftCadence, GiftRow} from './gift-schema';
7
+
8
+ type ParsedNqlFilter = unknown;
9
+
10
+ export interface GiftEventBrowseOptions {
11
+ filter?: string;
12
+ limit?: number | 'all';
13
+ order?: string;
14
+ page?: number;
15
+ }
16
+
17
+ export interface Pagination {
18
+ page: number;
19
+ pages: number;
20
+ limit: number | 'all';
21
+ total: number;
22
+ prev: number | null;
23
+ next: number | null;
24
+ }
25
+
26
+ export interface GiftEventData {
27
+ id: string;
28
+ member: Record<string, unknown> | null;
29
+ member_id: string | null;
30
+ tier_name?: string;
31
+ cadence: GiftCadence;
32
+ duration: number;
33
+ amount: number;
34
+ currency: string;
35
+ created_at: Date | string | null;
36
+ }
37
+
38
+ export interface GiftEventPage {
39
+ data: Array<{
40
+ type: 'gift_purchase_event' | 'gift_redemption_event';
41
+ data: GiftEventData;
42
+ }>;
43
+ meta: {
44
+ pagination?: Pagination;
45
+ };
46
+ }
47
+
48
+ export interface RepositoryTransactionOptions {
49
+ transacting?: Knex.Transaction;
50
+ forUpdate?: boolean;
51
+ }
52
+
53
+ export interface FindPendingReminderOptions {
54
+ now: Date;
55
+ reminderLeadMs: number;
56
+ reminderFloorMs: number;
57
+ transacting?: Knex.Transaction;
58
+ }
59
+
60
+ export interface GiftRepository {
61
+ existsByCheckoutSessionId(checkoutSessionId: string): Promise<boolean>;
62
+ getById(id: string, options?: RepositoryTransactionOptions): Promise<Gift | null>;
63
+ getByToken(token: string, options?: RepositoryTransactionOptions): Promise<Gift | null>;
64
+ getByPaymentIntentId(paymentIntentId: string): Promise<Gift | null>;
65
+ findPendingConsumption(): Promise<Gift[]>;
66
+ findPendingExpiration(): Promise<Gift[]>;
67
+ findPendingReminder(options: FindPendingReminderOptions): Promise<Gift[]>;
68
+ findUnsentReminders(): Promise<Gift[]>;
69
+ getActiveByMember(memberId: string, options?: RepositoryTransactionOptions): Promise<Gift | null>;
70
+ getActiveByMembers(memberIds: string[], options?: RepositoryTransactionOptions): Promise<Map<string, Gift>>;
71
+ browsePurchaseEvents(options?: GiftEventBrowseOptions, filter?: ParsedNqlFilter): Promise<GiftEventPage>;
72
+ browseRedemptionEvents(options?: GiftEventBrowseOptions, filter?: ParsedNqlFilter): Promise<GiftEventPage>;
73
+ create(gift: Gift, options?: RepositoryTransactionOptions): Promise<void>;
74
+ update(gift: Gift, options?: RepositoryTransactionOptions): Promise<void>;
75
+ transaction<T>(callback: (transacting: Knex.Transaction) => Promise<T>): Promise<T>;
76
+ }
77
+
78
+ type BookshelfSaveOptions = RepositoryTransactionOptions & {
79
+ autoRefresh?: boolean;
80
+ method?: 'update';
81
+ patch?: boolean;
82
+ };
83
+
84
+ type GiftEventQueryOptions = GiftEventBrowseOptions & {
85
+ withRelated: Array<'buyer' | 'redeemer' | 'tier'>;
86
+ filter: string;
87
+ useBasicCount: boolean;
88
+ mongoTransformer: (filter: ParsedNqlFilter) => ParsedNqlFilter;
89
+ };
90
+
91
+ type BookshelfFindOptions = RepositoryTransactionOptions & {
92
+ filter?: string;
93
+ require?: boolean;
94
+ };
4
95
 
5
96
  type BookshelfDocument<T> = {
6
- save(data: Partial<T>, options?: unknown): Promise<unknown>;
7
- set(data: Partial<T>): void;
97
+ save(data: Partial<T>, options?: BookshelfSaveOptions): Promise<BookshelfDocument<T>>;
8
98
  toJSON(): T;
9
99
  };
10
100
 
@@ -12,35 +102,31 @@ type BookshelfCollection<T> = {
12
102
  models: BookshelfDocument<T>[];
13
103
  };
14
104
 
15
- type BookshelfModel<T> = {
16
- add(data: Partial<T>, unfilteredOptions?: unknown): Promise<T>;
17
- transaction<R>(callback: (transacting: unknown) => Promise<R>): Promise<R>;
18
- findOne(data: Record<string, unknown>, unfilteredOptions?: unknown): Promise<BookshelfDocument<T> | null>;
19
- findAll(unfilteredOptions?: unknown): Promise<BookshelfCollection<T>>;
20
- };
21
-
22
- type GiftRow = {
105
+ type GiftEventBookshelfRow = {
23
106
  id: string;
24
- token: string;
25
- buyer_email: string;
26
107
  buyer_member_id: string | null;
27
108
  redeemer_member_id: string | null;
28
- tier_id: string;
109
+ buyer?: Record<string, unknown> | null;
110
+ redeemer?: Record<string, unknown> | null;
111
+ tier?: {name?: string} | null;
29
112
  cadence: GiftCadence;
30
113
  duration: number;
31
- currency: string;
32
114
  amount: number;
33
- stripe_checkout_session_id: string;
34
- stripe_payment_intent_id: string;
35
- consumes_at: Date | null;
36
- expires_at: Date;
37
- status: GiftStatus;
38
- purchased_at: Date;
39
- redeemed_at: Date | null;
40
- consumed_at: Date | null;
41
- expired_at: Date | null;
42
- refunded_at: Date | null;
43
- consumes_soon_reminder_sent_at: Date | null;
115
+ currency: string;
116
+ purchased_at: Date | string;
117
+ redeemed_at: Date | string | null;
118
+ };
119
+
120
+ type GiftEventBookshelfDocument = {
121
+ toJSON(options?: GiftEventQueryOptions): GiftEventBookshelfRow;
122
+ };
123
+
124
+ type BookshelfModel<T> = {
125
+ add(data: Partial<T>, options?: RepositoryTransactionOptions): Promise<BookshelfDocument<T>>;
126
+ transaction<R>(callback: (transacting: Knex.Transaction) => Promise<R>): Promise<R>;
127
+ findOne(data: Partial<T> & {id?: string}, options?: BookshelfFindOptions): Promise<BookshelfDocument<T> | null>;
128
+ findAll(options?: BookshelfFindOptions): Promise<BookshelfCollection<T>>;
129
+ findPage(options: GiftEventQueryOptions): Promise<{data: GiftEventBookshelfDocument[]; meta: GiftEventPage['meta']}>;
44
130
  };
45
131
 
46
132
  type GiftBookshelfModel = BookshelfModel<GiftRow>;
@@ -116,6 +202,28 @@ export class GiftBookshelfRepository implements GiftRepository {
116
202
  return map;
117
203
  }
118
204
 
205
+ browsePurchaseEvents(options: GiftEventBrowseOptions = {}, filter?: ParsedNqlFilter): Promise<GiftEventPage> {
206
+ return this.browseEvents({
207
+ options,
208
+ filter,
209
+ type: 'gift_purchase_event',
210
+ relation: 'buyer',
211
+ memberIdColumn: 'buyer_member_id',
212
+ dateColumn: 'purchased_at'
213
+ });
214
+ }
215
+
216
+ browseRedemptionEvents(options: GiftEventBrowseOptions = {}, filter?: ParsedNqlFilter): Promise<GiftEventPage> {
217
+ return this.browseEvents({
218
+ options,
219
+ filter,
220
+ type: 'gift_redemption_event',
221
+ relation: 'redeemer',
222
+ memberIdColumn: 'redeemer_member_id',
223
+ dateColumn: 'redeemed_at'
224
+ });
225
+ }
226
+
119
227
  async findPendingConsumption(): Promise<Gift[]> {
120
228
  const now = new Date();
121
229
 
@@ -179,59 +287,80 @@ export class GiftBookshelfRepository implements GiftRepository {
179
287
  });
180
288
  }
181
289
 
182
- async transaction<T>(callback: (transacting: unknown) => Promise<T>): Promise<T> {
290
+ async transaction<T>(callback: (transacting: Knex.Transaction) => Promise<T>): Promise<T> {
183
291
  return await this.model.transaction(callback);
184
292
  }
185
293
 
186
- private toRow(gift: Gift): Omit<GiftRow, 'id'> {
187
- return {
188
- token: gift.token,
189
- buyer_email: gift.buyerEmail,
190
- buyer_member_id: gift.buyerMemberId,
191
- redeemer_member_id: gift.redeemerMemberId,
192
- tier_id: gift.tierId,
193
- cadence: gift.cadence,
194
- duration: gift.duration,
195
- currency: gift.currency,
196
- amount: gift.amount,
197
- stripe_checkout_session_id: gift.stripeCheckoutSessionId,
198
- stripe_payment_intent_id: gift.stripePaymentIntentId,
199
- consumes_at: gift.consumesAt,
200
- expires_at: gift.expiresAt,
201
- status: gift.status,
202
- purchased_at: gift.purchasedAt,
203
- redeemed_at: gift.redeemedAt,
204
- consumed_at: gift.consumedAt,
205
- expired_at: gift.expiredAt,
206
- refunded_at: gift.refundedAt,
207
- consumes_soon_reminder_sent_at: gift.consumesSoonReminderSentAt
208
- };
294
+ private toRow(gift: Gift): GiftRow {
295
+ return encodeGift(gift);
209
296
  }
210
297
 
211
298
  private toGift(model: BookshelfDocument<GiftRow>): Gift {
212
- const json = model.toJSON();
213
-
214
- return new Gift({
215
- token: json.token,
216
- buyerEmail: json.buyer_email,
217
- buyerMemberId: json.buyer_member_id,
218
- redeemerMemberId: json.redeemer_member_id,
219
- tierId: json.tier_id,
220
- cadence: json.cadence,
221
- duration: json.duration,
222
- currency: json.currency,
223
- amount: json.amount,
224
- stripeCheckoutSessionId: json.stripe_checkout_session_id,
225
- stripePaymentIntentId: json.stripe_payment_intent_id,
226
- consumesAt: json.consumes_at,
227
- expiresAt: json.expires_at,
228
- status: json.status,
229
- purchasedAt: json.purchased_at,
230
- redeemedAt: json.redeemed_at,
231
- consumedAt: json.consumed_at,
232
- expiredAt: json.expired_at,
233
- refundedAt: json.refunded_at,
234
- consumesSoonReminderSentAt: json.consumes_soon_reminder_sent_at ?? null
299
+ return decodeGiftRow(model.toJSON());
300
+ }
301
+
302
+ private async browseEvents({
303
+ options,
304
+ filter,
305
+ type,
306
+ relation,
307
+ memberIdColumn,
308
+ dateColumn
309
+ }: {
310
+ options: GiftEventBrowseOptions;
311
+ filter?: ParsedNqlFilter;
312
+ type: 'gift_purchase_event' | 'gift_redemption_event';
313
+ relation: 'buyer' | 'redeemer';
314
+ memberIdColumn: 'buyer_member_id' | 'redeemer_member_id';
315
+ dateColumn: 'purchased_at' | 'redeemed_at';
316
+ }): Promise<GiftEventPage> {
317
+ const replaceCustomFilter = (existingFilter: ParsedNqlFilter): ParsedNqlFilter => replaceFilters(existingFilter, {
318
+ custom: filter
235
319
  });
320
+ const queryOptions: GiftEventQueryOptions = {
321
+ ...options,
322
+ withRelated: [relation, 'tier'],
323
+ filter: `${memberIdColumn}:-null+custom:true`,
324
+ useBasicCount: true,
325
+ mongoTransformer: chainTransformers(
326
+ replaceCustomFilter,
327
+ ...mapKeys({
328
+ 'data.created_at': dateColumn,
329
+ 'data.member_id': memberIdColumn
330
+ })
331
+ )
332
+ };
333
+
334
+ if (typeof queryOptions.order === 'string') {
335
+ queryOptions.order = queryOptions.order.replace(/created_at/g, dateColumn);
336
+ }
337
+
338
+ if (!this.model.findPage) {
339
+ throw new errors.InternalServerError({message: 'Gift model does not support paginated event queries.'});
340
+ }
341
+
342
+ const {data: models, meta} = await this.model.findPage(queryOptions);
343
+
344
+ return {
345
+ data: models.map((model) => {
346
+ const json = model.toJSON(queryOptions);
347
+
348
+ return {
349
+ type,
350
+ data: {
351
+ id: json.id,
352
+ member: json[relation] || null,
353
+ member_id: json[memberIdColumn],
354
+ tier_name: json.tier?.name,
355
+ cadence: json.cadence,
356
+ duration: json.duration,
357
+ amount: json.amount,
358
+ currency: json.currency,
359
+ created_at: json[dateColumn]
360
+ }
361
+ };
362
+ }),
363
+ meta
364
+ };
236
365
  }
237
366
  }
@@ -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
+ }