ghost 6.58.0 → 6.59.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 (453) hide show
  1. package/components/tryghost-adapter-base-scheduling-0.2.2.tgz +0 -0
  2. package/components/tryghost-adapter-base-sso-0.1.2.tgz +0 -0
  3. package/components/tryghost-i18n-0.0.0.tgz +0 -0
  4. package/components/tryghost-kg-default-nodes-2.2.0.tgz +0 -0
  5. package/components/tryghost-kg-html-to-lexical-1.4.0.tgz +0 -0
  6. package/components/tryghost-kg-lexical-html-renderer-1.5.0.tgz +0 -0
  7. package/content/themes/casper/.github/workflows/test.yml +11 -1
  8. package/content/themes/casper/AGENTS.md +25 -0
  9. package/content/themes/casper/assets/built/screen.css +1 -1
  10. package/content/themes/casper/assets/css/screen.css +15 -1
  11. package/content/themes/casper/gulpfile.js +3 -0
  12. package/content/themes/casper/package.json +8 -8
  13. package/content/themes/source/.gitattributes +5 -0
  14. package/content/themes/source/.github/workflows/test.yml +39 -0
  15. package/content/themes/source/AGENTS.md +25 -0
  16. package/content/themes/source/README.md +33 -4
  17. package/content/themes/source/assets/built/screen.css +1 -1
  18. package/content/themes/source/assets/css/screen.css +15 -0
  19. package/content/themes/source/gulpfile.js +6 -3
  20. package/content/themes/source/package.json +13 -10
  21. package/content/themes/source/pnpm-workspace.yaml +3 -0
  22. package/core/boot.js +11 -4
  23. package/core/built/admin/assets/Combination-BCm9FL4o.js +41 -0
  24. package/core/built/admin/assets/about-BdSCpjXG.js +1 -0
  25. package/core/built/admin/assets/account-migration-BpKAqo8k.js +1 -0
  26. package/core/built/admin/assets/activitypub/{account-migration-BpDeIF3G.js → account-migration-DVnYS1XG.js} +8 -8
  27. package/core/built/admin/assets/activitypub/activitypub.js +2 -2
  28. package/core/built/admin/assets/activitypub/{bluesky-sharing-aPMtrS3L.js → bluesky-sharing-DLVeE9ii.js} +31 -31
  29. package/core/built/admin/assets/activitypub/{content-formatters-BORfuA9G.js → content-formatters-DCsVic-t.js} +2 -2
  30. package/core/built/admin/assets/activitypub/{dist-ByFmDSNP2.js → dist-ByFmDSNP.js} +1 -1
  31. package/core/built/admin/assets/activitypub/{domain-CPdiB27_.js → domain-rrvDLfnK.js} +8 -8
  32. package/core/built/admin/assets/activitypub/{edit-profile-BY2PawIe.js → edit-profile-VT-piVxq.js} +23 -23
  33. package/core/built/admin/assets/activitypub/{explore-Sp9bgAwq.js → explore-DZg3m48P.js} +14 -14
  34. package/core/built/admin/assets/activitypub/{feed-BjtF4ohL.js → feed-BJqQvDMI.js} +41 -41
  35. package/core/built/admin/assets/activitypub/{field-BDOYVTg6.js → field-BezS4-1J.js} +2 -2
  36. package/core/built/admin/assets/activitypub/inbox-Uf0GVrI_.js +1284 -0
  37. package/core/built/admin/assets/activitypub/{label-1jhN-qhD.js → label-CZ6if4zK.js} +2 -2
  38. package/core/built/admin/assets/activitypub/layout-DNuYT4Gu.js +1 -0
  39. package/core/built/admin/assets/activitypub/moderation-CP6mDVky.js +340 -0
  40. package/core/built/admin/assets/activitypub/{note-nr_m_5yb.js → note-BlFkye2g.js} +16 -16
  41. package/core/built/admin/assets/activitypub/{notifications-BdcyivbT.js → notifications-GGTrGEdu.js} +163 -126
  42. package/core/built/admin/assets/activitypub/{preferences-CNqy1x9C.js → preferences-CjZXrZqr.js} +1550 -1551
  43. package/core/built/admin/assets/activitypub/{profile-DlY4rhbO.js → profile-CcXYxydm.js} +248 -248
  44. package/core/built/admin/assets/activitypub/{routes-CEHdEZzS.js → routes-nHCKlZl-.js} +1971 -1747
  45. package/core/built/admin/assets/activitypub/settings-pDzybcb3.js +315 -0
  46. package/core/built/admin/assets/activitypub/{site-CtBUCPBz.js → site-BZF0F8LN.js} +101 -101
  47. package/core/built/admin/assets/activitypub/{step-1-D9jGWyAy.js → step-1-C-Ns1Tfg.js} +3 -3
  48. package/core/built/admin/assets/activitypub/{step-3-KpUqpGWi.js → step-3-rDMNGmLg.js} +8 -8
  49. package/core/built/admin/assets/activitypub/{tabs-DvlsxTCI.js → tabs-CDjh3_Jp.js} +198 -198
  50. package/core/built/admin/assets/activitypub/{topic-filter-CIN1E2jg.js → topic-filter-CmTc8hpN.js} +2 -2
  51. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-CATFd6YV.js → use-activity-pub-queries-DUDEdSdR.js} +286 -243
  52. package/core/built/admin/assets/activitypub/{use-reply-chain-data-DJ0vhA8V.js → use-reply-chain-data-TunGtmJl.js} +14 -14
  53. package/core/built/admin/assets/add-integration-modal-CXRQi1x0.js +1 -0
  54. package/core/built/admin/assets/add-newsletter-modal-t447KRYz.js +1 -0
  55. package/core/built/admin/assets/add-recommendation-modal-D882MdnR.js +1 -0
  56. package/core/built/admin/assets/{analytics-BC2oR8ay.js → analytics-Dj-WlzPf.js} +1 -1
  57. package/core/built/admin/assets/announcement-bar-modal-CwHKKw-a.js +1 -0
  58. package/core/built/admin/assets/api-keys-Boy5pLbD.js +1 -0
  59. package/core/built/admin/assets/audience-Cc40y7zQ.js +1 -0
  60. package/core/built/admin/assets/automations-DNLK5Udr.js +1 -0
  61. package/core/built/admin/assets/bluesky-sharing-Cm4aFaOl.js +1 -0
  62. package/core/built/admin/assets/breadcrumb-CcFr_ltt.js +1 -0
  63. package/core/built/admin/assets/button-BshWPHvn.js +1 -0
  64. package/core/built/admin/assets/chart-helpers-BsbzoWhK.js +1 -0
  65. package/core/built/admin/assets/{checkbox-LmClhqcS.js → checkbox-1G6KfK2q.js} +1 -1
  66. package/core/built/admin/assets/chunk-KS7C4IRE-DCsL4i4D.js +4 -0
  67. package/core/built/admin/assets/{chunk.408.1a258af360eb8d905508.js → chunk.301.5673a0caf7c49bb00212.js} +3 -3
  68. package/core/built/admin/assets/{chunk.524.4c693c3a00ac91df34fa.js → chunk.524.86db0d945468c852374b.js} +4 -4
  69. package/core/built/admin/assets/{chunk.582.20b600e329e58d12d7c2.js → chunk.582.c2f6216d3c4af303e236.js} +6 -6
  70. package/core/built/admin/assets/{code-editor-view-DqTOUnbM.js → code-editor-view-BVjtWyst.js} +1 -1
  71. package/core/built/admin/assets/{code-editor-view-bA0lfxUL.js → code-editor-view-y_zVfmCa.js} +1 -1
  72. package/core/built/admin/assets/color-picker-XEMPgNJE.js +3 -0
  73. package/core/built/admin/assets/{color-picker-field-Cv9GOJQZ.js → color-picker-field-BhplXcy5.js} +1 -1
  74. package/core/built/admin/assets/combobox-CRMJrvcQ.js +1 -0
  75. package/core/built/admin/assets/command-151T2eRx.js +1 -0
  76. package/core/built/admin/assets/comments-Bjbl9toF.js +1 -0
  77. package/core/built/admin/assets/confirmation-context-QmFxSjWy.js +1 -0
  78. package/core/built/admin/assets/{content-api-modal-BOFWRUkC.js → content-api-modal-CpeABXne.js} +1 -1
  79. package/core/built/admin/assets/{copy-field-8biQvNMD.js → copy-field-CglgRZyG.js} +1 -1
  80. package/core/built/admin/assets/custom-field-type-option-DSyJw4T4.js +1 -0
  81. package/core/built/admin/assets/custom-integration-modal-DWB9bRVp.js +1 -0
  82. package/core/built/admin/assets/date-range-select-kaKtab5O.js +1 -0
  83. package/core/built/admin/assets/design-and-theme-modal-86s2AaZ-.js +23 -0
  84. package/core/built/admin/assets/dirty-confirm-dialog-CTFdXXy7.js +1 -0
  85. package/core/built/admin/assets/disabled-sources-indicator-BFyCxUOx.js +1 -0
  86. package/core/built/admin/assets/dist-C1-Oy3Db.js +1 -0
  87. package/core/built/admin/assets/dist-CJJqYPep.js +1 -0
  88. package/core/built/admin/assets/dist-CNlMj7ML.js +1 -0
  89. package/core/built/admin/assets/dist-Kll2twSG.js +1 -0
  90. package/core/built/admin/assets/domain-BOoMvBG3.js +1 -0
  91. package/core/built/admin/assets/{dropzone-B21Entqz.js → dropzone-B6KWzsrb.js} +2 -2
  92. package/core/built/admin/assets/edit-profile-BagrYOrN.js +1 -0
  93. package/core/built/admin/assets/editor-AaJjqbpk.js +8 -0
  94. package/core/built/admin/assets/embed-signup-form-modal-B8L95KwR.js +8 -0
  95. package/core/built/admin/assets/explore-xbykZTlO.js +1 -0
  96. package/core/built/admin/assets/feed-BAHSipWv.js +1 -0
  97. package/core/built/admin/assets/{field-BQqLTgHM.js → field-kJYZ2T3C.js} +1 -1
  98. package/core/built/admin/assets/{filter-sources-FgqoUIL7.js → filter-sources-QeqFGNRB.js} +1 -1
  99. package/core/built/admin/assets/{filters-D0uIMD8n.js → filters-By5KQeV6.js} +1 -1
  100. package/core/built/admin/assets/filters-DtYpAkDd.js +1 -0
  101. package/core/built/admin/assets/first-promoter-modal-8A02oyjA.js +1 -0
  102. package/core/built/admin/assets/{ghost-ba408063a5922b458b4a8f216cf23251.js → ghost-f110822e67868c9fdf9a6c23754b2559.js} +9 -8
  103. package/core/built/admin/assets/gift-link-modal-DbYCyuhF.js +1 -0
  104. package/core/built/admin/assets/global-data-context-gAMDH9u1.js +1 -0
  105. package/core/built/admin/assets/growth-BSOxJGey.js +3 -0
  106. package/core/built/admin/assets/growth-BZTjURq5.js +1 -0
  107. package/core/built/admin/assets/history-modal-CCgWtlZE.js +1 -0
  108. package/core/built/admin/assets/html-field-BQC7XyLA.js +1 -0
  109. package/core/built/admin/assets/iframe-buffering-DnOlWTD4.js +1 -0
  110. package/core/built/admin/assets/{images-3cQgwfTD.js → images-D5UKr2SG.js} +1 -1
  111. package/core/built/admin/assets/{inbox-Dz_JblyJ.js → inbox-DvN15_YH.js} +45 -39
  112. package/core/built/admin/assets/index-BJz9Zlsc.js +67 -0
  113. package/core/built/admin/assets/index-Dg8cdR8E.css +1 -0
  114. package/core/built/admin/assets/{input-group-DpogNUIt.js → input-group-BPAxETpQ.js} +1 -1
  115. package/core/built/admin/assets/invite-user-modal-jcUaaNkW.js +1 -0
  116. package/core/built/admin/assets/{koenig-lexical-2KuqRwZI.js → koenig-lexical-CfXTYeli.js} +1 -1
  117. package/core/built/admin/assets/koenig-loader-DktFM3Uh.js +1 -0
  118. package/core/built/admin/assets/{kpi-card-Ck3tT2Wg.js → kpi-card-fUHZlzBX.js} +1 -1
  119. package/core/built/admin/assets/{kpi-card-DDIxptaS.js → kpi-card-o0urVtNL.js} +1 -1
  120. package/core/built/admin/assets/kpi-helpers-2FTF4a3-.js +1 -0
  121. package/core/built/admin/assets/kpi-tabs-B8-_k2dQ.js +1 -0
  122. package/core/built/admin/assets/{label-DodDsMzn.js → label-CiRS3i_f.js} +1 -1
  123. package/core/built/admin/assets/label-picker-ao7tuPaK.js +1 -0
  124. package/core/built/admin/assets/layout-DC6QGDaw.js +1 -0
  125. package/core/built/admin/assets/member-custom-fields-BW756-EV.js +1 -0
  126. package/core/built/admin/assets/member-detail-CGGgUTSl.js +1 -0
  127. package/core/built/admin/assets/members-ZcPthwG0.js +11 -0
  128. package/core/built/admin/assets/moderation-24bElWs3.js +1 -0
  129. package/core/built/admin/assets/multi-select-combobox-CZPrgz-Z.js +1 -0
  130. package/core/built/admin/assets/navigation-modal-BiTP4U_J.js +1 -0
  131. package/core/built/admin/assets/newsletter-B3Ihwizp.js +1 -0
  132. package/core/built/admin/assets/newsletter-detail-modal-CYsu4qhl.js +1 -0
  133. package/core/built/admin/assets/newsletter-emails-CDPR4-SP.js +1 -0
  134. package/core/built/admin/assets/newsletters-bJiYLJWl.js +1 -0
  135. package/core/built/admin/assets/note-7f6urVVA.js +1 -0
  136. package/core/built/admin/assets/notifications-rosQCyXH.js +1 -0
  137. package/core/built/admin/assets/offers-container-modal-J2j6NpDL.js +1 -0
  138. package/core/built/admin/assets/onboarding-route-DTjcThKl.js +1 -0
  139. package/core/built/admin/assets/overview-BxvWzGaf.js +1 -0
  140. package/core/built/admin/assets/overview-D6JG0hkZ.js +1 -0
  141. package/core/built/admin/assets/{page-header-VLPJfj_d.js → page-header-l7vXmLIw.js} +1 -1
  142. package/core/built/admin/assets/pintura-modal-BGHmudVB.js +1 -0
  143. package/core/built/admin/assets/portal-frame-DVc4VHX_.js +1 -0
  144. package/core/built/admin/assets/portal-modal-BCOcdiRi.js +1 -0
  145. package/core/built/admin/assets/{post-analytics-Gv81eN_g.js → post-analytics-CTSZMK6I.js} +1 -1
  146. package/core/built/admin/assets/post-analytics-header-Dt4RQGq0.js +1 -0
  147. package/core/built/admin/assets/{post-analytics-provider-O2YjZMhN.js → post-analytics-provider-C4ydiboO.js} +1 -1
  148. package/core/built/admin/assets/post-share-modal-DjH_a_1L.js +1 -0
  149. package/core/built/admin/assets/preferences-BLo6VJUS.js +5 -0
  150. package/core/built/admin/assets/preview-modal-BOgjgsJL.js +1 -0
  151. package/core/built/admin/assets/profile-DG6iCdRH.js +1 -0
  152. package/core/built/admin/assets/radio-group-ChHD4EQJ.js +1 -0
  153. package/core/built/admin/assets/router-provider-Bexg1t-V.js +1 -0
  154. package/core/built/admin/assets/select-ChxknEcT.js +1 -0
  155. package/core/built/admin/assets/settings-B0VDAhgV.js +1 -0
  156. package/core/built/admin/assets/settings-Dwo3CeAD.js +4 -0
  157. package/core/built/admin/assets/settings-breadcrumbs-sLDNedBW.js +1 -0
  158. package/core/built/admin/assets/share-modal-BDb2nacg.js +1 -0
  159. package/core/built/admin/assets/skeleton-D7hwjdry.js +1 -0
  160. package/core/built/admin/assets/slack-modal-CwNAD69x.js +1 -0
  161. package/core/built/admin/assets/sort-button-DhqpxkrO.js +1 -0
  162. package/core/built/admin/assets/source-icon-BPsD5ees.js +1 -0
  163. package/core/built/admin/assets/stats-WdDJ8c_1.js +1 -0
  164. package/core/built/admin/assets/stats-view-J3PI2eT_.js +1 -0
  165. package/core/built/admin/assets/step-1-2q1SjerQ.js +1 -0
  166. package/core/built/admin/assets/step-2-D5mJB3Uu.js +1 -0
  167. package/core/built/admin/assets/step-3-BpAuYTGZ.js +2 -0
  168. package/core/built/admin/assets/{stripe-button-Bh4p6Wud.js → stripe-button-DDoUKN5a.js} +1 -1
  169. package/core/built/admin/assets/stripe-connect-modal-pMdF3t4S.js +1 -0
  170. package/core/built/admin/assets/switch-B_1yU3Aw.js +1 -0
  171. package/core/built/admin/assets/{table-DghJxZMH.js → table-CrqQtkR2.js} +1 -1
  172. package/core/built/admin/assets/tabs-BhzVdh5p.js +1 -0
  173. package/core/built/admin/assets/tag-detail-CwJhI4qg.js +2 -0
  174. package/core/built/admin/assets/tags-CvJ0PgrB.js +1 -0
  175. package/core/built/admin/assets/testimonials-modal-CV82IAnI.js +1 -0
  176. package/core/built/admin/assets/tier-detail-modal-pLzCOfeg.js +1 -0
  177. package/core/built/admin/assets/toggle-group-BW_T4yn6.js +1 -0
  178. package/core/built/admin/assets/topic-filter-DkXjDejV.js +1 -0
  179. package/core/built/admin/assets/transistor-modal-TMRvHZ6B.js +1 -0
  180. package/core/built/admin/assets/unsplash-modal-CV92RdPw.js +1 -0
  181. package/core/built/admin/assets/{use-analytics-data-IxJcSjc9.js → use-analytics-data-BM_uV9SU.js} +1 -1
  182. package/core/built/admin/assets/use-check-theme-limit-error-DkBnXMSY.js +1 -0
  183. package/core/built/admin/assets/use-confirm-unload-SzlkOIpm.js +1 -0
  184. package/core/built/admin/assets/use-feature-flag-cahOlMva.js +1 -0
  185. package/core/built/admin/assets/use-filter-params-BX5qf0Cg.js +1 -0
  186. package/core/built/admin/assets/{use-growth-stats-1NW-8eEZ.js → use-growth-stats-D0LlWBe6.js} +1 -1
  187. package/core/built/admin/assets/{use-hash-link-navigation-guard-B9j6HIrV.js → use-hash-link-navigation-guard-B_Y3FhYH.js} +1 -1
  188. package/core/built/admin/assets/use-limiter-D3DHSoAj.js +2 -0
  189. package/core/built/admin/assets/{use-pintura-config-BjFChXV1.js → use-pintura-config-D63Xu0SG.js} +1 -1
  190. package/core/built/admin/assets/use-pintura-editor-Z51aFnC8.js +1 -0
  191. package/core/built/admin/assets/{use-post-referrers-Dp1BnTAK.js → use-post-referrers-Cvymr3gt.js} +1 -1
  192. package/core/built/admin/assets/use-query-params-BfzuqbcD.js +1 -0
  193. package/core/built/admin/assets/use-reply-chain-data-Dc18Ujqo.js +1 -0
  194. package/core/built/admin/assets/use-setting-group-DYRIzqC0.js +1 -0
  195. package/core/built/admin/assets/use-settings-navigation-aDSLtmEl.js +1 -0
  196. package/core/built/admin/assets/{use-simple-pagination-DsEGSJhe.js → use-simple-pagination-B2TdNX67.js} +1 -1
  197. package/core/built/admin/assets/{use-tinybird-query-Ci_DiPkX.js → use-tinybird-query-BvQIVuRc.js} +1 -1
  198. package/core/built/admin/assets/{use-tinybird-token-CsUwc722.js → use-tinybird-token-COQTPIwL.js} +1 -1
  199. package/core/built/admin/assets/use-upgrade-route-BZf1g59m.js +1 -0
  200. package/core/built/admin/assets/{use-url-input-BEZJpVgN.js → use-url-input-PcqZadXa.js} +1 -1
  201. package/core/built/admin/assets/user-detail-modal-DeO3SAZq.js +1 -0
  202. package/core/built/admin/assets/{virtual-list-D5ugXyZY.js → virtual-list-D3MfPNOh.js} +1 -1
  203. package/core/built/admin/assets/web-CDdyG7z1.js +2 -0
  204. package/core/built/admin/assets/web-CwQ8_QUc.js +1 -0
  205. package/core/built/admin/assets/zapier-modal-a7t75iNI.js +1 -0
  206. package/core/built/admin/index.html +15 -12
  207. package/core/frontend/apps/private-blogging/lib/helpers/input_password.js +2 -1
  208. package/core/frontend/helpers/body_class.js +2 -1
  209. package/core/frontend/helpers/cancel_link.js +2 -1
  210. package/core/frontend/helpers/color_to_rgba.js +2 -1
  211. package/core/frontend/helpers/content_api_key.js +2 -1
  212. package/core/frontend/helpers/content_api_url.js +2 -1
  213. package/core/frontend/helpers/contrast_text_color.js +2 -1
  214. package/core/frontend/helpers/facebook_url.js +2 -1
  215. package/core/frontend/helpers/ghost_foot.js +2 -1
  216. package/core/frontend/helpers/ghost_head.js +2 -1
  217. package/core/frontend/helpers/has.js +4 -2
  218. package/core/frontend/helpers/link_class.js +2 -1
  219. package/core/frontend/helpers/meta_description.js +2 -1
  220. package/core/frontend/helpers/meta_title.js +2 -1
  221. package/core/frontend/helpers/navigation.js +113 -22
  222. package/core/frontend/helpers/page_url.js +2 -1
  223. package/core/frontend/helpers/post_class.js +2 -1
  224. package/core/frontend/helpers/reading_time.js +2 -1
  225. package/core/frontend/helpers/social_accounts.js +2 -1
  226. package/core/frontend/helpers/social_url.js +2 -1
  227. package/core/frontend/helpers/total_members.js +2 -1
  228. package/core/frontend/helpers/total_paid_members.js +2 -1
  229. package/core/frontend/helpers/tpl/navigation.hbs +1 -1
  230. package/core/frontend/helpers/twitter_url.js +2 -1
  231. package/core/frontend/public/cards.manifest.json +1 -1
  232. package/core/server/api/endpoints/posts.js +7 -4
  233. package/core/server/api/endpoints/utils/permissions.js +2 -1
  234. package/core/server/api/endpoints/utils/validators/input/settings.js +83 -1
  235. package/core/server/data/exporter/table-lists.js +1 -0
  236. package/core/server/data/importer/handlers/json.js +2 -1
  237. package/core/server/data/migrations/versions/5.19/2022-09-02-20-52-backfill-new-product-columns.js +2 -1
  238. package/core/server/data/migrations/versions/5.21/2022-10-26-04-50-member-subscription-created-batch-id.js +4 -2
  239. package/core/server/data/migrations/versions/5.22/2022-10-31-12-03-backfill-new-product-columns.js +2 -1
  240. package/core/server/data/migrations/versions/6.58/2026-08-11-21-22-48-rename-email-batches-provider-id.js +1 -1
  241. package/core/server/data/migrations/versions/6.59/2026-08-18-17-06-46-add-gift-delivery-fields-to-gifts.js +26 -0
  242. package/core/server/data/migrations/versions/6.59/2026-08-18-17-07-01-add-gift-deliveries.js +20 -0
  243. package/core/server/data/schema/commands.js +48 -24
  244. package/core/server/data/schema/schema.js +28 -7
  245. package/core/server/data/seeders/importers/automation-action-revisions-importer.js +3 -36
  246. package/core/server/data/seeders/importers/automation-actions-importer.js +5 -41
  247. package/core/server/data/seeders/importers/automation-run-steps-importer.js +3 -36
  248. package/core/server/data/seeders/importers/automation-runs-importer.js +7 -42
  249. package/core/server/data/seeders/importers/automations-importer.js +3 -36
  250. package/core/server/data/seeders/importers/comment-reports-importer.js +5 -5
  251. package/core/server/data/seeders/importers/comments-importer.js +5 -5
  252. package/core/server/data/seeders/importers/email-batches-importer.js +5 -4
  253. package/core/server/data/seeders/importers/email-recipients-importer.js +12 -20
  254. package/core/server/data/seeders/importers/emails-importer.js +6 -6
  255. package/core/server/data/seeders/importers/labels-importer.js +3 -3
  256. package/core/server/data/seeders/importers/members-click-events-importer.js +5 -5
  257. package/core/server/data/seeders/importers/members-created-events-importer.js +13 -12
  258. package/core/server/data/seeders/importers/members-feedback-importer.js +7 -7
  259. package/core/server/data/seeders/importers/members-importer.js +4 -4
  260. package/core/server/data/seeders/importers/members-login-events-importer.js +3 -3
  261. package/core/server/data/seeders/importers/members-products-importer.js +2 -2
  262. package/core/server/data/seeders/importers/members-status-events-importer.js +4 -3
  263. package/core/server/data/seeders/importers/members-stripe-customers-importer.js +2 -2
  264. package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +7 -7
  265. package/core/server/data/seeders/importers/members-subscribe-events-importer.js +3 -3
  266. package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +3 -3
  267. package/core/server/data/seeders/importers/offer-redemptions-importer.js +7 -7
  268. package/core/server/data/seeders/importers/offers-importer.js +3 -3
  269. package/core/server/data/seeders/importers/posts-importer.js +4 -4
  270. package/core/server/data/seeders/importers/table-importer.js +2 -35
  271. package/core/server/data/seeders/importers/tags-importer.js +2 -2
  272. package/core/server/data/seeders/importers/users-importer.js +2 -2
  273. package/core/server/data/seeders/importers/web-mentions-importer.js +2 -2
  274. package/core/server/data/seeders/utils/random.js +7 -0
  275. package/core/server/ghost-server.js +19 -5
  276. package/core/server/lib/db-date.js +72 -3
  277. package/core/server/models/gift-delivery.js +23 -0
  278. package/core/server/models/gift.js +9 -0
  279. package/core/server/models/index.js +3 -2
  280. package/core/server/models/settings.js +43 -4
  281. package/core/server/services/automations/database-automations-repository.js +38 -31
  282. package/core/server/services/automations/poll.js +1 -1
  283. package/core/server/services/content-import/csv/index.js +8 -0
  284. package/core/server/services/content-import/csv/parse.js +60 -0
  285. package/core/server/services/content-import/import/importer.js +222 -0
  286. package/core/server/services/content-import/import/post-data.js +52 -0
  287. package/core/server/services/content-import/import/reader.js +15 -0
  288. package/core/server/services/content-import/import/row.js +50 -0
  289. package/core/server/services/content-import/import/store.js +71 -0
  290. package/core/server/services/content-import/index.js +67 -0
  291. package/core/server/services/email-analytics/automation-email-analytics-batch-processor.js +1 -1
  292. package/core/server/services/email-analytics/index.js +3 -1
  293. package/core/server/services/email-analytics/jobs/index.js +2 -2
  294. package/core/server/services/email-service/email-service-wrapper.js +1 -1
  295. package/core/server/services/email-suppression-list/email-suppression-list.js +4 -2
  296. package/core/server/services/gifts/CONTEXT.md +27 -3
  297. package/core/server/services/gifts/README.md +8 -4
  298. package/core/server/services/gifts/constants.js +2 -1
  299. package/core/server/services/gifts/email-templates/gift-delivery.hbs +20 -0
  300. package/core/server/services/gifts/email-templates/gift-delivery.js +39 -0
  301. package/core/server/services/gifts/email-templates/gift-purchase-confirmation.hbs +2 -2
  302. package/core/server/services/gifts/email-templates/gift-purchase-confirmation.js +19 -4
  303. package/core/server/services/gifts/events/send-gift-delivery-event.js +15 -0
  304. package/core/server/services/gifts/gift-bookshelf-repository.js +23 -3
  305. package/core/server/services/gifts/gift-checkout-adapter.js +5 -3
  306. package/core/server/services/gifts/gift-codec.js +8 -0
  307. package/core/server/services/gifts/gift-delivery-bookshelf-repository.js +107 -0
  308. package/core/server/services/gifts/gift-delivery-codec.js +12 -0
  309. package/core/server/services/gifts/gift-delivery-schema.js +15 -0
  310. package/core/server/services/gifts/gift-delivery-service.js +209 -0
  311. package/core/server/services/gifts/gift-email-renderer.js +13 -1
  312. package/core/server/services/gifts/gift-email-service.js +97 -17
  313. package/core/server/services/gifts/gift-schema.js +10 -6
  314. package/core/server/services/gifts/gift-service.js +259 -34
  315. package/core/server/services/gifts/gift.js +59 -5
  316. package/core/server/services/gifts/index.js +69 -3
  317. package/core/server/services/lib/mailgun-client.js +5 -2
  318. package/core/server/services/mail/ghost-mailer.js +9 -1
  319. package/core/server/services/member-welcome-emails/service.js +6 -1
  320. package/core/server/services/members/members-api/controllers/router-controller.js +26 -8
  321. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +100 -12
  322. package/core/server/services/stripe/stripe-api.js +3 -2
  323. package/core/server/services/stripe/webhook-controller.js +6 -3
  324. package/core/server/services/stripe/webhook-manager.js +2 -0
  325. package/core/shared/labs.js +1 -0
  326. package/package.json +9 -8
  327. package/pnpm-lock.yaml +26 -11
  328. package/pnpm-workspace.yaml +5 -6
  329. package/content/themes/source/yarn.lock +0 -6232
  330. package/core/built/admin/assets/Combination-BDvCdg1t.js +0 -41
  331. package/core/built/admin/assets/about-B_BvSyLF.js +0 -1
  332. package/core/built/admin/assets/account-migration-oJUGxnzT.js +0 -1
  333. package/core/built/admin/assets/activitypub/dist-Dqggkccr.js +0 -215
  334. package/core/built/admin/assets/activitypub/inbox-z1o2j1Tb.js +0 -1221
  335. package/core/built/admin/assets/activitypub/layout-BRMezuty.js +0 -1
  336. package/core/built/admin/assets/activitypub/moderation-CZudJi8N.js +0 -160
  337. package/core/built/admin/assets/activitypub/settings-CrSV1cux.js +0 -107
  338. package/core/built/admin/assets/add-integration-modal-BW23PDYs.js +0 -1
  339. package/core/built/admin/assets/add-newsletter-modal-M0ls8BKO.js +0 -1
  340. package/core/built/admin/assets/add-recommendation-modal-B0NeBMzl.js +0 -1
  341. package/core/built/admin/assets/announcement-bar-modal-pXBqjQE_.js +0 -1
  342. package/core/built/admin/assets/api-keys-Df6uv1dT.js +0 -1
  343. package/core/built/admin/assets/audience-BY8od8If.js +0 -1
  344. package/core/built/admin/assets/automations-DCs6Wady.js +0 -1
  345. package/core/built/admin/assets/bluesky-sharing-D0P42Cws.js +0 -1
  346. package/core/built/admin/assets/breadcrumb-MTj_cKLb.js +0 -1
  347. package/core/built/admin/assets/chart-helpers-DcO3k_vA.js +0 -1
  348. package/core/built/admin/assets/chunk-KS7C4IRE-DG9HTHQ3.js +0 -4
  349. package/core/built/admin/assets/color-picker-CBlJMv3y.js +0 -3
  350. package/core/built/admin/assets/combobox-BbPJzjCp.js +0 -1
  351. package/core/built/admin/assets/command-BrhXIlDr.js +0 -1
  352. package/core/built/admin/assets/comments-Cu4rD1aJ.js +0 -1
  353. package/core/built/admin/assets/confirmation-provider-BEdrnHns.js +0 -1
  354. package/core/built/admin/assets/custom-field-type-option-CNhAgHbJ.js +0 -1
  355. package/core/built/admin/assets/custom-integration-modal-CrGOI8IB.js +0 -1
  356. package/core/built/admin/assets/date-range-select-Bsh4gIek.js +0 -1
  357. package/core/built/admin/assets/design-and-theme-modal-DWZguwWX.js +0 -23
  358. package/core/built/admin/assets/dirty-confirm-dialog-DmdMpgyN.js +0 -1
  359. package/core/built/admin/assets/disabled-sources-indicator-BLnmyWgt.js +0 -1
  360. package/core/built/admin/assets/dist-DMFGvqqU.js +0 -1
  361. package/core/built/admin/assets/dist-DlCyLrOC.js +0 -1
  362. package/core/built/admin/assets/dist-j1xxxnY8.js +0 -1
  363. package/core/built/admin/assets/domain-Dp9TRrFp.js +0 -1
  364. package/core/built/admin/assets/edit-profile-DG6lpMsr.js +0 -1
  365. package/core/built/admin/assets/editor-CPQOrhrn.js +0 -8
  366. package/core/built/admin/assets/embed-signup-form-modal-CgWniDha.js +0 -9
  367. package/core/built/admin/assets/explore-C3rzONJv.js +0 -1
  368. package/core/built/admin/assets/feed-DpUwS1cU.js +0 -1
  369. package/core/built/admin/assets/filters-DZJT29GW.js +0 -1
  370. package/core/built/admin/assets/first-promoter-modal-DJhQQheG.js +0 -1
  371. package/core/built/admin/assets/gift-link-modal-sdFLjND9.js +0 -1
  372. package/core/built/admin/assets/global-data-provider-CixmTX00.js +0 -1
  373. package/core/built/admin/assets/growth-0YYkBEeS.js +0 -1
  374. package/core/built/admin/assets/growth-DpjAPeQT.js +0 -3
  375. package/core/built/admin/assets/history-modal-BWJ4-cJ0.js +0 -1
  376. package/core/built/admin/assets/html-field-C-oa5DEl.js +0 -1
  377. package/core/built/admin/assets/iframe-buffering-Ba9sDpi2.js +0 -1
  378. package/core/built/admin/assets/index-BOJzlYiz.js +0 -67
  379. package/core/built/admin/assets/index-Bid6LAyc.css +0 -1
  380. package/core/built/admin/assets/invite-user-modal-DCiQSiuD.js +0 -1
  381. package/core/built/admin/assets/koenig-editor-base-BApy8Q7C.js +0 -1
  382. package/core/built/admin/assets/kpi-helpers-ssbuy-hD.js +0 -1
  383. package/core/built/admin/assets/kpi-tabs-BqoIM6bI.js +0 -1
  384. package/core/built/admin/assets/label-picker-Cvvbmzb5.js +0 -1
  385. package/core/built/admin/assets/layout-DkkGyev2.js +0 -1
  386. package/core/built/admin/assets/member-custom-fields-CvGcpaiW.js +0 -1
  387. package/core/built/admin/assets/member-detail-CMCMZ47_.js +0 -1
  388. package/core/built/admin/assets/members-sRl6fNfq.js +0 -11
  389. package/core/built/admin/assets/moderation-KhKwGqI1.js +0 -1
  390. package/core/built/admin/assets/multi-select-combobox-nhCff0_K.js +0 -1
  391. package/core/built/admin/assets/navigation-modal-MDJExWDJ.js +0 -1
  392. package/core/built/admin/assets/newsletter-BIBHumfV.js +0 -1
  393. package/core/built/admin/assets/newsletter-detail-modal-CfjBMH3v.js +0 -1
  394. package/core/built/admin/assets/newsletter-emails-ClDL-7dA.js +0 -1
  395. package/core/built/admin/assets/newsletters-CBe-IPzK.js +0 -1
  396. package/core/built/admin/assets/note-HnNUOt4d.js +0 -1
  397. package/core/built/admin/assets/notifications-Cj5e-258.js +0 -1
  398. package/core/built/admin/assets/offers-container-modal-BHwHtixC.js +0 -1
  399. package/core/built/admin/assets/onboarding-route-D_4jp7Nq.js +0 -1
  400. package/core/built/admin/assets/overview-CezDdg-F.js +0 -1
  401. package/core/built/admin/assets/overview-D5mM51Lj.js +0 -1
  402. package/core/built/admin/assets/pintura-modal-BXXJ8Iaa.js +0 -1
  403. package/core/built/admin/assets/portal-frame-BLCvF8f-.js +0 -1
  404. package/core/built/admin/assets/portal-modal-DN80tAcy.js +0 -1
  405. package/core/built/admin/assets/post-analytics-header-_ydSY8qO.js +0 -1
  406. package/core/built/admin/assets/post-share-modal-CzYak_a5.js +0 -1
  407. package/core/built/admin/assets/preferences-gneLb6LV.js +0 -5
  408. package/core/built/admin/assets/preview-modal-Bkq-tgVm.js +0 -1
  409. package/core/built/admin/assets/profile-CzXmCNTC.js +0 -1
  410. package/core/built/admin/assets/radio-group-Dsq5ZEbV.js +0 -1
  411. package/core/built/admin/assets/router-provider-CkB_ArLF.js +0 -1
  412. package/core/built/admin/assets/select-xuZAuDyS.js +0 -1
  413. package/core/built/admin/assets/settings-Ct8pabhr.js +0 -1
  414. package/core/built/admin/assets/settings-KSWWQanQ.js +0 -4
  415. package/core/built/admin/assets/settings-breadcrumbs-DbPGu-Tu.js +0 -1
  416. package/core/built/admin/assets/share-modal-CdiUx9WM.js +0 -1
  417. package/core/built/admin/assets/slack-modal-BP--Z3jE.js +0 -1
  418. package/core/built/admin/assets/sort-button-CjfKdlsL.js +0 -1
  419. package/core/built/admin/assets/source-icon-DHj1h6Ts.js +0 -1
  420. package/core/built/admin/assets/stats-C615kulJ.js +0 -1
  421. package/core/built/admin/assets/stats-view-BSMRdYiN.js +0 -1
  422. package/core/built/admin/assets/step-1-pSdGYtIF.js +0 -1
  423. package/core/built/admin/assets/step-2-T9Uy4eBD.js +0 -1
  424. package/core/built/admin/assets/step-3-D4uXe_XG.js +0 -2
  425. package/core/built/admin/assets/stripe-connect-modal-C2vTvepi.js +0 -1
  426. package/core/built/admin/assets/switch-CE2NuMn-.js +0 -1
  427. package/core/built/admin/assets/tabs-l9c7FjzI.js +0 -1
  428. package/core/built/admin/assets/tag-detail-DBw_M2-p.js +0 -2
  429. package/core/built/admin/assets/tags-DoWsAG6l.js +0 -1
  430. package/core/built/admin/assets/testimonials-modal-BZKePE2T.js +0 -1
  431. package/core/built/admin/assets/tier-detail-modal-D65pVWsN.js +0 -1
  432. package/core/built/admin/assets/toggle-group-uJnl42w3.js +0 -1
  433. package/core/built/admin/assets/topic-filter-BM7hz0kW.js +0 -1
  434. package/core/built/admin/assets/transistor-modal-VZOGN3od.js +0 -1
  435. package/core/built/admin/assets/unsplash-modal-CKGfACxF.js +0 -1
  436. package/core/built/admin/assets/use-check-theme-limit-error-CGp3CGT5.js +0 -1
  437. package/core/built/admin/assets/use-feature-flag-CSq2Lwzt.js +0 -1
  438. package/core/built/admin/assets/use-filter-params-0hcJJuEJ.js +0 -1
  439. package/core/built/admin/assets/use-limiter-ffwpsZPp.js +0 -2
  440. package/core/built/admin/assets/use-pintura-editor-Cb41oJyL.js +0 -1
  441. package/core/built/admin/assets/use-query-params-DHhhu8wi.js +0 -1
  442. package/core/built/admin/assets/use-reply-chain-data-C0ZUTkfk.js +0 -1
  443. package/core/built/admin/assets/use-setting-group-Bx8M6FL2.js +0 -1
  444. package/core/built/admin/assets/use-settings-navigation-BOZcat_V.js +0 -1
  445. package/core/built/admin/assets/use-upgrade-route-B3_DUiuT.js +0 -1
  446. package/core/built/admin/assets/user-detail-modal-595z1N5r.js +0 -1
  447. package/core/built/admin/assets/web-CAta3lRE.js +0 -1
  448. package/core/built/admin/assets/web-Ds9_wWJK.js +0 -2
  449. package/core/built/admin/assets/zapier-modal-CcfDZY53.js +0 -1
  450. package/core/server/data/seeders/utils/database-date.js +0 -28
  451. package/core/server/services/automations/database-date.js +0 -59
  452. /package/core/built/admin/assets/{dist-CqO2lWS02.js → dist-CqO2lWS0.js} +0 -0
  453. /package/core/server/services/{automations → lib}/mailgun-message-id.js +0 -0
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RowSkipped = void 0;
4
+ exports.default = buildPostData;
5
+ const row_1 = require("./row");
6
+ const { slugify } = require('@tryghost/string');
7
+ // A malformed row, refused before any write was attempted; distinct from a write
8
+ // that failed, which the importer records separately.
9
+ class RowSkipped extends Error {
10
+ constructor(reason) {
11
+ super(reason);
12
+ this.name = 'RowSkipped';
13
+ }
14
+ }
15
+ exports.RowSkipped = RowSkipped;
16
+ function buildPostData(row, htmlToLexical, importTagNames) {
17
+ const check = row_1.importableRowSchema.safeParse(row);
18
+ if (!check.success) {
19
+ throw new RowSkipped(check.error.issues[0].message);
20
+ }
21
+ const data = {
22
+ title: row.title,
23
+ // Slugified here with the standard rules: left to the model, the
24
+ // importing-context slug pass keeps every punctuation dash
25
+ // (slugify requiredChangesOnly).
26
+ slug: slugify(row.title),
27
+ // Explicit rather than left to the model, which would default status to draft and
28
+ // visibility to the default_content_visibility setting. Never 'scheduled': the
29
+ // post scheduler has no importing check.
30
+ status: 'published',
31
+ type: 'post',
32
+ visibility: 'public',
33
+ // The # prefix gives the batch tags internal visibility.
34
+ tags: importTagNames.map(name => ({ name }))
35
+ };
36
+ if (row.html) {
37
+ try {
38
+ data.lexical = JSON.stringify(htmlToLexical(row.html));
39
+ }
40
+ catch {
41
+ throw new RowSkipped('html could not be converted');
42
+ }
43
+ }
44
+ // The one date column dates the whole post; preserved only because the write runs
45
+ // under options.importing, which stops the model stamping its own timestamps.
46
+ if (row.published_at) {
47
+ data.published_at = row.published_at;
48
+ data.created_at = row.published_at;
49
+ data.updated_at = row.published_at;
50
+ }
51
+ return data;
52
+ }
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = readPostRows;
4
+ const csv_1 = require("../csv");
5
+ const row_1 = require("./row");
6
+ // Identity map until the field-mapping milestone adds caller-supplied mappings.
7
+ const FIELD_BY_HEADER = {
8
+ title: 'title',
9
+ html: 'html',
10
+ published_at: 'published_at'
11
+ };
12
+ async function readPostRows(path) {
13
+ const rows = await (0, csv_1.parse)(path, FIELD_BY_HEADER);
14
+ return rows.map(row => row_1.postImportRowSchema.parse(row));
15
+ }
@@ -0,0 +1,50 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.importableRowSchema = exports.postImportRowSchema = void 0;
4
+ const zod_1 = require("zod");
5
+ const temporal_polyfill_1 = require("temporal-polyfill");
6
+ // An empty cell (or the literal 'undefined') reads as absent, not as a value.
7
+ const optionalCell = zod_1.z.string()
8
+ .transform(cell => (cell === '' || cell === 'undefined' ? undefined : cell))
9
+ .optional();
10
+ // title is trimmed here because the model's own trim is skipped under
11
+ // options.importing; unknown columns pass through (.loose()) for field mapping.
12
+ exports.postImportRowSchema = zod_1.z.object({
13
+ title: zod_1.z.string().default('').transform(cell => cell.trim()),
14
+ html: zod_1.z.string().default(''),
15
+ published_at: optionalCell
16
+ }).loose();
17
+ // Validation for coerced rows; buildPostData turns the first issue into a row
18
+ // skip. A blank title is refused because the model's (Untitled) fallback is
19
+ // skipped under options.importing; an invalid date is refused rather than
20
+ // silently replaced with the import time (what the JSON importer does). The
21
+ // title cap matches the posts schema's isLength validation.
22
+ exports.importableRowSchema = zod_1.z.object({
23
+ title: zod_1.z.string()
24
+ .min(1, 'title is required')
25
+ .max(255, 'title must be 255 characters or fewer')
26
+ }).loose().superRefine((row, ctx) => {
27
+ const publishedAt = row.published_at;
28
+ if (typeof publishedAt === 'string' && !isValidDate(publishedAt)) {
29
+ ctx.addIssue({ code: 'custom', message: `published_at is not a valid date: "${publishedAt}"` });
30
+ }
31
+ });
32
+ // new Date() alone is not enough: it normalizes rolled-over calendar dates, so
33
+ // 2025-02-30 silently becomes March 2. For ISO-shaped values the calendar components
34
+ // are checked strictly with Temporal; other formats stay as loose as new Date().
35
+ function isValidDate(value) {
36
+ if (Number.isNaN(new Date(value).getTime())) {
37
+ return false;
38
+ }
39
+ const isoDate = /^(\d{4})-(\d{2})-(\d{2})/.exec(value);
40
+ if (!isoDate) {
41
+ return true;
42
+ }
43
+ try {
44
+ temporal_polyfill_1.Temporal.PlainDate.from({ year: Number(isoDate[1]), month: Number(isoDate[2]), day: Number(isoDate[3]) }, { overflow: 'reject' });
45
+ return true;
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ // The in-memory record of what a content import did: one run per upload, one
3
+ // outcome per row. Nothing is persisted; the durable job system milestone
4
+ // replaces this.
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ImportRunStore = void 0;
7
+ // Both bounds are applied lazily on create() so no timer keeps the process alive.
8
+ const MAX_RUNS = 10;
9
+ const MAX_RUN_AGE_MS = 60 * 60 * 1000;
10
+ class ImportRunStore {
11
+ // Insertion-ordered, so count-eviction drops the oldest run first.
12
+ _runs = new Map();
13
+ _now;
14
+ constructor({ now = () => new Date() } = {}) {
15
+ this._now = now;
16
+ }
17
+ create(id, total) {
18
+ this.evict();
19
+ const run = {
20
+ id,
21
+ status: 'running',
22
+ startedAt: this._now(),
23
+ total,
24
+ rows: []
25
+ };
26
+ this._runs.set(id, run);
27
+ return run;
28
+ }
29
+ record(id, outcome) {
30
+ this._runs.get(id)?.rows.push(outcome);
31
+ }
32
+ finish(id) {
33
+ const run = this._runs.get(id);
34
+ if (run) {
35
+ run.status = 'complete';
36
+ run.finishedAt = this._now();
37
+ }
38
+ }
39
+ fail(id, reason) {
40
+ const run = this._runs.get(id);
41
+ if (run) {
42
+ run.status = 'failed';
43
+ run.failureReason = reason;
44
+ run.finishedAt = this._now();
45
+ }
46
+ }
47
+ get(id) {
48
+ return this._runs.get(id);
49
+ }
50
+ // A running run is never evicted, whatever its age: the job holds only the runId,
51
+ // so evicting mid-import would silently turn its record()/finish() calls into
52
+ // no-ops and lose the report. The count cap can briefly overshoot while several
53
+ // imports run at once; the inline job queue bounds how many that can be.
54
+ evict() {
55
+ const cutoff = this._now().getTime() - MAX_RUN_AGE_MS;
56
+ for (const [id, run] of this._runs) {
57
+ const lastTouched = (run.finishedAt ?? run.startedAt).getTime();
58
+ if (run.status !== 'running' && lastTouched < cutoff) {
59
+ this._runs.delete(id);
60
+ }
61
+ }
62
+ while (this._runs.size >= MAX_RUNS) {
63
+ const oldestEvictable = [...this._runs.values()].find(run => run.status !== 'running');
64
+ if (!oldestEvictable) {
65
+ break;
66
+ }
67
+ this._runs.delete(oldestEvictable.id);
68
+ }
69
+ }
70
+ }
71
+ exports.ImportRunStore = ImportRunStore;
@@ -0,0 +1,67 @@
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.init = init;
7
+ exports.importCSV = importCSV;
8
+ const zod_1 = require("zod");
9
+ const importer_1 = __importDefault(require("./import/importer"));
10
+ const reader_1 = __importDefault(require("./import/reader"));
11
+ const store_1 = require("./import/store");
12
+ // The request is built from HTTP upload metadata, so it is validated at the
13
+ // service boundary rather than trusted.
14
+ const importRequestSchema = zod_1.z.object({ filePath: zod_1.z.string().min(1) });
15
+ // A junk timezone setting falls back to UTC rather than mis-stamping the batch tag.
16
+ const timezoneSchema = zod_1.z.string().min(1).catch('Etc/UTC');
17
+ const errors = require('@tryghost/errors');
18
+ const logging = require('@tryghost/logging');
19
+ const sentry = require('../../../shared/sentry');
20
+ // Composition root: models and services are wired behind the collaborators the
21
+ // importer declares.
22
+ function makeImporter() {
23
+ // Required lazily: boot initialises this service before the model and job layers
24
+ // are guaranteed loaded.
25
+ const models = require('../../models');
26
+ const lexicalLib = require('../../lib/lexical');
27
+ const jobsService = require('../jobs');
28
+ const settingsCache = require('../../../shared/settings-cache');
29
+ const urlService = require('../url');
30
+ const ObjectID = require('bson-objectid').default;
31
+ // Inline jobs never reach the job manager's Sentry handler, which is wired to the
32
+ // offloaded worker path only, so a throw here would be seen by nobody.
33
+ const report = (error) => {
34
+ try {
35
+ logging.error({ event: { name: 'content.import.error' }, err: error }, 'Content import failure');
36
+ sentry.captureException(error);
37
+ }
38
+ catch {
39
+ // Callers report from catch blocks, so this must not throw.
40
+ }
41
+ };
42
+ return new importer_1.default({
43
+ readRows: reader_1.default,
44
+ posts: {
45
+ create: (data, options) => models.Post.add(data, options)
46
+ },
47
+ getHtmlToLexical: () => lexicalLib.htmlToLexicalConverter,
48
+ addJob: jobsService.addJob.bind(jobsService),
49
+ report,
50
+ store: new store_1.ImportRunStore(),
51
+ // Degrades to the 404 URL for a post the URL service cannot route yet (e.g. a draft).
52
+ urlForPost: post => urlService.getUrlForResource({ ...post.toJSON(), type: 'posts' }, { absolute: true }),
53
+ newRunId: () => new ObjectID().toHexString(),
54
+ getTimezone: () => timezoneSchema.parse(settingsCache.get('timezone'))
55
+ });
56
+ }
57
+ let importer;
58
+ // Idempotent because tests may boot more than once per process.
59
+ function init() {
60
+ importer ??= makeImporter();
61
+ }
62
+ function importCSV(request) {
63
+ if (!importer) {
64
+ throw new errors.InternalServerError({ message: 'Content import service used before init' });
65
+ }
66
+ return importer.importCSV(importRequestSchema.parse(request));
67
+ }
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.AutomationEmailAnalyticsBatchProcessor = void 0;
4
- const mailgun_message_id_1 = require("../automations/mailgun-message-id");
4
+ const mailgun_message_id_1 = require("../lib/mailgun-message-id");
5
5
  const event_processing_result_1 = require("./event-processing-result");
6
6
  const getMailgunMessageIds = (events) => {
7
7
  const result = new Set();
@@ -8,6 +8,8 @@ const email_analytics_service_wrapper_1 = require("./email-analytics-service-wra
8
8
  // @ts-expect-error This module lacks type definitions.
9
9
  const newsletter_email_analytics_batch_processor_1 = require("./newsletter-email-analytics-batch-processor");
10
10
  // @ts-expect-error This module lacks type definitions.
11
+ const newsletter_email_analytics_batch_processor_2 = require("./newsletter-email-analytics-batch-processor");
12
+ // @ts-expect-error This module lacks type definitions.
11
13
  const newsletter_email_event_storage_1 = __importDefault(require("../email-service/newsletter-email-event-storage"));
12
14
  // @ts-expect-error This module lacks type definitions.
13
15
  const email_event_processor_1 = __importDefault(require("../email-service/email-event-processor"));
@@ -71,7 +73,7 @@ const init = ({ automationsApi, config, db, domainEvents, emailSuppressionList,
71
73
  },
72
74
  metrics,
73
75
  settingsCache,
74
- createEventProcessor: () => (new newsletter_email_analytics_batch_processor_1.NewsletterEmailAnalyticsBatchProcessor({
76
+ createEventProcessor: () => (new newsletter_email_analytics_batch_processor_2.NewsletterEmailAnalyticsBatchProcessor({
75
77
  config,
76
78
  emailEventProcessor: newsletterEmailEventProcessor,
77
79
  prometheusClient,
@@ -14,7 +14,7 @@ const emailAnalyticsJobScheduler = new EmailAnalyticsJobScheduler({
14
14
  * @returns {Promise<void>}
15
15
  */
16
16
  exports.scheduleRecurringNewslettersJob = async (...args) => {
17
- if (!process.env.NODE_ENV.startsWith('test')) {
17
+ if (!process.env.NODE_ENV?.startsWith('test')) {
18
18
  await emailAnalyticsJobScheduler.scheduleRecurringNewslettersJob(...args);
19
19
  }
20
20
  };
@@ -24,7 +24,7 @@ exports.scheduleRecurringNewslettersJob = async (...args) => {
24
24
  * @returns {Promise<void>}
25
25
  */
26
26
  exports.scheduleRecurringAutomationsJob = async (...args) => {
27
- if (!process.env.NODE_ENV.startsWith('test')) {
27
+ if (!process.env.NODE_ENV?.startsWith('test')) {
28
28
  await emailAnalyticsJobScheduler.scheduleRecurringAutomationsJob(...args);
29
29
  }
30
30
  };
@@ -136,7 +136,7 @@ class EmailServiceWrapper {
136
136
  });
137
137
 
138
138
  if (ghostServer) {
139
- ghostServer.registerCleanupTask(() => batchSendingService.onShutdown());
139
+ ghostServer.registerCleanupTask(() => batchSendingService.onShutdown(), 'Email batch sending');
140
140
  }
141
141
 
142
142
  this.renderer = emailRenderer;
@@ -63,7 +63,8 @@ class AbstractEmailSuppressionList {
63
63
  * @param {string} email
64
64
  * @returns {Promise<boolean>}
65
65
  */
66
- async removeEmail(email) { // eslint-disable-line
66
+ // eslint-disable-next-line
67
+ async removeEmail(email) {
67
68
  return Promise.reject();
68
69
  }
69
70
 
@@ -71,7 +72,8 @@ class AbstractEmailSuppressionList {
71
72
  * @param {string} email
72
73
  * @returns {Promise<EmailSuppressionData>}
73
74
  */
74
- async getSuppressionData(email) { // eslint-disable-line
75
+ // eslint-disable-next-line
76
+ async getSuppressionData(email) {
75
77
  return Promise.reject();
76
78
  }
77
79
 
@@ -14,6 +14,10 @@ _Avoid_: Purchaser, recipient, redeemer
14
14
  The member who claims a gift subscription and receives its access. The redeemer is determined at redemption, not purchase.
15
15
  _Avoid_: Recipient, buyer
16
16
 
17
+ **Recipient**:
18
+ The person for whom a gift subscription is intended. Their email routes delivery but does not reserve redemption; they need not be a member and do not become the redeemer until they claim the gift.
19
+ _Avoid_: Receiver, redeemer
20
+
17
21
  ### Purchase and claim
18
22
 
19
23
  **Gift subscription**:
@@ -21,17 +25,37 @@ A prepaid, non-renewing entitlement to one membership tier for a fixed period. I
21
25
  _Avoid_: Gift membership, recurring subscription
22
26
 
23
27
  **Gift purchase**:
24
- A one-time purchase that creates a gift subscription for the buyer to share.
28
+ A one-time purchase that creates a gift subscription for the buyer to share. Completing a gift purchase does not mean that an emailed gift has been sent.
25
29
  _Avoid_: Gift checkout
26
30
 
31
+ **Delivery method**:
32
+ The buyer's chosen way of handing over a gift subscription: the publication emails it to a recipient, or the buyer shares the redemption link privately.
33
+ _Avoid_: Delivery mode
34
+
27
35
  **Redemption link**:
28
- A single-use, time-limited link through which a purchased gift subscription can be claimed.
36
+ A single-use, time-limited link through which a purchased gift subscription can be viewed and claimed. Its bearer may see the gift's buyer name, intended recipient name, and personal message, but not email-routing or delivery details.
29
37
  _Avoid_: Gift Link
30
38
 
31
39
  **Gift redemption**:
32
40
  The act of claiming an eligible gift subscription. The member who claims it becomes the redeemer and gifted access begins at that point.
33
41
  _Avoid_: Gift activation
34
42
 
43
+ **Gift delivery**:
44
+ Communicating a gift subscription's redemption link to its recipient by email. Delivery does not claim the gift or begin gifted access.
45
+ _Avoid_: Gift redemption, gift activation
46
+
47
+ **Personal message**:
48
+ A note from the buyer that forms part of the gift and can be presented wherever the gift is shown, including email and redemption experiences.
49
+ _Avoid_: Delivery message, email message
50
+
51
+ **Gift sent**:
52
+ The recipient email has been accepted by the publication's configured mail transport. This completes gift delivery.
53
+ _Avoid_: Gift delivered, provider delivery
54
+
55
+ **Email delivery status**:
56
+ The current progress of handing a recipient email to the configured mail transport: pending, sending, sent, failed, or cancelled.
57
+ _Avoid_: Gift status
58
+
35
59
  ### Time and lifecycle
36
60
 
37
61
  **Gift duration**:
@@ -43,7 +67,7 @@ The monthly or yearly membership price basis used to value a gift subscription.
43
67
  _Avoid_: Gift duration, renewal interval
44
68
 
45
69
  **Gift expiration**:
46
- The end of the claim window for a purchased gift subscription that was never redeemed.
70
+ The end of the claim window that begins when a gift subscription is purchased and was never redeemed.
47
71
  _Avoid_: Gift consumption
48
72
 
49
73
  **Gift consumption**:
@@ -20,9 +20,13 @@ transactions, or Stripe objects.
20
20
  - `getMemberPresentations(memberIds)`, `getPreview(token)`,
21
21
  `browsePurchaseEvents(...)`, and `browseRedemptionEvents(...)` expose
22
22
  stable read models.
23
- - `processReminders()`, `processConsumed()`, and `processExpired()` own due
24
- lifecycle work; scheduler and HTTP triggers remain adapters.
23
+ - `processReminders()`, `processConsumed()`, and `processExpired()` own gift
24
+ lifecycle work. `GiftDeliveryService` owns email-delivery creation,
25
+ post-commit dispatch, cancellation, and processing behind a separate
26
+ interface. Email delivery starts immediately after purchase through an
27
+ in-process event. Delivery claims are atomic; stale in-progress claims are
28
+ retried after a crash, so mail-transport acceptance is at least once.
25
29
  - `reassignRedeemer(...)` is the import capability.
26
30
 
27
- The `Gift`, repository, Bookshelf query, Stripe checkout, email, scheduling,
28
- and notification collaborators are internal adapters.
31
+ The `Gift` model, gift-delivery data schema, their repositories, Bookshelf
32
+ queries, Stripe checkout and email collaborators are internal adapters.
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.GIFT_REMINDER_FLOOR_DAYS = exports.GIFT_REMINDER_LEAD_DAYS = exports.GIFT_EXPIRY_DAYS = void 0;
3
+ exports.GIFT_DELIVERY_STALE_AFTER_MS = exports.GIFT_REMINDER_FLOOR_DAYS = exports.GIFT_REMINDER_LEAD_DAYS = exports.GIFT_EXPIRY_DAYS = void 0;
4
4
  exports.GIFT_EXPIRY_DAYS = 365;
5
5
  exports.GIFT_REMINDER_LEAD_DAYS = 7;
6
6
  exports.GIFT_REMINDER_FLOOR_DAYS = 3;
7
+ exports.GIFT_DELIVERY_STALE_AFTER_MS = 60 * 60 * 1000;
@@ -0,0 +1,20 @@
1
+ <!doctype html>
2
+ <html>
3
+ <head><meta name="viewport" content="width=device-width"><meta http-equiv="Content-Type" content="text/html; charset=UTF-8"><title>{{t 'A gift, just for you'}}</title></head>
4
+ <body style="background:#fff;font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,Helvetica,Arial,sans-serif;margin:0;padding:0;color:#3A464C;">
5
+ <span class="preheader" style="color:transparent;display:none;height:0;max-height:0;max-width:0;opacity:0;overflow:hidden;visibility:hidden;width:0;">{{t 'You\'ve been gifted a membership to {siteTitle}' siteTitle=siteTitle}}</span>
6
+ <table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center">
7
+ <table role="presentation" width="540" cellpadding="0" cellspacing="0" border="0" style="max-width:540px;width:100%;"><tr><td style="padding:40px 20px;">
8
+ {{#if siteIconUrl}}<a href="{{siteUrl}}"><img src="{{siteIconUrl}}" alt="{{siteTitle}}" width="48" height="48" style="border-radius:4px;"></a>{{else}}<a href="{{siteUrl}}" style="color:{{accentColor}};font-size:17px;font-weight:700;text-decoration:none;">{{siteTitle}}</a>{{/if}}
9
+ <h1 style="color:#15212A;font-size:26px;line-height:31px;margin:22px 0 14px;">{{t 'A gift, just for you'}}</h1>
10
+ {{#if recipientName}}<p style="font-size:16px;line-height:24px;margin:0 0 16px;">{{t 'Hi {recipientName},' recipientName=(strong recipientName)}}</p>{{/if}}
11
+ <p style="font-size:16px;line-height:24px;margin:0 0 24px;">{{#if gift.isMonthly}}{{t '{buyerName} has gifted you a {duration}-month {tierName} membership to {siteTitle}' buyerName=(strong buyerName) duration=(strong gift.duration) count=gift.duration tierName=(strong gift.tierName) siteTitle=siteTitle}}{{else}}{{t '{buyerName} has gifted you a {duration}-year {tierName} membership to {siteTitle}' buyerName=(strong buyerName) duration=(strong gift.duration) count=gift.duration tierName=(strong gift.tierName) siteTitle=siteTitle}}{{/if}}</p>
12
+ {{#if personalMessage}}<div style="background:{{accentTint}};border-radius:8px;margin-bottom:24px;padding:16px 18px;"><p style="color:#15212A;font-size:16px;font-style:italic;line-height:24px;margin:0;white-space:pre-line;word-break:break-word;">{{personalMessage}}</p><p style="color:{{accentShade}};font-size:14px;margin:10px 0 0;">{{buyerName}}</p></div>{{/if}}
13
+ {{#if gift.benefits.length}}<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="margin-bottom:32px;"><tr><td><p style="font-size:16px;line-height:24px;margin:0 0 6px;">{{t 'What\'s included'}}</p></td></tr>{{#each gift.benefits}}<tr><td style="font-size:16px;line-height:24px;padding:4px 0;"><span style="color:{{../accentColor}};font-weight:bold;">&#10003;</span>&nbsp;&nbsp;{{this}}</td></tr>{{/each}}</table>{{/if}}
14
+ <a href="{{gift.link}}" style="background:{{accentColor}};border-radius:5px;color:#fff;display:block;font-size:16px;padding:10px 22px;text-align:center;text-decoration:none;">{{t 'Redeem your gift:'}}</a>
15
+ <p style="color:#738A94;font-size:13px;line-height:20px;margin:16px 0 0;">{{t 'This gift can only be redeemed once and expires on {expiresAt}.' expiresAt=(strong gift.expiresAt)}}</p>
16
+ <p style="color:#738A94;font-size:11px;line-height:16px;margin:40px 0 0;">{{t 'This message was sent from {siteDomain} to {email} on behalf of {buyerName} ({buyerEmail}).' siteDomain=(linkTag siteUrl siteDomain style="color:#738A94;") email=(linkTag (mailto toEmail) toEmail style="color:#738A94;") buyerName=buyerName buyerEmail=(linkTag (mailto buyerEmail) buyerEmail style="color:#738A94;")}}</p>
17
+ </td></tr></table>
18
+ </td></tr></table>
19
+ </body>
20
+ </html>
@@ -0,0 +1,39 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderText = renderText;
4
+ function renderText(data, t) {
5
+ const interpolation = { escapeValue: false };
6
+ const greeting = data.recipientName ? `${t('Hi {recipientName},', {
7
+ recipientName: data.recipientName,
8
+ interpolation
9
+ })}\n\n` : '';
10
+ const giftDescription = {
11
+ duration: data.gift.duration,
12
+ count: data.gift.duration,
13
+ tierName: data.gift.tierName,
14
+ siteTitle: data.siteTitle
15
+ };
16
+ const intro = data.gift.isMonthly
17
+ ? t('{buyerName} has gifted you a {duration}-month {tierName} membership to {siteTitle}', {
18
+ ...giftDescription,
19
+ buyerName: data.buyerName,
20
+ interpolation
21
+ })
22
+ : t('{buyerName} has gifted you a {duration}-year {tierName} membership to {siteTitle}', {
23
+ ...giftDescription,
24
+ buyerName: data.buyerName,
25
+ interpolation
26
+ });
27
+ const message = data.personalMessage ? `\n"${data.personalMessage}"\n— ${data.buyerName}\n` : '';
28
+ const benefits = data.gift.benefits.length ? `\n${t('What\'s included:')}\n${data.gift.benefits.map(benefit => `- ${benefit}`).join('\n')}\n` : '';
29
+ return `${t('A gift, just for you')}\n\n${greeting}${intro}\n${message}${benefits}\n${t('Redeem your gift:')}\n${data.gift.link}\n\n${t('This gift can only be redeemed once and expires on {expiresAt}.', {
30
+ expiresAt: data.gift.expiresAt,
31
+ interpolation
32
+ })}\n\n---\n${t('This message was sent from {siteDomain} to {email} on behalf of {buyerName} ({buyerEmail}).', {
33
+ siteDomain: data.siteDomain,
34
+ email: data.toEmail,
35
+ buyerName: data.buyerName,
36
+ buyerEmail: data.buyerEmail,
37
+ interpolation
38
+ })}`;
39
+ }
@@ -39,8 +39,8 @@
39
39
  {{/if}}
40
40
  <tr>
41
41
  <td style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 14px; vertical-align: top;">
42
- <h1 style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 26px; color: #15212A; font-weight: bold; line-height: 28px; margin: 0; padding-bottom: 16px;">{{t 'Your gift is ready'}}</h1>
43
- <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 24px; margin: 0; padding-bottom: 24px;">{{t 'Thank you for your support. Share the link below with whoever you\'d like to gift them a {cadenceLabel} {tierName} membership to {siteTitle}.' cadenceLabel=(strong gift.cadenceLabel) tierName=(strong gift.tierName) siteTitle=siteTitle}}</p>
42
+ <h1 style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 26px; color: #15212A; font-weight: bold; line-height: 28px; margin: 0; padding-bottom: 16px;">{{#if gift.recipientEmail}}{{t 'Your gift is on its way'}}{{else}}{{t 'Your gift is ready'}}{{/if}}</h1>
43
+ <p style="font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; font-size: 16px; color: #3A464C; font-weight: normal; line-height: 24px; margin: 0; padding-bottom: 24px;">{{#if gift.recipientEmail}}{{#if gift.isMonthly}}{{t 'Thank you for your support. Your gift — a {duration}-month {tierName} membership to {siteTitle} — is on its way to {recipientEmail}. You can also share the link below yourself.' duration=(strong gift.duration) count=gift.duration tierName=(strong gift.tierName) siteTitle=siteTitle recipientEmail=(strong gift.recipientEmail)}}{{else}}{{t 'Thank you for your support. Your gift — a {duration}-year {tierName} membership to {siteTitle} — is on its way to {recipientEmail}. You can also share the link below yourself.' duration=(strong gift.duration) count=gift.duration tierName=(strong gift.tierName) siteTitle=siteTitle recipientEmail=(strong gift.recipientEmail)}}{{/if}}{{else}}{{#if gift.isMonthly}}{{t 'Thank you for your support. Share the link below with whoever you\'d like to gift them a {duration}-month {tierName} membership to {siteTitle}.' duration=(strong gift.duration) count=gift.duration tierName=(strong gift.tierName) siteTitle=siteTitle}}{{else}}{{t 'Thank you for your support. Share the link below with whoever you\'d like to gift them a {duration}-year {tierName} membership to {siteTitle}.' duration=(strong gift.duration) count=gift.duration tierName=(strong gift.tierName) siteTitle=siteTitle}}{{/if}}{{/if}}</p>
44
44
  <table width="100%" border="0" cellpadding="0" cellspacing="0" style="border-collapse: separate; mso-table-lspace: 0pt; mso-table-rspace: 0pt; table-layout: fixed; width: 100%; min-width: 100%; box-sizing: border-box; background: #F4F5F6; border-radius: 6px;">
45
45
  <tbody>
46
46
  <tr>
@@ -2,13 +2,28 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.renderText = renderText;
4
4
  function renderText(data, t) {
5
- const intro = t('Thank you for your support. Share the link below with whoever you\'d like to gift them a {cadenceLabel} {tierName} membership to {siteTitle}.', {
6
- cadenceLabel: data.gift.cadenceLabel,
5
+ const giftDescription = {
6
+ duration: data.gift.duration,
7
+ count: data.gift.duration,
7
8
  tierName: data.gift.tierName,
8
9
  siteTitle: data.siteTitle,
9
10
  interpolation: { escapeValue: false }
10
- });
11
- return `${t('Your gift is ready')}
11
+ };
12
+ const intro = data.gift.recipientEmail
13
+ ? data.gift.isMonthly
14
+ ? t('Thank you for your support. Your gift — a {duration}-month {tierName} membership to {siteTitle} — is on its way to {recipientEmail}. You can also share the link below yourself.', {
15
+ ...giftDescription,
16
+ recipientEmail: data.gift.recipientEmail
17
+ })
18
+ : t('Thank you for your support. Your gift — a {duration}-year {tierName} membership to {siteTitle} — is on its way to {recipientEmail}. You can also share the link below yourself.', {
19
+ ...giftDescription,
20
+ recipientEmail: data.gift.recipientEmail
21
+ })
22
+ : data.gift.isMonthly
23
+ ? t('Thank you for your support. Share the link below with whoever you\'d like to gift them a {duration}-month {tierName} membership to {siteTitle}.', giftDescription)
24
+ : t('Thank you for your support. Share the link below with whoever you\'d like to gift them a {duration}-year {tierName} membership to {siteTitle}.', giftDescription);
25
+ const heading = data.gift.recipientEmail ? t('Your gift is on its way') : t('Your gift is ready');
26
+ return `${heading}
12
27
 
13
28
  ${intro}
14
29
 
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SendGiftDeliveryEvent = void 0;
4
+ class SendGiftDeliveryEvent {
5
+ data;
6
+ timestamp;
7
+ constructor(data, timestamp) {
8
+ this.data = data;
9
+ this.timestamp = timestamp;
10
+ }
11
+ static create(data, timestamp = new Date()) {
12
+ return new SendGiftDeliveryEvent(data, timestamp);
13
+ }
14
+ }
15
+ exports.SendGiftDeliveryEvent = SendGiftDeliveryEvent;
@@ -7,10 +7,13 @@ exports.GiftBookshelfRepository = void 0;
7
7
  const errors_1 = __importDefault(require("@tryghost/errors"));
8
8
  const mongo_utils_1 = require("@tryghost/mongo-utils");
9
9
  const gift_codec_1 = require("./gift-codec");
10
+ const db_date_1 = require("../../lib/db-date");
10
11
  class GiftBookshelfRepository {
11
12
  model;
12
- constructor({ GiftModel }) {
13
+ knex;
14
+ constructor({ GiftModel, knex }) {
13
15
  this.model = GiftModel;
16
+ this.knex = knex;
14
17
  }
15
18
  async existsByCheckoutSessionId(checkoutSessionId) {
16
19
  const existing = await this.model.findOne({
@@ -111,8 +114,19 @@ class GiftBookshelfRepository {
111
114
  });
112
115
  return collection.models.map(model => this.toGift(model));
113
116
  }
117
+ async deleteAbandonedCheckouts(cutoff) {
118
+ return this.knex('gifts')
119
+ .where({ status: 'payment_pending' })
120
+ .where('checkout_started_at', '<=', (0, db_date_1.toDatabaseDate)(cutoff))
121
+ .del();
122
+ }
114
123
  async create(gift, options = {}) {
115
- await this.model.add(this.toRow(gift), options);
124
+ const created = await this.model.add(this.toRow(gift), options);
125
+ const id = created.toJSON().id;
126
+ if (!id) {
127
+ throw new errors_1.default.InternalServerError({ message: 'Created gift is missing an id' });
128
+ }
129
+ return id;
116
130
  }
117
131
  async update(gift, options = {}) {
118
132
  const existing = await this.model.findOne({
@@ -128,6 +142,12 @@ class GiftBookshelfRepository {
128
142
  ...options
129
143
  });
130
144
  }
145
+ async deletePendingCheckout(id) {
146
+ const deleted = await this.knex('gifts')
147
+ .where({ id, status: 'payment_pending' })
148
+ .del();
149
+ return deleted === 1;
150
+ }
131
151
  async transaction(callback) {
132
152
  return await this.model.transaction(callback);
133
153
  }
@@ -144,7 +164,7 @@ class GiftBookshelfRepository {
144
164
  const queryOptions = {
145
165
  ...options,
146
166
  withRelated: [relation, 'tier'],
147
- filter: `${memberIdColumn}:-null+custom:true`,
167
+ filter: `${memberIdColumn}:-null+${dateColumn}:-null+custom:true`,
148
168
  useBasicCount: true,
149
169
  mongoTransformer: (0, mongo_utils_1.chainTransformers)(replaceCustomFilter, ...(0, mongo_utils_1.mapKeys)({
150
170
  'data.created_at': dateColumn,
@@ -48,12 +48,14 @@ module.exports = class GiftCheckoutAdapter {
48
48
  }
49
49
 
50
50
  async createSession(data) {
51
- const {customerId, ...sessionData} = data;
51
+ const {customerId, idempotencyKey, ...sessionData} = data;
52
52
  const session = await this.getStripeApi().createGiftCheckoutSession({
53
53
  ...sessionData,
54
- customer: customerId ? {id: customerId} : null
54
+ customer: customerId ? {id: customerId} : null,
55
+ idempotencyKey
55
56
  });
56
57
 
57
- return session.url;
58
+ return {id: session.id, url: session.url};
58
59
  }
60
+
59
61
  };