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.
Files changed (399) hide show
  1. package/components/tryghost-adapter-base-scheduling-0.2.3.tgz +0 -0
  2. package/components/tryghost-adapter-base-sso-0.1.3.tgz +0 -0
  3. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  4. package/components/tryghost-kg-default-cards-10.3.5.tgz +0 -0
  5. package/core/boot.js +13 -4
  6. package/core/built/admin/assets/{Combination-gnJO3vIw.js → Combination-5fYEzgbs.js} +1 -1
  7. package/core/built/admin/assets/{PolarAngleAxis-C4qtHy1B.js → PolarAngleAxis-pkMyqz8g.js} +1 -1
  8. package/core/built/admin/assets/{RadialBarChart-Bh09Stsj.js → RadialBarChart-JAKoS4KT.js} +1 -1
  9. package/core/built/admin/assets/{about-BgpmhCuM.js → about-Bve7IJS2.js} +1 -1
  10. package/core/built/admin/assets/{account-migration-8qTEI1i1.js → account-migration-DRhAehfw.js} +1 -1
  11. package/core/built/admin/assets/{action-list-lGzZLHc8.js → action-list-C4v0-du8.js} +1 -1
  12. package/core/built/admin/assets/activitypub/{account-migration-DMHuPnJn.js → account-migration-C-aej4x_.js} +7 -7
  13. package/core/built/admin/assets/activitypub/activitypub.js +3 -4
  14. package/core/built/admin/assets/activitypub/{avatar-DhTo5D1B.js → avatar-DLPL3Abs.js} +59 -68
  15. package/core/built/admin/assets/activitypub/{avatar-flipboard-DnVgbEfa.js → avatar-flipboard-C9iDY-QV.js} +2 -2
  16. package/core/built/admin/assets/activitypub/{bluesky-sharing-BT4zAlW7.js → bluesky-sharing-BXkA5mqV.js} +8 -8
  17. package/core/built/admin/assets/activitypub/{content-formatters-BeDZ-DIa.js → content-formatters-CHcyMFk4.js} +17 -17
  18. package/core/built/admin/assets/activitypub/{copy-8sqxUuNd.js → copy-DIAOQoCZ.js} +2 -2
  19. package/core/built/admin/assets/activitypub/{domain-xQDDu1CS.js → domain-B3ihkPde.js} +8 -8
  20. package/core/built/admin/assets/activitypub/{edit-profile-BXdF_r2G.js → edit-profile-DsZM3rTX.js} +5 -5
  21. package/core/built/admin/assets/activitypub/{explore-CMt7gKlL.js → explore-BodNrZgR.js} +8 -8
  22. package/core/built/admin/assets/activitypub/{feed-Bwqdddp_.js → feed-PwH1iJ84.js} +9 -9
  23. package/core/built/admin/assets/activitypub/{field-4co_8FVg.js → field-D06-kmOJ.js} +3 -3
  24. package/core/built/admin/assets/activitypub/{hash-DwakMs4X.js → hash-Bm3WxYN4.js} +2 -2
  25. package/core/built/admin/assets/activitypub/{inbox-DQ5CwLma.js → inbox-BluD2ucT.js} +16 -16
  26. package/core/built/admin/assets/activitypub/{inbox-ckD4wo_r.js → inbox-d8oJRTf4.js} +2 -2
  27. package/core/built/admin/assets/activitypub/{label-B72sfvEU.js → label-FHSh6ZAw.js} +3 -3
  28. package/core/built/admin/assets/activitypub/layout-Cq4NG0Xr.js +1 -0
  29. package/core/built/admin/assets/activitypub/{moderation-KCCO7L2J.js → moderation-D1Yapsod.js} +9 -9
  30. package/core/built/admin/assets/activitypub/{note-BX_Bz5A0.js → note-C5zjqq5C.js} +8 -8
  31. package/core/built/admin/assets/activitypub/{notifications-BQOYGvRv.js → notifications-B9p8eCHD.js} +9 -9
  32. package/core/built/admin/assets/activitypub/{preferences-6h43G9_q.js → preferences-BCqUdC3v.js} +9 -9
  33. package/core/built/admin/assets/activitypub/{profile-BMN9VVZ2.js → profile-BRCWoFem.js} +178 -178
  34. package/core/built/admin/assets/activitypub/{reply-ZNo04xsO.js → reply-DYK5vVqb.js} +2 -2
  35. package/core/built/admin/assets/activitypub/{routes-3B9mUQ7i.js → routes-BSXT992i.js} +81 -84
  36. package/core/built/admin/assets/activitypub/{separator-D9taVCCa.js → separator-BbQqTm-p.js} +3 -3
  37. package/core/built/admin/assets/activitypub/{settings-71gCm3SO.js → settings-DQDkr06X.js} +6 -6
  38. package/core/built/admin/assets/activitypub/{step-1-BsGGnBuT.js → step-1-B-YfsVeO.js} +5 -5
  39. package/core/built/admin/assets/activitypub/{step-2-teosIS0k.js → step-2-DtPQzlTH.js} +6 -6
  40. package/core/built/admin/assets/activitypub/{step-3-BjD5TokC.js → step-3-D6BFMXL_.js} +10 -10
  41. package/core/built/admin/assets/activitypub/{tabs-Cvwl1wXU.js → tabs-GpNecvfB.js} +6 -6
  42. package/core/built/admin/assets/activitypub/{topic-filter-C3LZJc2X.js → topic-filter-BPDeQQys.js} +3 -3
  43. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-Dp9f0gov.js → use-activity-pub-queries-BoVOxXkV.js} +2 -2
  44. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-C4RI0sa9.js → use-navigate-with-base-path-Bmk5h4lW.js} +220 -220
  45. package/core/built/admin/assets/activitypub/{use-reply-chain-data-C2oNDMUH.js → use-reply-chain-data-wVBX7swM.js} +5 -5
  46. package/core/built/admin/assets/activitypub/{x-Dxm6pV9X.js → x-DbQZOLiI.js} +2 -2
  47. package/core/built/admin/assets/{add-integration-modal--YpTq3c0.js → add-integration-modal-BC0jExDZ.js} +1 -1
  48. package/core/built/admin/assets/{add-newsletter-modal-C8Ix2hol.js → add-newsletter-modal-DchTkA7l.js} +1 -1
  49. package/core/built/admin/assets/{add-recommendation-modal-CScuNFz4.js → add-recommendation-modal-DMvaJZKp.js} +1 -1
  50. package/core/built/admin/assets/alert-dialog-QhXzdHfc.js +1 -0
  51. package/core/built/admin/assets/{analytics-Cnji45H5.js → analytics-No1DDY3A.js} +1 -1
  52. package/core/built/admin/assets/{announcement-bar-modal-BTHiy1ZO.js → announcement-bar-modal-DM3cYOpo.js} +1 -1
  53. package/core/built/admin/assets/{api-keys-Boy5pLbD.js → api-keys-BLm2NDBh.js} +1 -1
  54. package/core/built/admin/assets/audience-9mwU5TW6.js +1 -0
  55. package/core/built/admin/assets/{automations-BWpRCZ_f.js → automations-DpUNUdcZ.js} +1 -1
  56. package/core/built/admin/assets/{bluesky-sharing-DHOpQxff.js → bluesky-sharing-Cjrv_f62.js} +1 -1
  57. package/core/built/admin/assets/{box-By_EgGtm.js → box-CdfQAP_F.js} +1 -1
  58. package/core/built/admin/assets/{brand-icon-09YqFZjV.js → brand-icon-CrIGOA3Z.js} +1 -1
  59. package/core/built/admin/assets/{breadcrumb-CcFr_ltt.js → breadcrumb-DLiqCyFc.js} +1 -1
  60. package/core/built/admin/assets/{button-BshWPHvn.js → button-B2jcu6bk.js} +1 -1
  61. package/core/built/admin/assets/{card-CiiE7QfM.js → card--VNbfgo3.js} +1 -1
  62. package/core/built/admin/assets/{chart-BnXyfH11.js → chart-BrhA41Zi.js} +1 -1
  63. package/core/built/admin/assets/chart-helpers-Fo3bUL3K.js +1 -0
  64. package/core/built/admin/assets/{checkbox-Dwwr_3Py.js → checkbox-BfKktMph.js} +1 -1
  65. package/core/built/admin/assets/{chunk.524.91ecb33e6afc3bd8949b.js → chunk.524.60ca4692d91c6022042f.js} +4 -4
  66. package/core/built/admin/assets/{chunk.582.512549909741883f960c.js → chunk.582.525c7617c2d9e94c940a.js} +6 -6
  67. package/core/built/admin/assets/{chunk.16.f76a6314082d813d67e0.js → chunk.896.435fe0637944d4a69810.js} +3 -3
  68. package/core/built/admin/assets/{code-editor-view-BwIdPy_k.js → code-editor-view-CYVu4EW6.js} +1 -1
  69. package/core/built/admin/assets/{code-editor-view-CtkF82Fv.js → code-editor-view-DCQJxsfU.js} +1 -1
  70. package/core/built/admin/assets/{color-picker-Bmo1r9Fc.js → color-picker-BTY_NdEm.js} +1 -1
  71. package/core/built/admin/assets/{color-picker-field-DywowPhF.js → color-picker-field-CPlkzOH0.js} +1 -1
  72. package/core/built/admin/assets/{combobox-C4QCh6Yw.js → combobox-DrMtv_4P.js} +1 -1
  73. package/core/built/admin/assets/command-ZdgYJpoI.js +1 -0
  74. package/core/built/admin/assets/comments-kidj60-h.js +1 -0
  75. package/core/built/admin/assets/{confirmation-context-CLFHhyyH.js → confirmation-context-BhbmQQEl.js} +1 -1
  76. package/core/built/admin/assets/{container-LiZf3XdY.js → container-BuQG1wuP.js} +1 -1
  77. package/core/built/admin/assets/{content-api-modal-BtpNI92-.js → content-api-modal-CyQEC3fr.js} +1 -1
  78. package/core/built/admin/assets/{copy-field-CglgRZyG.js → copy-field-B3M1s78L.js} +1 -1
  79. package/core/built/admin/assets/{custom-field-type-option-D0PXoSnP.js → custom-field-type-option-CsGIZk1J.js} +1 -1
  80. package/core/built/admin/assets/{custom-integration-modal-DCIHKN33.js → custom-integration-modal-BY37O_9X.js} +1 -1
  81. package/core/built/admin/assets/{data-list-CupF7bjM.js → data-list-CA2jYGPq.js} +1 -1
  82. package/core/built/admin/assets/{date-range-select-JPwM0yUj.js → date-range-select-DD2RKkgB.js} +1 -1
  83. package/core/built/admin/assets/design-and-theme-modal-DUSKM4uC.js +22 -0
  84. package/core/built/admin/assets/{detail-page-D8lx2SdP.js → detail-page-Dnv-CdR1.js} +1 -1
  85. package/core/built/admin/assets/dialog-B44iRwlc.js +1 -0
  86. package/core/built/admin/assets/{dirty-confirm-dialog-CmARpZQS.js → dirty-confirm-dialog-BxgQgQ6H.js} +1 -1
  87. package/core/built/admin/assets/{disabled-sources-indicator-BTJ2ClQu.js → disabled-sources-indicator-Cvy47bE2.js} +1 -1
  88. package/core/built/admin/assets/{dist-DzcfoDJ3.js → dist-8YJUlf3B.js} +1 -1
  89. package/core/built/admin/assets/{dist-Cb5RSHSg.js → dist-B3keLvjj.js} +1 -1
  90. package/core/built/admin/assets/{dist-QLZuGq7o2.js → dist-Cy8tfrp_.js} +1 -1
  91. package/core/built/admin/assets/{dist-BT_Z-eCn.js → dist-Dgt2cr6a.js} +1 -1
  92. package/core/built/admin/assets/{domain-D0SAtc-T.js → domain-Ca6KhoQt.js} +1 -1
  93. package/core/built/admin/assets/{dropzone-CaPOeDRZ.js → dropzone-Bb-5zQTk.js} +1 -1
  94. package/core/built/admin/assets/edit-profile-BdupLlus.js +1 -0
  95. package/core/built/admin/assets/{editor-COXn4Atu.js → editor-BP883Lpn.js} +3 -3
  96. package/core/built/admin/assets/{embed-signup-form-modal-BFTPf04i.js → embed-signup-form-modal-DeBMLxlL.js} +1 -1
  97. package/core/built/admin/assets/{empty-indicator-WB1WgT9h.js → empty-indicator-D9QdpuNX.js} +1 -1
  98. package/core/built/admin/assets/{explore-BsNYrubV.js → explore-FB-CimXw.js} +1 -1
  99. package/core/built/admin/assets/{feed-8RR1O1bK.js → feed-C1GxsmI2.js} +1 -1
  100. package/core/built/admin/assets/{field-LmNk7wZh.js → field-Cj8lmDOo.js} +1 -1
  101. package/core/built/admin/assets/{filter-sources-4C0kF1NU.js → filter-sources-D5n5bFA2.js} +1 -1
  102. package/core/built/admin/assets/{filters-CV5jCb9n.js → filters-DJEzkMUK.js} +1 -1
  103. package/core/built/admin/assets/{filters-Dmr0PoqU.js → filters-MZXvdH5D.js} +1 -1
  104. package/core/built/admin/assets/{first-promoter-modal-OCzSaL3B.js → first-promoter-modal-CGTGLCr5.js} +1 -1
  105. package/core/built/admin/assets/{ghost-403cb39badad3df411fc0ba27d220047.js → ghost-3894eedcc6968f658578c0abfe942aab.js} +2 -2
  106. package/core/built/admin/assets/{gift-link-modal-0mzjn5oF.js → gift-link-modal-B6v0tnRN.js} +1 -1
  107. package/core/built/admin/assets/{grid-CUciBfjf.js → grid-Dj29PY05.js} +1 -1
  108. package/core/built/admin/assets/growth-BGAJD1rs.js +3 -0
  109. package/core/built/admin/assets/growth-D6mhcx3q.js +1 -0
  110. package/core/built/admin/assets/{heading-CRZZ2FUe.js → heading-Dh5SFVjL.js} +1 -1
  111. package/core/built/admin/assets/history-modal-iKwUY3XR.js +1 -0
  112. package/core/built/admin/assets/html-field-C8qVGGC1.js +1 -0
  113. package/core/built/admin/assets/{images-C0XvUYMb.js → images-h8VZEQ9k.js} +1 -1
  114. package/core/built/admin/assets/{inbox-DZXDUt4s.js → inbox-BYd1M06Q.js} +13 -13
  115. package/core/built/admin/assets/index-DjQe-e5s.js +4 -0
  116. package/core/built/admin/assets/{index.es-BMUCj9TR.js → index.es-VihPwfeH.js} +1 -1
  117. package/core/built/admin/assets/{inline-Cw-gigDW.js → inline-BWSs_VzR.js} +1 -1
  118. package/core/built/admin/assets/{input-UHrKuvBT.js → input--W-rCTrX.js} +1 -1
  119. package/core/built/admin/assets/{input-group-BPAxETpQ.js → input-group-D8sCXRF5.js} +1 -1
  120. package/core/built/admin/assets/{input-surface-BdOe4KzO.js → input-surface-BsrIZeSF.js} +1 -1
  121. package/core/built/admin/assets/{integration-header-B4DyXV06.js → integration-header-Bm6IDb1A.js} +1 -1
  122. package/core/built/admin/assets/{invite-user-modal-BrAkxEO3.js → invite-user-modal-MdImLCaJ.js} +1 -1
  123. package/core/built/admin/assets/{koenig-lexical-60O_8aiO.js → koenig-lexical-_-IsVI7E.js} +1 -1
  124. package/core/built/admin/assets/koenig-loader-DYZpm6El.js +2 -0
  125. package/core/built/admin/assets/{kpi-card-fUHZlzBX.js → kpi-card-DW_2y3ug.js} +1 -1
  126. package/core/built/admin/assets/kpi-card-DZQk_0W8.js +1 -0
  127. package/core/built/admin/assets/kpi-helpers-PueTKuA6.js +1 -0
  128. package/core/built/admin/assets/kpi-tabs-CojDs-Qz.js +1 -0
  129. package/core/built/admin/assets/{label-VIG969IC.js → label-DjbT91GM.js} +1 -1
  130. package/core/built/admin/assets/{label-picker-BBWykUeA.js → label-picker-cB51xl1r.js} +1 -1
  131. package/core/built/admin/assets/layout-c_B_1hax.js +1 -0
  132. package/core/built/admin/assets/{link-helpers-Dtq9fxQg.js → link-helpers-MVDdnFCY.js} +1 -1
  133. package/core/built/admin/assets/{list-page-CdsnQR9N.js → list-page-BucXJ0hi.js} +1 -1
  134. package/core/built/admin/assets/{loading-indicator-SFTqyYVE.js → loading-indicator-BpFWTVgh.js} +1 -1
  135. package/core/built/admin/assets/{member-custom-fields-gOv6Hoh3.js → member-custom-fields-qs0dIkxM.js} +1 -1
  136. package/core/built/admin/assets/member-detail-B6uec2a6.js +1 -0
  137. package/core/built/admin/assets/members-BQF3CqhI.js +2 -0
  138. package/core/built/admin/assets/{modal-page-BMTzFHVO.js → modal-page-xCivTMvI.js} +1 -1
  139. package/core/built/admin/assets/{moderation-DzzPLDyl.js → moderation-DY4wF1Q3.js} +1 -1
  140. package/core/built/admin/assets/{multi-select-combobox-CGohnsxJ.js → multi-select-combobox-B6LiRzDO.js} +1 -1
  141. package/core/built/admin/assets/navigation-modal-DX-gNF3X.js +1 -0
  142. package/core/built/admin/assets/newsletter-CyQA7m3m.js +1 -0
  143. package/core/built/admin/assets/{newsletter-detail-modal-DQU3r4g2.js → newsletter-detail-modal-BDrptuxn.js} +1 -1
  144. package/core/built/admin/assets/{newsletter-emails-D5ql1rud.js → newsletter-emails-CFeLlt4z.js} +1 -1
  145. package/core/built/admin/assets/newsletters-DXuNmC42.js +1 -0
  146. package/core/built/admin/assets/{note-i8k6pjaw.js → note-Cgo2LCJ6.js} +1 -1
  147. package/core/built/admin/assets/notifications-BC50AMku.js +1 -0
  148. package/core/built/admin/assets/{offers-container-modal-BWfjSdyg.js → offers-container-modal-DWycIMzT.js} +1 -1
  149. package/core/built/admin/assets/{onboarding-route-Cu_ROWPn.js → onboarding-route-Dmc4IPm3.js} +1 -1
  150. package/core/built/admin/assets/overview-CVbFtn-k.js +1 -0
  151. package/core/built/admin/assets/overview-YKj7SZaK.js +1 -0
  152. package/core/built/admin/assets/{page-header-Cfds8pCg.js → page-header-BR_nbIPp.js} +1 -1
  153. package/core/built/admin/assets/{pintura-modal-TDTrJ89x.js → pintura-modal-BR9hEvS8.js} +1 -1
  154. package/core/built/admin/assets/popover-CCy0h5rq.js +1 -0
  155. package/core/built/admin/assets/{portal-frame-DjBF-uy2.js → portal-frame-dc4XKr0e.js} +1 -1
  156. package/core/built/admin/assets/portal-modal-BvvdF_B9.js +1 -0
  157. package/core/built/admin/assets/{post-analytics-Buz1COoY.js → post-analytics-DX470WRG.js} +1 -1
  158. package/core/built/admin/assets/{post-analytics-header-B-P0eFnT.js → post-analytics-header-CXpJmdDv.js} +1 -1
  159. package/core/built/admin/assets/{post-analytics-provider-Cu3jNeFh.js → post-analytics-provider-CLfKGWZy.js} +1 -1
  160. package/core/built/admin/assets/{post-share-modal-P1_M6o-H.js → post-share-modal-Cl0NXRJr.js} +1 -1
  161. package/core/built/admin/assets/{preferences-Cq1aOiLy.js → preferences-DO0UUWFp.js} +2 -2
  162. package/core/built/admin/assets/{preview-chrome-BOVO6_PE.js → preview-chrome-BljUBlOk.js} +1 -1
  163. package/core/built/admin/assets/{preview-modal-MlTjP8UU.js → preview-modal-C-BL-F9k.js} +1 -1
  164. package/core/built/admin/assets/profile-CxT-0l4d.js +1 -0
  165. package/core/built/admin/assets/radio-group-Bgz60i81.js +1 -0
  166. package/core/built/admin/assets/{router-provider-CJC58zTy.js → router-provider-B0gZyiGJ.js} +1 -1
  167. package/core/built/admin/assets/{schemas-CRsC53ls.js → schemas-4UPic-oD.js} +2 -2
  168. package/core/built/admin/assets/select-DuDUFdPq.js +1 -0
  169. package/core/built/admin/assets/{settings-B5hJKZ5C.js → settings-BjCii7BM.js} +1 -1
  170. package/core/built/admin/assets/settings-b8kNzZSQ.js +4 -0
  171. package/core/built/admin/assets/{settings-breadcrumbs-sLDNedBW.js → settings-breadcrumbs-CyP_9zTo.js} +1 -1
  172. package/core/built/admin/assets/{shade-app-F7WxOyfk-nufqw1hd.js → shade-app-CcS3f8MZ-vwURazk8.js} +2 -2
  173. package/core/built/admin/assets/{share-modal-CLBfj377.js → share-modal-BV70mr9k.js} +1 -1
  174. package/core/built/admin/assets/{skeleton-D7hwjdry.js → skeleton-Ce9GgUpf.js} +1 -1
  175. package/core/built/admin/assets/{slack-modal-Bt9I_tEo.js → slack-modal-Be2_KK-z.js} +1 -1
  176. package/core/built/admin/assets/sort-button-Cc-Z96sA.js +1 -0
  177. package/core/built/admin/assets/{source-icon-BPsD5ees.js → source-icon-v6PUnmyM.js} +1 -1
  178. package/core/built/admin/assets/{stack-CIfOAAVf.js → stack-B2eszIiq.js} +1 -1
  179. package/core/built/admin/assets/{stats-B_YkF7bp.js → stats-C4reGh9O.js} +1 -1
  180. package/core/built/admin/assets/{stats-view-BOCp9RG8.js → stats-view-DP9pQ62k.js} +1 -1
  181. package/core/built/admin/assets/{step-1-CMta6YFO.js → step-1-DSRp2vBj.js} +1 -1
  182. package/core/built/admin/assets/{step-2-BG04nh5q.js → step-2-COqv2g0u.js} +1 -1
  183. package/core/built/admin/assets/{step-3-ZnMaVIPj.js → step-3-6W1NMVQc.js} +2 -2
  184. package/core/built/admin/assets/{stripe-button-DDoUKN5a.js → stripe-button-D2K42Uty.js} +1 -1
  185. package/core/built/admin/assets/{stripe-connect-modal-BeSukI8q.js → stripe-connect-modal-Z-UT_BGc.js} +1 -1
  186. package/core/built/admin/assets/{switch-C9djUIMO.js → switch-CI4gLWqb.js} +1 -1
  187. package/core/built/admin/assets/{table-CrqQtkR2.js → table-B2pIKjVh.js} +1 -1
  188. package/core/built/admin/assets/{tabs-CGisSbWq.js → tabs-SaW_UT-Q.js} +1 -1
  189. package/core/built/admin/assets/tag-detail-Dc0nTOtU.js +2 -0
  190. package/core/built/admin/assets/tags-BLaXBwnb.js +1 -0
  191. package/core/built/admin/assets/testimonials-modal-sFwRnNLh.js +1 -0
  192. package/core/built/admin/assets/{text-DU9Hv33_.js → text-BQDSDsKs.js} +1 -1
  193. package/core/built/admin/assets/{textarea-d9qEkMOP.js → textarea-DJ1Q5V39.js} +1 -1
  194. package/core/built/admin/assets/{tier-detail-modal-CxRwgTPR.js → tier-detail-modal-BCFpWwUS.js} +1 -1
  195. package/core/built/admin/assets/{toggle-group-CtKZURJd.js → toggle-group-EM9WpESu.js} +1 -1
  196. package/core/built/admin/assets/{topic-filter-D8JT8Tq1.js → topic-filter-DY4gNHn2.js} +1 -1
  197. package/core/built/admin/assets/{transistor-modal-6PUf0idx.js → transistor-modal-CpyPG-MH.js} +1 -1
  198. package/core/built/admin/assets/trend-badge-ByMkuAuH.js +1 -0
  199. package/core/built/admin/assets/{unsplash-modal-BPuDBPja.js → unsplash-modal-Cv2Q6GtY.js} +1 -1
  200. package/core/built/admin/assets/{use-analytics-data--opuzwcz.js → use-analytics-data-CHvjMYuT.js} +1 -1
  201. package/core/built/admin/assets/use-check-theme-limit-error-Qv4SVXqw.js +2 -0
  202. package/core/built/admin/assets/use-filter-params-CAvfDvf1.js +1 -0
  203. package/core/built/admin/assets/use-growth-stats-D6mZXGhK.js +1 -0
  204. package/core/built/admin/assets/{use-limiter-TDSNiHPB.js → use-limiter-cwJvsNBo.js} +1 -1
  205. package/core/built/admin/assets/{use-navigate-with-base-path-CK989MlC.js → use-navigate-with-base-path-BHPA228f.js} +1 -1
  206. package/core/built/admin/assets/{use-pintura-config-CnCGrDFk.js → use-pintura-config-CzTfnufz.js} +1 -1
  207. package/core/built/admin/assets/{use-pintura-editor-ntphvtUd.js → use-pintura-editor-BDBXWUYq.js} +1 -1
  208. package/core/built/admin/assets/{use-post-referrers-CbXLFIex.js → use-post-referrers-DbY-mzci.js} +1 -1
  209. package/core/built/admin/assets/{use-reply-chain-data-JYFKSO83.js → use-reply-chain-data-BeQwz8oF.js} +1 -1
  210. package/core/built/admin/assets/{use-router-history-entry-BwfQS2mq.js → use-router-history-entry-Bg6BhBQQ.js} +1 -1
  211. package/core/built/admin/assets/{use-setting-group-DQxV42-O.js → use-setting-group-VM7FQwMY.js} +1 -1
  212. package/core/built/admin/assets/{use-settings-navigation-BBzro2-V.js → use-settings-navigation-BbrSUVcf.js} +1 -1
  213. package/core/built/admin/assets/{use-simple-pagination-B2TdNX67.js → use-simple-pagination-Dum6Irbs.js} +1 -1
  214. package/core/built/admin/assets/{use-staff-users-QrR9voeC.js → use-staff-users-cZg4ugOF.js} +1 -1
  215. package/core/built/admin/assets/{use-tinybird-query-D2Aeoty5.js → use-tinybird-query-B1IMyAcv.js} +1 -1
  216. package/core/built/admin/assets/{use-tinybird-token-DBuwMmln.js → use-tinybird-token-Bow0Zwn8.js} +1 -1
  217. package/core/built/admin/assets/{use-unsaved-changes-guard-DpYJcj1d.js → use-unsaved-changes-guard-BAy0RANR.js} +1 -1
  218. package/core/built/admin/assets/{use-url-input-WnIqckzS.js → use-url-input-Df5QzNa7.js} +1 -1
  219. package/core/built/admin/assets/user-detail-modal-yipSoQfv.js +1 -0
  220. package/core/built/admin/assets/{virtual-list-D3MfPNOh.js → virtual-list-DEx8WgiO.js} +1 -1
  221. package/core/built/admin/assets/web-CKcXAu86.js +1 -0
  222. package/core/built/admin/assets/web-DR4TqM3E.js +2 -0
  223. package/core/built/admin/assets/{zapier-modal-BO6PS_Vd.js → zapier-modal-Cw3BTBUu.js} +1 -1
  224. package/core/built/admin/index.html +24 -25
  225. package/core/frontend/services/rendering/renderer.js +15 -0
  226. package/core/server/adapters/storage/LocalStorageBase.js +8 -5
  227. package/core/server/api/endpoints/db.js +19 -5
  228. package/core/server/api/endpoints/index.js +4 -0
  229. package/core/server/api/endpoints/member-custom-fields.js +4 -14
  230. package/core/server/api/endpoints/posts.js +1 -0
  231. package/core/server/api/endpoints/tiers-checkout-config.js +59 -0
  232. package/core/server/api/endpoints/utils/serializers/output/config.js +1 -0
  233. package/core/server/api/endpoints/utils/serializers/output/index.js +4 -0
  234. package/core/server/api/endpoints/utils/serializers/output/tiers-checkout-config.js +12 -0
  235. package/core/server/data/exporter/table-lists.js +3 -0
  236. package/core/server/data/importer/content-files.js +57 -0
  237. package/core/server/data/importer/handlers/image.js +2 -2
  238. package/core/server/data/importer/import-archive.js +131 -0
  239. package/core/server/data/importer/import-manager.js +18 -180
  240. package/core/server/data/importer/importers/content-file-importer.js +66 -4
  241. 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
  242. package/core/server/data/migrations/versions/6.61/2026-08-19-14-38-53-add-members-custom-field-bindings-table.js +26 -0
  243. package/core/server/data/migrations/versions/6.61/2026-08-19-14-48-06-add-tier-checkout-field-tables.js +35 -0
  244. package/core/server/data/schema/schema.js +90 -0
  245. package/core/server/data/seeders/importers/automation-action-revisions-importer.js +3 -3
  246. package/core/server/data/seeders/importers/automation-actions-importer.js +3 -3
  247. package/core/server/data/seeders/importers/automation-run-steps-importer.js +3 -3
  248. package/core/server/data/seeders/importers/automation-runs-importer.js +5 -5
  249. package/core/server/data/seeders/importers/automations-importer.js +3 -3
  250. package/core/server/data/seeders/importers/comment-reports-importer.js +1 -1
  251. package/core/server/data/seeders/importers/comments-importer.js +1 -1
  252. package/core/server/data/seeders/importers/email-batches-importer.js +1 -1
  253. package/core/server/data/seeders/importers/email-recipients-importer.js +1 -1
  254. package/core/server/data/seeders/importers/emails-importer.js +1 -1
  255. package/core/server/data/seeders/importers/labels-importer.js +1 -1
  256. package/core/server/data/seeders/importers/members-click-events-importer.js +1 -1
  257. package/core/server/data/seeders/importers/members-created-events-importer.js +1 -1
  258. package/core/server/data/seeders/importers/members-feedback-importer.js +1 -1
  259. package/core/server/data/seeders/importers/members-importer.js +1 -1
  260. package/core/server/data/seeders/importers/members-login-events-importer.js +1 -1
  261. package/core/server/data/seeders/importers/members-products-importer.js +1 -1
  262. package/core/server/data/seeders/importers/members-status-events-importer.js +1 -1
  263. package/core/server/data/seeders/importers/members-stripe-customers-importer.js +1 -1
  264. package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +1 -1
  265. package/core/server/data/seeders/importers/members-subscribe-events-importer.js +1 -1
  266. package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +1 -1
  267. package/core/server/data/seeders/importers/offer-redemptions-importer.js +1 -1
  268. package/core/server/data/seeders/importers/offers-importer.js +1 -1
  269. package/core/server/data/seeders/importers/posts-importer.js +1 -1
  270. package/core/server/data/seeders/importers/table-importer.js +2 -2
  271. package/core/server/data/seeders/importers/tags-importer.js +1 -1
  272. package/core/server/data/seeders/importers/users-importer.js +1 -1
  273. package/core/server/data/seeders/importers/web-mentions-importer.js +1 -1
  274. package/core/server/data/seeders/utils/random.js +3 -3
  275. package/core/server/ghost-server.js +3 -2
  276. package/core/server/lib/db-types/boolean.js +18 -0
  277. package/core/server/lib/promise-pool.js +31 -0
  278. package/core/server/services/automations/database-automations-repository.js +26 -26
  279. package/core/server/services/automations/poll.js +1 -0
  280. package/core/server/services/content-import/import/assets.js +74 -0
  281. package/core/server/services/content-import/import/importer.js +57 -14
  282. package/core/server/services/content-import/import/post-data.js +1 -0
  283. package/core/server/services/content-import/import/post-repository.js +78 -0
  284. package/core/server/services/content-import/import/relations.js +149 -0
  285. package/core/server/services/content-import/import/row.js +11 -0
  286. package/core/server/services/content-import/import/schema.js +29 -0
  287. package/core/server/services/content-import/import/source.js +125 -0
  288. package/core/server/services/content-import/index.js +6 -28
  289. package/core/server/services/email-analytics/email-analytics-service.js +8 -1
  290. package/core/server/services/email-analytics/lib/queries.js +7 -1
  291. package/core/server/services/gift-links/schema.js +3 -3
  292. package/core/server/services/gifts/CONTEXT.md +1 -1
  293. package/core/server/services/gifts/constants.js +1 -1
  294. package/core/server/services/gifts/gift-bookshelf-repository.js +2 -2
  295. package/core/server/services/gifts/gift-delivery-bookshelf-repository.js +18 -9
  296. package/core/server/services/gifts/gift-delivery-schema.js +5 -5
  297. package/core/server/services/gifts/gift-delivery-service.js +4 -0
  298. package/core/server/services/gifts/gift-schema.js +10 -10
  299. package/core/server/services/gifts/gift-service.js +4 -2
  300. package/core/server/services/jobs-service/register-job-handlers.js +5 -5
  301. package/core/server/services/lib/mailgun-client.js +44 -8
  302. package/core/server/services/media-inliner/external-media-inliner-job.js +12 -0
  303. package/core/server/services/media-inliner/external-media-inliner.js +103 -29
  304. package/core/server/services/media-inliner/service.js +13 -41
  305. package/core/server/services/media-inliner/types.js +2 -0
  306. package/core/server/services/members/import-export/export/exporter.js +4 -6
  307. package/core/server/services/members/import-export/import/importer.js +21 -5
  308. package/core/server/services/members/import-export/import/stripe-utils.js +6 -1
  309. package/core/server/services/members/import-export/index.js +6 -1
  310. package/core/server/services/members/members-api/members-api.js +5 -0
  311. package/core/server/services/members/members-api/services/member-bread-service.js +19 -1
  312. package/core/server/services/members/members-api/services/payments-service.js +47 -1
  313. package/core/server/services/members-custom-fields/actions.js +27 -1
  314. package/core/server/services/members-custom-fields/bindings-service.js +119 -0
  315. package/core/server/services/members-custom-fields/definitions-service.js +104 -13
  316. package/core/server/services/members-custom-fields/index.js +11 -4
  317. package/core/server/services/members-custom-fields/queries.js +5 -0
  318. package/core/server/services/members-custom-fields/schema.js +37 -6
  319. package/core/server/services/members-custom-fields/values-service.js +15 -3
  320. package/core/server/services/public-config/config.js +7 -0
  321. package/core/server/services/stripe/services/checkout/allowed-countries.js +267 -0
  322. package/core/server/services/stripe/services/checkout/completed-session.js +68 -0
  323. package/core/server/services/stripe/services/checkout/field-ports.js +34 -0
  324. package/core/server/services/stripe/services/checkout/session-options.js +97 -0
  325. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +49 -0
  326. package/core/server/services/stripe/stripe-api.js +29 -0
  327. package/core/server/services/stripe/stripe-service.js +7 -0
  328. package/core/server/services/tier-checkout-config/codec.js +95 -0
  329. package/core/server/services/tier-checkout-config/destinations.js +17 -0
  330. package/core/server/services/tier-checkout-config/index.js +26 -0
  331. package/core/server/services/tier-checkout-config/models.js +57 -0
  332. package/core/server/services/tier-checkout-config/queries.js +80 -0
  333. package/core/server/services/tier-checkout-config/schema.js +33 -0
  334. package/core/server/services/tier-checkout-config/serializers.js +122 -0
  335. package/core/server/services/tier-checkout-config/service.js +303 -0
  336. package/core/server/services/tiers/service.js +21 -0
  337. package/core/server/web/api/endpoints/admin/routes.js +24 -13
  338. package/core/server/web/api/middleware/upload.js +2 -2
  339. package/core/server/web/parent/app.js +5 -0
  340. package/core/server/web/parent/middleware/filter-query-parameters.js +94 -0
  341. package/core/server/web/parent/middleware/index.js +1 -0
  342. package/core/server/web/query-parameter-policy/index.js +9 -0
  343. package/core/server/web/query-parameter-policy/policy.json +58 -0
  344. package/core/server/web/query-parameter-policy/schema.js +38 -0
  345. package/core/shared/config/defaults.json +3 -0
  346. package/core/shared/config/env/config.testing-mysql.json +3 -0
  347. package/core/shared/config/env/config.testing.json +3 -0
  348. package/core/shared/config/overrides.json +4 -2
  349. package/core/shared/labs.js +5 -14
  350. package/package.json +12 -13
  351. package/pnpm-lock.yaml +44 -46
  352. package/pnpm-workspace.yaml +6 -6
  353. package/tsconfig.json +2 -3
  354. package/core/built/admin/assets/activitypub/layout-CXajhRsL.js +0 -1
  355. package/core/built/admin/assets/alert-dialog-wEbVxANM.js +0 -1
  356. package/core/built/admin/assets/app-utils-D2Wp7ZSS.js +0 -1
  357. package/core/built/admin/assets/audience-DTLMXAFH.js +0 -1
  358. package/core/built/admin/assets/chart-helpers-B3OqQ6Ez.js +0 -1
  359. package/core/built/admin/assets/command-DcLbSifC.js +0 -1
  360. package/core/built/admin/assets/comments-Bxy_3WyE.js +0 -1
  361. package/core/built/admin/assets/design-and-theme-modal-CZ-PkW7o.js +0 -23
  362. package/core/built/admin/assets/dialog-Db7c164G.js +0 -1
  363. package/core/built/admin/assets/edit-profile-By7v5XW5.js +0 -1
  364. package/core/built/admin/assets/growth-55vWlGT_.js +0 -1
  365. package/core/built/admin/assets/growth-DA5AluYw.js +0 -3
  366. package/core/built/admin/assets/history-modal-G1s5hHz7.js +0 -1
  367. package/core/built/admin/assets/html-field-BegxsWpw.js +0 -1
  368. package/core/built/admin/assets/index-CDP9tIOk.js +0 -4
  369. package/core/built/admin/assets/koenig-loader-DwVY_V-H.js +0 -1
  370. package/core/built/admin/assets/kpi-card-LERqWBoY.js +0 -1
  371. package/core/built/admin/assets/kpi-helpers-DB3ouhYF.js +0 -1
  372. package/core/built/admin/assets/kpi-tabs-BGvtMD8S.js +0 -1
  373. package/core/built/admin/assets/layout-Di5K7doM.js +0 -1
  374. package/core/built/admin/assets/member-detail-D7mZDDp3.js +0 -1
  375. package/core/built/admin/assets/member-route-0Aa52Ntz.js +0 -1
  376. package/core/built/admin/assets/members-DvZG9t69.js +0 -2
  377. package/core/built/admin/assets/navigation-modal-CI8Bx5vn.js +0 -1
  378. package/core/built/admin/assets/newsletter-Bg3cq5U-.js +0 -1
  379. package/core/built/admin/assets/newsletters-DMDfZbMS.js +0 -1
  380. package/core/built/admin/assets/notifications-psA_2Ph4.js +0 -1
  381. package/core/built/admin/assets/overview-C-6xOLaf.js +0 -1
  382. package/core/built/admin/assets/overview-DaLhpRrC.js +0 -1
  383. package/core/built/admin/assets/popover-CFgqFDvC.js +0 -1
  384. package/core/built/admin/assets/portal-modal-Cf0pBAk8.js +0 -1
  385. package/core/built/admin/assets/profile-D6dHYf2o.js +0 -1
  386. package/core/built/admin/assets/radio-group-CjtmsCPl.js +0 -1
  387. package/core/built/admin/assets/select-D_RQaXgt.js +0 -1
  388. package/core/built/admin/assets/settings-DIo_F-MF.js +0 -4
  389. package/core/built/admin/assets/sort-button-DMBXF7tP.js +0 -1
  390. package/core/built/admin/assets/tag-detail-2a3BfzTW.js +0 -2
  391. package/core/built/admin/assets/tags-Dp2_8UzL.js +0 -1
  392. package/core/built/admin/assets/testimonials-modal-C9nrypNo.js +0 -1
  393. package/core/built/admin/assets/use-check-theme-limit-error-C9-QhGdV.js +0 -1
  394. package/core/built/admin/assets/use-filter-params-CSpAvgD2.js +0 -1
  395. package/core/built/admin/assets/use-growth-stats-BOmoPDHJ.js +0 -1
  396. package/core/built/admin/assets/user-detail-modal-59mBJqxN.js +0 -1
  397. package/core/built/admin/assets/web-DUuX7nE2.js +0 -2
  398. package/core/built/admin/assets/web-N8ucs2aG.js +0 -1
  399. /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 gated by the members_custom_fields flag.
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
+ }
@@ -31,6 +31,9 @@
31
31
  },
32
32
  "useMinFiles": true,
33
33
  "verifyRequestIntegrity": true,
34
+ "queryParameterFiltering": {
35
+ "enabled": false
36
+ },
34
37
  "paths": {
35
38
  "contentPath": "content/",
36
39
  "fixtures": "core/server/data/schema/fixtures/fixtures",
@@ -19,6 +19,9 @@
19
19
  "level": "error",
20
20
  "logClientErrorsAsError": false
21
21
  },
22
+ "queryParameterFiltering": {
23
+ "enabled": true
24
+ },
22
25
  "adapters": {
23
26
  "Redis": {
24
27
  "host": "127.0.0.1",
@@ -15,6 +15,9 @@
15
15
  "level": "error",
16
16
  "logClientErrorsAsError": false
17
17
  },
18
+ "queryParameterFiltering": {
19
+ "enabled": true
20
+ },
18
21
  "adapters": {
19
22
  "Redis": {
20
23
  "host": "127.0.0.1",