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
|
@@ -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("../
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
24
|
-
lifecycle work
|
|
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
|
|
28
|
-
and
|
|
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;">✓</span> {{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 {
|
|
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
|
|
6
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
};
|