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.
- package/components/tryghost-adapter-base-scheduling-0.2.2.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.1.2.tgz +0 -0
- package/components/tryghost-i18n-0.0.0.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.2.0.tgz +0 -0
- package/components/tryghost-kg-html-to-lexical-1.4.0.tgz +0 -0
- package/components/tryghost-kg-lexical-html-renderer-1.5.0.tgz +0 -0
- package/content/themes/casper/.github/workflows/test.yml +11 -1
- package/content/themes/casper/AGENTS.md +25 -0
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/css/screen.css +15 -1
- package/content/themes/casper/gulpfile.js +3 -0
- package/content/themes/casper/package.json +8 -8
- package/content/themes/source/.gitattributes +5 -0
- package/content/themes/source/.github/workflows/test.yml +39 -0
- package/content/themes/source/AGENTS.md +25 -0
- package/content/themes/source/README.md +33 -4
- package/content/themes/source/assets/built/screen.css +1 -1
- package/content/themes/source/assets/css/screen.css +15 -0
- package/content/themes/source/gulpfile.js +6 -3
- package/content/themes/source/package.json +13 -10
- package/content/themes/source/pnpm-workspace.yaml +3 -0
- package/core/boot.js +11 -4
- package/core/built/admin/assets/Combination-BCm9FL4o.js +41 -0
- package/core/built/admin/assets/about-BdSCpjXG.js +1 -0
- package/core/built/admin/assets/account-migration-BpKAqo8k.js +1 -0
- package/core/built/admin/assets/activitypub/{account-migration-BpDeIF3G.js → account-migration-DVnYS1XG.js} +8 -8
- package/core/built/admin/assets/activitypub/activitypub.js +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-aPMtrS3L.js → bluesky-sharing-DLVeE9ii.js} +31 -31
- package/core/built/admin/assets/activitypub/{content-formatters-BORfuA9G.js → content-formatters-DCsVic-t.js} +2 -2
- package/core/built/admin/assets/activitypub/{dist-ByFmDSNP2.js → dist-ByFmDSNP.js} +1 -1
- package/core/built/admin/assets/activitypub/{domain-CPdiB27_.js → domain-rrvDLfnK.js} +8 -8
- package/core/built/admin/assets/activitypub/{edit-profile-BY2PawIe.js → edit-profile-VT-piVxq.js} +23 -23
- package/core/built/admin/assets/activitypub/{explore-Sp9bgAwq.js → explore-DZg3m48P.js} +14 -14
- package/core/built/admin/assets/activitypub/{feed-BjtF4ohL.js → feed-BJqQvDMI.js} +41 -41
- package/core/built/admin/assets/activitypub/{field-BDOYVTg6.js → field-BezS4-1J.js} +2 -2
- package/core/built/admin/assets/activitypub/inbox-Uf0GVrI_.js +1284 -0
- package/core/built/admin/assets/activitypub/{label-1jhN-qhD.js → label-CZ6if4zK.js} +2 -2
- package/core/built/admin/assets/activitypub/layout-DNuYT4Gu.js +1 -0
- package/core/built/admin/assets/activitypub/moderation-CP6mDVky.js +340 -0
- package/core/built/admin/assets/activitypub/{note-nr_m_5yb.js → note-BlFkye2g.js} +16 -16
- package/core/built/admin/assets/activitypub/{notifications-BdcyivbT.js → notifications-GGTrGEdu.js} +163 -126
- package/core/built/admin/assets/activitypub/{preferences-CNqy1x9C.js → preferences-CjZXrZqr.js} +1550 -1551
- package/core/built/admin/assets/activitypub/{profile-DlY4rhbO.js → profile-CcXYxydm.js} +248 -248
- package/core/built/admin/assets/activitypub/{routes-CEHdEZzS.js → routes-nHCKlZl-.js} +1971 -1747
- package/core/built/admin/assets/activitypub/settings-pDzybcb3.js +315 -0
- package/core/built/admin/assets/activitypub/{site-CtBUCPBz.js → site-BZF0F8LN.js} +101 -101
- package/core/built/admin/assets/activitypub/{step-1-D9jGWyAy.js → step-1-C-Ns1Tfg.js} +3 -3
- package/core/built/admin/assets/activitypub/{step-3-KpUqpGWi.js → step-3-rDMNGmLg.js} +8 -8
- package/core/built/admin/assets/activitypub/{tabs-DvlsxTCI.js → tabs-CDjh3_Jp.js} +198 -198
- package/core/built/admin/assets/activitypub/{topic-filter-CIN1E2jg.js → topic-filter-CmTc8hpN.js} +2 -2
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-CATFd6YV.js → use-activity-pub-queries-DUDEdSdR.js} +286 -243
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-DJ0vhA8V.js → use-reply-chain-data-TunGtmJl.js} +14 -14
- package/core/built/admin/assets/add-integration-modal-CXRQi1x0.js +1 -0
- package/core/built/admin/assets/add-newsletter-modal-t447KRYz.js +1 -0
- package/core/built/admin/assets/add-recommendation-modal-D882MdnR.js +1 -0
- package/core/built/admin/assets/{analytics-BC2oR8ay.js → analytics-Dj-WlzPf.js} +1 -1
- package/core/built/admin/assets/announcement-bar-modal-CwHKKw-a.js +1 -0
- package/core/built/admin/assets/api-keys-Boy5pLbD.js +1 -0
- package/core/built/admin/assets/audience-Cc40y7zQ.js +1 -0
- package/core/built/admin/assets/automations-DNLK5Udr.js +1 -0
- package/core/built/admin/assets/bluesky-sharing-Cm4aFaOl.js +1 -0
- package/core/built/admin/assets/breadcrumb-CcFr_ltt.js +1 -0
- package/core/built/admin/assets/button-BshWPHvn.js +1 -0
- package/core/built/admin/assets/chart-helpers-BsbzoWhK.js +1 -0
- package/core/built/admin/assets/{checkbox-LmClhqcS.js → checkbox-1G6KfK2q.js} +1 -1
- package/core/built/admin/assets/chunk-KS7C4IRE-DCsL4i4D.js +4 -0
- package/core/built/admin/assets/{chunk.408.1a258af360eb8d905508.js → chunk.301.5673a0caf7c49bb00212.js} +3 -3
- package/core/built/admin/assets/{chunk.524.4c693c3a00ac91df34fa.js → chunk.524.86db0d945468c852374b.js} +4 -4
- package/core/built/admin/assets/{chunk.582.20b600e329e58d12d7c2.js → chunk.582.c2f6216d3c4af303e236.js} +6 -6
- package/core/built/admin/assets/{code-editor-view-DqTOUnbM.js → code-editor-view-BVjtWyst.js} +1 -1
- package/core/built/admin/assets/{code-editor-view-bA0lfxUL.js → code-editor-view-y_zVfmCa.js} +1 -1
- package/core/built/admin/assets/color-picker-XEMPgNJE.js +3 -0
- package/core/built/admin/assets/{color-picker-field-Cv9GOJQZ.js → color-picker-field-BhplXcy5.js} +1 -1
- package/core/built/admin/assets/combobox-CRMJrvcQ.js +1 -0
- package/core/built/admin/assets/command-151T2eRx.js +1 -0
- package/core/built/admin/assets/comments-Bjbl9toF.js +1 -0
- package/core/built/admin/assets/confirmation-context-QmFxSjWy.js +1 -0
- package/core/built/admin/assets/{content-api-modal-BOFWRUkC.js → content-api-modal-CpeABXne.js} +1 -1
- package/core/built/admin/assets/{copy-field-8biQvNMD.js → copy-field-CglgRZyG.js} +1 -1
- package/core/built/admin/assets/custom-field-type-option-DSyJw4T4.js +1 -0
- package/core/built/admin/assets/custom-integration-modal-DWB9bRVp.js +1 -0
- package/core/built/admin/assets/date-range-select-kaKtab5O.js +1 -0
- package/core/built/admin/assets/design-and-theme-modal-86s2AaZ-.js +23 -0
- package/core/built/admin/assets/dirty-confirm-dialog-CTFdXXy7.js +1 -0
- package/core/built/admin/assets/disabled-sources-indicator-BFyCxUOx.js +1 -0
- package/core/built/admin/assets/dist-C1-Oy3Db.js +1 -0
- package/core/built/admin/assets/dist-CJJqYPep.js +1 -0
- package/core/built/admin/assets/dist-CNlMj7ML.js +1 -0
- package/core/built/admin/assets/dist-Kll2twSG.js +1 -0
- package/core/built/admin/assets/domain-BOoMvBG3.js +1 -0
- package/core/built/admin/assets/{dropzone-B21Entqz.js → dropzone-B6KWzsrb.js} +2 -2
- package/core/built/admin/assets/edit-profile-BagrYOrN.js +1 -0
- package/core/built/admin/assets/editor-AaJjqbpk.js +8 -0
- package/core/built/admin/assets/embed-signup-form-modal-B8L95KwR.js +8 -0
- package/core/built/admin/assets/explore-xbykZTlO.js +1 -0
- package/core/built/admin/assets/feed-BAHSipWv.js +1 -0
- package/core/built/admin/assets/{field-BQqLTgHM.js → field-kJYZ2T3C.js} +1 -1
- package/core/built/admin/assets/{filter-sources-FgqoUIL7.js → filter-sources-QeqFGNRB.js} +1 -1
- package/core/built/admin/assets/{filters-D0uIMD8n.js → filters-By5KQeV6.js} +1 -1
- package/core/built/admin/assets/filters-DtYpAkDd.js +1 -0
- package/core/built/admin/assets/first-promoter-modal-8A02oyjA.js +1 -0
- package/core/built/admin/assets/{ghost-ba408063a5922b458b4a8f216cf23251.js → ghost-f110822e67868c9fdf9a6c23754b2559.js} +9 -8
- package/core/built/admin/assets/gift-link-modal-DbYCyuhF.js +1 -0
- package/core/built/admin/assets/global-data-context-gAMDH9u1.js +1 -0
- package/core/built/admin/assets/growth-BSOxJGey.js +3 -0
- package/core/built/admin/assets/growth-BZTjURq5.js +1 -0
- package/core/built/admin/assets/history-modal-CCgWtlZE.js +1 -0
- package/core/built/admin/assets/html-field-BQC7XyLA.js +1 -0
- package/core/built/admin/assets/iframe-buffering-DnOlWTD4.js +1 -0
- package/core/built/admin/assets/{images-3cQgwfTD.js → images-D5UKr2SG.js} +1 -1
- package/core/built/admin/assets/{inbox-Dz_JblyJ.js → inbox-DvN15_YH.js} +45 -39
- package/core/built/admin/assets/index-BJz9Zlsc.js +67 -0
- package/core/built/admin/assets/index-Dg8cdR8E.css +1 -0
- package/core/built/admin/assets/{input-group-DpogNUIt.js → input-group-BPAxETpQ.js} +1 -1
- package/core/built/admin/assets/invite-user-modal-jcUaaNkW.js +1 -0
- package/core/built/admin/assets/{koenig-lexical-2KuqRwZI.js → koenig-lexical-CfXTYeli.js} +1 -1
- package/core/built/admin/assets/koenig-loader-DktFM3Uh.js +1 -0
- package/core/built/admin/assets/{kpi-card-Ck3tT2Wg.js → kpi-card-fUHZlzBX.js} +1 -1
- package/core/built/admin/assets/{kpi-card-DDIxptaS.js → kpi-card-o0urVtNL.js} +1 -1
- package/core/built/admin/assets/kpi-helpers-2FTF4a3-.js +1 -0
- package/core/built/admin/assets/kpi-tabs-B8-_k2dQ.js +1 -0
- package/core/built/admin/assets/{label-DodDsMzn.js → label-CiRS3i_f.js} +1 -1
- package/core/built/admin/assets/label-picker-ao7tuPaK.js +1 -0
- package/core/built/admin/assets/layout-DC6QGDaw.js +1 -0
- package/core/built/admin/assets/member-custom-fields-BW756-EV.js +1 -0
- package/core/built/admin/assets/member-detail-CGGgUTSl.js +1 -0
- package/core/built/admin/assets/members-ZcPthwG0.js +11 -0
- package/core/built/admin/assets/moderation-24bElWs3.js +1 -0
- package/core/built/admin/assets/multi-select-combobox-CZPrgz-Z.js +1 -0
- package/core/built/admin/assets/navigation-modal-BiTP4U_J.js +1 -0
- package/core/built/admin/assets/newsletter-B3Ihwizp.js +1 -0
- package/core/built/admin/assets/newsletter-detail-modal-CYsu4qhl.js +1 -0
- package/core/built/admin/assets/newsletter-emails-CDPR4-SP.js +1 -0
- package/core/built/admin/assets/newsletters-bJiYLJWl.js +1 -0
- package/core/built/admin/assets/note-7f6urVVA.js +1 -0
- package/core/built/admin/assets/notifications-rosQCyXH.js +1 -0
- package/core/built/admin/assets/offers-container-modal-J2j6NpDL.js +1 -0
- package/core/built/admin/assets/onboarding-route-DTjcThKl.js +1 -0
- package/core/built/admin/assets/overview-BxvWzGaf.js +1 -0
- package/core/built/admin/assets/overview-D6JG0hkZ.js +1 -0
- package/core/built/admin/assets/{page-header-VLPJfj_d.js → page-header-l7vXmLIw.js} +1 -1
- package/core/built/admin/assets/pintura-modal-BGHmudVB.js +1 -0
- package/core/built/admin/assets/portal-frame-DVc4VHX_.js +1 -0
- package/core/built/admin/assets/portal-modal-BCOcdiRi.js +1 -0
- package/core/built/admin/assets/{post-analytics-Gv81eN_g.js → post-analytics-CTSZMK6I.js} +1 -1
- package/core/built/admin/assets/post-analytics-header-Dt4RQGq0.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-O2YjZMhN.js → post-analytics-provider-C4ydiboO.js} +1 -1
- package/core/built/admin/assets/post-share-modal-DjH_a_1L.js +1 -0
- package/core/built/admin/assets/preferences-BLo6VJUS.js +5 -0
- package/core/built/admin/assets/preview-modal-BOgjgsJL.js +1 -0
- package/core/built/admin/assets/profile-DG6iCdRH.js +1 -0
- package/core/built/admin/assets/radio-group-ChHD4EQJ.js +1 -0
- package/core/built/admin/assets/router-provider-Bexg1t-V.js +1 -0
- package/core/built/admin/assets/select-ChxknEcT.js +1 -0
- package/core/built/admin/assets/settings-B0VDAhgV.js +1 -0
- package/core/built/admin/assets/settings-Dwo3CeAD.js +4 -0
- package/core/built/admin/assets/settings-breadcrumbs-sLDNedBW.js +1 -0
- package/core/built/admin/assets/share-modal-BDb2nacg.js +1 -0
- package/core/built/admin/assets/skeleton-D7hwjdry.js +1 -0
- package/core/built/admin/assets/slack-modal-CwNAD69x.js +1 -0
- package/core/built/admin/assets/sort-button-DhqpxkrO.js +1 -0
- package/core/built/admin/assets/source-icon-BPsD5ees.js +1 -0
- package/core/built/admin/assets/stats-WdDJ8c_1.js +1 -0
- package/core/built/admin/assets/stats-view-J3PI2eT_.js +1 -0
- package/core/built/admin/assets/step-1-2q1SjerQ.js +1 -0
- package/core/built/admin/assets/step-2-D5mJB3Uu.js +1 -0
- package/core/built/admin/assets/step-3-BpAuYTGZ.js +2 -0
- package/core/built/admin/assets/{stripe-button-Bh4p6Wud.js → stripe-button-DDoUKN5a.js} +1 -1
- package/core/built/admin/assets/stripe-connect-modal-pMdF3t4S.js +1 -0
- package/core/built/admin/assets/switch-B_1yU3Aw.js +1 -0
- package/core/built/admin/assets/{table-DghJxZMH.js → table-CrqQtkR2.js} +1 -1
- package/core/built/admin/assets/tabs-BhzVdh5p.js +1 -0
- package/core/built/admin/assets/tag-detail-CwJhI4qg.js +2 -0
- package/core/built/admin/assets/tags-CvJ0PgrB.js +1 -0
- package/core/built/admin/assets/testimonials-modal-CV82IAnI.js +1 -0
- package/core/built/admin/assets/tier-detail-modal-pLzCOfeg.js +1 -0
- package/core/built/admin/assets/toggle-group-BW_T4yn6.js +1 -0
- package/core/built/admin/assets/topic-filter-DkXjDejV.js +1 -0
- package/core/built/admin/assets/transistor-modal-TMRvHZ6B.js +1 -0
- package/core/built/admin/assets/unsplash-modal-CV92RdPw.js +1 -0
- package/core/built/admin/assets/{use-analytics-data-IxJcSjc9.js → use-analytics-data-BM_uV9SU.js} +1 -1
- package/core/built/admin/assets/use-check-theme-limit-error-DkBnXMSY.js +1 -0
- package/core/built/admin/assets/use-confirm-unload-SzlkOIpm.js +1 -0
- package/core/built/admin/assets/use-feature-flag-cahOlMva.js +1 -0
- package/core/built/admin/assets/use-filter-params-BX5qf0Cg.js +1 -0
- package/core/built/admin/assets/{use-growth-stats-1NW-8eEZ.js → use-growth-stats-D0LlWBe6.js} +1 -1
- package/core/built/admin/assets/{use-hash-link-navigation-guard-B9j6HIrV.js → use-hash-link-navigation-guard-B_Y3FhYH.js} +1 -1
- package/core/built/admin/assets/use-limiter-D3DHSoAj.js +2 -0
- package/core/built/admin/assets/{use-pintura-config-BjFChXV1.js → use-pintura-config-D63Xu0SG.js} +1 -1
- package/core/built/admin/assets/use-pintura-editor-Z51aFnC8.js +1 -0
- package/core/built/admin/assets/{use-post-referrers-Dp1BnTAK.js → use-post-referrers-Cvymr3gt.js} +1 -1
- package/core/built/admin/assets/use-query-params-BfzuqbcD.js +1 -0
- package/core/built/admin/assets/use-reply-chain-data-Dc18Ujqo.js +1 -0
- package/core/built/admin/assets/use-setting-group-DYRIzqC0.js +1 -0
- package/core/built/admin/assets/use-settings-navigation-aDSLtmEl.js +1 -0
- package/core/built/admin/assets/{use-simple-pagination-DsEGSJhe.js → use-simple-pagination-B2TdNX67.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-query-Ci_DiPkX.js → use-tinybird-query-BvQIVuRc.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-CsUwc722.js → use-tinybird-token-COQTPIwL.js} +1 -1
- package/core/built/admin/assets/use-upgrade-route-BZf1g59m.js +1 -0
- package/core/built/admin/assets/{use-url-input-BEZJpVgN.js → use-url-input-PcqZadXa.js} +1 -1
- package/core/built/admin/assets/user-detail-modal-DeO3SAZq.js +1 -0
- package/core/built/admin/assets/{virtual-list-D5ugXyZY.js → virtual-list-D3MfPNOh.js} +1 -1
- package/core/built/admin/assets/web-CDdyG7z1.js +2 -0
- package/core/built/admin/assets/web-CwQ8_QUc.js +1 -0
- package/core/built/admin/assets/zapier-modal-a7t75iNI.js +1 -0
- package/core/built/admin/index.html +15 -12
- package/core/frontend/apps/private-blogging/lib/helpers/input_password.js +2 -1
- package/core/frontend/helpers/body_class.js +2 -1
- package/core/frontend/helpers/cancel_link.js +2 -1
- package/core/frontend/helpers/color_to_rgba.js +2 -1
- package/core/frontend/helpers/content_api_key.js +2 -1
- package/core/frontend/helpers/content_api_url.js +2 -1
- package/core/frontend/helpers/contrast_text_color.js +2 -1
- package/core/frontend/helpers/facebook_url.js +2 -1
- package/core/frontend/helpers/ghost_foot.js +2 -1
- package/core/frontend/helpers/ghost_head.js +2 -1
- package/core/frontend/helpers/has.js +4 -2
- package/core/frontend/helpers/link_class.js +2 -1
- package/core/frontend/helpers/meta_description.js +2 -1
- package/core/frontend/helpers/meta_title.js +2 -1
- package/core/frontend/helpers/navigation.js +113 -22
- package/core/frontend/helpers/page_url.js +2 -1
- package/core/frontend/helpers/post_class.js +2 -1
- package/core/frontend/helpers/reading_time.js +2 -1
- package/core/frontend/helpers/social_accounts.js +2 -1
- package/core/frontend/helpers/social_url.js +2 -1
- package/core/frontend/helpers/total_members.js +2 -1
- package/core/frontend/helpers/total_paid_members.js +2 -1
- package/core/frontend/helpers/tpl/navigation.hbs +1 -1
- package/core/frontend/helpers/twitter_url.js +2 -1
- package/core/frontend/public/cards.manifest.json +1 -1
- package/core/server/api/endpoints/posts.js +7 -4
- package/core/server/api/endpoints/utils/permissions.js +2 -1
- package/core/server/api/endpoints/utils/validators/input/settings.js +83 -1
- package/core/server/data/exporter/table-lists.js +1 -0
- package/core/server/data/importer/handlers/json.js +2 -1
- package/core/server/data/migrations/versions/5.19/2022-09-02-20-52-backfill-new-product-columns.js +2 -1
- package/core/server/data/migrations/versions/5.21/2022-10-26-04-50-member-subscription-created-batch-id.js +4 -2
- package/core/server/data/migrations/versions/5.22/2022-10-31-12-03-backfill-new-product-columns.js +2 -1
- package/core/server/data/migrations/versions/6.58/2026-08-11-21-22-48-rename-email-batches-provider-id.js +1 -1
- package/core/server/data/migrations/versions/6.59/2026-08-18-17-06-46-add-gift-delivery-fields-to-gifts.js +26 -0
- package/core/server/data/migrations/versions/6.59/2026-08-18-17-07-01-add-gift-deliveries.js +20 -0
- package/core/server/data/schema/commands.js +48 -24
- package/core/server/data/schema/schema.js +28 -7
- package/core/server/data/seeders/importers/automation-action-revisions-importer.js +3 -36
- package/core/server/data/seeders/importers/automation-actions-importer.js +5 -41
- package/core/server/data/seeders/importers/automation-run-steps-importer.js +3 -36
- package/core/server/data/seeders/importers/automation-runs-importer.js +7 -42
- package/core/server/data/seeders/importers/automations-importer.js +3 -36
- package/core/server/data/seeders/importers/comment-reports-importer.js +5 -5
- package/core/server/data/seeders/importers/comments-importer.js +5 -5
- package/core/server/data/seeders/importers/email-batches-importer.js +5 -4
- package/core/server/data/seeders/importers/email-recipients-importer.js +12 -20
- package/core/server/data/seeders/importers/emails-importer.js +6 -6
- package/core/server/data/seeders/importers/labels-importer.js +3 -3
- package/core/server/data/seeders/importers/members-click-events-importer.js +5 -5
- package/core/server/data/seeders/importers/members-created-events-importer.js +13 -12
- package/core/server/data/seeders/importers/members-feedback-importer.js +7 -7
- package/core/server/data/seeders/importers/members-importer.js +4 -4
- package/core/server/data/seeders/importers/members-login-events-importer.js +3 -3
- package/core/server/data/seeders/importers/members-products-importer.js +2 -2
- package/core/server/data/seeders/importers/members-status-events-importer.js +4 -3
- package/core/server/data/seeders/importers/members-stripe-customers-importer.js +2 -2
- package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +7 -7
- package/core/server/data/seeders/importers/members-subscribe-events-importer.js +3 -3
- package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +3 -3
- package/core/server/data/seeders/importers/offer-redemptions-importer.js +7 -7
- package/core/server/data/seeders/importers/offers-importer.js +3 -3
- package/core/server/data/seeders/importers/posts-importer.js +4 -4
- package/core/server/data/seeders/importers/table-importer.js +2 -35
- package/core/server/data/seeders/importers/tags-importer.js +2 -2
- package/core/server/data/seeders/importers/users-importer.js +2 -2
- package/core/server/data/seeders/importers/web-mentions-importer.js +2 -2
- package/core/server/data/seeders/utils/random.js +7 -0
- package/core/server/ghost-server.js +19 -5
- package/core/server/lib/db-date.js +72 -3
- package/core/server/models/gift-delivery.js +23 -0
- package/core/server/models/gift.js +9 -0
- package/core/server/models/index.js +3 -2
- package/core/server/models/settings.js +43 -4
- package/core/server/services/automations/database-automations-repository.js +38 -31
- package/core/server/services/automations/poll.js +1 -1
- package/core/server/services/content-import/csv/index.js +8 -0
- package/core/server/services/content-import/csv/parse.js +60 -0
- package/core/server/services/content-import/import/importer.js +222 -0
- package/core/server/services/content-import/import/post-data.js +52 -0
- package/core/server/services/content-import/import/reader.js +15 -0
- package/core/server/services/content-import/import/row.js +50 -0
- package/core/server/services/content-import/import/store.js +71 -0
- package/core/server/services/content-import/index.js +67 -0
- package/core/server/services/email-analytics/automation-email-analytics-batch-processor.js +1 -1
- package/core/server/services/email-analytics/index.js +3 -1
- package/core/server/services/email-analytics/jobs/index.js +2 -2
- package/core/server/services/email-service/email-service-wrapper.js +1 -1
- package/core/server/services/email-suppression-list/email-suppression-list.js +4 -2
- package/core/server/services/gifts/CONTEXT.md +27 -3
- package/core/server/services/gifts/README.md +8 -4
- package/core/server/services/gifts/constants.js +2 -1
- package/core/server/services/gifts/email-templates/gift-delivery.hbs +20 -0
- package/core/server/services/gifts/email-templates/gift-delivery.js +39 -0
- package/core/server/services/gifts/email-templates/gift-purchase-confirmation.hbs +2 -2
- package/core/server/services/gifts/email-templates/gift-purchase-confirmation.js +19 -4
- package/core/server/services/gifts/events/send-gift-delivery-event.js +15 -0
- package/core/server/services/gifts/gift-bookshelf-repository.js +23 -3
- package/core/server/services/gifts/gift-checkout-adapter.js +5 -3
- package/core/server/services/gifts/gift-codec.js +8 -0
- package/core/server/services/gifts/gift-delivery-bookshelf-repository.js +107 -0
- package/core/server/services/gifts/gift-delivery-codec.js +12 -0
- package/core/server/services/gifts/gift-delivery-schema.js +15 -0
- package/core/server/services/gifts/gift-delivery-service.js +209 -0
- package/core/server/services/gifts/gift-email-renderer.js +13 -1
- package/core/server/services/gifts/gift-email-service.js +97 -17
- package/core/server/services/gifts/gift-schema.js +10 -6
- package/core/server/services/gifts/gift-service.js +259 -34
- package/core/server/services/gifts/gift.js +59 -5
- package/core/server/services/gifts/index.js +69 -3
- package/core/server/services/lib/mailgun-client.js +5 -2
- package/core/server/services/mail/ghost-mailer.js +9 -1
- package/core/server/services/member-welcome-emails/service.js +6 -1
- package/core/server/services/members/members-api/controllers/router-controller.js +26 -8
- package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +100 -12
- package/core/server/services/stripe/stripe-api.js +3 -2
- package/core/server/services/stripe/webhook-controller.js +6 -3
- package/core/server/services/stripe/webhook-manager.js +2 -0
- package/core/shared/labs.js +1 -0
- package/package.json +9 -8
- package/pnpm-lock.yaml +26 -11
- package/pnpm-workspace.yaml +5 -6
- package/content/themes/source/yarn.lock +0 -6232
- package/core/built/admin/assets/Combination-BDvCdg1t.js +0 -41
- package/core/built/admin/assets/about-B_BvSyLF.js +0 -1
- package/core/built/admin/assets/account-migration-oJUGxnzT.js +0 -1
- package/core/built/admin/assets/activitypub/dist-Dqggkccr.js +0 -215
- package/core/built/admin/assets/activitypub/inbox-z1o2j1Tb.js +0 -1221
- package/core/built/admin/assets/activitypub/layout-BRMezuty.js +0 -1
- package/core/built/admin/assets/activitypub/moderation-CZudJi8N.js +0 -160
- package/core/built/admin/assets/activitypub/settings-CrSV1cux.js +0 -107
- package/core/built/admin/assets/add-integration-modal-BW23PDYs.js +0 -1
- package/core/built/admin/assets/add-newsletter-modal-M0ls8BKO.js +0 -1
- package/core/built/admin/assets/add-recommendation-modal-B0NeBMzl.js +0 -1
- package/core/built/admin/assets/announcement-bar-modal-pXBqjQE_.js +0 -1
- package/core/built/admin/assets/api-keys-Df6uv1dT.js +0 -1
- package/core/built/admin/assets/audience-BY8od8If.js +0 -1
- package/core/built/admin/assets/automations-DCs6Wady.js +0 -1
- package/core/built/admin/assets/bluesky-sharing-D0P42Cws.js +0 -1
- package/core/built/admin/assets/breadcrumb-MTj_cKLb.js +0 -1
- package/core/built/admin/assets/chart-helpers-DcO3k_vA.js +0 -1
- package/core/built/admin/assets/chunk-KS7C4IRE-DG9HTHQ3.js +0 -4
- package/core/built/admin/assets/color-picker-CBlJMv3y.js +0 -3
- package/core/built/admin/assets/combobox-BbPJzjCp.js +0 -1
- package/core/built/admin/assets/command-BrhXIlDr.js +0 -1
- package/core/built/admin/assets/comments-Cu4rD1aJ.js +0 -1
- package/core/built/admin/assets/confirmation-provider-BEdrnHns.js +0 -1
- package/core/built/admin/assets/custom-field-type-option-CNhAgHbJ.js +0 -1
- package/core/built/admin/assets/custom-integration-modal-CrGOI8IB.js +0 -1
- package/core/built/admin/assets/date-range-select-Bsh4gIek.js +0 -1
- package/core/built/admin/assets/design-and-theme-modal-DWZguwWX.js +0 -23
- package/core/built/admin/assets/dirty-confirm-dialog-DmdMpgyN.js +0 -1
- package/core/built/admin/assets/disabled-sources-indicator-BLnmyWgt.js +0 -1
- package/core/built/admin/assets/dist-DMFGvqqU.js +0 -1
- package/core/built/admin/assets/dist-DlCyLrOC.js +0 -1
- package/core/built/admin/assets/dist-j1xxxnY8.js +0 -1
- package/core/built/admin/assets/domain-Dp9TRrFp.js +0 -1
- package/core/built/admin/assets/edit-profile-DG6lpMsr.js +0 -1
- package/core/built/admin/assets/editor-CPQOrhrn.js +0 -8
- package/core/built/admin/assets/embed-signup-form-modal-CgWniDha.js +0 -9
- package/core/built/admin/assets/explore-C3rzONJv.js +0 -1
- package/core/built/admin/assets/feed-DpUwS1cU.js +0 -1
- package/core/built/admin/assets/filters-DZJT29GW.js +0 -1
- package/core/built/admin/assets/first-promoter-modal-DJhQQheG.js +0 -1
- package/core/built/admin/assets/gift-link-modal-sdFLjND9.js +0 -1
- package/core/built/admin/assets/global-data-provider-CixmTX00.js +0 -1
- package/core/built/admin/assets/growth-0YYkBEeS.js +0 -1
- package/core/built/admin/assets/growth-DpjAPeQT.js +0 -3
- package/core/built/admin/assets/history-modal-BWJ4-cJ0.js +0 -1
- package/core/built/admin/assets/html-field-C-oa5DEl.js +0 -1
- package/core/built/admin/assets/iframe-buffering-Ba9sDpi2.js +0 -1
- package/core/built/admin/assets/index-BOJzlYiz.js +0 -67
- package/core/built/admin/assets/index-Bid6LAyc.css +0 -1
- package/core/built/admin/assets/invite-user-modal-DCiQSiuD.js +0 -1
- package/core/built/admin/assets/koenig-editor-base-BApy8Q7C.js +0 -1
- package/core/built/admin/assets/kpi-helpers-ssbuy-hD.js +0 -1
- package/core/built/admin/assets/kpi-tabs-BqoIM6bI.js +0 -1
- package/core/built/admin/assets/label-picker-Cvvbmzb5.js +0 -1
- package/core/built/admin/assets/layout-DkkGyev2.js +0 -1
- package/core/built/admin/assets/member-custom-fields-CvGcpaiW.js +0 -1
- package/core/built/admin/assets/member-detail-CMCMZ47_.js +0 -1
- package/core/built/admin/assets/members-sRl6fNfq.js +0 -11
- package/core/built/admin/assets/moderation-KhKwGqI1.js +0 -1
- package/core/built/admin/assets/multi-select-combobox-nhCff0_K.js +0 -1
- package/core/built/admin/assets/navigation-modal-MDJExWDJ.js +0 -1
- package/core/built/admin/assets/newsletter-BIBHumfV.js +0 -1
- package/core/built/admin/assets/newsletter-detail-modal-CfjBMH3v.js +0 -1
- package/core/built/admin/assets/newsletter-emails-ClDL-7dA.js +0 -1
- package/core/built/admin/assets/newsletters-CBe-IPzK.js +0 -1
- package/core/built/admin/assets/note-HnNUOt4d.js +0 -1
- package/core/built/admin/assets/notifications-Cj5e-258.js +0 -1
- package/core/built/admin/assets/offers-container-modal-BHwHtixC.js +0 -1
- package/core/built/admin/assets/onboarding-route-D_4jp7Nq.js +0 -1
- package/core/built/admin/assets/overview-CezDdg-F.js +0 -1
- package/core/built/admin/assets/overview-D5mM51Lj.js +0 -1
- package/core/built/admin/assets/pintura-modal-BXXJ8Iaa.js +0 -1
- package/core/built/admin/assets/portal-frame-BLCvF8f-.js +0 -1
- package/core/built/admin/assets/portal-modal-DN80tAcy.js +0 -1
- package/core/built/admin/assets/post-analytics-header-_ydSY8qO.js +0 -1
- package/core/built/admin/assets/post-share-modal-CzYak_a5.js +0 -1
- package/core/built/admin/assets/preferences-gneLb6LV.js +0 -5
- package/core/built/admin/assets/preview-modal-Bkq-tgVm.js +0 -1
- package/core/built/admin/assets/profile-CzXmCNTC.js +0 -1
- package/core/built/admin/assets/radio-group-Dsq5ZEbV.js +0 -1
- package/core/built/admin/assets/router-provider-CkB_ArLF.js +0 -1
- package/core/built/admin/assets/select-xuZAuDyS.js +0 -1
- package/core/built/admin/assets/settings-Ct8pabhr.js +0 -1
- package/core/built/admin/assets/settings-KSWWQanQ.js +0 -4
- package/core/built/admin/assets/settings-breadcrumbs-DbPGu-Tu.js +0 -1
- package/core/built/admin/assets/share-modal-CdiUx9WM.js +0 -1
- package/core/built/admin/assets/slack-modal-BP--Z3jE.js +0 -1
- package/core/built/admin/assets/sort-button-CjfKdlsL.js +0 -1
- package/core/built/admin/assets/source-icon-DHj1h6Ts.js +0 -1
- package/core/built/admin/assets/stats-C615kulJ.js +0 -1
- package/core/built/admin/assets/stats-view-BSMRdYiN.js +0 -1
- package/core/built/admin/assets/step-1-pSdGYtIF.js +0 -1
- package/core/built/admin/assets/step-2-T9Uy4eBD.js +0 -1
- package/core/built/admin/assets/step-3-D4uXe_XG.js +0 -2
- package/core/built/admin/assets/stripe-connect-modal-C2vTvepi.js +0 -1
- package/core/built/admin/assets/switch-CE2NuMn-.js +0 -1
- package/core/built/admin/assets/tabs-l9c7FjzI.js +0 -1
- package/core/built/admin/assets/tag-detail-DBw_M2-p.js +0 -2
- package/core/built/admin/assets/tags-DoWsAG6l.js +0 -1
- package/core/built/admin/assets/testimonials-modal-BZKePE2T.js +0 -1
- package/core/built/admin/assets/tier-detail-modal-D65pVWsN.js +0 -1
- package/core/built/admin/assets/toggle-group-uJnl42w3.js +0 -1
- package/core/built/admin/assets/topic-filter-BM7hz0kW.js +0 -1
- package/core/built/admin/assets/transistor-modal-VZOGN3od.js +0 -1
- package/core/built/admin/assets/unsplash-modal-CKGfACxF.js +0 -1
- package/core/built/admin/assets/use-check-theme-limit-error-CGp3CGT5.js +0 -1
- package/core/built/admin/assets/use-feature-flag-CSq2Lwzt.js +0 -1
- package/core/built/admin/assets/use-filter-params-0hcJJuEJ.js +0 -1
- package/core/built/admin/assets/use-limiter-ffwpsZPp.js +0 -2
- package/core/built/admin/assets/use-pintura-editor-Cb41oJyL.js +0 -1
- package/core/built/admin/assets/use-query-params-DHhhu8wi.js +0 -1
- package/core/built/admin/assets/use-reply-chain-data-C0ZUTkfk.js +0 -1
- package/core/built/admin/assets/use-setting-group-Bx8M6FL2.js +0 -1
- package/core/built/admin/assets/use-settings-navigation-BOZcat_V.js +0 -1
- package/core/built/admin/assets/use-upgrade-route-B3_DUiuT.js +0 -1
- package/core/built/admin/assets/user-detail-modal-595z1N5r.js +0 -1
- package/core/built/admin/assets/web-CAta3lRE.js +0 -1
- package/core/built/admin/assets/web-Ds9_wWJK.js +0 -2
- package/core/built/admin/assets/zapier-modal-CcfDZY53.js +0 -1
- package/core/server/data/seeders/utils/database-date.js +0 -28
- package/core/server/services/automations/database-date.js +0 -59
- /package/core/built/admin/assets/{dist-CqO2lWS02.js → dist-CqO2lWS0.js} +0 -0
- /package/core/server/services/{automations → lib}/mailgun-message-id.js +0 -0
|
@@ -16,6 +16,10 @@ const gift_checkout_offer_1 = require("./gift-checkout-offer");
|
|
|
16
16
|
const MS_PER_DAY = 24 * 60 * 60 * 1000;
|
|
17
17
|
const GIFT_REMINDER_LEAD_MS = constants_1.GIFT_REMINDER_LEAD_DAYS * MS_PER_DAY;
|
|
18
18
|
const GIFT_REMINDER_FLOOR_MS = constants_1.GIFT_REMINDER_FLOOR_DAYS * MS_PER_DAY;
|
|
19
|
+
const GIFT_NAME_MAX_LENGTH = 191;
|
|
20
|
+
const GIFT_EMAIL_MAX_LENGTH = 191;
|
|
21
|
+
const GIFT_CHECKOUT_MESSAGE_MAX_LENGTH = 250;
|
|
22
|
+
const GIFT_CHECKOUT_RETENTION_DAYS = 30;
|
|
19
23
|
const errorMessages = {
|
|
20
24
|
giftNotFound: 'This gift does not exist.',
|
|
21
25
|
giftAlreadyRedeemed: 'This gift has already been redeemed.',
|
|
@@ -41,12 +45,84 @@ const GiftPurchaseDataSchema = zod_1.z.object({
|
|
|
41
45
|
stripeCheckoutSessionId: zod_1.z.string().min(1),
|
|
42
46
|
stripePaymentIntentId: zod_1.z.string().min(1)
|
|
43
47
|
});
|
|
48
|
+
const StripeBuyerEmailSchema = zod_1.z.unknown().transform((value) => {
|
|
49
|
+
if (typeof value !== 'string') {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const email = value.trim();
|
|
53
|
+
return email && email.length <= GIFT_EMAIL_MAX_LENGTH ? email : null;
|
|
54
|
+
});
|
|
55
|
+
const GiftPaymentCompletionSchema = zod_1.z.object({
|
|
56
|
+
giftId: zod_1.z.string().min(1),
|
|
57
|
+
buyerEmail: StripeBuyerEmailSchema,
|
|
58
|
+
stripeCustomerId: zod_1.z.string().min(1).nullable(),
|
|
59
|
+
stripeCheckoutSessionId: zod_1.z.string().min(1),
|
|
60
|
+
stripePaymentIntentId: zod_1.z.string().min(1)
|
|
61
|
+
});
|
|
62
|
+
const NullableCheckoutStringSchema = (max) => zod_1.z.preprocess(value => typeof value === 'string' && value.trim() === '' ? null : value, zod_1.z.string().trim().max(max).nullable().optional().default(null));
|
|
63
|
+
const EmptyCheckoutStringSchema = zod_1.z.preprocess(value => typeof value === 'string' && value.trim() === '' ? null : value, zod_1.z.null().optional().default(null));
|
|
64
|
+
const RequiredCheckoutStringSchema = (max) => zod_1.z.string().trim().min(1).max(max);
|
|
65
|
+
const CheckoutBuyerEmailSchema = zod_1.z.string().trim().email().max(GIFT_EMAIL_MAX_LENGTH);
|
|
66
|
+
const GiftCheckoutDeliverySchema = zod_1.z.discriminatedUnion('deliveryMethod', [
|
|
67
|
+
zod_1.z.object({
|
|
68
|
+
deliveryMethod: zod_1.z.literal('link'),
|
|
69
|
+
recipientEmail: EmptyCheckoutStringSchema,
|
|
70
|
+
recipientName: EmptyCheckoutStringSchema,
|
|
71
|
+
personalMessage: EmptyCheckoutStringSchema,
|
|
72
|
+
buyerName: NullableCheckoutStringSchema(GIFT_NAME_MAX_LENGTH)
|
|
73
|
+
}),
|
|
74
|
+
zod_1.z.object({
|
|
75
|
+
deliveryMethod: zod_1.z.literal('email'),
|
|
76
|
+
recipientEmail: zod_1.z.string().trim().email().max(GIFT_EMAIL_MAX_LENGTH),
|
|
77
|
+
recipientName: NullableCheckoutStringSchema(GIFT_NAME_MAX_LENGTH),
|
|
78
|
+
personalMessage: NullableCheckoutStringSchema(GIFT_CHECKOUT_MESSAGE_MAX_LENGTH),
|
|
79
|
+
buyerName: RequiredCheckoutStringSchema(GIFT_NAME_MAX_LENGTH)
|
|
80
|
+
})
|
|
81
|
+
]);
|
|
44
82
|
class GiftService {
|
|
45
83
|
deps;
|
|
46
84
|
constructor(deps) {
|
|
47
85
|
this.deps = deps;
|
|
48
86
|
}
|
|
49
87
|
async startCheckout(input) {
|
|
88
|
+
const customizationEnabled = this.deps.labsService.isSet('giftSubCustomization');
|
|
89
|
+
const populatedDeliveryFields = [
|
|
90
|
+
input.recipientEmail,
|
|
91
|
+
input.recipientName,
|
|
92
|
+
input.buyerName,
|
|
93
|
+
input.personalMessage
|
|
94
|
+
].some(value => value !== undefined && value !== null && value !== '');
|
|
95
|
+
if (!customizationEnabled && (input.deliveryMethod === 'email' || populatedDeliveryFields)) {
|
|
96
|
+
throw new errors_1.default.BadRequestError({
|
|
97
|
+
message: 'Bad Request.',
|
|
98
|
+
context: 'Gift email delivery is not available'
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const parsedBuyerEmail = CheckoutBuyerEmailSchema.safeParse(input.buyer.email);
|
|
102
|
+
if (customizationEnabled && !parsedBuyerEmail.success) {
|
|
103
|
+
throw new errors_1.default.BadRequestError({
|
|
104
|
+
message: 'Bad Request.',
|
|
105
|
+
context: `Invalid gift buyer email: ${parsedBuyerEmail.error.issues[0].message}`
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
const buyerEmail = parsedBuyerEmail.success ? parsedBuyerEmail.data : input.buyer.email;
|
|
109
|
+
const parsedDelivery = GiftCheckoutDeliverySchema.safeParse(customizationEnabled ? {
|
|
110
|
+
deliveryMethod: input.deliveryMethod ?? 'link',
|
|
111
|
+
recipientEmail: input.recipientEmail,
|
|
112
|
+
recipientName: input.recipientName,
|
|
113
|
+
buyerName: input.buyerName,
|
|
114
|
+
personalMessage: input.personalMessage
|
|
115
|
+
} : {
|
|
116
|
+
deliveryMethod: 'link'
|
|
117
|
+
});
|
|
118
|
+
if (!parsedDelivery.success) {
|
|
119
|
+
const issue = parsedDelivery.error.issues[0];
|
|
120
|
+
throw new errors_1.default.BadRequestError({
|
|
121
|
+
message: 'Bad Request.',
|
|
122
|
+
context: `Invalid gift delivery data: ${issue.message}`
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
const delivery = parsedDelivery.data;
|
|
50
126
|
if (input.offerId) {
|
|
51
127
|
throw new errors_1.default.BadRequestError({
|
|
52
128
|
message: 'Bad Request.',
|
|
@@ -61,7 +137,7 @@ class GiftService {
|
|
|
61
137
|
}
|
|
62
138
|
let resolvedDuration = null;
|
|
63
139
|
let cadence;
|
|
64
|
-
if (
|
|
140
|
+
if (customizationEnabled) {
|
|
65
141
|
resolvedDuration = (0, gift_checkout_offer_1.resolveGiftDuration)(input);
|
|
66
142
|
cadence = resolvedDuration.cadence;
|
|
67
143
|
}
|
|
@@ -115,32 +191,68 @@ class GiftService {
|
|
|
115
191
|
successUrl.searchParams.set('gift_token', token);
|
|
116
192
|
successUrl.searchParams.set('gift_tier', tierId);
|
|
117
193
|
successUrl.searchParams.set('gift_cadence', cadence);
|
|
194
|
+
successUrl.searchParams.set('gift_delivery', delivery.deliveryMethod);
|
|
118
195
|
if (totalMonths !== undefined) {
|
|
119
196
|
successUrl.searchParams.set('gift_duration', String(totalMonths));
|
|
120
197
|
}
|
|
121
|
-
const
|
|
122
|
-
|
|
198
|
+
const buyer = { ...input.buyer, email: buyerEmail };
|
|
199
|
+
const customerId = buyer.isAuthenticated
|
|
200
|
+
? await this.deps.checkoutAdapter.getCustomerId(buyer)
|
|
123
201
|
: null;
|
|
124
|
-
const
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
202
|
+
const gift = gift_1.Gift.fromCheckout({
|
|
203
|
+
token,
|
|
204
|
+
buyerEmail,
|
|
205
|
+
buyerMemberId: buyer.isAuthenticated ? buyer.memberId : null,
|
|
206
|
+
buyerName: delivery.buyerName,
|
|
207
|
+
recipientName: delivery.recipientName,
|
|
208
|
+
personalMessage: delivery.personalMessage,
|
|
209
|
+
tierId,
|
|
128
210
|
cadence,
|
|
129
211
|
duration,
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
212
|
+
currency: tier.currency.toLowerCase(),
|
|
213
|
+
amount
|
|
214
|
+
});
|
|
215
|
+
const giftId = await this.deps.giftRepository.transaction(async (transacting) => {
|
|
216
|
+
const id = await this.deps.giftRepository.create(gift, { transacting });
|
|
217
|
+
if (delivery.deliveryMethod === 'email') {
|
|
218
|
+
await this.deps.giftDeliveryService.createForCheckout({
|
|
219
|
+
giftId: id,
|
|
220
|
+
recipientEmail: delivery.recipientEmail
|
|
221
|
+
}, { transacting });
|
|
222
|
+
}
|
|
223
|
+
return id;
|
|
142
224
|
});
|
|
143
|
-
|
|
225
|
+
let session;
|
|
226
|
+
try {
|
|
227
|
+
session = await this.deps.checkoutAdapter.createSession({
|
|
228
|
+
amount,
|
|
229
|
+
currency: tier.currency.toLowerCase(),
|
|
230
|
+
tierName: tier.name,
|
|
231
|
+
cadence,
|
|
232
|
+
duration,
|
|
233
|
+
metadata: { ghost_gift_id: giftId },
|
|
234
|
+
successUrl: successUrl.toString(),
|
|
235
|
+
cancelUrl: input.cancelUrl,
|
|
236
|
+
customerId,
|
|
237
|
+
customerEmail: customerId ? null : buyerEmail,
|
|
238
|
+
idempotencyKey: giftId
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
catch (err) {
|
|
242
|
+
try {
|
|
243
|
+
await this.deps.giftRepository.deletePendingCheckout(giftId);
|
|
244
|
+
}
|
|
245
|
+
catch (cleanupError) {
|
|
246
|
+
logging_1.default.error(cleanupError, `Failed to clean up gift checkout ${giftId} after Stripe session creation failed`);
|
|
247
|
+
}
|
|
248
|
+
throw err;
|
|
249
|
+
}
|
|
250
|
+
const bound = gift.bindCheckoutSession(session.id);
|
|
251
|
+
if (!bound) {
|
|
252
|
+
throw new errors_1.default.InternalServerError({ message: `Failed to bind checkout session to gift: ${giftId}` });
|
|
253
|
+
}
|
|
254
|
+
await this.deps.giftRepository.update(bound);
|
|
255
|
+
return { url: session.url };
|
|
144
256
|
}
|
|
145
257
|
generateToken() {
|
|
146
258
|
/**
|
|
@@ -155,6 +267,82 @@ class GiftService {
|
|
|
155
267
|
return token;
|
|
156
268
|
}
|
|
157
269
|
async completePurchase(input) {
|
|
270
|
+
if ('giftId' in input) {
|
|
271
|
+
return this.completePendingPurchase(input);
|
|
272
|
+
}
|
|
273
|
+
return this.completeLegacyPurchase(input);
|
|
274
|
+
}
|
|
275
|
+
async completePendingPurchase(input) {
|
|
276
|
+
const parsed = GiftPaymentCompletionSchema.safeParse(input);
|
|
277
|
+
if (!parsed.success) {
|
|
278
|
+
const issue = parsed.error.issues[0];
|
|
279
|
+
throw new errors_1.default.ValidationError({
|
|
280
|
+
message: 'Invalid gift purchase data.',
|
|
281
|
+
property: issue.path.join('.'),
|
|
282
|
+
context: issue.message
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
const data = parsed.data;
|
|
286
|
+
const member = data.stripeCustomerId
|
|
287
|
+
? await this.deps.memberRepository.get({ customer_id: data.stripeCustomerId })
|
|
288
|
+
: null;
|
|
289
|
+
const completed = await this.deps.giftRepository.transaction(async (transacting) => {
|
|
290
|
+
const gift = await this.deps.giftRepository.getById(data.giftId, { transacting, forUpdate: true });
|
|
291
|
+
if (!gift) {
|
|
292
|
+
logging_1.default.error({
|
|
293
|
+
event: { name: 'gift_purchase.completion_gift_missing' },
|
|
294
|
+
giftId: data.giftId,
|
|
295
|
+
stripeCheckoutSessionId: data.stripeCheckoutSessionId,
|
|
296
|
+
stripePaymentIntentId: data.stripePaymentIntentId
|
|
297
|
+
}, 'Paid checkout completion has no matching gift');
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
if (gift.status !== 'payment_pending') {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
if (gift.stripeCheckoutSessionId && gift.stripeCheckoutSessionId !== data.stripeCheckoutSessionId) {
|
|
304
|
+
throw new errors_1.default.ValidationError({ message: 'Checkout session does not match gift.' });
|
|
305
|
+
}
|
|
306
|
+
const buyerEmail = gift.buyerEmail ?? data.buyerEmail ?? member?.get('email') ?? null;
|
|
307
|
+
if (!buyerEmail) {
|
|
308
|
+
throw new errors_1.default.ValidationError({
|
|
309
|
+
message: 'Invalid gift purchase data.',
|
|
310
|
+
property: 'buyerEmail',
|
|
311
|
+
context: 'A purchased gift requires a buyer email'
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
// The pre-created gift owns the checkout price. Stripe's total may
|
|
315
|
+
// include automatic tax, so completion only adds settlement facts.
|
|
316
|
+
const purchased = gift.completePurchase({
|
|
317
|
+
buyerEmail,
|
|
318
|
+
buyerMemberId: member?.id ?? gift.buyerMemberId,
|
|
319
|
+
stripeCheckoutSessionId: data.stripeCheckoutSessionId,
|
|
320
|
+
stripePaymentIntentId: data.stripePaymentIntentId
|
|
321
|
+
});
|
|
322
|
+
if (!purchased) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
await this.deps.giftRepository.update(purchased, { transacting });
|
|
326
|
+
return purchased;
|
|
327
|
+
});
|
|
328
|
+
if (!completed) {
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
let recipientEmail = null;
|
|
332
|
+
try {
|
|
333
|
+
recipientEmail = await this.deps.giftDeliveryService.dispatchForGift(data.giftId);
|
|
334
|
+
}
|
|
335
|
+
catch (err) {
|
|
336
|
+
logging_1.default.error({
|
|
337
|
+
event: { name: 'gift_delivery.dispatch_failed' },
|
|
338
|
+
err,
|
|
339
|
+
giftId: data.giftId
|
|
340
|
+
}, 'Failed to dispatch purchased gift delivery');
|
|
341
|
+
}
|
|
342
|
+
await this.sendPurchaseNotifications(completed, member, recipientEmail);
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
async completeLegacyPurchase(input) {
|
|
158
346
|
const parsed = GiftPurchaseDataSchema.safeParse(input);
|
|
159
347
|
if (!parsed.success) {
|
|
160
348
|
const issue = parsed.error.issues[0];
|
|
@@ -184,20 +372,44 @@ class GiftService {
|
|
|
184
372
|
stripePaymentIntentId: data.stripePaymentIntentId
|
|
185
373
|
});
|
|
186
374
|
await this.deps.giftRepository.create(gift);
|
|
187
|
-
|
|
375
|
+
await this.sendPurchaseNotifications(gift, member, null);
|
|
376
|
+
return true;
|
|
377
|
+
}
|
|
378
|
+
async sendPurchaseNotifications(gift, member, recipientEmail) {
|
|
379
|
+
let tier;
|
|
380
|
+
try {
|
|
381
|
+
tier = await this.deps.tiersService.api.read(gift.tierId);
|
|
382
|
+
}
|
|
383
|
+
catch (err) {
|
|
384
|
+
logging_1.default.error({
|
|
385
|
+
event: { name: 'gift_purchase_notifications.tier_read_failed' },
|
|
386
|
+
err,
|
|
387
|
+
tierId: gift.tierId
|
|
388
|
+
}, 'Failed to read tier for gift purchase notifications');
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
188
391
|
if (!tier) {
|
|
189
|
-
|
|
392
|
+
logging_1.default.error({
|
|
393
|
+
event: { name: 'gift_purchase_notifications.tier_missing' },
|
|
394
|
+
tierId: gift.tierId
|
|
395
|
+
}, 'Tier not found for gift purchase notifications');
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
const buyerEmail = gift.buyerEmail ?? member?.get('email') ?? null;
|
|
399
|
+
if (!buyerEmail) {
|
|
400
|
+
logging_1.default.warn('Skipping purchase notifications because the buyer email is unavailable');
|
|
401
|
+
return;
|
|
190
402
|
}
|
|
191
403
|
try {
|
|
192
404
|
await this.deps.staffServiceEmails.notifyGiftPurchased({
|
|
193
405
|
name: member?.get('name') ?? null,
|
|
194
|
-
email: member?.get('email') ??
|
|
406
|
+
email: member?.get('email') ?? buyerEmail,
|
|
195
407
|
memberId: member?.id ?? null,
|
|
196
|
-
amount:
|
|
197
|
-
currency:
|
|
408
|
+
amount: gift.amount,
|
|
409
|
+
currency: gift.currency,
|
|
198
410
|
tierName: tier.name,
|
|
199
|
-
cadence:
|
|
200
|
-
duration:
|
|
411
|
+
cadence: gift.cadence,
|
|
412
|
+
duration: gift.duration
|
|
201
413
|
});
|
|
202
414
|
}
|
|
203
415
|
catch (err) {
|
|
@@ -205,23 +417,25 @@ class GiftService {
|
|
|
205
417
|
}
|
|
206
418
|
try {
|
|
207
419
|
await this.deps.giftEmailService.sendPurchaseConfirmation({
|
|
208
|
-
buyerEmail
|
|
209
|
-
token:
|
|
420
|
+
buyerEmail,
|
|
421
|
+
token: gift.token,
|
|
210
422
|
tierName: tier.name,
|
|
211
|
-
cadence:
|
|
212
|
-
duration:
|
|
213
|
-
expiresAt: gift.expiresAt
|
|
423
|
+
cadence: gift.cadence,
|
|
424
|
+
duration: gift.duration,
|
|
425
|
+
expiresAt: gift.expiresAt,
|
|
426
|
+
recipientEmail
|
|
214
427
|
});
|
|
215
428
|
}
|
|
216
429
|
catch (err) {
|
|
217
430
|
logging_1.default.error('Failed to send gift purchase confirmation email', err);
|
|
218
431
|
}
|
|
219
|
-
return true;
|
|
220
432
|
}
|
|
221
433
|
assertRedeemable(gift, memberStatus) {
|
|
222
434
|
const redeemableCheck = gift.checkRedeemable(memberStatus);
|
|
223
435
|
if (!redeemableCheck.redeemable) {
|
|
224
436
|
switch (redeemableCheck.reason) {
|
|
437
|
+
case 'payment-pending':
|
|
438
|
+
throw new errors_1.default.NotFoundError({ message: (0, tpl_1.default)(errorMessages.giftNotFound) });
|
|
225
439
|
case 'redeemed':
|
|
226
440
|
throw new errors_1.default.BadRequestError({
|
|
227
441
|
message: (0, tpl_1.default)(errorMessages.giftAlreadyRedeemed),
|
|
@@ -332,6 +546,7 @@ class GiftService {
|
|
|
332
546
|
status: 'gift'
|
|
333
547
|
}, { id: memberId, transacting });
|
|
334
548
|
await this.deps.giftRepository.update(redeemed, { transacting });
|
|
549
|
+
await this.deps.giftDeliveryService.cancelPendingForGift(redeemed.token, { transacting });
|
|
335
550
|
// Gift members receive the paid welcome email, as they receive access to paid content
|
|
336
551
|
await this.deps.memberRepository.triggerMemberSignupAutomation(memberId, member.get('email'), 'paid', { transacting });
|
|
337
552
|
return { redeemed, member };
|
|
@@ -390,7 +605,7 @@ class GiftService {
|
|
|
390
605
|
}
|
|
391
606
|
async getPreview(token) {
|
|
392
607
|
const gift = await this.deps.giftRepository.getByToken(token);
|
|
393
|
-
if (!gift) {
|
|
608
|
+
if (!gift || gift.status === 'payment_pending') {
|
|
394
609
|
return null;
|
|
395
610
|
}
|
|
396
611
|
const tier = await this.deps.tiersService.api.read(gift.tierId);
|
|
@@ -481,6 +696,7 @@ class GiftService {
|
|
|
481
696
|
}
|
|
482
697
|
await this.deps.giftRepository.transaction(async (transacting) => {
|
|
483
698
|
await this.deps.giftRepository.update(refunded, { transacting });
|
|
699
|
+
await this.deps.giftDeliveryService.cancelPendingForGift(refunded.token, { transacting });
|
|
484
700
|
if (gift.redeemerMemberId) {
|
|
485
701
|
const member = await this.deps.memberRepository.get({ id: gift.redeemerMemberId }, { transacting });
|
|
486
702
|
if (member?.get('status') === 'gift') {
|
|
@@ -562,11 +778,17 @@ class GiftService {
|
|
|
562
778
|
return;
|
|
563
779
|
}
|
|
564
780
|
await this.deps.giftRepository.update(expired, { transacting });
|
|
781
|
+
await this.deps.giftDeliveryService.cancelPendingForGift(expired.token, { transacting });
|
|
565
782
|
expiredCount += 1;
|
|
566
783
|
});
|
|
567
784
|
}
|
|
568
785
|
return { expiredCount };
|
|
569
786
|
}
|
|
787
|
+
async processAbandonedCheckouts() {
|
|
788
|
+
const cutoff = new Date(Date.now() - GIFT_CHECKOUT_RETENTION_DAYS * MS_PER_DAY);
|
|
789
|
+
const deletedCount = await this.deps.giftRepository.deleteAbandonedCheckouts(cutoff);
|
|
790
|
+
return { deletedCount };
|
|
791
|
+
}
|
|
570
792
|
async processReminders() {
|
|
571
793
|
const now = new Date();
|
|
572
794
|
const toRemind = await this.deps.giftRepository.findPendingReminder({
|
|
@@ -668,6 +890,9 @@ class GiftService {
|
|
|
668
890
|
duration: gift.duration,
|
|
669
891
|
currency: gift.currency,
|
|
670
892
|
amount: gift.amount,
|
|
893
|
+
buyer_name: gift.buyerName,
|
|
894
|
+
recipient_name: gift.recipientName,
|
|
895
|
+
message: gift.personalMessage,
|
|
671
896
|
expires_at: gift.expiresAt,
|
|
672
897
|
consumes_at: gift.consumesAt,
|
|
673
898
|
tier: {
|
|
@@ -6,6 +6,9 @@ class Gift {
|
|
|
6
6
|
token;
|
|
7
7
|
buyerEmail;
|
|
8
8
|
buyerMemberId;
|
|
9
|
+
buyerName;
|
|
10
|
+
recipientName;
|
|
11
|
+
personalMessage;
|
|
9
12
|
redeemerMemberId;
|
|
10
13
|
tierId;
|
|
11
14
|
cadence;
|
|
@@ -14,6 +17,7 @@ class Gift {
|
|
|
14
17
|
amount;
|
|
15
18
|
stripeCheckoutSessionId;
|
|
16
19
|
stripePaymentIntentId;
|
|
20
|
+
checkoutStartedAt;
|
|
17
21
|
consumesAt;
|
|
18
22
|
expiresAt;
|
|
19
23
|
status;
|
|
@@ -27,6 +31,9 @@ class Gift {
|
|
|
27
31
|
this.token = data.token;
|
|
28
32
|
this.buyerEmail = data.buyerEmail;
|
|
29
33
|
this.buyerMemberId = data.buyerMemberId;
|
|
34
|
+
this.buyerName = data.buyerName ?? null;
|
|
35
|
+
this.recipientName = data.recipientName ?? null;
|
|
36
|
+
this.personalMessage = data.personalMessage ?? null;
|
|
30
37
|
this.redeemerMemberId = data.redeemerMemberId;
|
|
31
38
|
this.tierId = data.tierId;
|
|
32
39
|
this.cadence = data.cadence;
|
|
@@ -35,27 +42,29 @@ class Gift {
|
|
|
35
42
|
this.amount = data.amount;
|
|
36
43
|
this.stripeCheckoutSessionId = data.stripeCheckoutSessionId;
|
|
37
44
|
this.stripePaymentIntentId = data.stripePaymentIntentId;
|
|
45
|
+
this.checkoutStartedAt = data.checkoutStartedAt ?? null;
|
|
38
46
|
this.consumesAt = data.consumesAt;
|
|
47
|
+
this.purchasedAt = data.purchasedAt;
|
|
39
48
|
this.expiresAt = data.expiresAt;
|
|
40
49
|
this.status = data.status;
|
|
41
|
-
this.purchasedAt = data.purchasedAt;
|
|
42
50
|
this.redeemedAt = data.redeemedAt;
|
|
43
51
|
this.consumedAt = data.consumedAt;
|
|
44
52
|
this.expiredAt = data.expiredAt;
|
|
45
53
|
this.refundedAt = data.refundedAt;
|
|
46
|
-
this.consumesSoonReminderSentAt = data.consumesSoonReminderSentAt;
|
|
54
|
+
this.consumesSoonReminderSentAt = data.consumesSoonReminderSentAt ?? null;
|
|
47
55
|
}
|
|
48
56
|
static fromPurchase(data) {
|
|
49
|
-
const
|
|
50
|
-
const expiresAt = new Date(
|
|
57
|
+
const purchasedAt = new Date();
|
|
58
|
+
const expiresAt = new Date(purchasedAt);
|
|
51
59
|
expiresAt.setDate(expiresAt.getDate() + constants_1.GIFT_EXPIRY_DAYS);
|
|
52
60
|
return new Gift({
|
|
53
61
|
...data,
|
|
54
62
|
redeemerMemberId: null,
|
|
55
63
|
consumesAt: null,
|
|
64
|
+
checkoutStartedAt: purchasedAt,
|
|
56
65
|
expiresAt,
|
|
57
66
|
status: 'purchased',
|
|
58
|
-
purchasedAt
|
|
67
|
+
purchasedAt,
|
|
59
68
|
redeemedAt: null,
|
|
60
69
|
consumedAt: null,
|
|
61
70
|
expiredAt: null,
|
|
@@ -63,6 +72,48 @@ class Gift {
|
|
|
63
72
|
consumesSoonReminderSentAt: null
|
|
64
73
|
});
|
|
65
74
|
}
|
|
75
|
+
static fromCheckout(data) {
|
|
76
|
+
return new Gift({
|
|
77
|
+
...data,
|
|
78
|
+
redeemerMemberId: null,
|
|
79
|
+
stripeCheckoutSessionId: null,
|
|
80
|
+
stripePaymentIntentId: null,
|
|
81
|
+
checkoutStartedAt: new Date(),
|
|
82
|
+
consumesAt: null,
|
|
83
|
+
expiresAt: null,
|
|
84
|
+
status: 'payment_pending',
|
|
85
|
+
purchasedAt: null,
|
|
86
|
+
redeemedAt: null,
|
|
87
|
+
consumedAt: null,
|
|
88
|
+
expiredAt: null,
|
|
89
|
+
refundedAt: null,
|
|
90
|
+
consumesSoonReminderSentAt: null
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
completePurchase(data) {
|
|
94
|
+
if (this.status !== 'payment_pending') {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
const purchasedAt = data.purchasedAt ?? new Date();
|
|
98
|
+
const expiresAt = new Date(purchasedAt);
|
|
99
|
+
expiresAt.setDate(expiresAt.getDate() + constants_1.GIFT_EXPIRY_DAYS);
|
|
100
|
+
return new Gift({
|
|
101
|
+
...this,
|
|
102
|
+
...data,
|
|
103
|
+
purchasedAt,
|
|
104
|
+
expiresAt,
|
|
105
|
+
status: 'purchased'
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
bindCheckoutSession(checkoutSessionId) {
|
|
109
|
+
if (this.status !== 'payment_pending' || this.stripeCheckoutSessionId !== null) {
|
|
110
|
+
return null;
|
|
111
|
+
}
|
|
112
|
+
return new Gift({
|
|
113
|
+
...this,
|
|
114
|
+
stripeCheckoutSessionId: checkoutSessionId
|
|
115
|
+
});
|
|
116
|
+
}
|
|
66
117
|
isRedeemed() {
|
|
67
118
|
return this.redeemedAt !== null;
|
|
68
119
|
}
|
|
@@ -76,6 +127,9 @@ class Gift {
|
|
|
76
127
|
return this.consumedAt !== null;
|
|
77
128
|
}
|
|
78
129
|
checkRedeemable(memberStatus) {
|
|
130
|
+
if (this.status === 'payment_pending') {
|
|
131
|
+
return { redeemable: false, reason: 'payment-pending' };
|
|
132
|
+
}
|
|
79
133
|
if (this.isRedeemed()) {
|
|
80
134
|
return { redeemable: false, reason: 'redeemed' };
|
|
81
135
|
}
|
|
@@ -2,16 +2,21 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.service = exports.controller = void 0;
|
|
4
4
|
exports.init = init;
|
|
5
|
+
exports.recoverPendingDeliveries = recoverPendingDeliveries;
|
|
5
6
|
const gift_bookshelf_repository_1 = require("./gift-bookshelf-repository");
|
|
7
|
+
const gift_delivery_bookshelf_repository_1 = require("./gift-delivery-bookshelf-repository");
|
|
8
|
+
const gift_delivery_service_1 = require("./gift-delivery-service");
|
|
6
9
|
const gift_service_1 = require("./gift-service");
|
|
7
10
|
const gift_reminder_scheduler_1 = require("./gift-reminder-scheduler");
|
|
8
11
|
const gift_email_service_1 = require("./gift-email-service");
|
|
9
12
|
const gift_controller_1 = require("./gift-controller");
|
|
13
|
+
const send_gift_delivery_event_1 = require("./events/send-gift-delivery-event");
|
|
14
|
+
let deliveryService;
|
|
10
15
|
async function init(options) {
|
|
11
16
|
if (exports.service) {
|
|
12
17
|
return;
|
|
13
18
|
}
|
|
14
|
-
const { Gift: GiftModel, MemberStripeCustomer: StripeCustomerModel } = require('../../models');
|
|
19
|
+
const { Base: BaseModel, Gift: GiftModel, GiftDelivery: GiftDeliveryModel, MemberStripeCustomer: StripeCustomerModel } = require('../../models');
|
|
15
20
|
const GiftCheckoutAdapter = require('./gift-checkout-adapter');
|
|
16
21
|
const membersService = require('../members');
|
|
17
22
|
const tiersService = require('../tiers');
|
|
@@ -23,6 +28,8 @@ async function init(options) {
|
|
|
23
28
|
const StartGiftCleanupEvent = require('./events/start-gift-cleanup-event');
|
|
24
29
|
const jobs = require('./jobs');
|
|
25
30
|
const { GhostMailer } = require('../mail');
|
|
31
|
+
const MailgunClient = require('../lib/mailgun-client');
|
|
32
|
+
const config = require('../../../shared/config');
|
|
26
33
|
const settingsCache = require('../../../shared/settings-cache');
|
|
27
34
|
const labsService = require('../../../shared/labs');
|
|
28
35
|
const urlUtils = require('../../../shared/url-utils').default;
|
|
@@ -31,20 +38,32 @@ async function init(options) {
|
|
|
31
38
|
const { blogIcon } = require('../../../server/lib/image');
|
|
32
39
|
const { t } = require('../i18n');
|
|
33
40
|
const repository = new gift_bookshelf_repository_1.GiftBookshelfRepository({
|
|
34
|
-
GiftModel
|
|
41
|
+
GiftModel,
|
|
42
|
+
knex: BaseModel.knex
|
|
43
|
+
});
|
|
44
|
+
const deliveryRepository = new gift_delivery_bookshelf_repository_1.GiftDeliveryBookshelfRepository({
|
|
45
|
+
GiftDeliveryModel,
|
|
46
|
+
knex: BaseModel.knex
|
|
35
47
|
});
|
|
36
48
|
const checkoutAdapter = new GiftCheckoutAdapter({
|
|
37
49
|
StripeCustomerModel,
|
|
38
50
|
getStripeApi: () => require('../stripe').api
|
|
39
51
|
});
|
|
40
52
|
const giftEmailService = new gift_email_service_1.GiftEmailService({
|
|
41
|
-
|
|
53
|
+
transactionalMailer: new GhostMailer(),
|
|
54
|
+
bulkMailer: new MailgunClient({ config, settings: settingsCache }),
|
|
42
55
|
settingsCache,
|
|
43
56
|
urlUtils,
|
|
44
57
|
getFromAddress: () => EmailAddressParser.stringify(settingsHelpers.getDefaultEmail()),
|
|
45
58
|
blogIcon,
|
|
46
59
|
t
|
|
47
60
|
});
|
|
61
|
+
const giftDeliveryService = new gift_delivery_service_1.GiftDeliveryService({
|
|
62
|
+
giftRepository: repository,
|
|
63
|
+
giftDeliveryRepository: deliveryRepository,
|
|
64
|
+
tiersService,
|
|
65
|
+
giftEmailService
|
|
66
|
+
});
|
|
48
67
|
const giftReminderScheduler = new gift_reminder_scheduler_1.GiftReminderScheduler({
|
|
49
68
|
apiUrl: options.apiUrl,
|
|
50
69
|
adapter: options.schedulerAdapter,
|
|
@@ -53,6 +72,7 @@ async function init(options) {
|
|
|
53
72
|
});
|
|
54
73
|
const giftService = new gift_service_1.GiftService({
|
|
55
74
|
giftRepository: repository,
|
|
75
|
+
giftDeliveryService,
|
|
56
76
|
get memberRepository() {
|
|
57
77
|
return membersService.api.members;
|
|
58
78
|
},
|
|
@@ -67,6 +87,7 @@ async function init(options) {
|
|
|
67
87
|
settingsCache
|
|
68
88
|
});
|
|
69
89
|
exports.service = giftService;
|
|
90
|
+
deliveryService = giftDeliveryService;
|
|
70
91
|
exports.controller = new gift_controller_1.GiftController({ service: giftService });
|
|
71
92
|
DomainEvents.subscribe(SubscriptionActivatedEvent, async (event) => {
|
|
72
93
|
try {
|
|
@@ -86,7 +107,25 @@ async function init(options) {
|
|
|
86
107
|
logging.error(err, 'Failed to process gift reminders');
|
|
87
108
|
}
|
|
88
109
|
});
|
|
110
|
+
DomainEvents.subscribe(send_gift_delivery_event_1.SendGiftDeliveryEvent, async (event) => {
|
|
111
|
+
const start = Date.now();
|
|
112
|
+
try {
|
|
113
|
+
const result = await giftDeliveryService.send(event.data.deliveryId);
|
|
114
|
+
logging.info(`Gift delivery ${event.data.deliveryId} ${result} in ${Date.now() - start}ms`);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
logging.error(err, `Failed to process gift delivery ${event.data.deliveryId}`);
|
|
118
|
+
}
|
|
119
|
+
});
|
|
89
120
|
DomainEvents.subscribe(StartGiftCleanupEvent, async () => {
|
|
121
|
+
const checkoutStart = Date.now();
|
|
122
|
+
try {
|
|
123
|
+
const { deletedCount } = await giftService.processAbandonedCheckouts();
|
|
124
|
+
logging.info(`Deleted ${deletedCount} abandoned gift checkouts in ${Date.now() - checkoutStart}ms`);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
logging.error(err, 'Failed to clean abandoned gift checkouts');
|
|
128
|
+
}
|
|
90
129
|
const consumedStart = Date.now();
|
|
91
130
|
try {
|
|
92
131
|
const { consumedCount, updatedMemberCount } = await giftService.processConsumed();
|
|
@@ -103,7 +142,34 @@ async function init(options) {
|
|
|
103
142
|
catch (err) {
|
|
104
143
|
logging.error(err, 'Failed to process expired gifts');
|
|
105
144
|
}
|
|
145
|
+
try {
|
|
146
|
+
const { sentCount, skippedCount, failedCount } = await giftDeliveryService.recoverPending();
|
|
147
|
+
if (sentCount + skippedCount + failedCount > 0) {
|
|
148
|
+
logging.info(`Gift delivery recovery during cleanup: ${sentCount} sent, ${skippedCount} skipped, ${failedCount} failed`);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
logging.error(err, 'Failed to recover pending gift deliveries during cleanup');
|
|
153
|
+
}
|
|
106
154
|
});
|
|
107
155
|
jobs.scheduleGiftCleanupJob();
|
|
108
156
|
jobs.scheduleGiftReminderJob();
|
|
109
157
|
}
|
|
158
|
+
// Retries deliveries interrupted by a previous shutdown. Sending needs the tiers
|
|
159
|
+
// and members services, which boot alongside this one, so this must run once
|
|
160
|
+
// every service has finished initialising rather than from init().
|
|
161
|
+
async function recoverPendingDeliveries() {
|
|
162
|
+
if (!deliveryService) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
const logging = require('@tryghost/logging');
|
|
166
|
+
try {
|
|
167
|
+
const { sentCount, skippedCount, failedCount } = await deliveryService.recoverPending();
|
|
168
|
+
if (sentCount + skippedCount + failedCount > 0) {
|
|
169
|
+
logging.info(`Gift delivery recovery: ${sentCount} sent, ${skippedCount} skipped, ${failedCount} failed`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
logging.error(err, 'Failed to recover pending gift deliveries');
|
|
174
|
+
}
|
|
175
|
+
}
|
|
@@ -95,8 +95,11 @@ module.exports = class MailgunClient {
|
|
|
95
95
|
messageData['o:testmode'] = true;
|
|
96
96
|
}
|
|
97
97
|
|
|
98
|
-
|
|
99
|
-
|
|
98
|
+
if (message.disable_tracking) {
|
|
99
|
+
messageData['o:tracking'] = 'no';
|
|
100
|
+
messageData['o:tracking-clicks'] = 'no';
|
|
101
|
+
messageData['o:tracking-opens'] = 'no';
|
|
102
|
+
} else if (message.track_opens) {
|
|
100
103
|
messageData['o:tracking-opens'] = true;
|
|
101
104
|
}
|
|
102
105
|
|