ghost 6.57.1 → 6.58.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 (747) hide show
  1. package/LICENSE +22 -0
  2. package/bin/validate-adapters.js +1 -5
  3. package/components/ghost-storage-base-3.0.0.tgz +0 -0
  4. package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
  5. package/components/tryghost-adapter-base-jobs-0.0.0.tgz +0 -0
  6. package/components/tryghost-adapter-base-redirects-0.0.0.tgz +0 -0
  7. package/components/tryghost-adapter-base-route-settings-0.0.0.tgz +0 -0
  8. package/components/tryghost-adapter-base-scheduling-0.2.2.tgz +0 -0
  9. package/components/tryghost-adapter-base-sso-0.1.2.tgz +0 -0
  10. package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
  11. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  12. package/components/tryghost-i18n-0.0.0.tgz +0 -0
  13. package/components/tryghost-kg-card-factory-5.2.4.tgz +0 -0
  14. package/components/tryghost-kg-clean-basic-html-4.3.4.tgz +0 -0
  15. package/components/tryghost-kg-converters-1.2.5.tgz +0 -0
  16. package/components/tryghost-kg-default-cards-10.3.5.tgz +0 -0
  17. package/components/tryghost-kg-default-nodes-2.2.0.tgz +0 -0
  18. package/components/tryghost-kg-default-transforms-1.3.4.tgz +0 -0
  19. package/components/tryghost-kg-html-to-lexical-1.4.0.tgz +0 -0
  20. package/components/tryghost-kg-lexical-html-renderer-1.5.0.tgz +0 -0
  21. package/components/tryghost-kg-markdown-html-renderer-7.2.4.tgz +0 -0
  22. package/components/tryghost-kg-utils-1.1.4.tgz +0 -0
  23. package/components/tryghost-nql-string-0.0.0.tgz +0 -0
  24. package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
  25. package/core/boot.js +35 -3
  26. package/core/built/admin/assets/{about-BNojRaxf.js → about-B_BvSyLF.js} +1 -1
  27. package/core/built/admin/assets/{account-migration-DMS8JsM-.js → account-migration-oJUGxnzT.js} +1 -1
  28. package/core/built/admin/assets/activitypub/{account-migration-DHFbePl6.js → account-migration-BpDeIF3G.js} +8 -8
  29. package/core/built/admin/assets/activitypub/activitypub.js +4 -4
  30. package/core/built/admin/assets/activitypub/{avatar-CT4tS2_S.js → avatar-B0SpBPVL.js} +3 -3
  31. package/core/built/admin/assets/activitypub/{avatar-flipboard-ByYWKipo.js → avatar-flipboard-DKt1LGcE.js} +3 -3
  32. package/core/built/admin/assets/activitypub/{bluesky-sharing-srVnSHDz.js → bluesky-sharing-aPMtrS3L.js} +10 -10
  33. package/core/built/admin/assets/activitypub/{chunk-KS7C4IRE-D4HEcEwi.js → chunk-KS7C4IRE-CnTrsgmM.js} +26 -15
  34. package/core/built/admin/assets/activitypub/{content-formatters-C2sos5Ht.js → content-formatters-BORfuA9G.js} +318 -284
  35. package/core/built/admin/assets/activitypub/{copy-B1UBTOoZ.js → copy-D-YvNi2E.js} +2 -2
  36. package/core/built/admin/assets/activitypub/{dist-MPsb2WZ1.js → dist-Dqggkccr.js} +5 -5
  37. package/core/built/admin/assets/activitypub/{domain-DzcM9Dzo.js → domain-CPdiB27_.js} +10 -10
  38. package/core/built/admin/assets/activitypub/{edit-profile-CsQ_4xAk.js → edit-profile-BY2PawIe.js} +7 -7
  39. package/core/built/admin/assets/activitypub/{explore-CJe0DZBK.js → explore-Sp9bgAwq.js} +11 -11
  40. package/core/built/admin/assets/activitypub/{feed-CKV3qWON.js → feed-BjtF4ohL.js} +10 -10
  41. package/core/built/admin/assets/activitypub/{field-DckHfDJf.js → field-BDOYVTg6.js} +4 -4
  42. package/core/built/admin/assets/activitypub/{hash-Dlp8RM55.js → hash-DqXYHgzG.js} +2 -2
  43. package/core/built/admin/assets/activitypub/{inbox-CsPy4R0M.js → inbox-CZ7IOAMn.js} +2 -2
  44. package/core/built/admin/assets/activitypub/{inbox-DAsjzUUe.js → inbox-z1o2j1Tb.js} +11 -11
  45. package/core/built/admin/assets/activitypub/{label-DWyo9E5T.js → label-1jhN-qhD.js} +4 -4
  46. package/core/built/admin/assets/activitypub/layout-BRMezuty.js +1 -0
  47. package/core/built/admin/assets/activitypub/{moderation-B0Inc03M.js → moderation-CZudJi8N.js} +9 -9
  48. package/core/built/admin/assets/activitypub/{note-CSE54lER.js → note-nr_m_5yb.js} +8 -8
  49. package/core/built/admin/assets/activitypub/{notifications-RSwbKqdh.js → notifications-BdcyivbT.js} +10 -10
  50. package/core/built/admin/assets/activitypub/{onboarding-BFDkzeR2.js → onboarding-B4kwav9u.js} +2 -2
  51. package/core/built/admin/assets/activitypub/{onboarding-D6rcBdPe.js → onboarding-DZNElJYP.js} +3 -3
  52. package/core/built/admin/assets/activitypub/{preferences-CE231n2_.js → preferences-CNqy1x9C.js} +13 -13
  53. package/core/built/admin/assets/activitypub/{profile-aaQ1j1Eo.js → profile-DlY4rhbO.js} +15 -15
  54. package/core/built/admin/assets/activitypub/{reply-Cjb0Vs3m.js → reply-DASIN0Ec.js} +2 -2
  55. package/core/built/admin/assets/activitypub/{routes-Djb0mc9k.js → routes-CEHdEZzS.js} +31 -31
  56. package/core/built/admin/assets/activitypub/{separator-D8pROI5n.js → separator-B3k35sUV.js} +4 -4
  57. package/core/built/admin/assets/activitypub/{settings-DMc32IQl.js → settings-CrSV1cux.js} +6 -6
  58. package/core/built/admin/assets/activitypub/{site-DVIcsohX.js → site-CtBUCPBz.js} +6 -6
  59. package/core/built/admin/assets/activitypub/{step-1-9Yey9usF.js → step-1-D9jGWyAy.js} +7 -7
  60. package/core/built/admin/assets/activitypub/{step-2-DdYmKRXV.js → step-2-Jz_enuo9.js} +7 -7
  61. package/core/built/admin/assets/activitypub/{step-3-hdSkcjZU.js → step-3-KpUqpGWi.js} +12 -12
  62. package/core/built/admin/assets/activitypub/{tabs-CUqseYzQ.js → tabs-DvlsxTCI.js} +7 -7
  63. package/core/built/admin/assets/activitypub/{topic-filter-Coni6OXM.js → topic-filter-CIN1E2jg.js} +4 -4
  64. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-MeHTPMQv.js → use-activity-pub-queries-CATFd6YV.js} +4 -4
  65. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-B046AM38.js → use-navigate-with-base-path-iCkcbMGk.js} +2 -2
  66. package/core/built/admin/assets/activitypub/{use-reply-chain-data-1AO-Xob6.js → use-reply-chain-data-DJ0vhA8V.js} +6 -6
  67. package/core/built/admin/assets/activitypub/{users-BLeiOTHc.js → users-sgJ6GNZX.js} +12 -11
  68. package/core/built/admin/assets/activitypub/{x-BRfRYd16.js → x-Cy2jUiYM.js} +2 -2
  69. package/core/built/admin/assets/add-integration-modal-BW23PDYs.js +1 -0
  70. package/core/built/admin/assets/add-newsletter-modal-M0ls8BKO.js +1 -0
  71. package/core/built/admin/assets/{add-recommendation-modal-D-3LOrdd.js → add-recommendation-modal-B0NeBMzl.js} +1 -1
  72. package/core/built/admin/assets/{analytics-DTIMRhWw.js → analytics-BC2oR8ay.js} +1 -1
  73. package/core/built/admin/assets/announcement-bar-modal-pXBqjQE_.js +1 -0
  74. package/core/built/admin/assets/{api-keys-_iCT4dxr.js → api-keys-BgKr003d.js} +1 -1
  75. package/core/built/admin/assets/{api-keys-Dx4NeNpK.js → api-keys-Df6uv1dT.js} +1 -1
  76. package/core/built/admin/assets/{audience-DkknNqhZ.js → audience-BY8od8If.js} +1 -1
  77. package/core/built/admin/assets/{automated-emails-PayPCT8L.js → automated-emails-28c2Lbb3.js} +1 -1
  78. package/core/built/admin/assets/{automations-DvSU7QhW.js → automations-DCs6Wady.js} +1 -1
  79. package/core/built/admin/assets/{automations-162q5HjL.js → automations-Dzp_QnON.js} +1 -1
  80. package/core/built/admin/assets/{bluesky-sharing-yZ9A_jL8.js → bluesky-sharing-D0P42Cws.js} +1 -1
  81. package/core/built/admin/assets/{breadcrumb-CjOH7LCU.js → breadcrumb-MTj_cKLb.js} +1 -1
  82. package/core/built/admin/assets/card-CiiE7QfM.js +1 -0
  83. package/core/built/admin/assets/{chart-helpers-DGxvWnBT.js → chart-helpers-DcO3k_vA.js} +1 -1
  84. package/core/built/admin/assets/{checkbox-BSUcnNq_.js → checkbox-LmClhqcS.js} +1 -1
  85. package/core/built/admin/assets/{chunk.424.4a27628efdade15c2dab.js → chunk.408.1a258af360eb8d905508.js} +40 -37
  86. package/core/built/admin/assets/{chunk.571.40f093ed6d883993714e.js → chunk.492.a9da46ca956b10116774.js} +1342 -1342
  87. package/core/built/admin/assets/{chunk.571.40f093ed6d883993714e.js.LICENSE.txt → chunk.492.a9da46ca956b10116774.js.LICENSE.txt} +1 -1
  88. package/core/built/admin/assets/chunk.524.4c693c3a00ac91df34fa.js +25 -0
  89. package/core/built/admin/assets/chunk.582.20b600e329e58d12d7c2.js +27 -0
  90. package/core/built/admin/assets/code-editor-view-DqTOUnbM.js +1 -0
  91. package/core/built/admin/assets/code-editor-view-bA0lfxUL.js +1 -0
  92. package/core/built/admin/assets/{debounce-Cx5B_9OI.js → color-picker-CBlJMv3y.js} +2 -2
  93. package/core/built/admin/assets/color-picker-field-Cv9GOJQZ.js +1 -0
  94. package/core/built/admin/assets/{combobox-5rdgDrDV.js → combobox-BbPJzjCp.js} +1 -1
  95. package/core/built/admin/assets/command-BrhXIlDr.js +1 -0
  96. package/core/built/admin/assets/comments-Cu4rD1aJ.js +1 -0
  97. package/core/built/admin/assets/confirmation-provider-BEdrnHns.js +1 -0
  98. package/core/built/admin/assets/{content-api-modal-BtW4Z1-1.js → content-api-modal-BOFWRUkC.js} +1 -1
  99. package/core/built/admin/assets/{copy-field-CEp_XieW.js → copy-field-8biQvNMD.js} +1 -1
  100. package/core/built/admin/assets/custom-field-type-option-CNhAgHbJ.js +1 -0
  101. package/core/built/admin/assets/custom-integration-modal-CrGOI8IB.js +1 -0
  102. package/core/built/admin/assets/{date-range-select-B3Ij1Rqg.js → date-range-select-Bsh4gIek.js} +1 -1
  103. package/core/built/admin/assets/debounce-pscB46sW.js +1 -0
  104. package/core/built/admin/assets/design-and-theme-modal-DWZguwWX.js +23 -0
  105. package/core/built/admin/assets/{dirty-confirm-dialog-YE9L21gY.js → dirty-confirm-dialog-DmdMpgyN.js} +1 -1
  106. package/core/built/admin/assets/{disabled-sources-indicator-uIzbWL7K.js → disabled-sources-indicator-BLnmyWgt.js} +1 -1
  107. package/core/built/admin/assets/dist-4m6pHdc0.js +23 -0
  108. package/core/built/admin/assets/{dist-DCk0UkGx.js → dist-B6bJuxL3.js} +1 -1
  109. package/core/built/admin/assets/{dist-CbHbGtQ-.js → dist-BtaTA7Ii.js} +1 -1
  110. package/core/built/admin/assets/{dist-Q1p9iXwH.js → dist-CZgIBXQN.js} +1 -1
  111. package/core/built/admin/assets/dist-CiHx7vA5.js +6 -0
  112. package/core/built/admin/assets/{dist-ARN6XTcR.js → dist-ECAUrdGc.js} +1 -1
  113. package/core/built/admin/assets/dist-Z97pCNqS.js +11 -0
  114. package/core/built/admin/assets/{dist-t6AXpf_4.js → dist-zwwGt8Yf.js} +1 -1
  115. package/core/built/admin/assets/{domain-DzrOAPY8.js → domain-Dp9TRrFp.js} +1 -1
  116. package/core/built/admin/assets/{dropzone-CzhP33wa.js → dropzone-B21Entqz.js} +1 -1
  117. package/core/built/admin/assets/{edit-profile-oufs4R4n.js → edit-profile-DG6lpMsr.js} +1 -1
  118. package/core/built/admin/assets/editor-CPQOrhrn.js +8 -0
  119. package/core/built/admin/assets/{editor-DtxX27Jz.css → editor-DLioOiRN.css} +1 -1
  120. package/core/built/admin/assets/{embed-signup-form-modal-Ch3s6lqA.js → embed-signup-form-modal-CgWniDha.js} +2 -2
  121. package/core/built/admin/assets/esm-CXo5SEPy.js +2 -0
  122. package/core/built/admin/assets/{explore-CA-R01Br.js → explore-C3rzONJv.js} +1 -1
  123. package/core/built/admin/assets/{feed-ClC8_Gld.js → feed-DpUwS1cU.js} +1 -1
  124. package/core/built/admin/assets/{field-B3OPenbP.js → field-BQqLTgHM.js} +1 -1
  125. package/core/built/admin/assets/{filter-sources-CiPLnG9A.js → filter-sources-FgqoUIL7.js} +1 -1
  126. package/core/built/admin/assets/{filters-yFvybRMe.js → filters-D0uIMD8n.js} +1 -1
  127. package/core/built/admin/assets/filters-DZJT29GW.js +1 -0
  128. package/core/built/admin/assets/{first-promoter-modal-BC0UpYCD.js → first-promoter-modal-DJhQQheG.js} +1 -1
  129. package/core/built/admin/assets/framework-provider-CFLwK5uS.js +4 -0
  130. package/core/built/admin/assets/{ghost-d38c30b866e59f2e06a357b5eb844fe5.css → ghost-68f447a1796b93a1f128b649bac8506d.css} +1 -1
  131. package/core/built/admin/assets/{ghost-d683fbeb945b13155ea669caa5d8db41.js → ghost-ba408063a5922b458b4a8f216cf23251.js} +124 -342
  132. package/core/built/admin/assets/{ghost-dark-1bffd5099ba7a749303133efacf8480b.css → ghost-dark-28ae4899c92022857f3584694bcb50ef.css} +1 -1
  133. package/core/built/admin/assets/{gift-link-modal-BKRuqo-k.js → gift-link-modal-sdFLjND9.js} +1 -1
  134. package/core/built/admin/assets/{gift-links-CVuqLhsZ.js → gift-links-D7BJ9qfe.js} +1 -1
  135. package/core/built/admin/assets/{global-data-provider-RzW6Bnlf.js → global-data-provider-CixmTX00.js} +1 -1
  136. package/core/built/admin/assets/{growth-ClNAldfL.js → growth-0YYkBEeS.js} +1 -1
  137. package/core/built/admin/assets/{growth-Dz8OSG06.js → growth-DpjAPeQT.js} +2 -2
  138. package/core/built/admin/assets/{history-modal-DIVuQ5Ct.js → history-modal-BWJ4-cJ0.js} +1 -1
  139. package/core/built/admin/assets/{hooks-D6EE7BIt.js → hooks-CJepWnbB.js} +1 -1
  140. package/core/built/admin/assets/{html-field-CVZ2sMxw.js → html-field-C-oa5DEl.js} +1 -1
  141. package/core/built/admin/assets/{iframe-buffering-e8vH4-Qn.js → iframe-buffering-Ba9sDpi2.js} +1 -1
  142. package/core/built/admin/assets/{images-Ok8FFJ3A.js → images-3cQgwfTD.js} +1 -1
  143. package/core/built/admin/assets/{inbox-CMUVI6qb.js → inbox-Dz_JblyJ.js} +6 -6
  144. package/core/built/admin/assets/index-BOJzlYiz.js +67 -0
  145. package/core/built/admin/assets/index-Bid6LAyc.css +1 -0
  146. package/core/built/admin/assets/{input-group-2XU-iuh_.js → input-group-DpogNUIt.js} +1 -1
  147. package/core/built/admin/assets/{integrations-BT6_iJvu.js → integrations-B9yYgci5.js} +1 -1
  148. package/core/built/admin/assets/{invite-user-modal-DrZ7xxrZ.js → invite-user-modal-DCiQSiuD.js} +1 -1
  149. package/core/built/admin/assets/{koenig-editor-base-BPj4S760.js → koenig-editor-base-BApy8Q7C.js} +1 -1
  150. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +8328 -8294
  151. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +84 -84
  152. package/core/built/admin/assets/{koenig-lexical-Cdjv-m3_.js → koenig-lexical-2KuqRwZI.js} +33 -33
  153. package/core/built/admin/assets/{kpi-card-J8Mj1sDX.js → kpi-card-Ck3tT2Wg.js} +1 -1
  154. package/core/built/admin/assets/{kpi-card-awos0BMH.js → kpi-card-DDIxptaS.js} +1 -1
  155. package/core/built/admin/assets/{kpi-helpers-SEhvH-8E.js → kpi-helpers-ssbuy-hD.js} +1 -1
  156. package/core/built/admin/assets/{kpi-tabs-R6BjsKhd.js → kpi-tabs-BqoIM6bI.js} +1 -1
  157. package/core/built/admin/assets/{label-BMC3dzLI.js → label-DodDsMzn.js} +1 -1
  158. package/core/built/admin/assets/label-picker-Cvvbmzb5.js +1 -0
  159. package/core/built/admin/assets/{labels-DAPjKPy2.js → labels-DqvSR6JF.js} +1 -1
  160. package/core/built/admin/assets/layout-DkkGyev2.js +1 -0
  161. package/core/built/admin/assets/limit-service-BhiEvqcP.js +20 -0
  162. package/core/built/admin/assets/{link-helpers-Bzkk68GD.js → link-helpers-kpbU8TWh.js} +1 -1
  163. package/core/built/admin/assets/luxon-D1RVjjpq.js +1 -0
  164. package/core/built/admin/assets/member-custom-fields-CvGcpaiW.js +1 -0
  165. package/core/built/admin/assets/member-detail-CMCMZ47_.js +1 -0
  166. package/core/built/admin/assets/members-sRl6fNfq.js +11 -0
  167. package/core/built/admin/assets/{moderation-mPjYHgZW.js → moderation-KhKwGqI1.js} +1 -1
  168. package/core/built/admin/assets/{multi-select-combobox-CsoyHAiX.js → multi-select-combobox-nhCff0_K.js} +1 -1
  169. package/core/built/admin/assets/{navigation-modal-D_c0badj.js → navigation-modal-MDJExWDJ.js} +1 -1
  170. package/core/built/admin/assets/newsletter-BIBHumfV.js +1 -0
  171. package/core/built/admin/assets/newsletter-detail-modal-CfjBMH3v.js +1 -0
  172. package/core/built/admin/assets/{newsletter-emails-B-OLfrvr.js → newsletter-emails-ClDL-7dA.js} +1 -1
  173. package/core/built/admin/assets/newsletters-CBe-IPzK.js +1 -0
  174. package/core/built/admin/assets/{newsletters-Du0lzskS.js → newsletters-ILsexy-5.js} +1 -1
  175. package/core/built/admin/assets/{note-CSVsUKBm.js → note-HnNUOt4d.js} +1 -1
  176. package/core/built/admin/assets/{notifications-DV9FRtDw.js → notifications-Cj5e-258.js} +1 -1
  177. package/core/built/admin/assets/nql-lang-CaWrLesS.js +19 -0
  178. package/core/built/admin/assets/{offers-Um6ab71O.js → offers-DMxebaoK.js} +1 -1
  179. package/core/built/admin/assets/offers-container-modal-BHwHtixC.js +1 -0
  180. package/core/built/admin/assets/{onboarding-route--jL5KLPD.js → onboarding-route-D_4jp7Nq.js} +1 -1
  181. package/core/built/admin/assets/overview-CezDdg-F.js +1 -0
  182. package/core/built/admin/assets/overview-D5mM51Lj.js +1 -0
  183. package/core/built/admin/assets/{page-header-DAvDYKO9.js → page-header-VLPJfj_d.js} +1 -1
  184. package/core/built/admin/assets/{pages-BVMnpI2O.js → pages-Gchxp5c7.js} +1 -1
  185. package/core/built/admin/assets/{pintura-modal-jiUCxX0Q.js → pintura-modal-BXXJ8Iaa.js} +1 -1
  186. package/core/built/admin/assets/{portal-frame-D4D4nEO6.js → portal-frame-BLCvF8f-.js} +1 -1
  187. package/core/built/admin/assets/portal-modal-DN80tAcy.js +1 -0
  188. package/core/built/admin/assets/{post-analytics-BhAtiFJp.js → post-analytics-Gv81eN_g.js} +1 -1
  189. package/core/built/admin/assets/post-analytics-header-_ydSY8qO.js +1 -0
  190. package/core/built/admin/assets/{post-analytics-provider-DGg0FUqs.js → post-analytics-provider-O2YjZMhN.js} +1 -1
  191. package/core/built/admin/assets/{post-share-modal-DtFAC132.js → post-share-modal-CzYak_a5.js} +1 -1
  192. package/core/built/admin/assets/{posts-CxG7v0-M.js → posts-DgcUi2rc.js} +1 -1
  193. package/core/built/admin/assets/preferences-gneLb6LV.js +5 -0
  194. package/core/built/admin/assets/{preview-modal-DctYx20m.js → preview-modal-Bkq-tgVm.js} +1 -1
  195. package/core/built/admin/assets/profile-CzXmCNTC.js +1 -0
  196. package/core/built/admin/assets/{radio-group-CI5_Kf_d.js → radio-group-Dsq5ZEbV.js} +1 -1
  197. package/core/built/admin/assets/{referrers-B9QDAfYm.js → referrers-C3a2K8qB.js} +1 -1
  198. package/core/built/admin/assets/{router-provider-DSQfprxH.js → router-provider-CkB_ArLF.js} +1 -1
  199. package/core/built/admin/assets/{select-CkXqNJDJ.js → select-xuZAuDyS.js} +1 -1
  200. package/core/built/admin/assets/{settings-ZCj-W56o.js → settings-Ct8pabhr.js} +1 -1
  201. package/core/built/admin/assets/settings-KSWWQanQ.js +4 -0
  202. package/core/built/admin/assets/{settings-breadcrumbs-DIq31FwA.js → settings-breadcrumbs-DbPGu-Tu.js} +1 -1
  203. package/core/built/admin/assets/{share-modal-BNj14g79.js → share-modal-CdiUx9WM.js} +1 -1
  204. package/core/built/admin/assets/{slack-modal-C6SpMQ-q.js → slack-modal-BP--Z3jE.js} +1 -1
  205. package/core/built/admin/assets/{sort-button-B-k54ow2.js → sort-button-CjfKdlsL.js} +1 -1
  206. package/core/built/admin/assets/{source-icon-Cej3sZM6.js → source-icon-DHj1h6Ts.js} +1 -1
  207. package/core/built/admin/assets/{stats-B2KW8E2y.js → stats-C615kulJ.js} +1 -1
  208. package/core/built/admin/assets/{stats-view-CVzU2-p5.js → stats-view-BSMRdYiN.js} +1 -1
  209. package/core/built/admin/assets/{step-1-Dbn1iIMU.js → step-1-pSdGYtIF.js} +1 -1
  210. package/core/built/admin/assets/{step-2-BahnSuN6.js → step-2-T9Uy4eBD.js} +1 -1
  211. package/core/built/admin/assets/{step-3-CW04e2ZJ.js → step-3-D4uXe_XG.js} +2 -2
  212. package/core/built/admin/assets/{stripe-button-BkBZqaGG.js → stripe-button-Bh4p6Wud.js} +1 -1
  213. package/core/built/admin/assets/stripe-connect-modal-C2vTvepi.js +1 -0
  214. package/core/built/admin/assets/{table-BtLcEi1M.js → table-DghJxZMH.js} +1 -1
  215. package/core/built/admin/assets/{tabs-BaWRfg8j.js → tabs-l9c7FjzI.js} +1 -1
  216. package/core/built/admin/assets/tag-detail-DBw_M2-p.js +2 -0
  217. package/core/built/admin/assets/{tags-BbygUvNB.js → tags-DoWsAG6l.js} +1 -1
  218. package/core/built/admin/assets/{tags-BwCgmr5v.js → tags-EmEvJN75.js} +1 -1
  219. package/core/built/admin/assets/{testimonials-modal--bdzppyh.js → testimonials-modal-BZKePE2T.js} +1 -1
  220. package/core/built/admin/assets/tier-detail-modal-D65pVWsN.js +1 -0
  221. package/core/built/admin/assets/{tiers-Da6kxHQP.js → tiers-8iTFjJWg.js} +1 -1
  222. package/core/built/admin/assets/{toggle-group-CVl-Xigr.js → toggle-group-uJnl42w3.js} +1 -1
  223. package/core/built/admin/assets/{topic-filter-BoRHRbL1.js → topic-filter-BM7hz0kW.js} +1 -1
  224. package/core/built/admin/assets/transistor-modal-VZOGN3od.js +1 -0
  225. package/core/built/admin/assets/{unsplash-modal-CixoRAO2.js → unsplash-modal-CKGfACxF.js} +1 -1
  226. package/core/built/admin/assets/{use-analytics-data-BiWvcf1u.js → use-analytics-data-IxJcSjc9.js} +1 -1
  227. package/core/built/admin/assets/use-check-theme-limit-error-CGp3CGT5.js +1 -0
  228. package/core/built/admin/assets/use-feature-flag-CSq2Lwzt.js +1 -0
  229. package/core/built/admin/assets/use-filter-params-0hcJJuEJ.js +1 -0
  230. package/core/built/admin/assets/{use-filterable-api-iMpDZBxD.js → use-filterable-api-CQBtVG-l.js} +1 -1
  231. package/core/built/admin/assets/{use-growth-stats-Dom8xVzo.js → use-growth-stats-1NW-8eEZ.js} +1 -1
  232. package/core/built/admin/assets/use-limiter-ffwpsZPp.js +2 -0
  233. package/core/built/admin/assets/{use-pintura-config-Bj4QmiVk.js → use-pintura-config-BjFChXV1.js} +1 -1
  234. package/core/built/admin/assets/{use-pintura-editor-DvrslmPE.js → use-pintura-editor-Cb41oJyL.js} +1 -1
  235. package/core/built/admin/assets/{use-post-referrers-pxIHTXFs.js → use-post-referrers-Dp1BnTAK.js} +1 -1
  236. package/core/built/admin/assets/{use-reply-chain-data-CmFe0N6F.js → use-reply-chain-data-C0ZUTkfk.js} +1 -1
  237. package/core/built/admin/assets/{use-setting-group-Cmp_sy-d.js → use-setting-group-Bx8M6FL2.js} +1 -1
  238. package/core/built/admin/assets/{use-settings-navigation-BmROPrls.js → use-settings-navigation-BOZcat_V.js} +1 -1
  239. package/core/built/admin/assets/{use-simple-pagination-D5rL3YwO.js → use-simple-pagination-DsEGSJhe.js} +1 -1
  240. package/core/built/admin/assets/{use-tinybird-query-DP2dMaq7.js → use-tinybird-query-Ci_DiPkX.js} +1 -1
  241. package/core/built/admin/assets/{use-tinybird-token-DrK_skPQ.js → use-tinybird-token-CsUwc722.js} +1 -1
  242. package/core/built/admin/assets/use-upgrade-route-B3_DUiuT.js +1 -0
  243. package/core/built/admin/assets/{use-url-input-ChrUZBFf.js → use-url-input-BEZJpVgN.js} +2 -2
  244. package/core/built/admin/assets/user-detail-modal-595z1N5r.js +1 -0
  245. package/core/built/admin/assets/{users-B6hIds6n.js → users-DmGMPfw7.js} +1 -1
  246. package/core/built/admin/assets/virtual-list-D5ugXyZY.js +1 -0
  247. package/core/built/admin/assets/{web-D71mjB2r.js → web-CAta3lRE.js} +1 -1
  248. package/core/built/admin/assets/web-Ds9_wWJK.js +2 -0
  249. package/core/built/admin/assets/zapier-modal-CcfDZY53.js +1 -0
  250. package/core/built/admin/index.html +12 -12
  251. package/core/frontend/apps/private-blogging/lib/middleware.js +5 -58
  252. package/core/frontend/helpers/ghost_head.js +52 -0
  253. package/core/frontend/meta/asset-url.js +20 -9
  254. package/core/frontend/public/cards.manifest.json +1 -0
  255. package/core/frontend/public/ghost-stats.min.js +1 -1
  256. package/core/frontend/services/assets-minification/card-assets.js +121 -46
  257. package/core/frontend/services/assets-minification/index.js +13 -4
  258. package/core/frontend/services/data/fetch-data.js +7 -14
  259. package/core/frontend/services/llms/service.js +27 -6
  260. package/core/frontend/services/proxy.js +1 -0
  261. package/core/frontend/services/routing/api-adapter.js +11 -5
  262. package/core/frontend/services/routing/collection-router.js +1 -2
  263. package/core/frontend/services/routing/controllers/entry/markdown.js +63 -5
  264. package/core/frontend/services/routing/controllers/entry.js +1 -1
  265. package/core/frontend/services/routing/controllers/previews.js +0 -3
  266. package/core/frontend/services/routing/events.js +2 -1
  267. package/core/frontend/services/routing/parent-router.js +0 -22
  268. package/core/frontend/services/routing/router-manager.js +2 -2
  269. package/core/frontend/services/routing/static-pages-router.js +0 -1
  270. package/core/frontend/services/sitemap/handler.js +3 -4
  271. package/core/frontend/services/sitemap/site-map-manager.js +1 -2
  272. package/core/frontend/services/theme-engine/i18n/i18n.js +0 -33
  273. package/core/frontend/web/middleware/llms-discovery.js +2 -2
  274. package/core/frontend/web/routers/serve-public-file.js +33 -3
  275. package/core/frontend/web/site.js +5 -4
  276. package/core/server/adapters/jobs/InMemoryJobsBackend.js +113 -0
  277. package/core/server/adapters/redirects/S3RedirectsStore.js +33 -13
  278. package/core/server/adapters/route-settings/S3RouteSettingsStore.js +33 -13
  279. package/core/server/adapters/scheduling/scheduling-default.js +6 -7
  280. package/core/server/adapters/storage/LocalStorageBase.js +1 -3
  281. package/core/server/api/endpoints/index.js +0 -4
  282. package/core/server/api/endpoints/member-custom-fields.js +15 -4
  283. package/core/server/api/endpoints/pages.js +3 -11
  284. package/core/server/api/endpoints/posts.js +3 -11
  285. package/core/server/api/endpoints/utils/api-filter-utils.js +15 -1
  286. package/core/server/api/endpoints/utils/serializers/input/settings.js +4 -0
  287. package/core/server/api/endpoints/utils/serializers/output/index.js +0 -4
  288. package/core/server/api/endpoints/utils/serializers/output/mappers/email-batches.js +1 -1
  289. package/core/server/api/endpoints/utils/serializers/output/mappers/snippets.js +0 -1
  290. package/core/server/api/endpoints/utils/serializers/output/member-custom-fields.js +1 -0
  291. package/core/server/data/exporter/table-lists.js +4 -1
  292. package/core/server/data/migrations/utils/schema.js +5 -3
  293. package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +19 -7
  294. package/core/server/data/migrations/versions/6.58/2026-08-10-08-20-50-add-machine-payments-settings.js +41 -0
  295. package/core/server/data/migrations/versions/6.58/2026-08-10-08-21-00-add-machine-payment-events-table.js +18 -0
  296. package/core/server/data/migrations/versions/6.58/2026-08-10-15-24-09-reset-custom-field-definitions-for-underscored-keys.js +66 -0
  297. package/core/server/data/migrations/versions/6.58/2026-08-11-10-00-00-swap-custom-field-values-to-key-fk.js +65 -0
  298. package/core/server/data/migrations/versions/6.58/2026-08-11-10-34-25-remove-legacy-explore-integration.js +131 -0
  299. package/core/server/data/migrations/versions/6.58/2026-08-11-16-00-00-add-sort-order-to-members-custom-fields.js +13 -0
  300. package/core/server/data/migrations/versions/6.58/2026-08-11-21-22-48-rename-email-batches-provider-id.js +8 -0
  301. package/core/server/data/migrations/versions/6.58/2026-08-13-16-07-33-add-automation-runs-automation-id-created-at-index.js +28 -0
  302. package/core/server/data/schema/README.md +87 -0
  303. package/core/server/data/schema/commands.js +5 -2
  304. package/core/server/data/schema/default-settings/default-settings.json +27 -0
  305. package/core/server/data/schema/fixtures/fixtures.json +0 -21
  306. package/core/server/data/schema/schema.js +37 -9
  307. package/core/server/data/seeders/data-generator.js +3 -1
  308. package/core/server/data/seeders/importers/automation-action-edges-importer.js +38 -0
  309. package/core/server/data/seeders/importers/automation-action-revisions-importer.js +139 -0
  310. package/core/server/data/seeders/importers/automation-actions-importer.js +84 -0
  311. package/core/server/data/seeders/importers/automation-run-steps-importer.js +134 -0
  312. package/core/server/data/seeders/importers/automation-runs-importer.js +97 -0
  313. package/core/server/data/seeders/importers/automations-importer.js +78 -0
  314. package/core/server/data/seeders/importers/benefits-importer.js +1 -1
  315. package/core/server/data/seeders/importers/comment-reports-importer.js +5 -5
  316. package/core/server/data/seeders/importers/comments-importer.js +6 -6
  317. package/core/server/data/seeders/importers/email-batches-importer.js +9 -8
  318. package/core/server/data/seeders/importers/email-recipient-failures-importer.js +1 -1
  319. package/core/server/data/seeders/importers/email-recipients-importer.js +12 -12
  320. package/core/server/data/seeders/importers/emails-importer.js +7 -7
  321. package/core/server/data/seeders/importers/index.js +6 -0
  322. package/core/server/data/seeders/importers/labels-importer.js +4 -4
  323. package/core/server/data/seeders/importers/members-click-events-importer.js +4 -4
  324. package/core/server/data/seeders/importers/members-created-events-importer.js +5 -5
  325. package/core/server/data/seeders/importers/members-feedback-importer.js +7 -7
  326. package/core/server/data/seeders/importers/members-importer.js +5 -5
  327. package/core/server/data/seeders/importers/members-labels-importer.js +1 -1
  328. package/core/server/data/seeders/importers/members-login-events-importer.js +4 -4
  329. package/core/server/data/seeders/importers/members-newsletters-importer.js +1 -1
  330. package/core/server/data/seeders/importers/members-paid-subscription-events-importer.js +1 -1
  331. package/core/server/data/seeders/importers/members-products-importer.js +3 -3
  332. package/core/server/data/seeders/importers/members-status-events-importer.js +4 -4
  333. package/core/server/data/seeders/importers/members-stripe-customers-importer.js +3 -3
  334. package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +8 -8
  335. package/core/server/data/seeders/importers/members-subscribe-events-importer.js +3 -3
  336. package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +4 -4
  337. package/core/server/data/seeders/importers/newsletters-importer.js +1 -1
  338. package/core/server/data/seeders/importers/offer-redemptions-importer.js +6 -6
  339. package/core/server/data/seeders/importers/offers-importer.js +4 -4
  340. package/core/server/data/seeders/importers/posts-authors-importer.js +1 -1
  341. package/core/server/data/seeders/importers/posts-importer.js +5 -5
  342. package/core/server/data/seeders/importers/posts-products-importer.js +1 -1
  343. package/core/server/data/seeders/importers/posts-tags-importer.js +1 -1
  344. package/core/server/data/seeders/importers/products-benefits-importer.js +1 -1
  345. package/core/server/data/seeders/importers/products-importer.js +1 -1
  346. package/core/server/data/seeders/importers/recommendation-click-events-importer.js +1 -1
  347. package/core/server/data/seeders/importers/recommendation-subscribe-events-importer.js +1 -1
  348. package/core/server/data/seeders/importers/recommendations-importer.js +1 -1
  349. package/core/server/data/seeders/importers/redirects-importer.js +1 -1
  350. package/core/server/data/seeders/importers/roles-users-importer.js +1 -1
  351. package/core/server/data/seeders/importers/stripe-prices-importer.js +1 -1
  352. package/core/server/data/seeders/importers/stripe-products-importer.js +1 -1
  353. package/core/server/data/seeders/importers/table-importer.js +92 -91
  354. package/core/server/data/seeders/importers/tags-importer.js +3 -3
  355. package/core/server/data/seeders/importers/users-importer.js +3 -3
  356. package/core/server/data/seeders/importers/web-mentions-importer.js +3 -3
  357. package/core/server/data/seeders/utils/database-date.js +14 -18
  358. package/core/server/data/seeders/utils/json-importer.js +1 -1
  359. package/core/server/data/seeders/utils/random.js +8 -7
  360. package/core/server/data/seeders/utils/topological-sort.js +5 -3
  361. package/core/server/data/tinybird/ARCHITECTURE.md +1 -2
  362. package/core/server/models/base/plugins/crud.js +0 -5
  363. package/core/server/models/index.js +2 -0
  364. package/core/server/models/machine-payment-event.js +18 -0
  365. package/core/server/models/member.js +24 -0
  366. package/core/server/models/settings.js +32 -3
  367. package/core/server/models/user.js +1 -1
  368. package/core/server/services/README.md +30 -0
  369. package/core/server/services/activitypub/activity-pub-service.js +2 -3
  370. package/core/server/services/adapter-manager/base-classes.js +3 -1
  371. package/core/server/services/auth/passwordreset.js +47 -51
  372. package/core/server/services/automations/database-automations-repository.js +19 -3
  373. package/core/server/services/automations/poll.js +2 -8
  374. package/core/server/services/comments/comments-service.js +4 -16
  375. package/core/server/services/comments/index.js +1 -3
  376. package/core/server/services/email-analytics/email-analytics-service-wrapper.js +68 -119
  377. package/core/server/services/email-analytics/email-analytics-service.js +73 -229
  378. package/core/server/services/email-analytics/event-processing-result.js +0 -10
  379. package/core/server/services/email-analytics/fetch-mailgun-events.js +25 -0
  380. package/core/server/services/email-analytics/index.js +35 -67
  381. package/core/server/services/email-analytics/lib/queries.js +88 -51
  382. package/core/server/services/email-analytics/newsletter-email-analytics-batch-processor.js +4 -1
  383. package/core/server/services/email-service/batch-sending-service.js +1 -1
  384. package/core/server/services/email-service/email-event-processor.js +12 -12
  385. package/core/server/services/email-service/events/email-bounced-event.js +8 -46
  386. package/core/server/services/email-service/events/email-delivered-event.js +8 -36
  387. package/core/server/services/email-service/events/email-opened-event.js +8 -36
  388. package/core/server/services/email-service/events/email-temporary-bounced-event.js +8 -46
  389. package/core/server/services/email-service/events/email-unsubscribed-event.js +8 -31
  390. package/core/server/services/email-service/events/spam-complaint-event.js +8 -31
  391. package/core/server/services/email-service/newsletter-email-event-storage.js +8 -7
  392. package/core/server/services/email-service/sending-service.js +1 -1
  393. package/core/server/services/email-suppression-list/mailgun-email-suppression-list.js +2 -2
  394. package/core/server/services/email-suppression-list/service.js +1 -3
  395. package/core/server/services/explore-ping/explore-ping-service.js +1 -6
  396. package/core/server/services/explore-ping/index.js +0 -2
  397. package/core/server/services/gifts/index.js +109 -3
  398. package/core/server/services/gifts/jobs/clean-gifts-job.js +1 -1
  399. package/core/server/services/gifts/jobs/send-gift-reminders-job.js +1 -1
  400. package/core/server/services/jobs-service/index.js +36 -0
  401. package/core/server/services/jobs-service/job.js +6 -0
  402. package/core/server/services/jobs-service/jobs-service.js +91 -0
  403. package/core/server/services/jobs-service/register-job-handlers.js +6 -0
  404. package/core/server/services/lib/mailgun-client.js +1 -1
  405. package/core/server/services/machine-payments/README.md +23 -0
  406. package/core/server/services/machine-payments/adapters/mpp-adapter.js +195 -0
  407. package/core/server/services/machine-payments/content-loader.js +89 -0
  408. package/core/server/services/machine-payments/eligibility.js +6 -0
  409. package/core/server/services/machine-payments/events/machine-payment-event-repository.js +58 -0
  410. package/core/server/services/machine-payments/events/machine-payment-event.js +46 -0
  411. package/core/server/services/machine-payments/index.js +87 -0
  412. package/core/server/services/machine-payments/pricing.js +54 -0
  413. package/core/server/services/machine-payments/service.js +275 -0
  414. package/core/server/services/machine-payments/stripe/deposit-address-store.js +157 -0
  415. package/core/server/services/machine-payments/stripe/payment-recorder.js +85 -0
  416. package/core/server/services/machine-payments/stripe/stripe-client-options.js +25 -0
  417. package/core/server/services/machine-payments/types.js +2 -0
  418. package/core/server/services/members/import-export/csv/parse.js +9 -1
  419. package/core/server/services/members/import-export/import/completion-email.js +61 -27
  420. package/core/server/services/members/import-export/import/email-template.js +28 -15
  421. package/core/server/services/members/import-export/import/importer.js +99 -51
  422. package/core/server/services/members/import-export/import/spool.js +1 -1
  423. package/core/server/services/members/import-export/index.js +24 -1
  424. package/core/server/services/members/members-api/services/member-bread-service.js +2 -2
  425. package/core/server/services/members/members-config-provider.js +12 -2
  426. package/core/server/services/members-custom-fields/actions.js +2 -1
  427. package/core/server/services/members-custom-fields/definitions-service.js +109 -27
  428. package/core/server/services/members-custom-fields/filter.js +199 -0
  429. package/core/server/services/members-custom-fields/key.js +37 -0
  430. package/core/server/services/members-custom-fields/queries.js +15 -0
  431. package/core/server/services/members-custom-fields/schema.js +1 -1
  432. package/core/server/services/members-custom-fields/values-service.js +18 -14
  433. package/core/server/services/members-events/last-seen-at-updater.js +1 -1
  434. package/core/server/services/oembed/oembed-service.js +8 -2
  435. package/core/server/services/permissions/can-this.js +4 -1
  436. package/core/server/services/posts/posts-service.js +18 -16
  437. package/core/server/services/public-config/config.js +1 -1
  438. package/core/server/services/route-settings/dynamic-routing-service.js +2 -0
  439. package/core/server/services/settings-helpers/settings-helpers.js +0 -1
  440. package/core/server/services/stats/posts-stats-service.js +0 -2
  441. package/core/server/services/stripe/stripe-api.js +2 -9
  442. package/core/server/web/api/endpoints/admin/middleware.js +0 -1
  443. package/core/server/web/api/endpoints/admin/routes.js +2 -3
  444. package/core/server/web/comments/routes.js +31 -8
  445. package/core/shared/config/defaults.json +17 -2
  446. package/core/shared/config/loader.js +11 -2
  447. package/core/shared/config/overrides.json +2 -0
  448. package/core/shared/config/secrets.js +71 -0
  449. package/core/shared/labs.js +4 -13
  450. package/core/shared/machine-payments.js +30 -0
  451. package/core/shared/private-site-access/index.js +68 -0
  452. package/package.json +48 -43
  453. package/pnpm-lock.yaml +1412 -1583
  454. package/pnpm-workspace.yaml +85 -48
  455. package/bin/validate-adapters.ts +0 -87
  456. package/components/tryghost-adapter-base-scheduling-0.2.1.tgz +0 -0
  457. package/components/tryghost-adapter-base-sso-0.1.1.tgz +0 -0
  458. package/components/tryghost-kg-card-factory-5.2.3.tgz +0 -0
  459. package/components/tryghost-kg-clean-basic-html-4.3.3.tgz +0 -0
  460. package/components/tryghost-kg-converters-1.2.4.tgz +0 -0
  461. package/components/tryghost-kg-default-cards-10.3.4.tgz +0 -0
  462. package/components/tryghost-kg-default-nodes-2.1.5.tgz +0 -0
  463. package/components/tryghost-kg-default-transforms-1.3.3.tgz +0 -0
  464. package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
  465. package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
  466. package/components/tryghost-kg-markdown-html-renderer-7.2.3.tgz +0 -0
  467. package/components/tryghost-kg-utils-1.1.3.tgz +0 -0
  468. package/content/themes/casper/assets/built/casper.js.map +0 -1
  469. package/content/themes/casper/assets/built/global.css.map +0 -1
  470. package/content/themes/casper/assets/built/screen.css.map +0 -1
  471. package/content/themes/source/assets/built/screen.css.map +0 -1
  472. package/content/themes/source/assets/built/source.js.map +0 -1
  473. package/core/built/admin/assets/activitypub/layout-DM_3pqsU.js +0 -1
  474. package/core/built/admin/assets/add-integration-modal-DU9ghGLS.js +0 -1
  475. package/core/built/admin/assets/add-newsletter-modal-DFxuQrYu.js +0 -1
  476. package/core/built/admin/assets/announcement-bar-modal-DGCcezB6.js +0 -1
  477. package/core/built/admin/assets/card-aUHX0hyR.js +0 -1
  478. package/core/built/admin/assets/chunk.524.dce1c51e0bc30fb2718e.js +0 -25
  479. package/core/built/admin/assets/chunk.582.3a430d5c6a744fd475b6.js +0 -27
  480. package/core/built/admin/assets/code-editor-view-BcyKABrG.js +0 -1
  481. package/core/built/admin/assets/color-picker-field-8NoSnhNI.js +0 -1
  482. package/core/built/admin/assets/command-BOYMbZa_.js +0 -1
  483. package/core/built/admin/assets/comments-BhNM5CYl.js +0 -1
  484. package/core/built/admin/assets/confirmation-modal-CYD7x3UQ.js +0 -1
  485. package/core/built/admin/assets/custom-integration-modal-bk3lNEuQ.js +0 -1
  486. package/core/built/admin/assets/design-and-theme-modal-B2xYKS1I.js +0 -23
  487. package/core/built/admin/assets/dist-Bg8bSvLA.js +0 -6
  488. package/core/built/admin/assets/dist-BnFdlebk.js +0 -11
  489. package/core/built/admin/assets/dist-CsvXCJSe.js +0 -23
  490. package/core/built/admin/assets/editor-BBKrunMK.js +0 -8
  491. package/core/built/admin/assets/filters-Bz0HsRie.js +0 -1
  492. package/core/built/admin/assets/framework-provider-CmtnPXIT.js +0 -4
  493. package/core/built/admin/assets/index-DUXM0NlS.js +0 -67
  494. package/core/built/admin/assets/index-Ds2iUoF6.css +0 -1
  495. package/core/built/admin/assets/label-picker-Bnlru5tO.js +0 -1
  496. package/core/built/admin/assets/layout-yR9rlvEw.js +0 -1
  497. package/core/built/admin/assets/limit-modal-COmLevuY.js +0 -1
  498. package/core/built/admin/assets/limit-service-9fwD33lU.js +0 -20
  499. package/core/built/admin/assets/member-custom-fields-BvFKEpxJ.js +0 -1
  500. package/core/built/admin/assets/member-detail-CWdPAe3p.js +0 -1
  501. package/core/built/admin/assets/members-Bnt0ih2R.js +0 -11
  502. package/core/built/admin/assets/newsletter-DR49NyGS.js +0 -1
  503. package/core/built/admin/assets/newsletter-detail-modal-B45pL1pb.js +0 -1
  504. package/core/built/admin/assets/newsletters-DGs5BaKH.js +0 -1
  505. package/core/built/admin/assets/nql-lang-B7faUf8D.js +0 -19
  506. package/core/built/admin/assets/offers-container-modal-B0hFS8nk.js +0 -1
  507. package/core/built/admin/assets/overview-B-ZPNqMo.js +0 -1
  508. package/core/built/admin/assets/overview-D96iwoJi.js +0 -1
  509. package/core/built/admin/assets/portal-modal-BLFdYeDH.js +0 -1
  510. package/core/built/admin/assets/post-analytics-header-DK27VjJm.js +0 -1
  511. package/core/built/admin/assets/preferences-CuWLISHa.js +0 -5
  512. package/core/built/admin/assets/profile-BsKYYtuL.js +0 -1
  513. package/core/built/admin/assets/settings-B4vM-rgV.js +0 -4
  514. package/core/built/admin/assets/stripe-connect-modal-CYDDrSJm.js +0 -1
  515. package/core/built/admin/assets/tag-detail-CmqiC32L.js +0 -1
  516. package/core/built/admin/assets/tier-detail-modal-BZmAsIy4.js +0 -1
  517. package/core/built/admin/assets/transistor-modal-CBPDqEGy.js +0 -1
  518. package/core/built/admin/assets/use-check-theme-limit-error-B14lZukR.js +0 -2
  519. package/core/built/admin/assets/use-feature-flag-BmJoc5WN.js +0 -1
  520. package/core/built/admin/assets/use-filter-params-ChoV72Hw.js +0 -1
  521. package/core/built/admin/assets/use-limiter-Dwg8ivgQ.js +0 -2
  522. package/core/built/admin/assets/user-detail-modal-XODpOqNC.js +0 -1
  523. package/core/built/admin/assets/virtual-list-Dnoafqbk.js +0 -1
  524. package/core/built/admin/assets/web-z71lA1Y3.js +0 -2
  525. package/core/built/admin/assets/zapier-modal-Bp507Um7.js +0 -1
  526. package/core/frontend/meta/og-type.ts +0 -13
  527. package/core/frontend/services/routing/api-adapter.ts +0 -157
  528. package/core/frontend/services/routing/config.ts +0 -59
  529. package/core/frontend/services/routing/controllers/entry/canonical-url.ts +0 -14
  530. package/core/frontend/services/routing/controllers/entry/gift-links.ts +0 -56
  531. package/core/frontend/services/routing/controllers/entry/markdown.ts +0 -72
  532. package/core/frontend/services/routing/controllers/entry.ts +0 -120
  533. package/core/frontend/services/routing/controllers/rss.ts +0 -66
  534. package/core/frontend/services/routing/permalink-adapter.ts +0 -10
  535. package/core/frontend/src/utils/privacy.ts +0 -66
  536. package/core/server/adapters/redirects/FileStore.ts +0 -113
  537. package/core/server/adapters/redirects/S3RedirectsStore.ts +0 -185
  538. package/core/server/adapters/route-settings/FileStore.ts +0 -144
  539. package/core/server/adapters/route-settings/S3RouteSettingsStore.ts +0 -199
  540. package/core/server/adapters/route-settings/utils.ts +0 -17
  541. package/core/server/adapters/scheduling/deps.d.ts +0 -1
  542. package/core/server/adapters/scheduling/error-capture.ts +0 -91
  543. package/core/server/adapters/scheduling/scheduling-default.ts +0 -248
  544. package/core/server/adapters/scheduling/utils.ts +0 -36
  545. package/core/server/adapters/sso/DefaultSSOAdapter.ts +0 -15
  546. package/core/server/adapters/storage/LocalFilesStorage.ts +0 -24
  547. package/core/server/adapters/storage/LocalImagesStorage.ts +0 -24
  548. package/core/server/adapters/storage/LocalMediaStorage.ts +0 -24
  549. package/core/server/adapters/storage/LocalStorageBase.ts +0 -342
  550. package/core/server/adapters/storage/S3Storage.ts +0 -506
  551. package/core/server/adapters/storage/utils.ts +0 -60
  552. package/core/server/api/endpoints/automation-action-links.ts +0 -46
  553. package/core/server/api/endpoints/explore.js +0 -18
  554. package/core/server/api/endpoints/gift-links.ts +0 -87
  555. package/core/server/api/endpoints/libraries.d.ts +0 -2
  556. package/core/server/api/endpoints/member-commenting.ts +0 -92
  557. package/core/server/api/endpoints/member-custom-fields.ts +0 -106
  558. package/core/server/api/endpoints/utils/api-filter-utils.ts +0 -22
  559. package/core/server/api/endpoints/utils/gift-link-access.ts +0 -82
  560. package/core/server/api/endpoints/utils/public-cache-keys.ts +0 -47
  561. package/core/server/api/endpoints/utils/serializers/input/member-commenting.ts +0 -29
  562. package/core/server/api/endpoints/utils/serializers/output/explore.js +0 -11
  563. package/core/server/api/endpoints/utils/serializers/output/gift-links.ts +0 -21
  564. package/core/server/api/endpoints/utils/serializers/output/member-commenting.ts +0 -8
  565. package/core/server/api/endpoints/utils/serializers/output/member-custom-fields.ts +0 -25
  566. package/core/server/api/endpoints/utils/serializers/output/members.d.ts +0 -20
  567. package/core/server/api/endpoints/utils/serializers/output/posts-csv-transform.ts +0 -46
  568. package/core/server/api/endpoints/utils/serializers/output/stream-csv-response.ts +0 -47
  569. package/core/server/api/endpoints/utils/validate-email-sender-fields.ts +0 -42
  570. package/core/server/data/importer/email-template.ts +0 -213
  571. package/core/server/data/seeders/utils/topological-sort.ts +0 -48
  572. package/core/server/lib/auto-filling-map.ts +0 -23
  573. package/core/server/lib/bootstrap-socket.ts +0 -114
  574. package/core/server/lib/case-keys.ts +0 -30
  575. package/core/server/lib/common/to-plain.ts +0 -25
  576. package/core/server/lib/db-date.ts +0 -8
  577. package/core/server/lib/get-inbox-links.ts +0 -276
  578. package/core/server/lib/post-revisions.ts +0 -152
  579. package/core/server/lib/soonest-timer.ts +0 -57
  580. package/core/server/models/base/plugins/bulk-filters.ts +0 -35
  581. package/core/server/models/role-utils.ts +0 -56
  582. package/core/server/services/activitypub/activity-pub-service.ts +0 -216
  583. package/core/server/services/adapter-manager/adapter-manager.ts +0 -253
  584. package/core/server/services/adapter-manager/adapter-paths.ts +0 -26
  585. package/core/server/services/adapter-manager/base-classes.ts +0 -22
  586. package/core/server/services/adapter-manager/index.ts +0 -16
  587. package/core/server/services/adapter-manager/types.ts +0 -45
  588. package/core/server/services/adapter-manager/utils.ts +0 -308
  589. package/core/server/services/auth/reset-authentication.ts +0 -72
  590. package/core/server/services/automations/automations-api.ts +0 -396
  591. package/core/server/services/automations/automations-repository.ts +0 -218
  592. package/core/server/services/automations/constants.ts +0 -4
  593. package/core/server/services/automations/database-automations-repository.ts +0 -1513
  594. package/core/server/services/automations/database-date.ts +0 -26
  595. package/core/server/services/automations/fake-wait-hours-multiplier.ts +0 -12
  596. package/core/server/services/automations/get-scheduler-idempotency-key.ts +0 -8
  597. package/core/server/services/automations/mailgun-message-id.ts +0 -27
  598. package/core/server/services/automations/poll.ts +0 -325
  599. package/core/server/services/automations/scheduler-poll-time.ts +0 -38
  600. package/core/server/services/automations/service.ts +0 -131
  601. package/core/server/services/automations/stale-lock-cutoff.ts +0 -5
  602. package/core/server/services/comments/email-templates/new-comment-reply.txt.ts +0 -25
  603. package/core/server/services/comments/email-templates/new-comment.txt.ts +0 -25
  604. package/core/server/services/comments/email-templates/report.txt.ts +0 -34
  605. package/core/server/services/custom-redirects/redirect-config-parser.ts +0 -148
  606. package/core/server/services/custom-redirects/redirects-service.ts +0 -181
  607. package/core/server/services/custom-redirects/utils.ts +0 -7
  608. package/core/server/services/donations/donation-bookshelf-repository.ts +0 -63
  609. package/core/server/services/donations/donation-payment-event.ts +0 -51
  610. package/core/server/services/email-address/email-address-parser.d.ts +0 -12
  611. package/core/server/services/email-address/email-address-service.ts +0 -241
  612. package/core/server/services/email-analytics/automation-email-analytics-batch-processor.ts +0 -130
  613. package/core/server/services/email-analytics/batch-event-processor.ts +0 -18
  614. package/core/server/services/email-analytics/email-analytics-provider-mailgun.js +0 -57
  615. package/core/server/services/email-analytics/event-processing-result.ts +0 -66
  616. package/core/server/services/email-analytics/events/start-automation-email-analytics-job-event.ts +0 -14
  617. package/core/server/services/email-analytics/events/start-email-analytics-job-event.ts +0 -15
  618. package/core/server/services/email-analytics/index.ts +0 -109
  619. package/core/server/services/email-analytics/jobs/email-analytics-job-scheduler.ts +0 -126
  620. package/core/server/services/email-analytics/lib/queries.ts +0 -341
  621. package/core/server/services/email-service/domain-warming-service.ts +0 -91
  622. package/core/server/services/explore/explore-service.js +0 -59
  623. package/core/server/services/explore/index.js +0 -18
  624. package/core/server/services/explore-ping/explore-ping-service.ts +0 -198
  625. package/core/server/services/explore-ping/index.ts +0 -42
  626. package/core/server/services/files/file-type-utils.ts +0 -56
  627. package/core/server/services/gift-links/actions.ts +0 -51
  628. package/core/server/services/gift-links/codec.ts +0 -16
  629. package/core/server/services/gift-links/index.ts +0 -20
  630. package/core/server/services/gift-links/models.ts +0 -22
  631. package/core/server/services/gift-links/schema.ts +0 -33
  632. package/core/server/services/gift-links/serializers.ts +0 -20
  633. package/core/server/services/gift-links/service.ts +0 -95
  634. package/core/server/services/gifts/constants.ts +0 -3
  635. package/core/server/services/gifts/email-templates/gift-purchase-confirmation.ts +0 -43
  636. package/core/server/services/gifts/email-templates/gift-reminder.ts +0 -50
  637. package/core/server/services/gifts/gift-bookshelf-repository.ts +0 -366
  638. package/core/server/services/gifts/gift-checkout-offer.ts +0 -127
  639. package/core/server/services/gifts/gift-codec.ts +0 -62
  640. package/core/server/services/gifts/gift-controller.ts +0 -51
  641. package/core/server/services/gifts/gift-email-renderer.ts +0 -97
  642. package/core/server/services/gifts/gift-email-service.ts +0 -158
  643. package/core/server/services/gifts/gift-reminder-scheduler.ts +0 -107
  644. package/core/server/services/gifts/gift-schema.ts +0 -39
  645. package/core/server/services/gifts/gift-service-wrapper.js +0 -142
  646. package/core/server/services/gifts/gift-service.ts +0 -1034
  647. package/core/server/services/gifts/gift.ts +0 -234
  648. package/core/server/services/identity-tokens/identity-token-service.ts +0 -36
  649. package/core/server/services/indexnow-ping/constants.ts +0 -32
  650. package/core/server/services/indexnow-ping/index.ts +0 -35
  651. package/core/server/services/indexnow-ping/indexnow-ping-service.ts +0 -311
  652. package/core/server/services/internal-keys/index.ts +0 -53
  653. package/core/server/services/lib/dynamic-redirect-manager.d.ts +0 -20
  654. package/core/server/services/lib/in-memory-repository.ts +0 -78
  655. package/core/server/services/lib/member-signup-contexts.ts +0 -22
  656. package/core/server/services/link-redirection/link-redirect.ts +0 -24
  657. package/core/server/services/member-welcome-emails/constants.ts +0 -35
  658. package/core/server/services/members/commenting/index.ts +0 -2
  659. package/core/server/services/members/commenting/member-commenting-codec.ts +0 -73
  660. package/core/server/services/members/commenting/member-commenting.ts +0 -69
  661. package/core/server/services/members/create-paid-member-shim.ts +0 -40
  662. package/core/server/services/members/import-export/config.ts +0 -34
  663. package/core/server/services/members/import-export/csv/formula.ts +0 -14
  664. package/core/server/services/members/import-export/csv/index.ts +0 -3
  665. package/core/server/services/members/import-export/csv/parse.ts +0 -69
  666. package/core/server/services/members/import-export/csv/serialize.ts +0 -18
  667. package/core/server/services/members/import-export/export/exporter.ts +0 -325
  668. package/core/server/services/members/import-export/import/completion-email.ts +0 -146
  669. package/core/server/services/members/import-export/import/importer.ts +0 -489
  670. package/core/server/services/members/import-export/import/reader.ts +0 -33
  671. package/core/server/services/members/import-export/import/row.ts +0 -56
  672. package/core/server/services/members/import-export/import/spool.ts +0 -38
  673. package/core/server/services/members/import-export/index.ts +0 -143
  674. package/core/server/services/members/index.d.ts +0 -32
  675. package/core/server/services/members-custom-fields/actions.ts +0 -62
  676. package/core/server/services/members-custom-fields/codec.ts +0 -13
  677. package/core/server/services/members-custom-fields/config.ts +0 -38
  678. package/core/server/services/members-custom-fields/definitions-service.ts +0 -413
  679. package/core/server/services/members-custom-fields/index.ts +0 -50
  680. package/core/server/services/members-custom-fields/models.ts +0 -15
  681. package/core/server/services/members-custom-fields/queries.ts +0 -13
  682. package/core/server/services/members-custom-fields/schema.ts +0 -71
  683. package/core/server/services/members-custom-fields/serializers.ts +0 -22
  684. package/core/server/services/members-custom-fields/storage.ts +0 -122
  685. package/core/server/services/members-custom-fields/values-service.ts +0 -269
  686. package/core/server/services/notifications/notification-email.ts +0 -66
  687. package/core/server/services/notifications/sanitize-email-html.ts +0 -35
  688. package/core/server/services/post-scheduling/index.ts +0 -13
  689. package/core/server/services/post-scheduling/post-scheduling.ts +0 -133
  690. package/core/server/services/recommendations/service/bookshelf-click-event-repository.ts +0 -49
  691. package/core/server/services/recommendations/service/bookshelf-recommendation-repository.ts +0 -117
  692. package/core/server/services/recommendations/service/bookshelf-repository.ts +0 -192
  693. package/core/server/services/recommendations/service/bookshelf-subscribe-event-repository.ts +0 -49
  694. package/core/server/services/recommendations/service/click-event.ts +0 -32
  695. package/core/server/services/recommendations/service/in-memory-recommendation-repository.ts +0 -20
  696. package/core/server/services/recommendations/service/incoming-recommendation-controller.ts +0 -51
  697. package/core/server/services/recommendations/service/incoming-recommendation-email-renderer.ts +0 -37
  698. package/core/server/services/recommendations/service/incoming-recommendation-service.ts +0 -159
  699. package/core/server/services/recommendations/service/index.ts +0 -15
  700. package/core/server/services/recommendations/service/libraries.d.ts +0 -2
  701. package/core/server/services/recommendations/service/recommendation-controller.ts +0 -257
  702. package/core/server/services/recommendations/service/recommendation-metadata-service.ts +0 -132
  703. package/core/server/services/recommendations/service/recommendation-repository.ts +0 -13
  704. package/core/server/services/recommendations/service/recommendation-service.ts +0 -285
  705. package/core/server/services/recommendations/service/recommendation.ts +0 -200
  706. package/core/server/services/recommendations/service/subscribe-event.ts +0 -32
  707. package/core/server/services/recommendations/service/unsafe-data.ts +0 -217
  708. package/core/server/services/recommendations/service/well-known-service.ts +0 -47
  709. package/core/server/services/remote-flags/index.ts +0 -101
  710. package/core/server/services/remote-flags/remote-flags-service.ts +0 -236
  711. package/core/server/services/remote-flags/resolve.ts +0 -108
  712. package/core/server/services/route-settings/route-settings-parser.ts +0 -455
  713. package/core/server/services/route-settings/validation-errors.ts +0 -271
  714. package/core/server/services/route-settings/yaml-parser.d.ts +0 -4
  715. package/core/server/services/slack-ping/constants.ts +0 -15
  716. package/core/server/services/slack-ping/index.ts +0 -35
  717. package/core/server/services/slack-ping/slack-ping-service.ts +0 -295
  718. package/core/server/services/staff/email-templates/donation.txt.ts +0 -26
  719. package/core/server/services/staff/email-templates/gift.txt.ts +0 -27
  720. package/core/server/services/staff/email-templates/new-free-signup.txt.ts +0 -15
  721. package/core/server/services/staff/email-templates/new-gift-subscription.txt.ts +0 -26
  722. package/core/server/services/staff/email-templates/new-milestone-received.txt.ts +0 -20
  723. package/core/server/services/staff/email-templates/new-paid-cancellation.txt.ts +0 -15
  724. package/core/server/services/staff/email-templates/new-paid-started.txt.ts +0 -15
  725. package/core/server/services/staff/email-templates/recommendation-received.txt.ts +0 -23
  726. package/core/server/services/staff/email-templates/types.ts +0 -13
  727. package/core/server/services/url/lazy-find-resource.ts +0 -104
  728. package/core/server/services/url/lazy-url-service.ts +0 -647
  729. package/core/server/services/url/permalink-matcher.ts +0 -119
  730. package/core/server/services/url/router-filter.ts +0 -80
  731. package/core/server/services/verification/verification-webhook-service.ts +0 -137
  732. package/core/shared/config/helpers.ts +0 -133
  733. package/core/shared/config/index.ts +0 -12
  734. package/core/shared/config/loader.ts +0 -87
  735. package/core/shared/config/utils.ts +0 -104
  736. package/core/shared/errify.ts +0 -31
  737. package/core/shared/events-ts/index.ts +0 -8
  738. package/core/shared/events-ts/post-deleted-event.ts +0 -15
  739. package/core/shared/events-ts/posts-bulk-add-tags-event.ts +0 -13
  740. package/core/shared/events-ts/posts-bulk-destroyed-event.ts +0 -13
  741. package/core/shared/events-ts/posts-bulk-featured-event.ts +0 -13
  742. package/core/shared/events-ts/posts-bulk-unfeatured-event.ts +0 -13
  743. package/core/shared/events-ts/posts-bulk-unpublished-event.ts +0 -13
  744. package/core/shared/events-ts/posts-bulk-unscheduled-event.ts +0 -13
  745. package/core/shared/labs-flag-overrides.ts +0 -35
  746. package/core/shared/one-at-a-time.ts +0 -47
  747. package/core/shared/url-utils.ts +0 -20
