ghost 6.54.0 → 6.55.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 (327) 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-scheduling-0.2.0.tgz +0 -0
  4. package/components/tryghost-adapter-base-sso-0.1.1.tgz +0 -0
  5. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  6. package/components/tryghost-kg-default-cards-10.3.3.tgz +0 -0
  7. package/core/boot.js +3 -6
  8. package/core/built/admin/assets/{account-migration-DLvVA5yq.js → account-migration-sjHLx5dy.js} +1 -1
  9. package/core/built/admin/assets/activitypub/{account-migration-DHbZcxGM.js → account-migration-DoYXtnZ8.js} +4 -4
  10. package/core/built/admin/assets/activitypub/activitypub.js +2 -2
  11. package/core/built/admin/assets/activitypub/{bluesky-sharing-zfxUey_L.js → bluesky-sharing-Bxeh6MKW.js} +4 -4
  12. package/core/built/admin/assets/activitypub/{dist-BqkDUQxJ2.js → dist-ByFmDSNP2.js} +64 -63
  13. package/core/built/admin/assets/activitypub/{dist-Ba_QxHHl.js → dist-D8sFfdpC.js} +2 -2
  14. package/core/built/admin/assets/activitypub/{domain-CmwSZLHK.js → domain-CWueFW6q.js} +5 -5
  15. package/core/built/admin/assets/activitypub/{edit-profile-CXdwNmVG.js → edit-profile-BGxPU7N5.js} +3 -3
  16. package/core/built/admin/assets/activitypub/{explore-DfbjzGM1.js → explore-DBK-u0Ch.js} +3 -3
  17. package/core/built/admin/assets/activitypub/{feed-DSEBJOqf.js → feed-Bcir5RQZ.js} +3 -3
  18. package/core/built/admin/assets/activitypub/{field-CY7XVcx6.js → field-BV-CIT3d.js} +2 -2
  19. package/core/built/admin/assets/activitypub/{inbox-DtQ0kytT.js → inbox-yC0nfvzl.js} +4 -4
  20. package/core/built/admin/assets/activitypub/{label-Dw96GQ7u.js → label-DTRS9TDk.js} +2 -2
  21. package/core/built/admin/assets/activitypub/layout-qx-bUL1k.js +1 -0
  22. package/core/built/admin/assets/activitypub/{moderation-Dg_-I-8C.js → moderation-Dwl9t0pb.js} +4 -4
  23. package/core/built/admin/assets/activitypub/{note-Cy1-uKMY.js → note-B-zKsZ7r.js} +3 -3
  24. package/core/built/admin/assets/activitypub/{notifications-B_fvoLIM.js → notifications-DjJZD54t.js} +3 -3
  25. package/core/built/admin/assets/activitypub/{preferences-ByJseZp6.js → preferences-CoU8vs6d.js} +6 -6
  26. package/core/built/admin/assets/activitypub/{profile-BFtbnIjz.js → profile-BAsaJekx.js} +6 -6
  27. package/core/built/admin/assets/activitypub/{routes-Dh3xXfce.js → routes-CLH3OVTP.js} +28 -25
  28. package/core/built/admin/assets/activitypub/{settings-DTgqJPxM.js → settings-DA6pNJ-Q.js} +3 -3
  29. package/core/built/admin/assets/activitypub/{site-CdL9ZLqm.js → site-CQD42Pw5.js} +2 -2
  30. package/core/built/admin/assets/activitypub/{tabs-ogqwoBlb.js → tabs-Cg0saow1.js} +7 -7
  31. package/core/built/admin/assets/activitypub/{use-reply-chain-data-CxonteFD.js → use-reply-chain-data-Br4aOWOh.js} +2 -2
  32. package/core/built/admin/assets/audience-DjEWNhp3.js +1 -0
  33. package/core/built/admin/assets/{automated-emails-Cn87L5Md.js → automated-emails-DRy7qY-J.js} +2 -2
  34. package/core/built/admin/assets/{automations-BP_aHamJ.js → automations-Bx3VcFIL.js} +1 -1
  35. package/core/built/admin/assets/{automations-B7-y3dXt.js → automations-DhKeS_U5.js} +1 -1
  36. package/core/built/admin/assets/{bluesky-sharing-Bh36XPh4.js → bluesky-sharing-ClMd0-Ft.js} +1 -1
  37. package/core/built/admin/assets/{breadcrumb-D34YWnaw.js → breadcrumb-BD0U2ygg.js} +1 -1
  38. package/core/built/admin/assets/{chart-helpers-DbklaJw9.js → chart-helpers-fjQKRVyY.js} +1 -1
  39. package/core/built/admin/assets/checkbox-CWa28lgO.js +1 -0
  40. package/core/built/admin/assets/{chunk.962.b73442b07be930bb5d85.js → chunk.211.6f7328d63c41536451b3.js} +737 -559
  41. package/core/built/admin/assets/{chunk.962.b73442b07be930bb5d85.js.LICENSE.txt → chunk.211.6f7328d63c41536451b3.js.LICENSE.txt} +2 -0
  42. package/core/built/admin/assets/{chunk.589.096f4d0bb775e5229aa9.js → chunk.481.6c7395a49484bb4583aa.js} +41 -38
  43. package/core/built/admin/assets/chunk.524.cffd399783a519e404a9.js +25 -0
  44. package/core/built/admin/assets/chunk.582.eebf0d0b2d202bb746a5.js +27 -0
  45. package/core/built/admin/assets/code-editor-view-CCr0rBWS.js +1 -0
  46. package/core/built/admin/assets/comments-DyUSMUdj.js +1 -0
  47. package/core/built/admin/assets/{date-range-select-B5vSsrpx.js → date-range-select-BFp58EMk.js} +1 -1
  48. package/core/built/admin/assets/{disabled-sources-indicator-NkOIoMue.js → disabled-sources-indicator-CUxMfiPa.js} +1 -1
  49. package/core/built/admin/assets/dist-2Tnz2Yfq.js +1 -0
  50. package/core/built/admin/assets/dist-CjY-f5Z1.js +1 -0
  51. package/core/built/admin/assets/dist-DIC8yz5C.js +1 -0
  52. package/core/built/admin/assets/domain-C0bV2UV8.js +1 -0
  53. package/core/built/admin/assets/dropzone-DlK-K2DQ.js +5 -0
  54. package/core/built/admin/assets/{edit-profile-rvDGrS8J.js → edit-profile-BKT-REJw.js} +1 -1
  55. package/core/built/admin/assets/editor-Dp865O-4.js +8 -0
  56. package/core/built/admin/assets/{explore-BZAZOg5X.js → explore-ClUpJZOp.js} +1 -1
  57. package/core/built/admin/assets/{feed-CFV8xDw9.js → feed-DF92dUMP.js} +1 -1
  58. package/core/built/admin/assets/field-Do7lieBA.js +1 -0
  59. package/core/built/admin/assets/{filter-sources-Fx8wiYYw.js → filter-sources-BV2rnpfA.js} +1 -1
  60. package/core/built/admin/assets/filters-CUXJ2Nm9.js +1 -0
  61. package/core/built/admin/assets/{filters-CGX_pf2N.js → filters-DfbIWc_l.js} +1 -1
  62. package/core/built/admin/assets/{ghost-3063eae29e6c9cc6d6e7131928a27da0.css → ghost-a9c788f6f0b1ea6cf0f14ca4b13f4496.css} +1 -1
  63. package/core/built/admin/assets/{ghost-b1dce91ad3232cc259ee5fbbf9526243.js → ghost-c4057474943effc568c5659ccc4909d7.js} +407 -406
  64. package/core/built/admin/assets/{ghost-dark-c8d0ab7f515b2010c173e23e9b32ea8a.css → ghost-dark-db36d795d21c61426837df40af270966.css} +1 -1
  65. package/core/built/admin/assets/gift-link-modal-f9VIMNDv.js +1 -0
  66. package/core/built/admin/assets/{gift-links-nBxJjZiQ.js → gift-links-CsUhefWG.js} +1 -1
  67. package/core/built/admin/assets/growth-B3I6gNLm.js +1 -0
  68. package/core/built/admin/assets/{Growth-CSaHoSVD.js → growth-CVw1My4S.js} +2 -2
  69. package/core/built/admin/assets/{hooks-BApwwBE0.js → hooks-CixO01lL.js} +1 -1
  70. package/core/built/admin/assets/{inbox-BRp1_aaC.js → inbox-CFBSu4Ed.js} +12 -12
  71. package/core/built/admin/assets/index-9Cw8z3rA.css +1 -0
  72. package/core/built/admin/assets/index-BGG7NckO.js +67 -0
  73. package/core/built/admin/assets/input-group-BSgG_lVw.js +1 -0
  74. package/core/built/admin/assets/input-surface-BpAjZXY5.js +1 -0
  75. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +974 -937
  76. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +28 -28
  77. package/core/built/admin/assets/koenig-lexical/style.css +1 -1
  78. package/core/built/admin/assets/{koenig-lexical-CM7UFNdL.js → koenig-lexical-f8vHRYvz.js} +28 -28
  79. package/core/built/admin/assets/{kpi-card-BrGH6FsT.js → kpi-card-DSap9rAy.js} +1 -1
  80. package/core/built/admin/assets/{kpi-card-CIGyijjx.js → kpi-card-DU0rn9Cj.js} +1 -1
  81. package/core/built/admin/assets/{kpi-helpers-_HRRsklE.js → kpi-helpers-Dz33AV6h.js} +1 -1
  82. package/core/built/admin/assets/{kpi-tabs-BSvh9eoq.js → kpi-tabs-BtTTrKvJ.js} +1 -1
  83. package/core/built/admin/assets/{label-BOR9XdKl.js → label-DS8Mi6G5.js} +1 -1
  84. package/core/built/admin/assets/label-picker-1DvSuLA1.js +1 -0
  85. package/core/built/admin/assets/labels-CDSUt4J_.js +1 -0
  86. package/core/built/admin/assets/layout-yQEi2qDM.js +1 -0
  87. package/core/built/admin/assets/{link-helpers-tOlxmuH5.js → link-helpers-7EYqvvHs.js} +1 -1
  88. package/core/built/admin/assets/member-custom-fields-yOMHyAg9.js +1 -0
  89. package/core/built/admin/assets/member-detail-BAq7gDaa.js +1 -0
  90. package/core/built/admin/assets/members-CQpVFJu4.js +10 -0
  91. package/core/built/admin/assets/modals-ReORvWnu.js +31 -0
  92. package/core/built/admin/assets/moderation-DySbgyUS.js +1 -0
  93. package/core/built/admin/assets/newsletter-CJQvtHqN.js +1 -0
  94. package/core/built/admin/assets/{Newsletters-C5cs6FGo.js → newsletters-B3QQXfGT.js} +1 -1
  95. package/core/built/admin/assets/{newsletters-D1jbtV-h.js → newsletters-BXJbJgA-.js} +1 -1
  96. package/core/built/admin/assets/note-C8kxrz6w.js +1 -0
  97. package/core/built/admin/assets/notifications-CgtSfX7t.js +1 -0
  98. package/core/built/admin/assets/{offers-CrY6nSRR.js → offers-DEniVRD5.js} +1 -1
  99. package/core/built/admin/assets/onboarding-route-4qtziA9d.js +1 -0
  100. package/core/built/admin/assets/overview-Bf2sbznq.js +1 -0
  101. package/core/built/admin/assets/overview-CBfePxcr.js +1 -0
  102. package/core/built/admin/assets/{page-header-C0jVxtS8.js → page-header-B3Wb9Jqv.js} +1 -1
  103. package/core/built/admin/assets/{pages-BbNWFcGj.js → pages-B4goRPUn.js} +1 -1
  104. package/core/built/admin/assets/{post-analytics-Iw7dk1mT.js → post-analytics-We-ko5Cb.js} +1 -1
  105. package/core/built/admin/assets/post-analytics-header-Bmc9su6q.js +1 -0
  106. package/core/built/admin/assets/post-analytics-provider-B6UynTGb.js +1 -0
  107. package/core/built/admin/assets/{post-share-modal-DBfKl2GZ.js → post-share-modal-DFfO93_0.js} +1 -1
  108. package/core/built/admin/assets/{posts-EWpZIjkr.js → posts-DhXE7b6r.js} +1 -1
  109. package/core/built/admin/assets/{preferences-D4EANGoc.js → preferences-DJPfHEGu.js} +2 -2
  110. package/core/built/admin/assets/profile-DqBQp-gT.js +1 -0
  111. package/core/built/admin/assets/{referrers-BCfFYSHc.js → referrers-pmJT4k3-.js} +1 -1
  112. package/core/built/admin/assets/select-C5_xEt5b.js +1 -0
  113. package/core/built/admin/assets/settings-BwiVIXau.js +4 -0
  114. package/core/built/admin/assets/{settings-Bz3RZRmE.js → settings-WXNeWyU1.js} +1 -1
  115. package/core/built/admin/assets/shade-app-B3heOAfz-CGXQwWeO.js +3 -0
  116. package/core/built/admin/assets/{share-modal-CkdTTMJd.js → share-modal-DsgW3wbg.js} +1 -1
  117. package/core/built/admin/assets/social-urls-BQPFEk1_.js +8 -0
  118. package/core/built/admin/assets/{sort-button-DZp9Y2r1.js → sort-button-Bb_FCl7e.js} +1 -1
  119. package/core/built/admin/assets/{source-icon-CHr9qJ2Y.js → source-icon-BeaRxUn9.js} +1 -1
  120. package/core/built/admin/assets/{stats-DGoXZxWE.js → stats-Bjj_kmJ1.js} +1 -1
  121. package/core/built/admin/assets/{stats-view-D2-1L0ms.js → stats-view-BxPbW6K1.js} +1 -1
  122. package/core/built/admin/assets/{step-1-RdahbwyG.js → step-1-B1e3-1F8.js} +1 -1
  123. package/core/built/admin/assets/{step-2-D83PeRwd.js → step-2-C5WaeBJo.js} +1 -1
  124. package/core/built/admin/assets/{step-3-p_Op4wv4.js → step-3-B46iVPwu.js} +2 -2
  125. package/core/built/admin/assets/switch-z5Y6Uzt6.js +1 -0
  126. package/core/built/admin/assets/{table-B8LgTQJZ.js → table-CB-4_HBJ.js} +1 -1
  127. package/core/built/admin/assets/tabs-CCEgKYzq.js +1 -0
  128. package/core/built/admin/assets/{tags-B4l891z2.js → tags-Cg2VJGmG.js} +1 -1
  129. package/core/built/admin/assets/textarea-Bjccvh_E.js +1 -0
  130. package/core/built/admin/assets/{tiers-BgYLWRJv.js → tiers-DYsa4DCo.js} +1 -1
  131. package/core/built/admin/assets/{toggle-group-DNXDkAwf.js → toggle-group-b3IlxvhV.js} +1 -1
  132. package/core/built/admin/assets/{topic-filter-DZGjEcz3.js → topic-filter-DAFT87tQ.js} +1 -1
  133. package/core/built/admin/assets/{use-analytics-data-CaT8RirX.js → use-analytics-data-S0FTd43s.js} +1 -1
  134. package/core/built/admin/assets/use-filter-params-Gpoq2E1L.js +1 -0
  135. package/core/built/admin/assets/{use-growth-stats-9Db2CuJe.js → use-growth-stats-CIaiHOVb.js} +1 -1
  136. package/core/built/admin/assets/{use-post-referrers-BV54YcQS.js → use-post-referrers-Cq2vBPrA.js} +1 -1
  137. package/core/built/admin/assets/{use-reply-chain-data-DuYZGDan.js → use-reply-chain-data-DBDRtGu-.js} +1 -1
  138. package/core/built/admin/assets/{use-simple-pagination-8fTua9Nt.js → use-simple-pagination-CjYNxdwe.js} +1 -1
  139. package/core/built/admin/assets/{use-tinybird-query-Dz156HvZ.js → use-tinybird-query-falHAtuK.js} +1 -1
  140. package/core/built/admin/assets/{use-tinybird-token-C9RUdL_n.js → use-tinybird-token-D7-8ND1y.js} +1 -1
  141. package/core/built/admin/assets/{users-CcSCQFcQ.js → users-Dkv8NQZe.js} +1 -1
  142. package/core/built/admin/assets/{virtual-list-BCvP0YF-.js → virtual-list-CvnC9zF2.js} +1 -1
  143. package/core/built/admin/assets/web-B_5GZRvH.js +1 -0
  144. package/core/built/admin/assets/web-jFYWCKDn.js +2 -0
  145. package/core/built/admin/index.html +14 -10
  146. package/core/frontend/services/proxy.js +10 -1
  147. package/core/frontend/services/routing/router-manager.js +14 -14
  148. package/core/frontend/services/routing/static-routes-router.js +8 -10
  149. package/core/server/adapters/storage/LocalFilesStorage.js +13 -11
  150. package/core/server/adapters/storage/LocalFilesStorage.ts +24 -0
  151. package/core/server/adapters/storage/LocalImagesStorage.js +14 -12
  152. package/core/server/adapters/storage/LocalImagesStorage.ts +24 -0
  153. package/core/server/adapters/storage/LocalMediaStorage.js +13 -11
  154. package/core/server/adapters/storage/LocalMediaStorage.ts +24 -0
  155. package/core/server/adapters/storage/LocalStorageBase.js +107 -167
  156. package/core/server/adapters/storage/LocalStorageBase.ts +342 -0
  157. package/core/server/adapters/storage/utils.js +36 -50
  158. package/core/server/adapters/storage/utils.ts +60 -0
  159. package/core/server/api/endpoints/automation-action-links.js +68 -0
  160. package/core/server/api/endpoints/automation-action-links.ts +46 -0
  161. package/core/server/api/endpoints/db.js +6 -0
  162. package/core/server/api/endpoints/index.js +4 -0
  163. package/core/server/api/endpoints/members.js +25 -28
  164. package/core/server/api/endpoints/pages.js +6 -1
  165. package/core/server/api/endpoints/posts.js +11 -2
  166. package/core/server/api/endpoints/settings.js +0 -2
  167. package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +4 -1
  168. package/core/server/api/endpoints/utils/serializers/output/members.js +18 -87
  169. package/core/server/api/endpoints/utils/serializers/output/utils/url.js +11 -1
  170. package/core/server/data/exporter/export-filename.js +2 -1
  171. package/core/server/data/importer/importers/data/revue-subscriber-importer.js +6 -10
  172. package/core/server/data/importer/importers/json-to-html.js +24 -4
  173. package/core/server/data/migrations/versions/6.55/2026-07-27-11-25-44-add-to-hash-to-redirects.js +51 -0
  174. package/core/server/data/migrations/versions/6.55/2026-07-28-12-08-00-remove-routes-hash-from-settings.js +5 -0
  175. package/core/server/data/schema/commands.js +5 -0
  176. package/core/server/data/schema/default-settings/default-settings.json +0 -4
  177. package/core/server/data/schema/schema.js +5 -1
  178. package/core/server/lib/image/image-size.js +3 -17
  179. package/core/server/lib/image/image-utils.js +15 -1
  180. package/core/server/lib/request-external.js +1 -1
  181. package/core/server/models/user.js +46 -70
  182. package/core/server/services/auth/session/session-service.js +28 -1
  183. package/core/server/services/automations/automations-api.js +21 -6
  184. package/core/server/services/automations/automations-api.ts +25 -6
  185. package/core/server/services/automations/automations-repository.ts +19 -0
  186. package/core/server/services/automations/database-automations-repository.js +116 -28
  187. package/core/server/services/automations/database-automations-repository.ts +118 -9
  188. package/core/server/services/automations/database-date.js +10 -0
  189. package/core/server/services/automations/database-date.ts +5 -0
  190. package/core/server/services/automations/poll.js +5 -2
  191. package/core/server/services/automations/poll.ts +7 -2
  192. package/core/server/services/automations/scheduler-poll-time.js +40 -0
  193. package/core/server/services/automations/scheduler-poll-time.ts +38 -0
  194. package/core/server/services/automations/service.js +15 -3
  195. package/core/server/services/automations/service.ts +16 -3
  196. package/core/server/services/comments/comments-service-emails.js +14 -8
  197. package/core/server/services/comments/comments-service.js +9 -4
  198. package/core/server/services/email-rendering/partials/email-wrapper.hbs +3 -1
  199. package/core/server/services/email-service/email-service-wrapper.js +6 -1
  200. package/core/server/services/email-service/email-templates/partials/styles.hbs +8 -0
  201. package/core/server/services/email-service/newsletter-email-event-storage.js +41 -10
  202. package/core/server/services/lib/dynamic-redirect-manager.js +12 -3
  203. package/core/server/services/link-redirection/link-redirect-repository.js +41 -4
  204. package/core/server/services/link-redirection/link-redirect.js +2 -0
  205. package/core/server/services/link-redirection/link-redirect.ts +3 -1
  206. package/core/server/services/link-redirection/link-redirects-service.js +38 -2
  207. package/core/server/services/link-tracking/click-event.js +3 -0
  208. package/core/server/services/link-tracking/index.js +38 -6
  209. package/core/server/services/link-tracking/link-click-repository.js +30 -9
  210. package/core/server/services/link-tracking/link-click-tracking-service.js +44 -3
  211. package/core/server/services/member-attribution/outbound-link-tagger.js +3 -1
  212. package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +19 -4
  213. package/core/server/services/member-welcome-emails/service.js +11 -3
  214. package/core/server/services/members/{importer → import-export}/config.js +4 -9
  215. package/core/server/services/members/{importer → import-export}/config.ts +4 -9
  216. package/core/server/services/members/import-export/csv/formula.js +16 -0
  217. package/core/server/services/members/import-export/csv/formula.ts +14 -0
  218. package/core/server/services/members/import-export/csv/index.js +12 -0
  219. package/core/server/services/members/import-export/csv/index.ts +3 -0
  220. package/core/server/services/members/import-export/csv/parse.js +64 -0
  221. package/core/server/services/members/import-export/csv/parse.ts +69 -0
  222. package/core/server/services/members/import-export/csv/serialize.js +13 -0
  223. package/core/server/services/members/import-export/csv/serialize.ts +18 -0
  224. package/core/server/services/members/import-export/export/exporter.js +202 -0
  225. package/core/server/services/members/import-export/export/exporter.ts +325 -0
  226. package/core/server/services/members/import-export/import/completion-email.js +84 -0
  227. package/core/server/services/members/import-export/import/completion-email.ts +141 -0
  228. package/core/server/services/members/import-export/import/importer.js +297 -0
  229. package/core/server/services/members/import-export/import/importer.ts +468 -0
  230. package/core/server/services/members/import-export/import/reader.js +32 -0
  231. package/core/server/services/members/import-export/import/reader.ts +33 -0
  232. package/core/server/services/members/import-export/import/row.js +33 -0
  233. package/core/server/services/members/import-export/import/row.ts +52 -0
  234. package/core/server/services/members/import-export/import/spool.js +30 -0
  235. package/core/server/services/members/import-export/import/spool.ts +38 -0
  236. package/core/server/services/members/import-export/index.js +99 -0
  237. package/core/server/services/members/import-export/index.ts +137 -0
  238. package/core/server/services/members/members-api/controllers/router-controller.js +7 -0
  239. package/core/server/services/members/service.js +25 -20
  240. package/core/server/services/members-custom-fields/values-service.js +19 -7
  241. package/core/server/services/members-custom-fields/values-service.ts +19 -7
  242. package/core/server/services/mentions/service.js +5 -1
  243. package/core/server/services/posts/posts-exporter.js +6 -4
  244. package/core/server/services/posts/posts-service.js +2 -2
  245. package/core/server/services/route-settings/activation-bridge.js +59 -49
  246. package/core/server/services/route-settings/activation-bridge.ts +91 -51
  247. package/core/server/services/route-settings/dynamic-routing-service.js +22 -51
  248. package/core/server/services/route-settings/index.js +1 -16
  249. package/core/server/services/settings/settings-service.js +0 -18
  250. package/core/server/services/themes/storage.js +9 -0
  251. package/core/server/services/themes/theme-storage.js +1 -1
  252. package/core/server/services/url/url-service-facade.js +1 -1
  253. package/core/server/services/url/url-service-facade.ts +8 -1
  254. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  255. package/core/shared/config/defaults.json +2 -1
  256. package/core/shared/express.js +6 -1
  257. package/core/shared/labs.js +2 -1
  258. package/package.json +11 -11
  259. package/pnpm-lock.yaml +18 -18
  260. package/pnpm-workspace.yaml +8 -7
  261. package/components/tryghost-adapter-base-scheduling-0.1.1.tgz +0 -0
  262. package/core/built/admin/assets/Overview-j4NaeUNm.js +0 -1
  263. package/core/built/admin/assets/Web-_fD935OX.js +0 -2
  264. package/core/built/admin/assets/activitypub/layout-IdVq3lRy.js +0 -1
  265. package/core/built/admin/assets/audience-BKRdsFmy.js +0 -1
  266. package/core/built/admin/assets/checkbox-DA4-1UiI.js +0 -1
  267. package/core/built/admin/assets/chunk.524.45e0875f41b71e46e849.js +0 -25
  268. package/core/built/admin/assets/chunk.582.51defd4b009f0de8d4d5.js +0 -27
  269. package/core/built/admin/assets/code-editor-view-BKBrkffZ.js +0 -1
  270. package/core/built/admin/assets/comments-CzVNIT8b.js +0 -1
  271. package/core/built/admin/assets/constants-c-JNF9c2.js +0 -1
  272. package/core/built/admin/assets/dist-CT46_f_J.js +0 -1
  273. package/core/built/admin/assets/domain-B7_n5nfx.js +0 -1
  274. package/core/built/admin/assets/dropzone-DcuJEEUf.js +0 -5
  275. package/core/built/admin/assets/editor-BCiIyFjm.js +0 -8
  276. package/core/built/admin/assets/field-BW-M2Zwp.js +0 -1
  277. package/core/built/admin/assets/filters-DtcXMZDU.js +0 -1
  278. package/core/built/admin/assets/gift-link-modal-Bk8qpnMw.js +0 -1
  279. package/core/built/admin/assets/growth-Ck2s3JjR.js +0 -1
  280. package/core/built/admin/assets/index-BBf502Wg.js +0 -69
  281. package/core/built/admin/assets/index-BCCZKxP2.css +0 -1
  282. package/core/built/admin/assets/input-group-DjuYnNCd.js +0 -1
  283. package/core/built/admin/assets/label-picker-BdNAWuUX.js +0 -1
  284. package/core/built/admin/assets/labels-Cyayfd-e.js +0 -1
  285. package/core/built/admin/assets/layout-Bw4GnxXp.js +0 -1
  286. package/core/built/admin/assets/member-custom-fields-DvFyamLu.js +0 -1
  287. package/core/built/admin/assets/member-detail-Cg2vTqvJ.js +0 -1
  288. package/core/built/admin/assets/members-DsPxYAVm.js +0 -10
  289. package/core/built/admin/assets/modals-Bf_fyvHZ.js +0 -31
  290. package/core/built/admin/assets/moderation-Dlp7M5sa.js +0 -1
  291. package/core/built/admin/assets/multi-select-combobox-CH1o8XXQ.js +0 -1
  292. package/core/built/admin/assets/newsletter-CQ5CAYUI.js +0 -1
  293. package/core/built/admin/assets/note-BrAZ7Z0o.js +0 -1
  294. package/core/built/admin/assets/notifications-BnbHLOPC.js +0 -1
  295. package/core/built/admin/assets/onboarding-route-eU146kuA.js +0 -1
  296. package/core/built/admin/assets/overview-BpbaK-xu.js +0 -1
  297. package/core/built/admin/assets/post-analytics-header-D9pJ4iNm.js +0 -1
  298. package/core/built/admin/assets/post-analytics-provider-BDqcfBor.js +0 -1
  299. package/core/built/admin/assets/profile-dJ44Bn_W.js +0 -1
  300. package/core/built/admin/assets/select--2uVN9_-.js +0 -1
  301. package/core/built/admin/assets/settings-SD9bxIpt.js +0 -4
  302. package/core/built/admin/assets/social-urls-BvFZwYcE.js +0 -8
  303. package/core/built/admin/assets/switch-qp518gFQ.js +0 -1
  304. package/core/built/admin/assets/tabs-D3ZOYkM7.js +0 -1
  305. package/core/built/admin/assets/textarea-CTlb3Uu6.js +0 -1
  306. package/core/built/admin/assets/use-filter-params-DeQ8sX4N.js +0 -1
  307. package/core/built/admin/assets/web-DTZnnUGr.js +0 -1
  308. package/core/server/services/mail/templates/newsletter.html +0 -876
  309. package/core/server/services/members/csv/index.js +0 -10
  310. package/core/server/services/members/csv/index.ts +0 -3
  311. package/core/server/services/members/csv/parse.js +0 -94
  312. package/core/server/services/members/csv/parse.ts +0 -111
  313. package/core/server/services/members/csv/types.js +0 -2
  314. package/core/server/services/members/csv/types.ts +0 -39
  315. package/core/server/services/members/csv/unparse.js +0 -69
  316. package/core/server/services/members/csv/unparse.ts +0 -79
  317. package/core/server/services/members/exporter/query.js +0 -233
  318. package/core/server/services/members/importer/index.js +0 -30
  319. package/core/server/services/members/importer/labels.js +0 -125
  320. package/core/server/services/members/importer/members-csv-importer.js +0 -520
  321. package/core/server/services/route-settings/default-settings-manager.js +0 -63
  322. package/core/server/services/route-settings/route-settings.js +0 -170
  323. package/core/server/services/route-settings/settings-loader.js +0 -60
  324. package/core/server/services/route-settings/settings-path-manager.js +0 -47
  325. package/core/server/services/route-settings/validate.js +0 -440
  326. /package/core/server/services/members/{importer → import-export/import}/email-template.js +0 -0
  327. /package/core/server/services/members/{importer/members-csv-importer-stripe-utils.js → import-export/import/stripe-utils.js} +0 -0
