ghost 6.61.0 → 6.63.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-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-limit-service-0.0.0.tgz +0 -0
- package/components/tryghost-metafield-types-0.0.0.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/content/themes/source/partials/post-card.hbs +1 -1
- package/core/boot.js +61 -27
- 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/{about-Bve7IJS2.js → about-C1C3s5-g.js} +1 -1
- package/core/built/admin/assets/accordion-CxGsDKwa.js +1 -0
- package/core/built/admin/assets/account-migration-CbUhW6gk.js +1 -0
- package/core/built/admin/assets/activitypub/{account-migration-C-aej4x_.js → account-migration-KA6Jjf9L.js} +16 -16
- package/core/built/admin/assets/activitypub/activitypub.js +9 -9
- package/core/built/admin/assets/activitypub/{avatar-DLPL3Abs.js → avatar-DVJF5bxP.js} +2 -2
- package/core/built/admin/assets/activitypub/{avatar-flipboard-C9iDY-QV.js → avatar-flipboard-csQaNeYK.js} +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-BXkA5mqV.js → bluesky-sharing-C7nTDMvw.js} +37 -37
- package/core/built/admin/assets/activitypub/{content-formatters-CHcyMFk4.js → content-formatters-CLcjaBwV.js} +4 -4
- package/core/built/admin/assets/activitypub/{copy-DIAOQoCZ.js → copy-DcOh8kXw.js} +2 -2
- package/core/built/admin/assets/activitypub/{domain-B3ihkPde.js → domain-fZ-X8i4-.js} +121 -121
- package/core/built/admin/assets/activitypub/{edit-profile-DsZM3rTX.js → edit-profile-BqMLQV88.js} +40 -40
- package/core/built/admin/assets/activitypub/{explore-BodNrZgR.js → explore-B4yPoP8E.js} +27 -27
- package/core/built/admin/assets/activitypub/{feed-PwH1iJ84.js → feed-BRC_mvAe.js} +51 -51
- package/core/built/admin/assets/activitypub/{field-D06-kmOJ.js → field-COacNgOt.js} +3 -3
- package/core/built/admin/assets/activitypub/{hash-Bm3WxYN4.js → hash-C7u-irQQ.js} +2 -2
- package/core/built/admin/assets/activitypub/inbox-BW-yvUoN.js +1284 -0
- package/core/built/admin/assets/activitypub/{inbox-d8oJRTf4.js → inbox-CbTTdDQ3.js} +2 -2
- package/core/built/admin/assets/activitypub/label-kPMmYI7l.js +16 -0
- package/core/built/admin/assets/activitypub/layout-BL4_jtuV.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-D1Yapsod.js → moderation-CuidJzc0.js} +91 -91
- package/core/built/admin/assets/activitypub/{note-C5zjqq5C.js → note-CAMRRSqD.js} +24 -24
- package/core/built/admin/assets/activitypub/{notifications-B9p8eCHD.js → notifications-BmNlXGgp.js} +51 -51
- package/core/built/admin/assets/activitypub/{onboarding-CNelD_uF.js → onboarding-Bqg7fh_L.js} +2 -2
- package/core/built/admin/assets/activitypub/{onboarding-mat5Hrl0.js → onboarding-Dmun9jr_.js} +2 -2
- package/core/built/admin/assets/activitypub/{preferences-BCqUdC3v.js → preferences--oH19wf9.js} +1519 -1519
- package/core/built/admin/assets/activitypub/{profile-BRCWoFem.js → profile-DH_tQx2D.js} +185 -185
- package/core/built/admin/assets/activitypub/{reply-DYK5vVqb.js → reply-Cap9PxSH.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-BSXT992i.js → routes-DdyY9uFF.js} +744 -735
- package/core/built/admin/assets/activitypub/{separator-BbQqTm-p.js → separator-CAxxu8VV.js} +3 -3
- package/core/built/admin/assets/activitypub/settings-B6EEcVkW.js +315 -0
- package/core/built/admin/assets/activitypub/{site-DHoMupSB.js → site-BYps63oh.js} +2 -2
- package/core/built/admin/assets/activitypub/{step-1-B-YfsVeO.js → step-1-CKjR2KAm.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-2-DtPQzlTH.js → step-2-B6PKfOUQ.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-3-D6BFMXL_.js → step-3-C-qBbcUo.js} +11 -11
- package/core/built/admin/assets/activitypub/{tabs-GpNecvfB.js → tabs-DLhZtVde.js} +306 -306
- package/core/built/admin/assets/activitypub/{topic-filter-BPDeQQys.js → topic-filter-DsUOuu0k.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-BoVOxXkV.js → use-activity-pub-queries-CX9snsLs.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-Bmk5h4lW.js → use-navigate-with-base-path-DsJTGDSS.js} +988 -959
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-wVBX7swM.js → use-reply-chain-data-AJi9zkzm.js} +30 -30
- package/core/built/admin/assets/activitypub/{users-CoX6FT_K.js → users-inweyKL5.js} +192 -164
- package/core/built/admin/assets/activitypub/{x-DbQZOLiI.js → x-DR8AT3-4.js} +2 -2
- package/core/built/admin/assets/add-integration-modal-BT_onDJy.js +1 -0
- package/core/built/admin/assets/add-newsletter-modal-Cx1kQpiY.js +1 -0
- package/core/built/admin/assets/add-recommendation-modal-B5nvAhvr.js +1 -0
- package/core/built/admin/assets/analytics-DjoJWPI7.js +1 -0
- package/core/built/admin/assets/{announcement-bar-modal-DM3cYOpo.js → announcement-bar-modal-Dwd0EGFP.js} +1 -1
- package/core/built/admin/assets/{api-keys-3-0vJC3Y.js → api-keys-BGPzLHzA.js} +1 -1
- package/core/built/admin/assets/{api-keys-BLm2NDBh.js → api-keys-BoeacM-s.js} +1 -1
- package/core/built/admin/assets/{audience-9mwU5TW6.js → audience-ceV3XVHq.js} +1 -1
- package/core/built/admin/assets/automated-emails-BLesE41L.js +1 -0
- package/core/built/admin/assets/{automations-sAcJ1334.js → automations-BN4u3GR8.js} +1 -1
- package/core/built/admin/assets/automations-C-ZTUwW3.js +1 -0
- package/core/built/admin/assets/bluesky-sharing-vzh-oPXL.js +1 -0
- package/core/built/admin/assets/{breadcrumb-DLiqCyFc.js → breadcrumb-CaLk2b7D.js} +1 -1
- package/core/built/admin/assets/{button-B2jcu6bk.js → button-Dvcv1QSR.js} +1 -1
- package/core/built/admin/assets/calendar-Bl9RLcZX.js +1 -0
- package/core/built/admin/assets/{chart-helpers-Fo3bUL3K.js → chart-helpers-FHywU0pj.js} +1 -1
- package/core/built/admin/assets/{checkbox-BfKktMph.js → checkbox-BtayFtjg.js} +1 -1
- package/core/built/admin/assets/chunk.254.035b85ec456ebbd7336b.js +440 -0
- package/core/built/admin/assets/{chunk.524.60ca4692d91c6022042f.js → chunk.524.e6975095fccdf22d90f8.js} +5 -5
- package/core/built/admin/assets/{chunk.582.525c7617c2d9e94c940a.js → chunk.582.98b485f9b2802f540897.js} +6 -6
- package/core/built/admin/assets/{chunk.492.f24d855d7e3f3aad261c.js → chunk.901.4ccc639821020230bcca.js} +9949 -7921
- package/core/built/admin/assets/{code-editor-view-DCQJxsfU.js → code-editor-view-C3ZfoF1X.js} +1 -1
- package/core/built/admin/assets/{code-editor-view-CYVu4EW6.js → code-editor-view-CAyRWy1s.js} +1 -1
- package/core/built/admin/assets/color-picker-G4kGR7rp.js +3 -0
- package/core/built/admin/assets/color-picker-field-DQFVTNnT.js +1 -0
- package/core/built/admin/assets/combobox-BnYzgEB4.js +1 -0
- package/core/built/admin/assets/command-C_vyTikI.js +1 -0
- package/core/built/admin/assets/comments-aBrxfnZf.js +1 -0
- package/core/built/admin/assets/{config-DOMQDZGZ.js → config-kSHvwdTG.js} +1 -1
- package/core/built/admin/assets/confirmation-context-DwXX_Y5h.js +1 -0
- package/core/built/admin/assets/{content-api-modal-CyQEC3fr.js → content-api-modal-D2G0nJP6.js} +1 -1
- package/core/built/admin/assets/context-menu-DOVB6yaz.js +1 -0
- package/core/built/admin/assets/{copy-field-B3M1s78L.js → copy-field-DCJwh9qf.js} +1 -1
- package/core/built/admin/assets/create-ghost-browse-value-source-5C53jJaX.js +1 -0
- package/core/built/admin/assets/custom-field-type-option-BAuW0F-E.js +1 -0
- package/core/built/admin/assets/custom-integration-modal-AbJGVyKc.js +1 -0
- package/core/built/admin/assets/{date-range-select-DD2RKkgB.js → date-range-select-BY3K-ukj.js} +1 -1
- package/core/built/admin/assets/design-and-theme-modal-BvEy4_pC.js +2 -0
- package/core/built/admin/assets/dirty-confirm-dialog-DVPuGz3U.js +1 -0
- package/core/built/admin/assets/{disabled-sources-indicator-Cvy47bE2.js → disabled-sources-indicator-Cy7Zp4c8.js} +1 -1
- package/core/built/admin/assets/dist-Dcsslyo72.js +1 -0
- package/core/built/admin/assets/domain-Dl61QGuk.js +1 -0
- package/core/built/admin/assets/dropzone-Dtz5Rmjt.js +5 -0
- package/core/built/admin/assets/edit-profile-DNJSMDwm.js +1 -0
- package/core/built/admin/assets/editor-DbAen9zf.js +8 -0
- package/core/built/admin/assets/editor-screen-BFWThIai.js +26 -0
- package/core/built/admin/assets/email-sending-status-context-CWDOD1FN.js +1 -0
- package/core/built/admin/assets/email-sending-status-provider-DCPkDICt.js +1 -0
- package/core/built/admin/assets/emails-vFMk4yYl.js +1 -0
- package/core/built/admin/assets/{embed-signup-form-modal-DeBMLxlL.js → embed-signup-form-modal-DGqsORxS.js} +1 -1
- package/core/built/admin/assets/en-B1jX3WMs.js +1 -0
- package/core/built/admin/assets/explore-nnRr1Fja.js +1 -0
- package/core/built/admin/assets/feature-flag-overrides-context-DQjdJFAk.js +1 -0
- package/core/built/admin/assets/feature-image-placeholder-BrIHmPs8.js +1 -0
- package/core/built/admin/assets/feed-BNUcNu-W.js +1 -0
- package/core/built/admin/assets/feedback-DJ4Zb9rZ.js +1 -0
- package/core/built/admin/assets/{field-Cj8lmDOo.js → field-C93_Mg6j.js} +1 -1
- package/core/built/admin/assets/filter-bar-Da1qxybu.js +1 -0
- package/core/built/admin/assets/filter-sources-CGJb4C5r.js +1 -0
- package/core/built/admin/assets/filters--kKP-EC8.js +1 -0
- package/core/built/admin/assets/filters-DxA_a4FP.js +1 -0
- package/core/built/admin/assets/first-promoter-modal-BYDtrz2S.js +1 -0
- package/core/built/admin/assets/focus-koenig-editor-on-bottom-click-BbSnGOtK.js +1 -0
- package/core/built/admin/assets/{ghost-c8807202a07c20030acb719ee0114815.css → ghost-8fd5bb799356bbefb254210ba7e1236d.css} +1 -1
- package/core/built/admin/assets/{ghost-3894eedcc6968f658578c0abfe942aab.js → ghost-b4e8a403baba61905cd7560e30b9732d.js} +63 -44
- package/core/built/admin/assets/{ghost-dark-fbf1e384aaef3ae315e540f0eb7373a7.css → ghost-dark-cac0cb2d5bb78e9bd14d8d1c2768ed45.css} +1 -1
- package/core/built/admin/assets/gift-link-modal-B7u5bD4e.js +1 -0
- package/core/built/admin/assets/{gift-links-DLDS7ljO.js → gift-links-CfLSnst2.js} +1 -1
- package/core/built/admin/assets/{growth-D6mhcx3q.js → growth-Cd1uO9d2.js} +1 -1
- package/core/built/admin/assets/growth-aG32xfdE.js +3 -0
- package/core/built/admin/assets/history-modal-Df8lW8h-.js +1 -0
- package/core/built/admin/assets/hooks-Cy_-oZ4o.js +2 -0
- package/core/built/admin/assets/{html-field-C8qVGGC1.js → html-field-DA4eU7eq.js} +1 -1
- package/core/built/admin/assets/{images-h8VZEQ9k.js → images-KXmyO3Ss.js} +1 -1
- package/core/built/admin/assets/{inbox-BYd1M06Q.js → inbox-gJHG7_xd.js} +14 -14
- package/core/built/admin/assets/index-BHHXtR5C.css +2 -0
- package/core/built/admin/assets/index-CgMCwsxN.js +4 -0
- package/core/built/admin/assets/index.es-CY6-yuw9.js +1 -0
- package/core/built/admin/assets/{input-group-D8sCXRF5.js → input-group-BE8rM2y1.js} +1 -1
- package/core/built/admin/assets/{integrations-Bjbu86Z0.js → integrations-B4kuRBFY.js} +1 -1
- package/core/built/admin/assets/invite-user-modal-B4CuNcx4.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-_-IsVI7E.js → koenig-lexical-CGXb0F8o.js} +3 -3
- package/core/built/admin/assets/koenig-loader-BeM5rFPX.js +2 -0
- package/core/built/admin/assets/{kpi-card-DZQk_0W8.js → kpi-card-B21aToe8.js} +1 -1
- package/core/built/admin/assets/{kpi-card-DW_2y3ug.js → kpi-card-pz6Wv4Ae.js} +1 -1
- package/core/built/admin/assets/{kpi-helpers-PueTKuA6.js → kpi-helpers-Dq5jelmP.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-CojDs-Qz.js → kpi-tabs-DEZHpKUt.js} +1 -1
- package/core/built/admin/assets/{label-DjbT91GM.js → label-OLCzze8N.js} +1 -1
- package/core/built/admin/assets/label-picker-BigOvMPV.js +1 -0
- package/core/built/admin/assets/{labels-CH3Kn8RV.js → labels-Dr0SgUQD.js} +1 -1
- package/core/built/admin/assets/layout-DUTL8SH5.js +1 -0
- package/core/built/admin/assets/lib-7QPTI0FS.js +1 -0
- package/core/built/admin/assets/link-helpers-BRAg9SCj.js +1 -0
- package/core/built/admin/assets/links-BDHPTlyj.js +1 -0
- package/core/built/admin/assets/member-custom-fields-TRTHCAz9.js +1 -0
- package/core/built/admin/assets/member-detail-b6dRwc3C.js +1 -0
- package/core/built/admin/assets/members-Czt5lJSc.js +4 -0
- package/core/built/admin/assets/moderation-ZWefD9uH.js +1 -0
- package/core/built/admin/assets/{multi-select-combobox-B6LiRzDO.js → multi-select-combobox-CT13MTo-.js} +1 -1
- package/core/built/admin/assets/navigation-modal-C8FEhBK5.js +1 -0
- package/core/built/admin/assets/newsletter-5VYW4RyJ.js +1 -0
- package/core/built/admin/assets/newsletter-detail-modal-ChjC0ZqH.js +1 -0
- package/core/built/admin/assets/{newsletter-emails-CFeLlt4z.js → newsletter-emails-DZH6I-PY.js} +1 -1
- package/core/built/admin/assets/newsletters-DOC9KBcX.js +1 -0
- package/core/built/admin/assets/newsletters-Rq1M3GD-.js +1 -0
- package/core/built/admin/assets/note-DIV-Pojr.js +1 -0
- package/core/built/admin/assets/notifications-s20-H49V.js +1 -0
- package/core/built/admin/assets/nql-BLpCn7qX.js +21 -0
- package/core/built/admin/assets/{offers-9J1CSiLy.js → offers--e2nGvkW.js} +1 -1
- package/core/built/admin/assets/offers-container-modal-BmG5M5Tv.js +1 -0
- package/core/built/admin/assets/onboarding-CUuy9unR.js +1 -0
- package/core/built/admin/assets/{onboarding-B4YhlZ0i.js → onboarding-pXyct0Jm.js} +1 -1
- package/core/built/admin/assets/{onboarding-route-Dmc4IPm3.js → onboarding-route-CK4pcXoR.js} +1 -1
- package/core/built/admin/assets/overview-CaPsciMs.js +1 -0
- package/core/built/admin/assets/overview-a8GfcsKX.js +1 -0
- package/core/built/admin/assets/{page-header-BR_nbIPp.js → page-header-DM_KyoFI.js} +1 -1
- package/core/built/admin/assets/pagemenu-vgZVUVKR.js +1 -0
- package/core/built/admin/assets/pages-CjDY2CBI.js +1 -0
- package/core/built/admin/assets/pages-route-BzKZYdV5.js +1 -0
- package/core/built/admin/assets/papaparse.min-cQwnxGCG.js +10 -0
- package/core/built/admin/assets/{pintura-modal-BR9hEvS8.js → pintura-modal-DExCF1ii.js} +1 -1
- package/core/built/admin/assets/portal-frame-Cqanidq4.js +1 -0
- package/core/built/admin/assets/portal-modal-oinBgA27.js +1 -0
- package/core/built/admin/assets/post-analytics-C5oJ1dWV.js +1 -0
- package/core/built/admin/assets/post-analytics-header-OATo3X_r.js +1 -0
- package/core/built/admin/assets/post-analytics-provider-tOpIVGT5.js +1 -0
- package/core/built/admin/assets/post-helpers-C88JBa4D.js +1 -0
- package/core/built/admin/assets/post-share-modal-DvhC1XgV.js +1 -0
- package/core/built/admin/assets/post-utils-DKF4mfuo.js +1 -0
- package/core/built/admin/assets/posts-list-screen-o4ZjbgS3.js +2 -0
- package/core/built/admin/assets/posts-pCPrhQeR.js +1 -0
- package/core/built/admin/assets/posts-route-9F--xdIb.js +1 -0
- package/core/built/admin/assets/preferences--vNubsqD.js +5 -0
- package/core/built/admin/assets/{preview-modal-C-BL-F9k.js → preview-modal-YYbzlYdO.js} +1 -1
- package/core/built/admin/assets/profile-BllKV3dR.js +1 -0
- package/core/built/admin/assets/{radio-group-Bgz60i81.js → radio-group-DL8GI8rt.js} +1 -1
- package/core/built/admin/assets/{referrers-CnhCMWCs.js → referrers-D8oHPmDB.js} +1 -1
- package/core/built/admin/assets/router-provider-BSvTXgZY.js +1 -0
- package/core/built/admin/assets/schemas-BoYdB6YU.js +64 -0
- package/core/built/admin/assets/select-v5YbA3Mw.js +1 -0
- package/core/built/admin/assets/settings-PEsZupw1.js +4 -0
- package/core/built/admin/assets/{settings-breadcrumbs-CyP_9zTo.js → settings-breadcrumbs-NwPnCTC7.js} +1 -1
- package/core/built/admin/assets/settings-u_s1CRkr.js +1 -0
- package/core/built/admin/assets/share-modal-SskCmVGL.js +1 -0
- package/core/built/admin/assets/slack-modal-XxSJiYgo.js +1 -0
- package/core/built/admin/assets/{sort-button-Cc-Z96sA.js → sort-button-Cx1KtINE.js} +1 -1
- package/core/built/admin/assets/{source-icon-v6PUnmyM.js → source-icon-Bdp9Im_5.js} +1 -1
- package/core/built/admin/assets/stats-D9PDLS3x.js +1 -0
- package/core/built/admin/assets/stats-view-W2g6Jsd-.js +1 -0
- package/core/built/admin/assets/{step-1-DSRp2vBj.js → step-1-BQYzvBzr.js} +1 -1
- package/core/built/admin/assets/{step-2-COqv2g0u.js → step-2-xES32xJV.js} +1 -1
- package/core/built/admin/assets/{step-3-6W1NMVQc.js → step-3-DA_XK5kW.js} +2 -2
- package/core/built/admin/assets/{stripe-button-D2K42Uty.js → stripe-button-CVsLHelt.js} +1 -1
- package/core/built/admin/assets/stripe-connect-modal-ClmLOMGS.js +1 -0
- package/core/built/admin/assets/style-Wd9nQubI.css +1 -0
- package/core/built/admin/assets/{table-B2pIKjVh.js → table-BenqVupI.js} +1 -1
- package/core/built/admin/assets/{tabs-SaW_UT-Q.js → tabs-eBBlxPp8.js} +1 -1
- package/core/built/admin/assets/tag-detail-Ce8Se2qd.js +2 -0
- package/core/built/admin/assets/tag-picker-C3UR-uJ-.js +1 -0
- package/core/built/admin/assets/tags-B-kJxCs7.js +1 -0
- package/core/built/admin/assets/{tags-zTFiKRpb.js → tags-ChfdwJyS.js} +1 -1
- package/core/built/admin/assets/testimonials-modal-9Q6fT06V.js +1 -0
- package/core/built/admin/assets/tier-detail-modal-CDyYdqK7.js +1 -0
- package/core/built/admin/assets/tiers-BCpIOGQr.js +1 -0
- package/core/built/admin/assets/{toggle-group-EM9WpESu.js → toggle-group-CviIkyok.js} +1 -1
- package/core/built/admin/assets/{topic-filter-DY4gNHn2.js → topic-filter-I2jCBGxm.js} +1 -1
- package/core/built/admin/assets/{transistor-modal-CpyPG-MH.js → transistor-modal-p53u3W8y.js} +1 -1
- package/core/built/admin/assets/{trend-badge-ByMkuAuH.js → trend-badge-CideVzQd.js} +1 -1
- package/core/built/admin/assets/{unsplash-modal-Cv2Q6GtY.js → unsplash-modal-DY8UcKcE.js} +1 -1
- package/core/built/admin/assets/{use-analytics-data-CHvjMYuT.js → use-analytics-data-DAIGcr2f.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-Qv4SVXqw.js → use-check-theme-limit-error-DRUjK4YM.js} +1 -1
- package/core/built/admin/assets/use-filter-params-JkZhIS8L.js +1 -0
- package/core/built/admin/assets/{use-filterable-api-CzXcpUh8.js → use-filterable-api-BVEVMrhZ.js} +1 -1
- package/core/built/admin/assets/use-growth-stats-937CUj6j.js +1 -0
- package/core/built/admin/assets/use-host-limits-D0Dv3tKA.js +1 -0
- package/core/built/admin/assets/{use-limiter-cwJvsNBo.js → use-limiter-CohWfzvK.js} +1 -1
- package/core/built/admin/assets/{use-navigate-with-base-path-BHPA228f.js → use-navigate-with-base-path-BkxcPj5l.js} +1 -1
- package/core/built/admin/assets/use-pintura-config-xgytjU35.js +1 -0
- package/core/built/admin/assets/{use-pintura-editor-BDBXWUYq.js → use-pintura-editor-CaV1mbfO.js} +1 -1
- package/core/built/admin/assets/{use-post-referrers-DbY-mzci.js → use-post-referrers-BCQI7fDg.js} +1 -1
- package/core/built/admin/assets/use-reply-chain-data-CYRhoaxY.js +1 -0
- package/core/built/admin/assets/{use-router-history-entry-Bg6BhBQQ.js → use-router-history-entry-NOKcd--l.js} +1 -1
- package/core/built/admin/assets/{use-setting-group-VM7FQwMY.js → use-setting-group-bLKMwoBT.js} +1 -1
- package/core/built/admin/assets/use-settings-navigation-B-qrriKD.js +1 -0
- package/core/built/admin/assets/{use-simple-pagination-Dum6Irbs.js → use-simple-pagination-CNPd6Rf6.js} +1 -1
- package/core/built/admin/assets/use-staff-users-pRIMIyiW.js +1 -0
- package/core/built/admin/assets/use-tinybird-query-CmdbCS9X.js +1 -0
- package/core/built/admin/assets/use-tinybird-token-KLne235s.js +1 -0
- package/core/built/admin/assets/{use-unsaved-changes-guard-BAy0RANR.js → use-unsaved-changes-guard-7-ug0JdH.js} +1 -1
- package/core/built/admin/assets/use-upgrade-route-Dm_qqKr0.js +1 -0
- package/core/built/admin/assets/{use-url-input-Df5QzNa7.js → use-url-input-fvp-TpPx.js} +1 -1
- package/core/built/admin/assets/user-detail-modal-D3KlGYv-.js +1 -0
- package/core/built/admin/assets/users-DVNWrqg9.js +1 -0
- package/core/built/admin/assets/{vendor-5753232c7d6ffb3c44e1eb0a02133953.js → vendor-2dd263291938d407802568d3357ec69e.js} +19 -17
- package/core/built/admin/assets/{virtual-list-DEx8WgiO.js → virtual-list-CZ3RK2Yf.js} +1 -1
- package/core/built/admin/assets/web-ClEJsCAj.js +2 -0
- package/core/built/admin/assets/web-X9rdnrtq.js +1 -0
- package/core/built/admin/assets/{zapier-modal-Cw3BTBUu.js → zapier-modal-DfVhV17j.js} +1 -1
- package/core/built/admin/index.html +23 -27
- 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/sitemap/user-map-generator.js +9 -2
- package/core/server/adapters/jobs/InMemoryJobsBackend.js +74 -29
- package/core/server/api/endpoints/authentication.js +1 -2
- package/core/server/api/endpoints/authors-public.js +15 -5
- package/core/server/api/endpoints/emails.js +13 -0
- package/core/server/api/endpoints/index.js +10 -2
- package/core/server/api/endpoints/mail.js +1 -1
- package/core/server/api/endpoints/member-metafields-members.js +23 -0
- package/core/server/api/endpoints/member-metafields.js +102 -0
- package/core/server/api/endpoints/members-account.js +42 -0
- package/core/server/api/endpoints/members.js +4 -4
- package/core/server/api/endpoints/newsletters-public.js +10 -1
- package/core/server/api/endpoints/pages-public.js +15 -5
- package/core/server/api/endpoints/posts-public.js +15 -5
- package/core/server/api/endpoints/posts.js +3 -3
- package/core/server/api/endpoints/tags-public.js +22 -3
- package/core/server/api/endpoints/tiers-checkout-config.js +2 -2
- package/core/server/api/endpoints/tiers-public.js +38 -1
- package/core/server/api/endpoints/users.js +2 -2
- package/core/server/api/endpoints/utils/api-filter-utils.js +170 -3
- package/core/server/api/endpoints/utils/public-cache-keys.js +3 -0
- package/core/server/api/endpoints/utils/serializers/input/authors.js +2 -0
- package/core/server/api/endpoints/utils/serializers/input/members.js +20 -9
- package/core/server/api/endpoints/utils/serializers/input/pages.js +2 -0
- package/core/server/api/endpoints/utils/serializers/input/posts.js +2 -0
- package/core/server/api/endpoints/utils/serializers/input/tags.js +2 -0
- package/core/server/api/endpoints/utils/serializers/output/emails.js +7 -0
- package/core/server/api/endpoints/utils/serializers/output/index.js +6 -2
- package/core/server/api/endpoints/utils/serializers/output/member-metafields.js +18 -0
- package/core/server/api/endpoints/utils/serializers/output/members-account.js +19 -0
- package/core/server/api/endpoints/utils/serializers/output/members-csv-transform.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/members.js +2 -4
- package/core/server/api/endpoints/utils/serializers/output/tiers.js +10 -2
- package/core/server/api/endpoints/utils/serializers/output/users.js +0 -10
- 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 -3
- 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.53/2026-07-15-20-22-53-add-members-custom-field-values.js +1 -1
- package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +10 -1
- package/core/server/data/migrations/versions/6.63/2026-09-03-10-07-03-rename-custom-field-tables-to-metafields.js +169 -0
- package/core/server/data/migrations/versions/6.63/2026-09-03-11-03-31-remove-member-custom-field-read-permissions.js +30 -0
- package/core/server/data/schema/fixtures/fixtures.json +0 -11
- package/core/server/data/schema/lib/default-index-name.js +8 -11
- package/core/server/data/schema/schema.js +18 -18
- package/core/server/data/seeders/README.md +48 -0
- package/core/server/data/tinybird/pipes/mv_hits.pipe +86 -50
- package/core/server/lib/db-types/count.js +16 -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/validate-password.js +81 -120
- package/core/server/models/action.js +4 -4
- package/core/server/models/base/plugins/filtered-collection.js +16 -0
- package/core/server/models/member.js +10 -21
- package/core/server/models/post.js +2 -2
- package/core/server/models/relations/authors.js +2 -16
- package/core/server/models/user.js +10 -3
- package/core/server/services/auth/passwordreset.js +13 -0
- package/core/server/services/automations/automations-api.js +1 -1
- package/core/server/services/automations/database-automations-repository.js +11 -4
- 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/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 +160 -53
- 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 +0 -1
- package/core/server/services/content-import/import/post-link.js +11 -0
- package/core/server/services/content-import/import/post-repository.js +25 -3
- package/core/server/services/content-import/import/reader.js +9 -2
- package/core/server/services/content-import/import/report.js +34 -0
- package/core/server/services/content-import/import/row.js +0 -2
- package/core/server/services/content-import/import/schema.js +1 -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 +52 -6
- 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/index.js +2 -7
- package/core/server/services/email-service/CONTEXT.md +37 -0
- package/core/server/services/email-service/README.md +42 -0
- package/core/server/services/email-service/batch-sending-service.js +2 -4
- package/core/server/services/email-service/email-controller.js +18 -2
- package/core/server/services/email-service/email-renderer.js +14 -4
- package/core/server/services/email-service/email-service-wrapper.js +3 -9
- package/core/server/services/email-service/mailgun-email-provider.js +20 -9
- package/core/server/services/email-service/sending-status-schema.js +21 -0
- package/core/server/services/email-service/sending-status-serializers.js +33 -0
- package/core/server/services/email-service/sending-status-service.js +47 -0
- package/core/server/services/email-service/sending-status.js +111 -0
- package/core/server/services/email-suppression-list/email-suppression-list.js +0 -33
- package/core/server/services/email-suppression-list/mailgun-email-suppression-list.js +6 -7
- package/core/server/services/gifts/README.md +2 -2
- 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-email-service.js +0 -1
- package/core/server/services/gifts/gift-service.js +19 -57
- package/core/server/services/gifts/index.js +3 -5
- 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/index.js +8 -0
- package/core/server/services/jobs-service/jobs-service.js +52 -3
- package/core/server/services/jobs-service/register-job-handlers.js +32 -11
- package/core/server/services/mail/ghost-mailer.js +4 -4
- package/core/server/services/member-welcome-emails/service.js +7 -7
- package/core/server/services/members/account-service.js +98 -0
- package/core/server/services/members/api.js +4 -3
- package/core/server/services/members/import-export/csv/parse.js +0 -7
- package/core/server/services/members/import-export/export/exporter.js +20 -20
- package/core/server/services/members/import-export/import/completion-email.js +8 -11
- package/core/server/services/members/import-export/import/importer.js +13 -13
- package/core/server/services/members/import-export/import/row.js +0 -3
- package/core/server/services/members/import-export/index.js +10 -17
- 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 +26 -8
- package/core/server/services/members/members-api/repositories/event-repository.js +0 -4
- package/core/server/services/members/members-api/repositories/member-repository.js +4 -4
- package/core/server/services/members/members-api/services/member-bread-service.js +62 -84
- package/core/server/services/members/members-api/services/payments-service.js +1 -1
- package/core/server/services/members/members-ssr.js +18 -0
- package/core/server/services/members/middleware.js +95 -81
- package/core/server/services/members/service.js +71 -47
- package/core/server/services/members/utils.js +9 -0
- package/core/server/services/members-metafields/access.js +28 -0
- package/core/server/services/{members-custom-fields → members-metafields}/actions.js +5 -5
- package/core/server/services/members-metafields/bindings-service.js +147 -0
- package/core/server/services/members-metafields/codec.js +14 -0
- package/core/server/services/{members-custom-fields → members-metafields}/config.js +1 -1
- package/core/server/services/{members-custom-fields → members-metafields}/definitions-service.js +68 -22
- package/core/server/services/members-metafields/filter.js +181 -0
- package/core/server/services/{members-custom-fields → members-metafields}/index.js +16 -7
- package/core/server/services/{members-custom-fields → members-metafields}/key.js +2 -1
- package/core/server/services/{members-custom-fields → members-metafields}/models.js +5 -5
- package/core/server/services/members-metafields/namespaces.js +34 -0
- package/core/server/services/{members-custom-fields → members-metafields}/queries.js +1 -1
- package/core/server/services/{members-custom-fields → members-metafields}/schema.js +16 -12
- package/core/server/services/members-metafields/serializers.js +22 -0
- package/core/server/services/{members-custom-fields → members-metafields}/values-service.js +80 -48
- package/core/server/services/mentions/mention-controller.js +23 -21
- package/core/server/services/mentions/mention-sending-service.js +26 -9
- package/core/server/services/mentions/process-webmention-job.js +16 -0
- package/core/server/services/mentions/send-webmentions-job.js +18 -0
- package/core/server/services/mentions/service.js +7 -33
- 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 +2 -2
- package/core/server/services/stripe/services/checkout/session-options.js +18 -15
- package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +28 -7
- package/core/server/services/stripe/stripe-api.js +2 -0
- package/core/server/services/stripe/stripe-service.js +24 -14
- package/core/server/services/stripe/webhook-controller.js +10 -0
- package/core/server/services/tier-checkout-config/codec.js +11 -7
- package/core/server/services/tier-checkout-config/index.js +2 -1
- package/core/server/services/tier-checkout-config/models.js +11 -5
- package/core/server/services/tier-checkout-config/queries.js +12 -12
- package/core/server/services/tier-checkout-config/serializers.js +73 -10
- package/core/server/services/tier-checkout-config/service.js +14 -13
- package/core/server/services/tiers/service.js +3 -3
- 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/services/url/router-filter.js +8 -10
- package/core/server/services/users.js +1 -19
- package/core/server/web/admin/controller.js +1 -1
- package/core/server/web/api/endpoints/admin/routes.js +20 -16
- package/core/server/web/members/account/index.js +2 -0
- package/core/server/web/members/account/routes.js +46 -0
- package/core/server/web/members/app.js +4 -18
- package/core/shared/config/defaults.json +1 -1
- package/core/shared/config/env/config.testing-mysql.json +1 -1
- package/core/shared/labs.js +7 -2
- package/package.json +42 -41
- package/pnpm-lock.yaml +1159 -582
- package/pnpm-workspace.yaml +18 -15
- package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.2.0.tgz +0 -0
- package/core/built/admin/assets/Combination-5fYEzgbs.js +0 -41
- package/core/built/admin/assets/account-migration-DRhAehfw.js +0 -1
- package/core/built/admin/assets/activitypub/inbox-BluD2ucT.js +0 -1284
- package/core/built/admin/assets/activitypub/label-FHSh6ZAw.js +0 -16
- package/core/built/admin/assets/activitypub/layout-Cq4NG0Xr.js +0 -1
- package/core/built/admin/assets/activitypub/settings-DQDkr06X.js +0 -315
- package/core/built/admin/assets/add-integration-modal-BC0jExDZ.js +0 -1
- package/core/built/admin/assets/add-newsletter-modal-DchTkA7l.js +0 -1
- package/core/built/admin/assets/add-recommendation-modal-DMvaJZKp.js +0 -1
- package/core/built/admin/assets/alert-dialog-QhXzdHfc.js +0 -1
- package/core/built/admin/assets/analytics-No1DDY3A.js +0 -1
- package/core/built/admin/assets/automated-emails-te1D5kq-.js +0 -1
- package/core/built/admin/assets/automations-DpUNUdcZ.js +0 -1
- package/core/built/admin/assets/bluesky-sharing-Cjrv_f62.js +0 -1
- package/core/built/admin/assets/chunk.896.435fe0637944d4a69810.js +0 -366
- package/core/built/admin/assets/color-picker-BTY_NdEm.js +0 -3
- package/core/built/admin/assets/color-picker-field-CPlkzOH0.js +0 -1
- package/core/built/admin/assets/combobox-DrMtv_4P.js +0 -1
- package/core/built/admin/assets/command-ZdgYJpoI.js +0 -1
- package/core/built/admin/assets/comments-kidj60-h.js +0 -1
- package/core/built/admin/assets/confirmation-context-BhbmQQEl.js +0 -1
- package/core/built/admin/assets/custom-field-type-option-CsGIZk1J.js +0 -10
- package/core/built/admin/assets/custom-integration-modal-BY37O_9X.js +0 -1
- package/core/built/admin/assets/design-and-theme-modal-DUSKM4uC.js +0 -22
- package/core/built/admin/assets/dialog-B44iRwlc.js +0 -1
- package/core/built/admin/assets/dirty-confirm-dialog-BxgQgQ6H.js +0 -1
- package/core/built/admin/assets/dist-Cy8tfrp_.js +0 -1
- package/core/built/admin/assets/domain-Ca6KhoQt.js +0 -1
- package/core/built/admin/assets/dropzone-Bb-5zQTk.js +0 -5
- package/core/built/admin/assets/edit-profile-BdupLlus.js +0 -1
- package/core/built/admin/assets/editor-BP883Lpn.js +0 -8
- package/core/built/admin/assets/explore-FB-CimXw.js +0 -1
- package/core/built/admin/assets/feed-C1GxsmI2.js +0 -1
- package/core/built/admin/assets/filter-sources-D5n5bFA2.js +0 -1
- package/core/built/admin/assets/filters-DJEzkMUK.js +0 -1
- package/core/built/admin/assets/filters-MZXvdH5D.js +0 -1
- package/core/built/admin/assets/first-promoter-modal-CGTGLCr5.js +0 -1
- package/core/built/admin/assets/gift-link-modal-B6v0tnRN.js +0 -1
- package/core/built/admin/assets/growth-BGAJD1rs.js +0 -3
- package/core/built/admin/assets/history-modal-iKwUY3XR.js +0 -1
- package/core/built/admin/assets/hooks-CuEZV2-q.js +0 -2
- package/core/built/admin/assets/index-DjQe-e5s.js +0 -4
- package/core/built/admin/assets/index-Dqf2x-ry.css +0 -1
- package/core/built/admin/assets/index.es-VihPwfeH.js +0 -1
- package/core/built/admin/assets/input--W-rCTrX.js +0 -1
- package/core/built/admin/assets/invite-user-modal-MdImLCaJ.js +0 -1
- package/core/built/admin/assets/koenig-loader-DYZpm6El.js +0 -2
- package/core/built/admin/assets/label-picker-cB51xl1r.js +0 -1
- package/core/built/admin/assets/layout-c_B_1hax.js +0 -1
- package/core/built/admin/assets/link-helpers-MVDdnFCY.js +0 -1
- package/core/built/admin/assets/loading-indicator-BpFWTVgh.js +0 -1
- package/core/built/admin/assets/member-custom-fields-qs0dIkxM.js +0 -1
- package/core/built/admin/assets/member-detail-B6uec2a6.js +0 -1
- package/core/built/admin/assets/members-BQF3CqhI.js +0 -2
- package/core/built/admin/assets/moderation-DY4wF1Q3.js +0 -1
- package/core/built/admin/assets/navigation-modal-DX-gNF3X.js +0 -1
- package/core/built/admin/assets/newsletter-CyQA7m3m.js +0 -1
- package/core/built/admin/assets/newsletter-detail-modal-BDrptuxn.js +0 -1
- package/core/built/admin/assets/newsletters-DXuNmC42.js +0 -1
- package/core/built/admin/assets/newsletters-DhNigJUh.js +0 -1
- package/core/built/admin/assets/note-Cgo2LCJ6.js +0 -1
- package/core/built/admin/assets/notifications-BC50AMku.js +0 -1
- package/core/built/admin/assets/offers-container-modal-DWycIMzT.js +0 -1
- package/core/built/admin/assets/onboarding-DGQuV7Ew.js +0 -1
- package/core/built/admin/assets/overview-CVbFtn-k.js +0 -1
- package/core/built/admin/assets/overview-YKj7SZaK.js +0 -1
- package/core/built/admin/assets/pages-B6vr9F_D.js +0 -1
- package/core/built/admin/assets/popover-CCy0h5rq.js +0 -1
- package/core/built/admin/assets/portal-frame-dc4XKr0e.js +0 -1
- package/core/built/admin/assets/portal-modal-BvvdF_B9.js +0 -1
- package/core/built/admin/assets/post-analytics-DX470WRG.js +0 -1
- package/core/built/admin/assets/post-analytics-header-CXpJmdDv.js +0 -1
- package/core/built/admin/assets/post-analytics-provider-CLfKGWZy.js +0 -1
- package/core/built/admin/assets/post-helpers-C5OQMBoi.js +0 -1
- package/core/built/admin/assets/post-share-modal-Cl0NXRJr.js +0 -1
- package/core/built/admin/assets/posts-KY3aUhVV.js +0 -1
- package/core/built/admin/assets/preferences-DO0UUWFp.js +0 -5
- package/core/built/admin/assets/profile-CxT-0l4d.js +0 -1
- package/core/built/admin/assets/router-provider-B0gZyiGJ.js +0 -1
- package/core/built/admin/assets/schemas-4UPic-oD.js +0 -64
- package/core/built/admin/assets/select-DuDUFdPq.js +0 -1
- package/core/built/admin/assets/settings-BjCii7BM.js +0 -1
- package/core/built/admin/assets/settings-b8kNzZSQ.js +0 -4
- package/core/built/admin/assets/share-modal-BV70mr9k.js +0 -1
- package/core/built/admin/assets/shim-ClF_m5ux.js +0 -1
- package/core/built/admin/assets/slack-modal-Be2_KK-z.js +0 -1
- package/core/built/admin/assets/stats-C4reGh9O.js +0 -1
- package/core/built/admin/assets/stats-view-DP9pQ62k.js +0 -1
- package/core/built/admin/assets/stripe-connect-modal-Z-UT_BGc.js +0 -1
- package/core/built/admin/assets/tag-detail-Dc0nTOtU.js +0 -2
- package/core/built/admin/assets/tags-BLaXBwnb.js +0 -1
- package/core/built/admin/assets/testimonials-modal-sFwRnNLh.js +0 -1
- package/core/built/admin/assets/tier-detail-modal-BCFpWwUS.js +0 -1
- package/core/built/admin/assets/tiers-Nd_z4BZY.js +0 -1
- package/core/built/admin/assets/use-filter-params-CAvfDvf1.js +0 -1
- package/core/built/admin/assets/use-growth-stats-D6mZXGhK.js +0 -1
- package/core/built/admin/assets/use-host-limits-DZtR08oa.js +0 -1
- package/core/built/admin/assets/use-pintura-config-CzTfnufz.js +0 -1
- package/core/built/admin/assets/use-reply-chain-data-BeQwz8oF.js +0 -1
- package/core/built/admin/assets/use-settings-navigation-BbrSUVcf.js +0 -1
- package/core/built/admin/assets/use-staff-users-cZg4ugOF.js +0 -1
- package/core/built/admin/assets/use-tinybird-query-B1IMyAcv.js +0 -39
- package/core/built/admin/assets/use-tinybird-token-Bow0Zwn8.js +0 -1
- package/core/built/admin/assets/use-upgrade-route-DhRSo7at.js +0 -1
- package/core/built/admin/assets/user-detail-modal-yipSoQfv.js +0 -1
- package/core/built/admin/assets/users-5msE1WMs.js +0 -1
- package/core/built/admin/assets/web-CKcXAu86.js +0 -1
- package/core/built/admin/assets/web-DR4TqM3E.js +0 -2
- package/core/server/api/endpoints/member-custom-fields.js +0 -90
- package/core/server/api/endpoints/utils/serializers/output/member-custom-fields.js +0 -20
- package/core/server/services/email-suppression-list/in-memory-email-suppression-list.js +0 -39
- 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/members-custom-fields/bindings-service.js +0 -119
- package/core/server/services/members-custom-fields/codec.js +0 -15
- package/core/server/services/members-custom-fields/filter.js +0 -199
- package/core/server/services/members-custom-fields/serializers.js +0 -24
- package/core/server/services/stripe/services/checkout/allowed-countries.js +0 -267
- package/core/server/services/stripe/services/checkout/field-ports.js +0 -34
- package/core/server/services/tier-checkout-config/destinations.js +0 -17
- package/core/server/services/update-check/run-update-check.js +0 -63
- /package/core/built/admin/assets/{chunk.492.f24d855d7e3f3aad261c.js.LICENSE.txt → chunk.901.4ccc639821020230bcca.js.LICENSE.txt} +0 -0
- /package/core/server/services/{members-custom-fields → members-metafields}/storage.js +0 -0
|
@@ -22,7 +22,7 @@ const emailSuppressionList = require('../email-suppression-list');
|
|
|
22
22
|
const commentsService = require('../comments');
|
|
23
23
|
const emailAddressService = require('../email-address');
|
|
24
24
|
const giftService = require('../gifts');
|
|
25
|
-
const
|
|
25
|
+
const metafieldsService = require('../members-metafields');
|
|
26
26
|
const { t } = require('../i18n');
|
|
27
27
|
const sentry = require('../../../shared/sentry');
|
|
28
28
|
|
|
@@ -271,9 +271,10 @@ function createApiInstance(config) {
|
|
|
271
271
|
giftService,
|
|
272
272
|
// Resolved here rather than passed as the module: the members service needs
|
|
273
273
|
// the values service itself, and reading it at construction is what ties the
|
|
274
|
-
// two together in boot order.
|
|
274
|
+
// two together in boot order. Metafields is initialised in initCore, the
|
|
275
275
|
// members API is built in initServices, so this is always the live instance.
|
|
276
|
-
|
|
276
|
+
metafieldValues: metafieldsService.values,
|
|
277
|
+
metafieldDefinitions: metafieldsService.definitions,
|
|
277
278
|
});
|
|
278
279
|
|
|
279
280
|
return membersApiInstance;
|
|
@@ -43,19 +43,12 @@ function parse(path, headerMapping) {
|
|
|
43
43
|
// hasOwn, not `in`: a prototype-named header would otherwise match an
|
|
44
44
|
// inherited method on the mapping and take a function as its mapped name.
|
|
45
45
|
if (headerMapping && Object.hasOwn(headerMapping, header)) {
|
|
46
|
-
// An empty target is the caller naming a column to leave out, which
|
|
47
|
-
// it has to name: an unnamed column carries through below, and for a
|
|
48
|
-
// custom_fields.* column carrying through means being imported. So
|
|
49
|
-
// omitting a column from the mapping is the opposite of excluding it.
|
|
50
46
|
if (!headerMapping[header]) {
|
|
51
47
|
continue;
|
|
52
48
|
}
|
|
53
49
|
row[headerMapping[header]] = value;
|
|
54
50
|
}
|
|
55
51
|
else if (isSafeColumnName(header)) {
|
|
56
|
-
// Carry any unmapped column through untouched, so the import is not
|
|
57
|
-
// constrained to a known vocabulary: a custom_fields.* column
|
|
58
|
-
// survives parsing even though nothing consumes it yet.
|
|
59
52
|
row[header] = value;
|
|
60
53
|
}
|
|
61
54
|
}
|
|
@@ -8,7 +8,7 @@ const moment_1 = __importDefault(require("moment"));
|
|
|
8
8
|
const node_stream_1 = require("node:stream");
|
|
9
9
|
const logging = require('@tryghost/logging');
|
|
10
10
|
const errors = require('@tryghost/errors');
|
|
11
|
-
const { csvCellsForFields } = require('@tryghost/
|
|
11
|
+
const { csvCellsForFields } = require('@tryghost/metafield-types/csv');
|
|
12
12
|
// The columns the export reads off the members table, checked against MemberDbRow so the
|
|
13
13
|
// query cannot select a column the row type does not declare.
|
|
14
14
|
const MEMBER_COLUMNS = ['id', 'email', 'name', 'note', 'status', 'created_at'];
|
|
@@ -32,22 +32,22 @@ function toExportCsvRow(row) {
|
|
|
32
32
|
labels: namesToCsv(row.labels),
|
|
33
33
|
tiers: namesToCsv(row.tiers),
|
|
34
34
|
gift_id: row.gift_id || '',
|
|
35
|
-
...row.
|
|
35
|
+
...row.metafield_cells,
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
const BATCH_SIZE = 1000;
|
|
39
39
|
// Streams the matching members as flat rows, each carrying its related data (tiers,
|
|
40
|
-
// labels, subscription, gift, Stripe customer,
|
|
41
|
-
// to turn into CSV lines. The members aggregate and
|
|
40
|
+
// labels, subscription, gift, Stripe customer, metafields) for the output serializer
|
|
41
|
+
// to turn into CSV lines. The members aggregate and metafields are reached only
|
|
42
42
|
// through the ports above, so nothing Bookshelf- or flag-shaped leaks into the export.
|
|
43
43
|
class MembersCSVExporter {
|
|
44
44
|
_knex;
|
|
45
45
|
_members;
|
|
46
|
-
|
|
47
|
-
constructor({ knex, members,
|
|
46
|
+
_metafields;
|
|
47
|
+
constructor({ knex, members, metafields }) {
|
|
48
48
|
this._knex = knex;
|
|
49
49
|
this._members = members;
|
|
50
|
-
this.
|
|
50
|
+
this._metafields = metafields;
|
|
51
51
|
}
|
|
52
52
|
async export(options = {}) {
|
|
53
53
|
const start = Date.now();
|
|
@@ -75,7 +75,7 @@ class MembersCSVExporter {
|
|
|
75
75
|
return processingTransform;
|
|
76
76
|
}
|
|
77
77
|
// products and labels are small, stable tables, read once up front as id->name
|
|
78
|
-
// maps.
|
|
78
|
+
// maps. Metafield definitions are read once too, which fixes the column set for
|
|
79
79
|
// the whole file: archiving a field mid-export cannot leave the header ragged. It
|
|
80
80
|
// can still leave that field's cells empty from the batch after the archive
|
|
81
81
|
// onwards, because the per-batch value read applies its own active filter.
|
|
@@ -94,8 +94,8 @@ class MembersCSVExporter {
|
|
|
94
94
|
return acc;
|
|
95
95
|
}, {}));
|
|
96
96
|
logging.info({ event: { name: 'members-export.reference_data_read' }, durationMs: Date.now() - start }, 'Read the products and labels an export names');
|
|
97
|
-
const
|
|
98
|
-
return { allProducts, allLabels,
|
|
97
|
+
const activeMetafields = await this._metafields.activeDefinitions();
|
|
98
|
+
return { allProducts, allLabels, activeMetafields };
|
|
99
99
|
}
|
|
100
100
|
// Group the member stream into batches so the related-data reads are one query per
|
|
101
101
|
// batch rather than one per member.
|
|
@@ -121,7 +121,7 @@ class MembersCSVExporter {
|
|
|
121
121
|
}
|
|
122
122
|
// Each flattened row is pushed individually so a batch's rows do not pile up in memory.
|
|
123
123
|
createProcessingTransform(reference) {
|
|
124
|
-
const assembleRelatedData = (memberIds) => this.assembleRelatedData(memberIds, reference.
|
|
124
|
+
const assembleRelatedData = (memberIds) => this.assembleRelatedData(memberIds, reference.activeMetafields);
|
|
125
125
|
const flattenBatch = (members, related) => this.flattenBatch(members, related, reference);
|
|
126
126
|
return new node_stream_1.Transform({
|
|
127
127
|
objectMode: true,
|
|
@@ -140,11 +140,11 @@ class MembersCSVExporter {
|
|
|
140
140
|
},
|
|
141
141
|
});
|
|
142
142
|
}
|
|
143
|
-
// One query per related table for the whole batch, not one per member.
|
|
143
|
+
// One query per related table for the whole batch, not one per member. Metafield
|
|
144
144
|
// values are read only when there are columns to fill.
|
|
145
|
-
async assembleRelatedData(memberIds,
|
|
145
|
+
async assembleRelatedData(memberIds, activeMetafields) {
|
|
146
146
|
const knex = this._knex;
|
|
147
|
-
const [tiers, labels, stripeCustomers, subscriptions, gifts,
|
|
147
|
+
const [tiers, labels, stripeCustomers, subscriptions, gifts, metafieldValuesMap] = await Promise.all([
|
|
148
148
|
knex('members_products')
|
|
149
149
|
.select('member_id', knex.raw('GROUP_CONCAT(product_id) as tiers'))
|
|
150
150
|
.whereIn('member_id', memberIds)
|
|
@@ -162,8 +162,8 @@ class MembersCSVExporter {
|
|
|
162
162
|
.select('id', 'redeemer_member_id')
|
|
163
163
|
.where('status', 'redeemed')
|
|
164
164
|
.whereIn('redeemer_member_id', memberIds),
|
|
165
|
-
|
|
166
|
-
? this.
|
|
165
|
+
activeMetafields.length > 0
|
|
166
|
+
? this._metafields.valuesForMembers(memberIds)
|
|
167
167
|
: new Map(),
|
|
168
168
|
]);
|
|
169
169
|
return {
|
|
@@ -178,12 +178,12 @@ class MembersCSVExporter {
|
|
|
178
178
|
row.redeemer_member_id,
|
|
179
179
|
row.id,
|
|
180
180
|
])),
|
|
181
|
-
|
|
181
|
+
metafieldValuesMap,
|
|
182
182
|
};
|
|
183
183
|
}
|
|
184
184
|
flattenBatch(members, related, reference) {
|
|
185
|
-
const { tiersMap, labelsMap, stripeCustomerMap, subscribedSet, giftIdMap,
|
|
186
|
-
const { allProducts, allLabels,
|
|
185
|
+
const { tiersMap, labelsMap, stripeCustomerMap, subscribedSet, giftIdMap, metafieldValuesMap } = related;
|
|
186
|
+
const { allProducts, allLabels, activeMetafields } = reference;
|
|
187
187
|
return members.map((row) => {
|
|
188
188
|
const tierConcat = tiersMap.get(row.id);
|
|
189
189
|
const tierIds = tierConcat ? tierConcat.split(',') : [];
|
|
@@ -202,7 +202,7 @@ class MembersCSVExporter {
|
|
|
202
202
|
// only knowable from the database. Every member carries a cell for every
|
|
203
203
|
// active field's column, so a member with no values still contributes the
|
|
204
204
|
// full set -- the CSV header is taken from whichever row streams first.
|
|
205
|
-
|
|
205
|
+
metafield_cells: csvCellsForFields(activeMetafields, metafieldValuesMap.get(row.id) || {}),
|
|
206
206
|
};
|
|
207
207
|
});
|
|
208
208
|
}
|
|
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.default = buildImportEmail;
|
|
37
37
|
const csv_1 = require("../csv");
|
|
38
38
|
const email_template_1 = __importStar(require("./email-template"));
|
|
39
|
-
const csv_2 = require("@tryghost/
|
|
39
|
+
const csv_2 = require("@tryghost/metafield-types/csv");
|
|
40
40
|
// Turn an ORM validation error into copy a member manager can act on. Presentation
|
|
41
41
|
// only: the API response keeps the raw messages, and just the emailed report is
|
|
42
42
|
// rewritten. Takes one reason at a time, so a rewrite matching to the end of its input
|
|
@@ -55,13 +55,11 @@ function humaniseError(row) {
|
|
|
55
55
|
function stringifyLabels(labels) {
|
|
56
56
|
return labels.map((label) => (typeof label === 'string' ? label : label.name)).join(',');
|
|
57
57
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
function customFieldCells(row) {
|
|
61
|
-
return Object.fromEntries(Object.entries(row).filter(([column]) => (0, csv_2.isCustomFieldColumn)(column)));
|
|
58
|
+
function metafieldCells(row) {
|
|
59
|
+
return Object.fromEntries(Object.entries(row).filter(([column]) => (0, csv_2.isMetafieldColumn)(column)));
|
|
62
60
|
}
|
|
63
61
|
// Shape a failed import row into its fixed error-report cells, with the raw ORM message
|
|
64
|
-
// rewritten into copy the member manager can act on.
|
|
62
|
+
// rewritten into copy the member manager can act on. Metafield cells are merged on by
|
|
65
63
|
// buildErrorReport, which owns the dynamic column set.
|
|
66
64
|
function toErrorReportRow(row) {
|
|
67
65
|
return {
|
|
@@ -82,14 +80,13 @@ function toErrorReportRow(row) {
|
|
|
82
80
|
}
|
|
83
81
|
// The error report attached to the completion email: the failed rows as CSV, called only
|
|
84
82
|
// when there are rows to list. It shares the serialiser with the export but not the
|
|
85
|
-
// shaping -- the export writes db members, this echoes submitted rows. Member columns
|
|
86
|
-
//
|
|
87
|
-
// before the last error column, and each row's custom cells merged on.
|
|
83
|
+
// shaping -- the export writes db members, this echoes submitted rows. Member columns
|
|
84
|
+
// come from the shaper's keys, so the type stays the single source.
|
|
88
85
|
function buildErrorReport(errors) {
|
|
89
86
|
const memberColumns = Object.keys(toErrorReportRow(errors[0])).filter((column) => column !== 'error');
|
|
90
|
-
const customColumns = [...new Set(errors.flatMap((row) => Object.keys(
|
|
87
|
+
const customColumns = [...new Set(errors.flatMap((row) => Object.keys(metafieldCells(row))))];
|
|
91
88
|
const columns = [...memberColumns, ...customColumns, 'error'];
|
|
92
|
-
const rows = errors.map((row) => ({ ...toErrorReportRow(row), ...
|
|
89
|
+
const rows = errors.map((row) => ({ ...toErrorReportRow(row), ...metafieldCells(row) }));
|
|
93
90
|
return (0, csv_1.serialize)(rows, { columns });
|
|
94
91
|
}
|
|
95
92
|
// The one email an import sends, whatever became of it. What the publisher is told and
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const moment_timezone_1 = __importDefault(require("moment-timezone"));
|
|
7
7
|
const completion_email_1 = __importDefault(require("./completion-email"));
|
|
8
8
|
const csv_1 = require("../csv");
|
|
9
|
-
const csv_2 = require("@tryghost/
|
|
9
|
+
const csv_2 = require("@tryghost/metafield-types/csv");
|
|
10
10
|
const metrics = require('@tryghost/metrics');
|
|
11
11
|
const errors = require('@tryghost/errors');
|
|
12
12
|
const logging = require('@tryghost/logging');
|
|
@@ -17,7 +17,7 @@ const messages = {
|
|
|
17
17
|
giftCannotCombineWithImportTier: 'Cannot specify both gift_id and import_tier.',
|
|
18
18
|
giftCannotCombineWithComplimentary: 'Cannot specify both gift_id and complimentary_plan.',
|
|
19
19
|
giftReassignFailed: 'Failed to reassign gift to member.',
|
|
20
|
-
|
|
20
|
+
metafieldWriteFailed: 'Failed to save the custom field values for this member.',
|
|
21
21
|
};
|
|
22
22
|
// Columns whose presence makes a row slow to import (they reach out to Stripe), so
|
|
23
23
|
// a file carrying any of them is deferred regardless of size. The routing decision
|
|
@@ -50,13 +50,13 @@ class MembersCSVImporter {
|
|
|
50
50
|
_tiers;
|
|
51
51
|
_stripe;
|
|
52
52
|
_gifts;
|
|
53
|
-
|
|
53
|
+
_metafields;
|
|
54
54
|
_email;
|
|
55
55
|
_report;
|
|
56
56
|
_addJob;
|
|
57
57
|
_getTimezone;
|
|
58
58
|
_getInlineThreshold;
|
|
59
|
-
constructor({ knex, readRows, spool, members, tiers, stripe, gifts,
|
|
59
|
+
constructor({ knex, readRows, spool, members, tiers, stripe, gifts, metafields, email, report, addJob, getTimezone, getInlineThreshold, }) {
|
|
60
60
|
this._knex = knex;
|
|
61
61
|
this._readRows = readRows;
|
|
62
62
|
this._spool = spool;
|
|
@@ -64,7 +64,7 @@ class MembersCSVImporter {
|
|
|
64
64
|
this._tiers = tiers;
|
|
65
65
|
this._stripe = stripe;
|
|
66
66
|
this._gifts = gifts;
|
|
67
|
-
this.
|
|
67
|
+
this._metafields = metafields;
|
|
68
68
|
this._email = email;
|
|
69
69
|
this._report = report;
|
|
70
70
|
this._addJob = addJob;
|
|
@@ -166,14 +166,14 @@ class MembersCSVImporter {
|
|
|
166
166
|
return {
|
|
167
167
|
defaultTier: await this._tiers.getDefault(),
|
|
168
168
|
// Empty when the feature is off, so a carried-through column is dropped.
|
|
169
|
-
|
|
169
|
+
activeMetafields: await this._metafields.activeFields(),
|
|
170
170
|
globalLabels: [{ name: labelName }, ...extraLabels],
|
|
171
171
|
};
|
|
172
172
|
}
|
|
173
173
|
// Must not throw: once a row has committed, an import that failed halfway is not one
|
|
174
174
|
// that never ran.
|
|
175
175
|
async writeRows(rows, prepared) {
|
|
176
|
-
const { defaultTier,
|
|
176
|
+
const { defaultTier, activeMetafields } = prepared;
|
|
177
177
|
const tierIdCache = new Map();
|
|
178
178
|
const archivableStripePriceIds = [];
|
|
179
179
|
// Copied per row: the member model stamps ids and trims names onto these in
|
|
@@ -199,12 +199,12 @@ class MembersCSVImporter {
|
|
|
199
199
|
}));
|
|
200
200
|
}
|
|
201
201
|
}
|
|
202
|
-
// Validate the row's
|
|
202
|
+
// Validate the row's metafield values before the transaction opens.
|
|
203
203
|
// planWrite only reads, and it throws on an invalid value to fail the row
|
|
204
204
|
// before any member write -- so there is no reason to hold a transaction
|
|
205
205
|
// across it, and doing so would deadlock the single-connection SQLite pool.
|
|
206
|
-
const
|
|
207
|
-
? await namingTheColumn(() => this.
|
|
206
|
+
const metafieldPlan = activeMetafields.length > 0
|
|
207
|
+
? await namingTheColumn(() => this._metafields.planWrite((0, csv_2.fieldValuesFromCsvRow)(activeMetafields, row, csv_1.stripFormulaGuard)))
|
|
208
208
|
: [];
|
|
209
209
|
trx = await this._knex.transaction(undefined, { doNotRejectOnRollback: false });
|
|
210
210
|
const options = { transacting: trx, context: IMPORT_CONTEXT };
|
|
@@ -307,7 +307,7 @@ class MembersCSVImporter {
|
|
|
307
307
|
}
|
|
308
308
|
// On the row's transaction, so the values commit or roll back with the member.
|
|
309
309
|
try {
|
|
310
|
-
await this.
|
|
310
|
+
await this._metafields.applyWrite(member.id, metafieldPlan, trx);
|
|
311
311
|
}
|
|
312
312
|
catch (writeError) {
|
|
313
313
|
// planWrite passed every value before the transaction opened, so a failure
|
|
@@ -315,7 +315,7 @@ class MembersCSVImporter {
|
|
|
315
315
|
// will have written a query into; the publisher gets a sentence instead, in a
|
|
316
316
|
// file they open next to a spreadsheet.
|
|
317
317
|
this._report(writeError);
|
|
318
|
-
throw new errors.DataImportError({ message: tpl(messages.
|
|
318
|
+
throw new errors.DataImportError({ message: tpl(messages.metafieldWriteFailed) });
|
|
319
319
|
}
|
|
320
320
|
await trx.commit();
|
|
321
321
|
imported += 1;
|
|
@@ -327,7 +327,7 @@ class MembersCSVImporter {
|
|
|
327
327
|
.filter((message) => typeof message === 'string');
|
|
328
328
|
const errorMessage = reasons.join('\n');
|
|
329
329
|
// trx is unset if the row failed before the transaction opened (a bad
|
|
330
|
-
//
|
|
330
|
+
// metafield value or gift combination). A rejected rollback must not
|
|
331
331
|
// escape: rows before this one are already committed, and a throw leaving
|
|
332
332
|
// here would be read as an import that never wrote anything.
|
|
333
333
|
if (trx) {
|
|
@@ -16,9 +16,6 @@ const optionalCell = zod_1.z
|
|
|
16
16
|
// not defaulted), so a CSV lacking these columns leaves the member's state untouched.
|
|
17
17
|
const isSubscribed = (cell) => cell.toLowerCase() !== 'false';
|
|
18
18
|
const isComplimentary = (cell) => cell.toLowerCase() === 'true';
|
|
19
|
-
// The member import vocabulary as a schema: the single source of truth that coerces the
|
|
20
|
-
// raw string Row into typed member fields. Unknown columns (custom_fields.*, and anything
|
|
21
|
-
// else a source carries) pass through via .loose() so nothing is lost before the kernel.
|
|
22
19
|
exports.memberImportRowSchema = zod_1.z
|
|
23
20
|
.object({
|
|
24
21
|
id: optionalCell,
|
|
@@ -12,7 +12,6 @@ const exporter_1 = __importDefault(require("./export/exporter"));
|
|
|
12
12
|
const MembersCSVImporterStripeUtils = require('./import/stripe-utils');
|
|
13
13
|
const db = require('../../../data/db');
|
|
14
14
|
const models = require('../../../models');
|
|
15
|
-
const labs = require('../../../../shared/labs');
|
|
16
15
|
const logging = require('@tryghost/logging');
|
|
17
16
|
const sentry = require('../../../../shared/sentry');
|
|
18
17
|
// Build the members CSV importer. This is the composition root: today's models and
|
|
@@ -57,15 +56,12 @@ function makeImporter(deps) {
|
|
|
57
56
|
transacting: options.transacting,
|
|
58
57
|
}),
|
|
59
58
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
const customFields = {
|
|
64
|
-
activeFields: async () => labs.isSet('membersCustomFields') ? deps.customFields.definitions.browse() : [],
|
|
65
|
-
planWrite: (values) => deps.customFields.values.planWrite(values),
|
|
59
|
+
const metafields = {
|
|
60
|
+
activeFields: async () => deps.metafields.definitions.browse(),
|
|
61
|
+
planWrite: (values) => deps.metafields.values.planWrite(values),
|
|
66
62
|
// Every value the import writes came out of the file, whichever column carried it.
|
|
67
63
|
// An import has no id to give until runs are tracked, so it names its kind only.
|
|
68
|
-
applyWrite: (memberId, plan, executor) => deps.
|
|
64
|
+
applyWrite: (memberId, plan, executor) => deps.metafields.values.applyWrite(memberId, plan, {
|
|
69
65
|
writtenBy: { type: 'import', id: null },
|
|
70
66
|
executor,
|
|
71
67
|
}),
|
|
@@ -95,7 +91,7 @@ function makeImporter(deps) {
|
|
|
95
91
|
productRepository: deps.productRepository,
|
|
96
92
|
}),
|
|
97
93
|
gifts,
|
|
98
|
-
|
|
94
|
+
metafields,
|
|
99
95
|
email,
|
|
100
96
|
report,
|
|
101
97
|
addJob: deps.addJob,
|
|
@@ -104,10 +100,8 @@ function makeImporter(deps) {
|
|
|
104
100
|
});
|
|
105
101
|
}
|
|
106
102
|
// Build the members CSV exporter. The same composition root from the other direction:
|
|
107
|
-
// knex and the members id lookup are wired here, and the
|
|
108
|
-
// values services are injected (boot builds them before this one).
|
|
109
|
-
// decides whether custom field columns appear, so nothing flag-shaped leaks into the
|
|
110
|
-
// exporter itself.
|
|
103
|
+
// knex and the members id lookup are wired here, and the metafields definitions and
|
|
104
|
+
// values services are injected (boot builds them before this one).
|
|
111
105
|
function makeExporter({ definitions, values, }) {
|
|
112
106
|
const exporter = new exporter_1.default({
|
|
113
107
|
knex: db.knex,
|
|
@@ -124,11 +118,10 @@ function makeExporter({ definitions, values, }) {
|
|
|
124
118
|
return page.data.map((member) => member.id);
|
|
125
119
|
},
|
|
126
120
|
},
|
|
127
|
-
|
|
121
|
+
metafields: {
|
|
128
122
|
// Boot builds the definitions and values services before this one, so they
|
|
129
|
-
// are always present -- no not-initialised state to guard.
|
|
130
|
-
|
|
131
|
-
activeDefinitions: async () => labs.isSet('membersCustomFields') ? definitions.browse() : [],
|
|
123
|
+
// are always present -- no not-initialised state to guard.
|
|
124
|
+
activeDefinitions: async () => definitions.browse(),
|
|
132
125
|
valuesForMembers: (memberIds) => values.getValuesForMembers(memberIds),
|
|
133
126
|
},
|
|
134
127
|
});
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const ObjectId = require('bson-objectid').default;
|
|
4
|
+
const { chunk: chunkArray } = require('lodash');
|
|
5
|
+
const moment = require('moment');
|
|
6
|
+
// Removes expired comped entries from the members_products table, moves the
|
|
7
|
+
// affected members from comped to free and records a comped->free status
|
|
8
|
+
// event for each of them. The three writes commit or roll back together, so a
|
|
9
|
+
// failed run leaves the source rows in place and a repeat run redoes the whole
|
|
10
|
+
// unit of work instead of finishing a half-applied one.
|
|
11
|
+
async function cleanExpiredComped({ db, models, events, logging, sentry, }) {
|
|
12
|
+
const startedAt = Date.now();
|
|
13
|
+
const { deletedSubscriptionCount, updatedMemberCount, updatedMembers, now } = await db.knex.transaction(async (trx) => {
|
|
14
|
+
const expiredCompedRows = await trx('members_products')
|
|
15
|
+
// we need to be careful about the type here. .format() is the only
|
|
16
|
+
// thing that works across SQLite and MySQL: an ISO cutoff compares
|
|
17
|
+
// lexically against SQLite's 'YYYY-MM-DD HH:mm:ss' text and its 'T'
|
|
18
|
+
// sorts above ' ', wrongly matching same-UTC-day expiries
|
|
19
|
+
.where('expiry_at', '<', moment.utc().startOf('day').format('YYYY-MM-DD HH:mm:ss'))
|
|
20
|
+
.select('*');
|
|
21
|
+
if (!expiredCompedRows.length) {
|
|
22
|
+
return {
|
|
23
|
+
deletedSubscriptionCount: 0,
|
|
24
|
+
updatedMemberCount: 0,
|
|
25
|
+
updatedMembers: [],
|
|
26
|
+
now: new Date(),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
const rowIds = expiredCompedRows.map((d) => d.id);
|
|
30
|
+
const memberIds = expiredCompedRows.map((d) => d.member_id);
|
|
31
|
+
// Only members still in the comped status change; anyone already paid,
|
|
32
|
+
// free or deleted is left untouched. The rows are locked for the rest
|
|
33
|
+
// of the transaction so a concurrent status change cannot slip between
|
|
34
|
+
// this read and the update below (forUpdate is a no-op on SQLite,
|
|
35
|
+
// where the single-writer transaction already guarantees this). The
|
|
36
|
+
// members rows are locked BEFORE the members_products delete so this
|
|
37
|
+
// transaction acquires locks in the same members -> members_products
|
|
38
|
+
// order as the member-edit flows (Bookshelf edit, linkSubscription);
|
|
39
|
+
// the reverse order could deadlock against them.
|
|
40
|
+
const compedMembers = await trx('members')
|
|
41
|
+
.whereIn('id', memberIds)
|
|
42
|
+
.andWhere('status', 'comped')
|
|
43
|
+
.forUpdate();
|
|
44
|
+
const deletedCount = await trx('members_products').whereIn('id', rowIds).del();
|
|
45
|
+
const updateMemberIds = compedMembers.map((d) => d.id);
|
|
46
|
+
const updatedAt = new Date();
|
|
47
|
+
const updatedCount = await trx('members').whereIn('id', updateMemberIds).update({
|
|
48
|
+
status: 'free',
|
|
49
|
+
updated_at: updatedAt,
|
|
50
|
+
});
|
|
51
|
+
const statusEvents = compedMembers.map((member) => {
|
|
52
|
+
return {
|
|
53
|
+
id: ObjectId().toHexString(),
|
|
54
|
+
member_id: member.id,
|
|
55
|
+
from_status: member.status,
|
|
56
|
+
to_status: 'free',
|
|
57
|
+
created_at: updatedAt,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
// SQLite >= 3.32.0 can support 32766 host parameters
|
|
61
|
+
// each row uses 5 variables so ⌊32766/5⌋ = 6553
|
|
62
|
+
const chunkSize = 6553;
|
|
63
|
+
for (const chunk of chunkArray(statusEvents, chunkSize)) {
|
|
64
|
+
await trx('members_status_events').insert(chunk);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
deletedSubscriptionCount: deletedCount,
|
|
68
|
+
updatedMemberCount: updatedCount,
|
|
69
|
+
updatedMembers: compedMembers,
|
|
70
|
+
now: updatedAt,
|
|
71
|
+
};
|
|
72
|
+
});
|
|
73
|
+
// Emitted after the commit so listeners never observe state that could
|
|
74
|
+
// still roll back. A failed emission is logged and skipped rather than
|
|
75
|
+
// failing the job: the database changes are already durable and the
|
|
76
|
+
// emitted_event_count below makes the shortfall visible.
|
|
77
|
+
let emittedEventCount = 0;
|
|
78
|
+
for (const member of updatedMembers) {
|
|
79
|
+
const emitted = await emitMemberEditedEvent({ member, now, models, events, logging, sentry });
|
|
80
|
+
if (emitted) {
|
|
81
|
+
emittedEventCount += 1;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
logging.info({
|
|
85
|
+
system: {
|
|
86
|
+
event: 'clean_expired_comped.completed',
|
|
87
|
+
deleted_subscription_count: deletedSubscriptionCount,
|
|
88
|
+
updated_member_count: updatedMemberCount,
|
|
89
|
+
emitted_event_count: emittedEventCount,
|
|
90
|
+
duration_ms: Date.now() - startedAt,
|
|
91
|
+
},
|
|
92
|
+
}, `[Background Job] clean-expired-comped removed ${deletedSubscriptionCount} expired subscriptions and updated ${updatedMemberCount} members`);
|
|
93
|
+
return { deletedSubscriptionCount, updatedMemberCount, emittedEventCount };
|
|
94
|
+
}
|
|
95
|
+
// Emits member.edited in the shape its listeners (webhooks in particular)
|
|
96
|
+
// expect: a freshly loaded model whose _previousAttributes carry the
|
|
97
|
+
// pre-cleanup status and _changed the comped->free transition, with an
|
|
98
|
+
// internal context.
|
|
99
|
+
async function emitMemberEditedEvent({ member, now, models, events, logging, sentry, }) {
|
|
100
|
+
try {
|
|
101
|
+
const model = await models.Member.findOne({ id: member.id }, { require: true, context: { internal: true } });
|
|
102
|
+
model._previousAttributes = normalizeDates({
|
|
103
|
+
...model.attributes,
|
|
104
|
+
status: member.status,
|
|
105
|
+
updated_at: member.updated_at,
|
|
106
|
+
});
|
|
107
|
+
model._changed = normalizeDates({
|
|
108
|
+
status: 'free',
|
|
109
|
+
updated_at: now,
|
|
110
|
+
});
|
|
111
|
+
events.emit('member.edited', model, { context: { internal: true } });
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
catch (err) {
|
|
115
|
+
if (isNotFoundError(err)) {
|
|
116
|
+
logging.warn(`Could not emit member.edited for expired comped member: Member ${member.id} was not found`);
|
|
117
|
+
return false;
|
|
118
|
+
}
|
|
119
|
+
logging.error(err);
|
|
120
|
+
sentry.captureException(err);
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
function normalizeDates(attributes) {
|
|
125
|
+
const normalized = { ...attributes };
|
|
126
|
+
for (const [key, value] of Object.entries(normalized)) {
|
|
127
|
+
if (key.endsWith('_at') && value && !(value instanceof Date)) {
|
|
128
|
+
normalized[key] = moment.utc(value).toDate();
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return normalized;
|
|
132
|
+
}
|
|
133
|
+
function isNotFoundError(err) {
|
|
134
|
+
if (!err || typeof err !== 'object') {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
const errorType = 'errorType' in err ? err.errorType : undefined;
|
|
138
|
+
const name = 'name' in err ? err.name : undefined;
|
|
139
|
+
const message = 'message' in err ? err.message : undefined;
|
|
140
|
+
return (errorType === 'NotFoundError' ||
|
|
141
|
+
name === 'NotFoundError' ||
|
|
142
|
+
message === 'NotFound' ||
|
|
143
|
+
message === 'EmptyResponse');
|
|
144
|
+
}
|
|
145
|
+
exports.default = cleanExpiredComped;
|
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
const path = require('path');
|
|
2
1
|
const logging = require('@tryghost/logging');
|
|
3
|
-
const
|
|
2
|
+
const errors = require('@tryghost/errors');
|
|
4
3
|
const CleanTokensJob = require('./clean-tokens-job').default;
|
|
4
|
+
const CleanExpiredCompedJob = require('./clean-expired-comped-job').default;
|
|
5
|
+
const cleanTokensTask = require('./clean-tokens-task').default;
|
|
6
|
+
const cleanExpiredCompedTask = require('./clean-expired-comped-task').default;
|
|
5
7
|
|
|
6
8
|
let hasScheduled = {
|
|
7
9
|
expiredComped: false,
|
|
8
10
|
tokens: false,
|
|
9
11
|
};
|
|
10
12
|
|
|
13
|
+
let tasks;
|
|
14
|
+
|
|
11
15
|
function alreadyScheduledOrTest(key) {
|
|
12
16
|
return hasScheduled[key] || process.env.NODE_ENV.startsWith('test');
|
|
13
17
|
}
|
|
@@ -19,43 +23,63 @@ function randomDailyCron(maxHour = 24) {
|
|
|
19
23
|
return `${s} ${m} ${h} * * *`;
|
|
20
24
|
}
|
|
21
25
|
|
|
22
|
-
function
|
|
23
|
-
if (
|
|
24
|
-
|
|
26
|
+
function getTasks() {
|
|
27
|
+
if (!tasks) {
|
|
28
|
+
throw new errors.IncorrectUsageError({
|
|
29
|
+
message: 'Member jobs used before init(). Call init() from boot first.',
|
|
30
|
+
});
|
|
25
31
|
}
|
|
32
|
+
return tasks;
|
|
33
|
+
}
|
|
26
34
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
module.exports = {
|
|
36
|
+
// Composition root for the member cleanup tasks. Idempotent because tests
|
|
37
|
+
// may boot more than once per process.
|
|
38
|
+
init() {
|
|
39
|
+
if (tasks) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
30
42
|
|
|
31
|
-
|
|
43
|
+
const db = require('../../../data/db');
|
|
44
|
+
const models = require('../../../models');
|
|
45
|
+
const events = require('../../../lib/common/events');
|
|
46
|
+
const sentry = require('../../../../shared/sentry');
|
|
32
47
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
});
|
|
48
|
+
tasks = {
|
|
49
|
+
cleanTokens: () => cleanTokensTask({ db, logging }),
|
|
50
|
+
cleanExpiredComped: () => cleanExpiredCompedTask({ db, models, events, logging, sentry }),
|
|
51
|
+
};
|
|
52
|
+
},
|
|
39
53
|
|
|
40
|
-
|
|
54
|
+
cleanTokens() {
|
|
55
|
+
return getTasks().cleanTokens();
|
|
56
|
+
},
|
|
41
57
|
|
|
42
|
-
|
|
43
|
-
|
|
58
|
+
cleanExpiredComped() {
|
|
59
|
+
return getTasks().cleanExpiredComped();
|
|
60
|
+
},
|
|
44
61
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
62
|
+
async scheduleExpiredCompCleanupJob(jobsService) {
|
|
63
|
+
if (alreadyScheduledOrTest('expiredComped')) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Keep the legacy off-peak window: a random time between 00:00 and 05:59
|
|
68
|
+
const cron = randomDailyCron(6);
|
|
69
|
+
logging.info(`[Background Job] clean-expired-comped scheduled at ${cron}`);
|
|
70
|
+
await jobsService.scheduleRecurring(new CleanExpiredCompedJob(), { cron });
|
|
71
|
+
|
|
72
|
+
hasScheduled.expiredComped = true;
|
|
48
73
|
},
|
|
49
74
|
|
|
50
|
-
async scheduleTokenCleanupJob() {
|
|
75
|
+
async scheduleTokenCleanupJob(jobsService) {
|
|
51
76
|
if (alreadyScheduledOrTest('tokens')) {
|
|
52
77
|
return;
|
|
53
78
|
}
|
|
54
79
|
|
|
55
|
-
const classBasedJobs = require('../../jobs-service').getInstance();
|
|
56
80
|
const cron = randomDailyCron();
|
|
57
81
|
logging.info(`[Background Job] clean-tokens scheduled at ${cron}`);
|
|
58
|
-
await
|
|
82
|
+
await jobsService.scheduleRecurring(new CleanTokensJob(), { cron });
|
|
59
83
|
|
|
60
84
|
hasScheduled.tokens = true;
|
|
61
85
|
},
|