ghost 6.54.0 → 6.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/ghost-storage-base-3.0.0.tgz +0 -0
- package/components/tryghost-adapter-base-cache-0.2.0.tgz +0 -0
- package/components/tryghost-adapter-base-scheduling-0.2.0.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.1.1.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
- package/core/built/admin/assets/Growth-BTP1LthE.js +3 -0
- package/core/built/admin/assets/Newsletters-CSEX0myc.js +1 -0
- package/core/built/admin/assets/Overview-BjACEsJF.js +1 -0
- package/core/built/admin/assets/Web-CxxxjCcv.js +2 -0
- package/core/built/admin/assets/{account-migration-DLvVA5yq.js → account-migration-BklF2I_I.js} +1 -1
- package/core/built/admin/assets/activitypub/{account-migration-DHbZcxGM.js → account-migration-CNovr6W-.js} +4 -4
- package/core/built/admin/assets/activitypub/activitypub.js +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-zfxUey_L.js → bluesky-sharing-Ceslb1_M.js} +4 -4
- package/core/built/admin/assets/activitypub/{dist-BqkDUQxJ2.js → dist-ByFmDSNP2.js} +64 -63
- package/core/built/admin/assets/activitypub/{dist-Ba_QxHHl.js → dist-DHAjBcV3.js} +2 -2
- package/core/built/admin/assets/activitypub/{domain-CmwSZLHK.js → domain-C1qQvJ8C.js} +5 -5
- package/core/built/admin/assets/activitypub/{edit-profile-CXdwNmVG.js → edit-profile-BcH-jEfM.js} +3 -3
- package/core/built/admin/assets/activitypub/{explore-DfbjzGM1.js → explore-AxwBh9dI.js} +3 -3
- package/core/built/admin/assets/activitypub/{feed-DSEBJOqf.js → feed-BaOlbh7a.js} +3 -3
- package/core/built/admin/assets/activitypub/{field-CY7XVcx6.js → field-B7mYYQpN.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-DtQ0kytT.js → inbox-vXez-5uJ.js} +4 -4
- package/core/built/admin/assets/activitypub/{label-Dw96GQ7u.js → label-Chv5T8bw.js} +2 -2
- package/core/built/admin/assets/activitypub/layout-CziktJXo.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-Dg_-I-8C.js → moderation-DtSM2i1I.js} +4 -4
- package/core/built/admin/assets/activitypub/{note-Cy1-uKMY.js → note-Dckp4S-2.js} +3 -3
- package/core/built/admin/assets/activitypub/{notifications-B_fvoLIM.js → notifications-CG5T22vg.js} +3 -3
- package/core/built/admin/assets/activitypub/{preferences-ByJseZp6.js → preferences-D_799jdl.js} +6 -6
- package/core/built/admin/assets/activitypub/{profile-BFtbnIjz.js → profile-D2JfXBQl.js} +6 -6
- package/core/built/admin/assets/activitypub/{routes-Dh3xXfce.js → routes-CCHe2dQp.js} +28 -25
- package/core/built/admin/assets/activitypub/{settings-DTgqJPxM.js → settings-kx-9Enkz.js} +3 -3
- package/core/built/admin/assets/activitypub/{site-CdL9ZLqm.js → site-x4_IjVJ0.js} +2 -2
- package/core/built/admin/assets/activitypub/{tabs-ogqwoBlb.js → tabs-CyPRQQ1N.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-CxonteFD.js → use-reply-chain-data-BAB6fNGt.js} +2 -2
- package/core/built/admin/assets/{audience-BKRdsFmy.js → audience-X_XsZivI.js} +1 -1
- package/core/built/admin/assets/{automated-emails-Cn87L5Md.js → automated-emails-of1MvVsv.js} +1 -1
- package/core/built/admin/assets/{automations-BP_aHamJ.js → automations-DRFjW5fw.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-Bh36XPh4.js → bluesky-sharing-BbbjWdzs.js} +1 -1
- package/core/built/admin/assets/{breadcrumb-D34YWnaw.js → breadcrumb-B-M9Ip_2.js} +1 -1
- package/core/built/admin/assets/{chart-helpers-DbklaJw9.js → chart-helpers-xiSsW8r0.js} +1 -1
- package/core/built/admin/assets/checkbox-EhJKHFdZ.js +1 -0
- package/core/built/admin/assets/{chunk.962.b73442b07be930bb5d85.js → chunk.211.6f7328d63c41536451b3.js} +737 -559
- package/core/built/admin/assets/{chunk.962.b73442b07be930bb5d85.js.LICENSE.txt → chunk.211.6f7328d63c41536451b3.js.LICENSE.txt} +2 -0
- package/core/built/admin/assets/{chunk.589.096f4d0bb775e5229aa9.js → chunk.361.3cdb584b973959e52ccc.js} +3 -3
- package/core/built/admin/assets/chunk.524.8365cd9280405c0f6216.js +25 -0
- package/core/built/admin/assets/chunk.582.61d078937e6102769f2c.js +27 -0
- package/core/built/admin/assets/code-editor-view-CZtJFGhR.js +1 -0
- package/core/built/admin/assets/comments-CvyXxOyN.js +1 -0
- package/core/built/admin/assets/{date-range-select-B5vSsrpx.js → date-range-select-CTlyo8zW.js} +1 -1
- package/core/built/admin/assets/{disabled-sources-indicator-NkOIoMue.js → disabled-sources-indicator-qs2GfLvl.js} +1 -1
- package/core/built/admin/assets/dist-2Tnz2Yfq.js +1 -0
- package/core/built/admin/assets/dist-CjY-f5Z1.js +1 -0
- package/core/built/admin/assets/dist-DIC8yz5C.js +1 -0
- package/core/built/admin/assets/{domain-B7_n5nfx.js → domain-CPNXKVOV.js} +1 -1
- package/core/built/admin/assets/dropzone-BmCXnw6J.js +5 -0
- package/core/built/admin/assets/{edit-profile-rvDGrS8J.js → edit-profile-SGrJve5M.js} +1 -1
- package/core/built/admin/assets/editor-BOG2YPUm.js +8 -0
- package/core/built/admin/assets/{explore-BZAZOg5X.js → explore-CUYLv9G9.js} +1 -1
- package/core/built/admin/assets/{feed-CFV8xDw9.js → feed-CPdJMttL.js} +1 -1
- package/core/built/admin/assets/{field-BW-M2Zwp.js → field-D0XISCK2.js} +1 -1
- package/core/built/admin/assets/{filter-sources-Fx8wiYYw.js → filter-sources-Bieps75g.js} +1 -1
- package/core/built/admin/assets/{filters-CGX_pf2N.js → filters-CuB-A7ma.js} +1 -1
- package/core/built/admin/assets/filters-bXkkWro8.js +1 -0
- package/core/built/admin/assets/{ghost-b1dce91ad3232cc259ee5fbbf9526243.js → ghost-05f9a7182ed56903dba05878a5b8422b.js} +290 -289
- package/core/built/admin/assets/{gift-link-modal-Bk8qpnMw.js → gift-link-modal-su8MsRdg.js} +1 -1
- package/core/built/admin/assets/{growth-Ck2s3JjR.js → growth-Ba6S5FQX.js} +1 -1
- package/core/built/admin/assets/{inbox-BRp1_aaC.js → inbox-Xx1CdnfO.js} +8 -8
- package/core/built/admin/assets/index-D8PbzuT9.css +1 -0
- package/core/built/admin/assets/index-ukW_0Uie.js +67 -0
- package/core/built/admin/assets/input-group-CGOXcy4l.js +1 -0
- package/core/built/admin/assets/input-surface-BpAjZXY5.js +1 -0
- package/core/built/admin/assets/{koenig-lexical-CM7UFNdL.js → koenig-lexical-B61IALf5.js} +1 -1
- package/core/built/admin/assets/{kpi-card-CIGyijjx.js → kpi-card-BlZaG1Lo.js} +1 -1
- package/core/built/admin/assets/{kpi-card-BrGH6FsT.js → kpi-card-DCdlyaSS.js} +1 -1
- package/core/built/admin/assets/{kpi-helpers-_HRRsklE.js → kpi-helpers-DpAuS1Nb.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-BSvh9eoq.js → kpi-tabs-Dme44bQS.js} +1 -1
- package/core/built/admin/assets/{label-BOR9XdKl.js → label-dSs5aepE.js} +1 -1
- package/core/built/admin/assets/{label-picker-BdNAWuUX.js → label-picker-Uq91atex.js} +1 -1
- package/core/built/admin/assets/layout-WUcz6tu1.js +1 -0
- package/core/built/admin/assets/{member-custom-fields-DvFyamLu.js → member-custom-fields-NCzrMuba.js} +1 -1
- package/core/built/admin/assets/member-detail-DPWDtYnN.js +1 -0
- package/core/built/admin/assets/{members-DsPxYAVm.js → members-B9rO7qr4.js} +2 -2
- package/core/built/admin/assets/modals-CxRaan1J.js +31 -0
- package/core/built/admin/assets/{moderation-Dlp7M5sa.js → moderation-D8zon60E.js} +1 -1
- package/core/built/admin/assets/{multi-select-combobox-CH1o8XXQ.js → multi-select-combobox-1wYm3nle.js} +1 -1
- package/core/built/admin/assets/newsletter-DbYMRxRS.js +1 -0
- package/core/built/admin/assets/{note-BrAZ7Z0o.js → note-PiWPnnrh.js} +1 -1
- package/core/built/admin/assets/{notifications-BnbHLOPC.js → notifications-DEgqqB9G.js} +1 -1
- package/core/built/admin/assets/{onboarding-route-eU146kuA.js → onboarding-route-BzF6HeVn.js} +1 -1
- package/core/built/admin/assets/{overview-BpbaK-xu.js → overview-DixLeIqY.js} +1 -1
- package/core/built/admin/assets/{page-header-C0jVxtS8.js → page-header-op0eN36I.js} +1 -1
- package/core/built/admin/assets/{post-analytics-Iw7dk1mT.js → post-analytics-WwRDEnrV.js} +1 -1
- package/core/built/admin/assets/post-analytics-header-DsMoLx0R.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-BDqcfBor.js → post-analytics-provider-BJCPLMIb.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-DBfKl2GZ.js → post-share-modal-Dx-MEjhI.js} +1 -1
- package/core/built/admin/assets/preferences-D7-j6A4C.js +5 -0
- package/core/built/admin/assets/profile-Dl69QPp0.js +1 -0
- package/core/built/admin/assets/select-C1qDZnW4.js +1 -0
- package/core/built/admin/assets/settings-BbmC_Iqq.js +4 -0
- package/core/built/admin/assets/{settings-Bz3RZRmE.js → settings-H6HxFgfI.js} +1 -1
- package/core/built/admin/assets/shade-app-B3heOAfz-CGXQwWeO.js +3 -0
- package/core/built/admin/assets/{share-modal-CkdTTMJd.js → share-modal-BJCadJyp.js} +1 -1
- package/core/built/admin/assets/social-urls-BPL1nADp.js +8 -0
- package/core/built/admin/assets/{sort-button-DZp9Y2r1.js → sort-button-DyYlfYp7.js} +1 -1
- package/core/built/admin/assets/{source-icon-CHr9qJ2Y.js → source-icon-_ubRDbg_.js} +1 -1
- package/core/built/admin/assets/{stats-DGoXZxWE.js → stats-BQZMGMvn.js} +1 -1
- package/core/built/admin/assets/{stats-view-D2-1L0ms.js → stats-view-Doei-qCX.js} +1 -1
- package/core/built/admin/assets/{step-1-RdahbwyG.js → step-1-IKiLBPoC.js} +1 -1
- package/core/built/admin/assets/{step-2-D83PeRwd.js → step-2-jJ41PsDN.js} +1 -1
- package/core/built/admin/assets/{step-3-p_Op4wv4.js → step-3-CaqFAEqf.js} +2 -2
- package/core/built/admin/assets/switch-t-WyENU2.js +1 -0
- package/core/built/admin/assets/{table-B8LgTQJZ.js → table-DxUL78jU.js} +1 -1
- package/core/built/admin/assets/{tabs-D3ZOYkM7.js → tabs-BQcJQ1ke.js} +1 -1
- package/core/built/admin/assets/{tags-B4l891z2.js → tags-CNujGwkT.js} +1 -1
- package/core/built/admin/assets/textarea-Bjccvh_E.js +1 -0
- package/core/built/admin/assets/{toggle-group-DNXDkAwf.js → toggle-group-BeEpQ69p.js} +1 -1
- package/core/built/admin/assets/{topic-filter-DZGjEcz3.js → topic-filter-Bb5MOD-1.js} +1 -1
- package/core/built/admin/assets/{use-analytics-data-CaT8RirX.js → use-analytics-data-Cds6XDjg.js} +1 -1
- package/core/built/admin/assets/use-filter-params-DjXkhO54.js +1 -0
- package/core/built/admin/assets/{use-growth-stats-9Db2CuJe.js → use-growth-stats-D8uf98-G.js} +1 -1
- package/core/built/admin/assets/{use-post-referrers-BV54YcQS.js → use-post-referrers-CRtgyuXB.js} +1 -1
- package/core/built/admin/assets/{use-reply-chain-data-DuYZGDan.js → use-reply-chain-data-DbKYX50v.js} +1 -1
- package/core/built/admin/assets/{use-simple-pagination-8fTua9Nt.js → use-simple-pagination-DC01kpo0.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-query-Dz156HvZ.js → use-tinybird-query-C7bQRJWw.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-C9RUdL_n.js → use-tinybird-token-Az4tam7_.js} +1 -1
- package/core/built/admin/assets/{virtual-list-BCvP0YF-.js → virtual-list-Dhuh9fGx.js} +1 -1
- package/core/built/admin/assets/web-LtHNiNBV.js +1 -0
- package/core/built/admin/index.html +11 -7
- package/core/frontend/services/proxy.js +11 -1
- package/core/server/adapters/storage/LocalFilesStorage.js +13 -11
- package/core/server/adapters/storage/LocalFilesStorage.ts +24 -0
- package/core/server/adapters/storage/LocalImagesStorage.js +14 -12
- package/core/server/adapters/storage/LocalImagesStorage.ts +24 -0
- package/core/server/adapters/storage/LocalMediaStorage.js +13 -11
- package/core/server/adapters/storage/LocalMediaStorage.ts +24 -0
- package/core/server/adapters/storage/LocalStorageBase.js +107 -167
- package/core/server/adapters/storage/LocalStorageBase.ts +342 -0
- package/core/server/adapters/storage/utils.js +36 -50
- package/core/server/adapters/storage/utils.ts +60 -0
- package/core/server/api/endpoints/db.js +6 -0
- package/core/server/api/endpoints/members.js +25 -28
- package/core/server/api/endpoints/pages.js +6 -1
- package/core/server/api/endpoints/posts.js +11 -2
- package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +4 -1
- package/core/server/api/endpoints/utils/serializers/output/members.js +18 -87
- package/core/server/data/exporter/export-filename.js +2 -1
- package/core/server/data/importer/importers/data/revue-subscriber-importer.js +6 -10
- package/core/server/data/importer/importers/json-to-html.js +24 -4
- package/core/server/lib/image/image-size.js +3 -17
- package/core/server/lib/image/image-utils.js +15 -1
- package/core/server/lib/request-external.js +1 -1
- package/core/server/models/user.js +46 -70
- package/core/server/services/auth/session/session-service.js +28 -1
- package/core/server/services/automations/automations-api.js +6 -6
- package/core/server/services/automations/automations-api.ts +6 -6
- package/core/server/services/automations/automations-repository.ts +1 -0
- package/core/server/services/automations/database-automations-repository.js +7 -2
- package/core/server/services/automations/database-automations-repository.ts +8 -2
- package/core/server/services/comments/comments-service-emails.js +4 -1
- package/core/server/services/members/{importer → import-export}/config.js +4 -9
- package/core/server/services/members/{importer → import-export}/config.ts +4 -9
- package/core/server/services/members/{csv → import-export/csv}/index.js +3 -3
- package/core/server/services/members/import-export/csv/index.ts +2 -0
- package/core/server/services/members/import-export/csv/parse.js +64 -0
- package/core/server/services/members/import-export/csv/parse.ts +69 -0
- package/core/server/services/members/import-export/csv/serialize.js +13 -0
- package/core/server/services/members/import-export/csv/serialize.ts +18 -0
- package/core/server/services/members/import-export/export/exporter.js +202 -0
- package/core/server/services/members/import-export/export/exporter.ts +325 -0
- package/core/server/services/members/import-export/import/completion-email.js +65 -0
- package/core/server/services/members/import-export/import/completion-email.ts +119 -0
- package/core/server/services/members/import-export/import/importer.js +276 -0
- package/core/server/services/members/import-export/import/importer.ts +430 -0
- package/core/server/services/members/import-export/import/reader.js +32 -0
- package/core/server/services/members/import-export/import/reader.ts +33 -0
- package/core/server/services/members/import-export/import/row.js +33 -0
- package/core/server/services/members/import-export/import/row.ts +52 -0
- package/core/server/services/members/import-export/import/spool.js +30 -0
- package/core/server/services/members/import-export/import/spool.ts +38 -0
- package/core/server/services/members/import-export/index.js +90 -0
- package/core/server/services/members/import-export/index.ts +118 -0
- package/core/server/services/members/members-api/controllers/router-controller.js +7 -0
- package/core/server/services/members/service.js +17 -19
- package/core/server/services/posts/posts-exporter.js +6 -4
- package/core/server/services/posts/posts-service.js +2 -2
- package/core/server/services/themes/storage.js +9 -0
- package/core/server/services/themes/theme-storage.js +1 -1
- package/core/shared/config/defaults.json +2 -1
- package/core/shared/express.js +6 -1
- package/core/shared/labs.js +2 -1
- package/package.json +9 -9
- package/pnpm-lock.yaml +17 -17
- package/pnpm-workspace.yaml +7 -7
- package/components/tryghost-adapter-base-scheduling-0.1.1.tgz +0 -0
- package/core/built/admin/assets/Growth-CSaHoSVD.js +0 -3
- package/core/built/admin/assets/Newsletters-C5cs6FGo.js +0 -1
- package/core/built/admin/assets/Overview-j4NaeUNm.js +0 -1
- package/core/built/admin/assets/Web-_fD935OX.js +0 -2
- package/core/built/admin/assets/activitypub/layout-IdVq3lRy.js +0 -1
- package/core/built/admin/assets/checkbox-DA4-1UiI.js +0 -1
- package/core/built/admin/assets/chunk.524.45e0875f41b71e46e849.js +0 -25
- package/core/built/admin/assets/chunk.582.51defd4b009f0de8d4d5.js +0 -27
- package/core/built/admin/assets/code-editor-view-BKBrkffZ.js +0 -1
- package/core/built/admin/assets/comments-CzVNIT8b.js +0 -1
- package/core/built/admin/assets/dist-CT46_f_J.js +0 -1
- package/core/built/admin/assets/dropzone-DcuJEEUf.js +0 -5
- package/core/built/admin/assets/editor-BCiIyFjm.js +0 -8
- package/core/built/admin/assets/filters-DtcXMZDU.js +0 -1
- package/core/built/admin/assets/index-BBf502Wg.js +0 -69
- package/core/built/admin/assets/index-BCCZKxP2.css +0 -1
- package/core/built/admin/assets/input-group-DjuYnNCd.js +0 -1
- package/core/built/admin/assets/layout-Bw4GnxXp.js +0 -1
- package/core/built/admin/assets/member-detail-Cg2vTqvJ.js +0 -1
- package/core/built/admin/assets/modals-Bf_fyvHZ.js +0 -31
- package/core/built/admin/assets/newsletter-CQ5CAYUI.js +0 -1
- package/core/built/admin/assets/post-analytics-header-D9pJ4iNm.js +0 -1
- package/core/built/admin/assets/preferences-D4EANGoc.js +0 -5
- package/core/built/admin/assets/profile-dJ44Bn_W.js +0 -1
- package/core/built/admin/assets/select--2uVN9_-.js +0 -1
- package/core/built/admin/assets/settings-SD9bxIpt.js +0 -4
- package/core/built/admin/assets/social-urls-BvFZwYcE.js +0 -8
- package/core/built/admin/assets/switch-qp518gFQ.js +0 -1
- package/core/built/admin/assets/textarea-CTlb3Uu6.js +0 -1
- package/core/built/admin/assets/use-filter-params-DeQ8sX4N.js +0 -1
- package/core/built/admin/assets/web-DTZnnUGr.js +0 -1
- package/core/server/services/mail/templates/newsletter.html +0 -876
- package/core/server/services/members/csv/index.ts +0 -3
- package/core/server/services/members/csv/parse.js +0 -94
- package/core/server/services/members/csv/parse.ts +0 -111
- package/core/server/services/members/csv/types.js +0 -2
- package/core/server/services/members/csv/types.ts +0 -39
- package/core/server/services/members/csv/unparse.js +0 -69
- package/core/server/services/members/csv/unparse.ts +0 -79
- package/core/server/services/members/exporter/query.js +0 -233
- package/core/server/services/members/importer/index.js +0 -30
- package/core/server/services/members/importer/labels.js +0 -125
- package/core/server/services/members/importer/members-csv-importer.js +0 -520
- /package/core/server/services/members/{importer → import-export/import}/email-template.js +0 -0
- /package/core/server/services/members/{importer/members-csv-importer-stripe-utils.js → import-export/import/stripe-utils.js} +0 -0
|
@@ -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,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
|
-
};
|