ghost 6.62.0 → 6.63.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (439) hide show
  1. package/components/tryghost-adapter-base-jobs-0.0.0.tgz +0 -0
  2. package/components/tryghost-adapter-base-scheduling-0.2.3.tgz +0 -0
  3. package/components/tryghost-adapter-base-sso-0.1.3.tgz +0 -0
  4. package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
  5. package/components/tryghost-checkout-0.0.0.tgz +0 -0
  6. package/components/tryghost-i18n-0.0.0.tgz +0 -0
  7. package/components/tryghost-kg-card-factory-5.2.4.tgz +0 -0
  8. package/components/tryghost-kg-clean-basic-html-4.3.4.tgz +0 -0
  9. package/components/tryghost-kg-converters-1.2.5.tgz +0 -0
  10. package/components/tryghost-kg-default-cards-10.3.5.tgz +0 -0
  11. package/components/tryghost-kg-default-nodes-2.2.1.tgz +0 -0
  12. package/components/tryghost-kg-default-transforms-1.3.4.tgz +0 -0
  13. package/components/tryghost-kg-html-to-lexical-1.4.0.tgz +0 -0
  14. package/components/tryghost-kg-lexical-html-renderer-1.5.0.tgz +0 -0
  15. package/components/tryghost-kg-markdown-html-renderer-7.2.4.tgz +0 -0
  16. package/components/tryghost-kg-utils-1.1.4.tgz +0 -0
  17. package/components/tryghost-limit-service-0.0.0.tgz +0 -0
  18. package/components/tryghost-metafield-types-0.0.0.tgz +0 -0
  19. package/content/themes/source/package.json +2 -2
  20. package/content/themes/source/partials/post-card.hbs +1 -1
  21. package/core/boot.js +7 -5
  22. package/core/built/admin/assets/{about-Dyae_UT6.js → about-C1C3s5-g.js} +1 -1
  23. package/core/built/admin/assets/accordion-CxGsDKwa.js +1 -0
  24. package/core/built/admin/assets/{account-migration-CESa0mnX.js → account-migration-CbUhW6gk.js} +1 -1
  25. package/core/built/admin/assets/activitypub/{account-migration-By69CgCp.js → account-migration-KA6Jjf9L.js} +7 -7
  26. package/core/built/admin/assets/activitypub/activitypub.js +3 -3
  27. package/core/built/admin/assets/activitypub/{avatar-DLPL3Abs.js → avatar-DVJF5bxP.js} +2 -2
  28. package/core/built/admin/assets/activitypub/{avatar-flipboard-C9iDY-QV.js → avatar-flipboard-csQaNeYK.js} +2 -2
  29. package/core/built/admin/assets/activitypub/{bluesky-sharing-BJcPMrDd.js → bluesky-sharing-C7nTDMvw.js} +9 -9
  30. package/core/built/admin/assets/activitypub/{content-formatters-3rTHg3mp.js → content-formatters-CLcjaBwV.js} +4 -4
  31. package/core/built/admin/assets/activitypub/{copy-DIAOQoCZ.js → copy-DcOh8kXw.js} +2 -2
  32. package/core/built/admin/assets/activitypub/{domain-DUR4xSkV.js → domain-fZ-X8i4-.js} +9 -9
  33. package/core/built/admin/assets/activitypub/{edit-profile-BqtfzxCp.js → edit-profile-BqMLQV88.js} +6 -6
  34. package/core/built/admin/assets/activitypub/{explore-BxuJd-J4.js → explore-B4yPoP8E.js} +10 -10
  35. package/core/built/admin/assets/activitypub/{feed-BM1TcksA.js → feed-BRC_mvAe.js} +9 -9
  36. package/core/built/admin/assets/activitypub/{field-Dn7HHx2-.js → field-COacNgOt.js} +3 -3
  37. package/core/built/admin/assets/activitypub/{hash-Bm3WxYN4.js → hash-C7u-irQQ.js} +2 -2
  38. package/core/built/admin/assets/activitypub/{inbox-CCjkuow2.js → inbox-BW-yvUoN.js} +10 -10
  39. package/core/built/admin/assets/activitypub/{inbox-d8oJRTf4.js → inbox-CbTTdDQ3.js} +2 -2
  40. package/core/built/admin/assets/activitypub/{label-1AA_s9Fa.js → label-kPMmYI7l.js} +3 -3
  41. package/core/built/admin/assets/activitypub/layout-BL4_jtuV.js +1 -0
  42. package/core/built/admin/assets/activitypub/{moderation-CnmxEB8w.js → moderation-CuidJzc0.js} +10 -10
  43. package/core/built/admin/assets/activitypub/{note-ZKc_Bnba.js → note-CAMRRSqD.js} +7 -7
  44. package/core/built/admin/assets/activitypub/{notifications-CB2tJZau.js → notifications-BmNlXGgp.js} +9 -9
  45. package/core/built/admin/assets/activitypub/{onboarding-BsJ9E5qf.js → onboarding-Bqg7fh_L.js} +2 -2
  46. package/core/built/admin/assets/activitypub/{onboarding-CSUJJIlh.js → onboarding-Dmun9jr_.js} +2 -2
  47. package/core/built/admin/assets/activitypub/{preferences-0-JZnUkI.js → preferences--oH19wf9.js} +11 -11
  48. package/core/built/admin/assets/activitypub/{profile-C35cFIhM.js → profile-DH_tQx2D.js} +14 -14
  49. package/core/built/admin/assets/activitypub/{reply-DYK5vVqb.js → reply-Cap9PxSH.js} +2 -2
  50. package/core/built/admin/assets/activitypub/{routes-BD4vscyc.js → routes-DdyY9uFF.js} +30 -30
  51. package/core/built/admin/assets/activitypub/{separator-BbQqTm-p.js → separator-CAxxu8VV.js} +3 -3
  52. package/core/built/admin/assets/activitypub/{settings-BsMROdqh.js → settings-B6EEcVkW.js} +6 -6
  53. package/core/built/admin/assets/activitypub/{site-O0RL9YCN.js → site-BYps63oh.js} +2 -2
  54. package/core/built/admin/assets/activitypub/{step-1-BMSx7c_X.js → step-1-CKjR2KAm.js} +6 -6
  55. package/core/built/admin/assets/activitypub/{step-2-DtPQzlTH.js → step-2-B6PKfOUQ.js} +6 -6
  56. package/core/built/admin/assets/activitypub/{step-3-CLlOZTyt.js → step-3-C-qBbcUo.js} +11 -11
  57. package/core/built/admin/assets/activitypub/{tabs-Dxk-b9M-.js → tabs-DLhZtVde.js} +6 -6
  58. package/core/built/admin/assets/activitypub/{topic-filter-5yZH6tId.js → topic-filter-DsUOuu0k.js} +3 -3
  59. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-CV50qe03.js → use-activity-pub-queries-CX9snsLs.js} +3 -3
  60. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-Bmk5h4lW.js → use-navigate-with-base-path-DsJTGDSS.js} +988 -959
  61. package/core/built/admin/assets/activitypub/{use-reply-chain-data-Cbvzgjzd.js → use-reply-chain-data-AJi9zkzm.js} +5 -5
  62. package/core/built/admin/assets/activitypub/{users-BjcoP_HC.js → users-inweyKL5.js} +186 -160
  63. package/core/built/admin/assets/activitypub/{x-DbQZOLiI.js → x-DR8AT3-4.js} +2 -2
  64. package/core/built/admin/assets/{add-integration-modal-BlF6rs_f.js → add-integration-modal-BT_onDJy.js} +1 -1
  65. package/core/built/admin/assets/{add-newsletter-modal-CglVQqah.js → add-newsletter-modal-Cx1kQpiY.js} +1 -1
  66. package/core/built/admin/assets/{add-recommendation-modal-wz7BsuaG.js → add-recommendation-modal-B5nvAhvr.js} +1 -1
  67. package/core/built/admin/assets/{analytics-C4HE1gBg.js → analytics-DjoJWPI7.js} +1 -1
  68. package/core/built/admin/assets/{announcement-bar-modal-Ci4zz8jO.js → announcement-bar-modal-Dwd0EGFP.js} +1 -1
  69. package/core/built/admin/assets/{api-keys-3-0vJC3Y.js → api-keys-BGPzLHzA.js} +1 -1
  70. package/core/built/admin/assets/{api-keys-Ck-Yteiv.js → api-keys-BoeacM-s.js} +1 -1
  71. package/core/built/admin/assets/{audience-CtbA54Sn.js → audience-ceV3XVHq.js} +1 -1
  72. package/core/built/admin/assets/automated-emails-BLesE41L.js +1 -0
  73. package/core/built/admin/assets/{automations-sAcJ1334.js → automations-BN4u3GR8.js} +1 -1
  74. package/core/built/admin/assets/automations-C-ZTUwW3.js +1 -0
  75. package/core/built/admin/assets/{bluesky-sharing-BEtJ_gL_.js → bluesky-sharing-vzh-oPXL.js} +1 -1
  76. package/core/built/admin/assets/{breadcrumb-zUrZrBtW.js → breadcrumb-CaLk2b7D.js} +1 -1
  77. package/core/built/admin/assets/{button-eNMKQWXr.js → button-Dvcv1QSR.js} +1 -1
  78. package/core/built/admin/assets/calendar-Bl9RLcZX.js +1 -0
  79. package/core/built/admin/assets/{chart-helpers-CfBKt0vT.js → chart-helpers-FHywU0pj.js} +1 -1
  80. package/core/built/admin/assets/{checkbox-CgOn_tKW.js → checkbox-BtayFtjg.js} +1 -1
  81. package/core/built/admin/assets/chunk.254.035b85ec456ebbd7336b.js +440 -0
  82. package/core/built/admin/assets/{chunk.524.a697dee88db67fdf6021.js → chunk.524.e6975095fccdf22d90f8.js} +5 -5
  83. package/core/built/admin/assets/{chunk.582.5ed5d124cfca9c259b9c.js → chunk.582.98b485f9b2802f540897.js} +6 -6
  84. package/core/built/admin/assets/{chunk.492.f24d855d7e3f3aad261c.js → chunk.901.4ccc639821020230bcca.js} +9949 -7921
  85. package/core/built/admin/assets/{code-editor-view-K2wI-I5f.js → code-editor-view-C3ZfoF1X.js} +1 -1
  86. package/core/built/admin/assets/{code-editor-view-BYNoAoNV.js → code-editor-view-CAyRWy1s.js} +1 -1
  87. package/core/built/admin/assets/{color-picker-Ddw80xX6.js → color-picker-G4kGR7rp.js} +1 -1
  88. package/core/built/admin/assets/{color-picker-field-D9nUhvA7.js → color-picker-field-DQFVTNnT.js} +1 -1
  89. package/core/built/admin/assets/{combobox-w9j8PPQT.js → combobox-BnYzgEB4.js} +1 -1
  90. package/core/built/admin/assets/{command-BfpHORBv.js → command-C_vyTikI.js} +1 -1
  91. package/core/built/admin/assets/comments-aBrxfnZf.js +1 -0
  92. package/core/built/admin/assets/{config-DOMQDZGZ.js → config-kSHvwdTG.js} +1 -1
  93. package/core/built/admin/assets/{confirmation-context-CBM8G0Vj.js → confirmation-context-DwXX_Y5h.js} +1 -1
  94. package/core/built/admin/assets/{content-api-modal-TXRz3U7S.js → content-api-modal-D2G0nJP6.js} +1 -1
  95. package/core/built/admin/assets/{context-menu-_QMdd3kp.js → context-menu-DOVB6yaz.js} +1 -1
  96. package/core/built/admin/assets/{copy-field-6N01S07E.js → copy-field-DCJwh9qf.js} +1 -1
  97. package/core/built/admin/assets/{create-ghost-browse-value-source-qIBymvvf.js → create-ghost-browse-value-source-5C53jJaX.js} +1 -1
  98. package/core/built/admin/assets/custom-field-type-option-BAuW0F-E.js +1 -0
  99. package/core/built/admin/assets/{custom-integration-modal-DmNquxUV.js → custom-integration-modal-AbJGVyKc.js} +1 -1
  100. package/core/built/admin/assets/{date-range-select-uvz_DtPh.js → date-range-select-BY3K-ukj.js} +1 -1
  101. package/core/built/admin/assets/design-and-theme-modal-BvEy4_pC.js +2 -0
  102. package/core/built/admin/assets/{dirty-confirm-dialog-BZ1iytEd.js → dirty-confirm-dialog-DVPuGz3U.js} +1 -1
  103. package/core/built/admin/assets/{disabled-sources-indicator-DMRrxsiJ.js → disabled-sources-indicator-Cy7Zp4c8.js} +1 -1
  104. package/core/built/admin/assets/{domain-BNYrt0aD.js → domain-Dl61QGuk.js} +1 -1
  105. package/core/built/admin/assets/{dropzone-D7nFdAe5.js → dropzone-Dtz5Rmjt.js} +4 -4
  106. package/core/built/admin/assets/edit-profile-DNJSMDwm.js +1 -0
  107. package/core/built/admin/assets/editor-DbAen9zf.js +8 -0
  108. package/core/built/admin/assets/editor-screen-BFWThIai.js +26 -0
  109. package/core/built/admin/assets/email-sending-status-context-CWDOD1FN.js +1 -0
  110. package/core/built/admin/assets/email-sending-status-provider-DCPkDICt.js +1 -0
  111. package/core/built/admin/assets/emails-vFMk4yYl.js +1 -0
  112. package/core/built/admin/assets/{embed-signup-form-modal-Ds6mPEi5.js → embed-signup-form-modal-DGqsORxS.js} +1 -1
  113. package/core/built/admin/assets/{explore-UY10Egsp.js → explore-nnRr1Fja.js} +1 -1
  114. package/core/built/admin/assets/feature-flag-overrides-context-DQjdJFAk.js +1 -0
  115. package/core/built/admin/assets/{feature-image-placeholder-D1iF_8bc.js → feature-image-placeholder-BrIHmPs8.js} +1 -1
  116. package/core/built/admin/assets/{feed-81C8mJWR.js → feed-BNUcNu-W.js} +1 -1
  117. package/core/built/admin/assets/feedback-DJ4Zb9rZ.js +1 -0
  118. package/core/built/admin/assets/{field-Dihp5p18.js → field-C93_Mg6j.js} +1 -1
  119. package/core/built/admin/assets/filter-sources-CGJb4C5r.js +1 -0
  120. package/core/built/admin/assets/{filters-DirqNXjH.js → filters--kKP-EC8.js} +1 -1
  121. package/core/built/admin/assets/filters-DxA_a4FP.js +1 -0
  122. package/core/built/admin/assets/{first-promoter-modal-CT6MxYZb.js → first-promoter-modal-BYDtrz2S.js} +1 -1
  123. package/core/built/admin/assets/focus-koenig-editor-on-bottom-click-BbSnGOtK.js +1 -0
  124. package/core/built/admin/assets/{ghost-8eabfc1ece81b03cf7b90780d31487e8.css → ghost-8fd5bb799356bbefb254210ba7e1236d.css} +1 -1
  125. package/core/built/admin/assets/{ghost-c2848508292937415f20f5a830eea59e.js → ghost-b4e8a403baba61905cd7560e30b9732d.js} +43 -27
  126. package/core/built/admin/assets/{ghost-dark-a7bebe2c561f55369dcf29f4c820f7ff.css → ghost-dark-cac0cb2d5bb78e9bd14d8d1c2768ed45.css} +1 -1
  127. package/core/built/admin/assets/{gift-link-modal-CQVAye17.js → gift-link-modal-B7u5bD4e.js} +1 -1
  128. package/core/built/admin/assets/{gift-links-DLDS7ljO.js → gift-links-CfLSnst2.js} +1 -1
  129. package/core/built/admin/assets/growth-Cd1uO9d2.js +1 -0
  130. package/core/built/admin/assets/growth-aG32xfdE.js +3 -0
  131. package/core/built/admin/assets/{history-modal-CFd1Hvx6.js → history-modal-Df8lW8h-.js} +1 -1
  132. package/core/built/admin/assets/hooks-Cy_-oZ4o.js +2 -0
  133. package/core/built/admin/assets/{html-field-CJBCLTvv.js → html-field-DA4eU7eq.js} +1 -1
  134. package/core/built/admin/assets/{images-scfkWZiK.js → images-KXmyO3Ss.js} +1 -1
  135. package/core/built/admin/assets/{inbox-Mcp4DXZN.js → inbox-gJHG7_xd.js} +6 -6
  136. package/core/built/admin/assets/index-BHHXtR5C.css +2 -0
  137. package/core/built/admin/assets/index-CgMCwsxN.js +4 -0
  138. package/core/built/admin/assets/index.es-CY6-yuw9.js +1 -0
  139. package/core/built/admin/assets/{input-group-CMhe2DvY.js → input-group-BE8rM2y1.js} +1 -1
  140. package/core/built/admin/assets/{integrations-Bjbu86Z0.js → integrations-B4kuRBFY.js} +1 -1
  141. package/core/built/admin/assets/{invite-user-modal-DfheMXs0.js → invite-user-modal-B4CuNcx4.js} +1 -1
  142. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +2 -2
  143. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +2 -2
  144. package/core/built/admin/assets/{koenig-lexical-DBSqAuiI.js → koenig-lexical-CGXb0F8o.js} +2 -2
  145. package/core/built/admin/assets/koenig-loader-BeM5rFPX.js +2 -0
  146. package/core/built/admin/assets/{kpi-card-DBRMfGOW.js → kpi-card-B21aToe8.js} +1 -1
  147. package/core/built/admin/assets/{kpi-card-DFafD1qq.js → kpi-card-pz6Wv4Ae.js} +1 -1
  148. package/core/built/admin/assets/{kpi-helpers-Cv8i8hns.js → kpi-helpers-Dq5jelmP.js} +1 -1
  149. package/core/built/admin/assets/{kpi-tabs-BJItvLYm.js → kpi-tabs-DEZHpKUt.js} +1 -1
  150. package/core/built/admin/assets/{label-BnSCvffu.js → label-OLCzze8N.js} +1 -1
  151. package/core/built/admin/assets/label-picker-BigOvMPV.js +1 -0
  152. package/core/built/admin/assets/{labels-CH3Kn8RV.js → labels-Dr0SgUQD.js} +1 -1
  153. package/core/built/admin/assets/layout-DUTL8SH5.js +1 -0
  154. package/core/built/admin/assets/lib-7QPTI0FS.js +1 -0
  155. package/core/built/admin/assets/link-helpers-BRAg9SCj.js +1 -0
  156. package/core/built/admin/assets/links-BDHPTlyj.js +1 -0
  157. package/core/built/admin/assets/{member-custom-fields-DDQV6QeG.js → member-custom-fields-TRTHCAz9.js} +1 -1
  158. package/core/built/admin/assets/member-detail-b6dRwc3C.js +1 -0
  159. package/core/built/admin/assets/members-Czt5lJSc.js +4 -0
  160. package/core/built/admin/assets/{moderation-AgCIEso4.js → moderation-ZWefD9uH.js} +1 -1
  161. package/core/built/admin/assets/{multi-select-combobox-BcITkUVr.js → multi-select-combobox-CT13MTo-.js} +1 -1
  162. package/core/built/admin/assets/navigation-modal-C8FEhBK5.js +1 -0
  163. package/core/built/admin/assets/newsletter-5VYW4RyJ.js +1 -0
  164. package/core/built/admin/assets/newsletter-detail-modal-ChjC0ZqH.js +1 -0
  165. package/core/built/admin/assets/{newsletter-emails-DKzwafuh.js → newsletter-emails-DZH6I-PY.js} +1 -1
  166. package/core/built/admin/assets/{newsletters-BRBb_uQC.js → newsletters-DOC9KBcX.js} +1 -1
  167. package/core/built/admin/assets/newsletters-Rq1M3GD-.js +1 -0
  168. package/core/built/admin/assets/{note-DyGHtS9a.js → note-DIV-Pojr.js} +1 -1
  169. package/core/built/admin/assets/{notifications-ZgWniKna.js → notifications-s20-H49V.js} +1 -1
  170. package/core/built/admin/assets/{offers-9J1CSiLy.js → offers--e2nGvkW.js} +1 -1
  171. package/core/built/admin/assets/{offers-container-modal-B-E8TIWr.js → offers-container-modal-BmG5M5Tv.js} +1 -1
  172. package/core/built/admin/assets/onboarding-CUuy9unR.js +1 -0
  173. package/core/built/admin/assets/{onboarding-CzIsre-d.js → onboarding-pXyct0Jm.js} +1 -1
  174. package/core/built/admin/assets/{onboarding-route-CaavZulg.js → onboarding-route-CK4pcXoR.js} +1 -1
  175. package/core/built/admin/assets/overview-CaPsciMs.js +1 -0
  176. package/core/built/admin/assets/overview-a8GfcsKX.js +1 -0
  177. package/core/built/admin/assets/{page-header-De0VzKBi.js → page-header-DM_KyoFI.js} +1 -1
  178. package/core/built/admin/assets/{pagemenu-B5KKCpbA.js → pagemenu-vgZVUVKR.js} +1 -1
  179. package/core/built/admin/assets/pages-CjDY2CBI.js +1 -0
  180. package/core/built/admin/assets/pages-route-BzKZYdV5.js +1 -0
  181. package/core/built/admin/assets/{pintura-modal-BZ2J9w3n.js → pintura-modal-DExCF1ii.js} +1 -1
  182. package/core/built/admin/assets/{portal-frame-B6Hr9wHc.js → portal-frame-Cqanidq4.js} +1 -1
  183. package/core/built/admin/assets/portal-modal-oinBgA27.js +1 -0
  184. package/core/built/admin/assets/post-analytics-C5oJ1dWV.js +1 -0
  185. package/core/built/admin/assets/post-analytics-header-OATo3X_r.js +1 -0
  186. package/core/built/admin/assets/post-analytics-provider-tOpIVGT5.js +1 -0
  187. package/core/built/admin/assets/post-helpers-C88JBa4D.js +1 -0
  188. package/core/built/admin/assets/post-share-modal-DvhC1XgV.js +1 -0
  189. package/core/built/admin/assets/post-utils-DKF4mfuo.js +1 -0
  190. package/core/built/admin/assets/posts-list-screen-o4ZjbgS3.js +2 -0
  191. package/core/built/admin/assets/posts-pCPrhQeR.js +1 -0
  192. package/core/built/admin/assets/posts-route-9F--xdIb.js +1 -0
  193. package/core/built/admin/assets/{preferences-C5YJLp74.js → preferences--vNubsqD.js} +2 -2
  194. package/core/built/admin/assets/{preview-modal-BfeFqi5y.js → preview-modal-YYbzlYdO.js} +1 -1
  195. package/core/built/admin/assets/profile-BllKV3dR.js +1 -0
  196. package/core/built/admin/assets/radio-group-DL8GI8rt.js +1 -0
  197. package/core/built/admin/assets/{referrers-CnhCMWCs.js → referrers-D8oHPmDB.js} +1 -1
  198. package/core/built/admin/assets/router-provider-BSvTXgZY.js +1 -0
  199. package/core/built/admin/assets/schemas-BoYdB6YU.js +64 -0
  200. package/core/built/admin/assets/{select-cb0t_TAJ.js → select-v5YbA3Mw.js} +1 -1
  201. package/core/built/admin/assets/settings-PEsZupw1.js +4 -0
  202. package/core/built/admin/assets/{settings-breadcrumbs-DLzbRRSt.js → settings-breadcrumbs-NwPnCTC7.js} +1 -1
  203. package/core/built/admin/assets/{settings-DZqxR0Da.js → settings-u_s1CRkr.js} +1 -1
  204. package/core/built/admin/assets/{share-modal-D2-Bz1_I.js → share-modal-SskCmVGL.js} +1 -1
  205. package/core/built/admin/assets/{slack-modal-DYDTUria.js → slack-modal-XxSJiYgo.js} +1 -1
  206. package/core/built/admin/assets/{sort-button-VVeQjjbu.js → sort-button-Cx1KtINE.js} +1 -1
  207. package/core/built/admin/assets/{source-icon-Ck5_5Kgq.js → source-icon-Bdp9Im_5.js} +1 -1
  208. package/core/built/admin/assets/stats-D9PDLS3x.js +1 -0
  209. package/core/built/admin/assets/stats-view-W2g6Jsd-.js +1 -0
  210. package/core/built/admin/assets/{step-1-CsaBjeN4.js → step-1-BQYzvBzr.js} +1 -1
  211. package/core/built/admin/assets/{step-2-D2vnxOOM.js → step-2-xES32xJV.js} +1 -1
  212. package/core/built/admin/assets/{step-3-wBoLdGEb.js → step-3-DA_XK5kW.js} +2 -2
  213. package/core/built/admin/assets/{stripe-button-CDgLz0S0.js → stripe-button-CVsLHelt.js} +1 -1
  214. package/core/built/admin/assets/{stripe-connect-modal-CSjMJetP.js → stripe-connect-modal-ClmLOMGS.js} +1 -1
  215. package/core/built/admin/assets/style-Wd9nQubI.css +1 -0
  216. package/core/built/admin/assets/{table-Do6iQ83Q.js → table-BenqVupI.js} +1 -1
  217. package/core/built/admin/assets/{tabs-D2yWLMp_.js → tabs-eBBlxPp8.js} +1 -1
  218. package/core/built/admin/assets/tag-detail-Ce8Se2qd.js +2 -0
  219. package/core/built/admin/assets/tag-picker-C3UR-uJ-.js +1 -0
  220. package/core/built/admin/assets/tags-B-kJxCs7.js +1 -0
  221. package/core/built/admin/assets/{tags-zTFiKRpb.js → tags-ChfdwJyS.js} +1 -1
  222. package/core/built/admin/assets/{testimonials-modal-CtnZBJKl.js → testimonials-modal-9Q6fT06V.js} +1 -1
  223. package/core/built/admin/assets/tier-detail-modal-CDyYdqK7.js +1 -0
  224. package/core/built/admin/assets/tiers-BCpIOGQr.js +1 -0
  225. package/core/built/admin/assets/{toggle-group-ByUJsxwD.js → toggle-group-CviIkyok.js} +1 -1
  226. package/core/built/admin/assets/{topic-filter-BgC3g5_v.js → topic-filter-I2jCBGxm.js} +1 -1
  227. package/core/built/admin/assets/{transistor-modal-Drr0SNfo.js → transistor-modal-p53u3W8y.js} +1 -1
  228. package/core/built/admin/assets/{trend-badge-6ye93YAW.js → trend-badge-CideVzQd.js} +1 -1
  229. package/core/built/admin/assets/{unsplash-modal-p6AoQoWp.js → unsplash-modal-DY8UcKcE.js} +1 -1
  230. package/core/built/admin/assets/{use-analytics-data-CWCMAESo.js → use-analytics-data-DAIGcr2f.js} +1 -1
  231. package/core/built/admin/assets/{use-check-theme-limit-error-Cw3WQHy8.js → use-check-theme-limit-error-DRUjK4YM.js} +1 -1
  232. package/core/built/admin/assets/use-filter-params-JkZhIS8L.js +1 -0
  233. package/core/built/admin/assets/{use-filterable-api-CzXcpUh8.js → use-filterable-api-BVEVMrhZ.js} +1 -1
  234. package/core/built/admin/assets/{use-growth-stats-DCVG3s9w.js → use-growth-stats-937CUj6j.js} +1 -1
  235. package/core/built/admin/assets/use-host-limits-D0Dv3tKA.js +1 -0
  236. package/core/built/admin/assets/{use-limiter-D2WRRx0H.js → use-limiter-CohWfzvK.js} +1 -1
  237. package/core/built/admin/assets/{use-navigate-with-base-path-BHPA228f.js → use-navigate-with-base-path-BkxcPj5l.js} +1 -1
  238. package/core/built/admin/assets/use-pintura-config-xgytjU35.js +1 -0
  239. package/core/built/admin/assets/{use-pintura-editor-BfN1UwzH.js → use-pintura-editor-CaV1mbfO.js} +1 -1
  240. package/core/built/admin/assets/{use-post-referrers-Czg47m8s.js → use-post-referrers-BCQI7fDg.js} +1 -1
  241. package/core/built/admin/assets/{use-reply-chain-data-ns0caK8k.js → use-reply-chain-data-CYRhoaxY.js} +1 -1
  242. package/core/built/admin/assets/{use-router-history-entry-uldn7zJO.js → use-router-history-entry-NOKcd--l.js} +1 -1
  243. package/core/built/admin/assets/{use-setting-group-DpcuCUDg.js → use-setting-group-bLKMwoBT.js} +1 -1
  244. package/core/built/admin/assets/{use-settings-navigation-CZjGrhsp.js → use-settings-navigation-B-qrriKD.js} +1 -1
  245. package/core/built/admin/assets/{use-simple-pagination-DzJWobPR.js → use-simple-pagination-CNPd6Rf6.js} +1 -1
  246. package/core/built/admin/assets/use-staff-users-pRIMIyiW.js +1 -0
  247. package/core/built/admin/assets/{use-tinybird-query-CSnGXzZh.js → use-tinybird-query-CmdbCS9X.js} +1 -1
  248. package/core/built/admin/assets/{use-tinybird-token-DTOKRR7e.js → use-tinybird-token-KLne235s.js} +1 -1
  249. package/core/built/admin/assets/{use-unsaved-changes-guard-CBzs-KPi.js → use-unsaved-changes-guard-7-ug0JdH.js} +1 -1
  250. package/core/built/admin/assets/use-upgrade-route-Dm_qqKr0.js +1 -0
  251. package/core/built/admin/assets/{use-url-input-DFr8lCbS.js → use-url-input-fvp-TpPx.js} +1 -1
  252. package/core/built/admin/assets/user-detail-modal-D3KlGYv-.js +1 -0
  253. package/core/built/admin/assets/{users-Cg-OeGzC.js → users-DVNWrqg9.js} +1 -1
  254. package/core/built/admin/assets/{vendor-5753232c7d6ffb3c44e1eb0a02133953.js → vendor-2dd263291938d407802568d3357ec69e.js} +19 -17
  255. package/core/built/admin/assets/{virtual-list-BW-padMe.js → virtual-list-CZ3RK2Yf.js} +1 -1
  256. package/core/built/admin/assets/{web-C1v33ehA.js → web-ClEJsCAj.js} +2 -2
  257. package/core/built/admin/assets/web-X9rdnrtq.js +1 -0
  258. package/core/built/admin/assets/{zapier-modal-BBFfI6Ml.js → zapier-modal-DfVhV17j.js} +1 -1
  259. package/core/built/admin/index.html +19 -18
  260. package/core/server/adapters/jobs/InMemoryJobsBackend.js +74 -29
  261. package/core/server/api/endpoints/authentication.js +1 -2
  262. package/core/server/api/endpoints/authors-public.js +15 -5
  263. package/core/server/api/endpoints/emails.js +13 -0
  264. package/core/server/api/endpoints/index.js +8 -0
  265. package/core/server/api/endpoints/member-metafields-members.js +23 -0
  266. package/core/server/api/endpoints/member-metafields.js +33 -20
  267. package/core/server/api/endpoints/members-account.js +42 -0
  268. package/core/server/api/endpoints/newsletters-public.js +10 -1
  269. package/core/server/api/endpoints/pages-public.js +15 -5
  270. package/core/server/api/endpoints/posts-public.js +15 -5
  271. package/core/server/api/endpoints/tags-public.js +22 -3
  272. package/core/server/api/endpoints/tiers-checkout-config.js +2 -2
  273. package/core/server/api/endpoints/tiers-public.js +38 -1
  274. package/core/server/api/endpoints/users.js +2 -2
  275. package/core/server/api/endpoints/utils/api-filter-utils.js +170 -3
  276. package/core/server/api/endpoints/utils/public-cache-keys.js +3 -0
  277. package/core/server/api/endpoints/utils/serializers/input/authors.js +2 -0
  278. package/core/server/api/endpoints/utils/serializers/input/members.js +16 -2
  279. package/core/server/api/endpoints/utils/serializers/input/pages.js +2 -0
  280. package/core/server/api/endpoints/utils/serializers/input/posts.js +2 -0
  281. package/core/server/api/endpoints/utils/serializers/input/tags.js +2 -0
  282. package/core/server/api/endpoints/utils/serializers/output/emails.js +7 -0
  283. package/core/server/api/endpoints/utils/serializers/output/index.js +4 -0
  284. package/core/server/api/endpoints/utils/serializers/output/member-metafields.js +3 -3
  285. package/core/server/api/endpoints/utils/serializers/output/members-account.js +19 -0
  286. package/core/server/api/endpoints/utils/serializers/output/members-csv-transform.js +1 -1
  287. package/core/server/api/endpoints/utils/serializers/output/tiers.js +10 -2
  288. package/core/server/api/endpoints/utils/serializers/output/users.js +0 -10
  289. package/core/server/data/exporter/table-lists.js +3 -3
  290. package/core/server/data/migrations/versions/6.53/2026-07-15-20-22-53-add-members-custom-field-values.js +1 -1
  291. package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +10 -1
  292. package/core/server/data/migrations/versions/6.63/2026-09-03-10-07-03-rename-custom-field-tables-to-metafields.js +169 -0
  293. package/core/server/data/migrations/versions/6.63/2026-09-03-11-03-31-remove-member-custom-field-read-permissions.js +30 -0
  294. package/core/server/data/schema/fixtures/fixtures.json +0 -11
  295. package/core/server/data/schema/lib/default-index-name.js +8 -11
  296. package/core/server/data/schema/schema.js +18 -18
  297. package/core/server/data/seeders/README.md +48 -0
  298. package/core/server/data/tinybird/pipes/mv_hits.pipe +86 -50
  299. package/core/server/lib/db-types/count.js +16 -0
  300. package/core/server/lib/validate-password.js +81 -120
  301. package/core/server/models/action.js +4 -4
  302. package/core/server/models/base/plugins/filtered-collection.js +16 -0
  303. package/core/server/models/member.js +6 -6
  304. package/core/server/models/user.js +1 -1
  305. package/core/server/services/automations/automations-api.js +1 -1
  306. package/core/server/services/automations/database-automations-repository.js +11 -4
  307. package/core/server/services/email-service/CONTEXT.md +37 -0
  308. package/core/server/services/email-service/README.md +42 -0
  309. package/core/server/services/email-service/batch-sending-service.js +2 -0
  310. package/core/server/services/email-service/email-controller.js +18 -2
  311. package/core/server/services/email-service/email-service-wrapper.js +3 -0
  312. package/core/server/services/email-service/sending-status-schema.js +21 -0
  313. package/core/server/services/email-service/sending-status-serializers.js +33 -0
  314. package/core/server/services/email-service/sending-status-service.js +47 -0
  315. package/core/server/services/email-service/sending-status.js +111 -0
  316. package/core/server/services/email-suppression-list/email-suppression-list.js +0 -33
  317. package/core/server/services/email-suppression-list/mailgun-email-suppression-list.js +6 -7
  318. package/core/server/services/gifts/README.md +2 -2
  319. package/core/server/services/gifts/constants.js +1 -1
  320. package/core/server/services/gifts/gift-service.js +19 -57
  321. package/core/server/services/gifts/index.js +0 -2
  322. package/core/server/services/jobs-service/index.js +8 -0
  323. package/core/server/services/jobs-service/jobs-service.js +52 -3
  324. package/core/server/services/jobs-service/register-job-handlers.js +13 -2
  325. package/core/server/services/members/account-service.js +98 -0
  326. package/core/server/services/members/api.js +4 -4
  327. package/core/server/services/members/import-export/export/exporter.js +20 -20
  328. package/core/server/services/members/import-export/import/completion-email.js +6 -6
  329. package/core/server/services/members/import-export/import/importer.js +13 -13
  330. package/core/server/services/members/import-export/index.js +7 -7
  331. package/core/server/services/members/members-api/members-api.js +26 -6
  332. package/core/server/services/members/members-api/repositories/member-repository.js +4 -4
  333. package/core/server/services/members/members-api/services/member-bread-service.js +45 -39
  334. package/core/server/services/members/members-ssr.js +18 -0
  335. package/core/server/services/members/middleware.js +95 -81
  336. package/core/server/services/members/service.js +70 -42
  337. package/core/server/services/members/utils.js +9 -0
  338. package/core/server/services/members-metafields/access.js +28 -0
  339. package/core/server/services/{members-custom-fields → members-metafields}/actions.js +5 -5
  340. package/core/server/services/members-metafields/bindings-service.js +147 -0
  341. package/core/server/services/{members-custom-fields → members-metafields}/codec.js +4 -4
  342. package/core/server/services/{members-custom-fields → members-metafields}/config.js +1 -1
  343. package/core/server/services/{members-custom-fields → members-metafields}/definitions-service.js +17 -23
  344. package/core/server/services/{members-custom-fields → members-metafields}/filter.js +9 -9
  345. package/core/server/services/{members-custom-fields → members-metafields}/index.js +16 -7
  346. package/core/server/services/{members-custom-fields → members-metafields}/key.js +1 -1
  347. package/core/server/services/{members-custom-fields → members-metafields}/models.js +4 -4
  348. package/core/server/services/members-metafields/namespaces.js +34 -0
  349. package/core/server/services/{members-custom-fields → members-metafields}/queries.js +1 -1
  350. package/core/server/services/{members-custom-fields → members-metafields}/schema.js +16 -12
  351. package/core/server/services/{members-custom-fields → members-metafields}/serializers.js +6 -6
  352. package/core/server/services/{members-custom-fields → members-metafields}/values-service.js +32 -22
  353. package/core/server/services/mentions/mention-sending-service.js +26 -9
  354. package/core/server/services/mentions/send-webmentions-job.js +18 -0
  355. package/core/server/services/mentions/service.js +1 -31
  356. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +2 -2
  357. package/core/server/services/stripe/stripe-service.js +4 -4
  358. package/core/server/services/tier-checkout-config/codec.js +2 -2
  359. package/core/server/services/tier-checkout-config/index.js +2 -1
  360. package/core/server/services/tier-checkout-config/models.js +2 -2
  361. package/core/server/services/tier-checkout-config/queries.js +8 -8
  362. package/core/server/services/tier-checkout-config/serializers.js +57 -0
  363. package/core/server/services/tier-checkout-config/service.js +1 -1
  364. package/core/server/services/tiers/service.js +3 -3
  365. package/core/server/services/url/router-filter.js +8 -10
  366. package/core/server/services/users.js +1 -19
  367. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  368. package/core/server/web/members/account/index.js +2 -0
  369. package/core/server/web/members/account/routes.js +46 -0
  370. package/core/server/web/members/app.js +4 -18
  371. package/core/shared/config/defaults.json +1 -1
  372. package/core/shared/labs.js +4 -2
  373. package/package.json +22 -22
  374. package/pnpm-lock.yaml +513 -571
  375. package/pnpm-workspace.yaml +12 -12
  376. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  377. package/core/built/admin/assets/activitypub/layout-2-0bw6HN.js +0 -1
  378. package/core/built/admin/assets/automated-emails-D5VVMND9.js +0 -1
  379. package/core/built/admin/assets/automations-CzKt5fb8.js +0 -1
  380. package/core/built/admin/assets/chunk.598.7e056fabb47ce7dbd73f.js +0 -366
  381. package/core/built/admin/assets/comments-BLmCmyCs.js +0 -1
  382. package/core/built/admin/assets/custom-field-type-option-BddFlJFa.js +0 -1
  383. package/core/built/admin/assets/design-and-theme-modal-BSmo12Op.js +0 -2
  384. package/core/built/admin/assets/edit-profile-C5Asm4LA.js +0 -1
  385. package/core/built/admin/assets/editor-REokrzci.js +0 -8
  386. package/core/built/admin/assets/filter-sources-BO9xUPj7.js +0 -1
  387. package/core/built/admin/assets/filters-CRUWecfY.js +0 -1
  388. package/core/built/admin/assets/growth-BWIu-6xj.js +0 -3
  389. package/core/built/admin/assets/growth-DU29oxeA.js +0 -1
  390. package/core/built/admin/assets/hooks-CuEZV2-q.js +0 -2
  391. package/core/built/admin/assets/index-6MkPdCtr.js +0 -4
  392. package/core/built/admin/assets/index-Bn7QhcZu.css +0 -2
  393. package/core/built/admin/assets/index.es-CNnuNH0d.js +0 -1
  394. package/core/built/admin/assets/koenig-loader-aFQNlJlD.js +0 -2
  395. package/core/built/admin/assets/label-picker-KVoo91sh.js +0 -1
  396. package/core/built/admin/assets/layout-Bjv2Ll-5.js +0 -1
  397. package/core/built/admin/assets/link-helpers-MVDdnFCY.js +0 -1
  398. package/core/built/admin/assets/member-detail-CdJF6Agw.js +0 -1
  399. package/core/built/admin/assets/members-DjQbq9Z2.js +0 -4
  400. package/core/built/admin/assets/navigation-modal-BkT7b1M2.js +0 -1
  401. package/core/built/admin/assets/newsletter-BMMAuvgq.js +0 -1
  402. package/core/built/admin/assets/newsletter-detail-modal-0SCJ7Zpm.js +0 -1
  403. package/core/built/admin/assets/newsletters-DhNigJUh.js +0 -1
  404. package/core/built/admin/assets/onboarding-qg8Akovm.js +0 -1
  405. package/core/built/admin/assets/overview-BW8GLeYi.js +0 -1
  406. package/core/built/admin/assets/overview-BkXZNzaZ.js +0 -1
  407. package/core/built/admin/assets/pages-jgaFm69d.js +0 -1
  408. package/core/built/admin/assets/pages-route-CIUAQxtY.js +0 -1
  409. package/core/built/admin/assets/portal-modal-D4ceei3t.js +0 -1
  410. package/core/built/admin/assets/post-analytics-header-BYCsT0GP.js +0 -1
  411. package/core/built/admin/assets/post-analytics-ot7f6pih.js +0 -1
  412. package/core/built/admin/assets/post-analytics-provider-Bo1gmYaP.js +0 -1
  413. package/core/built/admin/assets/post-helpers-C5OQMBoi.js +0 -1
  414. package/core/built/admin/assets/post-share-modal-C-PaYZ_e.js +0 -1
  415. package/core/built/admin/assets/posts-DuGazgTb.js +0 -1
  416. package/core/built/admin/assets/posts-list-screen-CBGGgjjj.js +0 -2
  417. package/core/built/admin/assets/posts-route-D6xmS7Vf.js +0 -1
  418. package/core/built/admin/assets/profile-B8mx2yAu.js +0 -1
  419. package/core/built/admin/assets/radio-group-DczlLKOn.js +0 -1
  420. package/core/built/admin/assets/router-provider-B0gZyiGJ.js +0 -1
  421. package/core/built/admin/assets/schemas-DppqmpeJ.js +0 -64
  422. package/core/built/admin/assets/settings-DvfTNdHQ.js +0 -4
  423. package/core/built/admin/assets/stats-Rjmk5owg.js +0 -1
  424. package/core/built/admin/assets/stats-view-BGGmkmgv.js +0 -1
  425. package/core/built/admin/assets/tag-detail-BhXlOsCa.js +0 -2
  426. package/core/built/admin/assets/tags-ldukRApe.js +0 -1
  427. package/core/built/admin/assets/tier-detail-modal-BOs8e_G8.js +0 -1
  428. package/core/built/admin/assets/tiers-Nd_z4BZY.js +0 -1
  429. package/core/built/admin/assets/use-filter-params-BIni9taJ.js +0 -1
  430. package/core/built/admin/assets/use-host-limits-DZtR08oa.js +0 -1
  431. package/core/built/admin/assets/use-pintura-config-h-J0Lvta.js +0 -1
  432. package/core/built/admin/assets/use-staff-users-Csipb-O0.js +0 -1
  433. package/core/built/admin/assets/use-upgrade-route-DhRSo7at.js +0 -1
  434. package/core/built/admin/assets/user-detail-modal-B_iUClI6.js +0 -1
  435. package/core/built/admin/assets/web-C4BKzn2T.js +0 -1
  436. package/core/server/services/email-suppression-list/in-memory-email-suppression-list.js +0 -39
  437. package/core/server/services/members-custom-fields/bindings-service.js +0 -120
  438. /package/core/built/admin/assets/{chunk.492.f24d855d7e3f3aad261c.js.LICENSE.txt → chunk.901.4ccc639821020230bcca.js.LICENSE.txt} +0 -0
  439. /package/core/server/services/{members-custom-fields → members-metafields}/storage.js +0 -0
