ghost 6.60.0 → 6.62.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/ghost-storage-base-3.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
- package/components/tryghost-adapter-base-jobs-0.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-redirects-0.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-route-settings-0.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-scheduling-0.2.3.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.1.3.tgz +0 -0
- package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
- package/components/tryghost-checkout-0.0.0.tgz +0 -0
- package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-i18n-0.0.0.tgz +0 -0
- package/components/tryghost-kg-card-factory-5.2.4.tgz +0 -0
- package/components/tryghost-kg-clean-basic-html-4.3.4.tgz +0 -0
- package/components/tryghost-kg-converters-1.2.5.tgz +0 -0
- package/components/tryghost-kg-default-cards-10.3.5.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.2.1.tgz +0 -0
- package/components/tryghost-kg-default-transforms-1.3.4.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/components/tryghost-kg-markdown-html-renderer-7.2.4.tgz +0 -0
- package/components/tryghost-kg-utils-1.1.4.tgz +0 -0
- package/components/tryghost-nql-string-0.0.0.tgz +0 -0
- package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
- package/content/settings/README.md +1 -1
- package/content/themes/casper/.gitignore +28 -0
- package/content/themes/casper/assets/built/casper.js +1 -1
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/css/screen.css +2 -1
- package/content/themes/casper/assets/js/lightbox.js +63 -46
- package/content/themes/casper/package.json +6 -6
- package/content/themes/casper/page.hbs +1 -0
- package/content/themes/casper/post.hbs +1 -0
- package/content/themes/source/.gitignore +28 -0
- package/content/themes/source/assets/built/screen.css +1 -1
- package/content/themes/source/assets/built/source.js +1 -1
- package/content/themes/source/assets/css/screen.css +2 -1
- package/content/themes/source/assets/js/lightbox.js +62 -45
- package/content/themes/source/assets/js/main.js +2 -2
- package/content/themes/source/package.json +4 -4
- package/content/themes/source/partials/feature-image.hbs +1 -0
- package/core/boot.js +62 -21
- package/core/built/admin/assets/Combination-B0l4MKVh.js +41 -0
- package/core/built/admin/assets/InterVariable-DiVDrmQJ.woff2 +0 -0
- package/core/built/admin/assets/InterVariable-Italic-FCBEiFp6.woff2 +0 -0
- package/core/built/admin/assets/{PolarAngleAxis-C4qtHy1B.js → PolarAngleAxis-pkMyqz8g.js} +1 -1
- package/core/built/admin/assets/{RadialBarChart-Bh09Stsj.js → RadialBarChart-JAKoS4KT.js} +1 -1
- package/core/built/admin/assets/{about-BgpmhCuM.js → about-Dyae_UT6.js} +1 -1
- package/core/built/admin/assets/account-migration-CESa0mnX.js +1 -0
- package/core/built/admin/assets/{action-list-lGzZLHc8.js → action-list-C4v0-du8.js} +1 -1
- package/core/built/admin/assets/activitypub/{account-migration-DMHuPnJn.js → account-migration-By69CgCp.js} +16 -16
- package/core/built/admin/assets/activitypub/activitypub.js +9 -10
- package/core/built/admin/assets/activitypub/{avatar-DhTo5D1B.js → avatar-DLPL3Abs.js} +59 -68
- package/core/built/admin/assets/activitypub/{avatar-flipboard-DnVgbEfa.js → avatar-flipboard-C9iDY-QV.js} +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-BT4zAlW7.js → bluesky-sharing-BJcPMrDd.js} +37 -37
- package/core/built/admin/assets/activitypub/{content-formatters-BeDZ-DIa.js → content-formatters-3rTHg3mp.js} +18 -18
- package/core/built/admin/assets/activitypub/{copy-8sqxUuNd.js → copy-DIAOQoCZ.js} +2 -2
- package/core/built/admin/assets/activitypub/{domain-xQDDu1CS.js → domain-DUR4xSkV.js} +121 -121
- package/core/built/admin/assets/activitypub/{edit-profile-BXdF_r2G.js → edit-profile-BqtfzxCp.js} +40 -40
- package/core/built/admin/assets/activitypub/{explore-CMt7gKlL.js → explore-BxuJd-J4.js} +27 -27
- package/core/built/admin/assets/activitypub/{feed-Bwqdddp_.js → feed-BM1TcksA.js} +51 -51
- package/core/built/admin/assets/activitypub/{field-4co_8FVg.js → field-Dn7HHx2-.js} +3 -3
- package/core/built/admin/assets/activitypub/{hash-DwakMs4X.js → hash-Bm3WxYN4.js} +2 -2
- package/core/built/admin/assets/activitypub/inbox-CCjkuow2.js +1284 -0
- package/core/built/admin/assets/activitypub/{inbox-ckD4wo_r.js → inbox-d8oJRTf4.js} +2 -2
- package/core/built/admin/assets/activitypub/label-1AA_s9Fa.js +16 -0
- package/core/built/admin/assets/activitypub/layout-2-0bw6HN.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-KCCO7L2J.js → moderation-CnmxEB8w.js} +91 -91
- package/core/built/admin/assets/activitypub/{note-BX_Bz5A0.js → note-ZKc_Bnba.js} +24 -24
- package/core/built/admin/assets/activitypub/{notifications-BQOYGvRv.js → notifications-CB2tJZau.js} +51 -51
- package/core/built/admin/assets/activitypub/{onboarding-CNelD_uF.js → onboarding-BsJ9E5qf.js} +2 -2
- package/core/built/admin/assets/activitypub/{onboarding-mat5Hrl0.js → onboarding-CSUJJIlh.js} +2 -2
- package/core/built/admin/assets/activitypub/{preferences-6h43G9_q.js → preferences-0-JZnUkI.js} +1519 -1519
- package/core/built/admin/assets/activitypub/{profile-BMN9VVZ2.js → profile-C35cFIhM.js} +256 -256
- package/core/built/admin/assets/activitypub/{reply-ZNo04xsO.js → reply-DYK5vVqb.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-3B9mUQ7i.js → routes-BD4vscyc.js} +796 -790
- package/core/built/admin/assets/activitypub/{separator-D9taVCCa.js → separator-BbQqTm-p.js} +3 -3
- package/core/built/admin/assets/activitypub/settings-BsMROdqh.js +315 -0
- package/core/built/admin/assets/activitypub/{site-DHoMupSB.js → site-O0RL9YCN.js} +2 -2
- package/core/built/admin/assets/activitypub/{step-1-BsGGnBuT.js → step-1-BMSx7c_X.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-2-teosIS0k.js → step-2-DtPQzlTH.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-3-BjD5TokC.js → step-3-CLlOZTyt.js} +11 -11
- package/core/built/admin/assets/activitypub/{tabs-Cvwl1wXU.js → tabs-Dxk-b9M-.js} +306 -306
- package/core/built/admin/assets/activitypub/{topic-filter-C3LZJc2X.js → topic-filter-5yZH6tId.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-Dp9f0gov.js → use-activity-pub-queries-CV50qe03.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-C4RI0sa9.js → use-navigate-with-base-path-Bmk5h4lW.js} +220 -220
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-C2oNDMUH.js → use-reply-chain-data-Cbvzgjzd.js} +30 -30
- package/core/built/admin/assets/activitypub/{users-CoX6FT_K.js → users-BjcoP_HC.js} +7 -5
- package/core/built/admin/assets/activitypub/{x-Dxm6pV9X.js → x-DbQZOLiI.js} +2 -2
- package/core/built/admin/assets/add-integration-modal-BlF6rs_f.js +1 -0
- package/core/built/admin/assets/add-newsletter-modal-CglVQqah.js +1 -0
- package/core/built/admin/assets/add-recommendation-modal-wz7BsuaG.js +1 -0
- package/core/built/admin/assets/analytics-C4HE1gBg.js +1 -0
- package/core/built/admin/assets/{announcement-bar-modal-BTHiy1ZO.js → announcement-bar-modal-Ci4zz8jO.js} +1 -1
- package/core/built/admin/assets/{api-keys-Boy5pLbD.js → api-keys-Ck-Yteiv.js} +1 -1
- package/core/built/admin/assets/audience-CtbA54Sn.js +1 -0
- package/core/built/admin/assets/{automated-emails-te1D5kq-.js → automated-emails-D5VVMND9.js} +1 -1
- package/core/built/admin/assets/automations-CzKt5fb8.js +1 -0
- package/core/built/admin/assets/bluesky-sharing-BEtJ_gL_.js +1 -0
- package/core/built/admin/assets/{box-By_EgGtm.js → box-CdfQAP_F.js} +1 -1
- package/core/built/admin/assets/{brand-icon-09YqFZjV.js → brand-icon-CrIGOA3Z.js} +1 -1
- package/core/built/admin/assets/{breadcrumb-CcFr_ltt.js → breadcrumb-zUrZrBtW.js} +1 -1
- package/core/built/admin/assets/{button-BshWPHvn.js → button-eNMKQWXr.js} +1 -1
- package/core/built/admin/assets/{card-CiiE7QfM.js → card--VNbfgo3.js} +1 -1
- package/core/built/admin/assets/{chart-BnXyfH11.js → chart-BrhA41Zi.js} +1 -1
- package/core/built/admin/assets/chart-helpers-CfBKt0vT.js +1 -0
- package/core/built/admin/assets/{checkbox-Dwwr_3Py.js → checkbox-CgOn_tKW.js} +1 -1
- package/core/built/admin/assets/{chunk.524.91ecb33e6afc3bd8949b.js → chunk.524.a697dee88db67fdf6021.js} +4 -4
- package/core/built/admin/assets/{chunk.582.512549909741883f960c.js → chunk.582.5ed5d124cfca9c259b9c.js} +6 -6
- package/core/built/admin/assets/{chunk.16.f76a6314082d813d67e0.js → chunk.598.7e056fabb47ce7dbd73f.js} +14 -14
- package/core/built/admin/assets/{code-editor-view-BwIdPy_k.js → code-editor-view-BYNoAoNV.js} +1 -1
- package/core/built/admin/assets/{code-editor-view-CtkF82Fv.js → code-editor-view-K2wI-I5f.js} +1 -1
- package/core/built/admin/assets/color-picker-Ddw80xX6.js +3 -0
- package/core/built/admin/assets/color-picker-field-D9nUhvA7.js +1 -0
- package/core/built/admin/assets/combobox-w9j8PPQT.js +1 -0
- package/core/built/admin/assets/command-BfpHORBv.js +1 -0
- package/core/built/admin/assets/comments-BLmCmyCs.js +1 -0
- package/core/built/admin/assets/confirmation-context-CBM8G0Vj.js +1 -0
- package/core/built/admin/assets/{container-LiZf3XdY.js → container-BuQG1wuP.js} +1 -1
- package/core/built/admin/assets/{content-api-modal-BtpNI92-.js → content-api-modal-TXRz3U7S.js} +1 -1
- package/core/built/admin/assets/context-menu-_QMdd3kp.js +1 -0
- package/core/built/admin/assets/{copy-field-CglgRZyG.js → copy-field-6N01S07E.js} +1 -1
- package/core/built/admin/assets/create-ghost-browse-value-source-qIBymvvf.js +1 -0
- package/core/built/admin/assets/custom-field-type-option-BddFlJFa.js +1 -0
- package/core/built/admin/assets/custom-integration-modal-DmNquxUV.js +1 -0
- package/core/built/admin/assets/{data-list-CupF7bjM.js → data-list-CA2jYGPq.js} +1 -1
- package/core/built/admin/assets/{date-range-select-JPwM0yUj.js → date-range-select-uvz_DtPh.js} +1 -1
- package/core/built/admin/assets/design-and-theme-modal-BSmo12Op.js +2 -0
- package/core/built/admin/assets/{detail-page-D8lx2SdP.js → detail-page-Dnv-CdR1.js} +1 -1
- package/core/built/admin/assets/dirty-confirm-dialog-BZ1iytEd.js +1 -0
- package/core/built/admin/assets/{disabled-sources-indicator-BTJ2ClQu.js → disabled-sources-indicator-DMRrxsiJ.js} +1 -1
- package/core/built/admin/assets/{dist-DzcfoDJ3.js → dist-8YJUlf3B.js} +1 -1
- package/core/built/admin/assets/{dist-Cb5RSHSg.js → dist-B3keLvjj.js} +1 -1
- package/core/built/admin/assets/dist-Dcsslyo72.js +1 -0
- package/core/built/admin/assets/{dist-BT_Z-eCn.js → dist-Dgt2cr6a.js} +1 -1
- package/core/built/admin/assets/domain-BNYrt0aD.js +1 -0
- package/core/built/admin/assets/dropzone-D7nFdAe5.js +5 -0
- package/core/built/admin/assets/edit-profile-C5Asm4LA.js +1 -0
- package/core/built/admin/assets/editor-REokrzci.js +8 -0
- package/core/built/admin/assets/{embed-signup-form-modal-BFTPf04i.js → embed-signup-form-modal-Ds6mPEi5.js} +1 -1
- package/core/built/admin/assets/{empty-indicator-WB1WgT9h.js → empty-indicator-D9QdpuNX.js} +1 -1
- package/core/built/admin/assets/en-B1jX3WMs.js +1 -0
- package/core/built/admin/assets/explore-UY10Egsp.js +1 -0
- package/core/built/admin/assets/feature-image-placeholder-D1iF_8bc.js +1 -0
- package/core/built/admin/assets/feed-81C8mJWR.js +1 -0
- package/core/built/admin/assets/{field-LmNk7wZh.js → field-Dihp5p18.js} +1 -1
- package/core/built/admin/assets/filter-bar-Da1qxybu.js +1 -0
- package/core/built/admin/assets/filter-sources-BO9xUPj7.js +1 -0
- package/core/built/admin/assets/filters-CRUWecfY.js +1 -0
- package/core/built/admin/assets/filters-DirqNXjH.js +1 -0
- package/core/built/admin/assets/first-promoter-modal-CT6MxYZb.js +1 -0
- package/core/built/admin/assets/{ghost-c8807202a07c20030acb719ee0114815.css → ghost-8eabfc1ece81b03cf7b90780d31487e8.css} +1 -1
- package/core/built/admin/assets/{ghost-403cb39badad3df411fc0ba27d220047.js → ghost-c2848508292937415f20f5a830eea59e.js} +31 -28
- package/core/built/admin/assets/{ghost-dark-fbf1e384aaef3ae315e540f0eb7373a7.css → ghost-dark-a7bebe2c561f55369dcf29f4c820f7ff.css} +1 -1
- package/core/built/admin/assets/gift-link-modal-CQVAye17.js +1 -0
- package/core/built/admin/assets/{grid-CUciBfjf.js → grid-Dj29PY05.js} +1 -1
- package/core/built/admin/assets/growth-BWIu-6xj.js +3 -0
- package/core/built/admin/assets/growth-DU29oxeA.js +1 -0
- package/core/built/admin/assets/{heading-CRZZ2FUe.js → heading-Dh5SFVjL.js} +1 -1
- package/core/built/admin/assets/history-modal-CFd1Hvx6.js +1 -0
- package/core/built/admin/assets/html-field-CJBCLTvv.js +1 -0
- package/core/built/admin/assets/{images-C0XvUYMb.js → images-scfkWZiK.js} +1 -1
- package/core/built/admin/assets/{inbox-DZXDUt4s.js → inbox-Mcp4DXZN.js} +14 -14
- package/core/built/admin/assets/index-6MkPdCtr.js +4 -0
- package/core/built/admin/assets/index-Bn7QhcZu.css +2 -0
- package/core/built/admin/assets/index.es-CNnuNH0d.js +1 -0
- package/core/built/admin/assets/{inline-Cw-gigDW.js → inline-BWSs_VzR.js} +1 -1
- package/core/built/admin/assets/{input-group-BPAxETpQ.js → input-group-CMhe2DvY.js} +1 -1
- package/core/built/admin/assets/{input-surface-BdOe4KzO.js → input-surface-BsrIZeSF.js} +1 -1
- package/core/built/admin/assets/{integration-header-B4DyXV06.js → integration-header-Bm6IDb1A.js} +1 -1
- package/core/built/admin/assets/invite-user-modal-DfheMXs0.js +1 -0
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +14 -14
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +3 -3
- package/core/built/admin/assets/koenig-lexical/style.css +1 -1
- package/core/built/admin/assets/{koenig-lexical-60O_8aiO.js → koenig-lexical-DBSqAuiI.js} +3 -3
- package/core/built/admin/assets/koenig-loader-aFQNlJlD.js +2 -0
- package/core/built/admin/assets/kpi-card-DBRMfGOW.js +1 -0
- package/core/built/admin/assets/{kpi-card-fUHZlzBX.js → kpi-card-DFafD1qq.js} +1 -1
- package/core/built/admin/assets/kpi-helpers-Cv8i8hns.js +1 -0
- package/core/built/admin/assets/kpi-tabs-BJItvLYm.js +1 -0
- package/core/built/admin/assets/{label-VIG969IC.js → label-BnSCvffu.js} +1 -1
- package/core/built/admin/assets/label-picker-KVoo91sh.js +1 -0
- package/core/built/admin/assets/layout-Bjv2Ll-5.js +1 -0
- package/core/built/admin/assets/{link-helpers-Dtq9fxQg.js → link-helpers-MVDdnFCY.js} +1 -1
- package/core/built/admin/assets/{list-page-CdsnQR9N.js → list-page-BucXJ0hi.js} +1 -1
- package/core/built/admin/assets/member-custom-fields-DDQV6QeG.js +1 -0
- package/core/built/admin/assets/member-detail-CdJF6Agw.js +1 -0
- package/core/built/admin/assets/members-DjQbq9Z2.js +4 -0
- package/core/built/admin/assets/{modal-page-BMTzFHVO.js → modal-page-xCivTMvI.js} +1 -1
- package/core/built/admin/assets/moderation-AgCIEso4.js +1 -0
- package/core/built/admin/assets/{multi-select-combobox-CGohnsxJ.js → multi-select-combobox-BcITkUVr.js} +1 -1
- package/core/built/admin/assets/navigation-modal-BkT7b1M2.js +1 -0
- package/core/built/admin/assets/newsletter-BMMAuvgq.js +1 -0
- package/core/built/admin/assets/newsletter-detail-modal-0SCJ7Zpm.js +1 -0
- package/core/built/admin/assets/{newsletter-emails-D5ql1rud.js → newsletter-emails-DKzwafuh.js} +1 -1
- package/core/built/admin/assets/newsletters-BRBb_uQC.js +1 -0
- package/core/built/admin/assets/note-DyGHtS9a.js +1 -0
- package/core/built/admin/assets/notifications-ZgWniKna.js +1 -0
- package/core/built/admin/assets/nql-BLpCn7qX.js +21 -0
- package/core/built/admin/assets/offers-container-modal-B-E8TIWr.js +1 -0
- package/core/built/admin/assets/{onboarding-B4YhlZ0i.js → onboarding-CzIsre-d.js} +1 -1
- package/core/built/admin/assets/onboarding-qg8Akovm.js +1 -0
- package/core/built/admin/assets/{onboarding-route-Cu_ROWPn.js → onboarding-route-CaavZulg.js} +1 -1
- package/core/built/admin/assets/overview-BW8GLeYi.js +1 -0
- package/core/built/admin/assets/overview-BkXZNzaZ.js +1 -0
- package/core/built/admin/assets/{page-header-Cfds8pCg.js → page-header-De0VzKBi.js} +1 -1
- package/core/built/admin/assets/pagemenu-B5KKCpbA.js +1 -0
- package/core/built/admin/assets/pages-jgaFm69d.js +1 -0
- package/core/built/admin/assets/pages-route-CIUAQxtY.js +1 -0
- package/core/built/admin/assets/papaparse.min-cQwnxGCG.js +10 -0
- package/core/built/admin/assets/{pintura-modal-TDTrJ89x.js → pintura-modal-BZ2J9w3n.js} +1 -1
- package/core/built/admin/assets/portal-frame-B6Hr9wHc.js +1 -0
- package/core/built/admin/assets/portal-modal-D4ceei3t.js +1 -0
- package/core/built/admin/assets/post-analytics-header-BYCsT0GP.js +1 -0
- package/core/built/admin/assets/post-analytics-ot7f6pih.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-Cu3jNeFh.js → post-analytics-provider-Bo1gmYaP.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-P1_M6o-H.js → post-share-modal-C-PaYZ_e.js} +1 -1
- package/core/built/admin/assets/posts-DuGazgTb.js +1 -0
- package/core/built/admin/assets/posts-list-screen-CBGGgjjj.js +2 -0
- package/core/built/admin/assets/posts-route-D6xmS7Vf.js +1 -0
- package/core/built/admin/assets/preferences-C5YJLp74.js +5 -0
- package/core/built/admin/assets/{preview-chrome-BOVO6_PE.js → preview-chrome-BljUBlOk.js} +1 -1
- package/core/built/admin/assets/{preview-modal-MlTjP8UU.js → preview-modal-BfeFqi5y.js} +1 -1
- package/core/built/admin/assets/profile-B8mx2yAu.js +1 -0
- package/core/built/admin/assets/{radio-group-CjtmsCPl.js → radio-group-DczlLKOn.js} +1 -1
- package/core/built/admin/assets/{router-provider-CJC58zTy.js → router-provider-B0gZyiGJ.js} +1 -1
- package/core/built/admin/assets/{schemas-CRsC53ls.js → schemas-DppqmpeJ.js} +2 -2
- package/core/built/admin/assets/select-cb0t_TAJ.js +1 -0
- package/core/built/admin/assets/settings-DZqxR0Da.js +1 -0
- package/core/built/admin/assets/settings-DvfTNdHQ.js +4 -0
- package/core/built/admin/assets/{settings-breadcrumbs-sLDNedBW.js → settings-breadcrumbs-DLzbRRSt.js} +1 -1
- package/core/built/admin/assets/{shade-app-F7WxOyfk-nufqw1hd.js → shade-app-CcS3f8MZ-vwURazk8.js} +2 -2
- package/core/built/admin/assets/share-modal-D2-Bz1_I.js +1 -0
- package/core/built/admin/assets/{skeleton-D7hwjdry.js → skeleton-Ce9GgUpf.js} +1 -1
- package/core/built/admin/assets/slack-modal-DYDTUria.js +1 -0
- package/core/built/admin/assets/sort-button-VVeQjjbu.js +1 -0
- package/core/built/admin/assets/{source-icon-BPsD5ees.js → source-icon-Ck5_5Kgq.js} +1 -1
- package/core/built/admin/assets/{stack-CIfOAAVf.js → stack-B2eszIiq.js} +1 -1
- package/core/built/admin/assets/stats-Rjmk5owg.js +1 -0
- package/core/built/admin/assets/stats-view-BGGmkmgv.js +1 -0
- package/core/built/admin/assets/{step-1-CMta6YFO.js → step-1-CsaBjeN4.js} +1 -1
- package/core/built/admin/assets/{step-2-BG04nh5q.js → step-2-D2vnxOOM.js} +1 -1
- package/core/built/admin/assets/{step-3-ZnMaVIPj.js → step-3-wBoLdGEb.js} +2 -2
- package/core/built/admin/assets/{stripe-button-DDoUKN5a.js → stripe-button-CDgLz0S0.js} +1 -1
- package/core/built/admin/assets/stripe-connect-modal-CSjMJetP.js +1 -0
- package/core/built/admin/assets/{switch-C9djUIMO.js → switch-CI4gLWqb.js} +1 -1
- package/core/built/admin/assets/{table-CrqQtkR2.js → table-Do6iQ83Q.js} +1 -1
- package/core/built/admin/assets/{tabs-CGisSbWq.js → tabs-D2yWLMp_.js} +1 -1
- package/core/built/admin/assets/tag-detail-BhXlOsCa.js +2 -0
- package/core/built/admin/assets/tags-ldukRApe.js +1 -0
- package/core/built/admin/assets/testimonials-modal-CtnZBJKl.js +1 -0
- package/core/built/admin/assets/{text-DU9Hv33_.js → text-BQDSDsKs.js} +1 -1
- package/core/built/admin/assets/{textarea-d9qEkMOP.js → textarea-DJ1Q5V39.js} +1 -1
- package/core/built/admin/assets/tier-detail-modal-BOs8e_G8.js +1 -0
- package/core/built/admin/assets/{toggle-group-CtKZURJd.js → toggle-group-ByUJsxwD.js} +1 -1
- package/core/built/admin/assets/{topic-filter-D8JT8Tq1.js → topic-filter-BgC3g5_v.js} +1 -1
- package/core/built/admin/assets/{transistor-modal-6PUf0idx.js → transistor-modal-Drr0SNfo.js} +1 -1
- package/core/built/admin/assets/trend-badge-6ye93YAW.js +1 -0
- package/core/built/admin/assets/{unsplash-modal-BPuDBPja.js → unsplash-modal-p6AoQoWp.js} +1 -1
- package/core/built/admin/assets/{use-analytics-data--opuzwcz.js → use-analytics-data-CWCMAESo.js} +1 -1
- package/core/built/admin/assets/use-check-theme-limit-error-B8bjF-gO.css +1 -0
- package/core/built/admin/assets/use-check-theme-limit-error-Cw3WQHy8.js +2 -0
- package/core/built/admin/assets/use-filter-params-BIni9taJ.js +1 -0
- package/core/built/admin/assets/use-growth-stats-DCVG3s9w.js +1 -0
- package/core/built/admin/assets/{use-limiter-TDSNiHPB.js → use-limiter-D2WRRx0H.js} +1 -1
- package/core/built/admin/assets/{use-navigate-with-base-path-CK989MlC.js → use-navigate-with-base-path-BHPA228f.js} +1 -1
- package/core/built/admin/assets/{use-pintura-config-CnCGrDFk.js → use-pintura-config-h-J0Lvta.js} +1 -1
- package/core/built/admin/assets/{use-pintura-editor-ntphvtUd.js → use-pintura-editor-BfN1UwzH.js} +1 -1
- package/core/built/admin/assets/use-post-referrers-Czg47m8s.js +1 -0
- package/core/built/admin/assets/use-reply-chain-data-ns0caK8k.js +1 -0
- package/core/built/admin/assets/{use-router-history-entry-BwfQS2mq.js → use-router-history-entry-uldn7zJO.js} +1 -1
- package/core/built/admin/assets/{use-setting-group-DQxV42-O.js → use-setting-group-DpcuCUDg.js} +1 -1
- package/core/built/admin/assets/use-settings-navigation-CZjGrhsp.js +1 -0
- package/core/built/admin/assets/{use-simple-pagination-B2TdNX67.js → use-simple-pagination-DzJWobPR.js} +1 -1
- package/core/built/admin/assets/{use-staff-users-QrR9voeC.js → use-staff-users-Csipb-O0.js} +1 -1
- package/core/built/admin/assets/use-tinybird-query-CSnGXzZh.js +1 -0
- package/core/built/admin/assets/use-tinybird-token-DTOKRR7e.js +1 -0
- package/core/built/admin/assets/{use-unsaved-changes-guard-DpYJcj1d.js → use-unsaved-changes-guard-CBzs-KPi.js} +1 -1
- package/core/built/admin/assets/{use-url-input-WnIqckzS.js → use-url-input-DFr8lCbS.js} +1 -1
- package/core/built/admin/assets/user-detail-modal-B_iUClI6.js +1 -0
- package/core/built/admin/assets/users-Cg-OeGzC.js +1 -0
- package/core/built/admin/assets/{virtual-list-D3MfPNOh.js → virtual-list-BW-padMe.js} +1 -1
- package/core/built/admin/assets/web-C1v33ehA.js +2 -0
- package/core/built/admin/assets/web-C4BKzn2T.js +1 -0
- package/core/built/admin/assets/{zapier-modal-BO6PS_Vd.js → zapier-modal-BBFfI6Ml.js} +1 -1
- package/core/built/admin/index.html +24 -30
- package/core/frontend/apps/private-blogging/lib/views/private.hbs +44 -43
- package/core/frontend/helpers/cancel_link.js +2 -2
- package/core/frontend/helpers/pagination.js +2 -2
- package/core/frontend/helpers/social_accounts.js +2 -2
- package/core/frontend/public/ghost.css +1 -1
- package/core/frontend/public/ghost.min.css +1 -1
- package/core/frontend/services/rendering/renderer.js +15 -0
- package/core/frontend/services/sitemap/user-map-generator.js +9 -2
- package/core/server/adapters/storage/LocalStorageBase.js +8 -5
- package/core/server/api/endpoints/db.js +19 -5
- package/core/server/api/endpoints/index.js +6 -2
- package/core/server/api/endpoints/mail.js +1 -1
- package/core/server/api/endpoints/member-metafields.js +89 -0
- package/core/server/api/endpoints/members.js +4 -4
- package/core/server/api/endpoints/posts.js +4 -3
- package/core/server/api/endpoints/tiers-checkout-config.js +59 -0
- package/core/server/api/endpoints/utils/serializers/input/members.js +4 -7
- package/core/server/api/endpoints/utils/serializers/output/config.js +1 -0
- package/core/server/api/endpoints/utils/serializers/output/index.js +6 -2
- package/core/server/api/endpoints/utils/serializers/output/{member-custom-fields.js → member-metafields.js} +2 -4
- package/core/server/api/endpoints/utils/serializers/output/members.js +2 -4
- package/core/server/api/endpoints/utils/serializers/output/tiers-checkout-config.js +12 -0
- package/core/server/api/endpoints/utils/validators/input/invitations.js +0 -5
- package/core/server/api/endpoints/utils/validators/input/pages.js +1 -1
- package/core/server/api/endpoints/utils/validators/input/posts.js +1 -1
- package/core/server/data/exporter/table-lists.js +3 -0
- package/core/server/data/importer/content-files.js +57 -0
- package/core/server/data/importer/handlers/image.js +2 -2
- package/core/server/data/importer/import-archive.js +131 -0
- package/core/server/data/importer/import-manager.js +18 -180
- package/core/server/data/importer/importers/content-file-importer.js +66 -4
- package/core/server/data/importer/importers/data/data-importer.js +3 -3
- package/core/server/data/migrations/utils/migrations.js +1 -1
- package/core/server/data/migrations/versions/6.61/2026-08-19-14-30-20-add-written-by-to-members-custom-field-values.js +42 -0
- package/core/server/data/migrations/versions/6.61/2026-08-19-14-38-53-add-members-custom-field-bindings-table.js +26 -0
- package/core/server/data/migrations/versions/6.61/2026-08-19-14-48-06-add-tier-checkout-field-tables.js +35 -0
- package/core/server/data/schema/schema.js +90 -0
- package/core/server/data/seeders/importers/automation-action-revisions-importer.js +3 -3
- package/core/server/data/seeders/importers/automation-actions-importer.js +3 -3
- package/core/server/data/seeders/importers/automation-run-steps-importer.js +3 -3
- package/core/server/data/seeders/importers/automation-runs-importer.js +5 -5
- package/core/server/data/seeders/importers/automations-importer.js +3 -3
- package/core/server/data/seeders/importers/comment-reports-importer.js +1 -1
- package/core/server/data/seeders/importers/comments-importer.js +1 -1
- package/core/server/data/seeders/importers/email-batches-importer.js +1 -1
- package/core/server/data/seeders/importers/email-recipients-importer.js +1 -1
- package/core/server/data/seeders/importers/emails-importer.js +1 -1
- package/core/server/data/seeders/importers/labels-importer.js +1 -1
- package/core/server/data/seeders/importers/members-click-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-created-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-feedback-importer.js +1 -1
- package/core/server/data/seeders/importers/members-importer.js +1 -1
- package/core/server/data/seeders/importers/members-login-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-products-importer.js +1 -1
- package/core/server/data/seeders/importers/members-status-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-stripe-customers-importer.js +1 -1
- package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +1 -1
- package/core/server/data/seeders/importers/members-subscribe-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +1 -1
- package/core/server/data/seeders/importers/offer-redemptions-importer.js +1 -1
- package/core/server/data/seeders/importers/offers-importer.js +1 -1
- package/core/server/data/seeders/importers/posts-importer.js +1 -1
- package/core/server/data/seeders/importers/table-importer.js +2 -2
- package/core/server/data/seeders/importers/tags-importer.js +1 -1
- package/core/server/data/seeders/importers/users-importer.js +1 -1
- package/core/server/data/seeders/importers/web-mentions-importer.js +1 -1
- package/core/server/data/seeders/utils/random.js +3 -3
- package/core/server/ghost-server.js +3 -2
- package/core/server/lib/db-types/boolean.js +18 -0
- package/core/server/lib/image/image-size.js +24 -0
- package/core/server/lib/package-json/parse.js +2 -2
- package/core/server/lib/promise-pool.js +31 -0
- package/core/server/models/member.js +6 -17
- package/core/server/models/post.js +2 -2
- package/core/server/models/relations/authors.js +2 -16
- package/core/server/models/user.js +9 -2
- package/core/server/services/auth/passwordreset.js +13 -0
- package/core/server/services/automations/database-automations-repository.js +26 -26
- package/core/server/services/automations/poll.js +1 -0
- package/core/server/services/comments/comments-controller.js +30 -9
- package/core/server/services/comments/comments-service.js +55 -5
- package/core/server/services/content-import/csv/formula.js +12 -0
- package/core/server/services/content-import/csv/index.js +6 -1
- package/core/server/services/content-import/csv/parse.js +27 -6
- package/core/server/services/content-import/csv/serialize.js +13 -0
- package/core/server/services/content-import/import/assets.js +74 -0
- package/core/server/services/content-import/import/completion-email.js +164 -0
- package/core/server/services/content-import/import/errors-file.js +36 -0
- package/core/server/services/content-import/import/importer.js +204 -54
- package/core/server/services/content-import/import/local-media-url.js +63 -0
- package/core/server/services/content-import/import/media.js +254 -0
- package/core/server/services/content-import/import/post-data.js +1 -1
- package/core/server/services/content-import/import/post-link.js +11 -0
- package/core/server/services/content-import/import/post-repository.js +100 -0
- package/core/server/services/content-import/import/reader.js +9 -2
- package/core/server/services/content-import/import/relations.js +149 -0
- package/core/server/services/content-import/import/report.js +34 -0
- package/core/server/services/content-import/import/row.js +11 -2
- package/core/server/services/content-import/import/schema.js +30 -0
- package/core/server/services/content-import/import/source.js +125 -0
- package/core/server/services/content-import/import/staged-file.js +29 -0
- package/core/server/services/content-import/import/store.js +25 -2
- package/core/server/services/content-import/index.js +58 -34
- package/core/server/services/content-import/jobs/content-csv-import-job.js +20 -0
- package/core/server/services/custom-redirects/redirect-config-parser.js +2 -2
- package/core/server/services/custom-redirects/redirects-service.js +1 -1
- package/core/server/services/custom-redirects/validation.js +1 -1
- package/core/server/services/email-address/email-address-parser.js +30 -38
- package/core/server/services/email-address/email-address-service.js +37 -4
- package/core/server/services/email-analytics/email-analytics-service.js +8 -1
- package/core/server/services/email-analytics/index.js +2 -7
- package/core/server/services/email-analytics/lib/queries.js +7 -1
- package/core/server/services/email-service/batch-sending-service.js +0 -4
- package/core/server/services/email-service/email-renderer.js +14 -4
- package/core/server/services/email-service/email-service-wrapper.js +0 -9
- package/core/server/services/email-service/mailgun-email-provider.js +20 -9
- package/core/server/services/gift-links/schema.js +3 -3
- package/core/server/services/gifts/CONTEXT.md +1 -1
- package/core/server/services/gifts/constants.js +1 -1
- package/core/server/services/gifts/email-templates/gift-delivery.hbs +3 -3
- package/core/server/services/gifts/gift-bookshelf-repository.js +2 -2
- package/core/server/services/gifts/gift-delivery-bookshelf-repository.js +18 -9
- package/core/server/services/gifts/gift-delivery-schema.js +5 -5
- package/core/server/services/gifts/gift-delivery-service.js +4 -0
- package/core/server/services/gifts/gift-email-service.js +0 -1
- package/core/server/services/gifts/gift-schema.js +10 -10
- package/core/server/services/gifts/gift-service.js +4 -2
- package/core/server/services/gifts/index.js +3 -3
- package/core/server/services/gifts/jobs/index.js +4 -4
- package/core/server/services/indexnow-ping/indexnow-ping-service.js +2 -2
- package/core/server/services/invitations/accept.js +4 -2
- package/core/server/services/jobs/job-service.js +0 -9
- package/core/server/services/jobs-service/register-job-handlers.js +25 -15
- package/core/server/services/lib/mailgun-client.js +44 -8
- package/core/server/services/mail/ghost-mailer.js +4 -4
- package/core/server/services/media-inliner/external-media-inliner-job.js +12 -0
- package/core/server/services/media-inliner/external-media-inliner.js +103 -29
- package/core/server/services/media-inliner/service.js +13 -41
- package/core/server/services/media-inliner/types.js +2 -0
- package/core/server/services/member-welcome-emails/service.js +7 -7
- package/core/server/services/members/api.js +1 -0
- package/core/server/services/members/import-export/csv/parse.js +0 -7
- package/core/server/services/members/import-export/export/exporter.js +4 -6
- package/core/server/services/members/import-export/import/completion-email.js +2 -5
- package/core/server/services/members/import-export/import/importer.js +21 -5
- package/core/server/services/members/import-export/import/row.js +0 -3
- package/core/server/services/members/import-export/import/stripe-utils.js +6 -1
- package/core/server/services/members/import-export/index.js +10 -12
- package/core/server/services/members/jobs/clean-expired-comped-job.js +7 -0
- package/core/server/services/members/jobs/clean-expired-comped-task.js +145 -0
- package/core/server/services/members/jobs/index.js +48 -24
- package/core/server/services/members/members-api/controllers/member-controller.js +11 -0
- package/core/server/services/members/members-api/controllers/router-controller.js +3 -6
- package/core/server/services/members/members-api/members-api.js +9 -6
- package/core/server/services/members/members-api/repositories/event-repository.js +0 -4
- package/core/server/services/members/members-api/services/member-bread-service.js +53 -63
- package/core/server/services/members/members-api/services/payments-service.js +47 -1
- package/core/server/services/members/service.js +1 -5
- package/core/server/services/members-custom-fields/actions.js +27 -1
- package/core/server/services/members-custom-fields/bindings-service.js +120 -0
- package/core/server/services/members-custom-fields/codec.js +3 -4
- package/core/server/services/members-custom-fields/definitions-service.js +165 -22
- package/core/server/services/members-custom-fields/filter.js +75 -93
- package/core/server/services/members-custom-fields/index.js +11 -4
- package/core/server/services/members-custom-fields/key.js +2 -1
- package/core/server/services/members-custom-fields/models.js +1 -1
- package/core/server/services/members-custom-fields/queries.js +5 -0
- package/core/server/services/members-custom-fields/schema.js +37 -6
- package/core/server/services/members-custom-fields/serializers.js +3 -5
- package/core/server/services/members-custom-fields/values-service.js +65 -31
- package/core/server/services/mentions/mention-controller.js +23 -21
- package/core/server/services/mentions/process-webmention-job.js +16 -0
- package/core/server/services/mentions/service.js +8 -4
- package/core/server/services/public-config/config.js +7 -0
- package/core/server/services/route-settings/validation-errors.js +1 -1
- package/core/server/services/route-settings/yaml-parser.js +1 -1
- package/core/server/services/settings/settings-service.js +1 -8
- package/core/server/services/settings-helpers/settings-helpers.js +4 -6
- package/core/server/services/slack-ping/slack-ping-service.js +1 -1
- package/core/server/services/staff/staff-service-emails.js +2 -2
- package/core/server/services/stripe/config.js +14 -8
- package/core/server/services/stripe/service.js +10 -1
- package/core/server/services/stripe/services/checkout/completed-session.js +68 -0
- package/core/server/services/stripe/services/checkout/session-options.js +100 -0
- package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +71 -1
- package/core/server/services/stripe/stripe-api.js +31 -0
- package/core/server/services/stripe/stripe-service.js +26 -9
- package/core/server/services/stripe/webhook-controller.js +10 -0
- package/core/server/services/tier-checkout-config/codec.js +99 -0
- package/core/server/services/tier-checkout-config/index.js +26 -0
- package/core/server/services/tier-checkout-config/models.js +63 -0
- package/core/server/services/tier-checkout-config/queries.js +80 -0
- package/core/server/services/tier-checkout-config/schema.js +33 -0
- package/core/server/services/tier-checkout-config/serializers.js +128 -0
- package/core/server/services/tier-checkout-config/service.js +304 -0
- package/core/server/services/tiers/service.js +21 -0
- package/core/server/services/update-check/index.js +13 -13
- package/core/server/services/update-check/jobs/update-check-job.js +7 -0
- package/core/server/services/update-check/update-check-service.js +2 -2
- package/core/server/web/admin/controller.js +1 -1
- package/core/server/web/api/endpoints/admin/routes.js +34 -20
- package/core/server/web/api/middleware/upload.js +2 -2
- package/core/server/web/parent/app.js +5 -0
- package/core/server/web/parent/middleware/filter-query-parameters.js +94 -0
- package/core/server/web/parent/middleware/index.js +1 -0
- package/core/server/web/query-parameter-policy/index.js +9 -0
- package/core/server/web/query-parameter-policy/policy.json +58 -0
- package/core/server/web/query-parameter-policy/schema.js +38 -0
- package/core/shared/config/defaults.json +3 -0
- package/core/shared/config/env/config.testing-mysql.json +4 -1
- package/core/shared/config/env/config.testing.json +3 -0
- package/core/shared/config/overrides.json +4 -2
- package/core/shared/labs.js +9 -15
- package/package.json +25 -25
- package/pnpm-lock.yaml +741 -108
- package/pnpm-workspace.yaml +12 -9
- package/tsconfig.json +2 -3
- package/components/tryghost-kg-default-nodes-2.2.0.tgz +0 -0
- package/core/built/admin/assets/Combination-gnJO3vIw.js +0 -41
- package/core/built/admin/assets/account-migration-8qTEI1i1.js +0 -1
- package/core/built/admin/assets/activitypub/inbox-DQ5CwLma.js +0 -1284
- package/core/built/admin/assets/activitypub/label-B72sfvEU.js +0 -16
- package/core/built/admin/assets/activitypub/layout-CXajhRsL.js +0 -1
- package/core/built/admin/assets/activitypub/settings-71gCm3SO.js +0 -315
- package/core/built/admin/assets/add-integration-modal--YpTq3c0.js +0 -1
- package/core/built/admin/assets/add-newsletter-modal-C8Ix2hol.js +0 -1
- package/core/built/admin/assets/add-recommendation-modal-CScuNFz4.js +0 -1
- package/core/built/admin/assets/alert-dialog-wEbVxANM.js +0 -1
- package/core/built/admin/assets/analytics-Cnji45H5.js +0 -1
- package/core/built/admin/assets/app-utils-D2Wp7ZSS.js +0 -1
- package/core/built/admin/assets/audience-DTLMXAFH.js +0 -1
- package/core/built/admin/assets/automations-BWpRCZ_f.js +0 -1
- package/core/built/admin/assets/bluesky-sharing-DHOpQxff.js +0 -1
- package/core/built/admin/assets/chart-helpers-B3OqQ6Ez.js +0 -1
- package/core/built/admin/assets/color-picker-Bmo1r9Fc.js +0 -3
- package/core/built/admin/assets/color-picker-field-DywowPhF.js +0 -1
- package/core/built/admin/assets/combobox-C4QCh6Yw.js +0 -1
- package/core/built/admin/assets/command-DcLbSifC.js +0 -1
- package/core/built/admin/assets/comments-Bxy_3WyE.js +0 -1
- package/core/built/admin/assets/confirmation-context-CLFHhyyH.js +0 -1
- package/core/built/admin/assets/custom-field-type-option-D0PXoSnP.js +0 -10
- package/core/built/admin/assets/custom-integration-modal-DCIHKN33.js +0 -1
- package/core/built/admin/assets/design-and-theme-modal-CZ-PkW7o.js +0 -23
- package/core/built/admin/assets/dialog-Db7c164G.js +0 -1
- package/core/built/admin/assets/dirty-confirm-dialog-CmARpZQS.js +0 -1
- package/core/built/admin/assets/dist-QLZuGq7o2.js +0 -1
- package/core/built/admin/assets/domain-D0SAtc-T.js +0 -1
- package/core/built/admin/assets/dropzone-CaPOeDRZ.js +0 -5
- package/core/built/admin/assets/edit-profile-By7v5XW5.js +0 -1
- package/core/built/admin/assets/editor-COXn4Atu.js +0 -8
- package/core/built/admin/assets/explore-BsNYrubV.js +0 -1
- package/core/built/admin/assets/feed-8RR1O1bK.js +0 -1
- package/core/built/admin/assets/filter-sources-4C0kF1NU.js +0 -1
- package/core/built/admin/assets/filters-CV5jCb9n.js +0 -1
- package/core/built/admin/assets/filters-Dmr0PoqU.js +0 -1
- package/core/built/admin/assets/first-promoter-modal-OCzSaL3B.js +0 -1
- package/core/built/admin/assets/gift-link-modal-0mzjn5oF.js +0 -1
- package/core/built/admin/assets/growth-55vWlGT_.js +0 -1
- package/core/built/admin/assets/growth-DA5AluYw.js +0 -3
- package/core/built/admin/assets/history-modal-G1s5hHz7.js +0 -1
- package/core/built/admin/assets/html-field-BegxsWpw.js +0 -1
- package/core/built/admin/assets/index-CDP9tIOk.js +0 -4
- package/core/built/admin/assets/index-Dqf2x-ry.css +0 -1
- package/core/built/admin/assets/index.es-BMUCj9TR.js +0 -1
- package/core/built/admin/assets/input-UHrKuvBT.js +0 -1
- package/core/built/admin/assets/invite-user-modal-BrAkxEO3.js +0 -1
- package/core/built/admin/assets/koenig-loader-DwVY_V-H.js +0 -1
- package/core/built/admin/assets/kpi-card-LERqWBoY.js +0 -1
- package/core/built/admin/assets/kpi-helpers-DB3ouhYF.js +0 -1
- package/core/built/admin/assets/kpi-tabs-BGvtMD8S.js +0 -1
- package/core/built/admin/assets/label-picker-BBWykUeA.js +0 -1
- package/core/built/admin/assets/layout-Di5K7doM.js +0 -1
- package/core/built/admin/assets/loading-indicator-SFTqyYVE.js +0 -1
- package/core/built/admin/assets/member-custom-fields-gOv6Hoh3.js +0 -1
- package/core/built/admin/assets/member-detail-D7mZDDp3.js +0 -1
- package/core/built/admin/assets/member-route-0Aa52Ntz.js +0 -1
- package/core/built/admin/assets/members-DvZG9t69.js +0 -2
- package/core/built/admin/assets/moderation-DzzPLDyl.js +0 -1
- package/core/built/admin/assets/navigation-modal-CI8Bx5vn.js +0 -1
- package/core/built/admin/assets/newsletter-Bg3cq5U-.js +0 -1
- package/core/built/admin/assets/newsletter-detail-modal-DQU3r4g2.js +0 -1
- package/core/built/admin/assets/newsletters-DMDfZbMS.js +0 -1
- package/core/built/admin/assets/note-i8k6pjaw.js +0 -1
- package/core/built/admin/assets/notifications-psA_2Ph4.js +0 -1
- package/core/built/admin/assets/offers-container-modal-BWfjSdyg.js +0 -1
- package/core/built/admin/assets/onboarding-DGQuV7Ew.js +0 -1
- package/core/built/admin/assets/overview-C-6xOLaf.js +0 -1
- package/core/built/admin/assets/overview-DaLhpRrC.js +0 -1
- package/core/built/admin/assets/pages-B6vr9F_D.js +0 -1
- package/core/built/admin/assets/popover-CFgqFDvC.js +0 -1
- package/core/built/admin/assets/portal-frame-DjBF-uy2.js +0 -1
- package/core/built/admin/assets/portal-modal-Cf0pBAk8.js +0 -1
- package/core/built/admin/assets/post-analytics-Buz1COoY.js +0 -1
- package/core/built/admin/assets/post-analytics-header-B-P0eFnT.js +0 -1
- package/core/built/admin/assets/posts-KY3aUhVV.js +0 -1
- package/core/built/admin/assets/preferences-Cq1aOiLy.js +0 -5
- package/core/built/admin/assets/profile-D6dHYf2o.js +0 -1
- package/core/built/admin/assets/select-D_RQaXgt.js +0 -1
- package/core/built/admin/assets/settings-B5hJKZ5C.js +0 -1
- package/core/built/admin/assets/settings-DIo_F-MF.js +0 -4
- package/core/built/admin/assets/share-modal-CLBfj377.js +0 -1
- package/core/built/admin/assets/shim-ClF_m5ux.js +0 -1
- package/core/built/admin/assets/slack-modal-Bt9I_tEo.js +0 -1
- package/core/built/admin/assets/sort-button-DMBXF7tP.js +0 -1
- package/core/built/admin/assets/stats-B_YkF7bp.js +0 -1
- package/core/built/admin/assets/stats-view-BOCp9RG8.js +0 -1
- package/core/built/admin/assets/stripe-connect-modal-BeSukI8q.js +0 -1
- package/core/built/admin/assets/tag-detail-2a3BfzTW.js +0 -2
- package/core/built/admin/assets/tags-Dp2_8UzL.js +0 -1
- package/core/built/admin/assets/testimonials-modal-C9nrypNo.js +0 -1
- package/core/built/admin/assets/tier-detail-modal-CxRwgTPR.js +0 -1
- package/core/built/admin/assets/use-check-theme-limit-error-C9-QhGdV.js +0 -1
- package/core/built/admin/assets/use-filter-params-CSpAvgD2.js +0 -1
- package/core/built/admin/assets/use-growth-stats-BOmoPDHJ.js +0 -1
- package/core/built/admin/assets/use-post-referrers-CbXLFIex.js +0 -1
- package/core/built/admin/assets/use-reply-chain-data-JYFKSO83.js +0 -1
- package/core/built/admin/assets/use-settings-navigation-BBzro2-V.js +0 -1
- package/core/built/admin/assets/use-tinybird-query-D2Aeoty5.js +0 -39
- package/core/built/admin/assets/use-tinybird-token-DBuwMmln.js +0 -1
- package/core/built/admin/assets/user-detail-modal-59mBJqxN.js +0 -1
- package/core/built/admin/assets/users-5msE1WMs.js +0 -1
- package/core/built/admin/assets/web-DUuX7nE2.js +0 -2
- package/core/built/admin/assets/web-N8ucs2aG.js +0 -1
- package/core/server/api/endpoints/member-custom-fields.js +0 -100
- package/core/server/services/jobs/worker-model-event-bridge.js +0 -149
- package/core/server/services/members/jobs/clean-expired-comped.js +0 -132
- package/core/server/services/update-check/run-update-check.js +0 -63
- /package/core/server/lib/{db-date.js → db-types/date.js} +0 -0
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.collectedByPort = exports.CompletedSession = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const checkout_1 = require("@tryghost/checkout");
|
|
6
|
+
/**
|
|
7
|
+
* What Ghost reads off a completed session.
|
|
8
|
+
*
|
|
9
|
+
* `shipping` is where the address lives at Ghost's pinned Stripe API version; later
|
|
10
|
+
* versions moved it to `collected_information.shipping_details`. An Event is an immutable
|
|
11
|
+
* snapshot rendered at the account's version, so nothing in a payload would warn us if
|
|
12
|
+
* that pin ever changed.
|
|
13
|
+
*
|
|
14
|
+
* A tax number is deliberately not read: Stripe keeps it against the customer it invoices
|
|
15
|
+
* and Ghost never stores one.
|
|
16
|
+
*/
|
|
17
|
+
const Collected = zod_1.z
|
|
18
|
+
.string()
|
|
19
|
+
.nullish()
|
|
20
|
+
.transform((given) => (given && given.trim() !== '' ? given : undefined));
|
|
21
|
+
exports.CompletedSession = zod_1.z
|
|
22
|
+
.object({
|
|
23
|
+
custom_fields: zod_1.z
|
|
24
|
+
.array(zod_1.z
|
|
25
|
+
.object({
|
|
26
|
+
key: zod_1.z.string().nullish(),
|
|
27
|
+
text: zod_1.z.object({ value: Collected }).nullish(),
|
|
28
|
+
})
|
|
29
|
+
.nullable())
|
|
30
|
+
.nullish(),
|
|
31
|
+
shipping: zod_1.z
|
|
32
|
+
.object({
|
|
33
|
+
name: Collected,
|
|
34
|
+
address: zod_1.z.record(zod_1.z.string(), Collected).nullish(),
|
|
35
|
+
})
|
|
36
|
+
.nullish(),
|
|
37
|
+
customer_details: zod_1.z.object({ phone: Collected }).nullish(),
|
|
38
|
+
})
|
|
39
|
+
.loose();
|
|
40
|
+
/** Our address type needs at least one part; an empty value would erase what is there. */
|
|
41
|
+
function addressValue(address) {
|
|
42
|
+
const value = Object.fromEntries(Object.entries(address ?? {}).filter((entry) => entry[1] !== undefined));
|
|
43
|
+
return Object.keys(value).length > 0 ? value : undefined;
|
|
44
|
+
}
|
|
45
|
+
const PORT_VALUES = {
|
|
46
|
+
// Stripe returns the recipient's name alongside the address rather than as part of it,
|
|
47
|
+
// while Ghost keeps a name and an address in two separate custom fields. So the name is
|
|
48
|
+
// carried back separately here, under its own name, to be routed to its own field.
|
|
49
|
+
shipping_name: (session) => session.shipping?.name,
|
|
50
|
+
shipping_address: (session) => addressValue(session.shipping?.address),
|
|
51
|
+
phone: (session) => session.customer_details?.phone,
|
|
52
|
+
};
|
|
53
|
+
/**
|
|
54
|
+
* Everything a completed checkout gives back, as a list of pairs: the name Stripe used for
|
|
55
|
+
* a value, and the value itself. Answers to questions and values Stripe collected on its
|
|
56
|
+
* own both come back this way, because both are saved by the same route afterwards.
|
|
57
|
+
*
|
|
58
|
+
* Order matters. The values Stripe collected come last, so that if a question's answer and
|
|
59
|
+
* a collected value are both saved into the same custom field, the collected value is the
|
|
60
|
+
* one the field ends up holding.
|
|
61
|
+
*/
|
|
62
|
+
exports.collectedByPort = exports.CompletedSession.transform((session) => {
|
|
63
|
+
const answers = (session.custom_fields ?? [])
|
|
64
|
+
.filter((field) => Boolean(field?.key && field.text?.value))
|
|
65
|
+
.map((field) => ({ port: field.key, value: field.text.value }));
|
|
66
|
+
const ports = checkout_1.STRIPE_PORTS.map((port) => ({ port, value: PORT_VALUES[port](session) })).filter((entry) => entry.value !== undefined);
|
|
67
|
+
return [...answers, ...ports];
|
|
68
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
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.stripeCheckoutCollectionOptions = stripeCheckoutCollectionOptions;
|
|
7
|
+
const logging_1 = __importDefault(require("@tryghost/logging"));
|
|
8
|
+
const checkout_1 = require("@tryghost/checkout");
|
|
9
|
+
/** Keyed on the eligible types, so this and the configure-time rule cannot drift apart. */
|
|
10
|
+
const QUESTION_TYPES = {
|
|
11
|
+
short_text: 'text',
|
|
12
|
+
};
|
|
13
|
+
function askable(question) {
|
|
14
|
+
if (!(0, checkout_1.isCheckoutEligible)(question.type)) {
|
|
15
|
+
logging_1.default.warn({
|
|
16
|
+
event: { name: 'stripe.checkout.question_skipped' },
|
|
17
|
+
customFieldKey: question.key,
|
|
18
|
+
fieldType: question.type,
|
|
19
|
+
reason: 'unsupported_type',
|
|
20
|
+
}, 'Skipping a Stripe checkout question');
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
if (question.prompt.length > checkout_1.MAX_CHECKOUT_LABEL_LENGTH) {
|
|
24
|
+
logging_1.default.warn({
|
|
25
|
+
event: { name: 'stripe.checkout.question_skipped' },
|
|
26
|
+
customFieldKey: question.key,
|
|
27
|
+
promptLength: question.prompt.length,
|
|
28
|
+
reason: 'label_too_long',
|
|
29
|
+
}, 'Skipping a Stripe checkout question');
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Build the collection parameters for a checkout, or nothing at all.
|
|
36
|
+
*
|
|
37
|
+
* Returns an object with no keys when a tier asks for nothing, so a caller can spread it
|
|
38
|
+
* over its session options unconditionally and change nothing.
|
|
39
|
+
*/
|
|
40
|
+
function stripeCheckoutCollectionOptions(checkout) {
|
|
41
|
+
const options = {};
|
|
42
|
+
if (!checkout) {
|
|
43
|
+
return options;
|
|
44
|
+
}
|
|
45
|
+
const eligible = checkout.customFields.filter(askable);
|
|
46
|
+
const questions = eligible.slice(0, checkout_1.MAX_CHECKOUT_CUSTOM_FIELDS);
|
|
47
|
+
if (questions.length < eligible.length) {
|
|
48
|
+
logging_1.default.warn({
|
|
49
|
+
event: { name: 'stripe.checkout.questions_trimmed' },
|
|
50
|
+
asked: questions.length,
|
|
51
|
+
configured: eligible.length,
|
|
52
|
+
}, 'Some Stripe checkout questions were not asked');
|
|
53
|
+
}
|
|
54
|
+
if (questions.length > 0) {
|
|
55
|
+
options.custom_fields = questions.map((question) => ({
|
|
56
|
+
// Ghost sends the custom field's own key as the question's identifier, and Stripe
|
|
57
|
+
// returns the buyer's answer labelled with that same key. Using it in both places
|
|
58
|
+
// means reading an answer is a direct lookup of the field it belongs to, with
|
|
59
|
+
// nothing in between that could map it to the wrong one.
|
|
60
|
+
key: question.key,
|
|
61
|
+
label: { type: 'custom', custom: question.prompt },
|
|
62
|
+
type: QUESTION_TYPES[question.type],
|
|
63
|
+
optional: question.optional,
|
|
64
|
+
}));
|
|
65
|
+
}
|
|
66
|
+
if (checkout.shipping) {
|
|
67
|
+
// Ghost stores "everywhere" as no list, because the set of countries moves and a
|
|
68
|
+
// stored copy of it would quietly become a restriction. Stripe has no such sentinel:
|
|
69
|
+
// `allowed_countries` is the only key `shipping_address_collection` has, so a request
|
|
70
|
+
// that omits it carries no parameter at all, and Stripe accepts it precisely because
|
|
71
|
+
// it was never asked to collect anything — a session that succeeds and collects no
|
|
72
|
+
// address. So everywhere is expanded to every country here, at the one point that
|
|
73
|
+
// builds the request.
|
|
74
|
+
//
|
|
75
|
+
// An empty list is neither everywhere nor a real restriction, and would encode to the
|
|
76
|
+
// same absent parameter. Defended here rather than trusted from the settings screen:
|
|
77
|
+
// this is the checkout path, and a malformed configuration must cost the collection
|
|
78
|
+
// rather than throw inside a session build.
|
|
79
|
+
const allowedCountries = checkout.shipping.allowedCountries ?? [...checkout_1.STRIPE_ALLOWED_COUNTRIES];
|
|
80
|
+
if (allowedCountries.length === 0) {
|
|
81
|
+
logging_1.default.warn({
|
|
82
|
+
event: { name: 'stripe.checkout.collection_skipped' },
|
|
83
|
+
port: 'shipping_address',
|
|
84
|
+
reason: 'no_allowed_countries',
|
|
85
|
+
}, 'Skipping a Stripe checkout collection');
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
options.shipping_address_collection = { allowed_countries: allowedCountries };
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Unioned with whatever automatic tax asks for. Both want the same thing, so a site
|
|
92
|
+
// running the 2024 tax beta keeps collecting and a site that asked for it starts.
|
|
93
|
+
if (checkout.taxNumber) {
|
|
94
|
+
options.tax_id_collection = { enabled: true };
|
|
95
|
+
}
|
|
96
|
+
if (checkout.phone) {
|
|
97
|
+
options.phone_number_collection = { enabled: true };
|
|
98
|
+
}
|
|
99
|
+
return options;
|
|
100
|
+
}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
2
|
const errors = require('@tryghost/errors');
|
|
3
3
|
const logging = require('@tryghost/logging');
|
|
4
|
-
const {
|
|
4
|
+
const {
|
|
5
|
+
SIGNUP_CONTEXTS,
|
|
6
|
+
canWelcomeEmailReplaceSignupPaidEmail,
|
|
7
|
+
} = require('../../../lib/member-signup-contexts');
|
|
8
|
+
const { collectedByPort } = require('../checkout/completed-session');
|
|
5
9
|
/** @typedef {import('../../../lib/member-signup-contexts').SignupContext} SignupContext */
|
|
6
10
|
|
|
7
11
|
function isStripeMetadataTrue(value) {
|
|
@@ -334,6 +338,7 @@ module.exports = class CheckoutSessionEventService {
|
|
|
334
338
|
email: customer.email,
|
|
335
339
|
});
|
|
336
340
|
|
|
341
|
+
const memberPreexisted = Boolean(member);
|
|
337
342
|
const checkoutType = _.get(session, 'metadata.checkoutType');
|
|
338
343
|
|
|
339
344
|
if (!member) {
|
|
@@ -422,6 +427,11 @@ module.exports = class CheckoutSessionEventService {
|
|
|
422
427
|
}
|
|
423
428
|
}
|
|
424
429
|
|
|
430
|
+
// After the subscription work, and deliberately not part of it: a value the member
|
|
431
|
+
// gave us for free must never be able to fail the webhook. A throw here would make
|
|
432
|
+
// Stripe retry the event and risk doing the payment work twice.
|
|
433
|
+
await this.writeCollectedFields(member.id, session, { memberPreexisted });
|
|
434
|
+
|
|
425
435
|
if (checkoutType !== 'upgrade') {
|
|
426
436
|
const ghostSignupContext = /** @type {SignupContext | undefined} */ (
|
|
427
437
|
session.metadata?.ghostSignupContext
|
|
@@ -440,4 +450,64 @@ module.exports = class CheckoutSessionEventService {
|
|
|
440
450
|
}
|
|
441
451
|
}
|
|
442
452
|
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* This service knows how to read a completed Stripe session. It does not know, and must
|
|
456
|
+
* not know, which custom field any of those values belongs in — that is what a binding
|
|
457
|
+
* decides, so no field key appears anywhere in this code.
|
|
458
|
+
*
|
|
459
|
+
* Nothing here may be fatal: a throw fails the webhook, which makes Stripe retry it and
|
|
460
|
+
* risks doing the payment work twice.
|
|
461
|
+
*
|
|
462
|
+
* @param {string} memberId
|
|
463
|
+
* @param {import('stripe').Stripe.Checkout.Session} session
|
|
464
|
+
* @param {object} options
|
|
465
|
+
* @param {boolean} options.memberPreexisted Whether the member's record existed before this event resolved it
|
|
466
|
+
*/
|
|
467
|
+
async writeCollectedFields(memberId, session, { memberPreexisted }) {
|
|
468
|
+
// Stamped at create time. A session predating this feature carries none. Read
|
|
469
|
+
// outside the try so a failure below can name the tier whose answers were lost.
|
|
470
|
+
const tierId = session.metadata?.ghostTierId;
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
if (!tierId) {
|
|
474
|
+
return;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// A checkout can be started with nothing but an email address, and typing an
|
|
478
|
+
// email is not proof of owning it. Values may land on the member this event just
|
|
479
|
+
// created — that record holds nothing the buyer didn't supply — but a record
|
|
480
|
+
// that existed before the checkout belongs to whoever verified that email, so it
|
|
481
|
+
// is only written when the session was created by a signed-in member.
|
|
482
|
+
const wasAuthenticated =
|
|
483
|
+
session.metadata?.ghostSignupContext === SIGNUP_CONTEXTS.ALREADY_AUTHENTICATED;
|
|
484
|
+
if (memberPreexisted && !wasAuthenticated) {
|
|
485
|
+
logging.warn(
|
|
486
|
+
{
|
|
487
|
+
event: { name: 'stripe_checkout.collected_fields.write_skipped' },
|
|
488
|
+
memberId,
|
|
489
|
+
tierId,
|
|
490
|
+
},
|
|
491
|
+
'Skipped storing the fields an unverified checkout collected for an existing member',
|
|
492
|
+
);
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
await this.deps.customFieldBindings.writeCollected(
|
|
497
|
+
memberId,
|
|
498
|
+
tierId,
|
|
499
|
+
collectedByPort.parse(session),
|
|
500
|
+
);
|
|
501
|
+
} catch (err) {
|
|
502
|
+
logging.error(
|
|
503
|
+
{
|
|
504
|
+
event: { name: 'stripe_checkout.collected_fields.write_failed' },
|
|
505
|
+
err,
|
|
506
|
+
memberId,
|
|
507
|
+
tierId,
|
|
508
|
+
},
|
|
509
|
+
'Failed to store the fields a checkout collected',
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
443
513
|
};
|
|
@@ -3,6 +3,7 @@ const debug = require('@tryghost/debug')('stripe');
|
|
|
3
3
|
const ghostConfig = require('../../../shared/config');
|
|
4
4
|
const stripe = require('stripe');
|
|
5
5
|
const i18n = require('../i18n');
|
|
6
|
+
const { stripeCheckoutCollectionOptions } = require('./services/checkout/session-options');
|
|
6
7
|
|
|
7
8
|
/* Stripe has the following rate limits:
|
|
8
9
|
* - For most APIs, 100 read requests per second in live mode, 25 read requests per second in test mode
|
|
@@ -61,6 +62,8 @@ const MANAGED_PAYMENTS_DISABLED = { enabled: false };
|
|
|
61
62
|
*/
|
|
62
63
|
|
|
63
64
|
module.exports = class StripeAPI {
|
|
65
|
+
static API_VERSION = STRIPE_API_VERSION;
|
|
66
|
+
|
|
64
67
|
/**
|
|
65
68
|
* StripeAPI
|
|
66
69
|
* @param {object} deps
|
|
@@ -556,6 +559,8 @@ module.exports = class StripeAPI {
|
|
|
556
559
|
* @param {string} options.customerEmail
|
|
557
560
|
* @param {number} options.trialDays
|
|
558
561
|
* @param {string} [options.coupon]
|
|
562
|
+
* @param {import('../tier-checkout-config').ResolvedCheckout} [options.checkout] What
|
|
563
|
+
* the tier's checkout asks for beyond the payment. Absent, or empty, adds nothing.
|
|
559
564
|
*
|
|
560
565
|
* @returns {Promise<ICheckoutSession>}
|
|
561
566
|
*/
|
|
@@ -636,6 +641,32 @@ module.exports = class StripeAPI {
|
|
|
636
641
|
hasCustomer: Boolean(customerId),
|
|
637
642
|
});
|
|
638
643
|
|
|
644
|
+
// Applied after automatic tax so the two agree about `tax_id_collection`: both ask
|
|
645
|
+
// for the same thing, and a site with either reason to collect gets it. Nothing is
|
|
646
|
+
// added for a tier that has configured nothing, so an unconfigured site's request
|
|
647
|
+
// is unchanged.
|
|
648
|
+
Object.assign(stripeSessionOptions, stripeCheckoutCollectionOptions(options.checkout));
|
|
649
|
+
|
|
650
|
+
// Stripe refuses to collect a tax id for a customer it may not rename:
|
|
651
|
+
//
|
|
652
|
+
// Tax ID collection requires updating business name on the customer. To enable tax
|
|
653
|
+
// ID collection for an existing customer, please set `customer_update[name]`.
|
|
654
|
+
//
|
|
655
|
+
// Measured against the live API rather than read — `pnpm --filter @tryghost/e2e
|
|
656
|
+
// stripe:probe` — because the same probe shows shipping and phone collection need no
|
|
657
|
+
// such thing. Every signed-in checkout carries a customer, so without this a tier
|
|
658
|
+
// that collects a tax number cannot be bought by an existing member.
|
|
659
|
+
//
|
|
660
|
+
// Merged rather than assigned: automatic tax sets this too, and whichever ran first
|
|
661
|
+
// must not lose its keys. Still never set without a customer, which is the shape
|
|
662
|
+
// that took the tax beta down.
|
|
663
|
+
if (stripeSessionOptions.tax_id_collection && customerId) {
|
|
664
|
+
stripeSessionOptions.customer_update = {
|
|
665
|
+
...stripeSessionOptions.customer_update,
|
|
666
|
+
name: 'auto',
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
|
|
639
670
|
const session = await this._stripe.checkout.sessions.create(stripeSessionOptions);
|
|
640
671
|
|
|
641
672
|
return session;
|
|
@@ -10,6 +10,7 @@ const InvoiceEventService = require('./services/webhook/invoice-event-service');
|
|
|
10
10
|
const CheckoutSessionEventService = require('./services/webhook/checkout-session-event-service');
|
|
11
11
|
const ChargeRefundedEventService = require('./services/webhook/charge-refunded-event-service');
|
|
12
12
|
const memberWelcomeEmailService = require('../member-welcome-emails/service');
|
|
13
|
+
const customFields = require('../members-custom-fields');
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* @typedef {object} IStripeServiceConfig
|
|
@@ -24,6 +25,7 @@ const memberWelcomeEmailService = require('../member-welcome-emails/service');
|
|
|
24
25
|
* @prop {boolean} testEnv Whether this is a test environment
|
|
25
26
|
* @prop {string} webhookSecret The Stripe webhook secret
|
|
26
27
|
* @prop {string} webhookHandlerUrl The URL to handle Stripe webhooks
|
|
28
|
+
* @prop {boolean} [ephemeralWebhook] Whether the webhook endpoint is deleted on shutdown
|
|
27
29
|
* @prop {string[]} webhookCustomerIgnoreList List of customer IDs for customer.subscription.updated webhook bypass
|
|
28
30
|
* @prop {string} siteUrl The site URL for billing portal return URL
|
|
29
31
|
*/
|
|
@@ -133,6 +135,11 @@ module.exports = class StripeService {
|
|
|
133
135
|
memberWelcomeEmailService.init();
|
|
134
136
|
return memberWelcomeEmailService.api.isMemberWelcomeEmailActive('paid');
|
|
135
137
|
},
|
|
138
|
+
// A getter because the custom field services are built during boot: reading the
|
|
139
|
+
// binding at construction would capture the empty value it had beforehand.
|
|
140
|
+
get customFieldBindings() {
|
|
141
|
+
return customFields.bindings;
|
|
142
|
+
},
|
|
136
143
|
});
|
|
137
144
|
|
|
138
145
|
const chargeRefundedEventService = new ChargeRefundedEventService({
|
|
@@ -155,6 +162,8 @@ module.exports = class StripeService {
|
|
|
155
162
|
this.migrations = migrations;
|
|
156
163
|
this.webhookController = webhookController;
|
|
157
164
|
this.billingPortalManager = billingPortalManager;
|
|
165
|
+
/** @private */
|
|
166
|
+
this.ephemeralWebhook = false;
|
|
158
167
|
}
|
|
159
168
|
|
|
160
169
|
async connect() {
|
|
@@ -206,23 +215,31 @@ module.exports = class StripeService {
|
|
|
206
215
|
webhookSecret: config.webhookSecret,
|
|
207
216
|
webhookHandlerUrl: config.webhookHandlerUrl,
|
|
208
217
|
});
|
|
218
|
+
this.ephemeralWebhook = config.ephemeralWebhook === true;
|
|
209
219
|
|
|
210
220
|
this.billingPortalManager.configure({
|
|
211
221
|
siteUrl: config.siteUrl,
|
|
212
222
|
});
|
|
213
223
|
|
|
214
|
-
// webhookManager.start()
|
|
215
|
-
//
|
|
216
|
-
//
|
|
217
|
-
//
|
|
218
|
-
//
|
|
219
|
-
//
|
|
220
|
-
// configurations, so this network-registration call only error-logs on
|
|
221
|
-
// every boot. Tests never need a registered portal configuration. Skip it
|
|
222
|
-
// under test; prod and dev register exactly as before.
|
|
224
|
+
// webhookManager.start() registers a webhook URL with Stripe only when no webhook
|
|
225
|
+
// secret was supplied. Outside production config.js supplies a placeholder secret
|
|
226
|
+
// unless stripeRemoteWebhooks is set, so start() normally returns without touching
|
|
227
|
+
// Stripe. billingPortalManager has no such guard: in the test environment the mock
|
|
228
|
+
// Stripe answers its registration call with a 500 on every boot, and tests never
|
|
229
|
+
// need a registered portal, so skip it under test only.
|
|
223
230
|
await this.webhookManager.start();
|
|
224
231
|
if (!config.testEnv) {
|
|
225
232
|
await this.billingPortalManager.start();
|
|
226
233
|
}
|
|
227
234
|
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Removes the webhook registration from Stripe, but only when it was marked ephemeral.
|
|
238
|
+
* A production registration is kept and reused on the next boot.
|
|
239
|
+
*/
|
|
240
|
+
async shutdown() {
|
|
241
|
+
if (this.ephemeralWebhook) {
|
|
242
|
+
await this.webhookManager.stop();
|
|
243
|
+
}
|
|
244
|
+
}
|
|
228
245
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const logging = require('@tryghost/logging');
|
|
2
|
+
const StripeAPI = require('./stripe-api');
|
|
2
3
|
|
|
3
4
|
module.exports = class WebhookController {
|
|
4
5
|
/**
|
|
@@ -89,6 +90,15 @@ module.exports = class WebhookController {
|
|
|
89
90
|
return res.end();
|
|
90
91
|
}
|
|
91
92
|
|
|
93
|
+
// Stripe shapes each event for the API version of the URL it was sent to. A different
|
|
94
|
+
// version means this event did not come through the URL Ghost registered, and fields
|
|
95
|
+
// may not be where Ghost reads them.
|
|
96
|
+
if (event.api_version !== StripeAPI.API_VERSION) {
|
|
97
|
+
logging.error(
|
|
98
|
+
`Webhook ${event.type} was rendered at Stripe API version ${event.api_version}, Ghost expects ${StripeAPI.API_VERSION}`,
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
92
102
|
const customerId = this.getEventCustomerId(event);
|
|
93
103
|
if (this.shouldIgnoreEvent(event, customerId)) {
|
|
94
104
|
logging.info(
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.questionRowCodec = exports.QuestionRow = exports.collectionRowCodec = exports.CollectionRow = exports.optionsCodec = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const custom_field_types_1 = require("@tryghost/custom-field-types");
|
|
6
|
+
const boolean_1 = require("../../lib/db-types/boolean");
|
|
7
|
+
const schema_1 = require("./schema");
|
|
8
|
+
const models_1 = require("./models");
|
|
9
|
+
const SEPARATOR = ',';
|
|
10
|
+
exports.optionsCodec = zod_1.z.codec(schema_1.DbCheckoutOptions, models_1.CheckoutOptions, {
|
|
11
|
+
// An absent list is everywhere, so the column is null rather than a copy of every
|
|
12
|
+
// country. An empty list is neither, and stays representable on purpose: nothing writes
|
|
13
|
+
// one, and the session builder refuses to collect against it rather than asking the
|
|
14
|
+
// processor for an address form it would never render.
|
|
15
|
+
decode: (columns) => ({
|
|
16
|
+
shippingAllowedCountries: columns.shipping_allowed_countries === null
|
|
17
|
+
? null
|
|
18
|
+
: splitList(columns.shipping_allowed_countries),
|
|
19
|
+
taxNumber: columns.tax_number_collect,
|
|
20
|
+
}),
|
|
21
|
+
encode: (options) => ({
|
|
22
|
+
shipping_allowed_countries: options.shippingAllowedCountries === null ? null : joinList(options.shippingAllowedCountries),
|
|
23
|
+
tax_number_collect: options.taxNumber,
|
|
24
|
+
}),
|
|
25
|
+
});
|
|
26
|
+
function splitList(stored) {
|
|
27
|
+
return stored.split(SEPARATOR).filter(Boolean);
|
|
28
|
+
}
|
|
29
|
+
function joinList(values) {
|
|
30
|
+
return [...new Set(values)].join(SEPARATOR);
|
|
31
|
+
}
|
|
32
|
+
exports.CollectionRow = zod_1.z.object({
|
|
33
|
+
product_id: zod_1.z.string(),
|
|
34
|
+
config_id: zod_1.z.string().nullable(),
|
|
35
|
+
shipping_allowed_countries: zod_1.z.string().nullable(),
|
|
36
|
+
tax_number_collect: boolean_1.DbBoolean.nullable(),
|
|
37
|
+
shipping_name_key: zod_1.z.string().nullable(),
|
|
38
|
+
shipping_name_collectable: zod_1.z.string().nullable(),
|
|
39
|
+
shipping_address_key: zod_1.z.string().nullable(),
|
|
40
|
+
shipping_address_collectable: zod_1.z.string().nullable(),
|
|
41
|
+
phone_key: zod_1.z.string().nullable(),
|
|
42
|
+
phone_collectable: zod_1.z.string().nullable(),
|
|
43
|
+
});
|
|
44
|
+
exports.collectionRowCodec = exports.CollectionRow.transform((row) => {
|
|
45
|
+
const collection = collectedInto(row);
|
|
46
|
+
return {
|
|
47
|
+
tierId: row.product_id,
|
|
48
|
+
configured: row.config_id !== null,
|
|
49
|
+
collection,
|
|
50
|
+
collecting: {
|
|
51
|
+
// Stripe collects the recipient's name and their address under one parameter, so it
|
|
52
|
+
// asks for both or neither. Archiving one of the two destinations is the publisher
|
|
53
|
+
// saying they no longer want that half: the step is still worth asking for while the
|
|
54
|
+
// other half can land, and whatever arrives for the archived one is dropped. Only
|
|
55
|
+
// archiving both leaves nothing worth asking for.
|
|
56
|
+
shipping: row.shipping_name_collectable === null && row.shipping_address_collectable === null
|
|
57
|
+
? null
|
|
58
|
+
: collection.shipping,
|
|
59
|
+
taxNumber: collection.taxNumber,
|
|
60
|
+
phone: row.phone_collectable === null ? null : collection.phone,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
exports.QuestionRow = zod_1.z.object({
|
|
65
|
+
product_id: zod_1.z.string(),
|
|
66
|
+
port: zod_1.z.string(),
|
|
67
|
+
label: schema_1.DbCheckoutQuestion.shape.label,
|
|
68
|
+
optional: schema_1.DbCheckoutQuestion.shape.optional,
|
|
69
|
+
question_name: zod_1.z.string().nullable(),
|
|
70
|
+
question_type: custom_field_types_1.FieldTypeSchema.nullable(),
|
|
71
|
+
});
|
|
72
|
+
exports.questionRowCodec = exports.QuestionRow.transform((row) => ({
|
|
73
|
+
tierId: row.product_id,
|
|
74
|
+
question: {
|
|
75
|
+
key: row.port,
|
|
76
|
+
label: row.label,
|
|
77
|
+
optional: row.optional,
|
|
78
|
+
},
|
|
79
|
+
askable: row.question_type === null
|
|
80
|
+
? null
|
|
81
|
+
: { prompt: row.label ?? row.question_name ?? row.port, type: row.question_type },
|
|
82
|
+
}));
|
|
83
|
+
function collectedInto(row) {
|
|
84
|
+
const { shippingAllowedCountries, taxNumber } = zod_1.z.decode(exports.optionsCodec, {
|
|
85
|
+
shipping_allowed_countries: row.shipping_allowed_countries,
|
|
86
|
+
tax_number_collect: row.tax_number_collect ?? false,
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
shipping: row.shipping_name_key !== null && row.shipping_address_key !== null
|
|
90
|
+
? {
|
|
91
|
+
allowedCountries: shippingAllowedCountries,
|
|
92
|
+
nameCustomFieldKey: row.shipping_name_key,
|
|
93
|
+
addressCustomFieldKey: row.shipping_address_key,
|
|
94
|
+
}
|
|
95
|
+
: null,
|
|
96
|
+
taxNumber,
|
|
97
|
+
phone: row.phone_key === null ? null : { customFieldKey: row.phone_key },
|
|
98
|
+
};
|
|
99
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toCheckoutConfigResponse = exports.TierCheckoutConfig = exports.ShippingCollection = exports.ResolvedQuestion = exports.ResolvedCheckout = exports.PhoneCollection = exports.emptyCheckoutConfig = exports.CheckoutQuestion = exports.TierCheckoutConfigService = void 0;
|
|
4
|
+
/**
|
|
5
|
+
* What a tier's checkout asks and collects.
|
|
6
|
+
*
|
|
7
|
+
* A domain of its own rather than part of the Tier aggregate: a tier is loaded into memory
|
|
8
|
+
* once at boot, and these rows are read live because deleting a custom field cascades a
|
|
9
|
+
* question away without that repository ever seeing it.
|
|
10
|
+
*
|
|
11
|
+
* Constructed by the tiers service wrapper at boot, which already holds the collaborators
|
|
12
|
+
* this needs, rather than by an init() here — the custom field services are built before
|
|
13
|
+
* it, so both are ready by the time it runs.
|
|
14
|
+
*/
|
|
15
|
+
var service_1 = require("./service");
|
|
16
|
+
Object.defineProperty(exports, "TierCheckoutConfigService", { enumerable: true, get: function () { return service_1.TierCheckoutConfigService; } });
|
|
17
|
+
var models_1 = require("./models");
|
|
18
|
+
Object.defineProperty(exports, "CheckoutQuestion", { enumerable: true, get: function () { return models_1.CheckoutQuestion; } });
|
|
19
|
+
Object.defineProperty(exports, "emptyCheckoutConfig", { enumerable: true, get: function () { return models_1.emptyCheckoutConfig; } });
|
|
20
|
+
Object.defineProperty(exports, "PhoneCollection", { enumerable: true, get: function () { return models_1.PhoneCollection; } });
|
|
21
|
+
Object.defineProperty(exports, "ResolvedCheckout", { enumerable: true, get: function () { return models_1.ResolvedCheckout; } });
|
|
22
|
+
Object.defineProperty(exports, "ResolvedQuestion", { enumerable: true, get: function () { return models_1.ResolvedQuestion; } });
|
|
23
|
+
Object.defineProperty(exports, "ShippingCollection", { enumerable: true, get: function () { return models_1.ShippingCollection; } });
|
|
24
|
+
Object.defineProperty(exports, "TierCheckoutConfig", { enumerable: true, get: function () { return models_1.TierCheckoutConfig; } });
|
|
25
|
+
var serializers_1 = require("./serializers");
|
|
26
|
+
Object.defineProperty(exports, "toCheckoutConfigResponse", { enumerable: true, get: function () { return serializers_1.toCheckoutConfigResponse; } });
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ResolvedCheckout = exports.ResolvedQuestion = exports.CheckoutOptions = exports.emptyCheckoutConfig = exports.emptyCollection = exports.TierCheckoutConfig = exports.PhoneCollection = exports.ShippingCollection = exports.CheckoutQuestion = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const custom_field_types_1 = require("@tryghost/custom-field-types");
|
|
6
|
+
exports.CheckoutQuestion = zod_1.z.object({
|
|
7
|
+
key: zod_1.z.string(),
|
|
8
|
+
label: zod_1.z.string().nullable(),
|
|
9
|
+
optional: zod_1.z.boolean(),
|
|
10
|
+
});
|
|
11
|
+
/**
|
|
12
|
+
* One toggle and two destinations: a processor returns the recipient and the address
|
|
13
|
+
* under one parameter, but a publisher keeps a name and an address in different fields.
|
|
14
|
+
*/
|
|
15
|
+
exports.ShippingCollection = zod_1.z.object({
|
|
16
|
+
/**
|
|
17
|
+
* ISO 3166-1 alpha-2, or null for everywhere the processor ships.
|
|
18
|
+
*
|
|
19
|
+
* Null rather than a stored enumeration of every country, because that list moves: the
|
|
20
|
+
* day the processor adds one, a saved "everywhere" would silently be a restriction that
|
|
21
|
+
* excludes it, and nothing would say so.
|
|
22
|
+
*/
|
|
23
|
+
allowedCountries: zod_1.z.array(zod_1.z.string()).nullable(),
|
|
24
|
+
nameCustomFieldKey: zod_1.z.string(),
|
|
25
|
+
addressCustomFieldKey: zod_1.z.string(),
|
|
26
|
+
});
|
|
27
|
+
exports.PhoneCollection = zod_1.z.object({
|
|
28
|
+
customFieldKey: zod_1.z.string(),
|
|
29
|
+
});
|
|
30
|
+
exports.TierCheckoutConfig = zod_1.z.object({
|
|
31
|
+
tierId: zod_1.z.string(),
|
|
32
|
+
customFields: zod_1.z.array(exports.CheckoutQuestion),
|
|
33
|
+
shipping: exports.ShippingCollection.nullable(),
|
|
34
|
+
/** Stripe keeps a tax number against the customer it invoices; Ghost never stores one. */
|
|
35
|
+
taxNumber: zod_1.z.boolean(),
|
|
36
|
+
phone: exports.PhoneCollection.nullable(),
|
|
37
|
+
});
|
|
38
|
+
const emptyCollection = () => ({
|
|
39
|
+
shipping: null,
|
|
40
|
+
taxNumber: false,
|
|
41
|
+
phone: null,
|
|
42
|
+
});
|
|
43
|
+
exports.emptyCollection = emptyCollection;
|
|
44
|
+
const emptyCheckoutConfig = (tierId) => ({
|
|
45
|
+
tierId,
|
|
46
|
+
customFields: [],
|
|
47
|
+
...(0, exports.emptyCollection)(),
|
|
48
|
+
});
|
|
49
|
+
exports.emptyCheckoutConfig = emptyCheckoutConfig;
|
|
50
|
+
exports.CheckoutOptions = zod_1.z.object({
|
|
51
|
+
shippingAllowedCountries: zod_1.z.array(zod_1.z.string()).nullable(),
|
|
52
|
+
taxNumber: zod_1.z.boolean(),
|
|
53
|
+
});
|
|
54
|
+
exports.ResolvedQuestion = exports.CheckoutQuestion.extend({
|
|
55
|
+
prompt: zod_1.z.string(),
|
|
56
|
+
type: custom_field_types_1.FieldTypeSchema,
|
|
57
|
+
});
|
|
58
|
+
exports.ResolvedCheckout = zod_1.z.object({
|
|
59
|
+
customFields: zod_1.z.array(exports.ResolvedQuestion),
|
|
60
|
+
shipping: exports.ShippingCollection.nullable(),
|
|
61
|
+
taxNumber: zod_1.z.boolean(),
|
|
62
|
+
phone: exports.PhoneCollection.nullable(),
|
|
63
|
+
});
|