@@ -1,10 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.unparse = exports.parse = void 0;
7
- var parse_1 = require("./parse");
8
- Object.defineProperty(exports, "parse", { enumerable: true, get: function () { return __importDefault(parse_1).default; } });
9
- var unparse_1 = require("./unparse");
10
- Object.defineProperty(exports, "unparse", { enumerable: true, get: function () { return __importDefault(unparse_1).default; } });
@@ -1,3 +0,0 @@
1
- export {default as parse} from './parse';
2
- export {default as unparse} from './unparse';
3
- export type {Label, MemberCsvRow} from './types';
@@ -1,94 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.default = parse;
7
- const node_stream_1 = require("node:stream");
8
- const papaparse_1 = __importDefault(require("papaparse"));
9
- const fs_extra_1 = __importDefault(require("fs-extra"));
10
- const transformValue = (header, value) => {
11
- if (header === 'labels') {
12
- if (value && typeof value === 'string') {
13
- return value.split(',').map(name => ({ name }));
14
- }
15
- return [];
16
- }
17
- if (header === 'subscribed') {
18
- return value.toLowerCase() !== 'false';
19
- }
20
- if (header === 'complimentary_plan') {
21
- return value.toLowerCase() === 'true';
22
- }
23
- if (value === '') {
24
- return null;
25
- }
26
- if (value === 'undefined') {
27
- return null;
28
- }
29
- if (value.toLowerCase() === 'false') {
30
- return false;
31
- }
32
- if (value.toLowerCase() === 'true') {
33
- return true;
34
- }
35
- return value;
36
- };
37
- /**
38
- * @param path - The path to the CSV to prepare
39
- * @param headerMapping - An object whose keys are headers in the input CSV and values are the header to replace it with
40
- * @param defaultLabels - A list of labels to apply to every parsed member row
41
- * @returns The parsed member rows, keyed by mapped header
42
- */
43
- function parse(path, headerMapping, defaultLabels = []) {
44
- return new Promise(function (resolve, reject) {
45
- const csvFileStream = fs_extra_1.default.createReadStream(path);
46
- const csvParserStream = papaparse_1.default.parse(papaparse_1.default.NODE_STREAM_INPUT, {
47
- header: true
48
- });
49
- const rows = [];
50
- const parsedCSVStream = (0, node_stream_1.pipeline)(csvFileStream, csvParserStream, (err) => {
51
- if (err) {
52
- return reject(err);
53
- }
54
- resolve(rows);
55
- });
56
- parsedCSVStream.on('data', (parsedRow) => {
57
- // a throw here escapes as an uncaught exception and leaves this
58
- // promise forever unsettled, so it has to become a rejection
59
- try {
60
- const row = {};
61
- for (const [header, value] of Object.entries(parsedRow)) {
62
- // papaparse gathers the overflow from a row carrying more
63
- // fields than there are headers under __parsed_extra, as an
64
- // array rather than a cell any mapping can name
65
- if (typeof value !== 'string') {
66
- continue;
67
- }
68
- // hasOwn, not `in`: a column named after an Object.prototype
69
- // member would otherwise pass as mapped and take a function
70
- // as its mapped name
71
- if (!headerMapping || !Object.hasOwn(headerMapping, header)) {
72
- continue;
73
- }
74
- const mappedHeader = headerMapping[header];
75
- row[mappedHeader] = transformValue(mappedHeader, value);
76
- }
77
- // skip rows with no data
78
- if (!Object.keys(row).length) {
79
- return;
80
- }
81
- // labels is absent when the column is unmapped, and an array otherwise
82
- const parsedLabels = row.labels;
83
- row.labels = [
84
- ...(Array.isArray(parsedLabels) ? parsedLabels : []),
85
- ...defaultLabels
86
- ];
87
- rows.push(row);
88
- }
89
- catch (err) {
90
- reject(err);
91
- }
92
- });
93
- });
94
- }
@@ -1,111 +0,0 @@
1
- import {pipeline} from 'node:stream';
2
- import papaparse from 'papaparse';
3
- import fs from 'fs-extra';
4
- import type {Label, MemberCsvRow} from './types';
5
-
6
- type CsvValue = string | boolean | null | Label[];
7
-
8
- const transformValue = (header: string, value: string): CsvValue => {
9
- if (header === 'labels') {
10
- if (value && typeof value === 'string') {
11
- return value.split(',').map(name => ({name}));
12
- }
13
- return [];
14
- }
15
-
16
- if (header === 'subscribed') {
17
- return value.toLowerCase() !== 'false';
18
- }
19
-
20
- if (header === 'complimentary_plan') {
21
- return value.toLowerCase() === 'true';
22
- }
23
-
24
- if (value === '') {
25
- return null;
26
- }
27
-
28
- if (value === 'undefined') {
29
- return null;
30
- }
31
-
32
- if (value.toLowerCase() === 'false') {
33
- return false;
34
- }
35
-
36
- if (value.toLowerCase() === 'true') {
37
- return true;
38
- }
39
-
40
- return value;
41
- };
42
-
43
- /**
44
- * @param path - The path to the CSV to prepare
45
- * @param headerMapping - An object whose keys are headers in the input CSV and values are the header to replace it with
46
- * @param defaultLabels - A list of labels to apply to every parsed member row
47
- * @returns The parsed member rows, keyed by mapped header
48
- */
49
- export default function parse(
50
- path: string,
51
- headerMapping?: Record<string, string>,
52
- defaultLabels: Label[] = []
53
- ): Promise<MemberCsvRow[]> {
54
- return new Promise(function (resolve, reject) {
55
- const csvFileStream = fs.createReadStream(path);
56
- const csvParserStream = papaparse.parse(papaparse.NODE_STREAM_INPUT, {
57
- header: true
58
- });
59
-
60
- const rows: MemberCsvRow[] = [];
61
- const parsedCSVStream = pipeline(csvFileStream, csvParserStream, (err) => {
62
- if (err) {
63
- return reject(err);
64
- }
65
- resolve(rows);
66
- });
67
-
68
- parsedCSVStream.on('data', (parsedRow: Record<string, unknown>) => {
69
- // a throw here escapes as an uncaught exception and leaves this
70
- // promise forever unsettled, so it has to become a rejection
71
- try {
72
- const row: MemberCsvRow = {};
73
-
74
- for (const [header, value] of Object.entries(parsedRow)) {
75
- // papaparse gathers the overflow from a row carrying more
76
- // fields than there are headers under __parsed_extra, as an
77
- // array rather than a cell any mapping can name
78
- if (typeof value !== 'string') {
79
- continue;
80
- }
81
-
82
- // hasOwn, not `in`: a column named after an Object.prototype
83
- // member would otherwise pass as mapped and take a function
84
- // as its mapped name
85
- if (!headerMapping || !Object.hasOwn(headerMapping, header)) {
86
- continue;
87
- }
88
-
89
- const mappedHeader = headerMapping[header];
90
- row[mappedHeader] = transformValue(mappedHeader, value);
91
- }
92
-
93
- // skip rows with no data
94
- if (!Object.keys(row).length) {
95
- return;
96
- }
97
-
98
- // labels is absent when the column is unmapped, and an array otherwise
99
- const parsedLabels = row.labels;
100
- row.labels = [
101
- ...(Array.isArray(parsedLabels) ? parsedLabels : []),
102
- ...defaultLabels
103
- ];
104
-
105
- rows.push(row);
106
- } catch (err) {
107
- reject(err);
108
- }
109
- });
110
- });
111
- }
@@ -1,2 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
@@ -1,39 +0,0 @@
1
- export interface Label {
2
- name: string;
3
- }
4
-
5
- /**
6
- * Any column the parser does not special-case is coerced, so a cell reading
7
- * TRUE or FALSE arrives as a boolean and an empty one as null, whatever the
8
- * column means. A member genuinely named TRUE parses to `name: true`.
9
- */
10
- export type ParsedValue = string | boolean | null;
11
-
12
- /**
13
- * The member CSV column vocabulary as it currently stands. Several fields exist
14
- * on one side only: tiers and deleted_at are written on export and not accepted
15
- * on import, import_tier is accepted on import and never written, and the email
16
- * subscription state is called subscribed coming in and subscribed_to_emails
17
- * going out. The index signature carries custom field columns, which are not
18
- * known ahead of time.
19
- */
20
- export interface MemberCsvRow {
21
- id?: ParsedValue;
22
- email?: ParsedValue;
23
- name?: ParsedValue;
24
- note?: ParsedValue;
25
- subscribed?: boolean;
26
- subscribed_to_emails?: boolean;
27
- comped?: boolean;
28
- complimentary_plan?: boolean;
29
- stripe_customer_id?: ParsedValue;
30
- subscriptions?: Array<{customer?: {id?: string}}>;
31
- created_at?: ParsedValue;
32
- deleted_at?: ParsedValue;
33
- labels?: string | Array<string | Label>;
34
- tiers?: Array<{name: string}>;
35
- import_tier?: ParsedValue;
36
- gift_id?: ParsedValue;
37
- error?: ParsedValue;
38
- [column: string]: unknown;
39
- }
@@ -1,69 +0,0 @@
1
- "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
- Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.default = unparse;
7
- const papaparse_1 = __importDefault(require("papaparse"));
8
- const DEFAULT_COLUMNS = [
9
- 'id',
10
- 'email',
11
- 'name',
12
- 'note',
13
- 'subscribed_to_emails',
14
- 'complimentary_plan',
15
- 'stripe_customer_id',
16
- 'created_at',
17
- 'deleted_at',
18
- 'labels',
19
- 'tiers',
20
- 'gift_id'
21
- ];
22
- function unparse(rows, columns = DEFAULT_COLUMNS, { escapeFormulae = true } = {}) {
23
- const outputColumns = columns.map((column) => {
24
- if (column === 'subscribed') {
25
- return 'subscribed_to_emails';
26
- }
27
- return column;
28
- });
29
- if (!outputColumns.includes('error') && rows.some(row => row.error)) {
30
- outputColumns.push('error');
31
- }
32
- const mappedRows = rows.map((row) => {
33
- let labels = '';
34
- if (typeof row.labels === 'string') {
35
- labels = row.labels;
36
- }
37
- else if (Array.isArray(row.labels)) {
38
- labels = row.labels.map((l) => {
39
- return typeof l === 'string' ? l : l.name;
40
- }).join(',');
41
- }
42
- let tiers = '';
43
- if (Array.isArray(row.tiers)) {
44
- tiers = row.tiers.map((tier) => {
45
- return tier.name;
46
- }).join(',');
47
- }
48
- return {
49
- id: row.id,
50
- email: row.email,
51
- name: row.name,
52
- note: row.note,
53
- subscribed_to_emails: 'subscribed' in row ? row.subscribed : row.subscribed_to_emails,
54
- complimentary_plan: row.comped || row.complimentary_plan,
55
- stripe_customer_id: row.subscriptions?.[0]?.customer?.id || row.stripe_customer_id,
56
- created_at: row.created_at,
57
- deleted_at: row.deleted_at,
58
- labels,
59
- tiers,
60
- import_tier: row.import_tier || null,
61
- gift_id: row.gift_id || null,
62
- error: row.error || null
63
- };
64
- });
65
- return papaparse_1.default.unparse(mappedRows, {
66
- escapeFormulae,
67
- columns: outputColumns
68
- });
69
- }
@@ -1,79 +0,0 @@
1
- import papaparse from 'papaparse';
2
- import type {MemberCsvRow} from './types';
3
-
4
- const DEFAULT_COLUMNS = [
5
- 'id',
6
- 'email',
7
- 'name',
8
- 'note',
9
- 'subscribed_to_emails',
10
- 'complimentary_plan',
11
- 'stripe_customer_id',
12
- 'created_at',
13
- 'deleted_at',
14
- 'labels',
15
- 'tiers',
16
- 'gift_id'
17
- ];
18
-
19
- interface UnparseOptions {
20
- // Prefixes a leading =, +, -, @ or tab with an apostrophe so a spreadsheet does
21
- // not read the value as a formula. On by default: it belongs on any CSV a person
22
- // opens. The importer's own intermediate file is not one, and turns it off, so a
23
- // member named "-5" is not read back as "'-5".
24
- escapeFormulae?: boolean;
25
- }
26
-
27
- export default function unparse(rows: MemberCsvRow[], columns: string[] = DEFAULT_COLUMNS, {escapeFormulae = true}: UnparseOptions = {}): string {
28
- const outputColumns = columns.map((column) => {
29
- if (column === 'subscribed') {
30
- return 'subscribed_to_emails';
31
- }
32
- return column;
33
- });
34
-
35
- if (!outputColumns.includes('error') && rows.some(row => row.error)) {
36
- outputColumns.push('error');
37
- }
38
-
39
- const mappedRows = rows.map((row) => {
40
- let labels = '';
41
- if (typeof row.labels === 'string') {
42
- labels = row.labels;
43
- } else if (Array.isArray(row.labels)) {
44
- labels = row.labels.map((l) => {
45
- return typeof l === 'string' ? l : l.name;
46
- }).join(',');
47
- }
48
-
49
- let tiers = '';
50
-
51
- if (Array.isArray(row.tiers)) {
52
- tiers = row.tiers.map((tier) => {
53
- return tier.name;
54
- }).join(',');
55
- }
56
-
57
- return {
58
- id: row.id,
59
- email: row.email,
60
- name: row.name,
61
- note: row.note,
62
- subscribed_to_emails: 'subscribed' in row ? row.subscribed : row.subscribed_to_emails,
63
- complimentary_plan: row.comped || row.complimentary_plan,
64
- stripe_customer_id: row.subscriptions?.[0]?.customer?.id || row.stripe_customer_id,
65
- created_at: row.created_at,
66
- deleted_at: row.deleted_at,
67
- labels,
68
- tiers,
69
- import_tier: row.import_tier || null,
70
- gift_id: row.gift_id || null,
71
- error: row.error || null
72
- };
73
- });
74
-
75
- return papaparse.unparse(mappedRows, {
76
- escapeFormulae,
77
- columns: outputColumns
78
- });
79
- }
@@ -1,233 +0,0 @@
1
- const models = require('../../../models');
2
- const {knex} = require('../../../data/db');
3
- const moment = require('moment');
4
- const logging = require('@tryghost/logging');
5
- const {Transform} = require('stream');
6
- const labs = require('../../../../shared/labs');
7
- const {csvCellsForFields} = require('@tryghost/custom-field-types/csv');
8
-
9
- /**
10
- * Process a batch of members with all their related data
11
- * @param {Array} members - Array of member records
12
- * @param {Object} tiersMap - Map of member_id to tiers
13
- * @param {Object} labelsMap - Map of member_id to labels
14
- * @param {Object} stripeCustomerMap - Map of member_id to stripe customer IDs
15
- * @param {Set} subscribedSet - Set of subscribed member IDs
16
- * @param {Map} giftIdMap - Map of member_id to gift id (for gift members only)
17
- * @param {Object} allProducts - Map of product IDs to product names
18
- * @param {Object} allLabels - Map of label IDs to label names
19
- * @param {Array} activeCustomFields - Active custom field definitions
20
- * @param {Map} customFieldValuesMap - Map of member_id to their custom field values
21
- * @returns {Array} Processed member records
22
- */
23
- function processMembersData(members, tiersMap, labelsMap, stripeCustomerMap, subscribedSet, giftIdMap, allProducts, allLabels, activeCustomFields, customFieldValuesMap) {
24
- for (const row of members) {
25
- const tierIds = tiersMap.get(row.id) ? tiersMap.get(row.id).split(',') : [];
26
- const tierDetails = tierIds.map((id) => {
27
- return {
28
- name: allProducts[id]
29
- };
30
- });
31
- row.tiers = tierDetails;
32
-
33
- const labelIds = labelsMap.get(row.id) ? labelsMap.get(row.id).split(',') : [];
34
- const labelDetails = labelIds.map((id) => {
35
- return {
36
- name: allLabels[id]
37
- };
38
- });
39
- row.labels = labelDetails;
40
-
41
- row.subscribed = subscribedSet.has(row.id);
42
- row.comped = row.status === 'comped';
43
- row.complimentary_plan = row.status === 'complimentary';
44
- row.gift_id = giftIdMap.get(row.id) || null;
45
- row.stripe_customer_id = stripeCustomerMap.get(row.id) || null;
46
- row.created_at = moment(row.created_at).toISOString();
47
-
48
- // Flattened here rather than in the serializer because the column set is
49
- // only knowable from the database. Every member carries a cell for every
50
- // active field's column, so a member with no values still contributes the
51
- // full set — the CSV header is taken from whichever row streams first.
52
- row.custom_field_cells = csvCellsForFields(activeCustomFields, customFieldValuesMap.get(row.id) || {});
53
- }
54
- return members;
55
- }
56
-
57
- /**
58
- * Creates a streaming implementation of the members export
59
- * @param {Object} options - Export options
60
- * @returns {Promise<Object>} A readable stream of processed members
61
- */
62
- async function createExportStream(options, {definitions, values}) {
63
- const hasFilter = options.limit !== 'all' || options.filter || options.search;
64
- const start = Date.now();
65
-
66
- let ids = null;
67
- if (hasFilter) {
68
- // do a very minimal query, only to fetch the ids of the filtered values
69
- const filterOptions = {...options};
70
- filterOptions.withRelated = [];
71
- filterOptions.columns = ['id'];
72
-
73
- // Important: We need to get ALL ids, not just the first page
74
- filterOptions.limit = 'all';
75
-
76
- const page = await models.Member.findPage(filterOptions);
77
- ids = page.data.map(d => d.id);
78
- logging.info(`[MembersExporter] Found ${ids.length} members matching filter criteria`);
79
- }
80
-
81
- const startFetchingProducts = Date.now();
82
-
83
- // Get all products and labels upfront as these are small tables
84
- const allProducts = await knex('products').select('id', 'name').then(rows => rows.reduce((acc, product) => {
85
- acc[product.id] = product.name;
86
- return acc;
87
- }, {}));
88
-
89
- const allLabels = await knex('labels').select('id', 'name').then(rows => rows.reduce((acc, label) => {
90
- acc[label.id] = label.name;
91
- return acc;
92
- }, {}));
93
-
94
- logging.info('[MembersExporter] Fetched products and labels in ' + (Date.now() - startFetchingProducts) + 'ms');
95
-
96
- // Definitions are a small, stable table, so they're read once rather than per
97
- // batch. Reading once also fixes the column set for the whole file: archiving a
98
- // field mid-export can't leave the header ragged. It can still leave that
99
- // field's cells empty from the batch after the archive onwards, because the
100
- // per-batch value read applies its own active filter.
101
- const activeCustomFields = labs.isSet('membersCustomFields')
102
- ? await definitions.browse()
103
- : [];
104
-
105
- // Create a transform stream that will process the members as they come in
106
- const processMembersTransform = new Transform({
107
- objectMode: true,
108
- highWaterMark: 1000, // Process 1000 members at a time
109
- async transform(batch, encoding, callback) {
110
- try {
111
- const memberIds = batch.map(member => member.id);
112
-
113
- // Fetch related data for this batch
114
- const [tiers, labels, stripeCustomers, subscriptions, gifts, customFieldValuesMap] = await Promise.all([
115
- knex('members_products')
116
- .select('member_id', knex.raw('GROUP_CONCAT(product_id) as tiers'))
117
- .whereIn('member_id', memberIds)
118
- .groupBy('member_id'),
119
-
120
- knex('members_labels')
121
- .select('member_id', knex.raw('GROUP_CONCAT(label_id) as labels'))
122
- .whereIn('member_id', memberIds)
123
- .groupBy('member_id'),
124
-
125
- knex('members_stripe_customers')
126
- .select('member_id', knex.raw('MIN(customer_id) as stripe_customer_id'))
127
- .whereIn('member_id', memberIds)
128
- .groupBy('member_id'),
129
-
130
- knex('members_newsletters')
131
- .distinct('member_id')
132
- .whereIn('member_id', memberIds),
133
-
134
- knex('gifts')
135
- .select('id', 'redeemer_member_id')
136
- .where('status', 'redeemed')
137
- .whereIn('redeemer_member_id', memberIds),
138
-
139
- activeCustomFields.length > 0
140
- ? values.getValuesForMembers(memberIds)
141
- : new Map()
142
- ]);
143
-
144
- // Create maps for quick lookups
145
- const tiersMap = new Map(tiers.map(row => [row.member_id, row.tiers]));
146
- const labelsMap = new Map(labels.map(row => [row.member_id, row.labels]));
147
- const stripeCustomerMap = new Map(stripeCustomers.map(row => [row.member_id, row.stripe_customer_id]));
148
- const subscribedSet = new Set(subscriptions.map(row => row.member_id));
149
- const giftIdMap = new Map(gifts.map(row => [row.redeemer_member_id, row.id]));
150
-
151
- // Process the batch
152
- const processedMembers = processMembersData(
153
- batch,
154
- tiersMap,
155
- labelsMap,
156
- stripeCustomerMap,
157
- subscribedSet,
158
- giftIdMap,
159
- allProducts,
160
- allLabels,
161
- activeCustomFields,
162
- customFieldValuesMap
163
- );
164
-
165
- // Push each member individually to avoid large arrays in memory
166
- processedMembers.forEach((member) => {
167
- this.push(member);
168
- });
169
-
170
- callback();
171
- } catch (err) {
172
- callback(err);
173
- }
174
- }
175
- });
176
-
177
- // Create a batching stream to group members
178
- const batchSize = 1000; // Process in batches of 1000
179
- let currentBatch = [];
180
-
181
- const batchingTransform = new Transform({
182
- objectMode: true,
183
- transform(member, encoding, callback) {
184
- currentBatch.push(member);
185
-
186
- if (currentBatch.length >= batchSize) {
187
- this.push(currentBatch);
188
- currentBatch = [];
189
- }
190
-
191
- callback();
192
- },
193
- flush(callback) {
194
- if (currentBatch.length > 0) {
195
- this.push(currentBatch);
196
- }
197
- callback();
198
- }
199
- });
200
-
201
- // Create a query stream for members
202
- const membersQuery = knex('members')
203
- .select('id', 'email', 'name', 'note', 'status', 'created_at');
204
-
205
- if (hasFilter) {
206
- membersQuery.whereIn('id', ids);
207
- }
208
-
209
- logging.info('[MembersExporter] Starting streaming export of members');
210
-
211
- // Chain the streams together
212
- const membersStream = membersQuery.stream()
213
- .pipe(batchingTransform)
214
- .pipe(processMembersTransform);
215
-
216
- // Log when export is complete
217
- membersStream.on('end', () => {
218
- logging.info('[MembersExporter] Total time taken for member export: ' + (Date.now() - start) / 1000 + 's');
219
- });
220
-
221
- return membersStream;
222
- }
223
-
224
- /**
225
- * Export members data
226
- * @param {Object} options - Export options
227
- * @returns {Promise<Array|Object>} Array of members or stream of members
228
- */
229
- module.exports = function createExporter({definitions, values}) {
230
- return async function (options = {}) {
231
- return createExportStream(options, {definitions, values});
232
- };
233
- };
@@ -1,30 +0,0 @@
1
- const MembersCSVImporter = require('./members-csv-importer');
2
- const MembersCSVImporterStripeUtils = require('./members-csv-importer-stripe-utils');
3
-
4
- /**
5
- * @typedef {import('./members-csv-importer').MembersCSVImporterOptions} MembersCSVImporterOptions
6
- */
7
-
8
- /**
9
- * @typedef {Object} MakeImporterDeps
10
- * @property {Object} stripeAPIService - Instance of StripeAPIService
11
- * @property {Object} productRepository - Instance of ProductRepository
12
- */
13
-
14
- /**
15
- * Make an instance of MembersCSVImporter
16
- *
17
- * @param {MakeImporterDeps & MembersCSVImporterOptions} deps
18
- * @returns {MembersCSVImporter}
19
- */
20
- module.exports = function makeImporter(deps) {
21
- const stripeUtils = new MembersCSVImporterStripeUtils({
22
- stripeAPIService: deps.stripeAPIService,
23
- productRepository: deps.productRepository
24
- });
25
-
26
- return new MembersCSVImporter({
27
- ...deps,
28
- stripeUtils
29
- });
30
- };