ghost 6.60.0 → 6.61.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/tryghost-adapter-base-scheduling-0.2.3.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.1.3.tgz +0 -0
- package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-kg-default-cards-10.3.5.tgz +0 -0
- package/core/boot.js +13 -4
- package/core/built/admin/assets/{Combination-gnJO3vIw.js → Combination-5fYEzgbs.js} +1 -1
- 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-Bve7IJS2.js} +1 -1
- package/core/built/admin/assets/{account-migration-8qTEI1i1.js → account-migration-DRhAehfw.js} +1 -1
- 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-C-aej4x_.js} +7 -7
- package/core/built/admin/assets/activitypub/activitypub.js +3 -4
- 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-BXkA5mqV.js} +8 -8
- package/core/built/admin/assets/activitypub/{content-formatters-BeDZ-DIa.js → content-formatters-CHcyMFk4.js} +17 -17
- package/core/built/admin/assets/activitypub/{copy-8sqxUuNd.js → copy-DIAOQoCZ.js} +2 -2
- package/core/built/admin/assets/activitypub/{domain-xQDDu1CS.js → domain-B3ihkPde.js} +8 -8
- package/core/built/admin/assets/activitypub/{edit-profile-BXdF_r2G.js → edit-profile-DsZM3rTX.js} +5 -5
- package/core/built/admin/assets/activitypub/{explore-CMt7gKlL.js → explore-BodNrZgR.js} +8 -8
- package/core/built/admin/assets/activitypub/{feed-Bwqdddp_.js → feed-PwH1iJ84.js} +9 -9
- package/core/built/admin/assets/activitypub/{field-4co_8FVg.js → field-D06-kmOJ.js} +3 -3
- package/core/built/admin/assets/activitypub/{hash-DwakMs4X.js → hash-Bm3WxYN4.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-DQ5CwLma.js → inbox-BluD2ucT.js} +16 -16
- package/core/built/admin/assets/activitypub/{inbox-ckD4wo_r.js → inbox-d8oJRTf4.js} +2 -2
- package/core/built/admin/assets/activitypub/{label-B72sfvEU.js → label-FHSh6ZAw.js} +3 -3
- package/core/built/admin/assets/activitypub/layout-Cq4NG0Xr.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-KCCO7L2J.js → moderation-D1Yapsod.js} +9 -9
- package/core/built/admin/assets/activitypub/{note-BX_Bz5A0.js → note-C5zjqq5C.js} +8 -8
- package/core/built/admin/assets/activitypub/{notifications-BQOYGvRv.js → notifications-B9p8eCHD.js} +9 -9
- package/core/built/admin/assets/activitypub/{preferences-6h43G9_q.js → preferences-BCqUdC3v.js} +9 -9
- package/core/built/admin/assets/activitypub/{profile-BMN9VVZ2.js → profile-BRCWoFem.js} +178 -178
- package/core/built/admin/assets/activitypub/{reply-ZNo04xsO.js → reply-DYK5vVqb.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-3B9mUQ7i.js → routes-BSXT992i.js} +81 -84
- package/core/built/admin/assets/activitypub/{separator-D9taVCCa.js → separator-BbQqTm-p.js} +3 -3
- package/core/built/admin/assets/activitypub/{settings-71gCm3SO.js → settings-DQDkr06X.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-1-BsGGnBuT.js → step-1-B-YfsVeO.js} +5 -5
- 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-D6BFMXL_.js} +10 -10
- package/core/built/admin/assets/activitypub/{tabs-Cvwl1wXU.js → tabs-GpNecvfB.js} +6 -6
- package/core/built/admin/assets/activitypub/{topic-filter-C3LZJc2X.js → topic-filter-BPDeQQys.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-Dp9f0gov.js → use-activity-pub-queries-BoVOxXkV.js} +2 -2
- 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-wVBX7swM.js} +5 -5
- package/core/built/admin/assets/activitypub/{x-Dxm6pV9X.js → x-DbQZOLiI.js} +2 -2
- package/core/built/admin/assets/{add-integration-modal--YpTq3c0.js → add-integration-modal-BC0jExDZ.js} +1 -1
- package/core/built/admin/assets/{add-newsletter-modal-C8Ix2hol.js → add-newsletter-modal-DchTkA7l.js} +1 -1
- package/core/built/admin/assets/{add-recommendation-modal-CScuNFz4.js → add-recommendation-modal-DMvaJZKp.js} +1 -1
- package/core/built/admin/assets/alert-dialog-QhXzdHfc.js +1 -0
- package/core/built/admin/assets/{analytics-Cnji45H5.js → analytics-No1DDY3A.js} +1 -1
- package/core/built/admin/assets/{announcement-bar-modal-BTHiy1ZO.js → announcement-bar-modal-DM3cYOpo.js} +1 -1
- package/core/built/admin/assets/{api-keys-Boy5pLbD.js → api-keys-BLm2NDBh.js} +1 -1
- package/core/built/admin/assets/audience-9mwU5TW6.js +1 -0
- package/core/built/admin/assets/{automations-BWpRCZ_f.js → automations-DpUNUdcZ.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-DHOpQxff.js → bluesky-sharing-Cjrv_f62.js} +1 -1
- 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-DLiqCyFc.js} +1 -1
- package/core/built/admin/assets/{button-BshWPHvn.js → button-B2jcu6bk.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-Fo3bUL3K.js +1 -0
- package/core/built/admin/assets/{checkbox-Dwwr_3Py.js → checkbox-BfKktMph.js} +1 -1
- package/core/built/admin/assets/{chunk.524.91ecb33e6afc3bd8949b.js → chunk.524.60ca4692d91c6022042f.js} +4 -4
- package/core/built/admin/assets/{chunk.582.512549909741883f960c.js → chunk.582.525c7617c2d9e94c940a.js} +6 -6
- package/core/built/admin/assets/{chunk.16.f76a6314082d813d67e0.js → chunk.896.435fe0637944d4a69810.js} +3 -3
- package/core/built/admin/assets/{code-editor-view-BwIdPy_k.js → code-editor-view-CYVu4EW6.js} +1 -1
- package/core/built/admin/assets/{code-editor-view-CtkF82Fv.js → code-editor-view-DCQJxsfU.js} +1 -1
- package/core/built/admin/assets/{color-picker-Bmo1r9Fc.js → color-picker-BTY_NdEm.js} +1 -1
- package/core/built/admin/assets/{color-picker-field-DywowPhF.js → color-picker-field-CPlkzOH0.js} +1 -1
- package/core/built/admin/assets/{combobox-C4QCh6Yw.js → combobox-DrMtv_4P.js} +1 -1
- package/core/built/admin/assets/command-ZdgYJpoI.js +1 -0
- package/core/built/admin/assets/comments-kidj60-h.js +1 -0
- package/core/built/admin/assets/{confirmation-context-CLFHhyyH.js → confirmation-context-BhbmQQEl.js} +1 -1
- 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-CyQEC3fr.js} +1 -1
- package/core/built/admin/assets/{copy-field-CglgRZyG.js → copy-field-B3M1s78L.js} +1 -1
- package/core/built/admin/assets/{custom-field-type-option-D0PXoSnP.js → custom-field-type-option-CsGIZk1J.js} +1 -1
- package/core/built/admin/assets/{custom-integration-modal-DCIHKN33.js → custom-integration-modal-BY37O_9X.js} +1 -1
- 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-DD2RKkgB.js} +1 -1
- package/core/built/admin/assets/design-and-theme-modal-DUSKM4uC.js +22 -0
- package/core/built/admin/assets/{detail-page-D8lx2SdP.js → detail-page-Dnv-CdR1.js} +1 -1
- package/core/built/admin/assets/dialog-B44iRwlc.js +1 -0
- package/core/built/admin/assets/{dirty-confirm-dialog-CmARpZQS.js → dirty-confirm-dialog-BxgQgQ6H.js} +1 -1
- package/core/built/admin/assets/{disabled-sources-indicator-BTJ2ClQu.js → disabled-sources-indicator-Cvy47bE2.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-QLZuGq7o2.js → dist-Cy8tfrp_.js} +1 -1
- package/core/built/admin/assets/{dist-BT_Z-eCn.js → dist-Dgt2cr6a.js} +1 -1
- package/core/built/admin/assets/{domain-D0SAtc-T.js → domain-Ca6KhoQt.js} +1 -1
- package/core/built/admin/assets/{dropzone-CaPOeDRZ.js → dropzone-Bb-5zQTk.js} +1 -1
- package/core/built/admin/assets/edit-profile-BdupLlus.js +1 -0
- package/core/built/admin/assets/{editor-COXn4Atu.js → editor-BP883Lpn.js} +3 -3
- package/core/built/admin/assets/{embed-signup-form-modal-BFTPf04i.js → embed-signup-form-modal-DeBMLxlL.js} +1 -1
- package/core/built/admin/assets/{empty-indicator-WB1WgT9h.js → empty-indicator-D9QdpuNX.js} +1 -1
- package/core/built/admin/assets/{explore-BsNYrubV.js → explore-FB-CimXw.js} +1 -1
- package/core/built/admin/assets/{feed-8RR1O1bK.js → feed-C1GxsmI2.js} +1 -1
- package/core/built/admin/assets/{field-LmNk7wZh.js → field-Cj8lmDOo.js} +1 -1
- package/core/built/admin/assets/{filter-sources-4C0kF1NU.js → filter-sources-D5n5bFA2.js} +1 -1
- package/core/built/admin/assets/{filters-CV5jCb9n.js → filters-DJEzkMUK.js} +1 -1
- package/core/built/admin/assets/{filters-Dmr0PoqU.js → filters-MZXvdH5D.js} +1 -1
- package/core/built/admin/assets/{first-promoter-modal-OCzSaL3B.js → first-promoter-modal-CGTGLCr5.js} +1 -1
- package/core/built/admin/assets/{ghost-403cb39badad3df411fc0ba27d220047.js → ghost-3894eedcc6968f658578c0abfe942aab.js} +2 -2
- package/core/built/admin/assets/{gift-link-modal-0mzjn5oF.js → gift-link-modal-B6v0tnRN.js} +1 -1
- package/core/built/admin/assets/{grid-CUciBfjf.js → grid-Dj29PY05.js} +1 -1
- package/core/built/admin/assets/growth-BGAJD1rs.js +3 -0
- package/core/built/admin/assets/growth-D6mhcx3q.js +1 -0
- package/core/built/admin/assets/{heading-CRZZ2FUe.js → heading-Dh5SFVjL.js} +1 -1
- package/core/built/admin/assets/history-modal-iKwUY3XR.js +1 -0
- package/core/built/admin/assets/html-field-C8qVGGC1.js +1 -0
- package/core/built/admin/assets/{images-C0XvUYMb.js → images-h8VZEQ9k.js} +1 -1
- package/core/built/admin/assets/{inbox-DZXDUt4s.js → inbox-BYd1M06Q.js} +13 -13
- package/core/built/admin/assets/index-DjQe-e5s.js +4 -0
- package/core/built/admin/assets/{index.es-BMUCj9TR.js → index.es-VihPwfeH.js} +1 -1
- package/core/built/admin/assets/{inline-Cw-gigDW.js → inline-BWSs_VzR.js} +1 -1
- package/core/built/admin/assets/{input-UHrKuvBT.js → input--W-rCTrX.js} +1 -1
- package/core/built/admin/assets/{input-group-BPAxETpQ.js → input-group-D8sCXRF5.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-BrAkxEO3.js → invite-user-modal-MdImLCaJ.js} +1 -1
- package/core/built/admin/assets/{koenig-lexical-60O_8aiO.js → koenig-lexical-_-IsVI7E.js} +1 -1
- package/core/built/admin/assets/koenig-loader-DYZpm6El.js +2 -0
- package/core/built/admin/assets/{kpi-card-fUHZlzBX.js → kpi-card-DW_2y3ug.js} +1 -1
- package/core/built/admin/assets/kpi-card-DZQk_0W8.js +1 -0
- package/core/built/admin/assets/kpi-helpers-PueTKuA6.js +1 -0
- package/core/built/admin/assets/kpi-tabs-CojDs-Qz.js +1 -0
- package/core/built/admin/assets/{label-VIG969IC.js → label-DjbT91GM.js} +1 -1
- package/core/built/admin/assets/{label-picker-BBWykUeA.js → label-picker-cB51xl1r.js} +1 -1
- package/core/built/admin/assets/layout-c_B_1hax.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/{loading-indicator-SFTqyYVE.js → loading-indicator-BpFWTVgh.js} +1 -1
- package/core/built/admin/assets/{member-custom-fields-gOv6Hoh3.js → member-custom-fields-qs0dIkxM.js} +1 -1
- package/core/built/admin/assets/member-detail-B6uec2a6.js +1 -0
- package/core/built/admin/assets/members-BQF3CqhI.js +2 -0
- package/core/built/admin/assets/{modal-page-BMTzFHVO.js → modal-page-xCivTMvI.js} +1 -1
- package/core/built/admin/assets/{moderation-DzzPLDyl.js → moderation-DY4wF1Q3.js} +1 -1
- package/core/built/admin/assets/{multi-select-combobox-CGohnsxJ.js → multi-select-combobox-B6LiRzDO.js} +1 -1
- package/core/built/admin/assets/navigation-modal-DX-gNF3X.js +1 -0
- package/core/built/admin/assets/newsletter-CyQA7m3m.js +1 -0
- package/core/built/admin/assets/{newsletter-detail-modal-DQU3r4g2.js → newsletter-detail-modal-BDrptuxn.js} +1 -1
- package/core/built/admin/assets/{newsletter-emails-D5ql1rud.js → newsletter-emails-CFeLlt4z.js} +1 -1
- package/core/built/admin/assets/newsletters-DXuNmC42.js +1 -0
- package/core/built/admin/assets/{note-i8k6pjaw.js → note-Cgo2LCJ6.js} +1 -1
- package/core/built/admin/assets/notifications-BC50AMku.js +1 -0
- package/core/built/admin/assets/{offers-container-modal-BWfjSdyg.js → offers-container-modal-DWycIMzT.js} +1 -1
- package/core/built/admin/assets/{onboarding-route-Cu_ROWPn.js → onboarding-route-Dmc4IPm3.js} +1 -1
- package/core/built/admin/assets/overview-CVbFtn-k.js +1 -0
- package/core/built/admin/assets/overview-YKj7SZaK.js +1 -0
- package/core/built/admin/assets/{page-header-Cfds8pCg.js → page-header-BR_nbIPp.js} +1 -1
- package/core/built/admin/assets/{pintura-modal-TDTrJ89x.js → pintura-modal-BR9hEvS8.js} +1 -1
- package/core/built/admin/assets/popover-CCy0h5rq.js +1 -0
- package/core/built/admin/assets/{portal-frame-DjBF-uy2.js → portal-frame-dc4XKr0e.js} +1 -1
- package/core/built/admin/assets/portal-modal-BvvdF_B9.js +1 -0
- package/core/built/admin/assets/{post-analytics-Buz1COoY.js → post-analytics-DX470WRG.js} +1 -1
- package/core/built/admin/assets/{post-analytics-header-B-P0eFnT.js → post-analytics-header-CXpJmdDv.js} +1 -1
- package/core/built/admin/assets/{post-analytics-provider-Cu3jNeFh.js → post-analytics-provider-CLfKGWZy.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-P1_M6o-H.js → post-share-modal-Cl0NXRJr.js} +1 -1
- package/core/built/admin/assets/{preferences-Cq1aOiLy.js → preferences-DO0UUWFp.js} +2 -2
- 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-C-BL-F9k.js} +1 -1
- package/core/built/admin/assets/profile-CxT-0l4d.js +1 -0
- package/core/built/admin/assets/radio-group-Bgz60i81.js +1 -0
- package/core/built/admin/assets/{router-provider-CJC58zTy.js → router-provider-B0gZyiGJ.js} +1 -1
- package/core/built/admin/assets/{schemas-CRsC53ls.js → schemas-4UPic-oD.js} +2 -2
- package/core/built/admin/assets/select-DuDUFdPq.js +1 -0
- package/core/built/admin/assets/{settings-B5hJKZ5C.js → settings-BjCii7BM.js} +1 -1
- package/core/built/admin/assets/settings-b8kNzZSQ.js +4 -0
- package/core/built/admin/assets/{settings-breadcrumbs-sLDNedBW.js → settings-breadcrumbs-CyP_9zTo.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-CLBfj377.js → share-modal-BV70mr9k.js} +1 -1
- package/core/built/admin/assets/{skeleton-D7hwjdry.js → skeleton-Ce9GgUpf.js} +1 -1
- package/core/built/admin/assets/{slack-modal-Bt9I_tEo.js → slack-modal-Be2_KK-z.js} +1 -1
- package/core/built/admin/assets/sort-button-Cc-Z96sA.js +1 -0
- package/core/built/admin/assets/{source-icon-BPsD5ees.js → source-icon-v6PUnmyM.js} +1 -1
- package/core/built/admin/assets/{stack-CIfOAAVf.js → stack-B2eszIiq.js} +1 -1
- package/core/built/admin/assets/{stats-B_YkF7bp.js → stats-C4reGh9O.js} +1 -1
- package/core/built/admin/assets/{stats-view-BOCp9RG8.js → stats-view-DP9pQ62k.js} +1 -1
- package/core/built/admin/assets/{step-1-CMta6YFO.js → step-1-DSRp2vBj.js} +1 -1
- package/core/built/admin/assets/{step-2-BG04nh5q.js → step-2-COqv2g0u.js} +1 -1
- package/core/built/admin/assets/{step-3-ZnMaVIPj.js → step-3-6W1NMVQc.js} +2 -2
- package/core/built/admin/assets/{stripe-button-DDoUKN5a.js → stripe-button-D2K42Uty.js} +1 -1
- package/core/built/admin/assets/{stripe-connect-modal-BeSukI8q.js → stripe-connect-modal-Z-UT_BGc.js} +1 -1
- package/core/built/admin/assets/{switch-C9djUIMO.js → switch-CI4gLWqb.js} +1 -1
- package/core/built/admin/assets/{table-CrqQtkR2.js → table-B2pIKjVh.js} +1 -1
- package/core/built/admin/assets/{tabs-CGisSbWq.js → tabs-SaW_UT-Q.js} +1 -1
- package/core/built/admin/assets/tag-detail-Dc0nTOtU.js +2 -0
- package/core/built/admin/assets/tags-BLaXBwnb.js +1 -0
- package/core/built/admin/assets/testimonials-modal-sFwRnNLh.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-CxRwgTPR.js → tier-detail-modal-BCFpWwUS.js} +1 -1
- package/core/built/admin/assets/{toggle-group-CtKZURJd.js → toggle-group-EM9WpESu.js} +1 -1
- package/core/built/admin/assets/{topic-filter-D8JT8Tq1.js → topic-filter-DY4gNHn2.js} +1 -1
- package/core/built/admin/assets/{transistor-modal-6PUf0idx.js → transistor-modal-CpyPG-MH.js} +1 -1
- package/core/built/admin/assets/trend-badge-ByMkuAuH.js +1 -0
- package/core/built/admin/assets/{unsplash-modal-BPuDBPja.js → unsplash-modal-Cv2Q6GtY.js} +1 -1
- package/core/built/admin/assets/{use-analytics-data--opuzwcz.js → use-analytics-data-CHvjMYuT.js} +1 -1
- package/core/built/admin/assets/use-check-theme-limit-error-Qv4SVXqw.js +2 -0
- package/core/built/admin/assets/use-filter-params-CAvfDvf1.js +1 -0
- package/core/built/admin/assets/use-growth-stats-D6mZXGhK.js +1 -0
- package/core/built/admin/assets/{use-limiter-TDSNiHPB.js → use-limiter-cwJvsNBo.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-CzTfnufz.js} +1 -1
- package/core/built/admin/assets/{use-pintura-editor-ntphvtUd.js → use-pintura-editor-BDBXWUYq.js} +1 -1
- package/core/built/admin/assets/{use-post-referrers-CbXLFIex.js → use-post-referrers-DbY-mzci.js} +1 -1
- package/core/built/admin/assets/{use-reply-chain-data-JYFKSO83.js → use-reply-chain-data-BeQwz8oF.js} +1 -1
- package/core/built/admin/assets/{use-router-history-entry-BwfQS2mq.js → use-router-history-entry-Bg6BhBQQ.js} +1 -1
- package/core/built/admin/assets/{use-setting-group-DQxV42-O.js → use-setting-group-VM7FQwMY.js} +1 -1
- package/core/built/admin/assets/{use-settings-navigation-BBzro2-V.js → use-settings-navigation-BbrSUVcf.js} +1 -1
- package/core/built/admin/assets/{use-simple-pagination-B2TdNX67.js → use-simple-pagination-Dum6Irbs.js} +1 -1
- package/core/built/admin/assets/{use-staff-users-QrR9voeC.js → use-staff-users-cZg4ugOF.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-query-D2Aeoty5.js → use-tinybird-query-B1IMyAcv.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-DBuwMmln.js → use-tinybird-token-Bow0Zwn8.js} +1 -1
- package/core/built/admin/assets/{use-unsaved-changes-guard-DpYJcj1d.js → use-unsaved-changes-guard-BAy0RANR.js} +1 -1
- package/core/built/admin/assets/{use-url-input-WnIqckzS.js → use-url-input-Df5QzNa7.js} +1 -1
- package/core/built/admin/assets/user-detail-modal-yipSoQfv.js +1 -0
- package/core/built/admin/assets/{virtual-list-D3MfPNOh.js → virtual-list-DEx8WgiO.js} +1 -1
- package/core/built/admin/assets/web-CKcXAu86.js +1 -0
- package/core/built/admin/assets/web-DR4TqM3E.js +2 -0
- package/core/built/admin/assets/{zapier-modal-BO6PS_Vd.js → zapier-modal-Cw3BTBUu.js} +1 -1
- package/core/built/admin/index.html +24 -25
- package/core/frontend/services/rendering/renderer.js +15 -0
- 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 +4 -0
- package/core/server/api/endpoints/member-custom-fields.js +4 -14
- package/core/server/api/endpoints/posts.js +1 -0
- package/core/server/api/endpoints/tiers-checkout-config.js +59 -0
- package/core/server/api/endpoints/utils/serializers/output/config.js +1 -0
- package/core/server/api/endpoints/utils/serializers/output/index.js +4 -0
- package/core/server/api/endpoints/utils/serializers/output/tiers-checkout-config.js +12 -0
- 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/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/promise-pool.js +31 -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/content-import/import/assets.js +74 -0
- package/core/server/services/content-import/import/importer.js +57 -14
- package/core/server/services/content-import/import/post-data.js +1 -0
- package/core/server/services/content-import/import/post-repository.js +78 -0
- package/core/server/services/content-import/import/relations.js +149 -0
- package/core/server/services/content-import/import/row.js +11 -0
- package/core/server/services/content-import/import/schema.js +29 -0
- package/core/server/services/content-import/import/source.js +125 -0
- package/core/server/services/content-import/index.js +6 -28
- package/core/server/services/email-analytics/email-analytics-service.js +8 -1
- package/core/server/services/email-analytics/lib/queries.js +7 -1
- 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/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-schema.js +10 -10
- package/core/server/services/gifts/gift-service.js +4 -2
- package/core/server/services/jobs-service/register-job-handlers.js +5 -5
- package/core/server/services/lib/mailgun-client.js +44 -8
- 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/members/import-export/export/exporter.js +4 -6
- package/core/server/services/members/import-export/import/importer.js +21 -5
- package/core/server/services/members/import-export/import/stripe-utils.js +6 -1
- package/core/server/services/members/import-export/index.js +6 -1
- package/core/server/services/members/members-api/members-api.js +5 -0
- package/core/server/services/members/members-api/services/member-bread-service.js +19 -1
- package/core/server/services/members/members-api/services/payments-service.js +47 -1
- package/core/server/services/members-custom-fields/actions.js +27 -1
- package/core/server/services/members-custom-fields/bindings-service.js +119 -0
- package/core/server/services/members-custom-fields/definitions-service.js +104 -13
- package/core/server/services/members-custom-fields/index.js +11 -4
- 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/values-service.js +15 -3
- package/core/server/services/public-config/config.js +7 -0
- package/core/server/services/stripe/services/checkout/allowed-countries.js +267 -0
- package/core/server/services/stripe/services/checkout/completed-session.js +68 -0
- package/core/server/services/stripe/services/checkout/field-ports.js +34 -0
- package/core/server/services/stripe/services/checkout/session-options.js +97 -0
- package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +49 -0
- package/core/server/services/stripe/stripe-api.js +29 -0
- package/core/server/services/stripe/stripe-service.js +7 -0
- package/core/server/services/tier-checkout-config/codec.js +95 -0
- package/core/server/services/tier-checkout-config/destinations.js +17 -0
- package/core/server/services/tier-checkout-config/index.js +26 -0
- package/core/server/services/tier-checkout-config/models.js +57 -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 +122 -0
- package/core/server/services/tier-checkout-config/service.js +303 -0
- package/core/server/services/tiers/service.js +21 -0
- package/core/server/web/api/endpoints/admin/routes.js +24 -13
- 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 +3 -0
- package/core/shared/config/env/config.testing.json +3 -0
- package/core/shared/config/overrides.json +4 -2
- package/core/shared/labs.js +5 -14
- package/package.json +12 -13
- package/pnpm-lock.yaml +44 -46
- package/pnpm-workspace.yaml +6 -6
- package/tsconfig.json +2 -3
- package/core/built/admin/assets/activitypub/layout-CXajhRsL.js +0 -1
- package/core/built/admin/assets/alert-dialog-wEbVxANM.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/chart-helpers-B3OqQ6Ez.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/design-and-theme-modal-CZ-PkW7o.js +0 -23
- package/core/built/admin/assets/dialog-Db7c164G.js +0 -1
- package/core/built/admin/assets/edit-profile-By7v5XW5.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/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/layout-Di5K7doM.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/navigation-modal-CI8Bx5vn.js +0 -1
- package/core/built/admin/assets/newsletter-Bg3cq5U-.js +0 -1
- package/core/built/admin/assets/newsletters-DMDfZbMS.js +0 -1
- package/core/built/admin/assets/notifications-psA_2Ph4.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/popover-CFgqFDvC.js +0 -1
- package/core/built/admin/assets/portal-modal-Cf0pBAk8.js +0 -1
- package/core/built/admin/assets/profile-D6dHYf2o.js +0 -1
- package/core/built/admin/assets/radio-group-CjtmsCPl.js +0 -1
- package/core/built/admin/assets/select-D_RQaXgt.js +0 -1
- package/core/built/admin/assets/settings-DIo_F-MF.js +0 -4
- package/core/built/admin/assets/sort-button-DMBXF7tP.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/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/user-detail-modal-59mBJqxN.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/lib/{db-date.js → db-types/date.js} +0 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TierCheckoutConfigService = void 0;
|
|
7
|
+
const bson_objectid_1 = __importDefault(require("bson-objectid"));
|
|
8
|
+
const errors_1 = __importDefault(require("@tryghost/errors"));
|
|
9
|
+
const zod_1 = require("zod");
|
|
10
|
+
const schema_1 = require("../members-custom-fields/schema");
|
|
11
|
+
const field_ports_1 = require("../stripe/services/checkout/field-ports");
|
|
12
|
+
const codec_1 = require("./codec");
|
|
13
|
+
const queries_1 = require("./queries");
|
|
14
|
+
const models_1 = require("./models");
|
|
15
|
+
const destinations_1 = require("./destinations");
|
|
16
|
+
const serializers_1 = require("./serializers");
|
|
17
|
+
/**
|
|
18
|
+
* A request states its settings in named sections: one for shipping, one for phone. Each
|
|
19
|
+
* section stands for one or more of the values Stripe hands back when a checkout is
|
|
20
|
+
* completed — shipping covers both the recipient's name and their address, phone covers
|
|
21
|
+
* only the phone number.
|
|
22
|
+
*
|
|
23
|
+
* Naming those values here means that a request which includes a section settles every
|
|
24
|
+
* value in it: each one is either given a destination or has its old one removed. Without
|
|
25
|
+
* this list, a value the request never mentioned could keep a destination from an earlier
|
|
26
|
+
* save that the publisher believes they have turned off.
|
|
27
|
+
*/
|
|
28
|
+
const BLOCK_PORTS = {
|
|
29
|
+
shipping: [field_ports_1.STRIPE_PORT.shippingName, field_ports_1.STRIPE_PORT.shippingAddress],
|
|
30
|
+
phone: [field_ports_1.STRIPE_PORT.phone],
|
|
31
|
+
};
|
|
32
|
+
class TierCheckoutConfigService {
|
|
33
|
+
knex;
|
|
34
|
+
bindings;
|
|
35
|
+
fields;
|
|
36
|
+
constructor({ knex, bindings, fields, }) {
|
|
37
|
+
this.knex = knex;
|
|
38
|
+
this.bindings = bindings;
|
|
39
|
+
this.fields = fields;
|
|
40
|
+
}
|
|
41
|
+
async browse() {
|
|
42
|
+
const rows = decodeCollection(await (0, queries_1.configuredCollectionRows)(this.knex));
|
|
43
|
+
if (rows.length === 0) {
|
|
44
|
+
return [];
|
|
45
|
+
}
|
|
46
|
+
const asked = await this.questions();
|
|
47
|
+
return rows.map((row) => assemble(row, asked.get(row.tierId) ?? []));
|
|
48
|
+
}
|
|
49
|
+
async read(productId) {
|
|
50
|
+
const [row] = decodeCollection(await (0, queries_1.collectionRowsForTier)(this.knex, productId));
|
|
51
|
+
if (!row) {
|
|
52
|
+
throw new errors_1.default.NotFoundError({ message: 'Tier not found.' });
|
|
53
|
+
}
|
|
54
|
+
if (!row.configured) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
const asked = await this.questions(productId);
|
|
58
|
+
return assemble(row, asked.get(productId) ?? []);
|
|
59
|
+
}
|
|
60
|
+
async resolve(productId) {
|
|
61
|
+
const [row] = decodeCollection(await (0, queries_1.collectionRowsForTier)(this.knex, productId));
|
|
62
|
+
if (!row?.configured) {
|
|
63
|
+
return { customFields: [], ...(0, models_1.emptyCollection)() };
|
|
64
|
+
}
|
|
65
|
+
const asked = await this.questions(productId);
|
|
66
|
+
const customFields = (asked.get(productId) ?? []).flatMap((entry) => entry.askable ? [{ ...entry.question, ...entry.askable }] : []);
|
|
67
|
+
return { customFields, ...row.collecting };
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Saves the checkout settings a request states, and leaves the rest alone.
|
|
71
|
+
*
|
|
72
|
+
* A request only has to include the sections it wants to change. Say nothing about
|
|
73
|
+
* shipping and the shipping settings stay exactly as they were, so a client that only
|
|
74
|
+
* knows how to edit the questions cannot wipe out the shipping settings by omitting
|
|
75
|
+
* them.
|
|
76
|
+
*/
|
|
77
|
+
async edit(context, productId, input) {
|
|
78
|
+
const stated = parseInput(input);
|
|
79
|
+
const now = new Date();
|
|
80
|
+
if (stated.custom_fields) {
|
|
81
|
+
await assertQuestionsAskable(this.knex, stated.custom_fields);
|
|
82
|
+
}
|
|
83
|
+
const plan = await this.planCollection(stated);
|
|
84
|
+
const created = await this.knex.transaction(async (trx) => {
|
|
85
|
+
await assertTierExists(trx, productId);
|
|
86
|
+
await writeOptions(trx, productId, stated, now);
|
|
87
|
+
for (const port of plan.clear) {
|
|
88
|
+
await this.bindings.remove(trx, productId, port);
|
|
89
|
+
}
|
|
90
|
+
const made = [];
|
|
91
|
+
for (const wanted of plan.create) {
|
|
92
|
+
made.push(await this.fields.addOne(wanted, { executor: trx }));
|
|
93
|
+
}
|
|
94
|
+
for (const { port, key } of plan.bind) {
|
|
95
|
+
await this.bindings.bind(trx, productId, port, key, now);
|
|
96
|
+
}
|
|
97
|
+
if (stated.custom_fields) {
|
|
98
|
+
await this.writeQuestions(trx, productId, stated.custom_fields, now);
|
|
99
|
+
}
|
|
100
|
+
return made;
|
|
101
|
+
});
|
|
102
|
+
await this.fields.recordCreated(context, created);
|
|
103
|
+
}
|
|
104
|
+
async questions(productId) {
|
|
105
|
+
const rows = await (0, queries_1.questionRows)(this.knex, productId);
|
|
106
|
+
const byTier = new Map();
|
|
107
|
+
for (const row of rows) {
|
|
108
|
+
const parts = zod_1.z.decode(codec_1.questionRowCodec, row);
|
|
109
|
+
const forTier = byTier.get(parts.tierId) ?? [];
|
|
110
|
+
forTier.push(parts);
|
|
111
|
+
byTier.set(parts.tierId, forTier);
|
|
112
|
+
}
|
|
113
|
+
return byTier;
|
|
114
|
+
}
|
|
115
|
+
async planCollection(stated) {
|
|
116
|
+
const wanted = [];
|
|
117
|
+
if (stated.shipping?.collect) {
|
|
118
|
+
wanted.push({ port: field_ports_1.STRIPE_PORT.shippingName, key: stated.shipping.name.custom_field_key }, { port: field_ports_1.STRIPE_PORT.shippingAddress, key: stated.shipping.address.custom_field_key });
|
|
119
|
+
}
|
|
120
|
+
if (stated.phone?.collect) {
|
|
121
|
+
wanted.push({ port: field_ports_1.STRIPE_PORT.phone, key: stated.phone.custom_field_key });
|
|
122
|
+
}
|
|
123
|
+
const bound = new Set(wanted.map(({ port }) => port));
|
|
124
|
+
const clear = [];
|
|
125
|
+
for (const block of ['shipping', 'phone']) {
|
|
126
|
+
if (stated[block]) {
|
|
127
|
+
clear.push(...BLOCK_PORTS[block].filter((port) => !bound.has(port)));
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
const create = new Map();
|
|
131
|
+
for (const { port, key } of wanted) {
|
|
132
|
+
const wants = destinations_1.PORT_FIELD[port];
|
|
133
|
+
const existing = await this.fields.findByKey(key);
|
|
134
|
+
if (existing) {
|
|
135
|
+
assertCollectableInto(port, existing, wants.type);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const alreadyPlanned = create.get(key);
|
|
139
|
+
if (alreadyPlanned && alreadyPlanned.type !== wants.type) {
|
|
140
|
+
throw new errors_1.default.ValidationError({
|
|
141
|
+
message: `This can only be collected into a ${wants.type} field.`,
|
|
142
|
+
property: `checkout.${port}.custom_field_key`,
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
if (!alreadyPlanned) {
|
|
146
|
+
create.set(key, { key, name: wants.name, type: wants.type });
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return { clear, create: [...create.values()], bind: wanted };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Saves the questions this tier asks its buyers during checkout.
|
|
153
|
+
*
|
|
154
|
+
* A binding records that one value coming back from Stripe belongs in one custom field,
|
|
155
|
+
* and it identifies the value by the name Stripe uses for it. For a question that name
|
|
156
|
+
* is the custom field's own key: Ghost sends the key to Stripe as the question's
|
|
157
|
+
* identifier, and Stripe returns the buyer's answer labelled with that same key. That is
|
|
158
|
+
* why the key is passed twice below — once as the name Stripe will answer under, and
|
|
159
|
+
* once as the field the answer is stored in.
|
|
160
|
+
*/
|
|
161
|
+
async writeQuestions(trx, productId, questions, now) {
|
|
162
|
+
const asked = new Set(questions.map((question) => question.key));
|
|
163
|
+
const alreadyAsked = await trx(queries_1.QUESTIONS_TABLE)
|
|
164
|
+
.join(queries_1.BINDINGS_TABLE, `${queries_1.BINDINGS_TABLE}.id`, `${queries_1.QUESTIONS_TABLE}.binding_id`)
|
|
165
|
+
.where(`${queries_1.BINDINGS_TABLE}.product_id`, productId)
|
|
166
|
+
.select(`${queries_1.BINDINGS_TABLE}.port`);
|
|
167
|
+
for (const { port } of alreadyAsked) {
|
|
168
|
+
if (!asked.has(port)) {
|
|
169
|
+
await this.bindings.remove(trx, productId, port);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
for (const [index, question] of questions.entries()) {
|
|
173
|
+
const bindingId = await this.bindings.bind(trx, productId, question.key, question.key, now);
|
|
174
|
+
await trx(queries_1.QUESTIONS_TABLE).where('binding_id', bindingId).del();
|
|
175
|
+
await trx(queries_1.QUESTIONS_TABLE).insert({
|
|
176
|
+
id: new bson_objectid_1.default().toHexString(),
|
|
177
|
+
binding_id: bindingId,
|
|
178
|
+
sort_order: index,
|
|
179
|
+
label: question.label ?? null,
|
|
180
|
+
optional: question.optional ?? true,
|
|
181
|
+
created_at: now,
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
exports.TierCheckoutConfigService = TierCheckoutConfigService;
|
|
187
|
+
function parseInput(input) {
|
|
188
|
+
const parsed = serializers_1.CheckoutConfigInput.safeParse(input);
|
|
189
|
+
if (parsed.success) {
|
|
190
|
+
return parsed.data;
|
|
191
|
+
}
|
|
192
|
+
const issue = parsed.error.issues[0];
|
|
193
|
+
throw new errors_1.default.ValidationError({
|
|
194
|
+
message: issue.message,
|
|
195
|
+
property: issue.path.join('.') || 'checkout',
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
function decodeCollection(rows) {
|
|
199
|
+
return rows.map((row) => zod_1.z.decode(codec_1.collectionRowCodec, row));
|
|
200
|
+
}
|
|
201
|
+
function assemble(row, asked) {
|
|
202
|
+
return {
|
|
203
|
+
tierId: row.tierId,
|
|
204
|
+
customFields: asked.map((entry) => entry.question),
|
|
205
|
+
...row.collection,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
async function assertTierExists(db, productId) {
|
|
209
|
+
const tier = await db('products').where('id', productId).first();
|
|
210
|
+
if (!tier) {
|
|
211
|
+
throw new errors_1.default.NotFoundError({ message: 'Tier not found.' });
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Refuses a custom field that cannot hold what Stripe will send back.
|
|
216
|
+
*
|
|
217
|
+
* A request names the custom field each collected value should be saved into. That field
|
|
218
|
+
* has to be active, because an archived field accepts no new values, and it has to store
|
|
219
|
+
* the right kind of data: Stripe returns a structured address for the shipping address,
|
|
220
|
+
* and plain text for a name or a phone number.
|
|
221
|
+
*
|
|
222
|
+
* Both refusals are deliberate rather than defensive. Ghost could save the value into some
|
|
223
|
+
* other field, or accept the request and quietly collect nothing, but a publisher who named
|
|
224
|
+
* a field meant that field. Being told now is better than finding out weeks later that
|
|
225
|
+
* nothing was ever recorded.
|
|
226
|
+
*/
|
|
227
|
+
function assertCollectableInto(port, field, valueType) {
|
|
228
|
+
if (field.status !== schema_1.FIELD_STATUS.active) {
|
|
229
|
+
throw new errors_1.default.ValidationError({
|
|
230
|
+
message: 'An archived custom field cannot receive collected data. Restore it first.',
|
|
231
|
+
property: `checkout.${port}.custom_field_key`,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
if (field.type !== valueType) {
|
|
235
|
+
throw new errors_1.default.ValidationError({
|
|
236
|
+
message: `This can only be collected into a ${valueType} field.`,
|
|
237
|
+
property: `checkout.${port}.custom_field_key`,
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
async function writeOptions(trx, productId, stated, now) {
|
|
242
|
+
const all = zod_1.z.encode(codec_1.optionsCodec, {
|
|
243
|
+
shippingAllowedCountries: stated.shipping?.collect ? stated.shipping.allowed_countries : [],
|
|
244
|
+
taxNumber: stated.tax_number?.collect ?? false,
|
|
245
|
+
});
|
|
246
|
+
// Only the columns this request spoke about are written, so two requests changing
|
|
247
|
+
// different parts of the same tier cannot undo each other, and a request that mentions
|
|
248
|
+
// neither still leaves the row behind as the record that this tier has been set up.
|
|
249
|
+
const columns = {
|
|
250
|
+
...(stated.shipping ? { shipping_allowed_countries: all.shipping_allowed_countries } : {}),
|
|
251
|
+
...(stated.tax_number ? { tax_number_collect: all.tax_number_collect } : {}),
|
|
252
|
+
};
|
|
253
|
+
await trx(queries_1.CONFIG_TABLE)
|
|
254
|
+
.insert({
|
|
255
|
+
id: new bson_objectid_1.default().toHexString(),
|
|
256
|
+
product_id: productId,
|
|
257
|
+
created_at: now,
|
|
258
|
+
updated_at: now,
|
|
259
|
+
...columns,
|
|
260
|
+
})
|
|
261
|
+
.onConflict('product_id')
|
|
262
|
+
.merge({ ...columns, updated_at: now });
|
|
263
|
+
}
|
|
264
|
+
async function assertQuestionsAskable(db, questions) {
|
|
265
|
+
const collides = questions.find((question) => (0, field_ports_1.isStripePort)(question.key));
|
|
266
|
+
if (collides) {
|
|
267
|
+
throw new errors_1.default.ValidationError({
|
|
268
|
+
message: `A field keyed ${collides.key} cannot be asked at checkout, because that is what this checkout calls something it collects for itself.`,
|
|
269
|
+
property: 'checkout.custom_fields',
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
const keys = questions.map((question) => question.key);
|
|
273
|
+
if (keys.length === 0) {
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const rows = await db(queries_1.FIELDS_TABLE)
|
|
277
|
+
.whereIn(`${queries_1.FIELDS_TABLE}.key`, keys)
|
|
278
|
+
.where(`${queries_1.FIELDS_TABLE}.status`, schema_1.FIELD_STATUS.active)
|
|
279
|
+
.select(`${queries_1.FIELDS_TABLE}.key`, `${queries_1.FIELDS_TABLE}.name`, `${queries_1.FIELDS_TABLE}.type`, `${queries_1.FIELDS_TABLE}.status`);
|
|
280
|
+
const byKey = new Map(rows.map((row) => [row.key, row]));
|
|
281
|
+
for (const question of questions) {
|
|
282
|
+
const field = byKey.get(question.key);
|
|
283
|
+
if (!field) {
|
|
284
|
+
throw new errors_1.default.ValidationError({
|
|
285
|
+
message: `Unknown custom field: ${question.key}`,
|
|
286
|
+
property: 'checkout.custom_fields',
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
if (!(0, field_ports_1.isCheckoutEligible)(field.type)) {
|
|
290
|
+
throw new errors_1.default.ValidationError({
|
|
291
|
+
message: `A ${field.type} field cannot be asked for at checkout.`,
|
|
292
|
+
property: 'checkout.custom_fields',
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
const prompt = question.label ?? field.name;
|
|
296
|
+
if (prompt.length > field_ports_1.MAX_CHECKOUT_LABEL_LENGTH) {
|
|
297
|
+
throw new errors_1.default.ValidationError({
|
|
298
|
+
message: `A checkout question can be at most ${field_ports_1.MAX_CHECKOUT_LABEL_LENGTH} characters. Give this one a shorter label.`,
|
|
299
|
+
property: 'checkout.custom_fields',
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
}
|
|
@@ -30,6 +30,27 @@ class TiersServiceWrapper {
|
|
|
30
30
|
repository,
|
|
31
31
|
slugService,
|
|
32
32
|
});
|
|
33
|
+
|
|
34
|
+
// What a tier's checkout asks, kept beside the tier rather than inside it: these
|
|
35
|
+
// rows are read live on every request, because deleting a custom field cascades a
|
|
36
|
+
// question away without the repository above ever seeing it, and a cached copy
|
|
37
|
+
// would go on naming a field the site no longer has.
|
|
38
|
+
//
|
|
39
|
+
// Boot builds the custom fields services before this one, so both collaborators
|
|
40
|
+
// are ready by the time this runs.
|
|
41
|
+
const { TierCheckoutConfigService } = require('../tier-checkout-config');
|
|
42
|
+
const { bindings, definitions } = require('../members-custom-fields');
|
|
43
|
+
this.checkout = new TierCheckoutConfigService({
|
|
44
|
+
knex: models.Base.knex,
|
|
45
|
+
// A binding is where a collected value lands, and the same rows are what a completed
|
|
46
|
+
// checkout is routed through later. Handed over rather than reached for, so the
|
|
47
|
+
// checkout domain states what it needs of them and nothing more.
|
|
48
|
+
bindings,
|
|
49
|
+
// Turning a collection on has to leave it somewhere to land, and making a field is
|
|
50
|
+
// the definitions' to do. A separate collaborator because it is a separate act, and
|
|
51
|
+
// because deciding whether one is needed belongs to the checkout rather than to them.
|
|
52
|
+
fields: definitions,
|
|
53
|
+
});
|
|
33
54
|
}
|
|
34
55
|
}
|
|
35
56
|
|
|
@@ -146,6 +146,25 @@ module.exports = function apiRoutes() {
|
|
|
146
146
|
// Tiers
|
|
147
147
|
router.get('/tiers', mw.authAdminApi, http(api.tiers.browse));
|
|
148
148
|
router.post('/tiers', mw.authAdminApi, http(api.tiers.add));
|
|
149
|
+
// What a tier's checkout asks for, read when a Stripe checkout session is built.
|
|
150
|
+
// Registered before /tiers/:id so the literal path isn't captured by :id.
|
|
151
|
+
//
|
|
152
|
+
// A sub-resource rather than a key on the tier, because the tier payload is a public
|
|
153
|
+
// projection: `tiers-public` shares this docName's serializer, so anything on a tier is
|
|
154
|
+
// rendered by themes. This is admin-only configuration that no client renders, since
|
|
155
|
+
// the questions are drawn by Stripe's own checkout page rather than by Portal.
|
|
156
|
+
//
|
|
157
|
+
// Named for the configuration rather than the checkout, so it cannot be mistaken for
|
|
158
|
+
// the session that `create-stripe-checkout-session` creates from it.
|
|
159
|
+
router.get('/tiers/checkout_config', mw.authAdminApi, http(api.tiersCheckoutConfig.browse));
|
|
160
|
+
router.get('/tiers/:id/checkout_config', mw.authAdminApi, http(api.tiersCheckoutConfig.read));
|
|
161
|
+
router.put(
|
|
162
|
+
'/tiers/:id/checkout_config',
|
|
163
|
+
mw.authAdminApi,
|
|
164
|
+
labs.enabledMiddleware('membersCustomFields'),
|
|
165
|
+
http(api.tiersCheckoutConfig.edit),
|
|
166
|
+
);
|
|
167
|
+
|
|
149
168
|
router.get('/tiers/:id', mw.authAdminApi, http(api.tiers.read));
|
|
150
169
|
router.put('/tiers/:id', mw.authAdminApi, http(api.tiers.edit));
|
|
151
170
|
|
|
@@ -176,14 +195,11 @@ module.exports = function apiRoutes() {
|
|
|
176
195
|
|
|
177
196
|
router.get('/members/stripe_connect', mw.authAdminApi, http(api.membersStripeConnect.auth));
|
|
178
197
|
|
|
179
|
-
// Member custom field definitions
|
|
198
|
+
// Member custom field definitions. Reading them is open: a site that has never
|
|
199
|
+
// turned the flag on has none, so the answer is an empty list rather than a 404, and
|
|
200
|
+
// Admin can ask without first knowing whether it may. Changing them is gated.
|
|
180
201
|
// Registered before /members/:id so the literal path isn't captured by :id.
|
|
181
|
-
router.get(
|
|
182
|
-
'/members/custom_fields',
|
|
183
|
-
mw.authAdminApi,
|
|
184
|
-
labs.enabledMiddleware('membersCustomFields'),
|
|
185
|
-
http(api.membersCustomFields.browse),
|
|
186
|
-
);
|
|
202
|
+
router.get('/members/custom_fields', mw.authAdminApi, http(api.membersCustomFields.browse));
|
|
187
203
|
router.post(
|
|
188
204
|
'/members/custom_fields',
|
|
189
205
|
mw.authAdminApi,
|
|
@@ -197,12 +213,7 @@ module.exports = function apiRoutes() {
|
|
|
197
213
|
labs.enabledMiddleware('membersCustomFields'),
|
|
198
214
|
http(api.membersCustomFields.reorder),
|
|
199
215
|
);
|
|
200
|
-
router.get(
|
|
201
|
-
'/members/custom_fields/:key',
|
|
202
|
-
mw.authAdminApi,
|
|
203
|
-
labs.enabledMiddleware('membersCustomFields'),
|
|
204
|
-
http(api.membersCustomFields.read),
|
|
205
|
-
);
|
|
216
|
+
router.get('/members/custom_fields/:key', mw.authAdminApi, http(api.membersCustomFields.read));
|
|
206
217
|
router.put(
|
|
207
218
|
'/members/custom_fields/:key',
|
|
208
219
|
mw.authAdminApi,
|
|
@@ -37,8 +37,8 @@ const messages = {
|
|
|
37
37
|
invalidFile: 'Please select a valid CSV file.',
|
|
38
38
|
},
|
|
39
39
|
posts: {
|
|
40
|
-
missingFile: 'Please select a posts CSV file.',
|
|
41
|
-
invalidFile: 'Please select a valid CSV file.',
|
|
40
|
+
missingFile: 'Please select a posts CSV or ZIP file.',
|
|
41
|
+
invalidFile: 'Please select a valid CSV or ZIP file.',
|
|
42
42
|
},
|
|
43
43
|
images: {
|
|
44
44
|
missingFile: 'Please select an image.',
|
|
@@ -12,6 +12,11 @@ module.exports = function setupParentApp() {
|
|
|
12
12
|
const parentApp = express('parent');
|
|
13
13
|
|
|
14
14
|
parentApp.use(mw.requestId);
|
|
15
|
+
|
|
16
|
+
if (config.get('queryParameterFiltering:enabled')) {
|
|
17
|
+
parentApp.use(mw.filterQueryParameters);
|
|
18
|
+
}
|
|
19
|
+
|
|
15
20
|
parentApp.use(mw.logRequest);
|
|
16
21
|
|
|
17
22
|
// Register event emitter on req/res to trigger cache invalidation webhook event
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.filterQueryParameters = filterQueryParameters;
|
|
7
|
+
const logging_1 = __importDefault(require("@tryghost/logging"));
|
|
8
|
+
const query_parameter_policy_1 = require("../../query-parameter-policy");
|
|
9
|
+
const allowedQueryParameters = new Set(query_parameter_policy_1.queryParameterPolicy.public.map((entry) => entry.name));
|
|
10
|
+
const allowedContentApiQueryParameters = new Set(query_parameter_policy_1.queryParameterPolicy.contentApi.map((entry) => entry.name));
|
|
11
|
+
const CONTENT_API_PATH_PATTERN = /\/ghost\/api\/(?:(?:v[0-9]+|canary)\/content|content)(?:\/|$)/;
|
|
12
|
+
const EXEMPT_PATH_PATTERNS = [
|
|
13
|
+
/\/ghost\/api(?:\/|$)/,
|
|
14
|
+
/\/\.ghost(?:\/|$)/,
|
|
15
|
+
/\/\.well-known(?:\/|$)/,
|
|
16
|
+
/\/socket\.io(?:\/|$)/,
|
|
17
|
+
];
|
|
18
|
+
const MAX_LOGGED_PARAMETERS = 10;
|
|
19
|
+
const REQUEST_URL_BASE = 'http://ignored.example';
|
|
20
|
+
const replaceQueryString = (requestTarget, filteredRequestTarget) => {
|
|
21
|
+
const requestUrl = new URL(requestTarget, REQUEST_URL_BASE);
|
|
22
|
+
const filteredUrl = new URL(filteredRequestTarget, REQUEST_URL_BASE);
|
|
23
|
+
return `${requestUrl.pathname}${filteredUrl.search}`;
|
|
24
|
+
};
|
|
25
|
+
const replaceParsedQuery = (query, requestTarget) => {
|
|
26
|
+
for (const parameter of Object.keys(query)) {
|
|
27
|
+
delete query[parameter];
|
|
28
|
+
}
|
|
29
|
+
const { searchParams } = new URL(requestTarget, REQUEST_URL_BASE);
|
|
30
|
+
for (const parameter of new Set(searchParams.keys())) {
|
|
31
|
+
const values = searchParams.getAll(parameter);
|
|
32
|
+
query[parameter] = values.length === 1 ? values[0] : values;
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
const removeUnknownParameters = (searchParams, allowlist) => {
|
|
36
|
+
const removed = new Set();
|
|
37
|
+
for (const parameter of [...searchParams.keys()]) {
|
|
38
|
+
if (!allowlist.has(parameter)) {
|
|
39
|
+
searchParams.delete(parameter);
|
|
40
|
+
removed.add(parameter);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return removed;
|
|
44
|
+
};
|
|
45
|
+
const filterRequestTarget = (requestTarget) => {
|
|
46
|
+
const { pathname, searchParams } = new URL(requestTarget, REQUEST_URL_BASE);
|
|
47
|
+
const contentApiRequest = CONTENT_API_PATH_PATTERN.test(pathname);
|
|
48
|
+
const exemptPath = EXEMPT_PATH_PATTERNS.some((pattern) => pattern.test(pathname));
|
|
49
|
+
if (!contentApiRequest && exemptPath) {
|
|
50
|
+
return {
|
|
51
|
+
requestTarget,
|
|
52
|
+
removedUnknownParameters: [],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
const allowlist = contentApiRequest ? allowedContentApiQueryParameters : allowedQueryParameters;
|
|
56
|
+
const removedUnknownParameters = removeUnknownParameters(searchParams, allowlist);
|
|
57
|
+
if (removedUnknownParameters.size === 0) {
|
|
58
|
+
return {
|
|
59
|
+
requestTarget,
|
|
60
|
+
removedUnknownParameters: [],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
const query = searchParams.toString();
|
|
64
|
+
return {
|
|
65
|
+
requestTarget: query ? `${pathname}?${query}` : pathname,
|
|
66
|
+
removedUnknownParameters: [...removedUnknownParameters].sort(),
|
|
67
|
+
};
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Applies Ghost(Pro)'s public query parameter allowlist in local development.
|
|
71
|
+
*
|
|
72
|
+
* Update the production policy and this manifest together when adding a parameter.
|
|
73
|
+
* This middleware is enabled by the root pnpm dev Docker Compose configuration.
|
|
74
|
+
*/
|
|
75
|
+
function filterQueryParameters(req, _res, next) {
|
|
76
|
+
const requestTarget = req.originalUrl;
|
|
77
|
+
const result = filterRequestTarget(requestTarget);
|
|
78
|
+
if (result.requestTarget !== requestTarget) {
|
|
79
|
+
const query = req.query;
|
|
80
|
+
req.originalUrl = result.requestTarget;
|
|
81
|
+
req.url = replaceQueryString(req.url, result.requestTarget);
|
|
82
|
+
replaceParsedQuery(query, result.requestTarget);
|
|
83
|
+
}
|
|
84
|
+
if (result.removedUnknownParameters.length > 0) {
|
|
85
|
+
const strippedParameters = result.removedUnknownParameters
|
|
86
|
+
.slice(0, MAX_LOGGED_PARAMETERS)
|
|
87
|
+
.map(encodeURIComponent)
|
|
88
|
+
.join(', ');
|
|
89
|
+
const omittedParameterCount = result.removedUnknownParameters.length - MAX_LOGGED_PARAMETERS;
|
|
90
|
+
const omittedParameters = omittedParameterCount > 0 ? `, and ${omittedParameterCount} more` : '';
|
|
91
|
+
logging_1.default.warn(`[query-parameter-filter] Stripped undeclared query parameter(s) from ${req.path}: ${strippedParameters}${omittedParameters}`);
|
|
92
|
+
}
|
|
93
|
+
next();
|
|
94
|
+
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
module.exports = {
|
|
2
2
|
emitEvents: require('./emit-events'),
|
|
3
|
+
filterQueryParameters: require('./filter-query-parameters').filterQueryParameters,
|
|
3
4
|
ghostLocals: require('./ghost-locals').ghostLocals,
|
|
4
5
|
logRequest: require('./log-request'),
|
|
5
6
|
queueRequest: require('./queue-request'),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.queryParameterPolicy = void 0;
|
|
7
|
+
const policy_json_1 = __importDefault(require("./policy.json"));
|
|
8
|
+
const schema_1 = require("./schema");
|
|
9
|
+
exports.queryParameterPolicy = (0, schema_1.parseQueryParameterPolicy)(policy_json_1.default);
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"public": [
|
|
4
|
+
{ "name": "m", "reason": "Link tracking" },
|
|
5
|
+
{ "name": "v", "reason": "Asset invalidation" },
|
|
6
|
+
{ "name": "r", "reason": "Password-protected site redirects" },
|
|
7
|
+
{ "name": "token", "reason": "Members API authentication" },
|
|
8
|
+
{ "name": "action", "reason": "Unsubscribe links" },
|
|
9
|
+
{ "name": "ssml", "reason": "Ghost SSO" },
|
|
10
|
+
{ "name": "ssts", "reason": "Ghost SSO" },
|
|
11
|
+
{ "name": "uuid", "reason": "Member UUID used for unsubscribing" },
|
|
12
|
+
{ "name": "key", "reason": "Members key used to sign unsubscribe paths" },
|
|
13
|
+
{ "name": "portal-action", "reason": "Members Portal actions" },
|
|
14
|
+
{ "name": "newsletter", "reason": "Multiple newsletters, including unsubscribe links" },
|
|
15
|
+
{ "name": "comments", "reason": "Unsubscribing from comment notifications" },
|
|
16
|
+
{ "name": "updatesandannouncements", "reason": "Unsubscribing from automations emails" },
|
|
17
|
+
{ "name": "firstStart", "reason": "Opening the getting-started modal" },
|
|
18
|
+
{ "name": "include", "reason": "Frontend comments API includes" },
|
|
19
|
+
{ "name": "fields", "reason": "Frontend comments API field selection" },
|
|
20
|
+
{ "name": "limit", "reason": "Frontend comments API pagination" },
|
|
21
|
+
{ "name": "order", "reason": "Frontend comments API sorting" },
|
|
22
|
+
{ "name": "filter", "reason": "Frontend comments API filtering" },
|
|
23
|
+
{ "name": "page", "reason": "Frontend comments API pagination" },
|
|
24
|
+
{ "name": "ids", "reason": "Frontend comments counts API" },
|
|
25
|
+
{ "name": "via", "reason": "FirstPromoter referral tracking" },
|
|
26
|
+
{ "name": "ref", "reason": "Attribution tracking" },
|
|
27
|
+
{ "name": "source", "reason": "Attribution tracking" },
|
|
28
|
+
{ "name": "utm_source", "reason": "Attribution tracking" },
|
|
29
|
+
{ "name": "utm_medium", "reason": "Attribution tracking" },
|
|
30
|
+
{ "name": "attribution_id", "reason": "Attribution tracking" },
|
|
31
|
+
{ "name": "attribution_type", "reason": "Attribution tracking" },
|
|
32
|
+
{ "name": "resource", "reason": "WebFinger resource lookup" },
|
|
33
|
+
{ "name": "sr_id", "reason": "Signup redirect tracking" },
|
|
34
|
+
{ "name": "member_status", "reason": "Testing card and paywall content visibility" },
|
|
35
|
+
{
|
|
36
|
+
"name": "member_tier",
|
|
37
|
+
"reason": "Previewing card and paywall visibility for a specific tier"
|
|
38
|
+
},
|
|
39
|
+
{ "name": "otc_verification", "reason": "One-time codes in member sign-ins" },
|
|
40
|
+
{ "name": "admin", "reason": "Admin toolbar activation" },
|
|
41
|
+
{ "name": "admin_toolbar", "reason": "Admin toolbar suppression" },
|
|
42
|
+
{ "name": "gift", "reason": "Gift-link unlock token on canonical post URLs" },
|
|
43
|
+
{ "name": "step", "reason": "Automations analytics" }
|
|
44
|
+
],
|
|
45
|
+
"contentApi": [
|
|
46
|
+
{ "name": "absolute_urls", "reason": "Converts relative URLs to absolute URLs" },
|
|
47
|
+
{ "name": "collection", "reason": "Filters resources by collection" },
|
|
48
|
+
{ "name": "debug", "reason": "Enables debug mode where available" },
|
|
49
|
+
{ "name": "fields", "reason": "Limits returned fields" },
|
|
50
|
+
{ "name": "filter", "reason": "Filters resources using NQL" },
|
|
51
|
+
{ "name": "formats", "reason": "Selects content formats" },
|
|
52
|
+
{ "name": "include", "reason": "Includes related resources" },
|
|
53
|
+
{ "name": "key", "reason": "Content API authentication key" },
|
|
54
|
+
{ "name": "limit", "reason": "Number of resources per page" },
|
|
55
|
+
{ "name": "order", "reason": "Sort order" },
|
|
56
|
+
{ "name": "page", "reason": "Page number for paginated results" }
|
|
57
|
+
]
|
|
58
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseQueryParameterPolicy = parseQueryParameterPolicy;
|
|
4
|
+
const zod_1 = require("zod");
|
|
5
|
+
const ALLOWED_NAME_CHARACTERS = /^[A-Za-z_-]+$/;
|
|
6
|
+
const QueryParameterPolicyEntrySchema = zod_1.z.object({
|
|
7
|
+
name: zod_1.z
|
|
8
|
+
.string()
|
|
9
|
+
.min(1, { error: 'Parameter names must be nonempty.' })
|
|
10
|
+
.regex(ALLOWED_NAME_CHARACTERS, {
|
|
11
|
+
error: 'Parameter names must only contain letters, underscores, and hyphens.',
|
|
12
|
+
}),
|
|
13
|
+
reason: zod_1.z
|
|
14
|
+
.string()
|
|
15
|
+
.refine((reason) => reason.trim().length > 0, { error: 'Parameter reasons must be nonempty.' }),
|
|
16
|
+
});
|
|
17
|
+
const QueryParameterListSchema = zod_1.z
|
|
18
|
+
.array(QueryParameterPolicyEntrySchema)
|
|
19
|
+
.superRefine((entries, context) => {
|
|
20
|
+
const names = new Set();
|
|
21
|
+
for (const entry of entries) {
|
|
22
|
+
if (names.has(entry.name)) {
|
|
23
|
+
context.addIssue({
|
|
24
|
+
code: 'custom',
|
|
25
|
+
message: `Duplicate parameter name "${entry.name}".`,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
names.add(entry.name);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
const QueryParameterPolicySchema = zod_1.z.object({
|
|
32
|
+
schemaVersion: zod_1.z.literal(1, { error: 'Unsupported schema version; expected 1.' }),
|
|
33
|
+
public: QueryParameterListSchema,
|
|
34
|
+
contentApi: QueryParameterListSchema,
|
|
35
|
+
});
|
|
36
|
+
function parseQueryParameterPolicy(value) {
|
|
37
|
+
return QueryParameterPolicySchema.parse(value);
|
|
38
|
+
}
|