ghost 6.62.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/tryghost-adapter-base-jobs-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/content/themes/source/package.json +2 -2
- package/content/themes/source/partials/post-card.hbs +1 -1
- package/core/boot.js +7 -5
- package/core/built/admin/assets/{about-Dyae_UT6.js → about-C1C3s5-g.js} +1 -1
- package/core/built/admin/assets/accordion-CxGsDKwa.js +1 -0
- package/core/built/admin/assets/{account-migration-CESa0mnX.js → account-migration-CbUhW6gk.js} +1 -1
- package/core/built/admin/assets/activitypub/{account-migration-By69CgCp.js → account-migration-KA6Jjf9L.js} +7 -7
- package/core/built/admin/assets/activitypub/activitypub.js +3 -3
- 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-BJcPMrDd.js → bluesky-sharing-C7nTDMvw.js} +9 -9
- package/core/built/admin/assets/activitypub/{content-formatters-3rTHg3mp.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-DUR4xSkV.js → domain-fZ-X8i4-.js} +9 -9
- package/core/built/admin/assets/activitypub/{edit-profile-BqtfzxCp.js → edit-profile-BqMLQV88.js} +6 -6
- package/core/built/admin/assets/activitypub/{explore-BxuJd-J4.js → explore-B4yPoP8E.js} +10 -10
- package/core/built/admin/assets/activitypub/{feed-BM1TcksA.js → feed-BRC_mvAe.js} +9 -9
- package/core/built/admin/assets/activitypub/{field-Dn7HHx2-.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-CCjkuow2.js → inbox-BW-yvUoN.js} +10 -10
- package/core/built/admin/assets/activitypub/{inbox-d8oJRTf4.js → inbox-CbTTdDQ3.js} +2 -2
- package/core/built/admin/assets/activitypub/{label-1AA_s9Fa.js → label-kPMmYI7l.js} +3 -3
- package/core/built/admin/assets/activitypub/layout-BL4_jtuV.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-CnmxEB8w.js → moderation-CuidJzc0.js} +10 -10
- package/core/built/admin/assets/activitypub/{note-ZKc_Bnba.js → note-CAMRRSqD.js} +7 -7
- package/core/built/admin/assets/activitypub/{notifications-CB2tJZau.js → notifications-BmNlXGgp.js} +9 -9
- package/core/built/admin/assets/activitypub/{onboarding-BsJ9E5qf.js → onboarding-Bqg7fh_L.js} +2 -2
- package/core/built/admin/assets/activitypub/{onboarding-CSUJJIlh.js → onboarding-Dmun9jr_.js} +2 -2
- package/core/built/admin/assets/activitypub/{preferences-0-JZnUkI.js → preferences--oH19wf9.js} +11 -11
- package/core/built/admin/assets/activitypub/{profile-C35cFIhM.js → profile-DH_tQx2D.js} +14 -14
- package/core/built/admin/assets/activitypub/{reply-DYK5vVqb.js → reply-Cap9PxSH.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-BD4vscyc.js → routes-DdyY9uFF.js} +30 -30
- package/core/built/admin/assets/activitypub/{separator-BbQqTm-p.js → separator-CAxxu8VV.js} +3 -3
- package/core/built/admin/assets/activitypub/{settings-BsMROdqh.js → settings-B6EEcVkW.js} +6 -6
- package/core/built/admin/assets/activitypub/{site-O0RL9YCN.js → site-BYps63oh.js} +2 -2
- package/core/built/admin/assets/activitypub/{step-1-BMSx7c_X.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-CLlOZTyt.js → step-3-C-qBbcUo.js} +11 -11
- package/core/built/admin/assets/activitypub/{tabs-Dxk-b9M-.js → tabs-DLhZtVde.js} +6 -6
- package/core/built/admin/assets/activitypub/{topic-filter-5yZH6tId.js → topic-filter-DsUOuu0k.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-CV50qe03.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-Cbvzgjzd.js → use-reply-chain-data-AJi9zkzm.js} +5 -5
- package/core/built/admin/assets/activitypub/{users-BjcoP_HC.js → users-inweyKL5.js} +186 -160
- package/core/built/admin/assets/activitypub/{x-DbQZOLiI.js → x-DR8AT3-4.js} +2 -2
- package/core/built/admin/assets/{add-integration-modal-BlF6rs_f.js → add-integration-modal-BT_onDJy.js} +1 -1
- package/core/built/admin/assets/{add-newsletter-modal-CglVQqah.js → add-newsletter-modal-Cx1kQpiY.js} +1 -1
- package/core/built/admin/assets/{add-recommendation-modal-wz7BsuaG.js → add-recommendation-modal-B5nvAhvr.js} +1 -1
- package/core/built/admin/assets/{analytics-C4HE1gBg.js → analytics-DjoJWPI7.js} +1 -1
- package/core/built/admin/assets/{announcement-bar-modal-Ci4zz8jO.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-Ck-Yteiv.js → api-keys-BoeacM-s.js} +1 -1
- package/core/built/admin/assets/{audience-CtbA54Sn.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-BEtJ_gL_.js → bluesky-sharing-vzh-oPXL.js} +1 -1
- package/core/built/admin/assets/{breadcrumb-zUrZrBtW.js → breadcrumb-CaLk2b7D.js} +1 -1
- package/core/built/admin/assets/{button-eNMKQWXr.js → button-Dvcv1QSR.js} +1 -1
- package/core/built/admin/assets/calendar-Bl9RLcZX.js +1 -0
- package/core/built/admin/assets/{chart-helpers-CfBKt0vT.js → chart-helpers-FHywU0pj.js} +1 -1
- package/core/built/admin/assets/{checkbox-CgOn_tKW.js → checkbox-BtayFtjg.js} +1 -1
- package/core/built/admin/assets/chunk.254.035b85ec456ebbd7336b.js +440 -0
- package/core/built/admin/assets/{chunk.524.a697dee88db67fdf6021.js → chunk.524.e6975095fccdf22d90f8.js} +5 -5
- package/core/built/admin/assets/{chunk.582.5ed5d124cfca9c259b9c.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-K2wI-I5f.js → code-editor-view-C3ZfoF1X.js} +1 -1
- package/core/built/admin/assets/{code-editor-view-BYNoAoNV.js → code-editor-view-CAyRWy1s.js} +1 -1
- package/core/built/admin/assets/{color-picker-Ddw80xX6.js → color-picker-G4kGR7rp.js} +1 -1
- package/core/built/admin/assets/{color-picker-field-D9nUhvA7.js → color-picker-field-DQFVTNnT.js} +1 -1
- package/core/built/admin/assets/{combobox-w9j8PPQT.js → combobox-BnYzgEB4.js} +1 -1
- package/core/built/admin/assets/{command-BfpHORBv.js → command-C_vyTikI.js} +1 -1
- 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-CBM8G0Vj.js → confirmation-context-DwXX_Y5h.js} +1 -1
- package/core/built/admin/assets/{content-api-modal-TXRz3U7S.js → content-api-modal-D2G0nJP6.js} +1 -1
- package/core/built/admin/assets/{context-menu-_QMdd3kp.js → context-menu-DOVB6yaz.js} +1 -1
- package/core/built/admin/assets/{copy-field-6N01S07E.js → copy-field-DCJwh9qf.js} +1 -1
- package/core/built/admin/assets/{create-ghost-browse-value-source-qIBymvvf.js → create-ghost-browse-value-source-5C53jJaX.js} +1 -1
- package/core/built/admin/assets/custom-field-type-option-BAuW0F-E.js +1 -0
- package/core/built/admin/assets/{custom-integration-modal-DmNquxUV.js → custom-integration-modal-AbJGVyKc.js} +1 -1
- package/core/built/admin/assets/{date-range-select-uvz_DtPh.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-BZ1iytEd.js → dirty-confirm-dialog-DVPuGz3U.js} +1 -1
- package/core/built/admin/assets/{disabled-sources-indicator-DMRrxsiJ.js → disabled-sources-indicator-Cy7Zp4c8.js} +1 -1
- package/core/built/admin/assets/{domain-BNYrt0aD.js → domain-Dl61QGuk.js} +1 -1
- package/core/built/admin/assets/{dropzone-D7nFdAe5.js → dropzone-Dtz5Rmjt.js} +4 -4
- 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-Ds6mPEi5.js → embed-signup-form-modal-DGqsORxS.js} +1 -1
- package/core/built/admin/assets/{explore-UY10Egsp.js → explore-nnRr1Fja.js} +1 -1
- package/core/built/admin/assets/feature-flag-overrides-context-DQjdJFAk.js +1 -0
- package/core/built/admin/assets/{feature-image-placeholder-D1iF_8bc.js → feature-image-placeholder-BrIHmPs8.js} +1 -1
- package/core/built/admin/assets/{feed-81C8mJWR.js → feed-BNUcNu-W.js} +1 -1
- package/core/built/admin/assets/feedback-DJ4Zb9rZ.js +1 -0
- package/core/built/admin/assets/{field-Dihp5p18.js → field-C93_Mg6j.js} +1 -1
- package/core/built/admin/assets/filter-sources-CGJb4C5r.js +1 -0
- package/core/built/admin/assets/{filters-DirqNXjH.js → filters--kKP-EC8.js} +1 -1
- package/core/built/admin/assets/filters-DxA_a4FP.js +1 -0
- package/core/built/admin/assets/{first-promoter-modal-CT6MxYZb.js → first-promoter-modal-BYDtrz2S.js} +1 -1
- package/core/built/admin/assets/focus-koenig-editor-on-bottom-click-BbSnGOtK.js +1 -0
- package/core/built/admin/assets/{ghost-8eabfc1ece81b03cf7b90780d31487e8.css → ghost-8fd5bb799356bbefb254210ba7e1236d.css} +1 -1
- package/core/built/admin/assets/{ghost-c2848508292937415f20f5a830eea59e.js → ghost-b4e8a403baba61905cd7560e30b9732d.js} +43 -27
- package/core/built/admin/assets/{ghost-dark-a7bebe2c561f55369dcf29f4c820f7ff.css → ghost-dark-cac0cb2d5bb78e9bd14d8d1c2768ed45.css} +1 -1
- package/core/built/admin/assets/{gift-link-modal-CQVAye17.js → gift-link-modal-B7u5bD4e.js} +1 -1
- package/core/built/admin/assets/{gift-links-DLDS7ljO.js → gift-links-CfLSnst2.js} +1 -1
- package/core/built/admin/assets/growth-Cd1uO9d2.js +1 -0
- package/core/built/admin/assets/growth-aG32xfdE.js +3 -0
- package/core/built/admin/assets/{history-modal-CFd1Hvx6.js → history-modal-Df8lW8h-.js} +1 -1
- package/core/built/admin/assets/hooks-Cy_-oZ4o.js +2 -0
- package/core/built/admin/assets/{html-field-CJBCLTvv.js → html-field-DA4eU7eq.js} +1 -1
- package/core/built/admin/assets/{images-scfkWZiK.js → images-KXmyO3Ss.js} +1 -1
- package/core/built/admin/assets/{inbox-Mcp4DXZN.js → inbox-gJHG7_xd.js} +6 -6
- 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-CMhe2DvY.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-DfheMXs0.js → invite-user-modal-B4CuNcx4.js} +1 -1
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +2 -2
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +2 -2
- package/core/built/admin/assets/{koenig-lexical-DBSqAuiI.js → koenig-lexical-CGXb0F8o.js} +2 -2
- package/core/built/admin/assets/koenig-loader-BeM5rFPX.js +2 -0
- package/core/built/admin/assets/{kpi-card-DBRMfGOW.js → kpi-card-B21aToe8.js} +1 -1
- package/core/built/admin/assets/{kpi-card-DFafD1qq.js → kpi-card-pz6Wv4Ae.js} +1 -1
- package/core/built/admin/assets/{kpi-helpers-Cv8i8hns.js → kpi-helpers-Dq5jelmP.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-BJItvLYm.js → kpi-tabs-DEZHpKUt.js} +1 -1
- package/core/built/admin/assets/{label-BnSCvffu.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-DDQV6QeG.js → member-custom-fields-TRTHCAz9.js} +1 -1
- 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-AgCIEso4.js → moderation-ZWefD9uH.js} +1 -1
- package/core/built/admin/assets/{multi-select-combobox-BcITkUVr.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-DKzwafuh.js → newsletter-emails-DZH6I-PY.js} +1 -1
- package/core/built/admin/assets/{newsletters-BRBb_uQC.js → newsletters-DOC9KBcX.js} +1 -1
- package/core/built/admin/assets/newsletters-Rq1M3GD-.js +1 -0
- package/core/built/admin/assets/{note-DyGHtS9a.js → note-DIV-Pojr.js} +1 -1
- package/core/built/admin/assets/{notifications-ZgWniKna.js → notifications-s20-H49V.js} +1 -1
- package/core/built/admin/assets/{offers-9J1CSiLy.js → offers--e2nGvkW.js} +1 -1
- package/core/built/admin/assets/{offers-container-modal-B-E8TIWr.js → offers-container-modal-BmG5M5Tv.js} +1 -1
- package/core/built/admin/assets/onboarding-CUuy9unR.js +1 -0
- package/core/built/admin/assets/{onboarding-CzIsre-d.js → onboarding-pXyct0Jm.js} +1 -1
- package/core/built/admin/assets/{onboarding-route-CaavZulg.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-De0VzKBi.js → page-header-DM_KyoFI.js} +1 -1
- package/core/built/admin/assets/{pagemenu-B5KKCpbA.js → pagemenu-vgZVUVKR.js} +1 -1
- 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/{pintura-modal-BZ2J9w3n.js → pintura-modal-DExCF1ii.js} +1 -1
- package/core/built/admin/assets/{portal-frame-B6Hr9wHc.js → portal-frame-Cqanidq4.js} +1 -1
- 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-C5YJLp74.js → preferences--vNubsqD.js} +2 -2
- package/core/built/admin/assets/{preview-modal-BfeFqi5y.js → preview-modal-YYbzlYdO.js} +1 -1
- package/core/built/admin/assets/profile-BllKV3dR.js +1 -0
- package/core/built/admin/assets/radio-group-DL8GI8rt.js +1 -0
- 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-cb0t_TAJ.js → select-v5YbA3Mw.js} +1 -1
- package/core/built/admin/assets/settings-PEsZupw1.js +4 -0
- package/core/built/admin/assets/{settings-breadcrumbs-DLzbRRSt.js → settings-breadcrumbs-NwPnCTC7.js} +1 -1
- package/core/built/admin/assets/{settings-DZqxR0Da.js → settings-u_s1CRkr.js} +1 -1
- package/core/built/admin/assets/{share-modal-D2-Bz1_I.js → share-modal-SskCmVGL.js} +1 -1
- package/core/built/admin/assets/{slack-modal-DYDTUria.js → slack-modal-XxSJiYgo.js} +1 -1
- package/core/built/admin/assets/{sort-button-VVeQjjbu.js → sort-button-Cx1KtINE.js} +1 -1
- package/core/built/admin/assets/{source-icon-Ck5_5Kgq.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-CsaBjeN4.js → step-1-BQYzvBzr.js} +1 -1
- package/core/built/admin/assets/{step-2-D2vnxOOM.js → step-2-xES32xJV.js} +1 -1
- package/core/built/admin/assets/{step-3-wBoLdGEb.js → step-3-DA_XK5kW.js} +2 -2
- package/core/built/admin/assets/{stripe-button-CDgLz0S0.js → stripe-button-CVsLHelt.js} +1 -1
- package/core/built/admin/assets/{stripe-connect-modal-CSjMJetP.js → stripe-connect-modal-ClmLOMGS.js} +1 -1
- package/core/built/admin/assets/style-Wd9nQubI.css +1 -0
- package/core/built/admin/assets/{table-Do6iQ83Q.js → table-BenqVupI.js} +1 -1
- package/core/built/admin/assets/{tabs-D2yWLMp_.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-CtnZBJKl.js → testimonials-modal-9Q6fT06V.js} +1 -1
- 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-ByUJsxwD.js → toggle-group-CviIkyok.js} +1 -1
- package/core/built/admin/assets/{topic-filter-BgC3g5_v.js → topic-filter-I2jCBGxm.js} +1 -1
- package/core/built/admin/assets/{transistor-modal-Drr0SNfo.js → transistor-modal-p53u3W8y.js} +1 -1
- package/core/built/admin/assets/{trend-badge-6ye93YAW.js → trend-badge-CideVzQd.js} +1 -1
- package/core/built/admin/assets/{unsplash-modal-p6AoQoWp.js → unsplash-modal-DY8UcKcE.js} +1 -1
- package/core/built/admin/assets/{use-analytics-data-CWCMAESo.js → use-analytics-data-DAIGcr2f.js} +1 -1
- package/core/built/admin/assets/{use-check-theme-limit-error-Cw3WQHy8.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-DCVG3s9w.js → use-growth-stats-937CUj6j.js} +1 -1
- package/core/built/admin/assets/use-host-limits-D0Dv3tKA.js +1 -0
- package/core/built/admin/assets/{use-limiter-D2WRRx0H.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-BfN1UwzH.js → use-pintura-editor-CaV1mbfO.js} +1 -1
- package/core/built/admin/assets/{use-post-referrers-Czg47m8s.js → use-post-referrers-BCQI7fDg.js} +1 -1
- package/core/built/admin/assets/{use-reply-chain-data-ns0caK8k.js → use-reply-chain-data-CYRhoaxY.js} +1 -1
- package/core/built/admin/assets/{use-router-history-entry-uldn7zJO.js → use-router-history-entry-NOKcd--l.js} +1 -1
- package/core/built/admin/assets/{use-setting-group-DpcuCUDg.js → use-setting-group-bLKMwoBT.js} +1 -1
- package/core/built/admin/assets/{use-settings-navigation-CZjGrhsp.js → use-settings-navigation-B-qrriKD.js} +1 -1
- package/core/built/admin/assets/{use-simple-pagination-DzJWobPR.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-CSnGXzZh.js → use-tinybird-query-CmdbCS9X.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-DTOKRR7e.js → use-tinybird-token-KLne235s.js} +1 -1
- package/core/built/admin/assets/{use-unsaved-changes-guard-CBzs-KPi.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-DFr8lCbS.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-Cg-OeGzC.js → users-DVNWrqg9.js} +1 -1
- package/core/built/admin/assets/{vendor-5753232c7d6ffb3c44e1eb0a02133953.js → vendor-2dd263291938d407802568d3357ec69e.js} +19 -17
- package/core/built/admin/assets/{virtual-list-BW-padMe.js → virtual-list-CZ3RK2Yf.js} +1 -1
- package/core/built/admin/assets/{web-C1v33ehA.js → web-ClEJsCAj.js} +2 -2
- package/core/built/admin/assets/web-X9rdnrtq.js +1 -0
- package/core/built/admin/assets/{zapier-modal-BBFfI6Ml.js → zapier-modal-DfVhV17j.js} +1 -1
- package/core/built/admin/index.html +19 -18
- 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 +8 -0
- package/core/server/api/endpoints/member-metafields-members.js +23 -0
- package/core/server/api/endpoints/member-metafields.js +33 -20
- package/core/server/api/endpoints/members-account.js +42 -0
- 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/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 +16 -2
- 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 +4 -0
- package/core/server/api/endpoints/utils/serializers/output/member-metafields.js +3 -3
- 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/tiers.js +10 -2
- package/core/server/api/endpoints/utils/serializers/output/users.js +0 -10
- package/core/server/data/exporter/table-lists.js +3 -3
- 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/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 +6 -6
- package/core/server/models/user.js +1 -1
- 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/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 -0
- package/core/server/services/email-service/email-controller.js +18 -2
- package/core/server/services/email-service/email-service-wrapper.js +3 -0
- 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/gift-service.js +19 -57
- package/core/server/services/gifts/index.js +0 -2
- 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 +13 -2
- package/core/server/services/members/account-service.js +98 -0
- package/core/server/services/members/api.js +4 -4
- package/core/server/services/members/import-export/export/exporter.js +20 -20
- package/core/server/services/members/import-export/import/completion-email.js +6 -6
- package/core/server/services/members/import-export/import/importer.js +13 -13
- package/core/server/services/members/import-export/index.js +7 -7
- package/core/server/services/members/members-api/members-api.js +26 -6
- 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 +45 -39
- 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 +70 -42
- 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-custom-fields → members-metafields}/codec.js +4 -4
- 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 +17 -23
- package/core/server/services/{members-custom-fields → members-metafields}/filter.js +9 -9
- package/core/server/services/{members-custom-fields → members-metafields}/index.js +16 -7
- package/core/server/services/{members-custom-fields → members-metafields}/key.js +1 -1
- package/core/server/services/{members-custom-fields → members-metafields}/models.js +4 -4
- 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-custom-fields → members-metafields}/serializers.js +6 -6
- package/core/server/services/{members-custom-fields → members-metafields}/values-service.js +32 -22
- package/core/server/services/mentions/mention-sending-service.js +26 -9
- package/core/server/services/mentions/send-webmentions-job.js +18 -0
- package/core/server/services/mentions/service.js +1 -31
- package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +2 -2
- package/core/server/services/stripe/stripe-service.js +4 -4
- package/core/server/services/tier-checkout-config/codec.js +2 -2
- package/core/server/services/tier-checkout-config/index.js +2 -1
- package/core/server/services/tier-checkout-config/models.js +2 -2
- package/core/server/services/tier-checkout-config/queries.js +8 -8
- package/core/server/services/tier-checkout-config/serializers.js +57 -0
- package/core/server/services/tier-checkout-config/service.js +1 -1
- package/core/server/services/tiers/service.js +3 -3
- package/core/server/services/url/router-filter.js +8 -10
- package/core/server/services/users.js +1 -19
- package/core/server/web/api/endpoints/admin/routes.js +1 -0
- 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/labs.js +4 -2
- package/package.json +22 -22
- package/pnpm-lock.yaml +513 -571
- package/pnpm-workspace.yaml +12 -12
- package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/core/built/admin/assets/activitypub/layout-2-0bw6HN.js +0 -1
- package/core/built/admin/assets/automated-emails-D5VVMND9.js +0 -1
- package/core/built/admin/assets/automations-CzKt5fb8.js +0 -1
- package/core/built/admin/assets/chunk.598.7e056fabb47ce7dbd73f.js +0 -366
- package/core/built/admin/assets/comments-BLmCmyCs.js +0 -1
- package/core/built/admin/assets/custom-field-type-option-BddFlJFa.js +0 -1
- package/core/built/admin/assets/design-and-theme-modal-BSmo12Op.js +0 -2
- package/core/built/admin/assets/edit-profile-C5Asm4LA.js +0 -1
- package/core/built/admin/assets/editor-REokrzci.js +0 -8
- package/core/built/admin/assets/filter-sources-BO9xUPj7.js +0 -1
- package/core/built/admin/assets/filters-CRUWecfY.js +0 -1
- package/core/built/admin/assets/growth-BWIu-6xj.js +0 -3
- package/core/built/admin/assets/growth-DU29oxeA.js +0 -1
- package/core/built/admin/assets/hooks-CuEZV2-q.js +0 -2
- package/core/built/admin/assets/index-6MkPdCtr.js +0 -4
- package/core/built/admin/assets/index-Bn7QhcZu.css +0 -2
- package/core/built/admin/assets/index.es-CNnuNH0d.js +0 -1
- package/core/built/admin/assets/koenig-loader-aFQNlJlD.js +0 -2
- package/core/built/admin/assets/label-picker-KVoo91sh.js +0 -1
- package/core/built/admin/assets/layout-Bjv2Ll-5.js +0 -1
- package/core/built/admin/assets/link-helpers-MVDdnFCY.js +0 -1
- package/core/built/admin/assets/member-detail-CdJF6Agw.js +0 -1
- package/core/built/admin/assets/members-DjQbq9Z2.js +0 -4
- package/core/built/admin/assets/navigation-modal-BkT7b1M2.js +0 -1
- package/core/built/admin/assets/newsletter-BMMAuvgq.js +0 -1
- package/core/built/admin/assets/newsletter-detail-modal-0SCJ7Zpm.js +0 -1
- package/core/built/admin/assets/newsletters-DhNigJUh.js +0 -1
- package/core/built/admin/assets/onboarding-qg8Akovm.js +0 -1
- package/core/built/admin/assets/overview-BW8GLeYi.js +0 -1
- package/core/built/admin/assets/overview-BkXZNzaZ.js +0 -1
- package/core/built/admin/assets/pages-jgaFm69d.js +0 -1
- package/core/built/admin/assets/pages-route-CIUAQxtY.js +0 -1
- package/core/built/admin/assets/portal-modal-D4ceei3t.js +0 -1
- package/core/built/admin/assets/post-analytics-header-BYCsT0GP.js +0 -1
- package/core/built/admin/assets/post-analytics-ot7f6pih.js +0 -1
- package/core/built/admin/assets/post-analytics-provider-Bo1gmYaP.js +0 -1
- package/core/built/admin/assets/post-helpers-C5OQMBoi.js +0 -1
- package/core/built/admin/assets/post-share-modal-C-PaYZ_e.js +0 -1
- package/core/built/admin/assets/posts-DuGazgTb.js +0 -1
- package/core/built/admin/assets/posts-list-screen-CBGGgjjj.js +0 -2
- package/core/built/admin/assets/posts-route-D6xmS7Vf.js +0 -1
- package/core/built/admin/assets/profile-B8mx2yAu.js +0 -1
- package/core/built/admin/assets/radio-group-DczlLKOn.js +0 -1
- package/core/built/admin/assets/router-provider-B0gZyiGJ.js +0 -1
- package/core/built/admin/assets/schemas-DppqmpeJ.js +0 -64
- package/core/built/admin/assets/settings-DvfTNdHQ.js +0 -4
- package/core/built/admin/assets/stats-Rjmk5owg.js +0 -1
- package/core/built/admin/assets/stats-view-BGGmkmgv.js +0 -1
- package/core/built/admin/assets/tag-detail-BhXlOsCa.js +0 -2
- package/core/built/admin/assets/tags-ldukRApe.js +0 -1
- package/core/built/admin/assets/tier-detail-modal-BOs8e_G8.js +0 -1
- package/core/built/admin/assets/tiers-Nd_z4BZY.js +0 -1
- package/core/built/admin/assets/use-filter-params-BIni9taJ.js +0 -1
- package/core/built/admin/assets/use-host-limits-DZtR08oa.js +0 -1
- package/core/built/admin/assets/use-pintura-config-h-J0Lvta.js +0 -1
- package/core/built/admin/assets/use-staff-users-Csipb-O0.js +0 -1
- package/core/built/admin/assets/use-upgrade-route-DhRSo7at.js +0 -1
- package/core/built/admin/assets/user-detail-modal-B_iUClI6.js +0 -1
- package/core/built/admin/assets/web-C4BKzn2T.js +0 -1
- package/core/server/services/email-suppression-list/in-memory-email-suppression-list.js +0 -39
- package/core/server/services/members-custom-fields/bindings-service.js +0 -120
- /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
|
@@ -2,19 +2,23 @@ TOKEN "axis" READ
|
|
|
2
2
|
|
|
3
3
|
NODE mv_hits_0
|
|
4
4
|
SQL >
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
SELECT
|
|
6
|
+
timestamp,
|
|
7
7
|
inserted_at,
|
|
8
8
|
-- payload.meta.received_timestamp may be missing or null
|
|
9
|
-
-- Nullable fields incur performance penalty in clickhouse, so we default to zero (unix epoch)
|
|
10
|
-
|
|
9
|
+
-- Nullable fields incur performance penalty in clickhouse, so we default to zero (unix epoch)
|
|
10
|
+
-- instead of null.
|
|
11
|
+
parseDateTime64BestEffortOrZero(
|
|
12
|
+
JSONExtractString(payload, 'meta', 'received_timestamp'), 3
|
|
13
|
+
) as received_at,
|
|
11
14
|
action,
|
|
12
15
|
version,
|
|
13
16
|
coalesce(session_id, '0') as session_id,
|
|
14
17
|
JSONExtractString(payload, 'locale') as locale,
|
|
15
18
|
JSONExtractString(payload, 'location') as location,
|
|
16
19
|
case
|
|
17
|
-
when JSONExtractString(payload, 'referrerSource') = ''
|
|
20
|
+
when JSONExtractString(payload, 'referrerSource') = ''
|
|
21
|
+
then JSONExtractString(payload, 'meta', 'referrerSource')
|
|
18
22
|
else JSONExtractString(payload, 'referrerSource')
|
|
19
23
|
end as referrer,
|
|
20
24
|
JSONExtractString(payload, 'pathname') as pathname,
|
|
@@ -35,11 +39,8 @@ SQL >
|
|
|
35
39
|
FROM analytics_events
|
|
36
40
|
where action = 'page_hit'
|
|
37
41
|
|
|
38
|
-
|
|
39
|
-
|
|
40
42
|
NODE mv_hits_1
|
|
41
43
|
SQL >
|
|
42
|
-
|
|
43
44
|
SELECT
|
|
44
45
|
site_uuid,
|
|
45
46
|
timestamp,
|
|
@@ -47,8 +48,10 @@ SQL >
|
|
|
47
48
|
inserted_at,
|
|
48
49
|
case
|
|
49
50
|
-- set to -1 if received_at is the unix epoch, or if received_at is after inserted_at
|
|
50
|
-
when toUnixTimestamp(received_at) = 0
|
|
51
|
-
|
|
51
|
+
when toUnixTimestamp(received_at) = 0
|
|
52
|
+
then -1
|
|
53
|
+
when received_at > inserted_at
|
|
54
|
+
then -1
|
|
52
55
|
else date_diff('millisecond', received_at, inserted_at)
|
|
53
56
|
end as ingestion_latency_ms,
|
|
54
57
|
action,
|
|
@@ -61,58 +64,91 @@ SQL >
|
|
|
61
64
|
gift_link,
|
|
62
65
|
location,
|
|
63
66
|
case
|
|
64
|
-
when referrer = ''
|
|
67
|
+
when referrer = ''
|
|
68
|
+
then ''
|
|
65
69
|
-- Social Media Consolidation
|
|
66
|
-
when
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
70
|
+
when
|
|
71
|
+
referrer IN (
|
|
72
|
+
'Facebook',
|
|
73
|
+
'www.facebook.com',
|
|
74
|
+
'l.facebook.com',
|
|
75
|
+
'lm.facebook.com',
|
|
76
|
+
'm.facebook.com',
|
|
77
|
+
'facebook'
|
|
78
|
+
)
|
|
79
|
+
then 'Facebook'
|
|
80
|
+
when referrer IN ('Twitter', 'x.com', 'com.twitter.android')
|
|
81
|
+
then 'Twitter'
|
|
82
|
+
when referrer IN ('go.bsky.app', 'bsky', 'bsky.app')
|
|
83
|
+
then 'Bluesky'
|
|
84
|
+
when referrer IN ('Instagram', 'www.instagram.com')
|
|
85
|
+
then 'Instagram'
|
|
86
|
+
when referrer IN ('LinkedIn', 'LINKEDIN_COMPANY')
|
|
87
|
+
then 'LinkedIn'
|
|
88
|
+
when referrer IN ('l.threads.com')
|
|
89
|
+
then 'Threads'
|
|
73
90
|
-- Reddit Ecosystem
|
|
74
|
-
when
|
|
75
|
-
|
|
91
|
+
when
|
|
92
|
+
referrer
|
|
93
|
+
IN ('www.reddit.com', 'out.reddit.com', 'old.reddit.com', 'com.reddit.frontpage')
|
|
94
|
+
then 'Reddit'
|
|
76
95
|
-- Search Engines (keep distinctions)
|
|
77
|
-
when referrer IN ('search.brave.com')
|
|
78
|
-
|
|
79
|
-
|
|
96
|
+
when referrer IN ('search.brave.com')
|
|
97
|
+
then 'Brave Search'
|
|
98
|
+
when referrer IN ('www.ecosia.org')
|
|
99
|
+
then 'Ecosia'
|
|
80
100
|
-- Email Services
|
|
81
|
-
when referrer IN ('Gmail', 'com.google.android.gm', 'mail.google.com')
|
|
82
|
-
|
|
83
|
-
when referrer IN ('
|
|
84
|
-
|
|
85
|
-
|
|
101
|
+
when referrer IN ('Gmail', 'com.google.android.gm', 'mail.google.com')
|
|
102
|
+
then 'Gmail'
|
|
103
|
+
when referrer IN ('Outlook.com')
|
|
104
|
+
then 'Outlook'
|
|
105
|
+
when referrer IN ('Yahoo!', 'www.yahoo.com', 'Yahoo! Mail', 'r.search.yahoo.com')
|
|
106
|
+
then 'Yahoo!'
|
|
107
|
+
when referrer IN ('AOL Mail')
|
|
108
|
+
then 'AOL Mail'
|
|
86
109
|
-- Content Platforms
|
|
87
|
-
when referrer IN ('flipboard', 'flipboard.com', 'flipboard.app')
|
|
88
|
-
|
|
89
|
-
when referrer IN ('
|
|
90
|
-
|
|
91
|
-
when referrer IN ('
|
|
92
|
-
|
|
93
|
-
|
|
110
|
+
when referrer IN ('flipboard', 'flipboard.com', 'flipboard.app')
|
|
111
|
+
then 'Flipboard'
|
|
112
|
+
when referrer IN ('substack', 'substack.com')
|
|
113
|
+
then 'Substack'
|
|
114
|
+
when referrer IN ('Ghost.org', 'ghost.org')
|
|
115
|
+
then 'Ghost'
|
|
116
|
+
when referrer IN ('buffer')
|
|
117
|
+
then 'Buffer'
|
|
118
|
+
when referrer IN ('Taboola')
|
|
119
|
+
then 'Taboola'
|
|
120
|
+
when referrer IN ('AppNexus')
|
|
121
|
+
then 'AppNexus'
|
|
94
122
|
-- Wikipedia
|
|
95
|
-
when referrer IN ('en.wikipedia.org', 'en.m.wikipedia.org')
|
|
96
|
-
|
|
123
|
+
when referrer IN ('en.wikipedia.org', 'en.m.wikipedia.org')
|
|
124
|
+
then 'Wikipedia'
|
|
97
125
|
-- Mastodon Network
|
|
98
|
-
when
|
|
99
|
-
|
|
126
|
+
when
|
|
127
|
+
referrer IN (
|
|
128
|
+
'mastodon.social',
|
|
129
|
+
'mastodon.online',
|
|
130
|
+
'org.joinmastodon.android',
|
|
131
|
+
'phanpy.social',
|
|
132
|
+
'dev.phanpy.social'
|
|
133
|
+
)
|
|
134
|
+
then 'Mastodon'
|
|
100
135
|
-- News Aggregators
|
|
101
|
-
when referrer IN ('www.memeorandum.com', 'memeorandum.com')
|
|
102
|
-
|
|
103
|
-
when referrer IN ('
|
|
104
|
-
|
|
105
|
-
|
|
136
|
+
when referrer IN ('www.memeorandum.com', 'memeorandum.com')
|
|
137
|
+
then 'Memeorandum'
|
|
138
|
+
when referrer IN ('ground.news')
|
|
139
|
+
then 'Ground News'
|
|
140
|
+
when referrer IN ('apple.news')
|
|
141
|
+
then 'Apple News'
|
|
142
|
+
when referrer IN ('www.smartnews.com')
|
|
143
|
+
then 'SmartNews'
|
|
106
144
|
-- Keep other sources as-is
|
|
107
|
-
when domainWithoutWWW(referrer) != ''
|
|
145
|
+
when domainWithoutWWW(referrer) != ''
|
|
146
|
+
then domainWithoutWWW(referrer)
|
|
108
147
|
else referrer
|
|
109
148
|
end as source,
|
|
110
149
|
pathname,
|
|
111
150
|
href,
|
|
112
|
-
case
|
|
113
|
-
when device = '' then 'unknown'
|
|
114
|
-
else device
|
|
115
|
-
end as device,
|
|
151
|
+
case when device = '' then 'unknown' else device end as device,
|
|
116
152
|
case
|
|
117
153
|
when match(user_agent, 'windows')
|
|
118
154
|
then 'windows'
|
|
@@ -146,5 +182,5 @@ SQL >
|
|
|
146
182
|
utm_content
|
|
147
183
|
FROM mv_hits_0
|
|
148
184
|
|
|
149
|
-
TYPE
|
|
185
|
+
TYPE MATERIALIZED
|
|
150
186
|
DATASOURCE _mv_hits
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DbCount = void 0;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const databaseCountInput = zod_1.z.union([zod_1.z.number(), zod_1.z.string().regex(/^\d+$/)]);
|
|
6
|
+
/**
|
|
7
|
+
* A non-negative integer count, whichever database driver handed it back.
|
|
8
|
+
*
|
|
9
|
+
* Aggregate functions such as COUNT can return a decimal string from MySQL and
|
|
10
|
+
* a number from SQLite. Every read normalises to a safe integer so callers do
|
|
11
|
+
* not need to handle that driver difference themselves.
|
|
12
|
+
*/
|
|
13
|
+
exports.DbCount = zod_1.z.codec(databaseCountInput, zod_1.z.number().int().nonnegative().max(Number.MAX_SAFE_INTEGER), {
|
|
14
|
+
decode: (stored) => (typeof stored === 'string' ? Number(stored) : stored),
|
|
15
|
+
encode: (value) => value,
|
|
16
|
+
});
|
|
@@ -1,131 +1,92 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
|
|
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.validatePassword = validatePassword;
|
|
7
|
+
const tpl_1 = __importDefault(require("@tryghost/tpl"));
|
|
8
|
+
// @ts-expect-error This module lacks type definitions.
|
|
9
|
+
const validator_1 = __importDefault(require("@tryghost/validator"));
|
|
10
|
+
// @ts-expect-error This module lacks type definitions.
|
|
11
|
+
const settings_cache_1 = __importDefault(require("../../shared/settings-cache"));
|
|
12
|
+
const url_utils_1 = __importDefault(require("../../shared/url-utils"));
|
|
9
13
|
const messages = {
|
|
10
|
-
|
|
11
|
-
|
|
14
|
+
passwordTooShort: 'Your password must be at least {minLength} characters long.',
|
|
15
|
+
passwordTooLong: 'Your password is too long.',
|
|
16
|
+
passwordDoesNotComplySecurity: 'Sorry, you cannot use an insecure password.',
|
|
12
17
|
};
|
|
13
|
-
|
|
14
18
|
/**
|
|
15
19
|
* Counts repeated characters in a string. When 50% or more characters are the same,
|
|
16
20
|
* we return false and therefore invalidate the string.
|
|
17
|
-
* @param {string} stringToTest The password string to check.
|
|
18
|
-
* @return {boolean}
|
|
19
21
|
*/
|
|
20
|
-
function
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
if (!chars[char]) {
|
|
31
|
-
chars[char] = 1;
|
|
32
|
-
} else {
|
|
33
|
-
chars[char] += 1;
|
|
34
|
-
}
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
// check if any of the accumulated chars exceed the allowed occurancy
|
|
38
|
-
// of 50% of the words' length.
|
|
39
|
-
_.forIn(chars, function (charCount) {
|
|
40
|
-
if (charCount >= allowedOccurancy) {
|
|
41
|
-
valid = false;
|
|
22
|
+
function characterOccurrence(stringToTest) {
|
|
23
|
+
const limit = stringToTest.length / 2;
|
|
24
|
+
const counts = new Map();
|
|
25
|
+
for (let i = 0; i < stringToTest.length; i++) {
|
|
26
|
+
const char = stringToTest[i];
|
|
27
|
+
const count = (counts.get(char) || 0) + 1;
|
|
28
|
+
if (count >= limit) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
counts.set(char, count);
|
|
42
32
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
return valid;
|
|
33
|
+
return true;
|
|
46
34
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
'
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
'
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
35
|
+
/** Validation against simple password rules. */
|
|
36
|
+
function validatePassword(password, email, siteTitle) {
|
|
37
|
+
// password cannot be longer than 256 code units, for performance
|
|
38
|
+
if (password.length > 256) {
|
|
39
|
+
return { isValid: false, message: (0, tpl_1.default)(messages.passwordTooLong) };
|
|
40
|
+
}
|
|
41
|
+
// password must be longer than 10 characters
|
|
42
|
+
if (!validator_1.default.isLength(password, 10)) {
|
|
43
|
+
return { isValid: false, message: (0, tpl_1.default)(messages.passwordTooShort, { minLength: 10 }) };
|
|
44
|
+
}
|
|
45
|
+
const invalidValidationResult = {
|
|
46
|
+
isValid: false,
|
|
47
|
+
message: (0, tpl_1.default)(messages.passwordDoesNotComplySecurity),
|
|
48
|
+
};
|
|
49
|
+
// password must not match with users' email
|
|
50
|
+
if (email && email.toLowerCase() === password.toLowerCase()) {
|
|
51
|
+
return invalidValidationResult;
|
|
52
|
+
}
|
|
53
|
+
// password must not match with site title
|
|
54
|
+
siteTitle = siteTitle || settings_cache_1.default.get('title');
|
|
55
|
+
if (siteTitle && siteTitle.toLowerCase() === password.toLowerCase()) {
|
|
56
|
+
return invalidValidationResult;
|
|
57
|
+
}
|
|
58
|
+
// password must not match with site URL (without protocol, with or without trailing slash)
|
|
59
|
+
let siteUrl = url_utils_1.default.urlFor('home', true);
|
|
60
|
+
siteUrl = siteUrl.replace(/^http(s?):\/\//, '');
|
|
61
|
+
if (siteUrl &&
|
|
62
|
+
(siteUrl.toLowerCase() === password.toLowerCase() ||
|
|
63
|
+
siteUrl.toLowerCase().replace(/\/$/, '') === password.toLowerCase())) {
|
|
64
|
+
return invalidValidationResult;
|
|
65
|
+
}
|
|
66
|
+
// disallow password from badPasswords list (e. g. '1234567890')
|
|
67
|
+
const badPasswords = [
|
|
68
|
+
'1234567890',
|
|
69
|
+
'qwertyuiop',
|
|
70
|
+
'qwertzuiop',
|
|
71
|
+
'asdfghjkl;',
|
|
72
|
+
'abcdefghij',
|
|
73
|
+
'0987654321',
|
|
74
|
+
'1q2w3e4r5t',
|
|
75
|
+
'12345asdfg',
|
|
76
|
+
];
|
|
77
|
+
if (badPasswords.includes(password)) {
|
|
78
|
+
return invalidValidationResult;
|
|
79
|
+
}
|
|
80
|
+
// password must not contain the words 'ghost', 'password', or 'passw0rd'
|
|
81
|
+
const disallowedPasswords = ['password', 'ghost', 'passw0rd'];
|
|
82
|
+
for (const disallowedPassword of disallowedPasswords) {
|
|
83
|
+
if (password.toLowerCase().includes(disallowedPassword)) {
|
|
84
|
+
return invalidValidationResult;
|
|
85
|
+
}
|
|
89
86
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
if (email && email.toLowerCase() === password.toLowerCase()) {
|
|
94
|
-
validationResult.isValid = false;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// password must not contain the words 'ghost', 'password', or 'passw0rd'
|
|
98
|
-
_.each(disallowedPasswords, function (disallowedPassword) {
|
|
99
|
-
if (password.toLowerCase().indexOf(disallowedPassword) >= 0) {
|
|
100
|
-
validationResult.isValid = false;
|
|
87
|
+
// dissallow passwords where 50% or more of characters are the same
|
|
88
|
+
if (!characterOccurrence(password)) {
|
|
89
|
+
return invalidValidationResult;
|
|
101
90
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
// password must not match with blog title
|
|
105
|
-
if (blogTitle && blogTitle.toLowerCase() === password.toLowerCase()) {
|
|
106
|
-
validationResult.isValid = false;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// password must not match with blog URL (without protocol, with or without trailing slash)
|
|
110
|
-
if (
|
|
111
|
-
blogUrl &&
|
|
112
|
-
(blogUrl.toLowerCase() === password.toLowerCase() ||
|
|
113
|
-
blogUrl.toLowerCase().replace(/\/$/, '') === password.toLowerCase())
|
|
114
|
-
) {
|
|
115
|
-
validationResult.isValid = false;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
// dissallow passwords where 50% or more of characters are the same
|
|
119
|
-
if (!characterOccurance(password)) {
|
|
120
|
-
validationResult.isValid = false;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// Generic error message for the rules where no dedicated error massage is set
|
|
124
|
-
if (!validationResult.isValid && !validationResult.message) {
|
|
125
|
-
validationResult.message = tpl(messages.passwordDoesNotComplySecurity);
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
return validationResult;
|
|
91
|
+
return { isValid: true };
|
|
129
92
|
}
|
|
130
|
-
|
|
131
|
-
module.exports = validatePassword;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
const ghostBookshelf = require('./base');
|
|
2
2
|
|
|
3
|
-
// Member
|
|
3
|
+
// Member metafields are owned by a raw-knex service rather than the Bookshelf
|
|
4
4
|
// registry. Actions still need a read model for `include=resource` to recognise
|
|
5
5
|
// their polymorphic resource type and load the current field definition.
|
|
6
|
-
const
|
|
7
|
-
tableName: '
|
|
6
|
+
const MemberMetafieldResource = ghostBookshelf.Model.extend({
|
|
7
|
+
tableName: 'members_metafields',
|
|
8
8
|
});
|
|
9
9
|
|
|
10
10
|
const Action = ghostBookshelf.Model.extend(
|
|
@@ -26,7 +26,7 @@ const Action = ghostBookshelf.Model.extend(
|
|
|
26
26
|
candidates.push([User, 'security_action']);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
-
candidates.push([
|
|
29
|
+
candidates.push([MemberMetafieldResource, 'member_custom_field']);
|
|
30
30
|
|
|
31
31
|
return candidates;
|
|
32
32
|
},
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
const _ = require('lodash');
|
|
2
|
+
const { hasPosts } = require('@tryghost/bookshelf-plugins');
|
|
3
|
+
|
|
1
4
|
/**
|
|
2
5
|
* @param {import('bookshelf')} Bookshelf
|
|
3
6
|
*/
|
|
@@ -17,6 +20,19 @@ module.exports = function (Bookshelf) {
|
|
|
17
20
|
// Apply model-specific search behavior
|
|
18
21
|
filteredCollection.applySearchQuery(options);
|
|
19
22
|
|
|
23
|
+
// Authors are hidden unless they have posts. Apply that constraint before
|
|
24
|
+
// fetchPage clones its count query so pagination cannot reveal hidden staff.
|
|
25
|
+
// Keep this author-specific: tags have separate, established count semantics.
|
|
26
|
+
if (filteredCollection.shouldHavePosts?.joinTable === 'posts_authors') {
|
|
27
|
+
filteredCollection.query(
|
|
28
|
+
hasPosts.addHasPostsWhere(
|
|
29
|
+
_.result(filteredCollection, 'tableName'),
|
|
30
|
+
filteredCollection.shouldHavePosts,
|
|
31
|
+
),
|
|
32
|
+
);
|
|
33
|
+
filteredCollection.shouldHavePosts = null;
|
|
34
|
+
}
|
|
35
|
+
|
|
20
36
|
return filteredCollection;
|
|
21
37
|
},
|
|
22
38
|
|
|
@@ -5,9 +5,9 @@ const { chainTransformers } = require('@tryghost/mongo-utils');
|
|
|
5
5
|
const config = require('../../shared/config');
|
|
6
6
|
const { MemberCommentingCodec } = require('../services/members/commenting');
|
|
7
7
|
const {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
} = require('../services/members-
|
|
8
|
+
METAFIELDS_RELATION,
|
|
9
|
+
createMetafieldsFilterTransformer,
|
|
10
|
+
} = require('../services/members-metafields/filter');
|
|
11
11
|
|
|
12
12
|
const DEEP_OFFSET_THRESHOLD = 1000;
|
|
13
13
|
|
|
@@ -195,8 +195,8 @@ const Member = ghostBookshelf.Model.extend(
|
|
|
195
195
|
// getFilteredCollectionQuery. Chaining the metafield filter transformer here covers them
|
|
196
196
|
// all without any call site wiring it.
|
|
197
197
|
applyDefaultAndCustomFilters(options) {
|
|
198
|
-
if (options.filter && options.filter.includes(`${
|
|
199
|
-
const transformer =
|
|
198
|
+
if (options.filter && options.filter.includes(`${METAFIELDS_RELATION.tableNameAs}.`)) {
|
|
199
|
+
const transformer = createMetafieldsFilterTransformer();
|
|
200
200
|
options.mongoTransformer = options.mongoTransformer
|
|
201
201
|
? chainTransformers(options.mongoTransformer, transformer)
|
|
202
202
|
: transformer;
|
|
@@ -206,7 +206,7 @@ const Member = ghostBookshelf.Model.extend(
|
|
|
206
206
|
|
|
207
207
|
filterRelations() {
|
|
208
208
|
return {
|
|
209
|
-
metafields:
|
|
209
|
+
metafields: METAFIELDS_RELATION,
|
|
210
210
|
labels: {
|
|
211
211
|
tableName: 'labels',
|
|
212
212
|
type: 'manyToMany',
|
|
@@ -6,7 +6,7 @@ const limitService = require('../services/limits');
|
|
|
6
6
|
const tpl = require('@tryghost/tpl');
|
|
7
7
|
const errors = require('@tryghost/errors');
|
|
8
8
|
const security = require('@tryghost/security');
|
|
9
|
-
const validatePassword = require('../lib/validate-password');
|
|
9
|
+
const { validatePassword } = require('../lib/validate-password');
|
|
10
10
|
const permissions = require('../services/permissions');
|
|
11
11
|
const urlUtils = require('../../shared/url-utils').default;
|
|
12
12
|
const { setIsRoles } = require('./role-utils');
|
|
@@ -78,7 +78,7 @@ const repository = (0, database_automations_repository_1.createDatabaseAutomatio
|
|
|
78
78
|
fakeWaitHoursMultiplier: (0, fake_wait_hours_multiplier_1.parseFakeWaitHoursMultiplier)(config.get('automations:fakeWaitHoursMultiplier')),
|
|
79
79
|
});
|
|
80
80
|
async function browse() {
|
|
81
|
-
return await repository.browse();
|
|
81
|
+
return await repository.browse({ includeStats: true });
|
|
82
82
|
}
|
|
83
83
|
async function read(automationId) {
|
|
84
84
|
const automation = await repository.getById(automationId);
|
|
@@ -35,14 +35,16 @@ const messages = {
|
|
|
35
35
|
const DEFAULT_EMAIL_DESIGN_SETTING_REFERENCE = constants_1.DEFAULT_EMAIL_DESIGN_SETTING_SLUG;
|
|
36
36
|
function createDatabaseAutomationsRepository({ knex, fakeWaitHoursMultiplier, }) {
|
|
37
37
|
return {
|
|
38
|
-
async browse() {
|
|
38
|
+
async browse({ includeStats }) {
|
|
39
39
|
return await knex.transaction(async (trx) => {
|
|
40
40
|
await ensureDefaultAutomations(trx);
|
|
41
|
-
const
|
|
41
|
+
const data = includeStats
|
|
42
|
+
? (await loadAutomationsWithStats(trx)).map((row) => buildAutomationBrowseResult(row))
|
|
43
|
+
: (await loadAutomations(trx)).map((row) => buildAutomationSummary(row));
|
|
42
44
|
return {
|
|
43
|
-
data
|
|
45
|
+
data,
|
|
44
46
|
meta: {
|
|
45
|
-
pagination: buildPagination(
|
|
47
|
+
pagination: buildPagination(data.length),
|
|
46
48
|
},
|
|
47
49
|
};
|
|
48
50
|
});
|
|
@@ -671,6 +673,11 @@ async function loadAutomationBySlug(trx, slug) {
|
|
|
671
673
|
return row ?? null;
|
|
672
674
|
}
|
|
673
675
|
async function loadAutomations(trx) {
|
|
676
|
+
return await trx('automations')
|
|
677
|
+
.select('id', 'slug', 'name', 'status', 'created_at', 'updated_at')
|
|
678
|
+
.orderBy('name');
|
|
679
|
+
}
|
|
680
|
+
async function loadAutomationsWithStats(trx) {
|
|
674
681
|
const inProgressRuns = trx('automation_run_steps')
|
|
675
682
|
.distinct('automation_run_id')
|
|
676
683
|
.where('status', 'pending')
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Newsletter Email Sending
|
|
2
|
+
|
|
3
|
+
Newsletter Email Sending covers the preparation and submission of newsletter emails to the configured email provider.
|
|
4
|
+
|
|
5
|
+
## Language
|
|
6
|
+
|
|
7
|
+
**Sending status**:
|
|
8
|
+
The state of a newsletter email's send: preparing, submitting, submitted, or failed. It is separate from any later provider-reported outcome.
|
|
9
|
+
_Avoid_: Delivery status, provider outcome
|
|
10
|
+
|
|
11
|
+
**Sending phase**:
|
|
12
|
+
The part of a send, before it is submitted or fails, that progress and failures are reported against: preparing or submitting.
|
|
13
|
+
_Avoid_: Step, stage
|
|
14
|
+
|
|
15
|
+
**Preparing**:
|
|
16
|
+
The sending phase while none of a newsletter email's recipient batches has started submitting, including before any sending work has begun.
|
|
17
|
+
_Avoid_: Pending, queued
|
|
18
|
+
|
|
19
|
+
**Submitting**:
|
|
20
|
+
The sending phase once at least one recipient batch has started submitting to the configured email provider and the send has not yet been submitted or failed.
|
|
21
|
+
_Avoid_: Sending, delivering
|
|
22
|
+
|
|
23
|
+
**Sending progress**:
|
|
24
|
+
The completed and total recipient counts for the current sending phase.
|
|
25
|
+
_Avoid_: Delivery progress, expected count
|
|
26
|
+
|
|
27
|
+
**Submitted**:
|
|
28
|
+
Every batch of a newsletter email has been accepted by the configured email provider. Submission does not mean that recipients have received the email.
|
|
29
|
+
_Avoid_: Sent, delivered
|
|
30
|
+
|
|
31
|
+
**Failed**:
|
|
32
|
+
Sending stopped with an error and will not continue without a retry.
|
|
33
|
+
_Avoid_: Bounced, rejected
|
|
34
|
+
|
|
35
|
+
**Failed during**:
|
|
36
|
+
The sending phase a failed newsletter email was in when sending stopped.
|
|
37
|
+
_Avoid_: Failure step
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Email service
|
|
2
|
+
|
|
3
|
+
Renders newsletter emails, splits them into recipient batches, and submits the
|
|
4
|
+
batches to the configured email provider. Domain terms live in
|
|
5
|
+
[CONTEXT.md](CONTEXT.md).
|
|
6
|
+
|
|
7
|
+
## Sending status
|
|
8
|
+
|
|
9
|
+
The sending status served by the Admin API's `emails/:id/status` endpoint is
|
|
10
|
+
derived on read. `sending-status.ts` owns the derivation from an email and its
|
|
11
|
+
batches with their recipient counts; `SendingStatusService` reads those rows
|
|
12
|
+
and `sending-status-serializers.ts` shapes the response. Submitted is terminal and the batch aggregation only
|
|
13
|
+
describes a send that is still in progress or has failed, so submitted emails answer with the email's stored
|
|
14
|
+
`email_count` as both completed and total; batch creation reconciles that
|
|
15
|
+
column to the recipient rows it built. That also keeps reads of long-finished
|
|
16
|
+
emails cheap, with no query over the batches or recipients. The endpoint is always available; Admin decides
|
|
17
|
+
whether to poll it while a send is active.
|
|
18
|
+
|
|
19
|
+
The phase is read from the batches: an email is submitting once any batch has
|
|
20
|
+
left `pending`, and preparing otherwise. Batch statuses persist across
|
|
21
|
+
attempts, so a retried email reports submitting with frozen progress while it
|
|
22
|
+
waits for its job, and `failed_during` is the same derivation applied to a
|
|
23
|
+
failed email.
|
|
24
|
+
|
|
25
|
+
The rough ETA measures recent recipient throughput, including work completed by
|
|
26
|
+
concurrent workers. It stays `null` until enough timing samples are available in
|
|
27
|
+
the current phase and attempt, so short sends may finish without showing an ETA.
|
|
28
|
+
Progress counts update independently of the estimate.
|
|
29
|
+
|
|
30
|
+
The ETA is always `null` for failed emails and `0` once no work remains in the
|
|
31
|
+
current phase. Batches that failed during the current attempt are not remaining
|
|
32
|
+
work until the email is retried, so the ETA can reach `0` while completed is
|
|
33
|
+
still below total. Consumers should key completion on the status, never the ETA.
|
|
34
|
+
|
|
35
|
+
Ghost does not record when a sending attempt or phase started, so the ETA uses
|
|
36
|
+
the email's `updated_at` as a proxy for the start of the current attempt: the
|
|
37
|
+
sending job saves the email when it takes its status lock and, when the
|
|
38
|
+
recipient count changed, again after creating batches; a retry or boot resume
|
|
39
|
+
saves it when re-queuing the email. Batches completed before that timestamp
|
|
40
|
+
belong to an earlier attempt and are left out of the rate window. The proxy
|
|
41
|
+
only holds while nothing saves the Email model, or sets `emails.updated_at`
|
|
42
|
+
through a raw update, while batches are being submitted.
|