@@ -1,4 +1,5 @@
1
1
  const errors = require('@tryghost/errors');
2
+ const { ADMIN } = require('../../../members-metafields');
2
3
  const logging = require('@tryghost/logging');
3
4
  const tpl = require('@tryghost/tpl');
4
5
  const moment = require('moment');
@@ -7,9 +8,9 @@ const messages = {
7
8
  stripeNotConnected: 'Missing Stripe connection.',
8
9
  memberAlreadyExists: 'Member already exists.',
9
10
  memberNotFound: 'Member not found.',
10
- customFieldsOnAdd:
11
+ metafieldsOnAdd:
11
12
  'Custom field values cannot be set while creating a member. Create the member, then set values with an edit.',
12
- customFieldsWithoutWriter:
13
+ metafieldsWithoutWriter:
13
14
  'Custom field values cannot be set by a request with no authenticated user or integration.',
14
15
  };
15
16
 
@@ -47,8 +48,8 @@ module.exports = class MemberBREADService {
47
48
  * @param {import('../../../settings-helpers/settings-helpers')} deps.settingsHelpers
48
49
  * @param {import('./next-payment-calculator')} deps.nextPaymentCalculator
49
50
  * @param {IGiftsModule} deps.giftService
50
- * @param {import('../../../members-custom-fields/values-service').CustomFieldValuesService} deps.customFieldValues Required: boot builds it before the members service
51
- * @param {import('../../../members-custom-fields/definitions-service').CustomFieldDefinitionsService} deps.customFieldDefinitions Required: boot builds it before the members service
51
+ * @param {import('../../../members-metafields/values-service').MetafieldValuesService} deps.metafieldValues Required: boot builds it before the members service
52
+ * @param {import('../../../members-metafields/definitions-service').MetafieldDefinitionsService} deps.metafieldDefinitions Required: boot builds it before the members service
52
53
  */
53
54
  constructor({
54
55
  memberRepository,
@@ -61,8 +62,8 @@ module.exports = class MemberBREADService {
61
62
  nextPaymentCalculator,
62
63
  commentsService,
63
64
  giftService,
64
- customFieldValues,
65
- customFieldDefinitions,
65
+ metafieldValues,
66
+ metafieldDefinitions,
66
67
  }) {
67
68
  this.offersAPI = offersAPI;
68
69
  /** @private */
@@ -84,9 +85,9 @@ module.exports = class MemberBREADService {
84
85
  /** @private */
85
86
  this.giftService = giftService;
86
87
  /** @private */
87
- this.customFieldValues = customFieldValues;
88
+ this.metafieldValues = metafieldValues;
88
89
  /** @private */
89
- this.customFieldDefinitions = customFieldDefinitions;
90
+ this.metafieldDefinitions = metafieldDefinitions;
90
91
  }
91
92
 
92
93
  /**
@@ -102,12 +103,12 @@ module.exports = class MemberBREADService {
102
103
  * @param {string[]} memberIds
103
104
  * @returns {Promise<Map<string, Record<string, unknown>> | null>}
104
105
  */
105
- async fetchCustomFieldValues(memberIds) {
106
- if (!(await this.customFieldDefinitions.hasAnyActive())) {
106
+ async fetchMetafieldValues(memberIds, audience) {
107
+ if (!(await this.metafieldDefinitions.hasAnyActive())) {
107
108
  return null;
108
109
  }
109
110
 
110
- return this.customFieldValues.getValuesForMembers(memberIds);
111
+ return this.metafieldValues.getValuesForMembers(memberIds, audience);
111
112
  }
112
113
 
113
114
  /**
@@ -384,13 +385,15 @@ module.exports = class MemberBREADService {
384
385
  /**
385
386
  * @param {object} data
386
387
  * @param {object} [options]
387
- * @param {boolean} [options.withCustomFields] Pass false to leave custom field values
388
- * off the result. Ghost calls this method to identify a signed-in reader on every page
389
- * view of a themed site, and that caller renders the member through a fixed list of
390
- * fields that has never included custom ones. Fetching them there costs two database
391
- * queries per page view whose results are then thrown away.
388
+ * @param {import('../../../members-metafields').Audience | null} [options.metafieldsFor]
389
+ * Who the extra fields a publisher defined are being read for, or null to leave them
390
+ * off entirely. Null is not the same as "nobody may see them": it means this caller
391
+ * never shows them, so fetching them is two database queries whose results are thrown
392
+ * away. Ghost identifies a signed-in reader on every page view of a themed site
393
+ * through this method, and that caller renders a member through a fixed list of
394
+ * fields which has never included these.
392
395
  */
393
- async read(data, { withCustomFields = true, ...options } = {}) {
396
+ async read(data, { metafieldsFor = ADMIN, ...options } = {}) {
394
397
  const defaultWithRelated = [
395
398
  'labels',
396
399
  'stripeSubscriptions',
@@ -451,10 +454,10 @@ module.exports = class MemberBREADService {
451
454
  const unsubscribeUrl = this.settingsHelpers.createUnsubscribeUrl(member.uuid);
452
455
  member.unsubscribe_url = unsubscribeUrl;
453
456
 
454
- if (withCustomFields) {
455
- const customFields = await this.fetchCustomFieldValues([member.id]);
456
- if (customFields) {
457
- member.metafields = customFields.get(member.id) ?? {};
457
+ if (metafieldsFor) {
458
+ const metafields = await this.fetchMetafieldValues([member.id], metafieldsFor);
459
+ if (metafields) {
460
+ member.metafields = metafields.get(member.id) ?? {};
458
461
  }
459
462
  }
460
463
 
@@ -462,9 +465,9 @@ module.exports = class MemberBREADService {
462
465
  }
463
466
 
464
467
  async add(data, options) {
465
- if (this.customFieldValues.namesValues(this.customFieldValues.unwrapWire(data.metafields))) {
468
+ if (this.metafieldValues.namesValues(this.metafieldValues.unwrapWire(data.metafields))) {
466
469
  throw new errors.ValidationError({
467
- message: tpl(messages.customFieldsOnAdd),
470
+ message: tpl(messages.metafieldsOnAdd),
468
471
  property: 'metafields',
469
472
  });
470
473
  }
@@ -565,16 +568,16 @@ module.exports = class MemberBREADService {
565
568
  async edit(data, options) {
566
569
  delete data.last_seen_at;
567
570
 
568
- const customFields = this.customFieldValues.unwrapWire(data.metafields);
569
- const writeCustomFields = customFields !== undefined;
571
+ const metafields = this.metafieldValues.unwrapWire(data.metafields);
572
+ const writeMetafields = metafields !== undefined;
570
573
  delete data.metafields;
571
574
 
572
575
  // Plan (which validates) before the member is touched, so a bad value 422s
573
576
  // here rather than after the member edit has been applied — and keep the
574
577
  // plan to apply once below, so the values aren't resolved and validated
575
578
  // twice.
576
- const plannedCustomFields = writeCustomFields
577
- ? await this.customFieldValues.planWrite(customFields)
579
+ const plannedMetafields = writeMetafields
580
+ ? await this.metafieldValues.planWrite(metafields, ADMIN)
578
581
  : null;
579
582
 
580
583
  let model;
@@ -627,7 +630,7 @@ module.exports = class MemberBREADService {
627
630
  }
628
631
  }
629
632
 
630
- if (plannedCustomFields) {
633
+ if (plannedMetafields) {
631
634
  // Every value reaching here was typed into the Admin API, so the writer is
632
635
  // whoever made the request — the same pair the action log records, so the two
633
636
  // agree about who did it rather than one saying only that it was "admin".
@@ -638,15 +641,15 @@ module.exports = class MemberBREADService {
638
641
  const context = options.context || {};
639
642
  if (!context.integration && !context.user) {
640
643
  throw new errors.IncorrectUsageError({
641
- message: tpl(messages.customFieldsWithoutWriter),
644
+ message: tpl(messages.metafieldsWithoutWriter),
642
645
  });
643
646
  }
644
647
  const writtenBy = context.integration
645
648
  ? { type: 'integration', id: context.integration.id }
646
649
  : { type: 'user', id: context.user };
647
- await this.customFieldValues.applyWrite(model.id, plannedCustomFields, { writtenBy });
650
+ await this.metafieldValues.applyWrite(model.id, plannedMetafields, { writtenBy });
648
651
 
649
- // Custom fields aren't a member column or relation, so an edit touching
652
+ // Metafields aren't a member column or relation, so an edit touching
650
653
  // only them leaves `model._changed` empty and the save fires nothing.
651
654
  // Declare the change into `_changed` — as bookshelf-relations does for a
652
655
  // labels change — so the member's edited lifecycle fires its usual signals
@@ -656,13 +659,13 @@ module.exports = class MemberBREADService {
656
659
  // populated `_changed` and fired the edited event during update(), so
657
660
  // re-firing would duplicate it (this also covers a full PUT that resends
658
661
  // unchanged member fields — `_changed` stays empty there too). That
659
- // combined event omits `custom_fields` from `_changed`, which nothing
660
- // reads: custom fields aren't in the webhook payload (they're injected
662
+ // combined event omits `metafields` from `_changed`, which nothing
663
+ // reads: metafields aren't in the webhook payload (they're injected
661
664
  // into read/browse responses, not the model), and `_changed` only gates
662
665
  // whether the event fires.
663
666
  const memberUnchanged = !model._changed || Object.keys(model._changed).length === 0;
664
- if (memberUnchanged && plannedCustomFields.length > 0) {
665
- model._changed = { custom_fields: true };
667
+ if (memberUnchanged && plannedMetafields.length > 0) {
668
+ model._changed = { metafields: true };
666
669
  // A mixed edit keeps the generic label on purpose: relabelling the
667
670
  // one action a member change already fired would bury that change
668
671
  // behind this one.
@@ -791,8 +794,11 @@ module.exports = class MemberBREADService {
791
794
 
792
795
  // One query for the whole page, not one per member. `null` when the flag
793
796
  // is off or the caller didn't ask — the same truthiness guard read uses.
794
- const customFieldsByMember = options.includeCustomFields
795
- ? await this.fetchCustomFieldValues(page.data.map((model) => model.id))
797
+ const metafieldsByMember = options.includeMetafields
798
+ ? await this.fetchMetafieldValues(
799
+ page.data.map((model) => model.id),
800
+ ADMIN,
801
+ )
796
802
  : null;
797
803
 
798
804
  const data = page.data.map((model, index) => {
@@ -804,8 +810,8 @@ module.exports = class MemberBREADService {
804
810
  if (!originalWithRelated.includes('products')) {
805
811
  delete member.products;
806
812
  }
807
- if (customFieldsByMember) {
808
- member.metafields = customFieldsByMember.get(model.id) ?? {};
813
+ if (metafieldsByMember) {
814
+ member.metafields = metafieldsByMember.get(model.id) ?? {};
809
815
  }
810
816
  member.email_suppression = {
811
817
  suppressed: bulkSuppressionData[index].suppressed || !!model.get('email_disabled'),
@@ -307,6 +307,24 @@ class MembersSSR {
307
307
  *
308
308
  * @returns {Promise<Member>}
309
309
  */
310
+ /**
311
+ * Who this session belongs to, without loading them.
312
+ *
313
+ * The counterpart to `getMemberDataFromSession`, for callers that need to know a
314
+ * member is there rather than what is on their record.
315
+ *
316
+ * @method getMemberIdentityFromSession
317
+ * @param {Request} req
318
+ * @param {Response} res
319
+ *
320
+ * @returns {Promise<{id: string, email: string} | null>}
321
+ */
322
+ async getMemberIdentityFromSession(req, res) {
323
+ const transientId = this._getSessionCookies(req, res);
324
+ const api = await this._getMembersApi();
325
+ return api.getMemberIdentity(transientId);
326
+ }
327
+
310
328
  async getMemberDataFromSession(req, res) {
311
329
  const transientId = this._getSessionCookies(req, res);
312
330
  const member = await this._getMemberIdentityDataFromTransientId(transientId);
@@ -2,11 +2,10 @@ const crypto = require('crypto');
2
2
  const _ = require('lodash');
3
3
  const logging = require('@tryghost/logging');
4
4
  const membersService = require('./service');
5
- const emailSuppressionList = require('../email-suppression-list');
6
5
  const models = require('../../models');
7
6
  const urlUtils = require('../../../shared/url-utils').default;
8
7
  const spamPrevention = require('../../web/shared/middleware/api/spam-prevention');
9
- const { formattedMemberResponse, formatNewsletterResponse } = require('./utils');
8
+ const { formatNewsletterResponse } = require('./utils');
10
9
  const errors = require('@tryghost/errors');
11
10
  const tpl = require('@tryghost/tpl');
12
11
  const onHeaders = require('on-headers');
@@ -18,6 +17,9 @@ const messages = {
18
17
  missingUuid: 'Missing uuid.',
19
18
  invalidUuid: 'Invalid uuid.',
20
19
  invalidKey: 'Invalid key.',
20
+ // Says that nobody is signed in, without naming the cookie that would have said
21
+ // so. Which cookie carries a session is Ghost's business, not the caller's.
22
+ noMemberSession: 'You must be signed in to do this.',
21
23
  };
22
24
 
23
25
  const getFreeTier = async function getFreeTier() {
@@ -116,6 +118,94 @@ const getRedirectUrl = function getRedirectUrl({ action, referrer, searchParams,
116
118
  return redirectUrl.href;
117
119
  };
118
120
 
121
+ /**
122
+ * Establish who is signed in, without loading them.
123
+ *
124
+ * Sets `req.identity` to an id and an email address, or to null when nobody is
125
+ * signed in. One lookup against an indexed column, with no relations attached and
126
+ * nothing fetched from outside Ghost.
127
+ *
128
+ * Deliberately not the member's record. Most endpoints that serve a member need to
129
+ * act on one rather than describe one, and the one that describes a member reads
130
+ * it again after writing anyway, because the response has to say what Ghost now
131
+ * holds rather than what was sent. What to load is the endpoint's own business.
132
+ *
133
+ * Distinct from `loadMemberSession`, which loads the whole member and is what a
134
+ * themed page renders from.
135
+ */
136
+ const loadMemberIdentity = async function loadMemberIdentity(req, res, next) {
137
+ try {
138
+ Object.assign(req, {
139
+ identity: await membersService.ssr.getMemberIdentityFromSession(req, res),
140
+ });
141
+ } catch (err) {
142
+ // Only a missing or unreadable cookie means nobody is signed in. Establishing
143
+ // an identity also reaches the database, and answering "you are not signed in"
144
+ // to that failing would tell a signed-in member something false and leave the
145
+ // real fault unreported.
146
+ if (!errors.utils.isGhostError(err) || err.errorType !== 'BadRequestError') {
147
+ return next(err);
148
+ }
149
+ Object.assign(req, { identity: null });
150
+ }
151
+
152
+ return next();
153
+ };
154
+
155
+ /**
156
+ * Hand the identity to whatever comes next, in the place the API framework reads.
157
+ *
158
+ * The framework takes whoever is acting from `req.member` and nowhere else, so a
159
+ * controller behind either gate below finds the identity there.
160
+ */
161
+ const carryIdentity = function carryIdentity(req, res) {
162
+ Object.assign(req, { member: req.identity });
163
+ res.locals.member = req.member;
164
+ };
165
+
166
+ /**
167
+ * Refuse a request that `loadMemberIdentity` could not put a name to.
168
+ *
169
+ * For endpoints where an unknown caller is asking for something they have no right
170
+ * to: changing a member's record, or reading what a publisher collects.
171
+ *
172
+ * Reads only `req.identity`, so a route using this must register
173
+ * `loadMemberIdentity` first. It looks nothing up itself: what a route needs is
174
+ * declared by the middleware it lists, rather than depending on what some earlier
175
+ * one happened to leave behind.
176
+ */
177
+ const rejectWhenAnonymous = function rejectWhenAnonymous(req, res, next) {
178
+ if (!req.identity) {
179
+ return next(
180
+ new errors.UnauthorizedError({
181
+ message: tpl(messages.noMemberSession),
182
+ }),
183
+ );
184
+ }
185
+
186
+ carryIdentity(req, res);
187
+ return next();
188
+ };
189
+
190
+ /**
191
+ * Answer with nothing when `loadMemberIdentity` could not put a name to the request.
192
+ *
193
+ * For reading who you are, where not knowing is an ordinary answer rather than a
194
+ * failure: a themed page asks on every view and most of those views have no member.
195
+ *
196
+ * The counterpart to `rejectWhenAnonymous`, and separate from it because which of
197
+ * the two applies is a fact about the endpoint, not a setting on a shared one.
198
+ */
199
+ const emptyWhenAnonymous = function emptyWhenAnonymous(req, res, next) {
200
+ if (!req.identity) {
201
+ res.writeHead(204);
202
+ return res.end();
203
+ }
204
+
205
+ carryIdentity(req, res);
206
+ return next();
207
+ };
208
+
119
209
  /**
120
210
  * Require member authentication, and make it possible to authenticate via uuid + hashed key.
121
211
  * You can chain this after loadMemberSession to make it possible to authenticate via both the uuid and the session.
@@ -243,42 +333,6 @@ const deleteSession = async function deleteSession(req, res) {
243
333
  }
244
334
  };
245
335
 
246
- const getMemberData = async function getMemberData(req, res) {
247
- try {
248
- const member = await membersService.ssr.getMemberDataFromSession(req, res);
249
- if (member) {
250
- res.json(formattedMemberResponse(member));
251
- } else {
252
- res.json(null);
253
- }
254
- } catch (err) {
255
- res.writeHead(204);
256
- res.end();
257
- }
258
- };
259
-
260
- const deleteSuppression = async function deleteSuppression(req, res) {
261
- try {
262
- const member = await membersService.ssr.getMemberDataFromSession(req, res);
263
- const options = {
264
- id: member.id,
265
- };
266
- await emailSuppressionList.removeEmail(member.email);
267
- await membersService.api.members.update({ email_disabled: false }, options);
268
-
269
- res.writeHead(204);
270
- res.end();
271
- } catch (err) {
272
- if (!err.statusCode) {
273
- logging.error(err);
274
- }
275
- res.writeHead(err.statusCode ?? 500, {
276
- 'Content-Type': 'text/plain;charset=UTF-8',
277
- });
278
- res.end(err.message);
279
- }
280
- };
281
-
282
336
  const getMemberNewsletters = async function getMemberNewsletters(req, res) {
283
337
  try {
284
338
  const memberData = req.member; // validation assumed
@@ -351,46 +405,6 @@ const updateMemberNewsletters = async function updateMemberNewsletters(req, res)
351
405
  }
352
406
  };
353
407
 
354
- const updateMemberData = async function updateMemberData(req, res) {
355
- try {
356
- const data = _.pick(
357
- req.body,
358
- 'name',
359
- 'expertise',
360
- 'subscribed',
361
- 'newsletters',
362
- 'enable_comment_notifications',
363
- 'enable_updates_and_announcements',
364
- );
365
- const member = await membersService.ssr.getMemberDataFromSession(req, res);
366
- if (member) {
367
- const options = {
368
- id: member.id,
369
- withRelated: [
370
- 'stripeSubscriptions',
371
- 'stripeSubscriptions.customer',
372
- 'stripeSubscriptions.stripePrice',
373
- 'newsletters',
374
- ],
375
- };
376
- await membersService.api.members.update(data, options);
377
- const updatedMember = await membersService.ssr.getMemberDataFromSession(req, res);
378
-
379
- res.json(formattedMemberResponse(updatedMember));
380
- } else {
381
- res.json(null);
382
- }
383
- } catch (err) {
384
- if (!err.statusCode) {
385
- logging.error(err);
386
- }
387
- res.writeHead(err.statusCode ?? 500, {
388
- 'Content-Type': 'text/plain;charset=UTF-8',
389
- });
390
- res.end(err.message);
391
- }
392
- };
393
-
394
408
  const createSessionFromMagicLink = async function createSessionFromMagicLink(req, res, next) {
395
409
  if (!req.url.includes('token=')) {
396
410
  return next();
@@ -500,17 +514,17 @@ const createSessionFromMagicLink = async function createSessionFromMagicLink(req
500
514
  // Set req.member & res.locals.member if a cookie is set
501
515
  module.exports = {
502
516
  loadMemberSession,
517
+ loadMemberIdentity,
518
+ rejectWhenAnonymous,
519
+ emptyWhenAnonymous,
503
520
  authMemberByUuid,
504
521
  createSessionFromMagicLink,
505
522
  getIdentityToken,
506
523
  getEntitlementToken,
507
524
  getMemberNewsletters,
508
- getMemberData,
509
- updateMemberData,
510
525
  updateMemberNewsletters,
511
526
  deleteSession,
512
527
  accessInfoSession,
513
- deleteSuppression,
514
528
  createIntegrityToken,
515
529
  verifyIntegrityToken,
516
530
  };
@@ -49,9 +49,9 @@ let membersApi;
49
49
  let verificationTrigger;
50
50
 
51
51
  const buildImporterDeps = ({ stripeAPIService }) => {
52
- // Required here, not statically: boot builds the custom fields services before this
52
+ // Required here, not statically: boot builds the metafields services before this
53
53
  // one (the exporter below relies on the same).
54
- const customFields = require('../members-custom-fields');
54
+ const metafields = require('../members-metafields');
55
55
  return {
56
56
  getTimezone: () => settingsCache.get('timezone'),
57
57
  // A getter rather than a value because the threshold is an operator
@@ -88,9 +88,9 @@ const buildImporterDeps = ({ stripeAPIService }) => {
88
88
  urlFor: urlUtils.urlFor.bind(urlUtils),
89
89
  stripeAPIService,
90
90
  productRepository: membersApi.productRepository,
91
- customFields: {
92
- definitions: customFields.definitions,
93
- values: customFields.values,
91
+ metafields: {
92
+ definitions: metafields.definitions,
93
+ values: metafields.values,
94
94
  },
95
95
  };
96
96
  };
@@ -115,6 +115,66 @@ const initVerificationTrigger = () => {
115
115
  });
116
116
  };
117
117
 
118
+ const membersMigrationJobName = 'members-migrations';
119
+
120
+ /**
121
+ * Runs the historical Stripe backfills once per site, retried only after a
122
+ * failed run.
123
+ *
124
+ * The `jobs` row named `members-migrations` is the only guard: any status other
125
+ * than `failed` means the run was already attempted and is skipped forever. The
126
+ * row is written after the run, so a boot that dies mid-way retries next time.
127
+ * The row is written even when Stripe is not configured, matching the job-based
128
+ * version: a site that connects Stripe later never runs these 2021-era backfills.
129
+ * Two processes booting a site with no row will both run the backfills, where the
130
+ * job-based version claimed the row first. Accepted: every site that has booted
131
+ * since Ghost 5.6 has the row, and the backfills do nothing without Stripe.
132
+ *
133
+ * @TODO: Delete the backfills, this runner and the `jobs` rows in the next major
134
+ *
135
+ * @param {import('../stripe')} stripeService
136
+ */
137
+ async function runStripeMigrations(stripeService) {
138
+ const existingJob = await models.Job.findOne({ name: membersMigrationJobName });
139
+
140
+ if (existingJob && existingJob.get('status') !== 'failed') {
141
+ logging.info(`Stripe ${membersMigrationJobName} skipped because it has already run`);
142
+ return;
143
+ }
144
+
145
+ const startedAt = Date.now();
146
+ logging.info(`Stripe ${membersMigrationJobName} started`);
147
+
148
+ let status = 'finished';
149
+ try {
150
+ await stripeService.migrations.execute();
151
+ logging.info(`Stripe ${membersMigrationJobName} completed in ${Date.now() - startedAt}ms`);
152
+ } catch (err) {
153
+ status = 'failed';
154
+ logging.error(
155
+ err,
156
+ `Stripe ${membersMigrationJobName} failed after ${Date.now() - startedAt}ms`,
157
+ );
158
+ }
159
+
160
+ const attrs = { status, started_at: new Date(startedAt), finished_at: new Date() };
161
+ if (existingJob) {
162
+ await models.Job.edit(attrs, { id: existingJob.id });
163
+ return;
164
+ }
165
+
166
+ try {
167
+ await models.Job.add({ name: membersMigrationJobName, ...attrs });
168
+ } catch (err) {
169
+ // Two processes booting a site with no row yet both get here, and the unique
170
+ // index on jobs.name rejects the second insert. The row exists, so move on.
171
+ if (!(await models.Job.findOne({ name: membersMigrationJobName }))) {
172
+ throw err;
173
+ }
174
+ logging.warn(`Stripe ${membersMigrationJobName} row was already written by another process`);
175
+ }
176
+ }
177
+
118
178
  module.exports = {
119
179
  async init() {
120
180
  const stripeService = require('../stripe');
@@ -175,46 +235,14 @@ module.exports = {
175
235
  membersCSVImporter.importInline(request, verificationTrigger);
176
236
 
177
237
  // Constructed here rather than required statically: the exporter needs the
178
- // custom fields services, which boot builds before this one.
179
- const customFields = require('../members-custom-fields');
238
+ // metafields services, which boot builds before this one.
239
+ const metafields = require('../members-metafields');
180
240
  module.exports.export = makeExporter({
181
- definitions: customFields.definitions,
182
- values: customFields.values,
241
+ definitions: metafields.definitions,
242
+ values: metafields.values,
183
243
  });
184
244
 
185
- if (!env?.startsWith('testing')) {
186
- const membersMigrationJobName = 'members-migrations';
187
- if (!(await jobsService.hasExecutedSuccessfully(membersMigrationJobName))) {
188
- logging.info(`[Background Job] ${membersMigrationJobName} queued`);
189
- jobsService.addOneOffJob({
190
- name: membersMigrationJobName,
191
- offloaded: false,
192
- job: async () => {
193
- const startedAt = Date.now();
194
- logging.info(`[Background Job] ${membersMigrationJobName} started`);
195
- try {
196
- const result = await stripeService.migrations.execute();
197
- logging.info(
198
- `[Background Job] ${membersMigrationJobName} completed in ${Date.now() - startedAt}ms`,
199
- );
200
- return result;
201
- } catch (err) {
202
- logging.error(
203
- err,
204
- `[Background Job] ${membersMigrationJobName} failed after ${Date.now() - startedAt}ms`,
205
- );
206
- throw err;
207
- }
208
- },
209
- });
210
-
211
- await jobsService.awaitOneOffCompletion(membersMigrationJobName);
212
- } else {
213
- logging.info(
214
- `[Background Job] ${membersMigrationJobName} skipped because it has already run`,
215
- );
216
- }
217
- }
245
+ await runStripeMigrations(stripeService);
218
246
  },
219
247
  contentGating: require('./content-gating'),
220
248
 
@@ -41,5 +41,14 @@ module.exports.formattedMemberResponse = function formattedMemberResponse(member
41
41
  data.email_suppression = member.email_suppression;
42
42
  }
43
43
 
44
+ // Absent rather than empty on a site that has defined no extra fields, which is
45
+ // most of them, so those members' accounts read exactly as they did before this
46
+ // existed. Present but empty means the publisher has defined fields and this
47
+ // member has answered none, which a client renders as blank inputs rather than
48
+ // as nothing to fill in.
49
+ if (member.metafields) {
50
+ data.metafields = member.metafields;
51
+ }
52
+
44
53
  return data;
45
54
  };
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.INTERNAL = exports.MEMBERS = exports.ADMIN = void 0;
4
+ exports.readableFields = readableFields;
5
+ exports.canWrite = canWrite;
6
+ exports.ADMIN = { entry: 'admin' };
7
+ exports.MEMBERS = { entry: 'members' };
8
+ exports.INTERNAL = { entry: 'internal' };
9
+ /**
10
+ * Which of the site's fields this audience may see.
11
+ *
12
+ * Every field, whichever door they came through. Written as a function rather than
13
+ * left unwritten so that when a publisher can mark a field as staff only, one
14
+ * function changes and every caller is already asking.
15
+ */
16
+ function readableFields(_audience, fields) {
17
+ return fields;
18
+ }
19
+ /**
20
+ * Whether this audience may write this field.
21
+ *
22
+ * Also total today. Kept separate from `readableFields` because the asymmetry to
23
+ * expect is a field a member may read but not change, which one combined
24
+ * permission could not express.
25
+ */
26
+ function canWrite(_audience, _field) {
27
+ return true;
28
+ }