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,10 +1,12 @@
|
|
|
1
1
|
//@ts-check
|
|
2
2
|
const _ = require('lodash');
|
|
3
3
|
const debug = require('@tryghost/debug')('api:endpoints:utils:serializers:output:members');
|
|
4
|
-
const {
|
|
4
|
+
const {serialize: serializeCSV} = require('../../../../../services/members/import-export/csv');
|
|
5
|
+
// The export CSV row shape and its encode live in the domain (typed against the export
|
|
6
|
+
// row) rather than here, so a change to the export row is caught by the compiler.
|
|
7
|
+
const {toExportCsvRow} = require('../../../../../services/members/import-export/export/exporter');
|
|
5
8
|
const mappers = require('./mappers');
|
|
6
9
|
const {Transform} = require('stream');
|
|
7
|
-
const papaparse = require('papaparse');
|
|
8
10
|
const {createCSVStreamResponse} = require('./stream-csv-response');
|
|
9
11
|
module.exports = {
|
|
10
12
|
browse: createSerializer('browse', paginatedMembers),
|
|
@@ -25,56 +27,6 @@ module.exports = {
|
|
|
25
27
|
activityFeed: createSerializer('activityFeed', activityFeed)
|
|
26
28
|
};
|
|
27
29
|
|
|
28
|
-
/**
|
|
29
|
-
* Formats a single member for CSV export
|
|
30
|
-
* @param {Object} member - Member object
|
|
31
|
-
* @returns {Object} Formatted member
|
|
32
|
-
*/
|
|
33
|
-
function formatMemberForCSV(member) {
|
|
34
|
-
let labels = '';
|
|
35
|
-
if (Array.isArray(member.labels)) {
|
|
36
|
-
labels = member.labels.map((l) => {
|
|
37
|
-
return typeof l === 'string' ? l : l.name;
|
|
38
|
-
}).join(',');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
let tiers = '';
|
|
42
|
-
if (Array.isArray(member.tiers)) {
|
|
43
|
-
tiers = member.tiers.map((tier) => {
|
|
44
|
-
return tier.name;
|
|
45
|
-
}).join(',');
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Convert boolean 'false' to empty string for tests to pass
|
|
49
|
-
// Only comped = true should result in 'true', otherwise empty string
|
|
50
|
-
const complimentaryPlan = member.comped === true ? 'true' : '';
|
|
51
|
-
|
|
52
|
-
// Gift members carry the gift id so an exported CSV can be re-imported and reassigned
|
|
53
|
-
// back to a (possibly new) member record via the gifts table
|
|
54
|
-
const giftId = member.gift_id || '';
|
|
55
|
-
|
|
56
|
-
// Convert subscribed boolean to string representation
|
|
57
|
-
const subscribedToEmails = member.subscribed === true ? 'true' : 'false';
|
|
58
|
-
|
|
59
|
-
return {
|
|
60
|
-
id: member.id,
|
|
61
|
-
email: member.email,
|
|
62
|
-
name: member.name,
|
|
63
|
-
note: member.note,
|
|
64
|
-
subscribed_to_emails: subscribedToEmails,
|
|
65
|
-
complimentary_plan: complimentaryPlan,
|
|
66
|
-
stripe_customer_id: member.stripe_customer_id,
|
|
67
|
-
created_at: member.created_at,
|
|
68
|
-
deleted_at: member.deleted_at || null,
|
|
69
|
-
labels: labels,
|
|
70
|
-
tiers: tiers,
|
|
71
|
-
gift_id: giftId,
|
|
72
|
-
// The exporter flattens custom field values into their CSV columns, since
|
|
73
|
-
// which columns exist is a per-site question only the database can answer.
|
|
74
|
-
...member.custom_field_cells
|
|
75
|
-
};
|
|
76
|
-
}
|
|
77
|
-
|
|
78
30
|
/**
|
|
79
31
|
* @template PageMeta
|
|
80
32
|
*
|
|
@@ -426,30 +378,16 @@ function createCSVTransform() {
|
|
|
426
378
|
transform(member, encoding, callback) {
|
|
427
379
|
try {
|
|
428
380
|
// Format the member data for CSV
|
|
429
|
-
const formattedMember =
|
|
381
|
+
const formattedMember = toExportCsvRow(member);
|
|
430
382
|
|
|
431
383
|
// For first chunk, include the headers
|
|
432
384
|
if (fields === null) {
|
|
433
385
|
fields = Object.keys(formattedMember);
|
|
434
|
-
const csv =
|
|
435
|
-
fields,
|
|
436
|
-
data: [formattedMember]
|
|
437
|
-
}, {
|
|
438
|
-
header: true,
|
|
439
|
-
escapeFormulae: true,
|
|
440
|
-
newline: '\r\n' // Explicitly set Windows-style line endings for compatibility
|
|
441
|
-
});
|
|
386
|
+
const csv = serializeCSV([formattedMember], {columns: fields, header: true});
|
|
442
387
|
callback(null, csv);
|
|
443
388
|
} else {
|
|
444
389
|
// For subsequent chunks, don't include headers, just the data
|
|
445
|
-
const csv =
|
|
446
|
-
fields,
|
|
447
|
-
data: [formattedMember]
|
|
448
|
-
}, {
|
|
449
|
-
header: false,
|
|
450
|
-
escapeFormulae: true,
|
|
451
|
-
newline: '\r\n' // Explicitly set Windows-style line endings for compatibility
|
|
452
|
-
});
|
|
390
|
+
const csv = serializeCSV([formattedMember], {columns: fields, header: false});
|
|
453
391
|
|
|
454
392
|
// Make sure each row starts with a newline to ensure separation between rows
|
|
455
393
|
// Ensure consistent line endings by using explicit CR+LF sequence
|
|
@@ -465,28 +403,21 @@ function createCSVTransform() {
|
|
|
465
403
|
/**
|
|
466
404
|
* @template PageMeta
|
|
467
405
|
*
|
|
468
|
-
* @param {{data:
|
|
406
|
+
* @param {{data: NodeJS.ReadableStream, filename?: string}} data
|
|
469
407
|
*
|
|
470
|
-
* @returns {
|
|
408
|
+
* @returns {Function} - A response handler that streams the CSV
|
|
471
409
|
*/
|
|
472
410
|
function exportCSV(data) {
|
|
473
411
|
debug('exportCSV');
|
|
474
412
|
|
|
475
|
-
//
|
|
476
|
-
|
|
477
|
-
|
|
413
|
+
// The export endpoint always yields a stream (response.stream: true), so there is
|
|
414
|
+
// only ever a stream to pipe.
|
|
415
|
+
const datetime = (new Date()).toJSON().substring(0, 10);
|
|
416
|
+
const filename = data.filename || `members.${datetime}.csv`;
|
|
478
417
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
source: data.data,
|
|
485
|
-
transform: createCSVTransform(),
|
|
486
|
-
filename
|
|
487
|
-
});
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// Otherwise use the unparse function for array data
|
|
491
|
-
return unparse(data.data);
|
|
418
|
+
return createCSVStreamResponse({
|
|
419
|
+
source: data.data,
|
|
420
|
+
transform: createCSVTransform(),
|
|
421
|
+
filename
|
|
422
|
+
});
|
|
492
423
|
}
|
|
@@ -3,6 +3,7 @@ const logging = require('@tryghost/logging');
|
|
|
3
3
|
const errors = require('@tryghost/errors');
|
|
4
4
|
const security = require('@tryghost/security');
|
|
5
5
|
const models = require('../../models');
|
|
6
|
+
const path = require('path');
|
|
6
7
|
|
|
7
8
|
const modelOptions = {context: {internal: true}};
|
|
8
9
|
|
|
@@ -14,7 +15,7 @@ const exportFileName = async function exportFileName(options) {
|
|
|
14
15
|
|
|
15
16
|
// custom filename
|
|
16
17
|
if (options.filename) {
|
|
17
|
-
return options.filename + '.json';
|
|
18
|
+
return path.basename(options.filename) + '.json';
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
try {
|
|
@@ -6,7 +6,6 @@ const path = require('path');
|
|
|
6
6
|
const fs = require('fs-extra');
|
|
7
7
|
|
|
8
8
|
const config = require('../../../../../shared/config');
|
|
9
|
-
const models = require('../../../../models');
|
|
10
9
|
|
|
11
10
|
class RevueSubscriberImporter extends BaseImporter {
|
|
12
11
|
constructor(allDataFromFile) {
|
|
@@ -38,17 +37,14 @@ class RevueSubscriberImporter extends BaseImporter {
|
|
|
38
37
|
const outputFilePath = path.join(config.getContentPath('data'), '/', outputFileName);
|
|
39
38
|
const csvData = papaparse.unparse(this.dataToImport);
|
|
40
39
|
|
|
41
|
-
const memberImporterOptions = {
|
|
42
|
-
pathToCSV: outputFilePath,
|
|
43
|
-
globalLabels: [{name: importLabel}],
|
|
44
|
-
importLabel: {name: importLabel},
|
|
45
|
-
LabelModel: models.Label,
|
|
46
|
-
forceInline: true
|
|
47
|
-
};
|
|
48
|
-
|
|
49
40
|
await fs.writeFile(outputFilePath, csvData);
|
|
50
41
|
|
|
51
|
-
|
|
42
|
+
// Inline: the data import has no request to hold open and needs the result now.
|
|
43
|
+
return membersService.importInline({
|
|
44
|
+
filePath: outputFilePath,
|
|
45
|
+
extraLabels: [{name: importLabel}],
|
|
46
|
+
requestUserEmail: null
|
|
47
|
+
});
|
|
52
48
|
}
|
|
53
49
|
}
|
|
54
50
|
|
|
@@ -4,6 +4,25 @@ const {cards} = require('@tryghost/kg-default-cards');
|
|
|
4
4
|
const imageCard = cards.find(c => c.name === 'image');
|
|
5
5
|
const embedCard = cards.find(c => c.name === 'embed');
|
|
6
6
|
|
|
7
|
+
const getValidURL = (url) => {
|
|
8
|
+
const normalizedURL = typeof url === 'string' ? url.trim() : '';
|
|
9
|
+
|
|
10
|
+
if (!normalizedURL) {
|
|
11
|
+
return '';
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const parsedURL = new URL(normalizedURL, 'https://example.com');
|
|
16
|
+
if (parsedURL.protocol === 'http:' || parsedURL.protocol === 'https:') {
|
|
17
|
+
return normalizedURL;
|
|
18
|
+
}
|
|
19
|
+
} catch {
|
|
20
|
+
// Invalid URLs are omitted from imported links
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return '';
|
|
24
|
+
};
|
|
25
|
+
|
|
7
26
|
// Take the array of items for a specific post and return the converted HTML
|
|
8
27
|
const itemsToHtml = (items) => {
|
|
9
28
|
let itemHTMLChunks = [];
|
|
@@ -30,22 +49,23 @@ const itemsToHtml = (items) => {
|
|
|
30
49
|
} else if (type === 'link') {
|
|
31
50
|
// This could be a bookmark, or it could be a paragraph of text with a linked header, there's no way to tell
|
|
32
51
|
// The safest option here is to output an image with text under it
|
|
52
|
+
const itemURL = getValidURL(item.url);
|
|
33
53
|
let cardOpts = {
|
|
34
54
|
env: {dom: new SimpleDom.Document()},
|
|
35
55
|
payload: {
|
|
36
56
|
src: item.image,
|
|
37
57
|
caption: item.title,
|
|
38
|
-
href:
|
|
58
|
+
href: itemURL
|
|
39
59
|
}
|
|
40
60
|
};
|
|
41
61
|
itemHTMLChunks.push(serializer.serialize(imageCard.render(cardOpts)));
|
|
42
|
-
|
|
43
|
-
let linkHTML = `<h4
|
|
62
|
+
const linkTitleHTML = itemURL ? `<a href="${serializer.escapeAttrValue(itemURL)}">${item.title}</a>` : item.title;
|
|
63
|
+
let linkHTML = `<h4>${linkTitleHTML}</h4>${item.description}`;
|
|
44
64
|
itemHTMLChunks.push(linkHTML);
|
|
45
65
|
} else if (type === 'tweet') {
|
|
46
66
|
// Should this be an oEmbed call? Probably.
|
|
47
67
|
itemHTMLChunks.push(`<figure class="kg-card kg-embed-card">
|
|
48
|
-
<blockquote class="twitter-tweet"><a href="${item.url}"></a></blockquote>
|
|
68
|
+
<blockquote class="twitter-tweet"><a href="${serializer.escapeAttrValue(item.url)}"></a></blockquote>
|
|
49
69
|
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>
|
|
50
70
|
</figure>`);
|
|
51
71
|
} else if (type === 'video') {
|
|
@@ -17,29 +17,15 @@ const FETCH_ONLY_FORMATS = [
|
|
|
17
17
|
];
|
|
18
18
|
|
|
19
19
|
class ImageSize {
|
|
20
|
-
constructor({config, imageStore, storageUtils, validator, urlUtils,
|
|
20
|
+
constructor({config, imageStore, storageUtils, validator, urlUtils, fetchExternal, probe}) {
|
|
21
21
|
this.config = config;
|
|
22
22
|
this.imageStore = imageStore;
|
|
23
23
|
this.storageUtils = storageUtils;
|
|
24
24
|
this.validator = validator;
|
|
25
25
|
this.urlUtils = urlUtils;
|
|
26
|
-
this.
|
|
26
|
+
this.fetchExternal = fetchExternal;
|
|
27
27
|
this.probe = probe;
|
|
28
28
|
|
|
29
|
-
this.REQUEST_OPTIONS = {
|
|
30
|
-
// we need the user-agent, otherwise some https request may fail (e.g. cloudfare)
|
|
31
|
-
headers: {
|
|
32
|
-
'User-Agent': 'Mozilla/5.0 Safari/537.36'
|
|
33
|
-
},
|
|
34
|
-
timeout: {
|
|
35
|
-
request: this.config.get('times:getImageSizeTimeoutInMS') || 10000
|
|
36
|
-
},
|
|
37
|
-
retry: {
|
|
38
|
-
limit: 0 // for `got`, used with image-size
|
|
39
|
-
},
|
|
40
|
-
responseType: 'buffer'
|
|
41
|
-
};
|
|
42
|
-
|
|
43
29
|
this.NEEDLE_OPTIONS = {
|
|
44
30
|
// we need the user-agent, otherwise some https request may fail (e.g. cloudflare)
|
|
45
31
|
headers: {
|
|
@@ -107,7 +93,7 @@ class ImageSize {
|
|
|
107
93
|
// download full image then use image-size to get it's dimensions
|
|
108
94
|
// returns promise which resolves dimensions
|
|
109
95
|
_fetchImageSizeFromUrl(imageUrl) {
|
|
110
|
-
return this.
|
|
96
|
+
return this.fetchExternal(imageUrl, this.NEEDLE_OPTIONS).then((response) => {
|
|
111
97
|
return this._imageSizeFromBuffer(response.body);
|
|
112
98
|
});
|
|
113
99
|
}
|
|
@@ -24,10 +24,23 @@ function probe(url, options = {}, {probeImageSize: probeImageSizeFn = probeImage
|
|
|
24
24
|
return promise;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
+
// For image formats that require a full fetch. This function ensures that
|
|
28
|
+
// SSRF protections are in place by using the externalRequest `got` client.
|
|
29
|
+
function fetchExternal(url, options = {}) {
|
|
30
|
+
return externalRequest.get(url, {
|
|
31
|
+
headers: options.headers,
|
|
32
|
+
timeout: {
|
|
33
|
+
request: options.response_timeout || 10000
|
|
34
|
+
},
|
|
35
|
+
responseType: 'buffer',
|
|
36
|
+
retry: {limit: 0}
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
27
40
|
class ImageUtils {
|
|
28
41
|
constructor({config, urlUtils, settingsCache, storageUtils, imageStore, validator, request, cacheStore}) {
|
|
29
42
|
this.blogIcon = new BlogIcon({config, urlUtils, settingsCache, storageUtils});
|
|
30
|
-
this.imageSize = new ImageSize({config, imageStore, storageUtils, validator, urlUtils,
|
|
43
|
+
this.imageSize = new ImageSize({config, imageStore, storageUtils, validator, urlUtils, fetchExternal, probe});
|
|
31
44
|
this.cachedImageSizeFromUrl = new CachedImageSizeFromUrl({
|
|
32
45
|
getImageSizeFromUrl: this.imageSize.getImageSizeFromUrl.bind(this.imageSize),
|
|
33
46
|
cache: cacheStore
|
|
@@ -38,3 +51,4 @@ class ImageUtils {
|
|
|
38
51
|
|
|
39
52
|
module.exports = ImageUtils;
|
|
40
53
|
module.exports.probe = probe;
|
|
54
|
+
module.exports.fetchExternal = fetchExternal;
|
|
@@ -248,7 +248,7 @@ function installSafeDnsLookup(options) {
|
|
|
248
248
|
const requestHref = options.url.href;
|
|
249
249
|
// Use 'lookup' (the native http.request option) rather than 'dnsLookup'
|
|
250
250
|
// (got's public API property which doesn't flow to the native request).
|
|
251
|
-
options.
|
|
251
|
+
options.dnsLookup = (hostname, dnsOpts, callback) => {
|
|
252
252
|
if (typeof dnsOpts === 'function') {
|
|
253
253
|
callback = dnsOpts;
|
|
254
254
|
dnsOpts = {};
|
|
@@ -6,7 +6,6 @@ const limitService = require('../services/limits');
|
|
|
6
6
|
const tpl = require('@tryghost/tpl');
|
|
7
7
|
const errors = require('@tryghost/errors');
|
|
8
8
|
const security = require('@tryghost/security');
|
|
9
|
-
const {pipeline} = require('@tryghost/promise');
|
|
10
9
|
const validatePassword = require('../lib/validate-password');
|
|
11
10
|
const permissions = require('../services/permissions');
|
|
12
11
|
const urlUtils = require('../../shared/url-utils').default;
|
|
@@ -572,91 +571,68 @@ User = ghostBookshelf.Model.extend({
|
|
|
572
571
|
* @extends ghostBookshelf.Model.edit to handle returning the full object
|
|
573
572
|
* **See:** [ghostBookshelf.Model.edit](base.js.html#edit)
|
|
574
573
|
*/
|
|
575
|
-
edit: function edit(data, unfilteredOptions) {
|
|
574
|
+
edit: async function edit(data, unfilteredOptions) {
|
|
576
575
|
const options = this.filterOptions(unfilteredOptions, 'edit');
|
|
577
576
|
const self = this;
|
|
578
|
-
const ops = [];
|
|
579
577
|
|
|
580
578
|
if (data.roles && data.roles.length > 1) {
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
})
|
|
585
|
-
);
|
|
579
|
+
throw new errors.ValidationError({
|
|
580
|
+
message: tpl(messages.onlyOneRolePerUserSupported)
|
|
581
|
+
});
|
|
586
582
|
}
|
|
587
583
|
|
|
588
584
|
if (data.email) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
message: tpl(messages.userUpdateError.emailIsAlreadyInUse)
|
|
594
|
-
}));
|
|
595
|
-
}
|
|
585
|
+
const existingUser = await self.getByEmail(data.email, options);
|
|
586
|
+
if (existingUser && existingUser.id !== options.id) {
|
|
587
|
+
throw new errors.ValidationError({
|
|
588
|
+
message: tpl(messages.userUpdateError.emailIsAlreadyInUse)
|
|
596
589
|
});
|
|
597
|
-
}
|
|
590
|
+
}
|
|
598
591
|
}
|
|
599
592
|
|
|
600
|
-
|
|
601
|
-
return ghostBookshelf.Model.edit.call(self, data, options).then(async (user) => {
|
|
602
|
-
let roleId;
|
|
593
|
+
const user = await ghostBookshelf.Model.edit.call(self, data, options);
|
|
603
594
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
roleId = data.roles[0].id || data.roles[0];
|
|
609
|
-
|
|
610
|
-
return user.roles().fetch().then((roles) => {
|
|
611
|
-
// return if the role is already assigned
|
|
612
|
-
if (roles.models[0].id === roleId) {
|
|
613
|
-
return;
|
|
614
|
-
}
|
|
595
|
+
if (!data.roles || !data.roles.length) {
|
|
596
|
+
return user;
|
|
597
|
+
}
|
|
615
598
|
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
return;
|
|
620
|
-
}
|
|
599
|
+
const roleId = data.roles[0].id || data.roles[0];
|
|
600
|
+
const roles = await user.roles().fetch();
|
|
601
|
+
let roleToAssign;
|
|
621
602
|
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
message: tpl(messages.invalidRoleValue.message),
|
|
633
|
-
context: tpl(messages.invalidRoleValue.context),
|
|
634
|
-
help: tpl(messages.invalidRoleValue.help)
|
|
635
|
-
})
|
|
636
|
-
);
|
|
637
|
-
}
|
|
638
|
-
}).then((roleToAssign) => {
|
|
639
|
-
if (roleToAssign && roleToAssign.get('name') === 'Owner') {
|
|
640
|
-
return Promise.reject(
|
|
641
|
-
new errors.ValidationError({
|
|
642
|
-
message: tpl(messages.methodDoesNotSupportOwnerRole)
|
|
643
|
-
})
|
|
644
|
-
);
|
|
645
|
-
} else if (roleToAssign) {
|
|
646
|
-
// assign all other roles
|
|
647
|
-
return user.roles().updatePivot({role_id: roleToAssign.id});
|
|
648
|
-
}
|
|
649
|
-
}).then(() => {
|
|
650
|
-
options.status = 'all';
|
|
651
|
-
return self.findOne({id: user.id}, options);
|
|
652
|
-
}).then((model) => {
|
|
653
|
-
model._changed = user._changed;
|
|
654
|
-
return model;
|
|
603
|
+
if (roles.models[0].id !== roleId) {
|
|
604
|
+
if (ASSIGNABLE_ROLES.includes(roleId)) {
|
|
605
|
+
if (roles.models[0].get('name') !== roleId) {
|
|
606
|
+
roleToAssign = await ghostBookshelf.model('Role').findOne({
|
|
607
|
+
name: roleId
|
|
608
|
+
});
|
|
609
|
+
}
|
|
610
|
+
} else if (ObjectId.isValid(roleId)) {
|
|
611
|
+
roleToAssign = await ghostBookshelf.model('Role').findOne({
|
|
612
|
+
id: roleId
|
|
655
613
|
});
|
|
614
|
+
} else {
|
|
615
|
+
throw new errors.ValidationError({
|
|
616
|
+
message: tpl(messages.invalidRoleValue.message),
|
|
617
|
+
context: tpl(messages.invalidRoleValue.context),
|
|
618
|
+
help: tpl(messages.invalidRoleValue.help)
|
|
619
|
+
});
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (roleToAssign && roleToAssign.get('name') === 'Owner') {
|
|
624
|
+
throw new errors.ValidationError({
|
|
625
|
+
message: tpl(messages.methodDoesNotSupportOwnerRole)
|
|
656
626
|
});
|
|
657
|
-
})
|
|
627
|
+
} else if (roleToAssign) {
|
|
628
|
+
// assign all other roles
|
|
629
|
+
await user.roles().updatePivot({role_id: roleToAssign.id});
|
|
630
|
+
}
|
|
658
631
|
|
|
659
|
-
|
|
632
|
+
options.status = 'all';
|
|
633
|
+
const model = await self.findOne({id: user.id}, options);
|
|
634
|
+
model._changed = user._changed;
|
|
635
|
+
return model;
|
|
660
636
|
},
|
|
661
637
|
|
|
662
638
|
/**
|
|
@@ -208,7 +208,34 @@ module.exports = function createSessionService({
|
|
|
208
208
|
* @returns {Promise<void>}
|
|
209
209
|
*/
|
|
210
210
|
async function createSessionForUser(req, res, user) {
|
|
211
|
-
const
|
|
211
|
+
const previousSession = await getSession(req, res);
|
|
212
|
+
|
|
213
|
+
// Carried over to the new session so verification state survives login
|
|
214
|
+
const {
|
|
215
|
+
user_id: previousUserId,
|
|
216
|
+
verified: previousVerified,
|
|
217
|
+
auth_code_challenge: previousAuthCodeChallenge,
|
|
218
|
+
auth_code_generated_at: previousAuthCodeGeneratedAt
|
|
219
|
+
} = previousSession;
|
|
220
|
+
|
|
221
|
+
// Ensure a new session is always created
|
|
222
|
+
await new Promise((resolve, reject) => {
|
|
223
|
+
req.session.regenerate((err) => {
|
|
224
|
+
if (err) {
|
|
225
|
+
reject(err);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
resolve();
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
const session = req.session;
|
|
233
|
+
session.user_id = previousUserId;
|
|
234
|
+
// A different user doesn't inherit the previous user's verification
|
|
235
|
+
session.verified = previousUserId && previousUserId !== user.id ? undefined : previousVerified;
|
|
236
|
+
session.auth_code_challenge = previousAuthCodeChallenge;
|
|
237
|
+
session.auth_code_generated_at = previousAuthCodeGeneratedAt;
|
|
238
|
+
|
|
212
239
|
const origin = getOriginOfRequest(req);
|
|
213
240
|
await assignUserToSession({
|
|
214
241
|
session,
|
|
@@ -47,8 +47,8 @@ const waitActionSchema = zod_1.z.object({
|
|
|
47
47
|
type: zod_1.z.literal('wait'),
|
|
48
48
|
data: zod_1.z.object({
|
|
49
49
|
wait_hours: zod_1.z.number().int().positive()
|
|
50
|
-
})
|
|
51
|
-
})
|
|
50
|
+
})
|
|
51
|
+
});
|
|
52
52
|
const sendEmailActionSchema = zod_1.z.object({
|
|
53
53
|
id: objectIdSchema,
|
|
54
54
|
type: zod_1.z.literal('send_email'),
|
|
@@ -56,12 +56,12 @@ const sendEmailActionSchema = zod_1.z.object({
|
|
|
56
56
|
email_subject: zod_1.z.string(),
|
|
57
57
|
email_lexical: zod_1.z.string(),
|
|
58
58
|
email_design_setting_id: zod_1.z.string().min(1)
|
|
59
|
-
})
|
|
60
|
-
})
|
|
59
|
+
})
|
|
60
|
+
});
|
|
61
61
|
const edgeSchema = zod_1.z.object({
|
|
62
62
|
source_action_id: objectIdSchema,
|
|
63
63
|
target_action_id: objectIdSchema
|
|
64
|
-
})
|
|
64
|
+
});
|
|
65
65
|
const editAutomationDataSchema = zod_1.z.object({
|
|
66
66
|
status: zod_1.z.enum(['active', 'inactive']),
|
|
67
67
|
actions: zod_1.z.array(zod_1.z.discriminatedUnion('type', [
|
|
@@ -69,7 +69,7 @@ const editAutomationDataSchema = zod_1.z.object({
|
|
|
69
69
|
sendEmailActionSchema
|
|
70
70
|
])).min(1).max(MAX_AUTOMATION_ACTIONS),
|
|
71
71
|
edges: zod_1.z.array(edgeSchema)
|
|
72
|
-
})
|
|
72
|
+
});
|
|
73
73
|
const repository = (0, database_automations_repository_1.createDatabaseAutomationsRepository)({
|
|
74
74
|
knex,
|
|
75
75
|
fakeWaitHoursMultiplier: (0, fake_wait_hours_multiplier_1.parseFakeWaitHoursMultiplier)(config.get('automations:fakeWaitHoursMultiplier'))
|
|
@@ -39,8 +39,8 @@ const waitActionSchema = z.object({
|
|
|
39
39
|
type: z.literal('wait'),
|
|
40
40
|
data: z.object({
|
|
41
41
|
wait_hours: z.number().int().positive()
|
|
42
|
-
})
|
|
43
|
-
})
|
|
42
|
+
})
|
|
43
|
+
});
|
|
44
44
|
|
|
45
45
|
const sendEmailActionSchema = z.object({
|
|
46
46
|
id: objectIdSchema,
|
|
@@ -49,13 +49,13 @@ const sendEmailActionSchema = z.object({
|
|
|
49
49
|
email_subject: z.string(),
|
|
50
50
|
email_lexical: z.string(),
|
|
51
51
|
email_design_setting_id: z.string().min(1)
|
|
52
|
-
})
|
|
53
|
-
})
|
|
52
|
+
})
|
|
53
|
+
});
|
|
54
54
|
|
|
55
55
|
const edgeSchema = z.object({
|
|
56
56
|
source_action_id: objectIdSchema,
|
|
57
57
|
target_action_id: objectIdSchema
|
|
58
|
-
})
|
|
58
|
+
});
|
|
59
59
|
|
|
60
60
|
const editAutomationDataSchema = z.object({
|
|
61
61
|
status: z.enum(['active', 'inactive']),
|
|
@@ -64,7 +64,7 @@ const editAutomationDataSchema = z.object({
|
|
|
64
64
|
sendEmailActionSchema
|
|
65
65
|
])).min(1).max(MAX_AUTOMATION_ACTIONS),
|
|
66
66
|
edges: z.array(edgeSchema)
|
|
67
|
-
})
|
|
67
|
+
});
|
|
68
68
|
|
|
69
69
|
const repository = createDatabaseAutomationsRepository({
|
|
70
70
|
knex,
|
|
@@ -902,6 +902,7 @@ async function loadActionStats(trx, actionIds) {
|
|
|
902
902
|
const rows = await trx('automation_action_revisions')
|
|
903
903
|
.select('action_id')
|
|
904
904
|
.sum({
|
|
905
|
+
email_clicked_count: 'email_clicked_count',
|
|
905
906
|
email_sent_count: 'email_sent_count',
|
|
906
907
|
email_opened_count: 'email_opened_count'
|
|
907
908
|
})
|
|
@@ -953,22 +954,26 @@ function buildActionPayload(row, stats) {
|
|
|
953
954
|
}
|
|
954
955
|
}
|
|
955
956
|
const EMPTY_EMAIL_STATS = {
|
|
957
|
+
email_clicked_count: 0,
|
|
956
958
|
email_sent_count: 0,
|
|
957
959
|
email_opened_count: 0,
|
|
958
960
|
opened_rate: null,
|
|
959
961
|
clicked_rate: null
|
|
960
962
|
};
|
|
961
963
|
function buildEmailStats(row) {
|
|
964
|
+
const emailClickedCount = row.email_clicked_count ?? 0;
|
|
962
965
|
const emailSentCount = row.email_sent_count ?? 0;
|
|
963
966
|
const emailOpenedCount = row.email_opened_count ?? 0;
|
|
964
967
|
return {
|
|
968
|
+
email_clicked_count: emailClickedCount,
|
|
965
969
|
email_sent_count: emailSentCount,
|
|
966
970
|
email_opened_count: emailOpenedCount,
|
|
967
971
|
opened_rate: emailSentCount
|
|
968
972
|
? Math.round(emailOpenedCount / emailSentCount * 100)
|
|
969
973
|
: null,
|
|
970
|
-
|
|
971
|
-
|
|
974
|
+
clicked_rate: emailSentCount
|
|
975
|
+
? Math.round(emailClickedCount / emailSentCount * 100)
|
|
976
|
+
: null
|
|
972
977
|
};
|
|
973
978
|
}
|
|
974
979
|
function requireValue(row, field) {
|