ghost 6.61.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 +54 -22
- package/core/built/admin/assets/Combination-B0l4MKVh.js +41 -0
- package/core/built/admin/assets/InterVariable-DiVDrmQJ.woff2 +0 -0
- package/core/built/admin/assets/InterVariable-Italic-FCBEiFp6.woff2 +0 -0
- package/core/built/admin/assets/{about-Bve7IJS2.js → about-Dyae_UT6.js} +1 -1
- package/core/built/admin/assets/account-migration-CESa0mnX.js +1 -0
- package/core/built/admin/assets/activitypub/{account-migration-C-aej4x_.js → account-migration-By69CgCp.js} +16 -16
- package/core/built/admin/assets/activitypub/activitypub.js +9 -9
- package/core/built/admin/assets/activitypub/{bluesky-sharing-BXkA5mqV.js → bluesky-sharing-BJcPMrDd.js} +36 -36
- package/core/built/admin/assets/activitypub/{content-formatters-CHcyMFk4.js → content-formatters-3rTHg3mp.js} +3 -3
- package/core/built/admin/assets/activitypub/{domain-B3ihkPde.js → domain-DUR4xSkV.js} +121 -121
- package/core/built/admin/assets/activitypub/{edit-profile-DsZM3rTX.js → edit-profile-BqtfzxCp.js} +40 -40
- package/core/built/admin/assets/activitypub/{explore-BodNrZgR.js → explore-BxuJd-J4.js} +26 -26
- package/core/built/admin/assets/activitypub/{feed-PwH1iJ84.js → feed-BM1TcksA.js} +48 -48
- package/core/built/admin/assets/activitypub/{field-D06-kmOJ.js → field-Dn7HHx2-.js} +2 -2
- package/core/built/admin/assets/activitypub/inbox-CCjkuow2.js +1284 -0
- 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-D1Yapsod.js → moderation-CnmxEB8w.js} +91 -91
- package/core/built/admin/assets/activitypub/{note-C5zjqq5C.js → note-ZKc_Bnba.js} +23 -23
- package/core/built/admin/assets/activitypub/{notifications-B9p8eCHD.js → notifications-CB2tJZau.js} +48 -48
- 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-BCqUdC3v.js → preferences-0-JZnUkI.js} +1519 -1519
- package/core/built/admin/assets/activitypub/{profile-BRCWoFem.js → profile-C35cFIhM.js} +183 -183
- package/core/built/admin/assets/activitypub/{routes-BSXT992i.js → routes-BD4vscyc.js} +740 -731
- 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-B-YfsVeO.js → step-1-BMSx7c_X.js} +4 -4
- package/core/built/admin/assets/activitypub/{step-3-D6BFMXL_.js → step-3-CLlOZTyt.js} +3 -3
- package/core/built/admin/assets/activitypub/{tabs-GpNecvfB.js → tabs-Dxk-b9M-.js} +306 -306
- package/core/built/admin/assets/activitypub/{topic-filter-BPDeQQys.js → topic-filter-5yZH6tId.js} +2 -2
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-BoVOxXkV.js → use-activity-pub-queries-CV50qe03.js} +2 -2
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-wVBX7swM.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/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-DM3cYOpo.js → announcement-bar-modal-Ci4zz8jO.js} +1 -1
- package/core/built/admin/assets/{api-keys-BLm2NDBh.js → api-keys-Ck-Yteiv.js} +1 -1
- package/core/built/admin/assets/{audience-9mwU5TW6.js → audience-CtbA54Sn.js} +1 -1
- 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/{breadcrumb-DLiqCyFc.js → breadcrumb-zUrZrBtW.js} +1 -1
- package/core/built/admin/assets/{button-B2jcu6bk.js → button-eNMKQWXr.js} +1 -1
- package/core/built/admin/assets/{chart-helpers-Fo3bUL3K.js → chart-helpers-CfBKt0vT.js} +1 -1
- package/core/built/admin/assets/{checkbox-BfKktMph.js → checkbox-CgOn_tKW.js} +1 -1
- package/core/built/admin/assets/{chunk.524.60ca4692d91c6022042f.js → chunk.524.a697dee88db67fdf6021.js} +4 -4
- package/core/built/admin/assets/{chunk.582.525c7617c2d9e94c940a.js → chunk.582.5ed5d124cfca9c259b9c.js} +6 -6
- package/core/built/admin/assets/{chunk.896.435fe0637944d4a69810.js → chunk.598.7e056fabb47ce7dbd73f.js} +14 -14
- package/core/built/admin/assets/{code-editor-view-CYVu4EW6.js → code-editor-view-BYNoAoNV.js} +1 -1
- package/core/built/admin/assets/{code-editor-view-DCQJxsfU.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/{content-api-modal-CyQEC3fr.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-B3M1s78L.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/{date-range-select-DD2RKkgB.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/dirty-confirm-dialog-BZ1iytEd.js +1 -0
- package/core/built/admin/assets/{disabled-sources-indicator-Cvy47bE2.js → disabled-sources-indicator-DMRrxsiJ.js} +1 -1
- package/core/built/admin/assets/dist-Dcsslyo72.js +1 -0
- 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-DeBMLxlL.js → embed-signup-form-modal-Ds6mPEi5.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-Cj8lmDOo.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-3894eedcc6968f658578c0abfe942aab.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/growth-BWIu-6xj.js +3 -0
- package/core/built/admin/assets/growth-DU29oxeA.js +1 -0
- package/core/built/admin/assets/history-modal-CFd1Hvx6.js +1 -0
- package/core/built/admin/assets/{html-field-C8qVGGC1.js → html-field-CJBCLTvv.js} +1 -1
- package/core/built/admin/assets/{images-h8VZEQ9k.js → images-scfkWZiK.js} +1 -1
- package/core/built/admin/assets/{inbox-BYd1M06Q.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/{input-group-D8sCXRF5.js → input-group-CMhe2DvY.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-_-IsVI7E.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-DZQk_0W8.js → kpi-card-DBRMfGOW.js} +1 -1
- package/core/built/admin/assets/{kpi-card-DW_2y3ug.js → kpi-card-DFafD1qq.js} +1 -1
- package/core/built/admin/assets/{kpi-helpers-PueTKuA6.js → kpi-helpers-Cv8i8hns.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-CojDs-Qz.js → kpi-tabs-BJItvLYm.js} +1 -1
- package/core/built/admin/assets/{label-DjbT91GM.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/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/moderation-AgCIEso4.js +1 -0
- package/core/built/admin/assets/{multi-select-combobox-B6LiRzDO.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-CFeLlt4z.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-Dmc4IPm3.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-BR_nbIPp.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-BR9hEvS8.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-CLfKGWZy.js → post-analytics-provider-Bo1gmYaP.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-Cl0NXRJr.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-modal-C-BL-F9k.js → preview-modal-BfeFqi5y.js} +1 -1
- package/core/built/admin/assets/profile-B8mx2yAu.js +1 -0
- package/core/built/admin/assets/radio-group-DczlLKOn.js +1 -0
- package/core/built/admin/assets/{schemas-4UPic-oD.js → schemas-DppqmpeJ.js} +1 -1
- 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-CyP_9zTo.js → settings-breadcrumbs-DLzbRRSt.js} +1 -1
- package/core/built/admin/assets/share-modal-D2-Bz1_I.js +1 -0
- package/core/built/admin/assets/slack-modal-DYDTUria.js +1 -0
- package/core/built/admin/assets/{sort-button-Cc-Z96sA.js → sort-button-VVeQjjbu.js} +1 -1
- package/core/built/admin/assets/{source-icon-v6PUnmyM.js → source-icon-Ck5_5Kgq.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-DSRp2vBj.js → step-1-CsaBjeN4.js} +1 -1
- package/core/built/admin/assets/{step-2-COqv2g0u.js → step-2-D2vnxOOM.js} +1 -1
- package/core/built/admin/assets/{step-3-6W1NMVQc.js → step-3-wBoLdGEb.js} +2 -2
- package/core/built/admin/assets/{stripe-button-D2K42Uty.js → stripe-button-CDgLz0S0.js} +1 -1
- package/core/built/admin/assets/stripe-connect-modal-CSjMJetP.js +1 -0
- package/core/built/admin/assets/{table-B2pIKjVh.js → table-Do6iQ83Q.js} +1 -1
- package/core/built/admin/assets/{tabs-SaW_UT-Q.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/tier-detail-modal-BOs8e_G8.js +1 -0
- package/core/built/admin/assets/{toggle-group-EM9WpESu.js → toggle-group-ByUJsxwD.js} +1 -1
- package/core/built/admin/assets/{topic-filter-DY4gNHn2.js → topic-filter-BgC3g5_v.js} +1 -1
- package/core/built/admin/assets/{transistor-modal-CpyPG-MH.js → transistor-modal-Drr0SNfo.js} +1 -1
- package/core/built/admin/assets/{trend-badge-ByMkuAuH.js → trend-badge-6ye93YAW.js} +1 -1
- package/core/built/admin/assets/{unsplash-modal-Cv2Q6GtY.js → unsplash-modal-p6AoQoWp.js} +1 -1
- package/core/built/admin/assets/{use-analytics-data-CHvjMYuT.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-Qv4SVXqw.js → use-check-theme-limit-error-Cw3WQHy8.js} +1 -1
- 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-cwJvsNBo.js → use-limiter-D2WRRx0H.js} +1 -1
- package/core/built/admin/assets/{use-pintura-config-CzTfnufz.js → use-pintura-config-h-J0Lvta.js} +1 -1
- package/core/built/admin/assets/{use-pintura-editor-BDBXWUYq.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-Bg6BhBQQ.js → use-router-history-entry-uldn7zJO.js} +1 -1
- package/core/built/admin/assets/{use-setting-group-VM7FQwMY.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-Dum6Irbs.js → use-simple-pagination-DzJWobPR.js} +1 -1
- package/core/built/admin/assets/{use-staff-users-cZg4ugOF.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-BAy0RANR.js → use-unsaved-changes-guard-CBzs-KPi.js} +1 -1
- package/core/built/admin/assets/{use-url-input-Df5QzNa7.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-DEx8WgiO.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-Cw3BTBUu.js → zapier-modal-BBFfI6Ml.js} +1 -1
- package/core/built/admin/index.html +17 -22
- package/core/frontend/apps/private-blogging/lib/views/private.hbs +44 -43
- package/core/frontend/helpers/cancel_link.js +2 -2
- package/core/frontend/helpers/pagination.js +2 -2
- package/core/frontend/helpers/social_accounts.js +2 -2
- package/core/frontend/public/ghost.css +1 -1
- package/core/frontend/public/ghost.min.css +1 -1
- package/core/frontend/services/sitemap/user-map-generator.js +9 -2
- package/core/server/api/endpoints/index.js +2 -2
- package/core/server/api/endpoints/mail.js +1 -1
- package/core/server/api/endpoints/{member-custom-fields.js → member-metafields.js} +27 -28
- package/core/server/api/endpoints/members.js +4 -4
- package/core/server/api/endpoints/posts.js +3 -3
- package/core/server/api/endpoints/utils/serializers/input/members.js +4 -7
- package/core/server/api/endpoints/utils/serializers/output/index.js +2 -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/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/importer/importers/data/data-importer.js +3 -3
- package/core/server/data/migrations/utils/migrations.js +1 -1
- package/core/server/lib/image/image-size.js +24 -0
- package/core/server/lib/package-json/parse.js +2 -2
- 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/comments/comments-controller.js +30 -9
- package/core/server/services/comments/comments-service.js +55 -5
- package/core/server/services/content-import/csv/formula.js +12 -0
- package/core/server/services/content-import/csv/index.js +6 -1
- package/core/server/services/content-import/csv/parse.js +27 -6
- package/core/server/services/content-import/csv/serialize.js +13 -0
- package/core/server/services/content-import/import/completion-email.js +164 -0
- package/core/server/services/content-import/import/errors-file.js +36 -0
- package/core/server/services/content-import/import/importer.js +160 -53
- package/core/server/services/content-import/import/local-media-url.js +63 -0
- package/core/server/services/content-import/import/media.js +254 -0
- package/core/server/services/content-import/import/post-data.js +0 -1
- package/core/server/services/content-import/import/post-link.js +11 -0
- package/core/server/services/content-import/import/post-repository.js +25 -3
- package/core/server/services/content-import/import/reader.js +9 -2
- package/core/server/services/content-import/import/report.js +34 -0
- package/core/server/services/content-import/import/row.js +0 -2
- package/core/server/services/content-import/import/schema.js +1 -0
- package/core/server/services/content-import/import/staged-file.js +29 -0
- package/core/server/services/content-import/import/store.js +25 -2
- package/core/server/services/content-import/index.js +52 -6
- package/core/server/services/content-import/jobs/content-csv-import-job.js +20 -0
- package/core/server/services/custom-redirects/redirect-config-parser.js +2 -2
- package/core/server/services/custom-redirects/redirects-service.js +1 -1
- package/core/server/services/custom-redirects/validation.js +1 -1
- package/core/server/services/email-address/email-address-parser.js +30 -38
- package/core/server/services/email-address/email-address-service.js +37 -4
- package/core/server/services/email-analytics/index.js +2 -7
- package/core/server/services/email-service/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/gifts/email-templates/gift-delivery.hbs +3 -3
- package/core/server/services/gifts/gift-email-service.js +0 -1
- 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 +21 -11
- package/core/server/services/mail/ghost-mailer.js +4 -4
- package/core/server/services/member-welcome-emails/service.js +7 -7
- package/core/server/services/members/api.js +1 -0
- package/core/server/services/members/import-export/csv/parse.js +0 -7
- package/core/server/services/members/import-export/import/completion-email.js +2 -5
- package/core/server/services/members/import-export/import/row.js +0 -3
- package/core/server/services/members/import-export/index.js +4 -11
- 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 +4 -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 +34 -62
- package/core/server/services/members/members-api/services/payments-service.js +1 -1
- package/core/server/services/members/service.js +1 -5
- package/core/server/services/members-custom-fields/bindings-service.js +2 -1
- package/core/server/services/members-custom-fields/codec.js +3 -4
- package/core/server/services/members-custom-fields/definitions-service.js +62 -10
- package/core/server/services/members-custom-fields/filter.js +75 -93
- 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/serializers.js +3 -5
- package/core/server/services/members-custom-fields/values-service.js +50 -28
- 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/route-settings/validation-errors.js +1 -1
- package/core/server/services/route-settings/yaml-parser.js +1 -1
- package/core/server/services/settings/settings-service.js +1 -8
- package/core/server/services/settings-helpers/settings-helpers.js +4 -6
- package/core/server/services/slack-ping/slack-ping-service.js +1 -1
- package/core/server/services/staff/staff-service-emails.js +2 -2
- package/core/server/services/stripe/config.js +14 -8
- package/core/server/services/stripe/service.js +10 -1
- package/core/server/services/stripe/services/checkout/completed-session.js +2 -2
- package/core/server/services/stripe/services/checkout/session-options.js +18 -15
- package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +26 -5
- package/core/server/services/stripe/stripe-api.js +2 -0
- package/core/server/services/stripe/stripe-service.js +20 -10
- package/core/server/services/stripe/webhook-controller.js +10 -0
- package/core/server/services/tier-checkout-config/codec.js +9 -5
- package/core/server/services/tier-checkout-config/models.js +9 -3
- package/core/server/services/tier-checkout-config/queries.js +4 -4
- package/core/server/services/tier-checkout-config/serializers.js +16 -10
- package/core/server/services/tier-checkout-config/service.js +13 -12
- 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 +19 -16
- package/core/shared/config/env/config.testing-mysql.json +1 -1
- package/core/shared/labs.js +5 -2
- package/package.json +24 -23
- package/pnpm-lock.yaml +704 -69
- package/pnpm-workspace.yaml +8 -5
- package/components/tryghost-kg-default-nodes-2.2.0.tgz +0 -0
- package/core/built/admin/assets/Combination-5fYEzgbs.js +0 -41
- package/core/built/admin/assets/account-migration-DRhAehfw.js +0 -1
- package/core/built/admin/assets/activitypub/inbox-BluD2ucT.js +0 -1284
- package/core/built/admin/assets/activitypub/label-FHSh6ZAw.js +0 -16
- package/core/built/admin/assets/activitypub/layout-Cq4NG0Xr.js +0 -1
- package/core/built/admin/assets/activitypub/settings-DQDkr06X.js +0 -315
- package/core/built/admin/assets/add-integration-modal-BC0jExDZ.js +0 -1
- package/core/built/admin/assets/add-newsletter-modal-DchTkA7l.js +0 -1
- package/core/built/admin/assets/add-recommendation-modal-DMvaJZKp.js +0 -1
- package/core/built/admin/assets/alert-dialog-QhXzdHfc.js +0 -1
- package/core/built/admin/assets/analytics-No1DDY3A.js +0 -1
- package/core/built/admin/assets/automations-DpUNUdcZ.js +0 -1
- package/core/built/admin/assets/bluesky-sharing-Cjrv_f62.js +0 -1
- package/core/built/admin/assets/color-picker-BTY_NdEm.js +0 -3
- package/core/built/admin/assets/color-picker-field-CPlkzOH0.js +0 -1
- package/core/built/admin/assets/combobox-DrMtv_4P.js +0 -1
- package/core/built/admin/assets/command-ZdgYJpoI.js +0 -1
- package/core/built/admin/assets/comments-kidj60-h.js +0 -1
- package/core/built/admin/assets/confirmation-context-BhbmQQEl.js +0 -1
- package/core/built/admin/assets/custom-field-type-option-CsGIZk1J.js +0 -10
- package/core/built/admin/assets/custom-integration-modal-BY37O_9X.js +0 -1
- package/core/built/admin/assets/design-and-theme-modal-DUSKM4uC.js +0 -22
- package/core/built/admin/assets/dialog-B44iRwlc.js +0 -1
- package/core/built/admin/assets/dirty-confirm-dialog-BxgQgQ6H.js +0 -1
- package/core/built/admin/assets/dist-Cy8tfrp_.js +0 -1
- package/core/built/admin/assets/domain-Ca6KhoQt.js +0 -1
- package/core/built/admin/assets/dropzone-Bb-5zQTk.js +0 -5
- package/core/built/admin/assets/edit-profile-BdupLlus.js +0 -1
- package/core/built/admin/assets/editor-BP883Lpn.js +0 -8
- package/core/built/admin/assets/explore-FB-CimXw.js +0 -1
- package/core/built/admin/assets/feed-C1GxsmI2.js +0 -1
- package/core/built/admin/assets/filter-sources-D5n5bFA2.js +0 -1
- package/core/built/admin/assets/filters-DJEzkMUK.js +0 -1
- package/core/built/admin/assets/filters-MZXvdH5D.js +0 -1
- package/core/built/admin/assets/first-promoter-modal-CGTGLCr5.js +0 -1
- package/core/built/admin/assets/gift-link-modal-B6v0tnRN.js +0 -1
- package/core/built/admin/assets/growth-BGAJD1rs.js +0 -3
- package/core/built/admin/assets/growth-D6mhcx3q.js +0 -1
- package/core/built/admin/assets/history-modal-iKwUY3XR.js +0 -1
- package/core/built/admin/assets/index-DjQe-e5s.js +0 -4
- package/core/built/admin/assets/index-Dqf2x-ry.css +0 -1
- package/core/built/admin/assets/index.es-VihPwfeH.js +0 -1
- package/core/built/admin/assets/input--W-rCTrX.js +0 -1
- package/core/built/admin/assets/invite-user-modal-MdImLCaJ.js +0 -1
- package/core/built/admin/assets/koenig-loader-DYZpm6El.js +0 -2
- package/core/built/admin/assets/label-picker-cB51xl1r.js +0 -1
- package/core/built/admin/assets/layout-c_B_1hax.js +0 -1
- package/core/built/admin/assets/loading-indicator-BpFWTVgh.js +0 -1
- package/core/built/admin/assets/member-custom-fields-qs0dIkxM.js +0 -1
- package/core/built/admin/assets/member-detail-B6uec2a6.js +0 -1
- package/core/built/admin/assets/members-BQF3CqhI.js +0 -2
- package/core/built/admin/assets/moderation-DY4wF1Q3.js +0 -1
- package/core/built/admin/assets/navigation-modal-DX-gNF3X.js +0 -1
- package/core/built/admin/assets/newsletter-CyQA7m3m.js +0 -1
- package/core/built/admin/assets/newsletter-detail-modal-BDrptuxn.js +0 -1
- package/core/built/admin/assets/newsletters-DXuNmC42.js +0 -1
- package/core/built/admin/assets/note-Cgo2LCJ6.js +0 -1
- package/core/built/admin/assets/notifications-BC50AMku.js +0 -1
- package/core/built/admin/assets/offers-container-modal-DWycIMzT.js +0 -1
- package/core/built/admin/assets/onboarding-DGQuV7Ew.js +0 -1
- package/core/built/admin/assets/overview-CVbFtn-k.js +0 -1
- package/core/built/admin/assets/overview-YKj7SZaK.js +0 -1
- package/core/built/admin/assets/pages-B6vr9F_D.js +0 -1
- package/core/built/admin/assets/popover-CCy0h5rq.js +0 -1
- package/core/built/admin/assets/portal-frame-dc4XKr0e.js +0 -1
- package/core/built/admin/assets/portal-modal-BvvdF_B9.js +0 -1
- package/core/built/admin/assets/post-analytics-DX470WRG.js +0 -1
- package/core/built/admin/assets/post-analytics-header-CXpJmdDv.js +0 -1
- package/core/built/admin/assets/posts-KY3aUhVV.js +0 -1
- package/core/built/admin/assets/preferences-DO0UUWFp.js +0 -5
- package/core/built/admin/assets/profile-CxT-0l4d.js +0 -1
- package/core/built/admin/assets/radio-group-Bgz60i81.js +0 -1
- package/core/built/admin/assets/select-DuDUFdPq.js +0 -1
- package/core/built/admin/assets/settings-BjCii7BM.js +0 -1
- package/core/built/admin/assets/settings-b8kNzZSQ.js +0 -4
- package/core/built/admin/assets/share-modal-BV70mr9k.js +0 -1
- package/core/built/admin/assets/shim-ClF_m5ux.js +0 -1
- package/core/built/admin/assets/slack-modal-Be2_KK-z.js +0 -1
- package/core/built/admin/assets/stats-C4reGh9O.js +0 -1
- package/core/built/admin/assets/stats-view-DP9pQ62k.js +0 -1
- package/core/built/admin/assets/stripe-connect-modal-Z-UT_BGc.js +0 -1
- package/core/built/admin/assets/tag-detail-Dc0nTOtU.js +0 -2
- package/core/built/admin/assets/tags-BLaXBwnb.js +0 -1
- package/core/built/admin/assets/testimonials-modal-sFwRnNLh.js +0 -1
- package/core/built/admin/assets/tier-detail-modal-BCFpWwUS.js +0 -1
- package/core/built/admin/assets/use-filter-params-CAvfDvf1.js +0 -1
- package/core/built/admin/assets/use-growth-stats-D6mZXGhK.js +0 -1
- package/core/built/admin/assets/use-post-referrers-DbY-mzci.js +0 -1
- package/core/built/admin/assets/use-reply-chain-data-BeQwz8oF.js +0 -1
- package/core/built/admin/assets/use-settings-navigation-BbrSUVcf.js +0 -1
- package/core/built/admin/assets/use-tinybird-query-B1IMyAcv.js +0 -39
- package/core/built/admin/assets/use-tinybird-token-Bow0Zwn8.js +0 -1
- package/core/built/admin/assets/user-detail-modal-yipSoQfv.js +0 -1
- package/core/built/admin/assets/users-5msE1WMs.js +0 -1
- package/core/built/admin/assets/web-CKcXAu86.js +0 -1
- package/core/built/admin/assets/web-DR4TqM3E.js +0 -2
- package/core/server/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/stripe/services/checkout/allowed-countries.js +0 -267
- package/core/server/services/stripe/services/checkout/field-ports.js +0 -34
- package/core/server/services/tier-checkout-config/destinations.js +0 -17
- package/core/server/services/update-check/run-update-check.js +0 -63
|
@@ -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");
|
|
@@ -92,7 +93,29 @@ class CustomFieldDefinitionsService {
|
|
|
92
93
|
// restart. Where the number comes from is the caller's business.
|
|
93
94
|
this.getMaxDefinitions = getMaxDefinitions;
|
|
94
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
|
+
}
|
|
95
115
|
async browse(options = {}) {
|
|
116
|
+
if (options.namespace !== undefined && !this.isStored(options.namespace)) {
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
96
119
|
// Archived fields are hidden by default: most surfaces (member details, the
|
|
97
120
|
// filter picker, the importer) only ever want active fields. A caller-
|
|
98
121
|
// supplied `filter` can widen that — Settings pulls active and archived
|
|
@@ -115,8 +138,8 @@ class CustomFieldDefinitionsService {
|
|
|
115
138
|
const rows = await (0, queries_1.inFieldOrder)(query).select('*');
|
|
116
139
|
return rows.map((row) => zod_1.z.decode(codec_1.customFieldCodec, row));
|
|
117
140
|
}
|
|
118
|
-
async read(key) {
|
|
119
|
-
const [field] = await this.list((0, queries_1.fieldByKey)(this.knex, key));
|
|
141
|
+
async read(namespace, key) {
|
|
142
|
+
const [field] = this.isStored(namespace) ? await this.list((0, queries_1.fieldByKey)(this.knex, key)) : [];
|
|
120
143
|
if (!field) {
|
|
121
144
|
throw new errors_1.default.NotFoundError({ message: 'Custom field not found.' });
|
|
122
145
|
}
|
|
@@ -132,7 +155,8 @@ class CustomFieldDefinitionsService {
|
|
|
132
155
|
* batch, so two items sharing a name are caught and two items deriving the same
|
|
133
156
|
* key get distinct ones, exactly as if they had arrived as separate requests.
|
|
134
157
|
*/
|
|
135
|
-
async add(context, input) {
|
|
158
|
+
async add(context, namespace, input) {
|
|
159
|
+
this.assertDefinable(namespace);
|
|
136
160
|
const requestedCount = Array.isArray(input) ? input.length : 0;
|
|
137
161
|
const parsed = AddFieldsInput.safeParse(input);
|
|
138
162
|
if (!parsed.success) {
|
|
@@ -310,7 +334,8 @@ class CustomFieldDefinitionsService {
|
|
|
310
334
|
* fresh rank, so a move is well-defined even where every rank is still the default.
|
|
311
335
|
* Returns every definition, archived included, matching what the request named.
|
|
312
336
|
*/
|
|
313
|
-
async reorder(context, input) {
|
|
337
|
+
async reorder(context, namespace, input) {
|
|
338
|
+
this.assertDefinable(namespace);
|
|
314
339
|
const parsed = ReorderInput.safeParse(input);
|
|
315
340
|
if (!parsed.success) {
|
|
316
341
|
const issue = parsed.error.issues[0];
|
|
@@ -429,7 +454,10 @@ class CustomFieldDefinitionsService {
|
|
|
429
454
|
});
|
|
430
455
|
}
|
|
431
456
|
}
|
|
432
|
-
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
|
+
}
|
|
433
461
|
const parsed = EditFieldInput.safeParse(input);
|
|
434
462
|
if (!parsed.success) {
|
|
435
463
|
throw new errors_1.default.ValidationError({
|
|
@@ -438,7 +466,7 @@ class CustomFieldDefinitionsService {
|
|
|
438
466
|
});
|
|
439
467
|
}
|
|
440
468
|
const patch = parsed.data;
|
|
441
|
-
const existing = await this.read(key);
|
|
469
|
+
const existing = await this.read(identity_1.CUSTOM_NAMESPACE, key);
|
|
442
470
|
// Key and type are immutable after creation: values are addressed by key
|
|
443
471
|
// and interpreted by type, so changing either would silently orphan or
|
|
444
472
|
// corrupt stored values. Reject a differing value rather than ignore it.
|
|
@@ -492,7 +520,7 @@ class CustomFieldDefinitionsService {
|
|
|
492
520
|
details: { primary_name: patch.name ?? existing.name, key },
|
|
493
521
|
});
|
|
494
522
|
}
|
|
495
|
-
return this.read(key);
|
|
523
|
+
return this.read(identity_1.CUSTOM_NAMESPACE, key);
|
|
496
524
|
}
|
|
497
525
|
/**
|
|
498
526
|
* Permanently delete a field and every member value attached to it (the FK
|
|
@@ -501,7 +529,10 @@ class CustomFieldDefinitionsService {
|
|
|
501
529
|
* which makes accidental data loss a deliberate two-step. Archiving is the
|
|
502
530
|
* reversible soft state (see `edit` with a status change).
|
|
503
531
|
*/
|
|
504
|
-
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
|
+
}
|
|
505
536
|
const field = await this.knex(TABLE).where('key', key).first();
|
|
506
537
|
if (!field) {
|
|
507
538
|
throw new errors_1.default.NotFoundError({ message: 'Custom field not found.' });
|
|
@@ -581,6 +612,24 @@ function applyFilter(query, filter) {
|
|
|
581
612
|
let mongoQuery;
|
|
582
613
|
try {
|
|
583
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 {
|
|
584
633
|
knexify(query, mongoQuery, { tableName: TABLE });
|
|
585
634
|
}
|
|
586
635
|
catch (err) {
|
|
@@ -599,12 +648,15 @@ function applyFilter(query, filter) {
|
|
|
599
648
|
// $and/$or/$nor combinators — so a status filter at any nesting counts as the
|
|
600
649
|
// caller opting in to (or out of) archived fields.
|
|
601
650
|
function filterReferencesStatus(query) {
|
|
651
|
+
return filterReferencesAttribute(query, 'status');
|
|
652
|
+
}
|
|
653
|
+
function filterReferencesAttribute(query, attribute) {
|
|
602
654
|
return Object.entries(query).some(([key, value]) => {
|
|
603
|
-
if (key ===
|
|
655
|
+
if (key === attribute) {
|
|
604
656
|
return true;
|
|
605
657
|
}
|
|
606
658
|
if ((key === '$and' || key === '$or' || key === '$nor') && Array.isArray(value)) {
|
|
607
|
-
return value.some((sub) =>
|
|
659
|
+
return value.some((sub) => filterReferencesAttribute(sub, attribute));
|
|
608
660
|
}
|
|
609
661
|
return false;
|
|
610
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',
|
|
@@ -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,
|
|
@@ -4,10 +4,8 @@ exports.toCustomFieldsResponse = void 0;
|
|
|
4
4
|
const zod_1 = require("zod");
|
|
5
5
|
const case_keys_1 = require("../../lib/case-keys");
|
|
6
6
|
const models_1 = require("./models");
|
|
7
|
-
// Response schema — the field-definition shape the admin endpoints emit. `id` is
|
|
8
|
-
// the DB primary key and deliberately never leaves the API; a field is addressed
|
|
9
|
-
// by its immutable `key`. The `.pipe` below strips id from the mapped output.
|
|
10
7
|
const CustomFieldResource = zod_1.z.object({
|
|
8
|
+
namespace: zod_1.z.string(),
|
|
11
9
|
key: zod_1.z.string(),
|
|
12
10
|
name: zod_1.z.string(),
|
|
13
11
|
type: zod_1.z.string(),
|
|
@@ -15,10 +13,10 @@ const CustomFieldResource = zod_1.z.object({
|
|
|
15
13
|
created_at: zod_1.z.date(),
|
|
16
14
|
updated_at: zod_1.z.date().nullable(),
|
|
17
15
|
});
|
|
18
|
-
const CustomFieldsResponse = zod_1.z.object({
|
|
16
|
+
const CustomFieldsResponse = zod_1.z.object({ members_metafields: zod_1.z.array(CustomFieldResource) });
|
|
19
17
|
exports.toCustomFieldsResponse = zod_1.z
|
|
20
18
|
.array(models_1.CustomField)
|
|
21
19
|
.transform((fields) => ({
|
|
22
|
-
|
|
20
|
+
members_metafields: fields.map((field) => (0, case_keys_1.snakeKeys)(field)),
|
|
23
21
|
}))
|
|
24
22
|
.pipe(CustomFieldsResponse);
|
|
@@ -9,6 +9,7 @@ const errors_1 = __importDefault(require("@tryghost/errors"));
|
|
|
9
9
|
const logging_1 = __importDefault(require("@tryghost/logging"));
|
|
10
10
|
const zod_1 = require("zod");
|
|
11
11
|
const custom_field_types_1 = require("@tryghost/custom-field-types");
|
|
12
|
+
const identity_1 = require("@tryghost/custom-field-types/identity");
|
|
12
13
|
const schema_1 = require("./schema");
|
|
13
14
|
const queries_1 = require("./queries");
|
|
14
15
|
const storage_1 = require("./storage");
|
|
@@ -29,9 +30,9 @@ const MAX_KEY_LENGTH = require('../../data/schema').tables[FIELDS_TABLE].key.max
|
|
|
29
30
|
* go when that lands; until then the alternative is hand-written upsert SQL per engine.
|
|
30
31
|
*/
|
|
31
32
|
const UPSERT_CHUNK = 400;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
const
|
|
33
|
+
const MAX_IDENTITY_LENGTH = MAX_KEY_LENGTH * 2 + 1;
|
|
34
|
+
const ValuesInput = zod_1.z.record(zod_1.z.string().max(MAX_IDENTITY_LENGTH), zod_1.z.unknown());
|
|
35
|
+
const wireProperty = (identity) => [identity_1.QUALIFIER, identity].join('.');
|
|
35
36
|
/**
|
|
36
37
|
* What a member holds for each defined field. Separate from the definitions service
|
|
37
38
|
* because a value belongs to the member and a definition belongs to the site's settings,
|
|
@@ -45,22 +46,22 @@ class CustomFieldValuesService {
|
|
|
45
46
|
this.knex = knex;
|
|
46
47
|
this.getMaxDefinitions = getMaxDefinitions;
|
|
47
48
|
}
|
|
48
|
-
async
|
|
49
|
+
async activeFieldsByIdentity(identities) {
|
|
50
|
+
const keys = identities
|
|
51
|
+
.map((identity) => (0, identity_1.parseIdentity)(identity))
|
|
52
|
+
.filter((parsed) => parsed !== null && parsed.namespace === identity_1.CUSTOM_NAMESPACE)
|
|
53
|
+
.map((parsed) => parsed.key);
|
|
49
54
|
if (keys.length === 0) {
|
|
50
55
|
return new Map();
|
|
51
56
|
}
|
|
52
57
|
const fields = await (0, queries_1.activeFields)(this.knex)
|
|
53
58
|
.whereIn('key', keys)
|
|
54
59
|
.select('id', 'key', 'name', 'type');
|
|
55
|
-
return new Map(fields.map((field) => [
|
|
60
|
+
return new Map(fields.map((field) => [
|
|
61
|
+
(0, identity_1.formatIdentity)({ namespace: identity_1.CUSTOM_NAMESPACE, key: field.key, partPath: null }),
|
|
62
|
+
{ ...field, namespace: identity_1.CUSTOM_NAMESPACE },
|
|
63
|
+
]));
|
|
56
64
|
}
|
|
57
|
-
/**
|
|
58
|
-
* Members' values, keyed by member id then field key; anything absent is unset.
|
|
59
|
-
*
|
|
60
|
-
* Archived fields are excluded to match the definitions browse: their values stay in
|
|
61
|
-
* the database and stop being addressable. A row that will not parse is dropped and
|
|
62
|
-
* logged rather than failing the read, so one stale row cannot take down a member.
|
|
63
|
-
*/
|
|
64
65
|
async getValuesForMembers(memberIds) {
|
|
65
66
|
if (memberIds.length === 0) {
|
|
66
67
|
return new Map();
|
|
@@ -88,19 +89,43 @@ class CustomFieldValuesService {
|
|
|
88
89
|
}, 'Skipping an unreadable custom field value');
|
|
89
90
|
}
|
|
90
91
|
}
|
|
91
|
-
|
|
92
|
+
const flat = (0, storage_1.valuesFromLeaves)(leaves);
|
|
93
|
+
return new Map(memberIds.map((memberId) => [memberId, { [identity_1.CUSTOM_NAMESPACE]: flat.get(memberId) ?? {} }]));
|
|
92
94
|
}
|
|
93
|
-
/** Shared by every caller so they cannot disagree on what a values object is. */
|
|
94
95
|
parseValues(input) {
|
|
95
96
|
const parsed = ValuesInput.safeParse(input);
|
|
96
97
|
if (!parsed.success) {
|
|
97
98
|
throw new errors_1.default.ValidationError({
|
|
98
|
-
message: 'Custom field values must be an object keyed by field
|
|
99
|
-
property:
|
|
99
|
+
message: 'Custom field values must be an object keyed by field identity.',
|
|
100
|
+
property: identity_1.QUALIFIER,
|
|
100
101
|
});
|
|
101
102
|
}
|
|
102
103
|
return parsed.data;
|
|
103
104
|
}
|
|
105
|
+
unwrapWire(input) {
|
|
106
|
+
if (input === undefined) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
if (typeof input !== 'object' || input === null || Array.isArray(input)) {
|
|
110
|
+
throw new errors_1.default.ValidationError({
|
|
111
|
+
message: 'Metafields must be an object keyed by namespace.',
|
|
112
|
+
property: identity_1.QUALIFIER,
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
const identified = {};
|
|
116
|
+
for (const [namespace, values] of Object.entries(input)) {
|
|
117
|
+
if (typeof values !== 'object' || values === null || Array.isArray(values)) {
|
|
118
|
+
throw new errors_1.default.ValidationError({
|
|
119
|
+
message: 'Metafield values must be an object keyed by field key.',
|
|
120
|
+
property: [identity_1.QUALIFIER, namespace].join('.'),
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
for (const [key, raw] of Object.entries(values)) {
|
|
124
|
+
identified[`${namespace}.${key}`] = raw;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
return identified;
|
|
128
|
+
}
|
|
104
129
|
/**
|
|
105
130
|
* Whether input names any values. Asks the shape question alone, with no catalog
|
|
106
131
|
* lookup, so it can be asked before a write is known to be permitted.
|
|
@@ -118,27 +143,24 @@ class CustomFieldValuesService {
|
|
|
118
143
|
*/
|
|
119
144
|
async planWrite(input) {
|
|
120
145
|
const values = this.parseValues(input);
|
|
121
|
-
const
|
|
146
|
+
const identities = Object.keys(values);
|
|
122
147
|
// Bounded by the definitions ceiling, which also holds the lookup below inside
|
|
123
148
|
// the driver's bound-parameter limit.
|
|
124
149
|
const maxKeys = this.getMaxDefinitions();
|
|
125
|
-
if (
|
|
150
|
+
if (identities.length > maxKeys) {
|
|
126
151
|
throw new errors_1.default.ValidationError({
|
|
127
152
|
message: `Custom field values are limited to ${maxKeys} fields per request.`,
|
|
128
|
-
property:
|
|
153
|
+
property: identity_1.QUALIFIER,
|
|
129
154
|
});
|
|
130
155
|
}
|
|
131
|
-
const
|
|
156
|
+
const byIdentity = await this.activeFieldsByIdentity(identities);
|
|
132
157
|
const writes = [];
|
|
133
|
-
for (const [
|
|
134
|
-
const field =
|
|
158
|
+
for (const [identity, raw] of Object.entries(values)) {
|
|
159
|
+
const field = byIdentity.get(identity);
|
|
135
160
|
if (!field) {
|
|
136
|
-
// Unknown or archived. Refused rather than ignored: a typo that silently
|
|
137
|
-
// drops what somebody typed is worse than a save that fails. The catalog
|
|
138
|
-
// applies the same rule to the parts of a composite value.
|
|
139
161
|
throw new errors_1.default.ValidationError({
|
|
140
|
-
message: `Unknown custom field: ${
|
|
141
|
-
property:
|
|
162
|
+
message: `Unknown custom field: ${identity}`,
|
|
163
|
+
property: wireProperty(identity),
|
|
142
164
|
});
|
|
143
165
|
}
|
|
144
166
|
// `null` clears any field, and `''` clears one with no parts. For a value
|
|
@@ -157,7 +179,7 @@ class CustomFieldValuesService {
|
|
|
157
179
|
const issue = value.error.issues[0];
|
|
158
180
|
throw new errors_1.default.ValidationError({
|
|
159
181
|
message: issue.message,
|
|
160
|
-
property: [
|
|
182
|
+
property: [wireProperty(identity), ...issue.path].join('.'),
|
|
161
183
|
});
|
|
162
184
|
}
|
|
163
185
|
writes.push({ field, value: value.data });
|