@@ -1,1034 +0,0 @@
1
- import crypto from 'node:crypto';
2
- import errors from '@tryghost/errors';
3
- import logging from '@tryghost/logging';
4
- import type {Knex} from 'knex';
5
- import {z} from 'zod';
6
- import {Gift} from './gift';
7
- import type {GiftEventBrowseOptions, GiftEventPage, GiftRepository} from './gift-bookshelf-repository';
8
- import type {GiftReminderScheduler} from './gift-reminder-scheduler';
9
- import {GiftCadenceSchema, type GiftCadence} from './gift-schema';
10
- import tpl from '@tryghost/tpl';
11
- import {GIFT_REMINDER_FLOOR_DAYS, GIFT_REMINDER_LEAD_DAYS} from './constants';
12
- import {
13
- resolveGiftDuration,
14
- validateGiftCheckoutOffer,
15
- type GiftCheckoutTier,
16
- type ResolvedGiftDuration
17
- } from './gift-checkout-offer';
18
-
19
- const MS_PER_DAY = 24 * 60 * 60 * 1000;
20
- const GIFT_REMINDER_LEAD_MS = GIFT_REMINDER_LEAD_DAYS * MS_PER_DAY;
21
- const GIFT_REMINDER_FLOOR_MS = GIFT_REMINDER_FLOOR_DAYS * MS_PER_DAY;
22
-
23
- const errorMessages = {
24
- giftNotFound: 'This gift does not exist.',
25
- giftAlreadyRedeemed: 'This gift has already been redeemed.',
26
- giftConsumed: 'This gift has already been consumed.',
27
- giftExpired: 'This gift has expired.',
28
- giftRefunded: 'This gift has been refunded.',
29
- paidMember: 'You already have an active subscription.',
30
- giftInvalidReassignStatus: 'This gift does not have a reassignable status.',
31
- giftInvalidReassignMember: 'Member already has an active subscription.',
32
- giftAlreadyAssigned: 'This gift is already assigned to another member.',
33
- giftMissingConsumesAt: 'This gift is missing a "consumes at" date.',
34
- giftMemberAlreadyHasGift: 'Member already has a different active gift attached.'
35
- };
36
-
37
- interface MemberModel {
38
- id: string;
39
- get(key: 'email'): string;
40
- get(key: 'status'): string;
41
- get(key: 'name'): string | null;
42
- get(key: 'email_disabled'): boolean;
43
- get(key: string): unknown;
44
- }
45
-
46
- interface MemberRepository {
47
- get(filter: Record<string, unknown>, options?: Record<string, unknown>): Promise<MemberModel | null>;
48
- update(data: Record<string, unknown>, options?: Record<string, unknown>): Promise<unknown>;
49
- triggerMemberSignupAutomation(
50
- memberId: string,
51
- memberEmail: string,
52
- memberStatus: 'free' | 'paid',
53
- options?: Record<string, unknown>
54
- ): Promise<unknown>;
55
- }
56
-
57
- type Tier = Omit<GiftCheckoutTier, 'currency'> & {
58
- id: string | {
59
- toHexString(): string;
60
- };
61
- name: string;
62
- currency: string;
63
- getPrice(cadence: GiftCadence): number;
64
- toJSON(): {
65
- id: string;
66
- name: string;
67
- description: string | null;
68
- benefits: string[];
69
- };
70
- };
71
-
72
- interface TiersService {
73
- api: {
74
- read(idString: string): Promise<Tier | null>;
75
- };
76
- }
77
-
78
- interface GiftEmailService {
79
- sendPurchaseConfirmation(data: {
80
- buyerEmail: string;
81
- token: string;
82
- tierName: string;
83
- cadence: GiftCadence;
84
- duration: number;
85
- expiresAt: Date;
86
- }): Promise<void>;
87
- sendReminder(data: {
88
- memberEmail: string;
89
- memberName: string | null;
90
- tierName: string;
91
- consumesAt: Date;
92
- }): Promise<void>;
93
- }
94
-
95
- interface StaffServiceEmails {
96
- notifyGiftPurchased(data: {
97
- name: string | null;
98
- email: string;
99
- memberId: string | null;
100
- amount: number;
101
- currency: string;
102
- tierName: string;
103
- cadence: GiftCadence;
104
- duration: number;
105
- }): Promise<void>;
106
- notifyGiftSubscriptionStarted(data: {
107
- memberId: string;
108
- memberEmail: string;
109
- memberName: string | null;
110
- tierName: string;
111
- cadence: GiftCadence;
112
- duration: number;
113
- buyerEmail: string;
114
- }): Promise<void>;
115
- }
116
-
117
- const GiftPurchaseDataSchema = z.object({
118
- token: z.string().min(1),
119
- buyerEmail: z.string().min(1),
120
- stripeCustomerId: z.string().min(1).nullable(),
121
- tierId: z.string().min(1),
122
- cadence: GiftCadenceSchema,
123
- duration: z.number().int().positive(),
124
- currency: z.string().min(1),
125
- amount: z.number().int().nonnegative(),
126
- stripeCheckoutSessionId: z.string().min(1),
127
- stripePaymentIntentId: z.string().min(1)
128
- });
129
-
130
- export type GiftPurchaseData = z.infer<typeof GiftPurchaseDataSchema>;
131
-
132
- interface GiftServiceDeps {
133
- giftRepository: GiftRepository;
134
- memberRepository: MemberRepository;
135
- tiersService: TiersService;
136
- giftEmailService: GiftEmailService;
137
- staffServiceEmails: StaffServiceEmails;
138
- giftReminderScheduler: Pick<GiftReminderScheduler, 'scheduleFor'>;
139
- checkoutAdapter: {
140
- getCustomerId(buyer: GiftCheckoutBuyer): Promise<string | null>;
141
- createSession(data: GiftCheckoutSession): Promise<string>;
142
- };
143
- labsService: {
144
- isSet(flag: string): boolean;
145
- };
146
- settingsCache: {
147
- get(key: string): unknown;
148
- };
149
- }
150
-
151
- interface ReminderSend {
152
- memberEmail: string;
153
- memberName: string | null;
154
- consumesAt: Date;
155
- }
156
-
157
- export interface GiftCheckoutBuyer {
158
- memberId: string | null;
159
- email: string | null;
160
- name: string | null;
161
- isAuthenticated: boolean;
162
- }
163
-
164
- export interface StartGiftCheckoutInput {
165
- tierId?: string;
166
- offerId?: string;
167
- cadence?: string;
168
- duration?: number;
169
- metadata: Record<string, unknown>;
170
- successUrl: string;
171
- cancelUrl?: string;
172
- buyer: GiftCheckoutBuyer;
173
- }
174
-
175
- interface GiftCheckoutSession {
176
- amount: number;
177
- currency: string;
178
- tierName: string;
179
- cadence: GiftCadence;
180
- duration: number;
181
- metadata: Record<string, unknown>;
182
- successUrl: string;
183
- cancelUrl?: string;
184
- customerId: string | null;
185
- customerEmail: string | null;
186
- }
187
-
188
- export interface GiftRedemption {
189
- token: string;
190
- cadence: GiftCadence;
191
- duration: number;
192
- currency: string;
193
- amount: number;
194
- expires_at: Date;
195
- consumes_at: Date | null;
196
- tier: {
197
- id: string;
198
- name: string;
199
- description: string | null;
200
- benefits: string[];
201
- };
202
- }
203
-
204
- export interface GiftContinuation {
205
- tierId: string;
206
- cadence: GiftCadence;
207
- trialDays: number | null;
208
- }
209
-
210
- export interface GiftMemberPresentation {
211
- cadence: GiftCadence;
212
- currency: string;
213
- amount: number;
214
- }
215
-
216
- export interface GiftPreview {
217
- cadence: GiftCadence;
218
- duration: number;
219
- tier: {
220
- id: string;
221
- name: string;
222
- };
223
- }
224
-
225
- export class GiftService {
226
- private readonly deps: GiftServiceDeps;
227
-
228
- constructor(deps: GiftServiceDeps) {
229
- this.deps = deps;
230
- }
231
-
232
- async startCheckout(input: StartGiftCheckoutInput): Promise<{url: string}> {
233
- if (input.offerId) {
234
- throw new errors.BadRequestError({
235
- message: 'Bad Request.',
236
- context: 'Offers cannot be applied to gift subscriptions'
237
- });
238
- }
239
-
240
- if (!input.tierId) {
241
- throw new errors.BadRequestError({
242
- message: 'Bad Request.',
243
- context: 'Expected offerId or tierId, received none'
244
- });
245
- }
246
-
247
- let resolvedDuration: ResolvedGiftDuration | null = null;
248
- let cadence: GiftCadence;
249
-
250
- if (this.deps.labsService.isSet('giftSubCustomization')) {
251
- resolvedDuration = resolveGiftDuration(input);
252
- cadence = resolvedDuration.cadence;
253
- } else {
254
- if (input.cadence !== 'month' && input.cadence !== 'year') {
255
- const receivedCadence = input.cadence ? `"${input.cadence}"` : input.cadence;
256
-
257
- throw new errors.BadRequestError({
258
- message: 'Bad Request.',
259
- context: `Expected cadence to be "month" or "year", received ${receivedCadence}`
260
- });
261
- }
262
-
263
- cadence = input.cadence;
264
- }
265
-
266
- let tier: Tier | null;
267
- try {
268
- tier = await this.deps.tiersService.api.read(input.tierId);
269
- } catch (err) {
270
- logging.error(err);
271
- tier = null;
272
- }
273
-
274
- if (!tier) {
275
- throw new errors.BadRequestError({
276
- message: 'This tier does not exist.',
277
- context: `Tier with id "${input.tierId}" not found`
278
- });
279
- }
280
-
281
- if (tier.status === 'archived') {
282
- throw new errors.NoPermissionError({
283
- message: 'This tier is archived.'
284
- });
285
- }
286
-
287
- let duration = 1;
288
- let totalMonths: number | undefined;
289
- let amount = tier.getPrice(cadence);
290
-
291
- if (resolvedDuration) {
292
- const plan = validateGiftCheckoutOffer({
293
- tier,
294
- portalPlans: this.deps.settingsCache.get('portal_plans'),
295
- offer: resolvedDuration
296
- });
297
-
298
- cadence = plan.cadence;
299
- duration = plan.duration;
300
- totalMonths = plan.totalMonths;
301
- amount = plan.amount;
302
- }
303
-
304
- const tierId = typeof tier.id === 'string' ? tier.id : tier.id.toHexString();
305
- const token = this.generateToken();
306
- const successUrl = new URL(input.successUrl);
307
-
308
- successUrl.searchParams.set('stripe', 'gift-purchase-success');
309
- successUrl.searchParams.set('gift_token', token);
310
- successUrl.searchParams.set('gift_tier', tierId);
311
- successUrl.searchParams.set('gift_cadence', cadence);
312
- if (totalMonths !== undefined) {
313
- successUrl.searchParams.set('gift_duration', String(totalMonths));
314
- }
315
-
316
- const customerId = input.buyer.isAuthenticated
317
- ? await this.deps.checkoutAdapter.getCustomerId(input.buyer)
318
- : null;
319
-
320
- const url = await this.deps.checkoutAdapter.createSession({
321
- amount,
322
- currency: tier.currency.toLowerCase(),
323
- tierName: tier.name,
324
- cadence,
325
- duration,
326
- metadata: {
327
- ...input.metadata,
328
- ghost_gift: 'true',
329
- gift_token: token,
330
- tier_id: tierId,
331
- cadence,
332
- duration: String(duration)
333
- },
334
- successUrl: successUrl.toString(),
335
- cancelUrl: input.cancelUrl,
336
- customerId,
337
- customerEmail: customerId ? null : input.buyer.email
338
- });
339
-
340
- return {url};
341
- }
342
-
343
- private generateToken(): string {
344
- /**
345
- * Combinations: 62^12 ≈ 3.23 × 10^21 (~3.23 sextillion)
346
- * Entropy: 12 × log2(62) ≈ 71.45 bits
347
- */
348
- const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
349
- let token = '';
350
-
351
- for (let i = 0; i < 12; i++) {
352
- token += alphabet[crypto.randomInt(alphabet.length)];
353
- }
354
-
355
- return token;
356
- }
357
-
358
- async completePurchase(input: GiftPurchaseData): Promise<boolean> {
359
- const parsed = GiftPurchaseDataSchema.safeParse(input);
360
- if (!parsed.success) {
361
- const issue = parsed.error.issues[0];
362
- throw new errors.ValidationError({
363
- message: 'Invalid gift purchase data.',
364
- property: issue.path.join('.'),
365
- context: issue.message
366
- });
367
- }
368
- const data = parsed.data;
369
-
370
- if (await this.deps.giftRepository.existsByCheckoutSessionId(data.stripeCheckoutSessionId)) {
371
- return false;
372
- }
373
-
374
- const member = data.stripeCustomerId
375
- ? await this.deps.memberRepository.get({customer_id: data.stripeCustomerId})
376
- : null;
377
-
378
- const gift = Gift.fromPurchase({
379
- token: data.token,
380
- buyerEmail: data.buyerEmail,
381
- buyerMemberId: member?.id ?? null,
382
- tierId: data.tierId,
383
- cadence: data.cadence,
384
- duration: data.duration,
385
- currency: data.currency,
386
- amount: data.amount,
387
- stripeCheckoutSessionId: data.stripeCheckoutSessionId,
388
- stripePaymentIntentId: data.stripePaymentIntentId
389
- });
390
-
391
- await this.deps.giftRepository.create(gift);
392
-
393
- const tier = await this.deps.tiersService.api.read(data.tierId);
394
-
395
- if (!tier) {
396
- throw new errors.NotFoundError({message: `Tier not found: ${data.tierId}`});
397
- }
398
-
399
- try {
400
- await this.deps.staffServiceEmails.notifyGiftPurchased({
401
- name: member?.get('name') ?? null,
402
- email: member?.get('email') ?? data.buyerEmail,
403
- memberId: member?.id ?? null,
404
- amount: data.amount,
405
- currency: data.currency,
406
- tierName: tier.name,
407
- cadence: data.cadence,
408
- duration: data.duration
409
- });
410
- } catch (err) {
411
- logging.error('Failed to notify staff of gift purchase', err);
412
- }
413
-
414
- try {
415
- await this.deps.giftEmailService.sendPurchaseConfirmation({
416
- buyerEmail: data.buyerEmail,
417
- token: data.token,
418
- tierName: tier.name,
419
- cadence: data.cadence,
420
- duration: data.duration,
421
- expiresAt: gift.expiresAt
422
- });
423
- } catch (err) {
424
- logging.error('Failed to send gift purchase confirmation email', err);
425
- }
426
-
427
- return true;
428
- }
429
-
430
- private assertRedeemable(gift: Gift, memberStatus: string | null): Gift {
431
- const redeemableCheck = gift.checkRedeemable(memberStatus);
432
-
433
- if (!redeemableCheck.redeemable) {
434
- switch (redeemableCheck.reason) {
435
- case 'redeemed':
436
- throw new errors.BadRequestError({
437
- message: tpl(errorMessages.giftAlreadyRedeemed),
438
- code: 'GIFT_REDEEMED'
439
- });
440
- case 'consumed':
441
- throw new errors.BadRequestError({
442
- message: tpl(errorMessages.giftConsumed),
443
- code: 'GIFT_CONSUMED'
444
- });
445
- case 'expired':
446
- throw new errors.BadRequestError({
447
- message: tpl(errorMessages.giftExpired),
448
- code: 'GIFT_EXPIRED'
449
- });
450
- case 'refunded':
451
- throw new errors.BadRequestError({
452
- message: tpl(errorMessages.giftRefunded),
453
- code: 'GIFT_REFUNDED'
454
- });
455
- case 'paid-member':
456
- throw new errors.BadRequestError({
457
- message: tpl(errorMessages.paidMember),
458
- code: 'GIFT_PAID_MEMBER'
459
- });
460
- default: {
461
- const exhaustiveCheck: never = redeemableCheck.reason;
462
-
463
- throw new errors.InternalServerError({
464
- message: `Unhandled redeem failure reason: ${exhaustiveCheck}`
465
- });
466
- }
467
- }
468
- }
469
-
470
- return gift;
471
- }
472
-
473
- async getRedeemable(input: {token: string; memberStatus: string | null}): Promise<GiftRedemption> {
474
- const gift = await this.deps.giftRepository.getByToken(input.token);
475
-
476
- if (!gift) {
477
- throw new errors.NotFoundError({message: tpl(errorMessages.giftNotFound)});
478
- }
479
-
480
- this.assertRedeemable(gift, input.memberStatus);
481
-
482
- return this.serializeRedemption(gift);
483
- }
484
-
485
- async redeem(input: {
486
- token: string;
487
- memberId: string;
488
- transacting?: Knex.Transaction;
489
- newMember?: boolean;
490
- }): Promise<GiftRedemption> {
491
- const run = async (transacting: Knex.Transaction) => {
492
- const {redeemed, member} = await this.redeemGift(input.token, input.memberId, {
493
- transacting,
494
- newMember: input.newMember
495
- });
496
- const redemption = await this.serializeRedemption(redeemed);
497
-
498
- return {redeemed, member, redemption};
499
- };
500
-
501
- const {redeemed, member, redemption} = input.transacting
502
- ? await run(input.transacting)
503
- : await this.deps.giftRepository.transaction(run);
504
-
505
- const notify = async () => {
506
- try {
507
- const tier = await this.deps.tiersService.api.read(redeemed.tierId);
508
-
509
- if (!tier) {
510
- throw new errors.NotFoundError({message: `Tier not found: ${redeemed.tierId}`});
511
- }
512
-
513
- await this.deps.staffServiceEmails.notifyGiftSubscriptionStarted({
514
- memberId: member.id,
515
- memberEmail: member.get('email'),
516
- memberName: member.get('name'),
517
- tierName: tier.name,
518
- cadence: redeemed.cadence,
519
- duration: redeemed.duration,
520
- buyerEmail: redeemed.buyerEmail
521
- });
522
- } catch (err) {
523
- logging.error('Failed to notify staff of gift redemption', err);
524
- }
525
-
526
- await this.deps.giftReminderScheduler.scheduleFor(redeemed);
527
- };
528
-
529
- if (input.transacting) {
530
- // Only notify once the transaction has finished
531
- input.transacting.executionPromise.then(notify, () => {});
532
- } else {
533
- await notify();
534
- }
535
-
536
- return redemption;
537
- }
538
-
539
- private async redeemGift(token: string, memberId: string, options: {transacting: Knex.Transaction; newMember?: boolean}): Promise<{redeemed: Gift; member: MemberModel}> {
540
- const {transacting} = options;
541
- const member = await this.deps.memberRepository.get({id: memberId}, {transacting, forUpdate: true});
542
- if (!member) {
543
- throw new errors.NotFoundError({message: `Member not found: ${memberId}`});
544
- }
545
-
546
- const gift = await this.deps.giftRepository.getByToken(token, {transacting, forUpdate: true});
547
- if (!gift) {
548
- throw new errors.NotFoundError({message: tpl(errorMessages.giftNotFound)});
549
- }
550
-
551
- if (options.newMember) {
552
- this.assertRedeemable(gift, null);
553
- } else {
554
- this.assertRedeemable(gift, member.get('status'));
555
- }
556
-
557
- const redeemed = gift.redeem({memberId});
558
-
559
- await this.deps.memberRepository.update({
560
- products: [{
561
- id: redeemed.tierId,
562
- expiry_at: redeemed.consumesAt
563
- }],
564
- status: 'gift'
565
- }, {id: memberId, transacting});
566
-
567
- await this.deps.giftRepository.update(redeemed, {transacting});
568
-
569
- // Gift members receive the paid welcome email, as they receive access to paid content
570
- await this.deps.memberRepository.triggerMemberSignupAutomation(
571
- memberId,
572
- member.get('email'),
573
- 'paid',
574
- {transacting}
575
- );
576
-
577
- return {redeemed, member};
578
- }
579
-
580
- private async getActiveByMember(memberId: string, options: {transacting?: Knex.Transaction} = {}): Promise<Gift | null> {
581
- if (!memberId) {
582
- return null;
583
- }
584
- return this.deps.giftRepository.getActiveByMember(memberId, options);
585
- }
586
-
587
- private async getActiveByMembers(memberIds: string[], options: {transacting?: Knex.Transaction} = {}): Promise<Map<string, Gift>> {
588
- if (!memberIds || memberIds.length === 0) {
589
- return new Map();
590
- }
591
- return this.deps.giftRepository.getActiveByMembers(memberIds, options);
592
- }
593
-
594
- private getRemainingActiveDays(gift: Gift, now: Date = new Date()): number {
595
- if (!gift.isRedeemed() || !gift.consumesAt || gift.isConsumed()) {
596
- return 0;
597
- }
598
-
599
- const diffDays = Math.ceil((gift.consumesAt.getTime() - now.getTime()) / MS_PER_DAY);
600
-
601
- return Math.max(0, diffDays);
602
- }
603
-
604
- async preparePaidContinuation({
605
- memberId,
606
- memberStatus
607
- }: {
608
- memberId: string;
609
- memberStatus: string;
610
- }): Promise<GiftContinuation> {
611
- if (memberStatus !== 'gift') {
612
- throw new errors.BadRequestError({
613
- message: 'Bad Request.',
614
- context: 'Member does not have an active gift subscription'
615
- });
616
- }
617
-
618
- const gift = await this.getActiveByMember(memberId);
619
-
620
- if (!gift) {
621
- throw new errors.BadRequestError({
622
- message: 'Bad Request.',
623
- context: 'No active gift subscription found for member'
624
- });
625
- }
626
-
627
- const remainingDays = this.getRemainingActiveDays(gift);
628
-
629
- return {
630
- tierId: gift.tierId,
631
- cadence: gift.cadence,
632
- trialDays: remainingDays > 0 ? Math.min(remainingDays, 730) : null
633
- };
634
- }
635
-
636
- async getMemberPresentations(memberIds: string[]): Promise<Map<string, GiftMemberPresentation>> {
637
- const gifts = await this.getActiveByMembers(memberIds);
638
- const presentations = new Map<string, GiftMemberPresentation>();
639
-
640
- for (const [memberId, gift] of gifts) {
641
- presentations.set(memberId, {
642
- cadence: gift.cadence,
643
- currency: gift.currency,
644
- amount: gift.amount
645
- });
646
- }
647
-
648
- return presentations;
649
- }
650
-
651
- async getPreview(token: string): Promise<GiftPreview | null> {
652
- const gift = await this.deps.giftRepository.getByToken(token);
653
-
654
- if (!gift) {
655
- return null;
656
- }
657
-
658
- const tier = await this.deps.tiersService.api.read(gift.tierId);
659
-
660
- if (!tier) {
661
- throw new errors.NotFoundError({message: `Tier not found for gift: ${gift.token}`});
662
- }
663
-
664
- const tierJSON = tier.toJSON();
665
-
666
- return {
667
- cadence: gift.cadence,
668
- duration: gift.duration,
669
- tier: {
670
- id: tierJSON.id,
671
- name: tierJSON.name
672
- }
673
- };
674
- }
675
-
676
- browsePurchaseEvents(options?: GiftEventBrowseOptions, filter?: unknown): Promise<GiftEventPage> {
677
- return this.deps.giftRepository.browsePurchaseEvents(options, filter);
678
- }
679
-
680
- browseRedemptionEvents(options?: GiftEventBrowseOptions, filter?: unknown): Promise<GiftEventPage> {
681
- return this.deps.giftRepository.browseRedemptionEvents(options, filter);
682
- }
683
-
684
- async reassignRedeemer(input: {giftId: string; memberId: string; transacting?: Knex.Transaction}): Promise<void> {
685
- const {giftId, memberId} = input;
686
- const run = async (transacting: Knex.Transaction): Promise<Gift> => {
687
- const gift = await this.deps.giftRepository.getById(giftId, {transacting, forUpdate: true});
688
-
689
- if (!gift) {
690
- throw new errors.NotFoundError({message: tpl(errorMessages.giftNotFound)});
691
- }
692
-
693
- if (gift.redeemerMemberId === memberId) {
694
- return gift;
695
- }
696
-
697
- const check = gift.checkReassignable();
698
-
699
- if (!check.reassignable) {
700
- switch (check.reason) {
701
- case 'assigned':
702
- throw new errors.BadRequestError({message: tpl(errorMessages.giftAlreadyAssigned)});
703
- case 'unredeemed':
704
- case 'consumed':
705
- case 'expired':
706
- case 'refunded':
707
- throw new errors.BadRequestError({message: tpl(errorMessages.giftInvalidReassignStatus)});
708
- case 'missing-consumes-at':
709
- throw new errors.BadRequestError({message: tpl(errorMessages.giftMissingConsumesAt)});
710
- default: {
711
- const exhaustiveCheck: never = check.reason;
712
-
713
- throw new errors.InternalServerError({
714
- message: `Unhandled reassign failure reason: ${exhaustiveCheck}`
715
- });
716
- }
717
- }
718
- }
719
-
720
- const member = await this.deps.memberRepository.get(
721
- {id: memberId},
722
- {transacting, forUpdate: true}
723
- );
724
-
725
- if (!member) {
726
- throw new errors.NotFoundError({message: `Member not found: ${memberId}`});
727
- }
728
-
729
- const memberStatus = member.get('status');
730
- if (memberStatus !== 'free' && memberStatus !== 'gift') {
731
- throw new errors.BadRequestError({message: tpl(errorMessages.giftInvalidReassignMember)});
732
- }
733
-
734
- const existingActiveGift = await this.deps.giftRepository.getActiveByMember(memberId, {transacting});
735
- if (existingActiveGift && existingActiveGift.token !== gift.token) {
736
- throw new errors.BadRequestError({message: tpl(errorMessages.giftMemberAlreadyHasGift)});
737
- }
738
-
739
- const reassignedGift = gift.reassignRedeemer(memberId);
740
-
741
- await this.deps.memberRepository.update({
742
- products: [{
743
- id: reassignedGift.tierId,
744
- expiry_at: reassignedGift.consumesAt
745
- }],
746
- status: 'gift'
747
- }, {id: memberId, transacting});
748
-
749
- await this.deps.giftRepository.update(reassignedGift, {transacting});
750
-
751
- return reassignedGift;
752
- };
753
-
754
- await (input.transacting
755
- ? run(input.transacting)
756
- : this.deps.giftRepository.transaction(run));
757
- }
758
-
759
- async handlePaymentRefund({paymentIntentId}: {paymentIntentId: string}): Promise<boolean> {
760
- const gift = await this.deps.giftRepository.getByPaymentIntentId(paymentIntentId);
761
-
762
- if (!gift) {
763
- return false;
764
- }
765
-
766
- const refunded = gift.refund();
767
-
768
- if (!refunded) {
769
- return true;
770
- }
771
-
772
- await this.deps.giftRepository.transaction(async (transacting) => {
773
- await this.deps.giftRepository.update(refunded, {transacting});
774
-
775
- if (gift.redeemerMemberId) {
776
- const member = await this.deps.memberRepository.get({id: gift.redeemerMemberId}, {transacting});
777
-
778
- if (member?.get('status') === 'gift') {
779
- await this.deps.memberRepository.update({
780
- products: [],
781
- status: 'free'
782
- }, {id: gift.redeemerMemberId, transacting});
783
- }
784
- }
785
- });
786
-
787
- return true;
788
- }
789
-
790
- async handlePaidSubscriptionActivation(memberId: string): Promise<boolean> {
791
- const gift = await this.getActiveByMember(memberId);
792
-
793
- if (!gift) {
794
- return false;
795
- }
796
-
797
- return Boolean(await this.consume(gift.token));
798
- }
799
-
800
- private async consume(token: string, options: {transacting?: Knex.Transaction} = {}): Promise<Gift | null> {
801
- const run = async (transacting: Knex.Transaction) => {
802
- // Fetch with a row lock to prevent race conditions under concurrency
803
- const gift = await this.deps.giftRepository.getByToken(token, {transacting, forUpdate: true});
804
-
805
- if (!gift || gift.status !== 'redeemed') {
806
- return null;
807
- }
808
-
809
- const consumed = gift.consume();
810
-
811
- if (!consumed) {
812
- return null;
813
- }
814
-
815
- await this.deps.giftRepository.update(consumed, {transacting});
816
-
817
- return consumed;
818
- };
819
-
820
- return options.transacting
821
- ? await run(options.transacting)
822
- : await this.deps.giftRepository.transaction(run);
823
- }
824
-
825
- async processConsumed(): Promise<{consumedCount: number; updatedMemberCount: number}> {
826
- const toConsume = await this.deps.giftRepository.findPendingConsumption();
827
-
828
- if (toConsume.length === 0) {
829
- return {consumedCount: 0, updatedMemberCount: 0};
830
- }
831
-
832
- let consumedCount = 0;
833
- let updatedMemberCount = 0;
834
-
835
- for (const gift of toConsume) {
836
- await this.deps.giftRepository.transaction(async (transacting) => {
837
- const consumed = await this.consume(gift.token, {transacting});
838
-
839
- if (!consumed) {
840
- return;
841
- }
842
-
843
- const member = await this.deps.memberRepository.get({id: consumed.redeemerMemberId}, {transacting, forUpdate: true});
844
-
845
- if (member && member.get('status') === 'gift') {
846
- await this.deps.memberRepository.update({
847
- products: [],
848
- status: 'free'
849
- }, {id: consumed.redeemerMemberId, transacting});
850
-
851
- updatedMemberCount += 1;
852
- }
853
-
854
- consumedCount += 1;
855
- });
856
- }
857
-
858
- return {consumedCount, updatedMemberCount};
859
- }
860
-
861
- async processExpired(): Promise<{expiredCount: number}> {
862
- const toExpire = await this.deps.giftRepository.findPendingExpiration();
863
-
864
- if (toExpire.length === 0) {
865
- return {expiredCount: 0};
866
- }
867
-
868
- let expiredCount = 0;
869
-
870
- for (const gift of toExpire) {
871
- await this.deps.giftRepository.transaction(async (transacting) => {
872
- // Re-fetch with a row lock to prevent races with concurrent redeems / refunds
873
- const locked = await this.deps.giftRepository.getByToken(gift.token, {transacting, forUpdate: true});
874
-
875
- if (locked?.status !== 'purchased') {
876
- return;
877
- }
878
-
879
- const expired = locked.expire();
880
-
881
- if (!expired) {
882
- return;
883
- }
884
-
885
- await this.deps.giftRepository.update(expired, {transacting});
886
-
887
- expiredCount += 1;
888
- });
889
- }
890
-
891
- return {expiredCount};
892
- }
893
-
894
- async processReminders(): Promise<{remindedCount: number; skippedCount: number; failedCount: number}> {
895
- const now = new Date();
896
- const toRemind = await this.deps.giftRepository.findPendingReminder({
897
- now,
898
- reminderLeadMs: GIFT_REMINDER_LEAD_MS,
899
- reminderFloorMs: GIFT_REMINDER_FLOOR_MS
900
- });
901
-
902
- if (toRemind.length === 0) {
903
- return {remindedCount: 0, skippedCount: 0, failedCount: 0};
904
- }
905
-
906
- let remindedCount = 0;
907
- let skippedCount = 0;
908
- let failedCount = 0;
909
-
910
- for (const gift of toRemind) {
911
- try {
912
- const sent = await this.sendReminderForGift(gift.token);
913
-
914
- if (sent) {
915
- remindedCount += 1;
916
- } else {
917
- skippedCount += 1;
918
- }
919
- } catch (err) {
920
- logging.error(err);
921
-
922
- failedCount += 1;
923
- }
924
- }
925
-
926
- return {remindedCount, skippedCount, failedCount};
927
- }
928
-
929
- private async sendReminderForGift(token: string): Promise<boolean> {
930
- const gift = await this.deps.giftRepository.getByToken(token);
931
-
932
- if (!gift) {
933
- return false;
934
- }
935
-
936
- const tier = await this.deps.tiersService.api.read(gift.tierId);
937
-
938
- if (!tier) {
939
- throw new errors.NotFoundError({message: `Tier not found for gift: ${gift.tierId}`});
940
- }
941
-
942
- const result = await this.deps.giftRepository.transaction(async (transacting): Promise<ReminderSend | null> => {
943
- const locked = await this.deps.giftRepository.getByToken(token, {transacting, forUpdate: true});
944
-
945
- if (!locked) {
946
- return null;
947
- }
948
-
949
- if (
950
- // Gift must still be active — a concurrent refund or early consume can happen
951
- // between `findPendingReminder` and this re-read.
952
- locked.status !== 'redeemed'
953
- // Idempotency guard: another path (rerun, scheduler) may already have sent.
954
- || locked.consumesSoonReminderSentAt !== null
955
- // Narrows `redeemerMemberId` from `string | null` to `string` — always set for redeemed gifts.
956
- || locked.redeemerMemberId === null
957
- // Narrows `consumesAt` from `Date | null` to `Date` — always set for redeemed gifts.
958
- || locked.consumesAt === null
959
- ) {
960
- return null;
961
- }
962
-
963
- const member = await this.deps.memberRepository.get(
964
- {id: locked.redeemerMemberId},
965
- {transacting, forUpdate: true}
966
- );
967
-
968
- // Record the reminder as sent before any skip or send below so we don't
969
- // re-try gifts with permanently unreachable redeemers on every poll.
970
- const reminded = locked.remind();
971
-
972
- if (!reminded) {
973
- return null;
974
- }
975
-
976
- await this.deps.giftRepository.update(reminded, {transacting});
977
-
978
- if (!member) {
979
- return null;
980
- }
981
-
982
- if (member.get('email_disabled')) {
983
- return null;
984
- }
985
-
986
- return {
987
- memberEmail: member.get('email'),
988
- memberName: member.get('name'),
989
- consumesAt: locked.consumesAt
990
- };
991
- });
992
-
993
- if (!result) {
994
- return false;
995
- }
996
-
997
- await this.deps.giftEmailService.sendReminder({
998
- memberEmail: result.memberEmail,
999
- memberName: result.memberName,
1000
- tierName: tier.name,
1001
- consumesAt: result.consumesAt
1002
- });
1003
-
1004
- return true;
1005
- }
1006
-
1007
- private async serializeRedemption(gift: Gift): Promise<GiftRedemption> {
1008
- const tier = await this.deps.tiersService.api.read(gift.tierId);
1009
-
1010
- if (!tier) {
1011
- throw new errors.InternalServerError({
1012
- message: `Tier ${gift.tierId} not found for gift: ${gift.token}`
1013
- });
1014
- }
1015
-
1016
- const tierJSON = tier.toJSON();
1017
-
1018
- return {
1019
- token: gift.token,
1020
- cadence: gift.cadence,
1021
- duration: gift.duration,
1022
- currency: gift.currency,
1023
- amount: gift.amount,
1024
- expires_at: gift.expiresAt,
1025
- consumes_at: gift.consumesAt,
1026
- tier: {
1027
- id: tierJSON.id,
1028
- name: tierJSON.name,
1029
- description: tierJSON.description,
1030
- benefits: tierJSON.benefits
1031
- }
1032
- };
1033
- }
1034
- }