ghost 6.55.0 → 6.57.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/validate-adapters.js +74 -0
- package/bin/validate-adapters.ts +87 -0
- package/bin/warm-compile-cache.sh +60 -0
- package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-kg-default-cards-10.3.3.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.1.4.tgz +0 -0
- package/components/tryghost-kg-default-transforms-1.3.3.tgz +0 -0
- package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
- package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
- package/components/tryghost-nql-string-0.0.0.tgz +0 -0
- package/components/tryghost-parse-email-address-0.0.0.tgz +0 -0
- package/core/app.js +1 -1
- package/core/boot.js +21 -25
- package/core/bridge.js +10 -12
- package/core/built/admin/assets/Combination-BDvCdg1t.js +41 -0
- package/core/built/admin/assets/{PolarAngleAxis-BGjf7sBu.js → PolarAngleAxis-C4qtHy1B.js} +1 -1
- package/core/built/admin/assets/{RadialBarChart-zeFf9mBz.js → RadialBarChart-Bh09Stsj.js} +1 -1
- package/core/built/admin/assets/about-C7O-AgRZ.js +1 -0
- package/core/built/admin/assets/{account-migration-sjHLx5dy.js → account-migration-Cv_ol1ko.js} +1 -1
- package/core/built/admin/assets/action-list-lGzZLHc8.js +1 -0
- package/core/built/admin/assets/activitypub/{account-migration-DoYXtnZ8.js → account-migration-DHFbePl6.js} +7 -7
- package/core/built/admin/assets/activitypub/activitypub.js +6 -6
- package/core/built/admin/assets/activitypub/{avatar-B3DEDB1l.js → avatar-CT4tS2_S.js} +2 -2
- package/core/built/admin/assets/activitypub/{avatar-flipboard-Cqrg0XMx.js → avatar-flipboard-ByYWKipo.js} +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-Bxeh6MKW.js → bluesky-sharing-srVnSHDz.js} +16 -16
- package/core/built/admin/assets/activitypub/{content-formatters-BpQARh28.js → content-formatters-C2sos5Ht.js} +3 -3
- package/core/built/admin/assets/activitypub/{copy-D9UWIAan.js → copy-B1UBTOoZ.js} +2 -2
- package/core/built/admin/assets/activitypub/{dist-D8sFfdpC.js → dist-MPsb2WZ1.js} +12 -12
- package/core/built/admin/assets/activitypub/{domain-CWueFW6q.js → domain-DzcM9Dzo.js} +9 -9
- package/core/built/admin/assets/activitypub/{edit-profile-BGxPU7N5.js → edit-profile-CsQ_4xAk.js} +945 -945
- package/core/built/admin/assets/activitypub/{explore-DBK-u0Ch.js → explore-CJe0DZBK.js} +8 -8
- package/core/built/admin/assets/activitypub/{feed-Bcir5RQZ.js → feed-CKV3qWON.js} +33 -33
- package/core/built/admin/assets/activitypub/{field-BV-CIT3d.js → field-DckHfDJf.js} +3 -3
- package/core/built/admin/assets/activitypub/{hash-BC9Q0N4c.js → hash-Dlp8RM55.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-DQ4ZnC1m.js → inbox-CsPy4R0M.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-yC0nfvzl.js → inbox-DAsjzUUe.js} +85 -85
- package/core/built/admin/assets/activitypub/{label-DTRS9TDk.js → label-DWyo9E5T.js} +3 -3
- package/core/built/admin/assets/activitypub/layout-DM_3pqsU.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-Dwl9t0pb.js → moderation-B0Inc03M.js} +11 -11
- package/core/built/admin/assets/activitypub/{note-B-zKsZ7r.js → note-CSE54lER.js} +7 -7
- package/core/built/admin/assets/activitypub/{notifications-DjJZD54t.js → notifications-RSwbKqdh.js} +9 -9
- package/core/built/admin/assets/activitypub/{preferences-CoU8vs6d.js → preferences-CE231n2_.js} +20 -20
- package/core/built/admin/assets/activitypub/{profile-BAsaJekx.js → profile-aaQ1j1Eo.js} +237 -237
- package/core/built/admin/assets/activitypub/{reply-CpdMeAO5.js → reply-Cjb0Vs3m.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-CLH3OVTP.js → routes-Djb0mc9k.js} +1139 -1150
- package/core/built/admin/assets/activitypub/{separator-BI1FLsYM.js → separator-D8pROI5n.js} +3 -3
- package/core/built/admin/assets/activitypub/{settings-DA6pNJ-Q.js → settings-DMc32IQl.js} +13 -13
- package/core/built/admin/assets/activitypub/{site-CQD42Pw5.js → site-DVIcsohX.js} +81 -81
- package/core/built/admin/assets/activitypub/{step-1-DIDg4aHb.js → step-1-9Yey9usF.js} +5 -5
- package/core/built/admin/assets/activitypub/{step-2-BfGe8BYt.js → step-2-DdYmKRXV.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-3-mE9spAqw.js → step-3-hdSkcjZU.js} +10 -10
- package/core/built/admin/assets/activitypub/{tabs-Cg0saow1.js → tabs-CUqseYzQ.js} +317 -315
- package/core/built/admin/assets/activitypub/{topic-filter-DLHBZT93.js → topic-filter-Coni6OXM.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-VzbN99u_.js → use-activity-pub-queries-MeHTPMQv.js} +2 -2
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-CAwXTmqH.js → use-navigate-with-base-path-B046AM38.js} +115 -96
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-Br4aOWOh.js → use-reply-chain-data-1AO-Xob6.js} +5 -5
- package/core/built/admin/assets/activitypub/{x-C8tAnXly.js → x-BRfRYd16.js} +2 -2
- package/core/built/admin/assets/add-integration-modal-m9Dv6f77.js +1 -0
- package/core/built/admin/assets/add-newsletter-modal-CPSVZL2H.js +1 -0
- package/core/built/admin/assets/add-recommendation-modal-Ci9Z5yat.js +1 -0
- package/core/built/admin/assets/analytics-DqSCVV8U.js +1 -0
- package/core/built/admin/assets/announcement-bar-modal-DU3avV7d.js +1 -0
- package/core/built/admin/assets/api-keys-BK0LylyK.js +1 -0
- package/core/built/admin/assets/api-keys-_iCT4dxr.js +1 -0
- package/core/built/admin/assets/{audience-DjEWNhp3.js → audience-DTJtXXWY.js} +1 -1
- package/core/built/admin/assets/automated-emails-PayPCT8L.js +1 -0
- package/core/built/admin/assets/{automations-DhKeS_U5.js → automations-162q5HjL.js} +1 -1
- package/core/built/admin/assets/{automations-Bx3VcFIL.js → automations-dtQ_MRZ7.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-ClMd0-Ft.js → bluesky-sharing-B8Es3AuV.js} +1 -1
- package/core/built/admin/assets/{box-BWbhXh21.js → box-By_EgGtm.js} +1 -1
- package/core/built/admin/assets/brand-icon-09YqFZjV.js +1 -0
- package/core/built/admin/assets/{breadcrumb-BD0U2ygg.js → breadcrumb-tg5zL--z.js} +1 -1
- package/core/built/admin/assets/build-CsiH26uS.js +1 -0
- package/core/built/admin/assets/{card-DGoDJzrn.js → card-aUHX0hyR.js} +1 -1
- package/core/built/admin/assets/{chart-BuRT2Y6p.js → chart-BnXyfH11.js} +1 -1
- package/core/built/admin/assets/{chart-helpers-fjQKRVyY.js → chart-helpers-CZElTGYL.js} +1 -1
- package/core/built/admin/assets/checkbox-Cyo-x7mC.js +1 -0
- package/core/built/admin/assets/chunk.524.cd5498dd4e195f679266.js +25 -0
- package/core/built/admin/assets/chunk.582.6f92ec1f03734bf2ea3f.js +27 -0
- package/core/built/admin/assets/{chunk.481.6c7395a49484bb4583aa.js → chunk.873.b8fc05a8ab877fb819ed.js} +62 -60
- package/core/built/admin/assets/code-editor-view-Dq1rQ4tS.js +1 -0
- package/core/built/admin/assets/color-picker-field-BonBgxhU.js +1 -0
- package/core/built/admin/assets/color-utils-BsrwXuDz.js +1 -0
- package/core/built/admin/assets/combobox-uX5r_paI.js +1 -0
- package/core/built/admin/assets/command-DxwBO2pF.js +1 -0
- package/core/built/admin/assets/comments-Dn-RO-nU.js +1 -0
- package/core/built/admin/assets/components-CwL6a6As-B0E0-NBJ.js +1 -0
- package/core/built/admin/assets/confirmation-modal-B6D6mk6R.js +1 -0
- package/core/built/admin/assets/{container-BYz2Ed39.js → container-LiZf3XdY.js} +1 -1
- package/core/built/admin/assets/content-api-modal-BxQ-7T6u.js +1 -0
- package/core/built/admin/assets/copy-field-BAxL4Zqn.js +1 -0
- package/core/built/admin/assets/currency-Dy4Tc2R2.js +1 -0
- package/core/built/admin/assets/custom-integration-modal---d_l7lc.js +1 -0
- package/core/built/admin/assets/{data-list-16rkJYYi.js → data-list-CupF7bjM.js} +1 -1
- package/core/built/admin/assets/{date-range-select-BFp58EMk.js → date-range-select-PB2n1HZf.js} +1 -1
- package/core/built/admin/assets/debounce-Dg2fW5Rg.js +3 -0
- package/core/built/admin/assets/design-and-theme-modal-DYRT207a.js +23 -0
- package/core/built/admin/assets/dirty-confirm-dialog-BnDJ39ao.js +1 -0
- package/core/built/admin/assets/disabled-sources-indicator-iMHrkyV3.js +1 -0
- package/core/built/admin/assets/dist-B2PvRwLn.js +1 -0
- package/core/built/admin/assets/{dist-DGzPJ_bO.js → dist-Cb5RSHSg.js} +1 -1
- package/core/built/admin/assets/{dist-P_ohUlTi.js → dist-CqO2lWS02.js} +1 -1
- package/core/built/admin/assets/{dist-pHiOxyxa.js → dist-D5vhMdXf.js} +1 -1
- package/core/built/admin/assets/dist-DlCyLrOC.js +1 -0
- package/core/built/admin/assets/dist-WeOIvSYM.js +23 -0
- package/core/built/admin/assets/{dist-DyViJwIy.js → dist-cFzKLG7y.js} +3 -3
- package/core/built/admin/assets/dist-j1xxxnY8.js +1 -0
- package/core/built/admin/assets/dist-myQaY0ka.js +1 -0
- package/core/built/admin/assets/domain-BR8FIdjD.js +1 -0
- package/core/built/admin/assets/{dropzone-DlK-K2DQ.js → dropzone-hNxmUvwD.js} +1 -1
- package/core/built/admin/assets/{edit-profile-BKT-REJw.js → edit-profile-CENrkY0l.js} +1 -1
- package/core/built/admin/assets/editor-B18D0lm8.js +8 -0
- package/core/built/admin/assets/embed-signup-form-modal-BsCYUMvQ.js +9 -0
- package/core/built/admin/assets/{empty-indicator-oX1u1Pkn.js → empty-indicator-WB1WgT9h.js} +1 -1
- package/core/built/admin/assets/{explore-ClUpJZOp.js → explore-BmrnljWw.js} +1 -1
- package/core/built/admin/assets/{feed-DF92dUMP.js → feed-Dy4li6i8.js} +1 -1
- package/core/built/admin/assets/{field-Do7lieBA.js → field-Dh_lwk13.js} +1 -1
- package/core/built/admin/assets/filter-sources-D2zDuJH5.js +1 -0
- package/core/built/admin/assets/filters-3JBQcNtJ.js +1 -0
- package/core/built/admin/assets/filters-CFt_BDe4.js +1 -0
- package/core/built/admin/assets/first-promoter-modal-D6Rjxugw.js +1 -0
- package/core/built/admin/assets/format-url-C-t8G6Qd.js +1 -0
- package/core/built/admin/assets/{ghost-a9c788f6f0b1ea6cf0f14ca4b13f4496.css → ghost-d38c30b866e59f2e06a357b5eb844fe5.css} +1 -1
- package/core/built/admin/assets/{ghost-c4057474943effc568c5659ccc4909d7.js → ghost-d683fbeb945b13155ea669caa5d8db41.js} +420 -398
- package/core/built/admin/assets/{ghost-dark-db36d795d21c61426837df40af270966.css → ghost-dark-1bffd5099ba7a749303133efacf8480b.css} +1 -1
- package/core/built/admin/assets/{gift-link-modal-f9VIMNDv.js → gift-link-modal-zMtlJhLQ.js} +1 -1
- package/core/built/admin/assets/{gift-links-CsUhefWG.js → gift-links-CVuqLhsZ.js} +1 -1
- package/core/built/admin/assets/global-data-provider-B949LDXJ.js +1 -0
- package/core/built/admin/assets/grid-CUciBfjf.js +1 -0
- package/core/built/admin/assets/{growth-CVw1My4S.js → growth-C38Xtp8D.js} +2 -2
- package/core/built/admin/assets/{growth-B3I6gNLm.js → growth-CFws67_i.js} +1 -1
- package/core/built/admin/assets/history-modal-ILAOkbxH.js +1 -0
- package/core/built/admin/assets/{hooks-CixO01lL.js → hooks-D6EE7BIt.js} +2 -2
- package/core/built/admin/assets/html-field-9a9Id_2q.js +1 -0
- package/core/built/admin/assets/iframe-buffering-e8vH4-Qn.js +1 -0
- package/core/built/admin/assets/images-CrQKVc9Q.js +1 -0
- package/core/built/admin/assets/{inbox-CFBSu4Ed.js → inbox-BrSRzyvz.js} +6 -6
- package/core/built/admin/assets/index-BHy2DZX-.js +67 -0
- package/core/built/admin/assets/index-Ds2iUoF6.css +1 -0
- package/core/built/admin/assets/index.es-B9tvf-t4.js +1 -0
- package/core/built/admin/assets/{inline-EMAm3okk.js → inline-Cw-gigDW.js} +1 -1
- package/core/built/admin/assets/input-group-6vEPvX9S.js +1 -0
- package/core/built/admin/assets/{input-surface-BpAjZXY5.js → input-surface-BdOe4KzO.js} +1 -1
- package/core/built/admin/assets/integration-header-B4DyXV06.js +1 -0
- package/core/built/admin/assets/integrations-BT6_iJvu.js +1 -0
- package/core/built/admin/assets/invite-user-modal-5xDCEw90.js +1 -0
- package/core/built/admin/assets/isEmail-BOfC647y.js +1 -0
- package/core/built/admin/assets/koenig-editor-base-BWLEB9Vf.js +1 -0
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +9535 -9527
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +124 -124
- package/core/built/admin/assets/{koenig-lexical-f8vHRYvz.js → koenig-lexical-Cj62r_y9.js} +86 -86
- package/core/built/admin/assets/{kpi-card-DSap9rAy.js → kpi-card-DOUnkZcN.js} +1 -1
- package/core/built/admin/assets/{kpi-card-DU0rn9Cj.js → kpi-card-IHDo8Q56.js} +1 -1
- package/core/built/admin/assets/{kpi-helpers-Dz33AV6h.js → kpi-helpers-CjUdECAK.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-BtTTrKvJ.js → kpi-tabs-X3DkOYbI.js} +1 -1
- package/core/built/admin/assets/{label-DS8Mi6G5.js → label-Dq6QaOuS.js} +1 -1
- package/core/built/admin/assets/label-picker-8YlhS2U3.js +1 -0
- package/core/built/admin/assets/labels-DAPjKPy2.js +1 -0
- package/core/built/admin/assets/layout-TMj4P-vv.js +1 -0
- package/core/built/admin/assets/limit-modal-BRPsit_B.js +1 -0
- package/core/built/admin/assets/link-helpers-Bzkk68GD.js +1 -0
- package/core/built/admin/assets/{list-page-B6ANtzkd.js → list-page-CdsnQR9N.js} +1 -1
- package/core/built/admin/assets/member-custom-fields-CmvsuuV_.js +1 -0
- package/core/built/admin/assets/member-detail-DCvRrkmJ.js +1 -0
- package/core/built/admin/assets/members-DxbSKngD.js +11 -0
- package/core/built/admin/assets/modal-page-BMTzFHVO.js +1 -0
- package/core/built/admin/assets/{moderation-DySbgyUS.js → moderation-Da5lhhWh.js} +1 -1
- package/core/built/admin/assets/multi-select-combobox-CYdkH7KQ.js +1 -0
- package/core/built/admin/assets/navigation-modal-BIoTwtMy.js +1 -0
- package/core/built/admin/assets/newsletter-BptlVoJm.js +1 -0
- package/core/built/admin/assets/newsletter-detail-modal-BIvxqE0N.js +1 -0
- package/core/built/admin/assets/newsletter-emails-Bf8IZLsP.js +1 -0
- package/core/built/admin/assets/newsletters-BgrIZk0V.js +1 -0
- package/core/built/admin/assets/{newsletters-BXJbJgA-.js → newsletters-Du0lzskS.js} +1 -1
- package/core/built/admin/assets/{note-C8kxrz6w.js → note-DYcLOmDb.js} +1 -1
- package/core/built/admin/assets/{notifications-CgtSfX7t.js → notifications-B2SPFIV9.js} +1 -1
- package/core/built/admin/assets/{offers-DEniVRD5.js → offers-Um6ab71O.js} +1 -1
- package/core/built/admin/assets/offers-container-modal-BZnZITMw.js +1 -0
- package/core/built/admin/assets/{onboarding-route-4qtziA9d.js → onboarding-route-DgdfxkQh.js} +1 -1
- package/core/built/admin/assets/overview-BYQJXSog.js +1 -0
- package/core/built/admin/assets/overview-Dt2_mR6U.js +1 -0
- package/core/built/admin/assets/{page-header-B3Wb9Jqv.js → page-header-CpmxJj5B.js} +1 -1
- package/core/built/admin/assets/{pages-B4goRPUn.js → pages-BVMnpI2O.js} +1 -1
- package/core/built/admin/assets/pintura-modal-B4MuBuuO.js +1 -0
- package/core/built/admin/assets/portal-frame-BF0NBIji.js +1 -0
- package/core/built/admin/assets/portal-modal-DMVbm0oy.js +1 -0
- package/core/built/admin/assets/{post-analytics-We-ko5Cb.js → post-analytics-CXWBOGyP.js} +1 -1
- package/core/built/admin/assets/post-analytics-header-ChzjXVPt.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-B6UynTGb.js → post-analytics-provider-Cgw4U_fw.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-DFfO93_0.js → post-share-modal-B9Vmk_XM.js} +1 -1
- package/core/built/admin/assets/{posts-DhXE7b6r.js → posts-CxG7v0-M.js} +1 -1
- package/core/built/admin/assets/preferences-BfzJmnsd.js +5 -0
- package/core/built/admin/assets/preview-chrome-BOVO6_PE.js +1 -0
- package/core/built/admin/assets/preview-modal-Dw_HtD6S.js +1 -0
- package/core/built/admin/assets/profile-BT5vtv9b.js +1 -0
- package/core/built/admin/assets/radio-group-I7ou7PoJ.js +1 -0
- package/core/built/admin/assets/{referrers-pmJT4k3-.js → referrers-B9QDAfYm.js} +1 -1
- package/core/built/admin/assets/{router-provider-C1R4AZ2o.js → router-provider-DSQfprxH.js} +1 -1
- package/core/built/admin/assets/select-D9oU1IoM.js +1 -0
- package/core/built/admin/assets/settings-CfZFbyu_.js +4 -0
- package/core/built/admin/assets/{settings-WXNeWyU1.js → settings-CpvHWix7.js} +1 -1
- package/core/built/admin/assets/settings-breadcrumbs-K0USP2nw.js +1 -0
- package/core/built/admin/assets/{shade-app-B3heOAfz-CGXQwWeO.js → shade-app-B3heOAfz-BhQXxNkT.js} +1 -1
- package/core/built/admin/assets/{share-modal-DsgW3wbg.js → share-modal-ClIyPzc5.js} +1 -1
- package/core/built/admin/assets/slack-modal-1XXqOfiw.js +1 -0
- package/core/built/admin/assets/social-urls-CHQ9cTAK.js +1 -0
- package/core/built/admin/assets/{sort-button-Bb_FCl7e.js → sort-button-CdBsm40v.js} +1 -1
- package/core/built/admin/assets/{source-icon-BeaRxUn9.js → source-icon-CEYmVLXR.js} +1 -1
- package/core/built/admin/assets/{stack-xiN0eQ6Z.js → stack-CIfOAAVf.js} +1 -1
- package/core/built/admin/assets/{stats-Bjj_kmJ1.js → stats-D8nZCTy5.js} +1 -1
- package/core/built/admin/assets/{stats-view-BxPbW6K1.js → stats-view-CugFVf0g.js} +1 -1
- package/core/built/admin/assets/{step-1-B1e3-1F8.js → step-1-DOYjrORn.js} +1 -1
- package/core/built/admin/assets/{step-2-C5WaeBJo.js → step-2-vA6RsarH.js} +1 -1
- package/core/built/admin/assets/{step-3-B46iVPwu.js → step-3-BtIBxMId.js} +2 -2
- package/core/built/admin/assets/stripe-button-UFP4QRXd.js +1 -0
- package/core/built/admin/assets/stripe-connect-modal-C_L3uM6n.js +1 -0
- package/core/built/admin/assets/switch-CE2NuMn-.js +1 -0
- package/core/built/admin/assets/{table-CB-4_HBJ.js → table-TMCgCQK7.js} +1 -1
- package/core/built/admin/assets/tabs-Dd6oOKbv.js +1 -0
- package/core/built/admin/assets/tag-detail-D3l-UJPR.js +1 -0
- package/core/built/admin/assets/tags-BwCgmr5v.js +1 -0
- package/core/built/admin/assets/tags-WdKQGWR8.js +1 -0
- package/core/built/admin/assets/testimonials-modal-CqWp-xXJ.js +1 -0
- package/core/built/admin/assets/{text-DwtjwGkh.js → text-DU9Hv33_.js} +1 -1
- package/core/built/admin/assets/textarea-d9qEkMOP.js +1 -0
- package/core/built/admin/assets/tier-detail-modal-BkcR2u0A.js +1 -0
- package/core/built/admin/assets/{tiers-DYsa4DCo.js → tiers-Da6kxHQP.js} +1 -1
- package/core/built/admin/assets/toggle-group-Guorw93z.js +1 -0
- package/core/built/admin/assets/{topic-filter-DAFT87tQ.js → topic-filter-CbgoWIHn.js} +1 -1
- package/core/built/admin/assets/transistor-modal-DuNnnpal.js +1 -0
- package/core/built/admin/assets/unsplash-modal-y2bVwTHH.js +1 -0
- package/core/built/admin/assets/{use-analytics-data-S0FTd43s.js → use-analytics-data-B-b4wNSC.js} +1 -1
- package/core/built/admin/assets/use-check-theme-limit-error-m09J38Mq.js +2 -0
- package/core/built/admin/assets/use-feature-flag-ud0Qz8vt.js +1 -0
- package/core/built/admin/assets/{use-filter-params-Gpoq2E1L.js → use-filter-params-BnbBDc7T.js} +1 -1
- package/core/built/admin/assets/use-filterable-api-iMpDZBxD.js +1 -0
- package/core/built/admin/assets/use-form-vRjI0Kwk.js +1 -0
- package/core/built/admin/assets/{use-growth-stats-CIaiHOVb.js → use-growth-stats-DPU5rt3W.js} +1 -1
- package/core/built/admin/assets/use-hash-link-navigation-guard-B9j6HIrV.js +1 -0
- package/core/built/admin/assets/use-limiter-BkN3E4gw.js +2 -0
- package/core/built/admin/assets/use-pintura-config-C_z8HTG2.js +1 -0
- package/core/built/admin/assets/use-pintura-editor-DNgbkhGp.js +1 -0
- package/core/built/admin/assets/{use-post-referrers-Cq2vBPrA.js → use-post-referrers-C2mXyrp-.js} +1 -1
- package/core/built/admin/assets/use-query-params-DHhhu8wi.js +1 -0
- package/core/built/admin/assets/{use-reply-chain-data-DBDRtGu-.js → use-reply-chain-data-DaK8q2qQ.js} +1 -1
- package/core/built/admin/assets/use-setting-group-YlYcoNyP.js +1 -0
- package/core/built/admin/assets/use-settings-navigation-Bf1g1rEh.js +1 -0
- package/core/built/admin/assets/{use-simple-pagination-CjYNxdwe.js → use-simple-pagination-D9ckKo5-.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-query-falHAtuK.js → use-tinybird-query-GSa32hVw.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-D7-8ND1y.js → use-tinybird-token-CKLOZFsw.js} +1 -1
- package/core/built/admin/assets/use-url-input-z-p0Kewm.js +5 -0
- package/core/built/admin/assets/user-detail-modal-BMxOA4Sv.js +1 -0
- package/core/built/admin/assets/{users-Dkv8NQZe.js → users-B6hIds6n.js} +1 -1
- package/core/built/admin/assets/validator-Cr0sMKuW.js +7 -0
- package/core/built/admin/assets/{virtual-list-CvnC9zF2.js → virtual-list-RHeEL05L.js} +1 -1
- package/core/built/admin/assets/{web-jFYWCKDn.js → web-Bw2QMO5u.js} +2 -2
- package/core/built/admin/assets/web-xJtcwMy4.js +1 -0
- package/core/built/admin/assets/zapier-modal-BP2f4-vY.js +1 -0
- package/core/built/admin/index.html +20 -18
- package/core/frontend/helpers/authors.js +1 -1
- package/core/frontend/helpers/link.js +3 -8
- package/core/frontend/helpers/tags.js +1 -1
- package/core/frontend/meta/author-url.js +2 -2
- package/core/frontend/meta/url.js +4 -4
- package/core/frontend/services/data/fetch-data.js +10 -5
- package/core/frontend/services/proxy.js +1 -1
- package/core/frontend/services/routing/api-adapter.js +115 -0
- package/core/frontend/services/routing/api-adapter.ts +157 -0
- package/core/frontend/services/routing/collection-router.js +5 -3
- package/core/frontend/services/routing/controllers/index.js +1 -1
- package/core/frontend/services/routing/controllers/rss.js +26 -31
- package/core/frontend/services/routing/controllers/rss.ts +66 -0
- package/core/frontend/services/routing/controllers/static.js +8 -7
- package/core/frontend/services/routing/events.js +20 -6
- package/core/frontend/services/routing/parent-router.js +17 -4
- package/core/frontend/services/routing/permalink-adapter.js +13 -0
- package/core/frontend/services/routing/permalink-adapter.ts +10 -0
- package/core/frontend/services/routing/registry.js +59 -33
- package/core/frontend/services/routing/router-manager.js +24 -22
- package/core/frontend/services/routing/static-routes-router.js +4 -4
- package/core/frontend/services/routing/taxonomy-router.js +6 -2
- package/core/frontend/services/sitemap/base-site-map-generator.js +1 -28
- package/core/frontend/services/sitemap/site-map-manager.js +22 -57
- package/core/server/adapters/route-settings/utils.js +10 -2
- package/core/server/adapters/route-settings/utils.ts +12 -2
- package/core/server/api/endpoints/email-post.js +1 -1
- package/core/server/api/endpoints/previews.js +1 -1
- package/core/server/api/endpoints/search-index-public.js +3 -3
- package/core/server/api/endpoints/search-index.js +4 -4
- package/core/server/api/endpoints/utils/serializers/input/authors.js +2 -2
- package/core/server/api/endpoints/utils/serializers/input/comments.js +1 -1
- package/core/server/api/endpoints/utils/serializers/input/pages.js +3 -3
- package/core/server/api/endpoints/utils/serializers/input/posts.js +3 -3
- package/core/server/api/endpoints/utils/serializers/input/tags.js +1 -1
- package/core/server/api/endpoints/utils/serializers/input/users.js +2 -2
- package/core/server/api/endpoints/utils/serializers/input/utils/url.js +31 -13
- package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +2 -2
- package/core/server/api/endpoints/utils/serializers/output/mappers/tags.js +2 -2
- package/core/server/api/endpoints/utils/serializers/output/mappers/users.js +8 -0
- package/core/server/api/endpoints/utils/serializers/output/utils/url.js +18 -11
- package/core/server/data/migrations/versions/6.56/2026-08-03-19-27-51-add-automation-run-step-id-to-automated-email-recipients.js +8 -0
- package/core/server/data/migrations/versions/6.56/2026-08-04-14-42-39-restrict-notification-add-permission-to-administrators.js +12 -0
- package/core/server/data/migrations/versions/6.57/2026-08-04-18-00-00-store-custom-field-values-as-leaf-rows.js +74 -0
- package/core/server/data/migrations/versions/6.57/2026-08-04-18-54-14-reset-automation-email-analytics.js +25 -0
- package/core/server/data/schema/commands.js +21 -5
- package/core/server/data/schema/fixtures/fixtures.json +2 -2
- package/core/server/data/schema/schema.js +25 -5
- package/core/server/lib/common/to-plain.ts +1 -1
- package/core/server/models/post.js +12 -3
- package/core/server/services/adapter-manager/adapter-manager.js +1 -45
- package/core/server/services/adapter-manager/adapter-manager.ts +2 -48
- package/core/server/services/adapter-manager/adapter-paths.js +24 -0
- package/core/server/services/adapter-manager/adapter-paths.ts +21 -0
- package/core/server/services/adapter-manager/base-classes.js +22 -0
- package/core/server/services/adapter-manager/base-classes.ts +22 -0
- package/core/server/services/adapter-manager/index.js +4 -27
- package/core/server/services/adapter-manager/index.ts +4 -31
- package/core/server/services/adapter-manager/utils.js +50 -0
- package/core/server/services/adapter-manager/utils.ts +57 -0
- package/core/server/services/audience-feedback/audience-feedback-controller.js +1 -1
- package/core/server/services/audience-feedback/audience-feedback-service.js +2 -2
- package/core/server/services/automations/automations-repository.ts +3 -1
- package/core/server/services/automations/database-automations-repository.js +33 -14
- package/core/server/services/automations/database-automations-repository.ts +41 -15
- package/core/server/services/automations/get-scheduler-idempotency-key.js +13 -0
- package/core/server/services/automations/get-scheduler-idempotency-key.ts +8 -0
- package/core/server/services/automations/poll.js +3 -1
- package/core/server/services/automations/poll.ts +4 -1
- package/core/server/services/automations/service.js +7 -3
- package/core/server/services/automations/service.ts +7 -3
- package/core/server/services/comments/comments-service-emails.js +1 -1
- package/core/server/services/comments/comments-service.js +1 -1
- package/core/server/services/email-analytics/email-analytics-service-wrapper.js +11 -2
- package/core/server/services/email-analytics/email-analytics-service.js +1 -1
- package/core/server/services/email-service/email-service-wrapper.js +2 -1
- package/core/server/services/email-service/mailgun-email-provider.js +15 -0
- package/core/server/services/gift-links/CONTEXT.md +9 -0
- package/core/server/services/gifts/CONTEXT.md +55 -0
- package/core/server/services/gifts/README.md +28 -0
- package/core/server/services/gifts/gift-bookshelf-repository.js +65 -45
- package/core/server/services/gifts/gift-bookshelf-repository.ts +203 -74
- package/core/server/services/gifts/gift-checkout-adapter.js +59 -0
- package/core/server/services/gifts/gift-checkout-offer.js +74 -0
- package/core/server/services/gifts/gift-checkout-offer.ts +127 -0
- package/core/server/services/gifts/gift-codec.js +63 -0
- package/core/server/services/gifts/gift-codec.ts +62 -0
- package/core/server/services/gifts/gift-controller.js +6 -31
- package/core/server/services/gifts/gift-controller.ts +6 -69
- package/core/server/services/gifts/gift-email-service.ts +3 -2
- package/core/server/services/gifts/gift-schema.js +34 -0
- package/core/server/services/gifts/gift-schema.ts +39 -0
- package/core/server/services/gifts/gift-service-wrapper.js +13 -11
- package/core/server/services/gifts/gift-service.js +265 -52
- package/core/server/services/gifts/gift-service.ts +431 -97
- package/core/server/services/gifts/gift.ts +16 -39
- package/core/server/services/indexnow-ping/indexnow-ping-service.js +1 -1
- package/core/server/services/indexnow-ping/indexnow-ping-service.ts +2 -4
- package/core/server/services/lib/mailgun-client.js +3 -3
- package/core/server/services/link-tracking/link-click-repository.js +6 -1
- package/core/server/services/link-tracking/link-click-tracking-service.js +12 -3
- package/core/server/services/media-inliner/external-media-inliner.js +12 -8
- package/core/server/services/member-attribution/url-translator.js +5 -7
- package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +4 -3
- package/core/server/services/member-welcome-emails/service.js +79 -15
- package/core/server/services/members/api.js +2 -3
- package/core/server/services/members/import-export/import/completion-email.js +8 -4
- package/core/server/services/members/import-export/import/completion-email.ts +9 -4
- package/core/server/services/members/import-export/import/importer.js +25 -4
- package/core/server/services/members/import-export/import/importer.ts +27 -6
- package/core/server/services/members/import-export/import/row.ts +5 -1
- package/core/server/services/members/import-export/index.js +6 -2
- package/core/server/services/members/import-export/index.ts +10 -4
- package/core/server/services/members/members-api/controllers/router-controller.js +38 -36
- package/core/server/services/members/members-api/members-api.js +13 -7
- package/core/server/services/members/members-api/repositories/event-repository.js +4 -94
- package/core/server/services/members/members-api/services/member-bread-service.js +3 -3
- package/core/server/services/members/members-api/services/payments-service.js +6 -68
- package/core/server/services/members-custom-fields/queries.js +4 -13
- package/core/server/services/members-custom-fields/queries.ts +4 -13
- package/core/server/services/members-custom-fields/schema.js +19 -21
- package/core/server/services/members-custom-fields/schema.ts +18 -20
- package/core/server/services/members-custom-fields/storage.js +94 -27
- package/core/server/services/members-custom-fields/storage.ts +114 -39
- package/core/server/services/members-custom-fields/values-service.js +98 -103
- package/core/server/services/members-custom-fields/values-service.ts +116 -114
- package/core/server/services/mentions/resource-service.js +1 -1
- package/core/server/services/mentions/service.js +2 -2
- package/core/server/services/oembed/oembed-service.js +44 -3
- package/core/server/services/posts/posts-service.js +11 -0
- package/core/server/services/route-settings/dynamic-routing-service.js +10 -62
- package/core/server/services/route-settings/route-settings-parser.js +25 -25
- package/core/server/services/route-settings/route-settings-parser.ts +27 -27
- package/core/server/services/route-settings/validation-errors.js +68 -4
- package/core/server/services/route-settings/validation-errors.ts +79 -4
- package/core/server/services/slack-ping/slack-ping-service.js +2 -2
- package/core/server/services/slack-ping/slack-ping-service.ts +3 -5
- package/core/server/services/stats/content-stats-service.js +3 -3
- package/core/server/services/stats/posts-stats-service.js +29 -25
- package/core/server/services/stripe/services/webhook/charge-refunded-event-service.js +1 -1
- package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +16 -4
- package/core/server/services/stripe/stripe-api.js +10 -0
- package/core/server/services/url/config.js +90 -135
- package/core/server/services/url/index.js +6 -49
- package/core/server/services/url/lazy-find-resource.js +14 -11
- package/core/server/services/url/lazy-find-resource.ts +14 -11
- package/core/server/services/url/lazy-url-service.js +174 -94
- package/core/server/services/url/lazy-url-service.ts +233 -97
- package/core/server/services/url/routable-resources.js +62 -71
- package/core/server/services/url/router-filter.js +12 -4
- package/core/server/services/url/router-filter.ts +12 -4
- package/core/server/services/webhooks/index.js +1 -1
- package/core/server/services/webhooks/serialize.js +2 -1
- package/core/server/web/gift-preview/controller.js +10 -25
- package/core/shared/config/env/config.testing-mysql.json +1 -2
- package/core/shared/config/env/config.testing.json +1 -2
- package/core/shared/events/index.js +1 -2
- package/core/shared/labs.js +4 -4
- package/package.json +10 -10
- package/pnpm-lock.yaml +27 -20
- package/pnpm-workspace.yaml +3 -2
- package/tsconfig.json +1 -0
- package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
- package/core/built/admin/assets/Combination-CJG7Flp-.js +0 -41
- package/core/built/admin/assets/activitypub/layout-qx-bUL1k.js +0 -1
- package/core/built/admin/assets/automated-emails-DRy7qY-J.js +0 -7
- package/core/built/admin/assets/checkbox-CWa28lgO.js +0 -1
- package/core/built/admin/assets/chunk.524.cffd399783a519e404a9.js +0 -25
- package/core/built/admin/assets/chunk.582.eebf0d0b2d202bb746a5.js +0 -27
- package/core/built/admin/assets/code-editor-view-CCr0rBWS.js +0 -1
- package/core/built/admin/assets/comments-DyUSMUdj.js +0 -1
- package/core/built/admin/assets/disabled-sources-indicator-CUxMfiPa.js +0 -1
- package/core/built/admin/assets/dist-2Tnz2Yfq.js +0 -1
- package/core/built/admin/assets/dist-CjY-f5Z1.js +0 -1
- package/core/built/admin/assets/dist-DDY6GRVz.js +0 -1
- package/core/built/admin/assets/dist-DG78yroX.js +0 -23
- package/core/built/admin/assets/domain-C0bV2UV8.js +0 -1
- package/core/built/admin/assets/editor-Dp865O-4.js +0 -8
- package/core/built/admin/assets/esm-CFX93H5M.js +0 -2
- package/core/built/admin/assets/filter-sources-BV2rnpfA.js +0 -1
- package/core/built/admin/assets/filters-CUXJ2Nm9.js +0 -1
- package/core/built/admin/assets/filters-DfbIWc_l.js +0 -1
- package/core/built/admin/assets/index-9Cw8z3rA.css +0 -1
- package/core/built/admin/assets/index-BGG7NckO.js +0 -67
- package/core/built/admin/assets/input-group-BSgG_lVw.js +0 -1
- package/core/built/admin/assets/label-picker-1DvSuLA1.js +0 -1
- package/core/built/admin/assets/labels-CDSUt4J_.js +0 -1
- package/core/built/admin/assets/layout-yQEi2qDM.js +0 -1
- package/core/built/admin/assets/link-helpers-7EYqvvHs.js +0 -1
- package/core/built/admin/assets/member-custom-fields-yOMHyAg9.js +0 -1
- package/core/built/admin/assets/member-detail-BAq7gDaa.js +0 -1
- package/core/built/admin/assets/members-CQpVFJu4.js +0 -10
- package/core/built/admin/assets/modals-ReORvWnu.js +0 -31
- package/core/built/admin/assets/newsletter-CJQvtHqN.js +0 -1
- package/core/built/admin/assets/newsletters-B3QQXfGT.js +0 -1
- package/core/built/admin/assets/nql-BsgFMd93.js +0 -1
- package/core/built/admin/assets/overview-Bf2sbznq.js +0 -1
- package/core/built/admin/assets/overview-CBfePxcr.js +0 -1
- package/core/built/admin/assets/post-analytics-header-Bmc9su6q.js +0 -1
- package/core/built/admin/assets/preferences-DJPfHEGu.js +0 -5
- package/core/built/admin/assets/profile-DqBQp-gT.js +0 -1
- package/core/built/admin/assets/select-C5_xEt5b.js +0 -1
- package/core/built/admin/assets/settings-BwiVIXau.js +0 -4
- package/core/built/admin/assets/social-urls-BQPFEk1_.js +0 -8
- package/core/built/admin/assets/switch-z5Y6Uzt6.js +0 -1
- package/core/built/admin/assets/tabs-CCEgKYzq.js +0 -1
- package/core/built/admin/assets/tags-Cg2VJGmG.js +0 -1
- package/core/built/admin/assets/textarea-Bjccvh_E.js +0 -1
- package/core/built/admin/assets/toggle-group-b3IlxvhV.js +0 -1
- package/core/built/admin/assets/web-B_5GZRvH.js +0 -1
- package/core/server/services/gifts/gift-repository.js +0 -2
- package/core/server/services/gifts/gift-repository.ts +0 -29
- package/core/server/services/route-settings/activation-bridge.js +0 -174
- package/core/server/services/route-settings/activation-bridge.ts +0 -250
- package/core/server/services/url/local-file-cache.js +0 -75
- package/core/server/services/url/queue.js +0 -260
- package/core/server/services/url/resource.js +0 -102
- package/core/server/services/url/resources.js +0 -480
- package/core/server/services/url/url-generator.js +0 -276
- package/core/server/services/url/url-service-facade.js +0 -366
- package/core/server/services/url/url-service-facade.ts +0 -519
- package/core/server/services/url/url-service.js +0 -387
- package/core/server/services/url/urls.js +0 -140
- package/core/shared/events/url-resource-updated-event.js +0 -33
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import fs from 'node:fs';
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import {createRequire} from 'node:module';
|
|
4
|
+
import errors from '@tryghost/errors';
|
|
4
5
|
import type {AdapterConstructor} from "./types";
|
|
5
6
|
import type {ConfigInstance} from '../../../shared/config/loader';
|
|
6
7
|
|
|
@@ -122,6 +123,62 @@ export function resolveAdapterExport(moduleExport: unknown): AdapterConstructor
|
|
|
122
123
|
return null;
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
/**
|
|
127
|
+
* Locate and load an adapter constructor by type and class name, trying each of
|
|
128
|
+
* `pathsToAdapters` in order. Shared by the adapter manager and by
|
|
129
|
+
* bin/validate-adapters.ts, so a name resolves — and fails — identically at build
|
|
130
|
+
* time and at runtime.
|
|
131
|
+
*/
|
|
132
|
+
export function loadAdapterClass(
|
|
133
|
+
adapterType: string,
|
|
134
|
+
adapterClassName: string,
|
|
135
|
+
pathsToAdapters: string[],
|
|
136
|
+
loadAdapterFromPath: (path: string) => unknown
|
|
137
|
+
): AdapterConstructor {
|
|
138
|
+
for (const pathToAdapters of pathsToAdapters) {
|
|
139
|
+
let pathToAdapter = path.join(pathToAdapters, adapterType, adapterClassName);
|
|
140
|
+
if (pathToAdapters === '') {
|
|
141
|
+
// We are loading from node_modules, we can remove the `adapterType` prefix
|
|
142
|
+
pathToAdapter = path.join(pathToAdapters, adapterClassName);
|
|
143
|
+
}
|
|
144
|
+
// An adapter directory may be a full module with its own
|
|
145
|
+
// package.json, in which case this resolves the `exports` entry
|
|
146
|
+
// point; otherwise the path passes through unchanged.
|
|
147
|
+
const moduleToLoad = resolveAdapterEntryPoint(pathToAdapter);
|
|
148
|
+
|
|
149
|
+
try {
|
|
150
|
+
const Adapter = resolveAdapterExport(loadAdapterFromPath(moduleToLoad));
|
|
151
|
+
if (Adapter) {
|
|
152
|
+
return Adapter;
|
|
153
|
+
}
|
|
154
|
+
} catch (err) {
|
|
155
|
+
// Catch runtime errors
|
|
156
|
+
if (!(err instanceof Error) || (err as NodeJS.ErrnoException).code !== 'MODULE_NOT_FOUND') {
|
|
157
|
+
throw new errors.IncorrectUsageError({err: err as Error});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Catch missing dependencies BUT NOT missing adapter.
|
|
161
|
+
// Only check the first line — Node appends a "Require stack"
|
|
162
|
+
// that includes the adapter's own path, which would false-positive.
|
|
163
|
+
const firstLine = err.message.split('\n')[0];
|
|
164
|
+
if (!firstLine.includes(pathToAdapter)) {
|
|
165
|
+
// Name the unresolved module so the error is actionable, e.g.
|
|
166
|
+
// "Cannot find module 'superagent'" -> 'superagent'.
|
|
167
|
+
const missingMatch = /Cannot find module '([^']+)'/.exec(firstLine);
|
|
168
|
+
const missingModule = missingMatch ? ` '${missingMatch[1]}'` : '';
|
|
169
|
+
throw new errors.IncorrectUsageError({
|
|
170
|
+
message: `You are missing a dependency${missingModule} in your adapter ${pathToAdapter}`,
|
|
171
|
+
err: err as Error
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
throw new errors.IncorrectUsageError({
|
|
178
|
+
message: `Unable to find ${adapterType} adapter ${adapterClassName} in ${pathsToAdapters}.`
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
|
|
125
182
|
function getSchedulingConfig(config: ConfigInstance) {
|
|
126
183
|
const adapterConfig = config.get('adapters:scheduling');
|
|
127
184
|
if (!adapterConfig) {
|
|
@@ -50,7 +50,7 @@ class AudienceFeedbackController {
|
|
|
50
50
|
// Load the full post (with tags/authors) rather than passing an
|
|
51
51
|
// id-only resource: the URL service needs status to apply the
|
|
52
52
|
// published base filter and tags/authors to evaluate filtered
|
|
53
|
-
// routes — the
|
|
53
|
+
// routes — the URL service reports a bare {id} as thin.
|
|
54
54
|
const post = await this.#repository.getPostById(postId, {withRelated: ['tags', 'authors']});
|
|
55
55
|
const target = post
|
|
56
56
|
? this.#audienceFeedbackService.buildLink(uuid, post, score, key)
|
|
@@ -24,7 +24,7 @@ class AudienceFeedbackService {
|
|
|
24
24
|
*/
|
|
25
25
|
buildLink(uuid, post, score, key) {
|
|
26
26
|
const postData = toPlain(post);
|
|
27
|
-
let postUrl = this.#urlService.
|
|
27
|
+
let postUrl = this.#urlService.getUrlForResource({...postData, type: 'posts'}, {absolute: true});
|
|
28
28
|
|
|
29
29
|
if (postUrl.match(/\/404\//)) {
|
|
30
30
|
postUrl = this.#baseURL;
|
|
@@ -36,7 +36,7 @@ class AudienceFeedbackService {
|
|
|
36
36
|
* Feedback link for a post that no longer exists. Goes straight to the
|
|
37
37
|
* home page — the same destination buildLink picks when the URL service
|
|
38
38
|
* has no URL for the post — without asking the URL service: an id-only
|
|
39
|
-
* resource can't be routed (the
|
|
39
|
+
* resource can't be routed (the URL service reports it as thin).
|
|
40
40
|
*
|
|
41
41
|
* @param {string} uuid
|
|
42
42
|
* @param {string} postId
|
|
@@ -90,6 +90,7 @@ export type AutomatedEmailEvents = {
|
|
|
90
90
|
|
|
91
91
|
export type RecordEmailSentOptions = Readonly<{
|
|
92
92
|
automationActionRevisionId: string;
|
|
93
|
+
automationRunStepId: string;
|
|
93
94
|
mailgunMessageId?: string;
|
|
94
95
|
memberEmail: string;
|
|
95
96
|
memberId: string;
|
|
@@ -203,11 +204,12 @@ export interface AutomationsRepository {
|
|
|
203
204
|
eventsByAutomatedEmailRecipientId: ReadonlyDeep<Map<string, AutomatedEmailEvents>>
|
|
204
205
|
): Promise<void>;
|
|
205
206
|
/**
|
|
206
|
-
*
|
|
207
|
+
* Record the first click timestamp for the automated email recipient identified by a run step.
|
|
207
208
|
*
|
|
208
209
|
*/
|
|
209
210
|
trackEmailClicked(options: {
|
|
210
211
|
automationActionRevisionId: string;
|
|
212
|
+
automationRunStepId: string;
|
|
211
213
|
memberId: string;
|
|
212
214
|
clickedAt: Readonly<Date>;
|
|
213
215
|
}, transactionOptions?: {
|
|
@@ -135,6 +135,7 @@ function createDatabaseAutomationsRepository({ knex, fakeWaitHoursMultiplier })
|
|
|
135
135
|
member_email: options.memberEmail,
|
|
136
136
|
member_name: options.memberName,
|
|
137
137
|
automation_action_revision_id: options.automationActionRevisionId,
|
|
138
|
+
automation_run_step_id: options.automationRunStepId,
|
|
138
139
|
...(options.mailgunMessageId ? { mailgun_message_id: options.mailgunMessageId } : {}),
|
|
139
140
|
track_clicks: options.trackClicks,
|
|
140
141
|
track_opens: options.trackOpens,
|
|
@@ -157,6 +158,13 @@ function createDatabaseAutomationsRepository({ knex, fakeWaitHoursMultiplier })
|
|
|
157
158
|
return;
|
|
158
159
|
}
|
|
159
160
|
await knex.transaction(async (trx) => {
|
|
161
|
+
const revisionIds = new Set();
|
|
162
|
+
for (const { openedAt, automationActionRevisionId } of eventsByAutomatedEmailRecipientId.values()) {
|
|
163
|
+
if (openedAt) {
|
|
164
|
+
revisionIds.add(automationActionRevisionId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const orderedRevisionIds = await lockActionRevisions(trx, revisionIds);
|
|
160
168
|
const notYetOpened = await lockNotYetOpened(trx, eventsByAutomatedEmailRecipientId);
|
|
161
169
|
const newOpensPerRevision = new Map();
|
|
162
170
|
for (const [id, { deliveredAt, openedAt, automationActionRevisionId }] of eventsByAutomatedEmailRecipientId) {
|
|
@@ -177,10 +185,11 @@ function createDatabaseAutomationsRepository({ knex, fakeWaitHoursMultiplier })
|
|
|
177
185
|
newOpensPerRevision.set(automationActionRevisionId, (newOpensPerRevision.get(automationActionRevisionId) ?? 0) + 1);
|
|
178
186
|
}
|
|
179
187
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
188
|
+
for (const id of orderedRevisionIds) {
|
|
189
|
+
const opens = newOpensPerRevision.get(id);
|
|
190
|
+
if (!opens) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
184
193
|
await trx('automation_action_revisions')
|
|
185
194
|
.where({ id })
|
|
186
195
|
.update({
|
|
@@ -189,24 +198,17 @@ function createDatabaseAutomationsRepository({ knex, fakeWaitHoursMultiplier })
|
|
|
189
198
|
}
|
|
190
199
|
});
|
|
191
200
|
},
|
|
192
|
-
async trackEmailClicked({ automationActionRevisionId, memberId, clickedAt }, { transacting } = {}) {
|
|
201
|
+
async trackEmailClicked({ automationActionRevisionId, automationRunStepId, memberId, clickedAt }, { transacting } = {}) {
|
|
193
202
|
const trackClick = async (trx) => {
|
|
194
|
-
|
|
195
|
-
await trx('automation_action_revisions')
|
|
196
|
-
.select('id')
|
|
197
|
-
.where({ id: automationActionRevisionId })
|
|
198
|
-
.forUpdate()
|
|
199
|
-
.first();
|
|
203
|
+
await lockActionRevisions(trx, [automationActionRevisionId]);
|
|
200
204
|
const recipient = await trx('automated_email_recipients')
|
|
201
205
|
.select('id', 'clicked_at')
|
|
202
206
|
.where({
|
|
203
207
|
automation_action_revision_id: automationActionRevisionId,
|
|
208
|
+
automation_run_step_id: automationRunStepId,
|
|
204
209
|
member_id: memberId,
|
|
205
210
|
track_clicks: true
|
|
206
211
|
})
|
|
207
|
-
.where('created_at', '<=', (0, database_date_1.toDatabaseDate)(clickedAt))
|
|
208
|
-
.orderBy('created_at', 'desc')
|
|
209
|
-
.orderBy('id', 'desc')
|
|
210
212
|
.forUpdate()
|
|
211
213
|
.first();
|
|
212
214
|
if (!recipient || recipient.clicked_at !== null) {
|
|
@@ -233,6 +235,23 @@ function createDatabaseAutomationsRepository({ knex, fakeWaitHoursMultiplier })
|
|
|
233
235
|
}
|
|
234
236
|
};
|
|
235
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Lock revisions before recipients because inserting a recipient takes a shared
|
|
240
|
+
* foreign-key lock on its revision. Updating that revision later can deadlock
|
|
241
|
+
* with another transaction that has already locked the revision and is waiting
|
|
242
|
+
* for the recipient. Keep multi-revision lock acquisition deterministic.
|
|
243
|
+
*/
|
|
244
|
+
async function lockActionRevisions(trx, revisionIds) {
|
|
245
|
+
const sortedRevisionIds = [...new Set(revisionIds)]
|
|
246
|
+
.sort((left, right) => left.localeCompare(right));
|
|
247
|
+
if (sortedRevisionIds.length > 0) {
|
|
248
|
+
await trx('automation_action_revisions')
|
|
249
|
+
.select('id')
|
|
250
|
+
.whereIn('id', sortedRevisionIds)
|
|
251
|
+
.forUpdate();
|
|
252
|
+
}
|
|
253
|
+
return sortedRevisionIds;
|
|
254
|
+
}
|
|
236
255
|
/**
|
|
237
256
|
* Which of these recipients have yet to open, and so should count towards their
|
|
238
257
|
* revision's open count. Locks them for the transaction, so a worker racing on
|
|
@@ -276,6 +276,7 @@ export function createDatabaseAutomationsRepository({
|
|
|
276
276
|
member_email: options.memberEmail,
|
|
277
277
|
member_name: options.memberName,
|
|
278
278
|
automation_action_revision_id: options.automationActionRevisionId,
|
|
279
|
+
automation_run_step_id: options.automationRunStepId,
|
|
279
280
|
...(options.mailgunMessageId ? {mailgun_message_id: options.mailgunMessageId} : {}),
|
|
280
281
|
track_clicks: options.trackClicks,
|
|
281
282
|
track_opens: options.trackOpens,
|
|
@@ -301,6 +302,15 @@ export function createDatabaseAutomationsRepository({
|
|
|
301
302
|
}
|
|
302
303
|
|
|
303
304
|
await knex.transaction(async (trx) => {
|
|
305
|
+
const revisionIds = new Set<string>();
|
|
306
|
+
for (const {openedAt, automationActionRevisionId} of eventsByAutomatedEmailRecipientId.values()) {
|
|
307
|
+
if (openedAt) {
|
|
308
|
+
revisionIds.add(automationActionRevisionId);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const orderedRevisionIds = await lockActionRevisions(trx, revisionIds);
|
|
313
|
+
|
|
304
314
|
const notYetOpened = await lockNotYetOpened(trx, eventsByAutomatedEmailRecipientId);
|
|
305
315
|
const newOpensPerRevision = new Map<string, number>();
|
|
306
316
|
|
|
@@ -327,11 +337,11 @@ export function createDatabaseAutomationsRepository({
|
|
|
327
337
|
}
|
|
328
338
|
}
|
|
329
339
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
340
|
+
for (const id of orderedRevisionIds) {
|
|
341
|
+
const opens = newOpensPerRevision.get(id);
|
|
342
|
+
if (!opens) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
335
345
|
await trx('automation_action_revisions')
|
|
336
346
|
.where({id})
|
|
337
347
|
.update({
|
|
@@ -341,25 +351,18 @@ export function createDatabaseAutomationsRepository({
|
|
|
341
351
|
});
|
|
342
352
|
},
|
|
343
353
|
|
|
344
|
-
async trackEmailClicked({automationActionRevisionId, memberId, clickedAt}, {transacting} = {}) {
|
|
354
|
+
async trackEmailClicked({automationActionRevisionId, automationRunStepId, memberId, clickedAt}, {transacting} = {}) {
|
|
345
355
|
const trackClick = async (trx: Knex.Transaction) => {
|
|
346
|
-
|
|
347
|
-
await trx('automation_action_revisions')
|
|
348
|
-
.select('id')
|
|
349
|
-
.where({id: automationActionRevisionId})
|
|
350
|
-
.forUpdate()
|
|
351
|
-
.first();
|
|
356
|
+
await lockActionRevisions(trx, [automationActionRevisionId]);
|
|
352
357
|
|
|
353
358
|
const recipient = await trx('automated_email_recipients')
|
|
354
359
|
.select('id', 'clicked_at')
|
|
355
360
|
.where({
|
|
356
361
|
automation_action_revision_id: automationActionRevisionId,
|
|
362
|
+
automation_run_step_id: automationRunStepId,
|
|
357
363
|
member_id: memberId,
|
|
358
364
|
track_clicks: true
|
|
359
365
|
})
|
|
360
|
-
.where('created_at', '<=', toDatabaseDate(clickedAt))
|
|
361
|
-
.orderBy('created_at', 'desc')
|
|
362
|
-
.orderBy('id', 'desc')
|
|
363
366
|
.forUpdate()
|
|
364
367
|
.first();
|
|
365
368
|
|
|
@@ -394,6 +397,29 @@ export function createDatabaseAutomationsRepository({
|
|
|
394
397
|
};
|
|
395
398
|
}
|
|
396
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Lock revisions before recipients because inserting a recipient takes a shared
|
|
402
|
+
* foreign-key lock on its revision. Updating that revision later can deadlock
|
|
403
|
+
* with another transaction that has already locked the revision and is waiting
|
|
404
|
+
* for the recipient. Keep multi-revision lock acquisition deterministic.
|
|
405
|
+
*/
|
|
406
|
+
async function lockActionRevisions(
|
|
407
|
+
trx: Knex.Transaction,
|
|
408
|
+
revisionIds: Iterable<string>
|
|
409
|
+
): Promise<string[]> {
|
|
410
|
+
const sortedRevisionIds = [...new Set(revisionIds)]
|
|
411
|
+
.sort((left, right) => left.localeCompare(right));
|
|
412
|
+
|
|
413
|
+
if (sortedRevisionIds.length > 0) {
|
|
414
|
+
await trx('automation_action_revisions')
|
|
415
|
+
.select('id')
|
|
416
|
+
.whereIn('id', sortedRevisionIds)
|
|
417
|
+
.forUpdate();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return sortedRevisionIds;
|
|
421
|
+
}
|
|
422
|
+
|
|
397
423
|
/**
|
|
398
424
|
* Which of these recipients have yet to open, and so should count towards their
|
|
399
425
|
* revision's open count. Locks them for the transaction, so a worker racing on
|
|
@@ -0,0 +1,13 @@
|
|
|
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.getSchedulerIdempotencyKey = getSchedulerIdempotencyKey;
|
|
7
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
|
+
function getSchedulerIdempotencyKey(date, url) {
|
|
9
|
+
const hash = node_crypto_1.default.createHash('sha256');
|
|
10
|
+
hash.update(date.toISOString());
|
|
11
|
+
hash.update(url.href);
|
|
12
|
+
return `ghost-automations-${hash.digest('hex')}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export function getSchedulerIdempotencyKey(date: Readonly<Date>, url: Readonly<URL>): string {
|
|
4
|
+
const hash = crypto.createHash('sha256');
|
|
5
|
+
hash.update(date.toISOString());
|
|
6
|
+
hash.update(url.href);
|
|
7
|
+
return `ghost-automations-${hash.digest('hex')}`;
|
|
8
|
+
}
|
|
@@ -129,7 +129,8 @@ const processStep = async ({ automationsApi, memberWelcomeEmailService, schedule
|
|
|
129
129
|
memberStatus,
|
|
130
130
|
trackOpens,
|
|
131
131
|
trackClicks,
|
|
132
|
-
automationActionRevisionId: step.automation_action_revision_id
|
|
132
|
+
automationActionRevisionId: step.automation_action_revision_id,
|
|
133
|
+
automationRunStepId: step.id
|
|
133
134
|
});
|
|
134
135
|
const mailgunMessageId = (0, mailgun_message_id_1.getMailgunMessageId)(sendResult);
|
|
135
136
|
// Only Mailgun sends can produce open events for automation emails
|
|
@@ -137,6 +138,7 @@ const processStep = async ({ automationsApi, memberWelcomeEmailService, schedule
|
|
|
137
138
|
try {
|
|
138
139
|
await automationsApi.recordEmailSent({
|
|
139
140
|
automationActionRevisionId: step.automation_action_revision_id,
|
|
141
|
+
automationRunStepId: step.id,
|
|
140
142
|
...(mailgunMessageId ? { mailgunMessageId } : {}),
|
|
141
143
|
memberEmail: member.get('email'),
|
|
142
144
|
memberId: step.member_id,
|
|
@@ -28,6 +28,7 @@ type MemberWelcomeEmailService = {
|
|
|
28
28
|
trackOpens: boolean;
|
|
29
29
|
trackClicks: boolean;
|
|
30
30
|
automationActionRevisionId: string;
|
|
31
|
+
automationRunStepId: string;
|
|
31
32
|
}) => Promise<unknown>;
|
|
32
33
|
};
|
|
33
34
|
};
|
|
@@ -202,7 +203,8 @@ const processStep = async ({
|
|
|
202
203
|
memberStatus,
|
|
203
204
|
trackOpens,
|
|
204
205
|
trackClicks,
|
|
205
|
-
automationActionRevisionId: step.automation_action_revision_id
|
|
206
|
+
automationActionRevisionId: step.automation_action_revision_id,
|
|
207
|
+
automationRunStepId: step.id
|
|
206
208
|
});
|
|
207
209
|
const mailgunMessageId = getMailgunMessageId(sendResult);
|
|
208
210
|
// Only Mailgun sends can produce open events for automation emails
|
|
@@ -210,6 +212,7 @@ const processStep = async ({
|
|
|
210
212
|
try {
|
|
211
213
|
await automationsApi.recordEmailSent({
|
|
212
214
|
automationActionRevisionId: step.automation_action_revision_id,
|
|
215
|
+
automationRunStepId: step.id,
|
|
213
216
|
...(mailgunMessageId ? {mailgunMessageId} : {}),
|
|
214
217
|
memberEmail: member.get('email'),
|
|
215
218
|
memberId: step.member_id,
|
|
@@ -41,6 +41,7 @@ const strict_1 = __importDefault(require("node:assert/strict"));
|
|
|
41
41
|
const one_at_a_time_1 = require("../../../shared/one-at-a-time");
|
|
42
42
|
const poll_1 = require("./poll");
|
|
43
43
|
const automationsApi = __importStar(require("./automations-api"));
|
|
44
|
+
const get_scheduler_idempotency_key_1 = require("./get-scheduler-idempotency-key");
|
|
44
45
|
const promises_1 = require("node:timers/promises");
|
|
45
46
|
const soonest_timer_1 = require("../../lib/soonest-timer");
|
|
46
47
|
const scheduler_poll_time_1 = require("./scheduler-poll-time");
|
|
@@ -101,20 +102,23 @@ class AutomationsService {
|
|
|
101
102
|
schedulerAdapter.schedule({
|
|
102
103
|
time: schedulerPollTime.getTime(),
|
|
103
104
|
url: url.toString(),
|
|
104
|
-
extra: {
|
|
105
|
+
extra: {
|
|
106
|
+
httpMethod: 'PUT',
|
|
107
|
+
idempotencyKey: (0, get_scheduler_idempotency_key_1.getSchedulerIdempotencyKey)(date, url)
|
|
108
|
+
}
|
|
105
109
|
});
|
|
106
110
|
}
|
|
107
111
|
catch (err) {
|
|
108
112
|
logging.error({ event: { name: 'automations.enqueue-poll.error' }, err, at: date.toISOString() }, 'Failed to enqueue automations poll');
|
|
109
113
|
}
|
|
110
114
|
};
|
|
111
|
-
domainEvents.subscribe(StartAutomationsPollEvent, (0, one_at_a_time_1.oneAtATime)(
|
|
115
|
+
domainEvents.subscribe(StartAutomationsPollEvent, (0, one_at_a_time_1.oneAtATime)(() => (0, poll_1.poll)({
|
|
112
116
|
automationsApi,
|
|
113
117
|
memberWelcomeEmailService,
|
|
114
118
|
scheduleAutomationEmailAnalyticsJob,
|
|
115
119
|
enqueueAnotherPollAt: enqueuePollAt
|
|
116
120
|
})));
|
|
117
|
-
domainEvents.subscribe(StartAutomationsPollEvent, (0, one_at_a_time_1.oneAtATime)(
|
|
121
|
+
domainEvents.subscribe(StartAutomationsPollEvent, (0, one_at_a_time_1.oneAtATime)(() => welcomeEmailAutomationPoll({
|
|
118
122
|
memberWelcomeEmailService,
|
|
119
123
|
enqueueAnotherPollAt: enqueuePollAt
|
|
120
124
|
})));
|
|
@@ -6,6 +6,7 @@ import type DomainEvents from '@tryghost/domain-events';
|
|
|
6
6
|
import {oneAtATime} from '../../../shared/one-at-a-time';
|
|
7
7
|
import {poll} from './poll';
|
|
8
8
|
import * as automationsApi from './automations-api';
|
|
9
|
+
import {getSchedulerIdempotencyKey} from './get-scheduler-idempotency-key';
|
|
9
10
|
import {setImmediate as flushEventLoop} from 'node:timers/promises';
|
|
10
11
|
import {SoonestTimer} from '../../lib/soonest-timer';
|
|
11
12
|
import {getSchedulerPollTime} from './scheduler-poll-time';
|
|
@@ -85,21 +86,24 @@ export class AutomationsService {
|
|
|
85
86
|
schedulerAdapter.schedule({
|
|
86
87
|
time: schedulerPollTime.getTime(),
|
|
87
88
|
url: url.toString(),
|
|
88
|
-
extra: {
|
|
89
|
+
extra: {
|
|
90
|
+
httpMethod: 'PUT',
|
|
91
|
+
idempotencyKey: getSchedulerIdempotencyKey(date, url)
|
|
92
|
+
}
|
|
89
93
|
});
|
|
90
94
|
} catch (err) {
|
|
91
95
|
logging.error({event: {name: 'automations.enqueue-poll.error'}, err, at: date.toISOString()}, 'Failed to enqueue automations poll');
|
|
92
96
|
}
|
|
93
97
|
};
|
|
94
98
|
|
|
95
|
-
domainEvents.subscribe(StartAutomationsPollEvent, oneAtATime(
|
|
99
|
+
domainEvents.subscribe(StartAutomationsPollEvent, oneAtATime(() => poll({
|
|
96
100
|
automationsApi,
|
|
97
101
|
memberWelcomeEmailService,
|
|
98
102
|
scheduleAutomationEmailAnalyticsJob,
|
|
99
103
|
enqueueAnotherPollAt: enqueuePollAt
|
|
100
104
|
})));
|
|
101
105
|
|
|
102
|
-
domainEvents.subscribe(StartAutomationsPollEvent, oneAtATime(
|
|
106
|
+
domainEvents.subscribe(StartAutomationsPollEvent, oneAtATime(() => welcomeEmailAutomationPoll({
|
|
103
107
|
memberWelcomeEmailService,
|
|
104
108
|
enqueueAnotherPollAt: enqueuePollAt
|
|
105
109
|
})));
|
|
@@ -29,7 +29,7 @@ class CommentsServiceEmails {
|
|
|
29
29
|
// Comments live on pages as well as posts; the URL service routes by
|
|
30
30
|
// the passed type, so a page typed 'posts' resolves to /404/.
|
|
31
31
|
const routerType = postData.type === 'page' ? 'pages' : 'posts';
|
|
32
|
-
const baseUrl = this.urlService.
|
|
32
|
+
const baseUrl = this.urlService.getUrlForResource({...postData, type: routerType}, {absolute: true});
|
|
33
33
|
return `${baseUrl}#ghost-comments-${commentId}`;
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -473,7 +473,7 @@ class CommentsService {
|
|
|
473
473
|
* @param {AdminBrowseAllOptions} options
|
|
474
474
|
*/
|
|
475
475
|
async getAdminAllComments({includeNested, filter, mongoTransformer, reportCount, order, page, limit}) {
|
|
476
|
-
const postUrlRelations = this.urlService.
|
|
476
|
+
const postUrlRelations = this.urlService.getRequiredRelations().map(relation => `post.${relation}`);
|
|
477
477
|
const withRelated = ['member', 'post', ...postUrlRelations, 'count.replies', 'count.direct_replies', 'count.likes', 'count.dislikes', 'count.net_score', 'count.reports', 'in_reply_to', 'parent'];
|
|
478
478
|
|
|
479
479
|
return await this.models.Comment.findPage({
|
|
@@ -4,7 +4,7 @@ const config = require('../../../shared/config');
|
|
|
4
4
|
const domainEvents = require('@tryghost/domain-events');
|
|
5
5
|
/** @import {PrometheusClient} from '@tryghost/prometheus-metrics' */
|
|
6
6
|
/** @import {BatchEventProcessor} from './batch-event-processor' */
|
|
7
|
-
/** @import {JobNames, CursorSeed} from './email-analytics-service' */
|
|
7
|
+
/** @import {JobNames, CursorSeed, EmailAnalyticsFetchResult} from './email-analytics-service' */
|
|
8
8
|
|
|
9
9
|
class EmailAnalyticsServiceWrapper {
|
|
10
10
|
/** @type {string} */ #logName;
|
|
@@ -74,7 +74,7 @@ class EmailAnalyticsServiceWrapper {
|
|
|
74
74
|
/**
|
|
75
75
|
* Log comprehensive job completion with timing metrics
|
|
76
76
|
* @param {string} jobType - Type of job (e.g., 'latest-opened', 'latest', 'missing', 'scheduled')
|
|
77
|
-
* @param {
|
|
77
|
+
* @param {EmailAnalyticsFetchResult} fetchResult - The fetch result from EmailAnalyticsService
|
|
78
78
|
* @param {number} totalDurationMs - Total duration in milliseconds
|
|
79
79
|
*/
|
|
80
80
|
_logJobCompletion(jobType, fetchResult, totalDurationMs) {
|
|
@@ -158,6 +158,11 @@ class EmailAnalyticsServiceWrapper {
|
|
|
158
158
|
return fetchResult.eventCount;
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
+
/**
|
|
162
|
+
* @param {object} options
|
|
163
|
+
* @param {number} options.maxEvents
|
|
164
|
+
* @returns {Promise<number>} The number of scheduled events fetched
|
|
165
|
+
*/
|
|
161
166
|
async fetchScheduled({maxEvents}) {
|
|
162
167
|
if (maxEvents < 300) {
|
|
163
168
|
return 0;
|
|
@@ -227,6 +232,10 @@ class EmailAnalyticsServiceWrapper {
|
|
|
227
232
|
this.#fetching = false;
|
|
228
233
|
}
|
|
229
234
|
|
|
235
|
+
/**
|
|
236
|
+
* @param {string} reason
|
|
237
|
+
* @returns {void}
|
|
238
|
+
*/
|
|
230
239
|
_restartFetch(reason) {
|
|
231
240
|
this.#fetching = false;
|
|
232
241
|
logging.info(`${this.#logPrefix} Restarting fetch due to ${reason}`);
|
|
@@ -372,7 +372,7 @@ module.exports = class EmailAnalyticsService {
|
|
|
372
372
|
* @param {EmailAnalyticsEvent[]} [options.eventTypes] - Array of event types to fetch. If not provided, Mailgun will return all event types.
|
|
373
373
|
* @returns {Promise<EmailAnalyticsFetchResult>} Fetch results with timing metrics
|
|
374
374
|
*/
|
|
375
|
-
async #fetchEvents(fetchData, {begin, end, maxEvents = Infinity, eventTypes
|
|
375
|
+
async #fetchEvents(fetchData, {begin, end, maxEvents = Infinity, eventTypes}) {
|
|
376
376
|
// Start where we left of, or the last stored event in the database, or start 30 minutes ago if we have nothing available
|
|
377
377
|
// Store that we started fetching
|
|
378
378
|
fetchData.running = true;
|
|
@@ -31,7 +31,7 @@ class EmailServiceWrapper {
|
|
|
31
31
|
|
|
32
32
|
const {Post, Newsletter, Email, EmailBatch, EmailRecipient, Member} = require('../../models');
|
|
33
33
|
const urlService = require('../url');
|
|
34
|
-
const getRequiredUrlRelations = () => urlService.
|
|
34
|
+
const getRequiredUrlRelations = () => urlService.getRequiredRelations();
|
|
35
35
|
const MailgunClient = require('../lib/mailgun-client');
|
|
36
36
|
const configService = require('../../../shared/config');
|
|
37
37
|
const settingsCache = require('../../../shared/settings-cache');
|
|
@@ -75,6 +75,7 @@ class EmailServiceWrapper {
|
|
|
75
75
|
|
|
76
76
|
const mailgunEmailProvider = new MailgunEmailProvider({
|
|
77
77
|
mailgunClient,
|
|
78
|
+
config: configService,
|
|
78
79
|
errorHandler
|
|
79
80
|
});
|
|
80
81
|
|
|
@@ -2,6 +2,11 @@ const logging = require('@tryghost/logging');
|
|
|
2
2
|
const errors = require('@tryghost/errors');
|
|
3
3
|
const debug = require('@tryghost/debug')('email-service:mailgun-provider-service');
|
|
4
4
|
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {object} Config
|
|
7
|
+
* @prop {(key: string) => unknown} get
|
|
8
|
+
*/
|
|
9
|
+
|
|
5
10
|
/**
|
|
6
11
|
* @typedef {object} Recipient
|
|
7
12
|
* @prop {string} email
|
|
@@ -29,18 +34,22 @@ const debug = require('@tryghost/debug')('email-service:mailgun-provider-service
|
|
|
29
34
|
|
|
30
35
|
class MailgunEmailProvider {
|
|
31
36
|
#mailgunClient;
|
|
37
|
+
#config;
|
|
32
38
|
#errorHandler;
|
|
33
39
|
|
|
34
40
|
/**
|
|
35
41
|
* @param {object} dependencies
|
|
36
42
|
* @param {import('../lib/mailgun-client')} dependencies.mailgunClient - mailgun client to send emails
|
|
43
|
+
* @param {Config} dependencies.config
|
|
37
44
|
* @param {Function} [dependencies.errorHandler] - custom error handler for logging exceptions
|
|
38
45
|
*/
|
|
39
46
|
constructor({
|
|
40
47
|
mailgunClient,
|
|
48
|
+
config,
|
|
41
49
|
errorHandler
|
|
42
50
|
}) {
|
|
43
51
|
this.#mailgunClient = mailgunClient;
|
|
52
|
+
this.#config = config;
|
|
44
53
|
this.#errorHandler = errorHandler;
|
|
45
54
|
}
|
|
46
55
|
|
|
@@ -108,12 +117,18 @@ class MailgunEmailProvider {
|
|
|
108
117
|
plaintext,
|
|
109
118
|
from,
|
|
110
119
|
replyTo,
|
|
120
|
+
tags: ['bulk-email'],
|
|
111
121
|
domainOverride,
|
|
112
122
|
id: emailId,
|
|
113
123
|
track_opens: !!options.openTrackingEnabled,
|
|
114
124
|
track_clicks: !!options.clickTrackingEnabled
|
|
115
125
|
};
|
|
116
126
|
|
|
127
|
+
const mailgunTagFromConfig = this.#config.get('bulkEmail:mailgun:tag');
|
|
128
|
+
if (typeof mailgunTagFromConfig === 'string' && mailgunTagFromConfig.length > 0) {
|
|
129
|
+
messageData.tags.push(mailgunTagFromConfig);
|
|
130
|
+
}
|
|
131
|
+
|
|
117
132
|
if (options.deliveryTime && options.deliveryTime instanceof Date) {
|
|
118
133
|
messageData.deliveryTime = options.deliveryTime;
|
|
119
134
|
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
# Gift Links
|
|
2
|
+
|
|
3
|
+
Gift Links covers shareable access to individual protected posts and pages without creating a membership.
|
|
4
|
+
|
|
5
|
+
## Language
|
|
6
|
+
|
|
7
|
+
**Gift Link**:
|
|
8
|
+
A revocable link that grants its holder access to one protected post or page. It does not create or redeem a gift subscription.
|
|
9
|
+
_Avoid_: Redemption link, gift-subscription link
|