ghost 6.61.0 → 6.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (472) hide show
  1. package/components/ghost-storage-base-3.0.0.tgz +0 -0
  2. package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
  3. package/components/tryghost-adapter-base-jobs-0.0.0.tgz +0 -0
  4. package/components/tryghost-adapter-base-redirects-0.0.0.tgz +0 -0
  5. package/components/tryghost-adapter-base-route-settings-0.0.0.tgz +0 -0
  6. package/components/tryghost-adapter-base-scheduling-0.2.3.tgz +0 -0
  7. package/components/tryghost-adapter-base-sso-0.1.3.tgz +0 -0
  8. package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
  9. package/components/tryghost-checkout-0.0.0.tgz +0 -0
  10. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  11. package/components/tryghost-i18n-0.0.0.tgz +0 -0
  12. package/components/tryghost-kg-card-factory-5.2.4.tgz +0 -0
  13. package/components/tryghost-kg-clean-basic-html-4.3.4.tgz +0 -0
  14. package/components/tryghost-kg-converters-1.2.5.tgz +0 -0
  15. package/components/tryghost-kg-default-cards-10.3.5.tgz +0 -0
  16. package/components/tryghost-kg-default-nodes-2.2.1.tgz +0 -0
  17. package/components/tryghost-kg-default-transforms-1.3.4.tgz +0 -0
  18. package/components/tryghost-kg-html-to-lexical-1.4.0.tgz +0 -0
  19. package/components/tryghost-kg-lexical-html-renderer-1.5.0.tgz +0 -0
  20. package/components/tryghost-kg-markdown-html-renderer-7.2.4.tgz +0 -0
  21. package/components/tryghost-kg-utils-1.1.4.tgz +0 -0
  22. package/components/tryghost-nql-string-0.0.0.tgz +0 -0
  23. package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
  24. package/content/settings/README.md +1 -1
  25. package/content/themes/casper/.gitignore +28 -0
  26. package/content/themes/casper/assets/built/casper.js +1 -1
  27. package/content/themes/casper/assets/built/screen.css +1 -1
  28. package/content/themes/casper/assets/css/screen.css +2 -1
  29. package/content/themes/casper/assets/js/lightbox.js +63 -46
  30. package/content/themes/casper/package.json +6 -6
  31. package/content/themes/casper/page.hbs +1 -0
  32. package/content/themes/casper/post.hbs +1 -0
  33. package/content/themes/source/.gitignore +28 -0
  34. package/content/themes/source/assets/built/screen.css +1 -1
  35. package/content/themes/source/assets/built/source.js +1 -1
  36. package/content/themes/source/assets/css/screen.css +2 -1
  37. package/content/themes/source/assets/js/lightbox.js +62 -45
  38. package/content/themes/source/assets/js/main.js +2 -2
  39. package/content/themes/source/package.json +4 -4
  40. package/content/themes/source/partials/feature-image.hbs +1 -0
  41. package/core/boot.js +54 -22
  42. package/core/built/admin/assets/Combination-B0l4MKVh.js +41 -0
  43. package/core/built/admin/assets/InterVariable-DiVDrmQJ.woff2 +0 -0
  44. package/core/built/admin/assets/InterVariable-Italic-FCBEiFp6.woff2 +0 -0
  45. package/core/built/admin/assets/{about-Bve7IJS2.js → about-Dyae_UT6.js} +1 -1
  46. package/core/built/admin/assets/account-migration-CESa0mnX.js +1 -0
  47. package/core/built/admin/assets/activitypub/{account-migration-C-aej4x_.js → account-migration-By69CgCp.js} +16 -16
  48. package/core/built/admin/assets/activitypub/activitypub.js +9 -9
  49. package/core/built/admin/assets/activitypub/{bluesky-sharing-BXkA5mqV.js → bluesky-sharing-BJcPMrDd.js} +36 -36
  50. package/core/built/admin/assets/activitypub/{content-formatters-CHcyMFk4.js → content-formatters-3rTHg3mp.js} +3 -3
  51. package/core/built/admin/assets/activitypub/{domain-B3ihkPde.js → domain-DUR4xSkV.js} +121 -121
  52. package/core/built/admin/assets/activitypub/{edit-profile-DsZM3rTX.js → edit-profile-BqtfzxCp.js} +40 -40
  53. package/core/built/admin/assets/activitypub/{explore-BodNrZgR.js → explore-BxuJd-J4.js} +26 -26
  54. package/core/built/admin/assets/activitypub/{feed-PwH1iJ84.js → feed-BM1TcksA.js} +48 -48
  55. package/core/built/admin/assets/activitypub/{field-D06-kmOJ.js → field-Dn7HHx2-.js} +2 -2
  56. package/core/built/admin/assets/activitypub/inbox-CCjkuow2.js +1284 -0
  57. package/core/built/admin/assets/activitypub/label-1AA_s9Fa.js +16 -0
  58. package/core/built/admin/assets/activitypub/layout-2-0bw6HN.js +1 -0
  59. package/core/built/admin/assets/activitypub/{moderation-D1Yapsod.js → moderation-CnmxEB8w.js} +91 -91
  60. package/core/built/admin/assets/activitypub/{note-C5zjqq5C.js → note-ZKc_Bnba.js} +23 -23
  61. package/core/built/admin/assets/activitypub/{notifications-B9p8eCHD.js → notifications-CB2tJZau.js} +48 -48
  62. package/core/built/admin/assets/activitypub/{onboarding-CNelD_uF.js → onboarding-BsJ9E5qf.js} +2 -2
  63. package/core/built/admin/assets/activitypub/{onboarding-mat5Hrl0.js → onboarding-CSUJJIlh.js} +2 -2
  64. package/core/built/admin/assets/activitypub/{preferences-BCqUdC3v.js → preferences-0-JZnUkI.js} +1519 -1519
  65. package/core/built/admin/assets/activitypub/{profile-BRCWoFem.js → profile-C35cFIhM.js} +183 -183
  66. package/core/built/admin/assets/activitypub/{routes-BSXT992i.js → routes-BD4vscyc.js} +740 -731
  67. package/core/built/admin/assets/activitypub/settings-BsMROdqh.js +315 -0
  68. package/core/built/admin/assets/activitypub/{site-DHoMupSB.js → site-O0RL9YCN.js} +2 -2
  69. package/core/built/admin/assets/activitypub/{step-1-B-YfsVeO.js → step-1-BMSx7c_X.js} +4 -4
  70. package/core/built/admin/assets/activitypub/{step-3-D6BFMXL_.js → step-3-CLlOZTyt.js} +3 -3
  71. package/core/built/admin/assets/activitypub/{tabs-GpNecvfB.js → tabs-Dxk-b9M-.js} +306 -306
  72. package/core/built/admin/assets/activitypub/{topic-filter-BPDeQQys.js → topic-filter-5yZH6tId.js} +2 -2
  73. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-BoVOxXkV.js → use-activity-pub-queries-CV50qe03.js} +2 -2
  74. package/core/built/admin/assets/activitypub/{use-reply-chain-data-wVBX7swM.js → use-reply-chain-data-Cbvzgjzd.js} +30 -30
  75. package/core/built/admin/assets/activitypub/{users-CoX6FT_K.js → users-BjcoP_HC.js} +7 -5
  76. package/core/built/admin/assets/add-integration-modal-BlF6rs_f.js +1 -0
  77. package/core/built/admin/assets/add-newsletter-modal-CglVQqah.js +1 -0
  78. package/core/built/admin/assets/add-recommendation-modal-wz7BsuaG.js +1 -0
  79. package/core/built/admin/assets/analytics-C4HE1gBg.js +1 -0
  80. package/core/built/admin/assets/{announcement-bar-modal-DM3cYOpo.js → announcement-bar-modal-Ci4zz8jO.js} +1 -1
  81. package/core/built/admin/assets/{api-keys-BLm2NDBh.js → api-keys-Ck-Yteiv.js} +1 -1
  82. package/core/built/admin/assets/{audience-9mwU5TW6.js → audience-CtbA54Sn.js} +1 -1
  83. package/core/built/admin/assets/{automated-emails-te1D5kq-.js → automated-emails-D5VVMND9.js} +1 -1
  84. package/core/built/admin/assets/automations-CzKt5fb8.js +1 -0
  85. package/core/built/admin/assets/bluesky-sharing-BEtJ_gL_.js +1 -0
  86. package/core/built/admin/assets/{breadcrumb-DLiqCyFc.js → breadcrumb-zUrZrBtW.js} +1 -1
  87. package/core/built/admin/assets/{button-B2jcu6bk.js → button-eNMKQWXr.js} +1 -1
  88. package/core/built/admin/assets/{chart-helpers-Fo3bUL3K.js → chart-helpers-CfBKt0vT.js} +1 -1
  89. package/core/built/admin/assets/{checkbox-BfKktMph.js → checkbox-CgOn_tKW.js} +1 -1
  90. package/core/built/admin/assets/{chunk.524.60ca4692d91c6022042f.js → chunk.524.a697dee88db67fdf6021.js} +4 -4
  91. package/core/built/admin/assets/{chunk.582.525c7617c2d9e94c940a.js → chunk.582.5ed5d124cfca9c259b9c.js} +6 -6
  92. package/core/built/admin/assets/{chunk.896.435fe0637944d4a69810.js → chunk.598.7e056fabb47ce7dbd73f.js} +14 -14
  93. package/core/built/admin/assets/{code-editor-view-CYVu4EW6.js → code-editor-view-BYNoAoNV.js} +1 -1
  94. package/core/built/admin/assets/{code-editor-view-DCQJxsfU.js → code-editor-view-K2wI-I5f.js} +1 -1
  95. package/core/built/admin/assets/color-picker-Ddw80xX6.js +3 -0
  96. package/core/built/admin/assets/color-picker-field-D9nUhvA7.js +1 -0
  97. package/core/built/admin/assets/combobox-w9j8PPQT.js +1 -0
  98. package/core/built/admin/assets/command-BfpHORBv.js +1 -0
  99. package/core/built/admin/assets/comments-BLmCmyCs.js +1 -0
  100. package/core/built/admin/assets/confirmation-context-CBM8G0Vj.js +1 -0
  101. package/core/built/admin/assets/{content-api-modal-CyQEC3fr.js → content-api-modal-TXRz3U7S.js} +1 -1
  102. package/core/built/admin/assets/context-menu-_QMdd3kp.js +1 -0
  103. package/core/built/admin/assets/{copy-field-B3M1s78L.js → copy-field-6N01S07E.js} +1 -1
  104. package/core/built/admin/assets/create-ghost-browse-value-source-qIBymvvf.js +1 -0
  105. package/core/built/admin/assets/custom-field-type-option-BddFlJFa.js +1 -0
  106. package/core/built/admin/assets/custom-integration-modal-DmNquxUV.js +1 -0
  107. package/core/built/admin/assets/{date-range-select-DD2RKkgB.js → date-range-select-uvz_DtPh.js} +1 -1
  108. package/core/built/admin/assets/design-and-theme-modal-BSmo12Op.js +2 -0
  109. package/core/built/admin/assets/dirty-confirm-dialog-BZ1iytEd.js +1 -0
  110. package/core/built/admin/assets/{disabled-sources-indicator-Cvy47bE2.js → disabled-sources-indicator-DMRrxsiJ.js} +1 -1
  111. package/core/built/admin/assets/dist-Dcsslyo72.js +1 -0
  112. package/core/built/admin/assets/domain-BNYrt0aD.js +1 -0
  113. package/core/built/admin/assets/dropzone-D7nFdAe5.js +5 -0
  114. package/core/built/admin/assets/edit-profile-C5Asm4LA.js +1 -0
  115. package/core/built/admin/assets/editor-REokrzci.js +8 -0
  116. package/core/built/admin/assets/{embed-signup-form-modal-DeBMLxlL.js → embed-signup-form-modal-Ds6mPEi5.js} +1 -1
  117. package/core/built/admin/assets/en-B1jX3WMs.js +1 -0
  118. package/core/built/admin/assets/explore-UY10Egsp.js +1 -0
  119. package/core/built/admin/assets/feature-image-placeholder-D1iF_8bc.js +1 -0
  120. package/core/built/admin/assets/feed-81C8mJWR.js +1 -0
  121. package/core/built/admin/assets/{field-Cj8lmDOo.js → field-Dihp5p18.js} +1 -1
  122. package/core/built/admin/assets/filter-bar-Da1qxybu.js +1 -0
  123. package/core/built/admin/assets/filter-sources-BO9xUPj7.js +1 -0
  124. package/core/built/admin/assets/filters-CRUWecfY.js +1 -0
  125. package/core/built/admin/assets/filters-DirqNXjH.js +1 -0
  126. package/core/built/admin/assets/first-promoter-modal-CT6MxYZb.js +1 -0
  127. package/core/built/admin/assets/{ghost-c8807202a07c20030acb719ee0114815.css → ghost-8eabfc1ece81b03cf7b90780d31487e8.css} +1 -1
  128. package/core/built/admin/assets/{ghost-3894eedcc6968f658578c0abfe942aab.js → ghost-c2848508292937415f20f5a830eea59e.js} +31 -28
  129. package/core/built/admin/assets/{ghost-dark-fbf1e384aaef3ae315e540f0eb7373a7.css → ghost-dark-a7bebe2c561f55369dcf29f4c820f7ff.css} +1 -1
  130. package/core/built/admin/assets/gift-link-modal-CQVAye17.js +1 -0
  131. package/core/built/admin/assets/growth-BWIu-6xj.js +3 -0
  132. package/core/built/admin/assets/growth-DU29oxeA.js +1 -0
  133. package/core/built/admin/assets/history-modal-CFd1Hvx6.js +1 -0
  134. package/core/built/admin/assets/{html-field-C8qVGGC1.js → html-field-CJBCLTvv.js} +1 -1
  135. package/core/built/admin/assets/{images-h8VZEQ9k.js → images-scfkWZiK.js} +1 -1
  136. package/core/built/admin/assets/{inbox-BYd1M06Q.js → inbox-Mcp4DXZN.js} +14 -14
  137. package/core/built/admin/assets/index-6MkPdCtr.js +4 -0
  138. package/core/built/admin/assets/index-Bn7QhcZu.css +2 -0
  139. package/core/built/admin/assets/index.es-CNnuNH0d.js +1 -0
  140. package/core/built/admin/assets/{input-group-D8sCXRF5.js → input-group-CMhe2DvY.js} +1 -1
  141. package/core/built/admin/assets/invite-user-modal-DfheMXs0.js +1 -0
  142. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +14 -14
  143. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +3 -3
  144. package/core/built/admin/assets/koenig-lexical/style.css +1 -1
  145. package/core/built/admin/assets/{koenig-lexical-_-IsVI7E.js → koenig-lexical-DBSqAuiI.js} +3 -3
  146. package/core/built/admin/assets/koenig-loader-aFQNlJlD.js +2 -0
  147. package/core/built/admin/assets/{kpi-card-DZQk_0W8.js → kpi-card-DBRMfGOW.js} +1 -1
  148. package/core/built/admin/assets/{kpi-card-DW_2y3ug.js → kpi-card-DFafD1qq.js} +1 -1
  149. package/core/built/admin/assets/{kpi-helpers-PueTKuA6.js → kpi-helpers-Cv8i8hns.js} +1 -1
  150. package/core/built/admin/assets/{kpi-tabs-CojDs-Qz.js → kpi-tabs-BJItvLYm.js} +1 -1
  151. package/core/built/admin/assets/{label-DjbT91GM.js → label-BnSCvffu.js} +1 -1
  152. package/core/built/admin/assets/label-picker-KVoo91sh.js +1 -0
  153. package/core/built/admin/assets/layout-Bjv2Ll-5.js +1 -0
  154. package/core/built/admin/assets/member-custom-fields-DDQV6QeG.js +1 -0
  155. package/core/built/admin/assets/member-detail-CdJF6Agw.js +1 -0
  156. package/core/built/admin/assets/members-DjQbq9Z2.js +4 -0
  157. package/core/built/admin/assets/moderation-AgCIEso4.js +1 -0
  158. package/core/built/admin/assets/{multi-select-combobox-B6LiRzDO.js → multi-select-combobox-BcITkUVr.js} +1 -1
  159. package/core/built/admin/assets/navigation-modal-BkT7b1M2.js +1 -0
  160. package/core/built/admin/assets/newsletter-BMMAuvgq.js +1 -0
  161. package/core/built/admin/assets/newsletter-detail-modal-0SCJ7Zpm.js +1 -0
  162. package/core/built/admin/assets/{newsletter-emails-CFeLlt4z.js → newsletter-emails-DKzwafuh.js} +1 -1
  163. package/core/built/admin/assets/newsletters-BRBb_uQC.js +1 -0
  164. package/core/built/admin/assets/note-DyGHtS9a.js +1 -0
  165. package/core/built/admin/assets/notifications-ZgWniKna.js +1 -0
  166. package/core/built/admin/assets/nql-BLpCn7qX.js +21 -0
  167. package/core/built/admin/assets/offers-container-modal-B-E8TIWr.js +1 -0
  168. package/core/built/admin/assets/{onboarding-B4YhlZ0i.js → onboarding-CzIsre-d.js} +1 -1
  169. package/core/built/admin/assets/onboarding-qg8Akovm.js +1 -0
  170. package/core/built/admin/assets/{onboarding-route-Dmc4IPm3.js → onboarding-route-CaavZulg.js} +1 -1
  171. package/core/built/admin/assets/overview-BW8GLeYi.js +1 -0
  172. package/core/built/admin/assets/overview-BkXZNzaZ.js +1 -0
  173. package/core/built/admin/assets/{page-header-BR_nbIPp.js → page-header-De0VzKBi.js} +1 -1
  174. package/core/built/admin/assets/pagemenu-B5KKCpbA.js +1 -0
  175. package/core/built/admin/assets/pages-jgaFm69d.js +1 -0
  176. package/core/built/admin/assets/pages-route-CIUAQxtY.js +1 -0
  177. package/core/built/admin/assets/papaparse.min-cQwnxGCG.js +10 -0
  178. package/core/built/admin/assets/{pintura-modal-BR9hEvS8.js → pintura-modal-BZ2J9w3n.js} +1 -1
  179. package/core/built/admin/assets/portal-frame-B6Hr9wHc.js +1 -0
  180. package/core/built/admin/assets/portal-modal-D4ceei3t.js +1 -0
  181. package/core/built/admin/assets/post-analytics-header-BYCsT0GP.js +1 -0
  182. package/core/built/admin/assets/post-analytics-ot7f6pih.js +1 -0
  183. package/core/built/admin/assets/{post-analytics-provider-CLfKGWZy.js → post-analytics-provider-Bo1gmYaP.js} +1 -1
  184. package/core/built/admin/assets/{post-share-modal-Cl0NXRJr.js → post-share-modal-C-PaYZ_e.js} +1 -1
  185. package/core/built/admin/assets/posts-DuGazgTb.js +1 -0
  186. package/core/built/admin/assets/posts-list-screen-CBGGgjjj.js +2 -0
  187. package/core/built/admin/assets/posts-route-D6xmS7Vf.js +1 -0
  188. package/core/built/admin/assets/preferences-C5YJLp74.js +5 -0
  189. package/core/built/admin/assets/{preview-modal-C-BL-F9k.js → preview-modal-BfeFqi5y.js} +1 -1
  190. package/core/built/admin/assets/profile-B8mx2yAu.js +1 -0
  191. package/core/built/admin/assets/radio-group-DczlLKOn.js +1 -0
  192. package/core/built/admin/assets/{schemas-4UPic-oD.js → schemas-DppqmpeJ.js} +1 -1
  193. package/core/built/admin/assets/select-cb0t_TAJ.js +1 -0
  194. package/core/built/admin/assets/settings-DZqxR0Da.js +1 -0
  195. package/core/built/admin/assets/settings-DvfTNdHQ.js +4 -0
  196. package/core/built/admin/assets/{settings-breadcrumbs-CyP_9zTo.js → settings-breadcrumbs-DLzbRRSt.js} +1 -1
  197. package/core/built/admin/assets/share-modal-D2-Bz1_I.js +1 -0
  198. package/core/built/admin/assets/slack-modal-DYDTUria.js +1 -0
  199. package/core/built/admin/assets/{sort-button-Cc-Z96sA.js → sort-button-VVeQjjbu.js} +1 -1
  200. package/core/built/admin/assets/{source-icon-v6PUnmyM.js → source-icon-Ck5_5Kgq.js} +1 -1
  201. package/core/built/admin/assets/stats-Rjmk5owg.js +1 -0
  202. package/core/built/admin/assets/stats-view-BGGmkmgv.js +1 -0
  203. package/core/built/admin/assets/{step-1-DSRp2vBj.js → step-1-CsaBjeN4.js} +1 -1
  204. package/core/built/admin/assets/{step-2-COqv2g0u.js → step-2-D2vnxOOM.js} +1 -1
  205. package/core/built/admin/assets/{step-3-6W1NMVQc.js → step-3-wBoLdGEb.js} +2 -2
  206. package/core/built/admin/assets/{stripe-button-D2K42Uty.js → stripe-button-CDgLz0S0.js} +1 -1
  207. package/core/built/admin/assets/stripe-connect-modal-CSjMJetP.js +1 -0
  208. package/core/built/admin/assets/{table-B2pIKjVh.js → table-Do6iQ83Q.js} +1 -1
  209. package/core/built/admin/assets/{tabs-SaW_UT-Q.js → tabs-D2yWLMp_.js} +1 -1
  210. package/core/built/admin/assets/tag-detail-BhXlOsCa.js +2 -0
  211. package/core/built/admin/assets/tags-ldukRApe.js +1 -0
  212. package/core/built/admin/assets/testimonials-modal-CtnZBJKl.js +1 -0
  213. package/core/built/admin/assets/tier-detail-modal-BOs8e_G8.js +1 -0
  214. package/core/built/admin/assets/{toggle-group-EM9WpESu.js → toggle-group-ByUJsxwD.js} +1 -1
  215. package/core/built/admin/assets/{topic-filter-DY4gNHn2.js → topic-filter-BgC3g5_v.js} +1 -1
  216. package/core/built/admin/assets/{transistor-modal-CpyPG-MH.js → transistor-modal-Drr0SNfo.js} +1 -1
  217. package/core/built/admin/assets/{trend-badge-ByMkuAuH.js → trend-badge-6ye93YAW.js} +1 -1
  218. package/core/built/admin/assets/{unsplash-modal-Cv2Q6GtY.js → unsplash-modal-p6AoQoWp.js} +1 -1
  219. package/core/built/admin/assets/{use-analytics-data-CHvjMYuT.js → use-analytics-data-CWCMAESo.js} +1 -1
  220. package/core/built/admin/assets/use-check-theme-limit-error-B8bjF-gO.css +1 -0
  221. package/core/built/admin/assets/{use-check-theme-limit-error-Qv4SVXqw.js → use-check-theme-limit-error-Cw3WQHy8.js} +1 -1
  222. package/core/built/admin/assets/use-filter-params-BIni9taJ.js +1 -0
  223. package/core/built/admin/assets/use-growth-stats-DCVG3s9w.js +1 -0
  224. package/core/built/admin/assets/{use-limiter-cwJvsNBo.js → use-limiter-D2WRRx0H.js} +1 -1
  225. package/core/built/admin/assets/{use-pintura-config-CzTfnufz.js → use-pintura-config-h-J0Lvta.js} +1 -1
  226. package/core/built/admin/assets/{use-pintura-editor-BDBXWUYq.js → use-pintura-editor-BfN1UwzH.js} +1 -1
  227. package/core/built/admin/assets/use-post-referrers-Czg47m8s.js +1 -0
  228. package/core/built/admin/assets/use-reply-chain-data-ns0caK8k.js +1 -0
  229. package/core/built/admin/assets/{use-router-history-entry-Bg6BhBQQ.js → use-router-history-entry-uldn7zJO.js} +1 -1
  230. package/core/built/admin/assets/{use-setting-group-VM7FQwMY.js → use-setting-group-DpcuCUDg.js} +1 -1
  231. package/core/built/admin/assets/use-settings-navigation-CZjGrhsp.js +1 -0
  232. package/core/built/admin/assets/{use-simple-pagination-Dum6Irbs.js → use-simple-pagination-DzJWobPR.js} +1 -1
  233. package/core/built/admin/assets/{use-staff-users-cZg4ugOF.js → use-staff-users-Csipb-O0.js} +1 -1
  234. package/core/built/admin/assets/use-tinybird-query-CSnGXzZh.js +1 -0
  235. package/core/built/admin/assets/use-tinybird-token-DTOKRR7e.js +1 -0
  236. package/core/built/admin/assets/{use-unsaved-changes-guard-BAy0RANR.js → use-unsaved-changes-guard-CBzs-KPi.js} +1 -1
  237. package/core/built/admin/assets/{use-url-input-Df5QzNa7.js → use-url-input-DFr8lCbS.js} +1 -1
  238. package/core/built/admin/assets/user-detail-modal-B_iUClI6.js +1 -0
  239. package/core/built/admin/assets/users-Cg-OeGzC.js +1 -0
  240. package/core/built/admin/assets/{virtual-list-DEx8WgiO.js → virtual-list-BW-padMe.js} +1 -1
  241. package/core/built/admin/assets/web-C1v33ehA.js +2 -0
  242. package/core/built/admin/assets/web-C4BKzn2T.js +1 -0
  243. package/core/built/admin/assets/{zapier-modal-Cw3BTBUu.js → zapier-modal-BBFfI6Ml.js} +1 -1
  244. package/core/built/admin/index.html +17 -22
  245. package/core/frontend/apps/private-blogging/lib/views/private.hbs +44 -43
  246. package/core/frontend/helpers/cancel_link.js +2 -2
  247. package/core/frontend/helpers/pagination.js +2 -2
  248. package/core/frontend/helpers/social_accounts.js +2 -2
  249. package/core/frontend/public/ghost.css +1 -1
  250. package/core/frontend/public/ghost.min.css +1 -1
  251. package/core/frontend/services/sitemap/user-map-generator.js +9 -2
  252. package/core/server/api/endpoints/index.js +2 -2
  253. package/core/server/api/endpoints/mail.js +1 -1
  254. package/core/server/api/endpoints/{member-custom-fields.js → member-metafields.js} +27 -28
  255. package/core/server/api/endpoints/members.js +4 -4
  256. package/core/server/api/endpoints/posts.js +3 -3
  257. package/core/server/api/endpoints/utils/serializers/input/members.js +4 -7
  258. package/core/server/api/endpoints/utils/serializers/output/index.js +2 -2
  259. package/core/server/api/endpoints/utils/serializers/output/{member-custom-fields.js → member-metafields.js} +2 -4
  260. package/core/server/api/endpoints/utils/serializers/output/members.js +2 -4
  261. package/core/server/api/endpoints/utils/validators/input/invitations.js +0 -5
  262. package/core/server/api/endpoints/utils/validators/input/pages.js +1 -1
  263. package/core/server/api/endpoints/utils/validators/input/posts.js +1 -1
  264. package/core/server/data/importer/importers/data/data-importer.js +3 -3
  265. package/core/server/data/migrations/utils/migrations.js +1 -1
  266. package/core/server/lib/image/image-size.js +24 -0
  267. package/core/server/lib/package-json/parse.js +2 -2
  268. package/core/server/models/member.js +6 -17
  269. package/core/server/models/post.js +2 -2
  270. package/core/server/models/relations/authors.js +2 -16
  271. package/core/server/models/user.js +9 -2
  272. package/core/server/services/auth/passwordreset.js +13 -0
  273. package/core/server/services/comments/comments-controller.js +30 -9
  274. package/core/server/services/comments/comments-service.js +55 -5
  275. package/core/server/services/content-import/csv/formula.js +12 -0
  276. package/core/server/services/content-import/csv/index.js +6 -1
  277. package/core/server/services/content-import/csv/parse.js +27 -6
  278. package/core/server/services/content-import/csv/serialize.js +13 -0
  279. package/core/server/services/content-import/import/completion-email.js +164 -0
  280. package/core/server/services/content-import/import/errors-file.js +36 -0
  281. package/core/server/services/content-import/import/importer.js +160 -53
  282. package/core/server/services/content-import/import/local-media-url.js +63 -0
  283. package/core/server/services/content-import/import/media.js +254 -0
  284. package/core/server/services/content-import/import/post-data.js +0 -1
  285. package/core/server/services/content-import/import/post-link.js +11 -0
  286. package/core/server/services/content-import/import/post-repository.js +25 -3
  287. package/core/server/services/content-import/import/reader.js +9 -2
  288. package/core/server/services/content-import/import/report.js +34 -0
  289. package/core/server/services/content-import/import/row.js +0 -2
  290. package/core/server/services/content-import/import/schema.js +1 -0
  291. package/core/server/services/content-import/import/staged-file.js +29 -0
  292. package/core/server/services/content-import/import/store.js +25 -2
  293. package/core/server/services/content-import/index.js +52 -6
  294. package/core/server/services/content-import/jobs/content-csv-import-job.js +20 -0
  295. package/core/server/services/custom-redirects/redirect-config-parser.js +2 -2
  296. package/core/server/services/custom-redirects/redirects-service.js +1 -1
  297. package/core/server/services/custom-redirects/validation.js +1 -1
  298. package/core/server/services/email-address/email-address-parser.js +30 -38
  299. package/core/server/services/email-address/email-address-service.js +37 -4
  300. package/core/server/services/email-analytics/index.js +2 -7
  301. package/core/server/services/email-service/batch-sending-service.js +0 -4
  302. package/core/server/services/email-service/email-renderer.js +14 -4
  303. package/core/server/services/email-service/email-service-wrapper.js +0 -9
  304. package/core/server/services/email-service/mailgun-email-provider.js +20 -9
  305. package/core/server/services/gifts/email-templates/gift-delivery.hbs +3 -3
  306. package/core/server/services/gifts/gift-email-service.js +0 -1
  307. package/core/server/services/gifts/index.js +3 -3
  308. package/core/server/services/gifts/jobs/index.js +4 -4
  309. package/core/server/services/indexnow-ping/indexnow-ping-service.js +2 -2
  310. package/core/server/services/invitations/accept.js +4 -2
  311. package/core/server/services/jobs/job-service.js +0 -9
  312. package/core/server/services/jobs-service/register-job-handlers.js +21 -11
  313. package/core/server/services/mail/ghost-mailer.js +4 -4
  314. package/core/server/services/member-welcome-emails/service.js +7 -7
  315. package/core/server/services/members/api.js +1 -0
  316. package/core/server/services/members/import-export/csv/parse.js +0 -7
  317. package/core/server/services/members/import-export/import/completion-email.js +2 -5
  318. package/core/server/services/members/import-export/import/row.js +0 -3
  319. package/core/server/services/members/import-export/index.js +4 -11
  320. package/core/server/services/members/jobs/clean-expired-comped-job.js +7 -0
  321. package/core/server/services/members/jobs/clean-expired-comped-task.js +145 -0
  322. package/core/server/services/members/jobs/index.js +48 -24
  323. package/core/server/services/members/members-api/controllers/member-controller.js +11 -0
  324. package/core/server/services/members/members-api/controllers/router-controller.js +3 -6
  325. package/core/server/services/members/members-api/members-api.js +4 -6
  326. package/core/server/services/members/members-api/repositories/event-repository.js +0 -4
  327. package/core/server/services/members/members-api/services/member-bread-service.js +34 -62
  328. package/core/server/services/members/members-api/services/payments-service.js +1 -1
  329. package/core/server/services/members/service.js +1 -5
  330. package/core/server/services/members-custom-fields/bindings-service.js +2 -1
  331. package/core/server/services/members-custom-fields/codec.js +3 -4
  332. package/core/server/services/members-custom-fields/definitions-service.js +62 -10
  333. package/core/server/services/members-custom-fields/filter.js +75 -93
  334. package/core/server/services/members-custom-fields/key.js +2 -1
  335. package/core/server/services/members-custom-fields/models.js +1 -1
  336. package/core/server/services/members-custom-fields/serializers.js +3 -5
  337. package/core/server/services/members-custom-fields/values-service.js +50 -28
  338. package/core/server/services/mentions/mention-controller.js +23 -21
  339. package/core/server/services/mentions/process-webmention-job.js +16 -0
  340. package/core/server/services/mentions/service.js +8 -4
  341. package/core/server/services/route-settings/validation-errors.js +1 -1
  342. package/core/server/services/route-settings/yaml-parser.js +1 -1
  343. package/core/server/services/settings/settings-service.js +1 -8
  344. package/core/server/services/settings-helpers/settings-helpers.js +4 -6
  345. package/core/server/services/slack-ping/slack-ping-service.js +1 -1
  346. package/core/server/services/staff/staff-service-emails.js +2 -2
  347. package/core/server/services/stripe/config.js +14 -8
  348. package/core/server/services/stripe/service.js +10 -1
  349. package/core/server/services/stripe/services/checkout/completed-session.js +2 -2
  350. package/core/server/services/stripe/services/checkout/session-options.js +18 -15
  351. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +26 -5
  352. package/core/server/services/stripe/stripe-api.js +2 -0
  353. package/core/server/services/stripe/stripe-service.js +20 -10
  354. package/core/server/services/stripe/webhook-controller.js +10 -0
  355. package/core/server/services/tier-checkout-config/codec.js +9 -5
  356. package/core/server/services/tier-checkout-config/models.js +9 -3
  357. package/core/server/services/tier-checkout-config/queries.js +4 -4
  358. package/core/server/services/tier-checkout-config/serializers.js +16 -10
  359. package/core/server/services/tier-checkout-config/service.js +13 -12
  360. package/core/server/services/update-check/index.js +13 -13
  361. package/core/server/services/update-check/jobs/update-check-job.js +7 -0
  362. package/core/server/services/update-check/update-check-service.js +2 -2
  363. package/core/server/web/admin/controller.js +1 -1
  364. package/core/server/web/api/endpoints/admin/routes.js +19 -16
  365. package/core/shared/config/env/config.testing-mysql.json +1 -1
  366. package/core/shared/labs.js +5 -2
  367. package/package.json +24 -23
  368. package/pnpm-lock.yaml +704 -69
  369. package/pnpm-workspace.yaml +8 -5
  370. package/components/tryghost-kg-default-nodes-2.2.0.tgz +0 -0
  371. package/core/built/admin/assets/Combination-5fYEzgbs.js +0 -41
  372. package/core/built/admin/assets/account-migration-DRhAehfw.js +0 -1
  373. package/core/built/admin/assets/activitypub/inbox-BluD2ucT.js +0 -1284
  374. package/core/built/admin/assets/activitypub/label-FHSh6ZAw.js +0 -16
  375. package/core/built/admin/assets/activitypub/layout-Cq4NG0Xr.js +0 -1
  376. package/core/built/admin/assets/activitypub/settings-DQDkr06X.js +0 -315
  377. package/core/built/admin/assets/add-integration-modal-BC0jExDZ.js +0 -1
  378. package/core/built/admin/assets/add-newsletter-modal-DchTkA7l.js +0 -1
  379. package/core/built/admin/assets/add-recommendation-modal-DMvaJZKp.js +0 -1
  380. package/core/built/admin/assets/alert-dialog-QhXzdHfc.js +0 -1
  381. package/core/built/admin/assets/analytics-No1DDY3A.js +0 -1
  382. package/core/built/admin/assets/automations-DpUNUdcZ.js +0 -1
  383. package/core/built/admin/assets/bluesky-sharing-Cjrv_f62.js +0 -1
  384. package/core/built/admin/assets/color-picker-BTY_NdEm.js +0 -3
  385. package/core/built/admin/assets/color-picker-field-CPlkzOH0.js +0 -1
  386. package/core/built/admin/assets/combobox-DrMtv_4P.js +0 -1
  387. package/core/built/admin/assets/command-ZdgYJpoI.js +0 -1
  388. package/core/built/admin/assets/comments-kidj60-h.js +0 -1
  389. package/core/built/admin/assets/confirmation-context-BhbmQQEl.js +0 -1
  390. package/core/built/admin/assets/custom-field-type-option-CsGIZk1J.js +0 -10
  391. package/core/built/admin/assets/custom-integration-modal-BY37O_9X.js +0 -1
  392. package/core/built/admin/assets/design-and-theme-modal-DUSKM4uC.js +0 -22
  393. package/core/built/admin/assets/dialog-B44iRwlc.js +0 -1
  394. package/core/built/admin/assets/dirty-confirm-dialog-BxgQgQ6H.js +0 -1
  395. package/core/built/admin/assets/dist-Cy8tfrp_.js +0 -1
  396. package/core/built/admin/assets/domain-Ca6KhoQt.js +0 -1
  397. package/core/built/admin/assets/dropzone-Bb-5zQTk.js +0 -5
  398. package/core/built/admin/assets/edit-profile-BdupLlus.js +0 -1
  399. package/core/built/admin/assets/editor-BP883Lpn.js +0 -8
  400. package/core/built/admin/assets/explore-FB-CimXw.js +0 -1
  401. package/core/built/admin/assets/feed-C1GxsmI2.js +0 -1
  402. package/core/built/admin/assets/filter-sources-D5n5bFA2.js +0 -1
  403. package/core/built/admin/assets/filters-DJEzkMUK.js +0 -1
  404. package/core/built/admin/assets/filters-MZXvdH5D.js +0 -1
  405. package/core/built/admin/assets/first-promoter-modal-CGTGLCr5.js +0 -1
  406. package/core/built/admin/assets/gift-link-modal-B6v0tnRN.js +0 -1
  407. package/core/built/admin/assets/growth-BGAJD1rs.js +0 -3
  408. package/core/built/admin/assets/growth-D6mhcx3q.js +0 -1
  409. package/core/built/admin/assets/history-modal-iKwUY3XR.js +0 -1
  410. package/core/built/admin/assets/index-DjQe-e5s.js +0 -4
  411. package/core/built/admin/assets/index-Dqf2x-ry.css +0 -1
  412. package/core/built/admin/assets/index.es-VihPwfeH.js +0 -1
  413. package/core/built/admin/assets/input--W-rCTrX.js +0 -1
  414. package/core/built/admin/assets/invite-user-modal-MdImLCaJ.js +0 -1
  415. package/core/built/admin/assets/koenig-loader-DYZpm6El.js +0 -2
  416. package/core/built/admin/assets/label-picker-cB51xl1r.js +0 -1
  417. package/core/built/admin/assets/layout-c_B_1hax.js +0 -1
  418. package/core/built/admin/assets/loading-indicator-BpFWTVgh.js +0 -1
  419. package/core/built/admin/assets/member-custom-fields-qs0dIkxM.js +0 -1
  420. package/core/built/admin/assets/member-detail-B6uec2a6.js +0 -1
  421. package/core/built/admin/assets/members-BQF3CqhI.js +0 -2
  422. package/core/built/admin/assets/moderation-DY4wF1Q3.js +0 -1
  423. package/core/built/admin/assets/navigation-modal-DX-gNF3X.js +0 -1
  424. package/core/built/admin/assets/newsletter-CyQA7m3m.js +0 -1
  425. package/core/built/admin/assets/newsletter-detail-modal-BDrptuxn.js +0 -1
  426. package/core/built/admin/assets/newsletters-DXuNmC42.js +0 -1
  427. package/core/built/admin/assets/note-Cgo2LCJ6.js +0 -1
  428. package/core/built/admin/assets/notifications-BC50AMku.js +0 -1
  429. package/core/built/admin/assets/offers-container-modal-DWycIMzT.js +0 -1
  430. package/core/built/admin/assets/onboarding-DGQuV7Ew.js +0 -1
  431. package/core/built/admin/assets/overview-CVbFtn-k.js +0 -1
  432. package/core/built/admin/assets/overview-YKj7SZaK.js +0 -1
  433. package/core/built/admin/assets/pages-B6vr9F_D.js +0 -1
  434. package/core/built/admin/assets/popover-CCy0h5rq.js +0 -1
  435. package/core/built/admin/assets/portal-frame-dc4XKr0e.js +0 -1
  436. package/core/built/admin/assets/portal-modal-BvvdF_B9.js +0 -1
  437. package/core/built/admin/assets/post-analytics-DX470WRG.js +0 -1
  438. package/core/built/admin/assets/post-analytics-header-CXpJmdDv.js +0 -1
  439. package/core/built/admin/assets/posts-KY3aUhVV.js +0 -1
  440. package/core/built/admin/assets/preferences-DO0UUWFp.js +0 -5
  441. package/core/built/admin/assets/profile-CxT-0l4d.js +0 -1
  442. package/core/built/admin/assets/radio-group-Bgz60i81.js +0 -1
  443. package/core/built/admin/assets/select-DuDUFdPq.js +0 -1
  444. package/core/built/admin/assets/settings-BjCii7BM.js +0 -1
  445. package/core/built/admin/assets/settings-b8kNzZSQ.js +0 -4
  446. package/core/built/admin/assets/share-modal-BV70mr9k.js +0 -1
  447. package/core/built/admin/assets/shim-ClF_m5ux.js +0 -1
  448. package/core/built/admin/assets/slack-modal-Be2_KK-z.js +0 -1
  449. package/core/built/admin/assets/stats-C4reGh9O.js +0 -1
  450. package/core/built/admin/assets/stats-view-DP9pQ62k.js +0 -1
  451. package/core/built/admin/assets/stripe-connect-modal-Z-UT_BGc.js +0 -1
  452. package/core/built/admin/assets/tag-detail-Dc0nTOtU.js +0 -2
  453. package/core/built/admin/assets/tags-BLaXBwnb.js +0 -1
  454. package/core/built/admin/assets/testimonials-modal-sFwRnNLh.js +0 -1
  455. package/core/built/admin/assets/tier-detail-modal-BCFpWwUS.js +0 -1
  456. package/core/built/admin/assets/use-filter-params-CAvfDvf1.js +0 -1
  457. package/core/built/admin/assets/use-growth-stats-D6mZXGhK.js +0 -1
  458. package/core/built/admin/assets/use-post-referrers-DbY-mzci.js +0 -1
  459. package/core/built/admin/assets/use-reply-chain-data-BeQwz8oF.js +0 -1
  460. package/core/built/admin/assets/use-settings-navigation-BbrSUVcf.js +0 -1
  461. package/core/built/admin/assets/use-tinybird-query-B1IMyAcv.js +0 -39
  462. package/core/built/admin/assets/use-tinybird-token-Bow0Zwn8.js +0 -1
  463. package/core/built/admin/assets/user-detail-modal-yipSoQfv.js +0 -1
  464. package/core/built/admin/assets/users-5msE1WMs.js +0 -1
  465. package/core/built/admin/assets/web-CKcXAu86.js +0 -1
  466. package/core/built/admin/assets/web-DR4TqM3E.js +0 -2
  467. package/core/server/services/jobs/worker-model-event-bridge.js +0 -149
  468. package/core/server/services/members/jobs/clean-expired-comped.js +0 -132
  469. package/core/server/services/stripe/services/checkout/allowed-countries.js +0 -267
  470. package/core/server/services/stripe/services/checkout/field-ports.js +0 -34
  471. package/core/server/services/tier-checkout-config/destinations.js +0 -17
  472. package/core/server/services/update-check/run-update-check.js +0 -63
