ghost 6.60.0 → 6.62.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ghost-storage-base-3.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
- package/components/tryghost-adapter-base-jobs-0.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-redirects-0.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-route-settings-0.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-scheduling-0.2.3.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.1.3.tgz +0 -0
- package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
- package/components/tryghost-checkout-0.0.0.tgz +0 -0
- package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-i18n-0.0.0.tgz +0 -0
- package/components/tryghost-kg-card-factory-5.2.4.tgz +0 -0
- package/components/tryghost-kg-clean-basic-html-4.3.4.tgz +0 -0
- package/components/tryghost-kg-converters-1.2.5.tgz +0 -0
- package/components/tryghost-kg-default-cards-10.3.5.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.2.1.tgz +0 -0
- package/components/tryghost-kg-default-transforms-1.3.4.tgz +0 -0
- package/components/tryghost-kg-html-to-lexical-1.4.0.tgz +0 -0
- package/components/tryghost-kg-lexical-html-renderer-1.5.0.tgz +0 -0
- package/components/tryghost-kg-markdown-html-renderer-7.2.4.tgz +0 -0
- package/components/tryghost-kg-utils-1.1.4.tgz +0 -0
- package/components/tryghost-nql-string-0.0.0.tgz +0 -0
- package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
- package/content/settings/README.md +1 -1
- package/content/themes/casper/.gitignore +28 -0
- package/content/themes/casper/assets/built/casper.js +1 -1
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/css/screen.css +2 -1
- package/content/themes/casper/assets/js/lightbox.js +63 -46
- package/content/themes/casper/package.json +6 -6
- package/content/themes/casper/page.hbs +1 -0
- package/content/themes/casper/post.hbs +1 -0
- package/content/themes/source/.gitignore +28 -0
- package/content/themes/source/assets/built/screen.css +1 -1
- package/content/themes/source/assets/built/source.js +1 -1
- package/content/themes/source/assets/css/screen.css +2 -1
- package/content/themes/source/assets/js/lightbox.js +62 -45
- package/content/themes/source/assets/js/main.js +2 -2
- package/content/themes/source/package.json +4 -4
- package/content/themes/source/partials/feature-image.hbs +1 -0
- package/core/boot.js +62 -21
- package/core/built/admin/assets/Combination-B0l4MKVh.js +41 -0
- package/core/built/admin/assets/InterVariable-DiVDrmQJ.woff2 +0 -0
- package/core/built/admin/assets/InterVariable-Italic-FCBEiFp6.woff2 +0 -0
- package/core/built/admin/assets/{PolarAngleAxis-C4qtHy1B.js → PolarAngleAxis-pkMyqz8g.js} +1 -1
- package/core/built/admin/assets/{RadialBarChart-Bh09Stsj.js → RadialBarChart-JAKoS4KT.js} +1 -1
- package/core/built/admin/assets/{about-BgpmhCuM.js → about-Dyae_UT6.js} +1 -1
- package/core/built/admin/assets/account-migration-CESa0mnX.js +1 -0
- package/core/built/admin/assets/{action-list-lGzZLHc8.js → action-list-C4v0-du8.js} +1 -1
- package/core/built/admin/assets/activitypub/{account-migration-DMHuPnJn.js → account-migration-By69CgCp.js} +16 -16
- package/core/built/admin/assets/activitypub/activitypub.js +9 -10
- package/core/built/admin/assets/activitypub/{avatar-DhTo5D1B.js → avatar-DLPL3Abs.js} +59 -68
- package/core/built/admin/assets/activitypub/{avatar-flipboard-DnVgbEfa.js → avatar-flipboard-C9iDY-QV.js} +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-BT4zAlW7.js → bluesky-sharing-BJcPMrDd.js} +37 -37
- package/core/built/admin/assets/activitypub/{content-formatters-BeDZ-DIa.js → content-formatters-3rTHg3mp.js} +18 -18
- package/core/built/admin/assets/activitypub/{copy-8sqxUuNd.js → copy-DIAOQoCZ.js} +2 -2
- package/core/built/admin/assets/activitypub/{domain-xQDDu1CS.js → domain-DUR4xSkV.js} +121 -121
- package/core/built/admin/assets/activitypub/{edit-profile-BXdF_r2G.js → edit-profile-BqtfzxCp.js} +40 -40
- package/core/built/admin/assets/activitypub/{explore-CMt7gKlL.js → explore-BxuJd-J4.js} +27 -27
- package/core/built/admin/assets/activitypub/{feed-Bwqdddp_.js → feed-BM1TcksA.js} +51 -51
- package/core/built/admin/assets/activitypub/{field-4co_8FVg.js → field-Dn7HHx2-.js} +3 -3
- package/core/built/admin/assets/activitypub/{hash-DwakMs4X.js → hash-Bm3WxYN4.js} +2 -2
- package/core/built/admin/assets/activitypub/inbox-CCjkuow2.js +1284 -0
- package/core/built/admin/assets/activitypub/{inbox-ckD4wo_r.js → inbox-d8oJRTf4.js} +2 -2
- package/core/built/admin/assets/activitypub/label-1AA_s9Fa.js +16 -0
- package/core/built/admin/assets/activitypub/layout-2-0bw6HN.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-KCCO7L2J.js → moderation-CnmxEB8w.js} +91 -91
- package/core/built/admin/assets/activitypub/{note-BX_Bz5A0.js → note-ZKc_Bnba.js} +24 -24
- package/core/built/admin/assets/activitypub/{notifications-BQOYGvRv.js → notifications-CB2tJZau.js} +51 -51
- package/core/built/admin/assets/activitypub/{onboarding-CNelD_uF.js → onboarding-BsJ9E5qf.js} +2 -2
- package/core/built/admin/assets/activitypub/{onboarding-mat5Hrl0.js → onboarding-CSUJJIlh.js} +2 -2
- package/core/built/admin/assets/activitypub/{preferences-6h43G9_q.js → preferences-0-JZnUkI.js} +1519 -1519
- package/core/built/admin/assets/activitypub/{profile-BMN9VVZ2.js → profile-C35cFIhM.js} +256 -256
- package/core/built/admin/assets/activitypub/{reply-ZNo04xsO.js → reply-DYK5vVqb.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-3B9mUQ7i.js → routes-BD4vscyc.js} +796 -790
- package/core/built/admin/assets/activitypub/{separator-D9taVCCa.js → separator-BbQqTm-p.js} +3 -3
- package/core/built/admin/assets/activitypub/settings-BsMROdqh.js +315 -0
- package/core/built/admin/assets/activitypub/{site-DHoMupSB.js → site-O0RL9YCN.js} +2 -2
- package/core/built/admin/assets/activitypub/{step-1-BsGGnBuT.js → step-1-BMSx7c_X.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-2-teosIS0k.js → step-2-DtPQzlTH.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-3-BjD5TokC.js → step-3-CLlOZTyt.js} +11 -11
- package/core/built/admin/assets/activitypub/{tabs-Cvwl1wXU.js → tabs-Dxk-b9M-.js} +306 -306
- package/core/built/admin/assets/activitypub/{topic-filter-C3LZJc2X.js → topic-filter-5yZH6tId.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-Dp9f0gov.js → use-activity-pub-queries-CV50qe03.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-C4RI0sa9.js → use-navigate-with-base-path-Bmk5h4lW.js} +220 -220
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-C2oNDMUH.js → use-reply-chain-data-Cbvzgjzd.js} +30 -30
- package/core/built/admin/assets/activitypub/{users-CoX6FT_K.js → users-BjcoP_HC.js} +7 -5
- package/core/built/admin/assets/activitypub/{x-Dxm6pV9X.js → x-DbQZOLiI.js} +2 -2
- package/core/built/admin/assets/add-integration-modal-BlF6rs_f.js +1 -0
- package/core/built/admin/assets/add-newsletter-modal-CglVQqah.js +1 -0
- package/core/built/admin/assets/add-recommendation-modal-wz7BsuaG.js +1 -0
- package/core/built/admin/assets/analytics-C4HE1gBg.js +1 -0
- package/core/built/admin/assets/{announcement-bar-modal-BTHiy1ZO.js → announcement-bar-modal-Ci4zz8jO.js} +1 -1
- package/core/built/admin/assets/{api-keys-Boy5pLbD.js → api-keys-Ck-Yteiv.js} +1 -1
- package/core/built/admin/assets/audience-CtbA54Sn.js +1 -0
- package/core/built/admin/assets/{automated-emails-te1D5kq-.js → automated-emails-D5VVMND9.js} +1 -1
- package/core/built/admin/assets/automations-CzKt5fb8.js +1 -0
- package/core/built/admin/assets/bluesky-sharing-BEtJ_gL_.js +1 -0
- package/core/built/admin/assets/{box-By_EgGtm.js → box-CdfQAP_F.js} +1 -1
- package/core/built/admin/assets/{brand-icon-09YqFZjV.js → brand-icon-CrIGOA3Z.js} +1 -1
- package/core/built/admin/assets/{breadcrumb-CcFr_ltt.js → breadcrumb-zUrZrBtW.js} +1 -1
- package/core/built/admin/assets/{button-BshWPHvn.js → button-eNMKQWXr.js} +1 -1
- package/core/built/admin/assets/{card-CiiE7QfM.js → card--VNbfgo3.js} +1 -1
- package/core/built/admin/assets/{chart-BnXyfH11.js → chart-BrhA41Zi.js} +1 -1
- package/core/built/admin/assets/chart-helpers-CfBKt0vT.js +1 -0
- package/core/built/admin/assets/{checkbox-Dwwr_3Py.js → checkbox-CgOn_tKW.js} +1 -1
- package/core/built/admin/assets/{chunk.524.91ecb33e6afc3bd8949b.js → chunk.524.a697dee88db67fdf6021.js} +4 -4
- package/core/built/admin/assets/{chunk.582.512549909741883f960c.js → chunk.582.5ed5d124cfca9c259b9c.js} +6 -6
- package/core/built/admin/assets/{chunk.16.f76a6314082d813d67e0.js → chunk.598.7e056fabb47ce7dbd73f.js} +14 -14
- package/core/built/admin/assets/{code-editor-view-BwIdPy_k.js → code-editor-view-BYNoAoNV.js} +1 -1
- package/core/built/admin/assets/{code-editor-view-CtkF82Fv.js → code-editor-view-K2wI-I5f.js} +1 -1
- package/core/built/admin/assets/color-picker-Ddw80xX6.js +3 -0
- package/core/built/admin/assets/color-picker-field-D9nUhvA7.js +1 -0
- package/core/built/admin/assets/combobox-w9j8PPQT.js +1 -0
- package/core/built/admin/assets/command-BfpHORBv.js +1 -0
- package/core/built/admin/assets/comments-BLmCmyCs.js +1 -0
- package/core/built/admin/assets/confirmation-context-CBM8G0Vj.js +1 -0
- package/core/built/admin/assets/{container-LiZf3XdY.js → container-BuQG1wuP.js} +1 -1
- package/core/built/admin/assets/{content-api-modal-BtpNI92-.js → content-api-modal-TXRz3U7S.js} +1 -1
- package/core/built/admin/assets/context-menu-_QMdd3kp.js +1 -0
- package/core/built/admin/assets/{copy-field-CglgRZyG.js → copy-field-6N01S07E.js} +1 -1
- package/core/built/admin/assets/create-ghost-browse-value-source-qIBymvvf.js +1 -0
- package/core/built/admin/assets/custom-field-type-option-BddFlJFa.js +1 -0
- package/core/built/admin/assets/custom-integration-modal-DmNquxUV.js +1 -0
- package/core/built/admin/assets/{data-list-CupF7bjM.js → data-list-CA2jYGPq.js} +1 -1
- package/core/built/admin/assets/{date-range-select-JPwM0yUj.js → date-range-select-uvz_DtPh.js} +1 -1
- package/core/built/admin/assets/design-and-theme-modal-BSmo12Op.js +2 -0
- package/core/built/admin/assets/{detail-page-D8lx2SdP.js → detail-page-Dnv-CdR1.js} +1 -1
- package/core/built/admin/assets/dirty-confirm-dialog-BZ1iytEd.js +1 -0
- package/core/built/admin/assets/{disabled-sources-indicator-BTJ2ClQu.js → disabled-sources-indicator-DMRrxsiJ.js} +1 -1
- package/core/built/admin/assets/{dist-DzcfoDJ3.js → dist-8YJUlf3B.js} +1 -1
- package/core/built/admin/assets/{dist-Cb5RSHSg.js → dist-B3keLvjj.js} +1 -1
- package/core/built/admin/assets/dist-Dcsslyo72.js +1 -0
- package/core/built/admin/assets/{dist-BT_Z-eCn.js → dist-Dgt2cr6a.js} +1 -1
- package/core/built/admin/assets/domain-BNYrt0aD.js +1 -0
- package/core/built/admin/assets/dropzone-D7nFdAe5.js +5 -0
- package/core/built/admin/assets/edit-profile-C5Asm4LA.js +1 -0
- package/core/built/admin/assets/editor-REokrzci.js +8 -0
- package/core/built/admin/assets/{embed-signup-form-modal-BFTPf04i.js → embed-signup-form-modal-Ds6mPEi5.js} +1 -1
- package/core/built/admin/assets/{empty-indicator-WB1WgT9h.js → empty-indicator-D9QdpuNX.js} +1 -1
- package/core/built/admin/assets/en-B1jX3WMs.js +1 -0
- package/core/built/admin/assets/explore-UY10Egsp.js +1 -0
- package/core/built/admin/assets/feature-image-placeholder-D1iF_8bc.js +1 -0
- package/core/built/admin/assets/feed-81C8mJWR.js +1 -0
- package/core/built/admin/assets/{field-LmNk7wZh.js → field-Dihp5p18.js} +1 -1
- package/core/built/admin/assets/filter-bar-Da1qxybu.js +1 -0
- package/core/built/admin/assets/filter-sources-BO9xUPj7.js +1 -0
- package/core/built/admin/assets/filters-CRUWecfY.js +1 -0
- package/core/built/admin/assets/filters-DirqNXjH.js +1 -0
- package/core/built/admin/assets/first-promoter-modal-CT6MxYZb.js +1 -0
- package/core/built/admin/assets/{ghost-c8807202a07c20030acb719ee0114815.css → ghost-8eabfc1ece81b03cf7b90780d31487e8.css} +1 -1
- package/core/built/admin/assets/{ghost-403cb39badad3df411fc0ba27d220047.js → ghost-c2848508292937415f20f5a830eea59e.js} +31 -28
- package/core/built/admin/assets/{ghost-dark-fbf1e384aaef3ae315e540f0eb7373a7.css → ghost-dark-a7bebe2c561f55369dcf29f4c820f7ff.css} +1 -1
- package/core/built/admin/assets/gift-link-modal-CQVAye17.js +1 -0
- package/core/built/admin/assets/{grid-CUciBfjf.js → grid-Dj29PY05.js} +1 -1
- package/core/built/admin/assets/growth-BWIu-6xj.js +3 -0
- package/core/built/admin/assets/growth-DU29oxeA.js +1 -0
- package/core/built/admin/assets/{heading-CRZZ2FUe.js → heading-Dh5SFVjL.js} +1 -1
- package/core/built/admin/assets/history-modal-CFd1Hvx6.js +1 -0
- package/core/built/admin/assets/html-field-CJBCLTvv.js +1 -0
- package/core/built/admin/assets/{images-C0XvUYMb.js → images-scfkWZiK.js} +1 -1
- package/core/built/admin/assets/{inbox-DZXDUt4s.js → inbox-Mcp4DXZN.js} +14 -14
- package/core/built/admin/assets/index-6MkPdCtr.js +4 -0
- package/core/built/admin/assets/index-Bn7QhcZu.css +2 -0
- package/core/built/admin/assets/index.es-CNnuNH0d.js +1 -0
- package/core/built/admin/assets/{inline-Cw-gigDW.js → inline-BWSs_VzR.js} +1 -1
- package/core/built/admin/assets/{input-group-BPAxETpQ.js → input-group-CMhe2DvY.js} +1 -1
- package/core/built/admin/assets/{input-surface-BdOe4KzO.js → input-surface-BsrIZeSF.js} +1 -1
- package/core/built/admin/assets/{integration-header-B4DyXV06.js → integration-header-Bm6IDb1A.js} +1 -1
- package/core/built/admin/assets/invite-user-modal-DfheMXs0.js +1 -0
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +14 -14
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +3 -3
- package/core/built/admin/assets/koenig-lexical/style.css +1 -1
- package/core/built/admin/assets/{koenig-lexical-60O_8aiO.js → koenig-lexical-DBSqAuiI.js} +3 -3
- package/core/built/admin/assets/koenig-loader-aFQNlJlD.js +2 -0
- package/core/built/admin/assets/kpi-card-DBRMfGOW.js +1 -0
- package/core/built/admin/assets/{kpi-card-fUHZlzBX.js → kpi-card-DFafD1qq.js} +1 -1
- package/core/built/admin/assets/kpi-helpers-Cv8i8hns.js +1 -0
- package/core/built/admin/assets/kpi-tabs-BJItvLYm.js +1 -0
- package/core/built/admin/assets/{label-VIG969IC.js → label-BnSCvffu.js} +1 -1
- package/core/built/admin/assets/label-picker-KVoo91sh.js +1 -0
- package/core/built/admin/assets/layout-Bjv2Ll-5.js +1 -0
- package/core/built/admin/assets/{link-helpers-Dtq9fxQg.js → link-helpers-MVDdnFCY.js} +1 -1
- package/core/built/admin/assets/{list-page-CdsnQR9N.js → list-page-BucXJ0hi.js} +1 -1
- package/core/built/admin/assets/member-custom-fields-DDQV6QeG.js +1 -0
- package/core/built/admin/assets/member-detail-CdJF6Agw.js +1 -0
- package/core/built/admin/assets/members-DjQbq9Z2.js +4 -0
- package/core/built/admin/assets/{modal-page-BMTzFHVO.js → modal-page-xCivTMvI.js} +1 -1
- package/core/built/admin/assets/moderation-AgCIEso4.js +1 -0
- package/core/built/admin/assets/{multi-select-combobox-CGohnsxJ.js → multi-select-combobox-BcITkUVr.js} +1 -1
- package/core/built/admin/assets/navigation-modal-BkT7b1M2.js +1 -0
- package/core/built/admin/assets/newsletter-BMMAuvgq.js +1 -0
- package/core/built/admin/assets/newsletter-detail-modal-0SCJ7Zpm.js +1 -0
- package/core/built/admin/assets/{newsletter-emails-D5ql1rud.js → newsletter-emails-DKzwafuh.js} +1 -1
- package/core/built/admin/assets/newsletters-BRBb_uQC.js +1 -0
- package/core/built/admin/assets/note-DyGHtS9a.js +1 -0
- package/core/built/admin/assets/notifications-ZgWniKna.js +1 -0
- package/core/built/admin/assets/nql-BLpCn7qX.js +21 -0
- package/core/built/admin/assets/offers-container-modal-B-E8TIWr.js +1 -0
- package/core/built/admin/assets/{onboarding-B4YhlZ0i.js → onboarding-CzIsre-d.js} +1 -1
- package/core/built/admin/assets/onboarding-qg8Akovm.js +1 -0
- package/core/built/admin/assets/{onboarding-route-Cu_ROWPn.js → onboarding-route-CaavZulg.js} +1 -1
- package/core/built/admin/assets/overview-BW8GLeYi.js +1 -0
- package/core/built/admin/assets/overview-BkXZNzaZ.js +1 -0
- package/core/built/admin/assets/{page-header-Cfds8pCg.js → page-header-De0VzKBi.js} +1 -1
- package/core/built/admin/assets/pagemenu-B5KKCpbA.js +1 -0
- package/core/built/admin/assets/pages-jgaFm69d.js +1 -0
- package/core/built/admin/assets/pages-route-CIUAQxtY.js +1 -0
- package/core/built/admin/assets/papaparse.min-cQwnxGCG.js +10 -0
- package/core/built/admin/assets/{pintura-modal-TDTrJ89x.js → pintura-modal-BZ2J9w3n.js} +1 -1
- package/core/built/admin/assets/portal-frame-B6Hr9wHc.js +1 -0
- package/core/built/admin/assets/portal-modal-D4ceei3t.js +1 -0
- package/core/built/admin/assets/post-analytics-header-BYCsT0GP.js +1 -0
- package/core/built/admin/assets/post-analytics-ot7f6pih.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-Cu3jNeFh.js → post-analytics-provider-Bo1gmYaP.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-P1_M6o-H.js → post-share-modal-C-PaYZ_e.js} +1 -1
- package/core/built/admin/assets/posts-DuGazgTb.js +1 -0
- package/core/built/admin/assets/posts-list-screen-CBGGgjjj.js +2 -0
- package/core/built/admin/assets/posts-route-D6xmS7Vf.js +1 -0
- package/core/built/admin/assets/preferences-C5YJLp74.js +5 -0
- package/core/built/admin/assets/{preview-chrome-BOVO6_PE.js → preview-chrome-BljUBlOk.js} +1 -1
- package/core/built/admin/assets/{preview-modal-MlTjP8UU.js → preview-modal-BfeFqi5y.js} +1 -1
- package/core/built/admin/assets/profile-B8mx2yAu.js +1 -0
- package/core/built/admin/assets/{radio-group-CjtmsCPl.js → radio-group-DczlLKOn.js} +1 -1
- package/core/built/admin/assets/{router-provider-CJC58zTy.js → router-provider-B0gZyiGJ.js} +1 -1
- package/core/built/admin/assets/{schemas-CRsC53ls.js → schemas-DppqmpeJ.js} +2 -2
- package/core/built/admin/assets/select-cb0t_TAJ.js +1 -0
- package/core/built/admin/assets/settings-DZqxR0Da.js +1 -0
- package/core/built/admin/assets/settings-DvfTNdHQ.js +4 -0
- package/core/built/admin/assets/{settings-breadcrumbs-sLDNedBW.js → settings-breadcrumbs-DLzbRRSt.js} +1 -1
- package/core/built/admin/assets/{shade-app-F7WxOyfk-nufqw1hd.js → shade-app-CcS3f8MZ-vwURazk8.js} +2 -2
- package/core/built/admin/assets/share-modal-D2-Bz1_I.js +1 -0
- package/core/built/admin/assets/{skeleton-D7hwjdry.js → skeleton-Ce9GgUpf.js} +1 -1
- package/core/built/admin/assets/slack-modal-DYDTUria.js +1 -0
- package/core/built/admin/assets/sort-button-VVeQjjbu.js +1 -0
- package/core/built/admin/assets/{source-icon-BPsD5ees.js → source-icon-Ck5_5Kgq.js} +1 -1
- package/core/built/admin/assets/{stack-CIfOAAVf.js → stack-B2eszIiq.js} +1 -1
- package/core/built/admin/assets/stats-Rjmk5owg.js +1 -0
- package/core/built/admin/assets/stats-view-BGGmkmgv.js +1 -0
- package/core/built/admin/assets/{step-1-CMta6YFO.js → step-1-CsaBjeN4.js} +1 -1
- package/core/built/admin/assets/{step-2-BG04nh5q.js → step-2-D2vnxOOM.js} +1 -1
- package/core/built/admin/assets/{step-3-ZnMaVIPj.js → step-3-wBoLdGEb.js} +2 -2
- package/core/built/admin/assets/{stripe-button-DDoUKN5a.js → stripe-button-CDgLz0S0.js} +1 -1
- package/core/built/admin/assets/stripe-connect-modal-CSjMJetP.js +1 -0
- package/core/built/admin/assets/{switch-C9djUIMO.js → switch-CI4gLWqb.js} +1 -1
- package/core/built/admin/assets/{table-CrqQtkR2.js → table-Do6iQ83Q.js} +1 -1
- package/core/built/admin/assets/{tabs-CGisSbWq.js → tabs-D2yWLMp_.js} +1 -1
- package/core/built/admin/assets/tag-detail-BhXlOsCa.js +2 -0
- package/core/built/admin/assets/tags-ldukRApe.js +1 -0
- package/core/built/admin/assets/testimonials-modal-CtnZBJKl.js +1 -0
- package/core/built/admin/assets/{text-DU9Hv33_.js → text-BQDSDsKs.js} +1 -1
- package/core/built/admin/assets/{textarea-d9qEkMOP.js → textarea-DJ1Q5V39.js} +1 -1
- package/core/built/admin/assets/tier-detail-modal-BOs8e_G8.js +1 -0
- package/core/built/admin/assets/{toggle-group-CtKZURJd.js → toggle-group-ByUJsxwD.js} +1 -1
- package/core/built/admin/assets/{topic-filter-D8JT8Tq1.js → topic-filter-BgC3g5_v.js} +1 -1
- package/core/built/admin/assets/{transistor-modal-6PUf0idx.js → transistor-modal-Drr0SNfo.js} +1 -1
- package/core/built/admin/assets/trend-badge-6ye93YAW.js +1 -0
- package/core/built/admin/assets/{unsplash-modal-BPuDBPja.js → unsplash-modal-p6AoQoWp.js} +1 -1
- package/core/built/admin/assets/{use-analytics-data--opuzwcz.js → use-analytics-data-CWCMAESo.js} +1 -1
- package/core/built/admin/assets/use-check-theme-limit-error-B8bjF-gO.css +1 -0
- package/core/built/admin/assets/use-check-theme-limit-error-Cw3WQHy8.js +2 -0
- package/core/built/admin/assets/use-filter-params-BIni9taJ.js +1 -0
- package/core/built/admin/assets/use-growth-stats-DCVG3s9w.js +1 -0
- package/core/built/admin/assets/{use-limiter-TDSNiHPB.js → use-limiter-D2WRRx0H.js} +1 -1
- package/core/built/admin/assets/{use-navigate-with-base-path-CK989MlC.js → use-navigate-with-base-path-BHPA228f.js} +1 -1
- package/core/built/admin/assets/{use-pintura-config-CnCGrDFk.js → use-pintura-config-h-J0Lvta.js} +1 -1
- package/core/built/admin/assets/{use-pintura-editor-ntphvtUd.js → use-pintura-editor-BfN1UwzH.js} +1 -1
- package/core/built/admin/assets/use-post-referrers-Czg47m8s.js +1 -0
- package/core/built/admin/assets/use-reply-chain-data-ns0caK8k.js +1 -0
- package/core/built/admin/assets/{use-router-history-entry-BwfQS2mq.js → use-router-history-entry-uldn7zJO.js} +1 -1
- package/core/built/admin/assets/{use-setting-group-DQxV42-O.js → use-setting-group-DpcuCUDg.js} +1 -1
- package/core/built/admin/assets/use-settings-navigation-CZjGrhsp.js +1 -0
- package/core/built/admin/assets/{use-simple-pagination-B2TdNX67.js → use-simple-pagination-DzJWobPR.js} +1 -1
- package/core/built/admin/assets/{use-staff-users-QrR9voeC.js → use-staff-users-Csipb-O0.js} +1 -1
- package/core/built/admin/assets/use-tinybird-query-CSnGXzZh.js +1 -0
- package/core/built/admin/assets/use-tinybird-token-DTOKRR7e.js +1 -0
- package/core/built/admin/assets/{use-unsaved-changes-guard-DpYJcj1d.js → use-unsaved-changes-guard-CBzs-KPi.js} +1 -1
- package/core/built/admin/assets/{use-url-input-WnIqckzS.js → use-url-input-DFr8lCbS.js} +1 -1
- package/core/built/admin/assets/user-detail-modal-B_iUClI6.js +1 -0
- package/core/built/admin/assets/users-Cg-OeGzC.js +1 -0
- package/core/built/admin/assets/{virtual-list-D3MfPNOh.js → virtual-list-BW-padMe.js} +1 -1
- package/core/built/admin/assets/web-C1v33ehA.js +2 -0
- package/core/built/admin/assets/web-C4BKzn2T.js +1 -0
- package/core/built/admin/assets/{zapier-modal-BO6PS_Vd.js → zapier-modal-BBFfI6Ml.js} +1 -1
- package/core/built/admin/index.html +24 -30
- package/core/frontend/apps/private-blogging/lib/views/private.hbs +44 -43
- package/core/frontend/helpers/cancel_link.js +2 -2
- package/core/frontend/helpers/pagination.js +2 -2
- package/core/frontend/helpers/social_accounts.js +2 -2
- package/core/frontend/public/ghost.css +1 -1
- package/core/frontend/public/ghost.min.css +1 -1
- package/core/frontend/services/rendering/renderer.js +15 -0
- package/core/frontend/services/sitemap/user-map-generator.js +9 -2
- package/core/server/adapters/storage/LocalStorageBase.js +8 -5
- package/core/server/api/endpoints/db.js +19 -5
- package/core/server/api/endpoints/index.js +6 -2
- package/core/server/api/endpoints/mail.js +1 -1
- package/core/server/api/endpoints/member-metafields.js +89 -0
- package/core/server/api/endpoints/members.js +4 -4
- package/core/server/api/endpoints/posts.js +4 -3
- package/core/server/api/endpoints/tiers-checkout-config.js +59 -0
- package/core/server/api/endpoints/utils/serializers/input/members.js +4 -7
- package/core/server/api/endpoints/utils/serializers/output/config.js +1 -0
- package/core/server/api/endpoints/utils/serializers/output/index.js +6 -2
- package/core/server/api/endpoints/utils/serializers/output/{member-custom-fields.js → member-metafields.js} +2 -4
- package/core/server/api/endpoints/utils/serializers/output/members.js +2 -4
- package/core/server/api/endpoints/utils/serializers/output/tiers-checkout-config.js +12 -0
- package/core/server/api/endpoints/utils/validators/input/invitations.js +0 -5
- package/core/server/api/endpoints/utils/validators/input/pages.js +1 -1
- package/core/server/api/endpoints/utils/validators/input/posts.js +1 -1
- package/core/server/data/exporter/table-lists.js +3 -0
- package/core/server/data/importer/content-files.js +57 -0
- package/core/server/data/importer/handlers/image.js +2 -2
- package/core/server/data/importer/import-archive.js +131 -0
- package/core/server/data/importer/import-manager.js +18 -180
- package/core/server/data/importer/importers/content-file-importer.js +66 -4
- package/core/server/data/importer/importers/data/data-importer.js +3 -3
- package/core/server/data/migrations/utils/migrations.js +1 -1
- package/core/server/data/migrations/versions/6.61/2026-08-19-14-30-20-add-written-by-to-members-custom-field-values.js +42 -0
- package/core/server/data/migrations/versions/6.61/2026-08-19-14-38-53-add-members-custom-field-bindings-table.js +26 -0
- package/core/server/data/migrations/versions/6.61/2026-08-19-14-48-06-add-tier-checkout-field-tables.js +35 -0
- package/core/server/data/schema/schema.js +90 -0
- package/core/server/data/seeders/importers/automation-action-revisions-importer.js +3 -3
- package/core/server/data/seeders/importers/automation-actions-importer.js +3 -3
- package/core/server/data/seeders/importers/automation-run-steps-importer.js +3 -3
- package/core/server/data/seeders/importers/automation-runs-importer.js +5 -5
- package/core/server/data/seeders/importers/automations-importer.js +3 -3
- package/core/server/data/seeders/importers/comment-reports-importer.js +1 -1
- package/core/server/data/seeders/importers/comments-importer.js +1 -1
- package/core/server/data/seeders/importers/email-batches-importer.js +1 -1
- package/core/server/data/seeders/importers/email-recipients-importer.js +1 -1
- package/core/server/data/seeders/importers/emails-importer.js +1 -1
- package/core/server/data/seeders/importers/labels-importer.js +1 -1
- package/core/server/data/seeders/importers/members-click-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-created-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-feedback-importer.js +1 -1
- package/core/server/data/seeders/importers/members-importer.js +1 -1
- package/core/server/data/seeders/importers/members-login-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-products-importer.js +1 -1
- package/core/server/data/seeders/importers/members-status-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-stripe-customers-importer.js +1 -1
- package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +1 -1
- package/core/server/data/seeders/importers/members-subscribe-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +1 -1
- package/core/server/data/seeders/importers/offer-redemptions-importer.js +1 -1
- package/core/server/data/seeders/importers/offers-importer.js +1 -1
- package/core/server/data/seeders/importers/posts-importer.js +1 -1
- package/core/server/data/seeders/importers/table-importer.js +2 -2
- package/core/server/data/seeders/importers/tags-importer.js +1 -1
- package/core/server/data/seeders/importers/users-importer.js +1 -1
- package/core/server/data/seeders/importers/web-mentions-importer.js +1 -1
- package/core/server/data/seeders/utils/random.js +3 -3
- package/core/server/ghost-server.js +3 -2
- package/core/server/lib/db-types/boolean.js +18 -0
- package/core/server/lib/image/image-size.js +24 -0
- package/core/server/lib/package-json/parse.js +2 -2
- package/core/server/lib/promise-pool.js +31 -0
- package/core/server/models/member.js +6 -17
- package/core/server/models/post.js +2 -2
- package/core/server/models/relations/authors.js +2 -16
- package/core/server/models/user.js +9 -2
- package/core/server/services/auth/passwordreset.js +13 -0
- package/core/server/services/automations/database-automations-repository.js +26 -26
- package/core/server/services/automations/poll.js +1 -0
- package/core/server/services/comments/comments-controller.js +30 -9
- package/core/server/services/comments/comments-service.js +55 -5
- package/core/server/services/content-import/csv/formula.js +12 -0
- package/core/server/services/content-import/csv/index.js +6 -1
- package/core/server/services/content-import/csv/parse.js +27 -6
- package/core/server/services/content-import/csv/serialize.js +13 -0
- package/core/server/services/content-import/import/assets.js +74 -0
- package/core/server/services/content-import/import/completion-email.js +164 -0
- package/core/server/services/content-import/import/errors-file.js +36 -0
- package/core/server/services/content-import/import/importer.js +204 -54
- package/core/server/services/content-import/import/local-media-url.js +63 -0
- package/core/server/services/content-import/import/media.js +254 -0
- package/core/server/services/content-import/import/post-data.js +1 -1
- package/core/server/services/content-import/import/post-link.js +11 -0
- package/core/server/services/content-import/import/post-repository.js +100 -0
- package/core/server/services/content-import/import/reader.js +9 -2
- package/core/server/services/content-import/import/relations.js +149 -0
- package/core/server/services/content-import/import/report.js +34 -0
- package/core/server/services/content-import/import/row.js +11 -2
- package/core/server/services/content-import/import/schema.js +30 -0
- package/core/server/services/content-import/import/source.js +125 -0
- package/core/server/services/content-import/import/staged-file.js +29 -0
- package/core/server/services/content-import/import/store.js +25 -2
- package/core/server/services/content-import/index.js +58 -34
- package/core/server/services/content-import/jobs/content-csv-import-job.js +20 -0
- package/core/server/services/custom-redirects/redirect-config-parser.js +2 -2
- package/core/server/services/custom-redirects/redirects-service.js +1 -1
- package/core/server/services/custom-redirects/validation.js +1 -1
- package/core/server/services/email-address/email-address-parser.js +30 -38
- package/core/server/services/email-address/email-address-service.js +37 -4
- package/core/server/services/email-analytics/email-analytics-service.js +8 -1
- package/core/server/services/email-analytics/index.js +2 -7
- package/core/server/services/email-analytics/lib/queries.js +7 -1
- package/core/server/services/email-service/batch-sending-service.js +0 -4
- package/core/server/services/email-service/email-renderer.js +14 -4
- package/core/server/services/email-service/email-service-wrapper.js +0 -9
- package/core/server/services/email-service/mailgun-email-provider.js +20 -9
- package/core/server/services/gift-links/schema.js +3 -3
- package/core/server/services/gifts/CONTEXT.md +1 -1
- package/core/server/services/gifts/constants.js +1 -1
- package/core/server/services/gifts/email-templates/gift-delivery.hbs +3 -3
- package/core/server/services/gifts/gift-bookshelf-repository.js +2 -2
- package/core/server/services/gifts/gift-delivery-bookshelf-repository.js +18 -9
- package/core/server/services/gifts/gift-delivery-schema.js +5 -5
- package/core/server/services/gifts/gift-delivery-service.js +4 -0
- package/core/server/services/gifts/gift-email-service.js +0 -1
- package/core/server/services/gifts/gift-schema.js +10 -10
- package/core/server/services/gifts/gift-service.js +4 -2
- package/core/server/services/gifts/index.js +3 -3
- package/core/server/services/gifts/jobs/index.js +4 -4
- package/core/server/services/indexnow-ping/indexnow-ping-service.js +2 -2
- package/core/server/services/invitations/accept.js +4 -2
- package/core/server/services/jobs/job-service.js +0 -9
- package/core/server/services/jobs-service/register-job-handlers.js +25 -15
- package/core/server/services/lib/mailgun-client.js +44 -8
- package/core/server/services/mail/ghost-mailer.js +4 -4
- package/core/server/services/media-inliner/external-media-inliner-job.js +12 -0
- package/core/server/services/media-inliner/external-media-inliner.js +103 -29
- package/core/server/services/media-inliner/service.js +13 -41
- package/core/server/services/media-inliner/types.js +2 -0
- package/core/server/services/member-welcome-emails/service.js +7 -7
- package/core/server/services/members/api.js +1 -0
- package/core/server/services/members/import-export/csv/parse.js +0 -7
- package/core/server/services/members/import-export/export/exporter.js +4 -6
- package/core/server/services/members/import-export/import/completion-email.js +2 -5
- package/core/server/services/members/import-export/import/importer.js +21 -5
- package/core/server/services/members/import-export/import/row.js +0 -3
- package/core/server/services/members/import-export/import/stripe-utils.js +6 -1
- package/core/server/services/members/import-export/index.js +10 -12
- package/core/server/services/members/jobs/clean-expired-comped-job.js +7 -0
- package/core/server/services/members/jobs/clean-expired-comped-task.js +145 -0
- package/core/server/services/members/jobs/index.js +48 -24
- package/core/server/services/members/members-api/controllers/member-controller.js +11 -0
- package/core/server/services/members/members-api/controllers/router-controller.js +3 -6
- package/core/server/services/members/members-api/members-api.js +9 -6
- package/core/server/services/members/members-api/repositories/event-repository.js +0 -4
- package/core/server/services/members/members-api/services/member-bread-service.js +53 -63
- package/core/server/services/members/members-api/services/payments-service.js +47 -1
- package/core/server/services/members/service.js +1 -5
- package/core/server/services/members-custom-fields/actions.js +27 -1
- package/core/server/services/members-custom-fields/bindings-service.js +120 -0
- package/core/server/services/members-custom-fields/codec.js +3 -4
- package/core/server/services/members-custom-fields/definitions-service.js +165 -22
- package/core/server/services/members-custom-fields/filter.js +75 -93
- package/core/server/services/members-custom-fields/index.js +11 -4
- package/core/server/services/members-custom-fields/key.js +2 -1
- package/core/server/services/members-custom-fields/models.js +1 -1
- package/core/server/services/members-custom-fields/queries.js +5 -0
- package/core/server/services/members-custom-fields/schema.js +37 -6
- package/core/server/services/members-custom-fields/serializers.js +3 -5
- package/core/server/services/members-custom-fields/values-service.js +65 -31
- package/core/server/services/mentions/mention-controller.js +23 -21
- package/core/server/services/mentions/process-webmention-job.js +16 -0
- package/core/server/services/mentions/service.js +8 -4
- package/core/server/services/public-config/config.js +7 -0
- package/core/server/services/route-settings/validation-errors.js +1 -1
- package/core/server/services/route-settings/yaml-parser.js +1 -1
- package/core/server/services/settings/settings-service.js +1 -8
- package/core/server/services/settings-helpers/settings-helpers.js +4 -6
- package/core/server/services/slack-ping/slack-ping-service.js +1 -1
- package/core/server/services/staff/staff-service-emails.js +2 -2
- package/core/server/services/stripe/config.js +14 -8
- package/core/server/services/stripe/service.js +10 -1
- package/core/server/services/stripe/services/checkout/completed-session.js +68 -0
- package/core/server/services/stripe/services/checkout/session-options.js +100 -0
- package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +71 -1
- package/core/server/services/stripe/stripe-api.js +31 -0
- package/core/server/services/stripe/stripe-service.js +26 -9
- package/core/server/services/stripe/webhook-controller.js +10 -0
- package/core/server/services/tier-checkout-config/codec.js +99 -0
- package/core/server/services/tier-checkout-config/index.js +26 -0
- package/core/server/services/tier-checkout-config/models.js +63 -0
- package/core/server/services/tier-checkout-config/queries.js +80 -0
- package/core/server/services/tier-checkout-config/schema.js +33 -0
- package/core/server/services/tier-checkout-config/serializers.js +128 -0
- package/core/server/services/tier-checkout-config/service.js +304 -0
- package/core/server/services/tiers/service.js +21 -0
- package/core/server/services/update-check/index.js +13 -13
- package/core/server/services/update-check/jobs/update-check-job.js +7 -0
- package/core/server/services/update-check/update-check-service.js +2 -2
- package/core/server/web/admin/controller.js +1 -1
- package/core/server/web/api/endpoints/admin/routes.js +34 -20
- package/core/server/web/api/middleware/upload.js +2 -2
- package/core/server/web/parent/app.js +5 -0
- package/core/server/web/parent/middleware/filter-query-parameters.js +94 -0
- package/core/server/web/parent/middleware/index.js +1 -0
- package/core/server/web/query-parameter-policy/index.js +9 -0
- package/core/server/web/query-parameter-policy/policy.json +58 -0
- package/core/server/web/query-parameter-policy/schema.js +38 -0
- package/core/shared/config/defaults.json +3 -0
- package/core/shared/config/env/config.testing-mysql.json +4 -1
- package/core/shared/config/env/config.testing.json +3 -0
- package/core/shared/config/overrides.json +4 -2
- package/core/shared/labs.js +9 -15
- package/package.json +25 -25
- package/pnpm-lock.yaml +741 -108
- package/pnpm-workspace.yaml +12 -9
- package/tsconfig.json +2 -3
- package/components/tryghost-kg-default-nodes-2.2.0.tgz +0 -0
- package/core/built/admin/assets/Combination-gnJO3vIw.js +0 -41
- package/core/built/admin/assets/account-migration-8qTEI1i1.js +0 -1
- package/core/built/admin/assets/activitypub/inbox-DQ5CwLma.js +0 -1284
- package/core/built/admin/assets/activitypub/label-B72sfvEU.js +0 -16
- package/core/built/admin/assets/activitypub/layout-CXajhRsL.js +0 -1
- package/core/built/admin/assets/activitypub/settings-71gCm3SO.js +0 -315
- package/core/built/admin/assets/add-integration-modal--YpTq3c0.js +0 -1
- package/core/built/admin/assets/add-newsletter-modal-C8Ix2hol.js +0 -1
- package/core/built/admin/assets/add-recommendation-modal-CScuNFz4.js +0 -1
- package/core/built/admin/assets/alert-dialog-wEbVxANM.js +0 -1
- package/core/built/admin/assets/analytics-Cnji45H5.js +0 -1
- package/core/built/admin/assets/app-utils-D2Wp7ZSS.js +0 -1
- package/core/built/admin/assets/audience-DTLMXAFH.js +0 -1
- package/core/built/admin/assets/automations-BWpRCZ_f.js +0 -1
- package/core/built/admin/assets/bluesky-sharing-DHOpQxff.js +0 -1
- package/core/built/admin/assets/chart-helpers-B3OqQ6Ez.js +0 -1
- package/core/built/admin/assets/color-picker-Bmo1r9Fc.js +0 -3
- package/core/built/admin/assets/color-picker-field-DywowPhF.js +0 -1
- package/core/built/admin/assets/combobox-C4QCh6Yw.js +0 -1
- package/core/built/admin/assets/command-DcLbSifC.js +0 -1
- package/core/built/admin/assets/comments-Bxy_3WyE.js +0 -1
- package/core/built/admin/assets/confirmation-context-CLFHhyyH.js +0 -1
- package/core/built/admin/assets/custom-field-type-option-D0PXoSnP.js +0 -10
- package/core/built/admin/assets/custom-integration-modal-DCIHKN33.js +0 -1
- package/core/built/admin/assets/design-and-theme-modal-CZ-PkW7o.js +0 -23
- package/core/built/admin/assets/dialog-Db7c164G.js +0 -1
- package/core/built/admin/assets/dirty-confirm-dialog-CmARpZQS.js +0 -1
- package/core/built/admin/assets/dist-QLZuGq7o2.js +0 -1
- package/core/built/admin/assets/domain-D0SAtc-T.js +0 -1
- package/core/built/admin/assets/dropzone-CaPOeDRZ.js +0 -5
- package/core/built/admin/assets/edit-profile-By7v5XW5.js +0 -1
- package/core/built/admin/assets/editor-COXn4Atu.js +0 -8
- package/core/built/admin/assets/explore-BsNYrubV.js +0 -1
- package/core/built/admin/assets/feed-8RR1O1bK.js +0 -1
- package/core/built/admin/assets/filter-sources-4C0kF1NU.js +0 -1
- package/core/built/admin/assets/filters-CV5jCb9n.js +0 -1
- package/core/built/admin/assets/filters-Dmr0PoqU.js +0 -1
- package/core/built/admin/assets/first-promoter-modal-OCzSaL3B.js +0 -1
- package/core/built/admin/assets/gift-link-modal-0mzjn5oF.js +0 -1
- package/core/built/admin/assets/growth-55vWlGT_.js +0 -1
- package/core/built/admin/assets/growth-DA5AluYw.js +0 -3
- package/core/built/admin/assets/history-modal-G1s5hHz7.js +0 -1
- package/core/built/admin/assets/html-field-BegxsWpw.js +0 -1
- package/core/built/admin/assets/index-CDP9tIOk.js +0 -4
- package/core/built/admin/assets/index-Dqf2x-ry.css +0 -1
- package/core/built/admin/assets/index.es-BMUCj9TR.js +0 -1
- package/core/built/admin/assets/input-UHrKuvBT.js +0 -1
- package/core/built/admin/assets/invite-user-modal-BrAkxEO3.js +0 -1
- package/core/built/admin/assets/koenig-loader-DwVY_V-H.js +0 -1
- package/core/built/admin/assets/kpi-card-LERqWBoY.js +0 -1
- package/core/built/admin/assets/kpi-helpers-DB3ouhYF.js +0 -1
- package/core/built/admin/assets/kpi-tabs-BGvtMD8S.js +0 -1
- package/core/built/admin/assets/label-picker-BBWykUeA.js +0 -1
- package/core/built/admin/assets/layout-Di5K7doM.js +0 -1
- package/core/built/admin/assets/loading-indicator-SFTqyYVE.js +0 -1
- package/core/built/admin/assets/member-custom-fields-gOv6Hoh3.js +0 -1
- package/core/built/admin/assets/member-detail-D7mZDDp3.js +0 -1
- package/core/built/admin/assets/member-route-0Aa52Ntz.js +0 -1
- package/core/built/admin/assets/members-DvZG9t69.js +0 -2
- package/core/built/admin/assets/moderation-DzzPLDyl.js +0 -1
- package/core/built/admin/assets/navigation-modal-CI8Bx5vn.js +0 -1
- package/core/built/admin/assets/newsletter-Bg3cq5U-.js +0 -1
- package/core/built/admin/assets/newsletter-detail-modal-DQU3r4g2.js +0 -1
- package/core/built/admin/assets/newsletters-DMDfZbMS.js +0 -1
- package/core/built/admin/assets/note-i8k6pjaw.js +0 -1
- package/core/built/admin/assets/notifications-psA_2Ph4.js +0 -1
- package/core/built/admin/assets/offers-container-modal-BWfjSdyg.js +0 -1
- package/core/built/admin/assets/onboarding-DGQuV7Ew.js +0 -1
- package/core/built/admin/assets/overview-C-6xOLaf.js +0 -1
- package/core/built/admin/assets/overview-DaLhpRrC.js +0 -1
- package/core/built/admin/assets/pages-B6vr9F_D.js +0 -1
- package/core/built/admin/assets/popover-CFgqFDvC.js +0 -1
- package/core/built/admin/assets/portal-frame-DjBF-uy2.js +0 -1
- package/core/built/admin/assets/portal-modal-Cf0pBAk8.js +0 -1
- package/core/built/admin/assets/post-analytics-Buz1COoY.js +0 -1
- package/core/built/admin/assets/post-analytics-header-B-P0eFnT.js +0 -1
- package/core/built/admin/assets/posts-KY3aUhVV.js +0 -1
- package/core/built/admin/assets/preferences-Cq1aOiLy.js +0 -5
- package/core/built/admin/assets/profile-D6dHYf2o.js +0 -1
- package/core/built/admin/assets/select-D_RQaXgt.js +0 -1
- package/core/built/admin/assets/settings-B5hJKZ5C.js +0 -1
- package/core/built/admin/assets/settings-DIo_F-MF.js +0 -4
- package/core/built/admin/assets/share-modal-CLBfj377.js +0 -1
- package/core/built/admin/assets/shim-ClF_m5ux.js +0 -1
- package/core/built/admin/assets/slack-modal-Bt9I_tEo.js +0 -1
- package/core/built/admin/assets/sort-button-DMBXF7tP.js +0 -1
- package/core/built/admin/assets/stats-B_YkF7bp.js +0 -1
- package/core/built/admin/assets/stats-view-BOCp9RG8.js +0 -1
- package/core/built/admin/assets/stripe-connect-modal-BeSukI8q.js +0 -1
- package/core/built/admin/assets/tag-detail-2a3BfzTW.js +0 -2
- package/core/built/admin/assets/tags-Dp2_8UzL.js +0 -1
- package/core/built/admin/assets/testimonials-modal-C9nrypNo.js +0 -1
- package/core/built/admin/assets/tier-detail-modal-CxRwgTPR.js +0 -1
- package/core/built/admin/assets/use-check-theme-limit-error-C9-QhGdV.js +0 -1
- package/core/built/admin/assets/use-filter-params-CSpAvgD2.js +0 -1
- package/core/built/admin/assets/use-growth-stats-BOmoPDHJ.js +0 -1
- package/core/built/admin/assets/use-post-referrers-CbXLFIex.js +0 -1
- package/core/built/admin/assets/use-reply-chain-data-JYFKSO83.js +0 -1
- package/core/built/admin/assets/use-settings-navigation-BBzro2-V.js +0 -1
- package/core/built/admin/assets/use-tinybird-query-D2Aeoty5.js +0 -39
- package/core/built/admin/assets/use-tinybird-token-DBuwMmln.js +0 -1
- package/core/built/admin/assets/user-detail-modal-59mBJqxN.js +0 -1
- package/core/built/admin/assets/users-5msE1WMs.js +0 -1
- package/core/built/admin/assets/web-DUuX7nE2.js +0 -2
- package/core/built/admin/assets/web-N8ucs2aG.js +0 -1
- package/core/server/api/endpoints/member-custom-fields.js +0 -100
- package/core/server/services/jobs/worker-model-event-bridge.js +0 -149
- package/core/server/services/members/jobs/clean-expired-comped.js +0 -132
- package/core/server/services/update-check/run-update-check.js +0 -63
- /package/core/server/lib/{db-date.js → db-types/date.js} +0 -0
|
@@ -8,6 +8,7 @@ const bson_objectid_1 = __importDefault(require("bson-objectid"));
|
|
|
8
8
|
const errors_1 = __importDefault(require("@tryghost/errors"));
|
|
9
9
|
const zod_1 = require("zod");
|
|
10
10
|
const custom_field_types_1 = require("@tryghost/custom-field-types");
|
|
11
|
+
const identity_1 = require("@tryghost/custom-field-types/identity");
|
|
11
12
|
const codec_1 = require("./codec");
|
|
12
13
|
const schema_1 = require("./schema");
|
|
13
14
|
const queries_1 = require("./queries");
|
|
@@ -33,10 +34,12 @@ const MAX_KEY_BASE_LENGTH = MAX_KEY_LENGTH - (String(MAX_KEY_ITERATIONS).length
|
|
|
33
34
|
// object from JSON.parse. A key naming a member of Object.prototype reads back as
|
|
34
35
|
// inherited rather than absent wherever one of those objects is indexed.
|
|
35
36
|
//
|
|
36
|
-
// Derived rather than listed,
|
|
37
|
-
// minted:
|
|
38
|
-
//
|
|
39
|
-
|
|
37
|
+
// Derived rather than listed, from the format a key may take rather than from how one
|
|
38
|
+
// is minted: a caller can state a key instead of deriving it from a name, so what has
|
|
39
|
+
// to be reserved is every prototype name a well-formed key could spell. The ones that
|
|
40
|
+
// carry a capital cannot be spelled at all and need no reserving; `constructor` and
|
|
41
|
+
// `__proto__` can.
|
|
42
|
+
const RESERVED_KEYS = Object.getOwnPropertyNames(Object.prototype).filter((name) => key_1.KEY_CHARACTERS.test(name));
|
|
40
43
|
const FieldName = zod_1.z
|
|
41
44
|
.string()
|
|
42
45
|
.trim()
|
|
@@ -90,7 +93,29 @@ class CustomFieldDefinitionsService {
|
|
|
90
93
|
// restart. Where the number comes from is the caller's business.
|
|
91
94
|
this.getMaxDefinitions = getMaxDefinitions;
|
|
92
95
|
}
|
|
96
|
+
async hasAnyActive() {
|
|
97
|
+
const [field] = await this.list((0, queries_1.activeFields)(this.knex).limit(1));
|
|
98
|
+
return Boolean(field);
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* The fields table has no namespace column: every row in it belongs to the publisher's
|
|
102
|
+
* `custom` namespace, so no other namespace can have a stored field.
|
|
103
|
+
*/
|
|
104
|
+
isStored(namespace) {
|
|
105
|
+
return namespace === identity_1.CUSTOM_NAMESPACE;
|
|
106
|
+
}
|
|
107
|
+
assertDefinable(namespace) {
|
|
108
|
+
if (!this.isStored(namespace)) {
|
|
109
|
+
throw new errors_1.default.ValidationError({
|
|
110
|
+
message: `Fields cannot be defined in the "${namespace}" namespace.`,
|
|
111
|
+
property: 'namespace',
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
93
115
|
async browse(options = {}) {
|
|
116
|
+
if (options.namespace !== undefined && !this.isStored(options.namespace)) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
94
119
|
// Archived fields are hidden by default: most surfaces (member details, the
|
|
95
120
|
// filter picker, the importer) only ever want active fields. A caller-
|
|
96
121
|
// supplied `filter` can widen that — Settings pulls active and archived
|
|
@@ -113,12 +138,12 @@ class CustomFieldDefinitionsService {
|
|
|
113
138
|
const rows = await (0, queries_1.inFieldOrder)(query).select('*');
|
|
114
139
|
return rows.map((row) => zod_1.z.decode(codec_1.customFieldCodec, row));
|
|
115
140
|
}
|
|
116
|
-
async read(key) {
|
|
117
|
-
const
|
|
118
|
-
if (!
|
|
141
|
+
async read(namespace, key) {
|
|
142
|
+
const [field] = this.isStored(namespace) ? await this.list((0, queries_1.fieldByKey)(this.knex, key)) : [];
|
|
143
|
+
if (!field) {
|
|
119
144
|
throw new errors_1.default.NotFoundError({ message: 'Custom field not found.' });
|
|
120
145
|
}
|
|
121
|
-
return
|
|
146
|
+
return field;
|
|
122
147
|
}
|
|
123
148
|
/**
|
|
124
149
|
* Create one or more field definitions. All-or-nothing: the batch runs in a
|
|
@@ -130,7 +155,8 @@ class CustomFieldDefinitionsService {
|
|
|
130
155
|
* batch, so two items sharing a name are caught and two items deriving the same
|
|
131
156
|
* key get distinct ones, exactly as if they had arrived as separate requests.
|
|
132
157
|
*/
|
|
133
|
-
async add(context, input) {
|
|
158
|
+
async add(context, namespace, input) {
|
|
159
|
+
this.assertDefinable(namespace);
|
|
134
160
|
const requestedCount = Array.isArray(input) ? input.length : 0;
|
|
135
161
|
const parsed = AddFieldsInput.safeParse(input);
|
|
136
162
|
if (!parsed.success) {
|
|
@@ -167,13 +193,11 @@ class CustomFieldDefinitionsService {
|
|
|
167
193
|
for (const [index, field] of fields.entries()) {
|
|
168
194
|
await this.assertNameAvailable(trx, field.name);
|
|
169
195
|
const key = await this.mintKey(trx, bases[index]);
|
|
170
|
-
await
|
|
171
|
-
id: new bson_objectid_1.default().toHexString(),
|
|
196
|
+
await this.insertField(trx, {
|
|
172
197
|
key,
|
|
173
198
|
name: field.name,
|
|
174
199
|
type: field.type,
|
|
175
|
-
|
|
176
|
-
created_at: new Date(),
|
|
200
|
+
sortOrder: firstSortOrder + index,
|
|
177
201
|
});
|
|
178
202
|
keys.push(key);
|
|
179
203
|
}
|
|
@@ -194,7 +218,69 @@ class CustomFieldDefinitionsService {
|
|
|
194
218
|
// Logged after the commit: the action log is a separate Bookshelf write
|
|
195
219
|
// outside this transaction, so recording inside it would leave orphaned
|
|
196
220
|
// "added" entries for fields a rollback never created.
|
|
197
|
-
|
|
221
|
+
await this.recordCreated(context, created);
|
|
222
|
+
return created;
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Given an executor it joins that transaction; given none it opens its own. Unlike
|
|
226
|
+
* `add`, the key is stated rather than minted from the name, and nothing about it is
|
|
227
|
+
* worked around: a caller states a key because something else already names that key,
|
|
228
|
+
* so a variant of it would be a field nothing points at.
|
|
229
|
+
*
|
|
230
|
+
* The field is returned rather than logged: the history writes on its own connection,
|
|
231
|
+
* which a single-connection pool would deadlock against an open transaction.
|
|
232
|
+
*/
|
|
233
|
+
async addOne(wanted, { executor = this.knex } = {}) {
|
|
234
|
+
// Before any database access, the way `add` mints before opening its transaction:
|
|
235
|
+
// an unusable key is a payload problem worth reporting on its own terms.
|
|
236
|
+
assertKeyUsable(wanted.key);
|
|
237
|
+
const write = async (db) => {
|
|
238
|
+
await this.assertWithinLimit(db, 1);
|
|
239
|
+
await this.assertKeyAvailable(db, wanted.key);
|
|
240
|
+
await this.assertNameAvailable(db, wanted.name);
|
|
241
|
+
await this.insertField(db, {
|
|
242
|
+
key: wanted.key,
|
|
243
|
+
name: wanted.name,
|
|
244
|
+
type: wanted.type,
|
|
245
|
+
sortOrder: await this.nextSortOrder(db),
|
|
246
|
+
});
|
|
247
|
+
const [created] = await this.readMany(db, [wanted.key]);
|
|
248
|
+
return created;
|
|
249
|
+
};
|
|
250
|
+
// knex's marker for a transactor: join it rather than nesting a savepoint under it.
|
|
251
|
+
return executor.isTransaction ? write(executor) : executor.transaction(write);
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Only for a stated key. Minting picks a free one instead, so this reads as a clean 422
|
|
255
|
+
* where the unique index would read as a 500.
|
|
256
|
+
*/
|
|
257
|
+
async assertKeyAvailable(db, key) {
|
|
258
|
+
const taken = await db(TABLE).where('key', key).first();
|
|
259
|
+
if (taken) {
|
|
260
|
+
throw new errors_1.default.ValidationError({
|
|
261
|
+
message: 'A custom field with this key already exists.',
|
|
262
|
+
property: 'key',
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
async insertField(db, field) {
|
|
267
|
+
await db(TABLE).insert({
|
|
268
|
+
id: new bson_objectid_1.default().toHexString(),
|
|
269
|
+
key: field.key,
|
|
270
|
+
name: field.name,
|
|
271
|
+
type: field.type,
|
|
272
|
+
sort_order: field.sortOrder,
|
|
273
|
+
created_at: new Date(),
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
/** `read` for a caller that is deciding rather than serving: absent is an answer. */
|
|
277
|
+
async findByKey(key, { executor = this.knex } = {}) {
|
|
278
|
+
const row = await executor(TABLE).where('key', key).first();
|
|
279
|
+
return row ? zod_1.z.decode(codec_1.customFieldCodec, row) : null;
|
|
280
|
+
}
|
|
281
|
+
/** The same entry `add` writes. Only the caller knows its transaction committed. */
|
|
282
|
+
async recordCreated(context, fields) {
|
|
283
|
+
for (const field of fields) {
|
|
198
284
|
await this.recordAction({
|
|
199
285
|
context,
|
|
200
286
|
verb: 'create',
|
|
@@ -202,7 +288,6 @@ class CustomFieldDefinitionsService {
|
|
|
202
288
|
details: { primary_name: field.name, key: field.key },
|
|
203
289
|
});
|
|
204
290
|
}
|
|
205
|
-
return created;
|
|
206
291
|
}
|
|
207
292
|
/**
|
|
208
293
|
* The operational ceiling on how many definitions a site can hold. This is a
|
|
@@ -249,7 +334,8 @@ class CustomFieldDefinitionsService {
|
|
|
249
334
|
* fresh rank, so a move is well-defined even where every rank is still the default.
|
|
250
335
|
* Returns every definition, archived included, matching what the request named.
|
|
251
336
|
*/
|
|
252
|
-
async reorder(context, input) {
|
|
337
|
+
async reorder(context, namespace, input) {
|
|
338
|
+
this.assertDefinable(namespace);
|
|
253
339
|
const parsed = ReorderInput.safeParse(input);
|
|
254
340
|
if (!parsed.success) {
|
|
255
341
|
const issue = parsed.error.issues[0];
|
|
@@ -368,7 +454,10 @@ class CustomFieldDefinitionsService {
|
|
|
368
454
|
});
|
|
369
455
|
}
|
|
370
456
|
}
|
|
371
|
-
async edit(context, key, input) {
|
|
457
|
+
async edit(context, namespace, key, input) {
|
|
458
|
+
if (!this.isStored(namespace)) {
|
|
459
|
+
throw new errors_1.default.NotFoundError({ message: 'Custom field not found.' });
|
|
460
|
+
}
|
|
372
461
|
const parsed = EditFieldInput.safeParse(input);
|
|
373
462
|
if (!parsed.success) {
|
|
374
463
|
throw new errors_1.default.ValidationError({
|
|
@@ -377,7 +466,7 @@ class CustomFieldDefinitionsService {
|
|
|
377
466
|
});
|
|
378
467
|
}
|
|
379
468
|
const patch = parsed.data;
|
|
380
|
-
const existing = await this.read(key);
|
|
469
|
+
const existing = await this.read(identity_1.CUSTOM_NAMESPACE, key);
|
|
381
470
|
// Key and type are immutable after creation: values are addressed by key
|
|
382
471
|
// and interpreted by type, so changing either would silently orphan or
|
|
383
472
|
// corrupt stored values. Reject a differing value rather than ignore it.
|
|
@@ -431,7 +520,7 @@ class CustomFieldDefinitionsService {
|
|
|
431
520
|
details: { primary_name: patch.name ?? existing.name, key },
|
|
432
521
|
});
|
|
433
522
|
}
|
|
434
|
-
return this.read(key);
|
|
523
|
+
return this.read(identity_1.CUSTOM_NAMESPACE, key);
|
|
435
524
|
}
|
|
436
525
|
/**
|
|
437
526
|
* Permanently delete a field and every member value attached to it (the FK
|
|
@@ -440,7 +529,10 @@ class CustomFieldDefinitionsService {
|
|
|
440
529
|
* which makes accidental data loss a deliberate two-step. Archiving is the
|
|
441
530
|
* reversible soft state (see `edit` with a status change).
|
|
442
531
|
*/
|
|
443
|
-
async destroy(context, key) {
|
|
532
|
+
async destroy(context, namespace, key) {
|
|
533
|
+
if (!this.isStored(namespace)) {
|
|
534
|
+
throw new errors_1.default.NotFoundError({ message: 'Custom field not found.' });
|
|
535
|
+
}
|
|
444
536
|
const field = await this.knex(TABLE).where('key', key).first();
|
|
445
537
|
if (!field) {
|
|
446
538
|
throw new errors_1.default.NotFoundError({ message: 'Custom field not found.' });
|
|
@@ -460,6 +552,36 @@ class CustomFieldDefinitionsService {
|
|
|
460
552
|
}
|
|
461
553
|
}
|
|
462
554
|
exports.CustomFieldDefinitionsService = CustomFieldDefinitionsService;
|
|
555
|
+
/**
|
|
556
|
+
* The shape a stated key has to have. Minting derives one that is usable by
|
|
557
|
+
* construction, so this is the check that path never needed: a caller stating its own
|
|
558
|
+
* key has said nothing about the format, and guarding here covers every route in
|
|
559
|
+
* rather than whichever one arrived first.
|
|
560
|
+
*
|
|
561
|
+
* The length bound is the column's own, not `mintKey`'s: that one holds back room for a
|
|
562
|
+
* `_<n>` collision suffix, and a stated key is written exactly as given and never
|
|
563
|
+
* suffixed, so the whole column is available to it.
|
|
564
|
+
*/
|
|
565
|
+
function assertKeyUsable(key) {
|
|
566
|
+
if (!key_1.KEY_CHARACTERS.test(key)) {
|
|
567
|
+
throw new errors_1.default.ValidationError({
|
|
568
|
+
message: 'A custom field key can only contain lowercase letters, numbers and underscores.',
|
|
569
|
+
property: 'key',
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
if (key.length > MAX_KEY_LENGTH) {
|
|
573
|
+
throw new errors_1.default.ValidationError({
|
|
574
|
+
message: `A custom field key can be at most ${MAX_KEY_LENGTH} characters.`,
|
|
575
|
+
property: 'key',
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
if (RESERVED_KEYS.includes(key)) {
|
|
579
|
+
throw new errors_1.default.ValidationError({
|
|
580
|
+
message: `${key} cannot be used as a custom field key.`,
|
|
581
|
+
property: 'key',
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
}
|
|
463
585
|
// The field a zod issue points at. Create validates an array, so an issue's path
|
|
464
586
|
// is prefixed with the item's index (`[0, 'name']`); `property` names the field
|
|
465
587
|
// that is wrong, so the numeric prefix is dropped. Which item it was is reported
|
|
@@ -490,6 +612,24 @@ function applyFilter(query, filter) {
|
|
|
490
612
|
let mongoQuery;
|
|
491
613
|
try {
|
|
492
614
|
mongoQuery = nql(filter).toJSON();
|
|
615
|
+
}
|
|
616
|
+
catch (err) {
|
|
617
|
+
throw new errors_1.default.BadRequestError({
|
|
618
|
+
message: 'Could not parse the filter parameter.',
|
|
619
|
+
property: 'filter',
|
|
620
|
+
err: err,
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
// The table has no `namespace` column, so mongo-knex would compile this into SQL against a
|
|
624
|
+
// column that does not exist. Refuse it at the edge, naming the URL as the way to pick a
|
|
625
|
+
// namespace, rather than surface a database error.
|
|
626
|
+
if (filterReferencesAttribute(mongoQuery, 'namespace')) {
|
|
627
|
+
throw new errors_1.default.BadRequestError({
|
|
628
|
+
message: 'Filtering on namespace is not supported. Scope by the namespace route instead.',
|
|
629
|
+
property: 'filter',
|
|
630
|
+
});
|
|
631
|
+
}
|
|
632
|
+
try {
|
|
493
633
|
knexify(query, mongoQuery, { tableName: TABLE });
|
|
494
634
|
}
|
|
495
635
|
catch (err) {
|
|
@@ -508,12 +648,15 @@ function applyFilter(query, filter) {
|
|
|
508
648
|
// $and/$or/$nor combinators — so a status filter at any nesting counts as the
|
|
509
649
|
// caller opting in to (or out of) archived fields.
|
|
510
650
|
function filterReferencesStatus(query) {
|
|
651
|
+
return filterReferencesAttribute(query, 'status');
|
|
652
|
+
}
|
|
653
|
+
function filterReferencesAttribute(query, attribute) {
|
|
511
654
|
return Object.entries(query).some(([key, value]) => {
|
|
512
|
-
if (key ===
|
|
655
|
+
if (key === attribute) {
|
|
513
656
|
return true;
|
|
514
657
|
}
|
|
515
658
|
if ((key === '$and' || key === '$or' || key === '$nor') && Array.isArray(value)) {
|
|
516
|
-
return value.some((sub) =>
|
|
659
|
+
return value.some((sub) => filterReferencesAttribute(sub, attribute));
|
|
517
660
|
}
|
|
518
661
|
return false;
|
|
519
662
|
});
|
|
@@ -5,34 +5,27 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.CUSTOM_FIELDS_RELATION = void 0;
|
|
7
7
|
exports.createCustomFieldsFilterTransformer = createCustomFieldsFilterTransformer;
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
// `key
|
|
11
|
-
//
|
|
8
|
+
// Translates the metafield clauses of a members filter into a query over the
|
|
9
|
+
// values table. A metafield is a publisher-defined field on a member; it is
|
|
10
|
+
// addressed as `namespace.key`, and a composite value such as an address names
|
|
11
|
+
// one of its parts by appending it: `namespace.key.part`.
|
|
12
12
|
//
|
|
13
|
-
// (
|
|
14
|
-
// (
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
// (custom_fields.key:'shipping_address'+custom_fields.path:-'country') // part not set
|
|
13
|
+
// (metafields.key:'custom.company'+metafields.value:'Ghost')
|
|
14
|
+
// (metafields.key:'custom.shipping_address.country'+metafields.value:'GB')
|
|
15
|
+
// metafields.key:'custom.company' // has a value
|
|
16
|
+
// metafields.key:-'custom.shipping_address.country' // that part is not set
|
|
18
17
|
//
|
|
19
|
-
// The values table
|
|
20
|
-
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
23
|
-
//
|
|
24
|
-
// `$not` asserts none does. The public grammar is the storage vocabulary here — the key
|
|
25
|
-
// sits on the row — so the transformer emits it straight, with no id lookup and no field
|
|
26
|
-
// catalog read. That is what lets the Member model chain it inline on every members query,
|
|
27
|
-
// rather than each call site remembering to wire it. A key that names no current field
|
|
28
|
-
// matches no leaf; the admin never offers an archived or deleted field in the filter UI,
|
|
29
|
-
// though the API leaves them queryable.
|
|
18
|
+
// The values table holds one row per stored value: `custom_field_key`, `path`
|
|
19
|
+
// (empty for a plain field, the part's name for one part of a composite) and
|
|
20
|
+
// `value_text`. A filter addresses those columns directly, so each one becomes a
|
|
21
|
+
// single $elemMatch over them — positive asserts a matching row exists, $not
|
|
22
|
+
// asserts none does — with no lookup against the field definitions.
|
|
30
23
|
const errors_1 = __importDefault(require("@tryghost/errors"));
|
|
31
|
-
const
|
|
24
|
+
const identity_1 = require("@tryghost/custom-field-types/identity");
|
|
25
|
+
const RELATION = 'metafields';
|
|
32
26
|
const PREFIX = `${RELATION}.`;
|
|
33
27
|
const KEY_ATTRIBUTE = `${PREFIX}key`;
|
|
34
28
|
const VALUE_ATTRIBUTE = `${PREFIX}value`;
|
|
35
|
-
const PATH_ATTRIBUTE = `${PREFIX}path`;
|
|
36
29
|
const ROOT_PATH = '';
|
|
37
30
|
// Only recurse into plain objects/arrays; a RegExp, Date, etc. is a leaf value that
|
|
38
31
|
// must pass through by reference (recursing would strip a regex to `{}`).
|
|
@@ -42,8 +35,7 @@ function isPlainObject(value) {
|
|
|
42
35
|
!Array.isArray(value) &&
|
|
43
36
|
(Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null));
|
|
44
37
|
}
|
|
45
|
-
|
|
46
|
-
function isCustomFieldClause(node) {
|
|
38
|
+
function isMetafieldClause(node) {
|
|
47
39
|
if (!isPlainObject(node)) {
|
|
48
40
|
return false;
|
|
49
41
|
}
|
|
@@ -57,100 +49,94 @@ function isCustomFieldClause(node) {
|
|
|
57
49
|
// `key:'a'+key:'b'`, since a bare key clause needs no group of its own. That is two
|
|
58
50
|
// filters — a member with a value for both — not one leaf that is somehow both fields, so
|
|
59
51
|
// it has to fall through and be transformed a clause at a time.
|
|
60
|
-
function
|
|
61
|
-
if (!Array.isArray(clauses) || clauses.length < 2 || !clauses.every(
|
|
52
|
+
function isMetafieldCompound(clauses) {
|
|
53
|
+
if (!Array.isArray(clauses) || clauses.length < 2 || !clauses.every(isMetafieldClause)) {
|
|
62
54
|
return false;
|
|
63
55
|
}
|
|
64
56
|
return clauses.filter((clause) => Object.keys(clause)[0] === KEY_ATTRIBUTE).length === 1;
|
|
65
57
|
}
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
// attribute isn't a value clause.
|
|
69
|
-
function pathForValueAttribute(attribute) {
|
|
70
|
-
if (attribute === VALUE_ATTRIBUTE) {
|
|
71
|
-
return ROOT_PATH;
|
|
72
|
-
}
|
|
73
|
-
if (attribute.startsWith(`${VALUE_ATTRIBUTE}.`)) {
|
|
74
|
-
return attribute.slice(`${VALUE_ATTRIBUTE}.`.length);
|
|
75
|
-
}
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
// The single `{$ne}` shape all negations arrive as (is-not-set on a key or part).
|
|
58
|
+
// NQL compiles every negation in this grammar to `{$ne: <string>}`, so a negated clause
|
|
59
|
+
// can arrive in no other shape.
|
|
79
60
|
function negatedString(value) {
|
|
80
61
|
return isPlainObject(value) && typeof value.$ne === 'string' ? value.$ne : null;
|
|
81
62
|
}
|
|
63
|
+
const ANY_PATH = null;
|
|
64
|
+
// A stored key never contains a dot, so a full dotted identity is a key no stored
|
|
65
|
+
// row can equal.
|
|
66
|
+
function unmatchableLeaf(identity) {
|
|
67
|
+
return { key: (0, identity_1.formatIdentity)(identity), path: identity.partPath };
|
|
68
|
+
}
|
|
69
|
+
function parseLeafAddress(raw) {
|
|
70
|
+
const identity = typeof raw === 'string' ? (0, identity_1.parseIdentity)(raw) : null;
|
|
71
|
+
if (!identity) {
|
|
72
|
+
throw new errors_1.default.BadRequestError({
|
|
73
|
+
message: `A metafield filter names a field as namespace.key, for example (${KEY_ATTRIBUTE}:'custom.company'+${VALUE_ATTRIBUTE}:'Ghost').`,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
if (identity.namespace !== identity_1.CUSTOM_NAMESPACE) {
|
|
77
|
+
return unmatchableLeaf(identity);
|
|
78
|
+
}
|
|
79
|
+
return { key: identity.key, path: identity.partPath };
|
|
80
|
+
}
|
|
82
81
|
function createCustomFieldsFilterTransformer() {
|
|
83
|
-
// The grammar carries the field key in the clause value; take it as a string,
|
|
84
|
-
// defaulting to one no field can hold so a malformed clause matches nothing.
|
|
85
|
-
const keyOf = (value) => (typeof value === 'string' ? value : '');
|
|
86
82
|
// One (maybe-negated) $elemMatch over the leaf columns: positive is "a leaf pinned
|
|
87
83
|
// by these matches", `$not` is "no leaf does".
|
|
88
|
-
function buildElemMatch(
|
|
89
|
-
const match = { custom_field_key:
|
|
84
|
+
function buildElemMatch(target, value, negate) {
|
|
85
|
+
const match = { custom_field_key: target.key };
|
|
86
|
+
if (value !== undefined) {
|
|
87
|
+
match.path = target.path ?? ROOT_PATH;
|
|
88
|
+
match.value_text = value;
|
|
89
|
+
}
|
|
90
|
+
else if (target.path !== ANY_PATH) {
|
|
91
|
+
match.path = target.path;
|
|
92
|
+
}
|
|
90
93
|
if (negate) {
|
|
91
94
|
return { [RELATION]: { $not: { $elemMatch: match } } };
|
|
92
95
|
}
|
|
93
96
|
return { [RELATION]: { $elemMatch: match } };
|
|
94
97
|
}
|
|
95
|
-
// A `(key + value[.part] | path)` compound. A `value` clause pins the part and its
|
|
96
|
-
// value; a `path` clause pins the part's presence (no value), and a negated path is
|
|
97
|
-
// is-not-set — no such leaf — which negates the whole match.
|
|
98
98
|
function toElemMatch(clauses) {
|
|
99
|
-
let
|
|
100
|
-
let
|
|
101
|
-
let
|
|
102
|
-
const conditions = {};
|
|
103
|
-
let negate = false;
|
|
104
|
-
const oneLeafOnly = () => {
|
|
105
|
-
if (hasLeaf) {
|
|
106
|
-
throw new errors_1.default.BadRequestError({
|
|
107
|
-
message: 'A custom field filter takes one value or path clause.',
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
hasLeaf = true;
|
|
111
|
-
};
|
|
99
|
+
let target;
|
|
100
|
+
let value;
|
|
101
|
+
let hasValue = false;
|
|
112
102
|
for (const clause of clauses) {
|
|
113
|
-
const [attribute,
|
|
103
|
+
const [attribute, clauseValue] = Object.entries(clause)[0];
|
|
114
104
|
if (attribute === KEY_ATTRIBUTE) {
|
|
115
|
-
if (
|
|
105
|
+
if (negatedString(clauseValue) !== null) {
|
|
116
106
|
throw new errors_1.default.BadRequestError({
|
|
117
|
-
message: `A
|
|
107
|
+
message: `A negated "${KEY_ATTRIBUTE}" stands alone; it cannot be grouped with a value.`,
|
|
118
108
|
});
|
|
119
109
|
}
|
|
120
|
-
|
|
121
|
-
fieldKey = keyOf(value);
|
|
122
|
-
}
|
|
123
|
-
else if (attribute === PATH_ATTRIBUTE) {
|
|
124
|
-
oneLeafOnly();
|
|
125
|
-
const negatedPath = negatedString(value);
|
|
126
|
-
conditions.path = negatedPath ?? value;
|
|
127
|
-
negate = negatedPath !== null;
|
|
110
|
+
target = parseLeafAddress(clauseValue);
|
|
128
111
|
}
|
|
129
|
-
else {
|
|
130
|
-
|
|
131
|
-
if (path === null) {
|
|
132
|
-
// Not the key, a value/value.<part>, or a path: it names no leaf
|
|
133
|
-
// column, so dropping it would silently widen the match to the key
|
|
134
|
-
// alone. Fail closed instead.
|
|
112
|
+
else if (attribute === VALUE_ATTRIBUTE) {
|
|
113
|
+
if (hasValue) {
|
|
135
114
|
throw new errors_1.default.BadRequestError({
|
|
136
|
-
message: `
|
|
115
|
+
message: `A metafield filter takes one "${VALUE_ATTRIBUTE}" clause.`,
|
|
137
116
|
});
|
|
138
117
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
conditions.value_text = value;
|
|
118
|
+
hasValue = true;
|
|
119
|
+
value = clauseValue;
|
|
142
120
|
}
|
|
121
|
+
else {
|
|
122
|
+
throw new errors_1.default.BadRequestError({
|
|
123
|
+
message: `Unsupported metafield filter clause "${attribute}".`,
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
if (!target) {
|
|
128
|
+
throw new errors_1.default.BadRequestError({
|
|
129
|
+
message: `A metafield filter needs a "${KEY_ATTRIBUTE}" clause.`,
|
|
130
|
+
});
|
|
143
131
|
}
|
|
144
|
-
return buildElemMatch(
|
|
132
|
+
return buildElemMatch(target, hasValue ? value : undefined, false);
|
|
145
133
|
}
|
|
146
|
-
// A standalone `key` clause is whole-field is-set / is-not-set: a leaf for this
|
|
147
|
-
// field exists (any part), or none does.
|
|
148
134
|
function fromKeyClause(value) {
|
|
149
135
|
const negatedKey = negatedString(value);
|
|
150
136
|
if (negatedKey !== null) {
|
|
151
|
-
return buildElemMatch(
|
|
137
|
+
return buildElemMatch(parseLeafAddress(negatedKey), undefined, true);
|
|
152
138
|
}
|
|
153
|
-
return buildElemMatch(
|
|
139
|
+
return buildElemMatch(parseLeafAddress(value), undefined, false);
|
|
154
140
|
}
|
|
155
141
|
function transform(node) {
|
|
156
142
|
if (Array.isArray(node)) {
|
|
@@ -159,7 +145,7 @@ function createCustomFieldsFilterTransformer() {
|
|
|
159
145
|
if (!isPlainObject(node)) {
|
|
160
146
|
return node;
|
|
161
147
|
}
|
|
162
|
-
if (
|
|
148
|
+
if (isMetafieldCompound(node.$and)) {
|
|
163
149
|
return toElemMatch(node.$and);
|
|
164
150
|
}
|
|
165
151
|
const out = {};
|
|
@@ -168,12 +154,8 @@ function createCustomFieldsFilterTransformer() {
|
|
|
168
154
|
Object.assign(out, fromKeyClause(value));
|
|
169
155
|
}
|
|
170
156
|
else if (key.startsWith(PREFIX)) {
|
|
171
|
-
// A value or path clause only means something paired with its key in one
|
|
172
|
-
// leaf, which arrives as the compound handled above. A `custom_fields.*`
|
|
173
|
-
// clause reaching here is unpaired (or its pair was not grouped), so fail
|
|
174
|
-
// closed rather than pass it to mongo-knex as a column that does not exist.
|
|
175
157
|
throw new errors_1.default.BadRequestError({
|
|
176
|
-
message: `A "${key}" filter must be grouped with its "${KEY_ATTRIBUTE}" clause, for example (${KEY_ATTRIBUTE}:'a_field'+${VALUE_ATTRIBUTE}:'a value').`,
|
|
158
|
+
message: `A "${key}" filter must be grouped with its "${KEY_ATTRIBUTE}" clause, for example (${KEY_ATTRIBUTE}:'custom.a_field'+${VALUE_ATTRIBUTE}:'a value').`,
|
|
177
159
|
});
|
|
178
160
|
}
|
|
179
161
|
else {
|
|
@@ -189,7 +171,7 @@ function createCustomFieldsFilterTransformer() {
|
|
|
189
171
|
* rows (one per field, or per part of a composite field), and a predicate asks whether
|
|
190
172
|
* one of them matches, so this joins the values table on member_id and mongo-knex emits
|
|
191
173
|
* it as a correlated `members.id IN (…)` subquery — composing with every other member
|
|
192
|
-
* filter.
|
|
174
|
+
* filter.
|
|
193
175
|
*/
|
|
194
176
|
exports.CUSTOM_FIELDS_RELATION = {
|
|
195
177
|
tableName: 'members_custom_field_values',
|
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.values = exports.definitions = void 0;
|
|
3
|
+
exports.bindings = exports.values = exports.definitions = exports.actingContext = void 0;
|
|
4
4
|
exports.init = init;
|
|
5
5
|
const definitions_service_1 = require("./definitions-service");
|
|
6
6
|
const values_service_1 = require("./values-service");
|
|
7
|
+
const bindings_service_1 = require("./bindings-service");
|
|
7
8
|
const actions_1 = require("./actions");
|
|
8
9
|
const config_1 = require("./config");
|
|
10
|
+
var actions_2 = require("./actions");
|
|
11
|
+
Object.defineProperty(exports, "actingContext", { enumerable: true, get: function () { return actions_2.actingContext; } });
|
|
9
12
|
function init() {
|
|
10
|
-
// The
|
|
11
|
-
//
|
|
12
|
-
if (exports.definitions && exports.values) {
|
|
13
|
+
// The three are constructed together below, so checking all of them keeps the "all or
|
|
14
|
+
// none" invariant explicit rather than trusting one to stand in for the rest.
|
|
15
|
+
if (exports.definitions && exports.values && exports.bindings) {
|
|
13
16
|
return;
|
|
14
17
|
}
|
|
15
18
|
const { knex } = require('../../data/db');
|
|
@@ -31,4 +34,8 @@ function init() {
|
|
|
31
34
|
knex,
|
|
32
35
|
getMaxDefinitions: () => (0, config_1.resolveMaxDefinitions)(config.get('members:customFields:maxDefinitions')),
|
|
33
36
|
});
|
|
37
|
+
// Built after the values, which is what a binding routes into. It has no handle on the
|
|
38
|
+
// definitions: making a field is not part of binding to one, so a caller that needs both
|
|
39
|
+
// asks for both.
|
|
40
|
+
exports.bindings = new bindings_service_1.CustomFieldBindingsService({ knex, values: exports.values });
|
|
34
41
|
}
|
|
@@ -2,11 +2,12 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.KEY_CHARACTERS = void 0;
|
|
4
4
|
exports.mintableKey = mintableKey;
|
|
5
|
+
const identity_1 = require("@tryghost/custom-field-types/identity");
|
|
5
6
|
// Neither package ships types.
|
|
6
7
|
const { stripInvisibleChars } = require('@tryghost/string');
|
|
7
8
|
const unidecode = require('unidecode');
|
|
8
9
|
/** Every character a key may contain. Nothing else survives minting. */
|
|
9
|
-
exports.KEY_CHARACTERS =
|
|
10
|
+
exports.KEY_CHARACTERS = identity_1.IDENTITY_SEGMENT;
|
|
10
11
|
/**
|
|
11
12
|
* Mint the key a field is addressed by, from the name a publisher chose.
|
|
12
13
|
*
|
|
@@ -4,9 +4,9 @@ exports.CustomField = void 0;
|
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const custom_field_types_1 = require("@tryghost/custom-field-types");
|
|
6
6
|
const schema_1 = require("./schema");
|
|
7
|
-
// The domain shape of a field definition (camelCase; distinct from the DB row).
|
|
8
7
|
exports.CustomField = zod_1.z.object({
|
|
9
8
|
id: zod_1.z.string(),
|
|
9
|
+
namespace: zod_1.z.string(),
|
|
10
10
|
key: zod_1.z.string(),
|
|
11
11
|
name: zod_1.z.string(),
|
|
12
12
|
type: custom_field_types_1.FieldTypeSchema,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.activeFields = activeFields;
|
|
4
|
+
exports.fieldByKey = fieldByKey;
|
|
4
5
|
exports.inFieldOrder = inFieldOrder;
|
|
5
6
|
const schema_1 = require("./schema");
|
|
6
7
|
const FIELDS_TABLE = 'members_custom_fields';
|
|
@@ -11,6 +12,10 @@ const FIELDS_TABLE = 'members_custom_fields';
|
|
|
11
12
|
function activeFields(db) {
|
|
12
13
|
return db(FIELDS_TABLE).where('status', schema_1.FIELD_STATUS.active);
|
|
13
14
|
}
|
|
15
|
+
/** One field by key, as the same kind of query the rest of this reads through. */
|
|
16
|
+
function fieldByKey(db, key) {
|
|
17
|
+
return db(FIELDS_TABLE).where(`${FIELDS_TABLE}.key`, key);
|
|
18
|
+
}
|
|
14
19
|
/**
|
|
15
20
|
* The publisher's order, applied to every read of the list. Here for the same reason the
|
|
16
21
|
* status filter is: a read that forgets it comes back in whatever order the engine chose.
|