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
|
@@ -0,0 +1,276 @@
|
|
|
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
|
+
const moment_timezone_1 = __importDefault(require("moment-timezone"));
|
|
7
|
+
const completion_email_1 = __importDefault(require("./completion-email"));
|
|
8
|
+
const metrics = require('@tryghost/metrics');
|
|
9
|
+
const errors = require('@tryghost/errors');
|
|
10
|
+
const tpl = require('@tryghost/tpl');
|
|
11
|
+
const logging = require('@tryghost/logging');
|
|
12
|
+
const messages = {
|
|
13
|
+
freeMemberNotAllowedImportTier: 'You cannot import a free member with a specified tier.',
|
|
14
|
+
invalidImportTier: '"{tier}" is not a valid tier.',
|
|
15
|
+
giftCannotCombineWithImportTier: 'Cannot specify both gift_id and import_tier.',
|
|
16
|
+
giftCannotCombineWithComplimentary: 'Cannot specify both gift_id and complimentary_plan.',
|
|
17
|
+
giftReassignFailed: 'Failed to reassign gift to member.'
|
|
18
|
+
};
|
|
19
|
+
// Columns whose presence makes a row slow to import (they reach out to Stripe), so
|
|
20
|
+
// a file carrying any of them is deferred regardless of size. The routing decision
|
|
21
|
+
// only ever sees the boolean this produces, never the column names themselves.
|
|
22
|
+
const EXPENSIVE_COLUMNS = ['stripe_customer_id'];
|
|
23
|
+
// The context each repository call runs under: the import identifies itself, and a
|
|
24
|
+
// created member is additionally flagged as imported so downstream hooks can tell.
|
|
25
|
+
const IMPORT_CONTEXT = { importer: true };
|
|
26
|
+
const CREATE_CONTEXT = { import: true };
|
|
27
|
+
// The import label members are tagged with, named for the request time. Shared by
|
|
28
|
+
// the kernel (the label) and the deferred email (the error CSV filename).
|
|
29
|
+
function buildImportLabelName(timezone) {
|
|
30
|
+
return `Import ${(0, moment_timezone_1.default)().tz(timezone).format('YYYY-MM-DD HH:mm')}`;
|
|
31
|
+
}
|
|
32
|
+
function hasExpensiveColumns(rows) {
|
|
33
|
+
return rows.some(row => EXPENSIVE_COLUMNS.some(column => !!row[column]));
|
|
34
|
+
}
|
|
35
|
+
// The routing decision, in terms it can reason about without knowing what a member
|
|
36
|
+
// or a Stripe customer is: an empty or small, cheap file imports inline (an empty
|
|
37
|
+
// one simply imports nothing); a large one, or one carrying expensive columns, is
|
|
38
|
+
// too slow to hold a request open, so it is deferred to a background job.
|
|
39
|
+
function canImportInline(rowCount, expensive, inlineThreshold) {
|
|
40
|
+
return rowCount <= inlineThreshold && !expensive;
|
|
41
|
+
}
|
|
42
|
+
class MembersCSVImporter {
|
|
43
|
+
_knex;
|
|
44
|
+
_readRows;
|
|
45
|
+
_spool;
|
|
46
|
+
_members;
|
|
47
|
+
_tiers;
|
|
48
|
+
_stripe;
|
|
49
|
+
_gifts;
|
|
50
|
+
_email;
|
|
51
|
+
_addJob;
|
|
52
|
+
_getTimezone;
|
|
53
|
+
_getInlineThreshold;
|
|
54
|
+
constructor({ knex, readRows, spool, members, tiers, stripe, gifts, email, addJob, getTimezone, getInlineThreshold }) {
|
|
55
|
+
this._knex = knex;
|
|
56
|
+
this._readRows = readRows;
|
|
57
|
+
this._spool = spool;
|
|
58
|
+
this._members = members;
|
|
59
|
+
this._tiers = tiers;
|
|
60
|
+
this._stripe = stripe;
|
|
61
|
+
this._gifts = gifts;
|
|
62
|
+
this._email = email;
|
|
63
|
+
this._addJob = addJob;
|
|
64
|
+
this._getTimezone = getTimezone;
|
|
65
|
+
this._getInlineThreshold = getInlineThreshold;
|
|
66
|
+
}
|
|
67
|
+
async importCSV(request, verificationTrigger) {
|
|
68
|
+
const rows = await this._readRows(request.filePath, request.mapping);
|
|
69
|
+
const labelName = buildImportLabelName(this._getTimezone());
|
|
70
|
+
const extraLabels = request.extraLabels ?? [];
|
|
71
|
+
if (canImportInline(rows.length, hasExpensiveColumns(rows), this._getInlineThreshold())) {
|
|
72
|
+
const result = await this.importRows(rows, labelName, extraLabels);
|
|
73
|
+
await verificationTrigger.testImportThreshold();
|
|
74
|
+
return { deferred: false, originalImportSize: rows.length, result };
|
|
75
|
+
}
|
|
76
|
+
await this.deferImport(rows, { labelName, extraLabels, requestUserEmail: request.requestUserEmail }, verificationTrigger);
|
|
77
|
+
return { deferred: true, originalImportSize: rows.length };
|
|
78
|
+
}
|
|
79
|
+
async importInline(request, verificationTrigger) {
|
|
80
|
+
const rows = await this._readRows(request.filePath, request.mapping);
|
|
81
|
+
const result = await this.importRows(rows, buildImportLabelName(this._getTimezone()), request.extraLabels ?? []);
|
|
82
|
+
await verificationTrigger.testImportThreshold();
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
85
|
+
async deferImport(rows, { labelName, extraLabels, requestUserEmail }, verificationTrigger) {
|
|
86
|
+
// Resolved here, not at the API boundary, so the owner lookup only runs when
|
|
87
|
+
// a request without a user actually reaches the deferred path.
|
|
88
|
+
const emailRecipient = requestUserEmail ?? await this._email.getDefaultRecipient();
|
|
89
|
+
const spooled = await this._spool.write(rows);
|
|
90
|
+
this._addJob({
|
|
91
|
+
job: () => this.runImportJob(spooled, { labelName, extraLabels, emailRecipient }, verificationTrigger),
|
|
92
|
+
offloaded: false,
|
|
93
|
+
name: 'members-import'
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
// Swallows its own errors: a failed import or email must not leave the queued job
|
|
97
|
+
// rejected, and the verification trigger must still run afterwards either way.
|
|
98
|
+
async runImportJob(spooled, { labelName, extraLabels, emailRecipient }, verificationTrigger) {
|
|
99
|
+
try {
|
|
100
|
+
const spooledRows = await spooled.read();
|
|
101
|
+
const result = await this.importRows(spooledRows, labelName, extraLabels);
|
|
102
|
+
const importLabel = result.importLabel ?? null;
|
|
103
|
+
await this._email.send((0, completion_email_1.default)({
|
|
104
|
+
result,
|
|
105
|
+
recipient: emailRecipient,
|
|
106
|
+
labelName,
|
|
107
|
+
importLabel,
|
|
108
|
+
urlFor: this._email.urlFor
|
|
109
|
+
}));
|
|
110
|
+
}
|
|
111
|
+
catch (e) {
|
|
112
|
+
logging.error('Error in members import job');
|
|
113
|
+
logging.error(e);
|
|
114
|
+
}
|
|
115
|
+
finally {
|
|
116
|
+
await spooled.remove();
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
await verificationTrigger.testImportThreshold();
|
|
120
|
+
}
|
|
121
|
+
catch (e) {
|
|
122
|
+
logging.error('Error in members import job when testing import threshold');
|
|
123
|
+
logging.error(e);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// The members import kernel, shared by the inline and deferred paths. Each row is
|
|
127
|
+
// created or updated in its own transaction, so one row's failure rolls back only
|
|
128
|
+
// itself; failures are collected, and any Stripe prices an import tier created are
|
|
129
|
+
// archived at the end.
|
|
130
|
+
async importRows(rows, labelName, extraLabels) {
|
|
131
|
+
const importLabel = { name: labelName };
|
|
132
|
+
const globalLabels = [importLabel, ...extraLabels];
|
|
133
|
+
const performStart = Date.now();
|
|
134
|
+
const defaultTier = await this._tiers.getDefault();
|
|
135
|
+
const tierIdCache = new Map();
|
|
136
|
+
const archivableStripePriceIds = [];
|
|
137
|
+
// Copied per row: the member model stamps ids and trims names onto these in
|
|
138
|
+
// place, and each row runs in its own transaction that can roll back. A
|
|
139
|
+
// caller can hand in a nameless label, which the model would drop anyway.
|
|
140
|
+
const cloneGlobalLabels = () => globalLabels
|
|
141
|
+
.map(label => ({ ...label }))
|
|
142
|
+
.filter(label => label.name);
|
|
143
|
+
let imported = 0;
|
|
144
|
+
const importErrors = [];
|
|
145
|
+
for (const row of rows) {
|
|
146
|
+
const trx = await this._knex.transaction(undefined, { doNotRejectOnRollback: false });
|
|
147
|
+
const options = { transacting: trx, context: IMPORT_CONTEXT };
|
|
148
|
+
try {
|
|
149
|
+
if (row.gift_id) {
|
|
150
|
+
if (row.import_tier) {
|
|
151
|
+
throw wrapGiftError(new errors.DataImportError({ message: tpl(messages.giftCannotCombineWithImportTier) }));
|
|
152
|
+
}
|
|
153
|
+
if (row.complimentary_plan) {
|
|
154
|
+
throw wrapGiftError(new errors.DataImportError({ message: tpl(messages.giftCannotCombineWithComplimentary) }));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const createdAt = (row.created_at && (0, moment_timezone_1.default)(row.created_at).isAfter((0, moment_timezone_1.default)())) ? (0, moment_timezone_1.default)().toDate() : row.created_at;
|
|
158
|
+
const memberValues = {
|
|
159
|
+
email: row.email,
|
|
160
|
+
name: row.name,
|
|
161
|
+
note: row.note,
|
|
162
|
+
subscribed: row.subscribed,
|
|
163
|
+
created_at: createdAt,
|
|
164
|
+
labels: [...row.labels, ...cloneGlobalLabels()]
|
|
165
|
+
};
|
|
166
|
+
const existingMember = row.email
|
|
167
|
+
? await this._members.get({ email: row.email }, { ...options, withRelated: ['labels', 'newsletters'] })
|
|
168
|
+
: null;
|
|
169
|
+
let member;
|
|
170
|
+
if (existingMember) {
|
|
171
|
+
const existingLabels = existingMember.related('labels').toJSON();
|
|
172
|
+
const existingNewsletters = existingMember.related('newsletters');
|
|
173
|
+
if (existingNewsletters.length > 0 && memberValues.subscribed) {
|
|
174
|
+
memberValues.newsletters = existingNewsletters.toJSON();
|
|
175
|
+
}
|
|
176
|
+
if (!existingNewsletters.length && memberValues.subscribed) {
|
|
177
|
+
memberValues.subscribed = false;
|
|
178
|
+
}
|
|
179
|
+
if (!row.name) {
|
|
180
|
+
memberValues.name = existingMember.name;
|
|
181
|
+
}
|
|
182
|
+
if (!row.note) {
|
|
183
|
+
memberValues.note = existingMember.note;
|
|
184
|
+
}
|
|
185
|
+
member = await this._members.update({
|
|
186
|
+
...memberValues,
|
|
187
|
+
labels: existingLabels.concat(memberValues.labels)
|
|
188
|
+
}, { ...options, id: existingMember.id });
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
member = await this._members.create(memberValues, Object.assign({}, options, { context: CREATE_CONTEXT }));
|
|
192
|
+
}
|
|
193
|
+
let importTierId;
|
|
194
|
+
if (row.import_tier) {
|
|
195
|
+
if (!tierIdCache.has(row.import_tier)) {
|
|
196
|
+
const tier = await this._tiers.getByName(row.import_tier);
|
|
197
|
+
tierIdCache.set(row.import_tier, tier ? tier.id.toString() : null);
|
|
198
|
+
}
|
|
199
|
+
importTierId = tierIdCache.get(row.import_tier);
|
|
200
|
+
if (!importTierId) {
|
|
201
|
+
throw new errors.DataImportError({ message: tpl(messages.invalidImportTier, { tier: row.import_tier }) });
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
if (row.stripe_customer_id) {
|
|
205
|
+
let stripeCustomerId;
|
|
206
|
+
if (row.stripe_customer_id.toLowerCase() === 'auto') {
|
|
207
|
+
stripeCustomerId = row.email ? await this._members.getCustomerIdByEmail(row.email) : undefined;
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
stripeCustomerId = row.stripe_customer_id;
|
|
211
|
+
}
|
|
212
|
+
if (stripeCustomerId) {
|
|
213
|
+
if (row.import_tier) {
|
|
214
|
+
const { isNewStripePrice, stripePriceId } = await this._stripe.forceStripeSubscriptionToProduct({
|
|
215
|
+
customer_id: stripeCustomerId,
|
|
216
|
+
product_id: importTierId
|
|
217
|
+
}, options);
|
|
218
|
+
if (isNewStripePrice) {
|
|
219
|
+
archivableStripePriceIds.push(stripePriceId);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
await this._members.linkStripeCustomer({ customer_id: stripeCustomerId, member_id: member.id }, options);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
else if (row.complimentary_plan) {
|
|
226
|
+
const products = [];
|
|
227
|
+
if (row.import_tier) {
|
|
228
|
+
products.push({ id: importTierId });
|
|
229
|
+
}
|
|
230
|
+
else {
|
|
231
|
+
products.push({ id: defaultTier.id.toString() });
|
|
232
|
+
}
|
|
233
|
+
await this._members.update({ products }, { ...options, id: member.id });
|
|
234
|
+
}
|
|
235
|
+
else if (row.import_tier) {
|
|
236
|
+
throw new errors.DataImportError({ message: tpl(messages.freeMemberNotAllowedImportTier) });
|
|
237
|
+
}
|
|
238
|
+
if (row.gift_id) {
|
|
239
|
+
try {
|
|
240
|
+
await this._gifts.reassignRedeemer(row.gift_id, member.id, { transacting: trx });
|
|
241
|
+
}
|
|
242
|
+
catch (giftError) {
|
|
243
|
+
throw wrapGiftError(giftError);
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
await trx.commit();
|
|
247
|
+
imported += 1;
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
const errorList = Array.isArray(error) ? error : [error];
|
|
251
|
+
const errorMessage = errorList
|
|
252
|
+
.map(e => (typeof e === 'object' && e !== null && 'message' in e ? e.message : undefined))
|
|
253
|
+
.join(', ');
|
|
254
|
+
await trx.rollback();
|
|
255
|
+
importErrors.push({ ...row, error: errorMessage });
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
await Promise.all(archivableStripePriceIds.map(id => this._stripe.archivePrice(id)));
|
|
259
|
+
metrics.metric('members-import', { imported, errors: importErrors.length, value: Date.now() - performStart });
|
|
260
|
+
if (imported > 0) {
|
|
261
|
+
// The import label exists now that members carry it, so fetch it to report.
|
|
262
|
+
// Guarded against a null lookup in case a row is ever counted without it.
|
|
263
|
+
const importLabelModel = await this._members.getImportLabel(labelName);
|
|
264
|
+
return { imported, errors: importErrors, importLabel: importLabelModel?.toJSON() };
|
|
265
|
+
}
|
|
266
|
+
return { imported: 0, errors: importErrors };
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
function wrapGiftError(error) {
|
|
270
|
+
const err = error;
|
|
271
|
+
const message = (err && typeof err.message === 'string' && err.message) || tpl(messages.giftReassignFailed);
|
|
272
|
+
return new errors.DataImportError({
|
|
273
|
+
message: `Member cannot be assigned to a gift: ${message}`
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
exports.default = MembersCSVImporter;
|