@@ -0,0 +1,145 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const ObjectId = require('bson-objectid').default;
4
+ const { chunk: chunkArray } = require('lodash');
5
+ const moment = require('moment');
6
+ // Removes expired comped entries from the members_products table, moves the
7
+ // affected members from comped to free and records a comped->free status
8
+ // event for each of them. The three writes commit or roll back together, so a
9
+ // failed run leaves the source rows in place and a repeat run redoes the whole
10
+ // unit of work instead of finishing a half-applied one.
11
+ async function cleanExpiredComped({ db, models, events, logging, sentry, }) {
12
+ const startedAt = Date.now();
13
+ const { deletedSubscriptionCount, updatedMemberCount, updatedMembers, now } = await db.knex.transaction(async (trx) => {
14
+ const expiredCompedRows = await trx('members_products')
15
+ // we need to be careful about the type here. .format() is the only
16
+ // thing that works across SQLite and MySQL: an ISO cutoff compares
17
+ // lexically against SQLite's 'YYYY-MM-DD HH:mm:ss' text and its 'T'
18
+ // sorts above ' ', wrongly matching same-UTC-day expiries
19
+ .where('expiry_at', '<', moment.utc().startOf('day').format('YYYY-MM-DD HH:mm:ss'))
20
+ .select('*');
21
+ if (!expiredCompedRows.length) {
22
+ return {
23
+ deletedSubscriptionCount: 0,
24
+ updatedMemberCount: 0,
25
+ updatedMembers: [],
26
+ now: new Date(),
27
+ };
28
+ }
29
+ const rowIds = expiredCompedRows.map((d) => d.id);
30
+ const memberIds = expiredCompedRows.map((d) => d.member_id);
31
+ // Only members still in the comped status change; anyone already paid,
32
+ // free or deleted is left untouched. The rows are locked for the rest
33
+ // of the transaction so a concurrent status change cannot slip between
34
+ // this read and the update below (forUpdate is a no-op on SQLite,
35
+ // where the single-writer transaction already guarantees this). The
36
+ // members rows are locked BEFORE the members_products delete so this
37
+ // transaction acquires locks in the same members -> members_products
38
+ // order as the member-edit flows (Bookshelf edit, linkSubscription);
39
+ // the reverse order could deadlock against them.
40
+ const compedMembers = await trx('members')
41
+ .whereIn('id', memberIds)
42
+ .andWhere('status', 'comped')
43
+ .forUpdate();
44
+ const deletedCount = await trx('members_products').whereIn('id', rowIds).del();
45
+ const updateMemberIds = compedMembers.map((d) => d.id);
46
+ const updatedAt = new Date();
47
+ const updatedCount = await trx('members').whereIn('id', updateMemberIds).update({
48
+ status: 'free',
49
+ updated_at: updatedAt,
50
+ });
51
+ const statusEvents = compedMembers.map((member) => {
52
+ return {
53
+ id: ObjectId().toHexString(),
54
+ member_id: member.id,
55
+ from_status: member.status,
56
+ to_status: 'free',
57
+ created_at: updatedAt,
58
+ };
59
+ });
60
+ // SQLite >= 3.32.0 can support 32766 host parameters
61
+ // each row uses 5 variables so ⌊32766/5⌋ = 6553
62
+ const chunkSize = 6553;
63
+ for (const chunk of chunkArray(statusEvents, chunkSize)) {
64
+ await trx('members_status_events').insert(chunk);
65
+ }
66
+ return {
67
+ deletedSubscriptionCount: deletedCount,
68
+ updatedMemberCount: updatedCount,
69
+ updatedMembers: compedMembers,
70
+ now: updatedAt,
71
+ };
72
+ });
73
+ // Emitted after the commit so listeners never observe state that could
74
+ // still roll back. A failed emission is logged and skipped rather than
75
+ // failing the job: the database changes are already durable and the
76
+ // emitted_event_count below makes the shortfall visible.
77
+ let emittedEventCount = 0;
78
+ for (const member of updatedMembers) {
79
+ const emitted = await emitMemberEditedEvent({ member, now, models, events, logging, sentry });
80
+ if (emitted) {
81
+ emittedEventCount += 1;
82
+ }
83
+ }
84
+ logging.info({
85
+ system: {
86
+ event: 'clean_expired_comped.completed',
87
+ deleted_subscription_count: deletedSubscriptionCount,
88
+ updated_member_count: updatedMemberCount,
89
+ emitted_event_count: emittedEventCount,
90
+ duration_ms: Date.now() - startedAt,
91
+ },
92
+ }, `[Background Job] clean-expired-comped removed ${deletedSubscriptionCount} expired subscriptions and updated ${updatedMemberCount} members`);
93
+ return { deletedSubscriptionCount, updatedMemberCount, emittedEventCount };
94
+ }
95
+ // Emits member.edited in the shape its listeners (webhooks in particular)
96
+ // expect: a freshly loaded model whose _previousAttributes carry the
97
+ // pre-cleanup status and _changed the comped->free transition, with an
98
+ // internal context.
99
+ async function emitMemberEditedEvent({ member, now, models, events, logging, sentry, }) {
100
+ try {
101
+ const model = await models.Member.findOne({ id: member.id }, { require: true, context: { internal: true } });
102
+ model._previousAttributes = normalizeDates({
103
+ ...model.attributes,
104
+ status: member.status,
105
+ updated_at: member.updated_at,
106
+ });
107
+ model._changed = normalizeDates({
108
+ status: 'free',
109
+ updated_at: now,
110
+ });
111
+ events.emit('member.edited', model, { context: { internal: true } });
112
+ return true;
113
+ }
114
+ catch (err) {
115
+ if (isNotFoundError(err)) {
116
+ logging.warn(`Could not emit member.edited for expired comped member: Member ${member.id} was not found`);
117
+ return false;
118
+ }
119
+ logging.error(err);
120
+ sentry.captureException(err);
121
+ return false;
122
+ }
123
+ }
124
+ function normalizeDates(attributes) {
125
+ const normalized = { ...attributes };
126
+ for (const [key, value] of Object.entries(normalized)) {
127
+ if (key.endsWith('_at') && value && !(value instanceof Date)) {
128
+ normalized[key] = moment.utc(value).toDate();
129
+ }
130
+ }
131
+ return normalized;
132
+ }
133
+ function isNotFoundError(err) {
134
+ if (!err || typeof err !== 'object') {
135
+ return false;
136
+ }
137
+ const errorType = 'errorType' in err ? err.errorType : undefined;
138
+ const name = 'name' in err ? err.name : undefined;
139
+ const message = 'message' in err ? err.message : undefined;
140
+ return (errorType === 'NotFoundError' ||
141
+ name === 'NotFoundError' ||
142
+ message === 'NotFound' ||
143
+ message === 'EmptyResponse');
144
+ }
145
+ exports.default = cleanExpiredComped;
@@ -1,13 +1,17 @@
1
- const path = require('path');
2
1
  const logging = require('@tryghost/logging');
3
- const jobsService = require('../../jobs');
2
+ const errors = require('@tryghost/errors');
4
3
  const CleanTokensJob = require('./clean-tokens-job').default;
4
+ const CleanExpiredCompedJob = require('./clean-expired-comped-job').default;
5
+ const cleanTokensTask = require('./clean-tokens-task').default;
6
+ const cleanExpiredCompedTask = require('./clean-expired-comped-task').default;
5
7
 
6
8
  let hasScheduled = {
7
9
  expiredComped: false,
8
10
  tokens: false,
9
11
  };
10
12
 
13
+ let tasks;
14
+
11
15
  function alreadyScheduledOrTest(key) {
12
16
  return hasScheduled[key] || process.env.NODE_ENV.startsWith('test');
13
17
  }
@@ -19,43 +23,63 @@ function randomDailyCron(maxHour = 24) {
19
23
  return `${s} ${m} ${h} * * *`;
20
24
  }
21
25
 
22
- function scheduleJob(key, name, jobFile, maxHour = 6) {
23
- if (alreadyScheduledOrTest(key)) {
24
- return hasScheduled[key];
26
+ function getTasks() {
27
+ if (!tasks) {
28
+ throw new errors.IncorrectUsageError({
29
+ message: 'Member jobs used before init(). Call init() from boot first.',
30
+ });
25
31
  }
32
+ return tasks;
33
+ }
26
34
 
27
- const s = Math.floor(Math.random() * 60);
28
- const m = Math.floor(Math.random() * 60);
29
- const h = Math.floor(Math.random() * maxHour);
35
+ module.exports = {
36
+ // Composition root for the member cleanup tasks. Idempotent because tests
37
+ // may boot more than once per process.
38
+ init() {
39
+ if (tasks) {
40
+ return;
41
+ }
30
42
 
31
- const at = `${s} ${m} ${h} * * *`;
43
+ const db = require('../../../data/db');
44
+ const models = require('../../../models');
45
+ const events = require('../../../lib/common/events');
46
+ const sentry = require('../../../../shared/sentry');
32
47
 
33
- logging.info(`[Background Job] ${name} scheduled at ${at}`);
34
- jobsService.addJob({
35
- at,
36
- job: path.resolve(__dirname, jobFile),
37
- name,
38
- });
48
+ tasks = {
49
+ cleanTokens: () => cleanTokensTask({ db, logging }),
50
+ cleanExpiredComped: () => cleanExpiredCompedTask({ db, models, events, logging, sentry }),
51
+ };
52
+ },
39
53
 
40
- hasScheduled[key] = true;
54
+ cleanTokens() {
55
+ return getTasks().cleanTokens();
56
+ },
41
57
 
42
- return true;
43
- }
58
+ cleanExpiredComped() {
59
+ return getTasks().cleanExpiredComped();
60
+ },
44
61
 
45
- module.exports = {
46
- async scheduleExpiredCompCleanupJob() {
47
- return scheduleJob('expiredComped', 'clean-expired-comped', 'clean-expired-comped.js');
62
+ async scheduleExpiredCompCleanupJob(jobsService) {
63
+ if (alreadyScheduledOrTest('expiredComped')) {
64
+ return;
65
+ }
66
+
67
+ // Keep the legacy off-peak window: a random time between 00:00 and 05:59
68
+ const cron = randomDailyCron(6);
69
+ logging.info(`[Background Job] clean-expired-comped scheduled at ${cron}`);
70
+ await jobsService.scheduleRecurring(new CleanExpiredCompedJob(), { cron });
71
+
72
+ hasScheduled.expiredComped = true;
48
73
  },
49
74
 
50
- async scheduleTokenCleanupJob() {
75
+ async scheduleTokenCleanupJob(jobsService) {
51
76
  if (alreadyScheduledOrTest('tokens')) {
52
77
  return;
53
78
  }
54
79
 
55
- const classBasedJobs = require('../../jobs-service').getInstance();
56
80
  const cron = randomDailyCron();
57
81
  logging.info(`[Background Job] clean-tokens scheduled at ${cron}`);
58
- await classBasedJobs.scheduleRecurring(new CleanTokensJob(), { cron });
82
+ await jobsService.scheduleRecurring(new CleanTokensJob(), { cron });
59
83
 
60
84
  hasScheduled.tokens = true;
61
85
  },
@@ -146,6 +146,17 @@ module.exports = class MemberController {
146
146
 
147
147
  if (tierId && cadence) {
148
148
  const tier = await this._tiersService.api.read(tierId);
149
+
150
+ if (!tier) {
151
+ res.writeHead(404);
152
+ return res.end('Not Found.');
153
+ }
154
+
155
+ if (tier.status !== 'active') {
156
+ res.writeHead(403);
157
+ return res.end('Tier is archived.');
158
+ }
159
+
149
160
  const stripePrice = await this._paymentsService.getPriceForTierCadence(tier, cadence);
150
161
 
151
162
  await this._memberRepository.updateSubscription({
@@ -140,7 +140,6 @@ module.exports = class RouterController {
140
140
  * @param {any} deps.offersAPI
141
141
  * @param {any} deps.paymentsService
142
142
  * @param {any} deps.memberRepository
143
- * @param {any} deps.StripePrice
144
143
  * @param {() => boolean} deps.allowSelfSignup
145
144
  * @param {any} deps.magicLinkService
146
145
  * @param {import('../../../stripe/stripe-api')} deps.stripeAPIService
@@ -160,7 +159,6 @@ module.exports = class RouterController {
160
159
  paymentsService,
161
160
  tiersService,
162
161
  memberRepository,
163
- StripePrice,
164
162
  allowSelfSignup,
165
163
  magicLinkService,
166
164
  stripeAPIService,
@@ -180,7 +178,6 @@ module.exports = class RouterController {
180
178
  this._paymentsService = paymentsService;
181
179
  this._tiersService = tiersService;
182
180
  this._memberRepository = memberRepository;
183
- this._StripePrice = StripePrice;
184
181
  this._allowSelfSignup = allowSelfSignup;
185
182
  this._magicLinkService = magicLinkService;
186
183
  this._stripeAPIService = stripeAPIService;
@@ -602,11 +599,11 @@ module.exports = class RouterController {
602
599
  if (member) {
603
600
  options.successUrl = this._generateSuccessUrl(options.successUrl, tier.welcomePageURL);
604
601
 
605
- const restrictCheckout = member.get('status') === 'paid';
602
+ // Existing paid members must manage their subscription instead of creating another.
603
+ // Other members must prove ownership of the provided email before checking out.
604
+ const restrictCheckout = member.get('status') === 'paid' || !options.isAuthenticated;
606
605
 
607
606
  if (restrictCheckout) {
608
- // This member is already subscribed to a paid tier
609
- // We don't want to create a duplicate subscription
610
607
  if (!options.isAuthenticated && options.email) {
611
608
  try {
612
609
  await this._sendEmailWithMagicLink({ email: options.email, requestedType: 'signin' });
@@ -41,7 +41,6 @@ module.exports = function MembersAPI({
41
41
  MemberProductEvent,
42
42
  MemberEmailChangeEvent,
43
43
  MemberCreatedEvent,
44
- SubscriptionCreatedEvent,
45
44
  MemberLinkClickEvent,
46
45
  EmailSpamComplaintEvent,
47
46
  Offer,
@@ -72,6 +71,7 @@ module.exports = function MembersAPI({
72
71
  emailAddressService,
73
72
  giftService,
74
73
  customFieldValues,
74
+ customFieldDefinitions,
75
75
  }) {
76
76
  const tokenService = new TokenService({
77
77
  privateKey,
@@ -119,7 +119,6 @@ module.exports = function MembersAPI({
119
119
  MemberStatusEvent,
120
120
  MemberLoginEvent,
121
121
  MemberCreatedEvent,
122
- SubscriptionCreatedEvent,
123
122
  MemberLinkClickEvent,
124
123
  MemberFeedback,
125
124
  EmailSpamComplaintEvent,
@@ -147,7 +146,6 @@ module.exports = function MembersAPI({
147
146
  });
148
147
  },
149
148
  },
150
- labsService,
151
149
  stripeService: stripeAPIService,
152
150
  memberAttributionService,
153
151
  emailSuppressionList,
@@ -156,6 +154,7 @@ module.exports = function MembersAPI({
156
154
  commentsService,
157
155
  giftService,
158
156
  customFieldValues,
157
+ customFieldDefinitions,
159
158
  });
160
159
 
161
160
  const geolocationService = new GeolocationService();
@@ -202,7 +201,6 @@ module.exports = function MembersAPI({
202
201
  paymentsService,
203
202
  tiersService,
204
203
  memberRepository,
205
- StripePrice,
206
204
  allowSelfSignup,
207
205
  magicLinkService,
208
206
  stripeAPIService,
@@ -358,11 +356,11 @@ module.exports = function MembersAPI({
358
356
  }
359
357
 
360
358
  async function getMemberIdentityData(email) {
361
- return memberBREADService.read({ email });
359
+ return memberBREADService.read({ email }, { withCustomFields: false });
362
360
  }
363
361
 
364
362
  async function getMemberIdentityDataFromTransientId(transientId) {
365
- return memberBREADService.read({ transient_id: transientId });
363
+ return memberBREADService.read({ transient_id: transientId }, { withCustomFields: false });
366
364
  }
367
365
 
368
366
  async function cycleTransientId(memberId) {
@@ -34,13 +34,11 @@ module.exports = class EventRepository {
34
34
  MemberStatusEvent,
35
35
  MemberLoginEvent,
36
36
  MemberCreatedEvent,
37
- SubscriptionCreatedEvent,
38
37
  MemberPaidSubscriptionEvent,
39
38
  MemberLinkClickEvent,
40
39
  MemberFeedback,
41
40
  EmailSpamComplaintEvent,
42
41
  Comment,
43
- labsService,
44
42
  memberAttributionService,
45
43
  urlService,
46
44
  MemberEmailChangeEvent,
@@ -55,10 +53,8 @@ module.exports = class EventRepository {
55
53
  this._MemberLoginEvent = MemberLoginEvent;
56
54
  this._EmailRecipient = EmailRecipient;
57
55
  this._Comment = Comment;
58
- this._labsService = labsService;
59
56
  this._urlService = urlService;
60
57
  this._MemberCreatedEvent = MemberCreatedEvent;
61
- this._SubscriptionCreatedEvent = SubscriptionCreatedEvent;
62
58
  this._MemberLinkClickEvent = MemberLinkClickEvent;
63
59
  this._MemberFeedback = MemberFeedback;
64
60
  this._EmailSpamComplaintEvent = EmailSpamComplaintEvent;
@@ -16,11 +16,6 @@ const messages = {
16
16
  // Stored in the action's `context.action_name`; Admin maps it to a display label.
17
17
  const CUSTOM_FIELDS_EDITED_ACTION = 'custom_fields_edited';
18
18
 
19
- /**
20
- * @typedef {object} ILabsService
21
- * @prop {(key: string) => boolean} isSet
22
- */
23
-
24
19
  /**
25
20
  * @typedef {object} IEmailService
26
21
  * @prop {(data: {email: string, requestedType: string}) => Promise<any>} sendEmailWithMagicLink
@@ -45,7 +40,6 @@ module.exports = class MemberBREADService {
45
40
  * @param {object} deps
46
41
  * @param {import('../repositories/member-repository')} deps.memberRepository
47
42
  * @param {import('../../../offers/application/offers-api')} deps.offersAPI
48
- * @param {ILabsService} deps.labsService
49
43
  * @param {IEmailService} deps.emailService
50
44
  * @param {IStripeService} deps.stripeService
51
45
  * @param {import('../../../member-attribution/member-attribution-service')} deps.memberAttributionService
@@ -54,10 +48,10 @@ module.exports = class MemberBREADService {
54
48
  * @param {import('./next-payment-calculator')} deps.nextPaymentCalculator
55
49
  * @param {IGiftsModule} deps.giftService
56
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
57
52
  */
58
53
  constructor({
59
54
  memberRepository,
60
- labsService,
61
55
  emailService,
62
56
  stripeService,
63
57
  offersAPI,
@@ -68,13 +62,12 @@ module.exports = class MemberBREADService {
68
62
  commentsService,
69
63
  giftService,
70
64
  customFieldValues,
65
+ customFieldDefinitions,
71
66
  }) {
72
67
  this.offersAPI = offersAPI;
73
68
  /** @private */
74
69
  this.memberRepository = memberRepository;
75
70
  /** @private */
76
- this.labsService = labsService;
77
- /** @private */
78
71
  this.emailService = emailService;
79
72
  /** @private */
80
73
  this.stripeService = stripeService;
@@ -92,31 +85,25 @@ module.exports = class MemberBREADService {
92
85
  this.giftService = giftService;
93
86
  /** @private */
94
87
  this.customFieldValues = customFieldValues;
88
+ /** @private */
89
+ this.customFieldDefinitions = customFieldDefinitions;
95
90
  }
96
91
 
97
92
  /**
98
- * @private
99
- * Custom field values keyed by member id, or `null` when the feature is off
100
- * the flag lives here, so callers just check the result: `null` means omit the
101
- * `custom_fields` key entirely, keeping a pre-feature member payload identical.
102
- *
103
- * A read gets values on the flag alone, with no opt-in include: a member's
104
- * custom fields are their own data, like labels and tiers, which a read
105
- * already returns unasked. `include` is for things that are expensive or
106
- * aggregate (email_recipients, counts), and one flat lookup on a read that
107
- * already issues a dozen queries is neither. The flag, not an include, is what
108
- * protects consumers that predate the feature — an include would outlive it
109
- * and become permanent API surface.
93
+ * Metafields are extra fields a publisher can define on member records, such as a shoe
94
+ * size or a delivery address. Their values live in their own table, so they are fetched
95
+ * here rather than loaded alongside the member.
110
96
  *
111
- * Browse is the opposite opt-in via `include=custom_fields`, exactly how
112
- * `products`/`tiers` already behave: a read always carries them, a list only
113
- * when asked. Read and browse must stay format-identical, so a browse that
114
- * asks gets the same key a read gives unasked.
97
+ * Returns null when the site has defined no fields, which tells the caller to leave the
98
+ * `metafields` key off the member payload rather than send an empty object: a key added to
99
+ * an API response cannot be withdrawn without breaking whoever started reading it, and
100
+ * most sites have never defined a field, so those sites keep the payload they had before
101
+ * this feature existed.
115
102
  * @param {string[]} memberIds
116
103
  * @returns {Promise<Map<string, Record<string, unknown>> | null>}
117
104
  */
118
105
  async fetchCustomFieldValues(memberIds) {
119
- if (!this.labsService.isSet('membersCustomFields')) {
106
+ if (!(await this.customFieldDefinitions.hasAnyActive())) {
120
107
  return null;
121
108
  }
122
109
 
@@ -394,7 +381,16 @@ module.exports = class MemberBREADService {
394
381
  }
395
382
  }
396
383
 
397
- async read(data, options = {}) {
384
+ /**
385
+ * @param {object} data
386
+ * @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.
392
+ */
393
+ async read(data, { withCustomFields = true, ...options } = {}) {
398
394
  const defaultWithRelated = [
399
395
  'labels',
400
396
  'stripeSubscriptions',
@@ -455,43 +451,25 @@ module.exports = class MemberBREADService {
455
451
  const unsubscribeUrl = this.settingsHelpers.createUnsubscribeUrl(member.uuid);
456
452
  member.unsubscribe_url = unsubscribeUrl;
457
453
 
458
- const customFields = await this.fetchCustomFieldValues([member.id]);
459
- if (customFields) {
460
- member.custom_fields = customFields.get(member.id) ?? {};
454
+ if (withCustomFields) {
455
+ const customFields = await this.fetchCustomFieldValues([member.id]);
456
+ if (customFields) {
457
+ member.metafields = customFields.get(member.id) ?? {};
458
+ }
461
459
  }
462
460
 
463
461
  return member;
464
462
  }
465
463
 
466
- /**
467
- * @private
468
- * The write-side flag gate, paired with `fetchCustomFieldValues` on the read
469
- * side. The schema declares `custom_fields` for every site, so the key arrives
470
- * whether or not the feature is on and this is what decides it goes no further.
471
- * @param {object} data
472
- */
473
- dropCustomFieldsWhenDisabled(data) {
474
- if (!this.labsService.isSet('membersCustomFields')) {
475
- delete data.custom_fields;
476
- }
477
- }
478
-
479
464
  async add(data, options) {
480
- this.dropCustomFieldsWhenDisabled(data);
481
-
482
- // Values cannot be set on create, only on a subsequent edit. `namesValues`
483
- // both judges the body and rejects a malformed one, so a body refused here is
484
- // refused on edit for the same reason.
485
- if (this.customFieldValues.namesValues(data.custom_fields)) {
465
+ if (this.customFieldValues.namesValues(this.customFieldValues.unwrapWire(data.metafields))) {
486
466
  throw new errors.ValidationError({
487
467
  message: tpl(messages.customFieldsOnAdd),
488
- property: 'custom_fields',
468
+ property: 'metafields',
489
469
  });
490
470
  }
491
471
 
492
- // Not a member column, so it comes off before the repository sees it. Only
493
- // an absent key or one naming no values gets this far.
494
- delete data.custom_fields;
472
+ delete data.metafields;
495
473
 
496
474
  if (!this.stripeService.configured && (data.comped || data.stripe_customer_id)) {
497
475
  const property = data.comped ? 'comped' : 'stripe_customer_id';
@@ -587,15 +565,9 @@ module.exports = class MemberBREADService {
587
565
  async edit(data, options) {
588
566
  delete data.last_seen_at;
589
567
 
590
- this.dropCustomFieldsWhenDisabled(data);
591
-
592
- // Values live in their own table, so they come off the member data before
593
- // the repository sees it — `custom_fields` is not a member column. The gate
594
- // above has already dropped it when the feature is off, so by this point its
595
- // presence is the signal to write.
596
- const customFields = data.custom_fields;
568
+ const customFields = this.customFieldValues.unwrapWire(data.metafields);
597
569
  const writeCustomFields = customFields !== undefined;
598
- delete data.custom_fields;
570
+ delete data.metafields;
599
571
 
600
572
  // Plan (which validates) before the member is touched, so a bad value 422s
601
573
  // here rather than after the member edit has been applied — and keep the
@@ -833,7 +805,7 @@ module.exports = class MemberBREADService {
833
805
  delete member.products;
834
806
  }
835
807
  if (customFieldsByMember) {
836
- member.custom_fields = customFieldsByMember.get(model.id) ?? {};
808
+ member.metafields = customFieldsByMember.get(model.id) ?? {};
837
809
  }
838
810
  member.email_suppression = {
839
811
  suppressed: bulkSuppressionData[index].suppressed || !!model.get('email_disabled'),
@@ -161,7 +161,7 @@ class PaymentsService {
161
161
  */
162
162
  async getCheckoutConfigForTier(tier) {
163
163
  const checkoutConfig = this.tiersService?.checkout;
164
- if (!this.labsService?.isSet('membersCustomFields') || !checkoutConfig) {
164
+ if (!this.labsService?.isSet('stripeCheckoutCollection') || !checkoutConfig) {
165
165
  return undefined;
166
166
  }
167
167
  try {
@@ -7,7 +7,6 @@ const MembersConfigProvider = require('./members-config-provider');
7
7
  const { makeImporter, makeExporter } = require('./import-export');
8
8
  const { resolveInlineThreshold } = require('./import-export/config');
9
9
  const MembersStats = require('./stats/members-stats');
10
- const memberJobs = require('./jobs');
11
10
  const logging = require('@tryghost/logging');
12
11
  const urlUtils = require('../../../shared/url-utils').default;
13
12
  const settingsCache = require('../../../shared/settings-cache');
@@ -29,7 +28,7 @@ const messages = {
29
28
  sslRequiredForStripe:
30
29
  'Cannot run Ghost without SSL when Stripe is connected. Please update your url config to use "https://".',
31
30
  remoteWebhooksInDevelopment:
32
- 'Cannot use remote webhooks in development. See https://ghost.org/docs/webhooks/#stripe-webhooks for developing with Stripe.',
31
+ 'Cannot use remote webhooks in development. See https://docs.ghost.org/webhooks/#stripe-webhooks for developing with Stripe.',
33
32
  };
34
33
 
35
34
  const ghostMailer = new GhostMailer();
@@ -216,9 +215,6 @@ module.exports = {
216
215
  );
217
216
  }
218
217
  }
219
-
220
- // Schedule daily cron job to clean expired comp subs
221
- memberJobs.scheduleExpiredCompCleanupJob();
222
218
  },
223
219
  contentGating: require('./content-gating'),
224
220
 
@@ -8,6 +8,7 @@ const bson_objectid_1 = __importDefault(require("bson-objectid"));
8
8
  const logging_1 = __importDefault(require("@tryghost/logging"));
9
9
  const schema_1 = require("./schema");
10
10
  const FIELDS_TABLE = 'members_custom_fields';
11
+ const { CUSTOM_NAMESPACE } = require('@tryghost/custom-field-types/identity');
11
12
  const BINDINGS_TABLE = 'members_custom_field_bindings';
12
13
  /**
13
14
  * Where a source sends what it collected: a `port` is the name that source uses for a
@@ -60,7 +61,7 @@ class CustomFieldBindingsService {
60
61
  async writeOne(memberId, into, value) {
61
62
  let planned;
62
63
  try {
63
- planned = await this.values.planWrite({ [into.key]: value });
64
+ planned = await this.values.planWrite({ [`${CUSTOM_NAMESPACE}.${into.key}`]: value });
64
65
  }
65
66
  catch (err) {
66
67
  logging_1.default.warn({
@@ -2,14 +2,13 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.customFieldCodec = void 0;
4
4
  const zod_1 = require("zod");
5
+ const identity_1 = require("@tryghost/custom-field-types/identity");
5
6
  const case_keys_1 = require("../../lib/case-keys");
6
7
  const schema_1 = require("./schema");
7
8
  const models_1 = require("./models");
8
- // Maps a members_custom_fields row to/from the domain CustomField (snake_case to
9
- // camelCase; DbDate decoding happens in DbCustomField).
10
9
  exports.customFieldCodec = zod_1.z.codec(schema_1.DbCustomField, models_1.CustomField, {
11
10
  // DbCustomField validates `type` as the field-type enum, so the decoded row
12
11
  // already carries a FieldType and camelKeys preserves it — no cast needed.
13
- decode: (row) => (0, case_keys_1.camelKeys)(row),
14
- encode: (field) => (0, case_keys_1.snakeKeys)(field),
12
+ decode: (row) => ({ ...(0, case_keys_1.camelKeys)(row), namespace: identity_1.CUSTOM_NAMESPACE }),
13
+ encode: ({ namespace: _namespace, ...field }) => (0, case_keys_1.snakeKeys)(field),
15
14
  });