ghost 6.56.0 → 6.57.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/bin/validate-adapters.js +74 -0
- package/bin/validate-adapters.ts +87 -0
- package/bin/warm-compile-cache.sh +60 -0
- package/components/tryghost-adapter-base-scheduling-0.2.1.tgz +0 -0
- package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-kg-converters-1.2.4.tgz +0 -0
- package/components/tryghost-kg-default-cards-10.3.4.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.1.5.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-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/about-BNojRaxf.js +1 -0
- package/core/built/admin/assets/{account-migration-D3O_yVck.js → account-migration-DMS8JsM-.js} +1 -1
- package/core/built/admin/assets/action-list-lGzZLHc8.js +1 -0
- package/core/built/admin/assets/activitypub/{account-migration-D8FbP-Z-.js → account-migration-DHFbePl6.js} +7 -7
- package/core/built/admin/assets/activitypub/activitypub.js +3 -3
- package/core/built/admin/assets/activitypub/{avatar-11EA1VSs.js → avatar-CT4tS2_S.js} +2 -2
- package/core/built/admin/assets/activitypub/{avatar-flipboard-jfbLG-xe.js → avatar-flipboard-ByYWKipo.js} +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-f1jTQe2P.js → bluesky-sharing-srVnSHDz.js} +8 -8
- package/core/built/admin/assets/activitypub/{content-formatters-nmTOPY9M.js → content-formatters-C2sos5Ht.js} +3 -3
- package/core/built/admin/assets/activitypub/{copy-BCwo9ep3.js → copy-B1UBTOoZ.js} +2 -2
- package/core/built/admin/assets/activitypub/{dist-BEfX-0aW.js → dist-MPsb2WZ1.js} +4 -4
- package/core/built/admin/assets/activitypub/{domain-AaDlZlrA.js → domain-DzcM9Dzo.js} +9 -9
- package/core/built/admin/assets/activitypub/{edit-profile-DPi8Tl5M.js → edit-profile-CsQ_4xAk.js} +5 -5
- package/core/built/admin/assets/activitypub/{explore-CxTzjILQ.js → explore-CJe0DZBK.js} +8 -8
- package/core/built/admin/assets/activitypub/{feed-DPv5x2MX.js → feed-CKV3qWON.js} +9 -9
- package/core/built/admin/assets/activitypub/{field-DbgA8lat.js → field-DckHfDJf.js} +3 -3
- package/core/built/admin/assets/activitypub/{hash-BWuZoYkA.js → hash-Dlp8RM55.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-35VDQ2K2.js → inbox-CsPy4R0M.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-DSrF_i1O.js → inbox-DAsjzUUe.js} +10 -10
- package/core/built/admin/assets/activitypub/{label-CPjFBbo7.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-BkDC1XVZ.js → moderation-B0Inc03M.js} +7 -7
- package/core/built/admin/assets/activitypub/{note-xEyH4xT-.js → note-CSE54lER.js} +7 -7
- package/core/built/admin/assets/activitypub/{notifications-DClaL3ui.js → notifications-RSwbKqdh.js} +9 -9
- package/core/built/admin/assets/activitypub/{preferences-DpnOa-4Z.js → preferences-CE231n2_.js} +11 -11
- package/core/built/admin/assets/activitypub/{profile-Dvi9mz-6.js → profile-aaQ1j1Eo.js} +13 -13
- package/core/built/admin/assets/activitypub/{reply-Cs9ySd6Z.js → reply-Cjb0Vs3m.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-DFBeTnfn.js → routes-Djb0mc9k.js} +26 -26
- package/core/built/admin/assets/activitypub/{separator-C5u_VjzY.js → separator-D8pROI5n.js} +3 -3
- package/core/built/admin/assets/activitypub/{settings-CV2fFu6D.js → settings-DMc32IQl.js} +5 -5
- package/core/built/admin/assets/activitypub/{site-_TgDd_EM.js → site-DVIcsohX.js} +4 -4
- package/core/built/admin/assets/activitypub/{step-1-BfnNL2oF.js → step-1-9Yey9usF.js} +5 -5
- package/core/built/admin/assets/activitypub/{step-2-DLndZkO-.js → step-2-DdYmKRXV.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-3-A-PZTtRE.js → step-3-hdSkcjZU.js} +10 -10
- package/core/built/admin/assets/activitypub/{tabs-BUbz7tea.js → tabs-CUqseYzQ.js} +6 -6
- package/core/built/admin/assets/activitypub/{topic-filter-BdX_xYYT.js → topic-filter-Coni6OXM.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-EwuRemHh.js → use-activity-pub-queries-MeHTPMQv.js} +2 -2
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-cy4ACBc4.js → use-navigate-with-base-path-B046AM38.js} +24 -7
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-CyFcGfan.js → use-reply-chain-data-1AO-Xob6.js} +5 -5
- package/core/built/admin/assets/activitypub/{x-7zk8mV2p.js → x-BRfRYd16.js} +2 -2
- package/core/built/admin/assets/add-integration-modal-DU9ghGLS.js +1 -0
- package/core/built/admin/assets/add-newsletter-modal-DFxuQrYu.js +1 -0
- package/core/built/admin/assets/add-recommendation-modal-D-3LOrdd.js +1 -0
- package/core/built/admin/assets/analytics-DTIMRhWw.js +1 -0
- package/core/built/admin/assets/announcement-bar-modal-DGCcezB6.js +1 -0
- package/core/built/admin/assets/api-keys-Dx4NeNpK.js +1 -0
- package/core/built/admin/assets/api-keys-_iCT4dxr.js +1 -0
- package/core/built/admin/assets/{audience-CkBb2LlO.js → audience-DkknNqhZ.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-cMllU545.js → automations-DvSU7QhW.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-DPGpyD-G.js → bluesky-sharing-yZ9A_jL8.js} +1 -1
- package/core/built/admin/assets/brand-icon-09YqFZjV.js +1 -0
- package/core/built/admin/assets/{breadcrumb-DIfcF5ow.js → breadcrumb-CjOH7LCU.js} +1 -1
- package/core/built/admin/assets/{chart-helpers-DV0FkdcD.js → chart-helpers-DGxvWnBT.js} +1 -1
- package/core/built/admin/assets/checkbox-BSUcnNq_.js +1 -0
- package/core/built/admin/assets/{chunk.332.05991a101d67f732dec3.js → chunk.424.4a27628efdade15c2dab.js} +28 -27
- package/core/built/admin/assets/chunk.524.dce1c51e0bc30fb2718e.js +25 -0
- package/core/built/admin/assets/{chunk.211.6f7328d63c41536451b3.js → chunk.571.40f093ed6d883993714e.js} +5 -5
- package/core/built/admin/assets/chunk.582.3a430d5c6a744fd475b6.js +27 -0
- package/core/built/admin/assets/code-editor-view-BcyKABrG.js +1 -0
- package/core/built/admin/assets/color-picker-field-8NoSnhNI.js +1 -0
- package/core/built/admin/assets/color-utils-Dzeeuj1F.js +1 -0
- package/core/built/admin/assets/combobox-5rdgDrDV.js +1 -0
- package/core/built/admin/assets/command-BOYMbZa_.js +1 -0
- package/core/built/admin/assets/comments-BhNM5CYl.js +1 -0
- package/core/built/admin/assets/components-CwL6a6As-B0E0-NBJ.js +1 -0
- package/core/built/admin/assets/confirmation-modal-CYD7x3UQ.js +1 -0
- package/core/built/admin/assets/content-api-modal-BtW4Z1-1.js +1 -0
- package/core/built/admin/assets/copy-field-CEp_XieW.js +1 -0
- package/core/built/admin/assets/currency-Dy4Tc2R2.js +1 -0
- package/core/built/admin/assets/custom-integration-modal-bk3lNEuQ.js +1 -0
- package/core/built/admin/assets/{date-range-select-dXc-GsVe.js → date-range-select-B3Ij1Rqg.js} +1 -1
- package/core/built/admin/assets/debounce-Cx5B_9OI.js +3 -0
- package/core/built/admin/assets/design-and-theme-modal-B2xYKS1I.js +23 -0
- package/core/built/admin/assets/dirty-confirm-dialog-YE9L21gY.js +1 -0
- package/core/built/admin/assets/disabled-sources-indicator-uIzbWL7K.js +1 -0
- package/core/built/admin/assets/{dist-pHiOxyxa.js → dist-ARN6XTcR.js} +1 -1
- package/core/built/admin/assets/dist-B2PvRwLn.js +1 -0
- package/core/built/admin/assets/{dist-DyViJwIy.js → dist-Bg8bSvLA.js} +3 -3
- package/core/built/admin/assets/dist-CsvXCJSe.js +23 -0
- package/core/built/admin/assets/dist-DlCyLrOC.js +1 -0
- 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-B5ogXUBa.js → domain-DzrOAPY8.js} +1 -1
- package/core/built/admin/assets/{dropzone-BnFG7dDP.js → dropzone-CzhP33wa.js} +1 -1
- package/core/built/admin/assets/{edit-profile-B07jCZy9.js → edit-profile-oufs4R4n.js} +1 -1
- package/core/built/admin/assets/editor-BBKrunMK.js +8 -0
- package/core/built/admin/assets/embed-signup-form-modal-Ch3s6lqA.js +9 -0
- package/core/built/admin/assets/{explore-Ca1y8HW_.js → explore-CA-R01Br.js} +1 -1
- package/core/built/admin/assets/{feed-x4iFMBP4.js → feed-ClC8_Gld.js} +1 -1
- package/core/built/admin/assets/{field-ChCAabZg.js → field-B3OPenbP.js} +1 -1
- package/core/built/admin/assets/{filter-sources-LdtH1e-T.js → filter-sources-CiPLnG9A.js} +1 -1
- package/core/built/admin/assets/filters-Bz0HsRie.js +1 -0
- package/core/built/admin/assets/{filters-CjVKRP8U.js → filters-yFvybRMe.js} +1 -1
- package/core/built/admin/assets/first-promoter-modal-BC0UpYCD.js +1 -0
- package/core/built/admin/assets/format-url-C-t8G6Qd.js +1 -0
- package/core/built/admin/assets/{ghost-7f01131115186d1af84ead76e79646dd.js → ghost-d683fbeb945b13155ea669caa5d8db41.js} +300 -298
- package/core/built/admin/assets/{gift-link-modal-Buqh2vXh.js → gift-link-modal-BKRuqo-k.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-RzW6Bnlf.js +1 -0
- package/core/built/admin/assets/grid-CUciBfjf.js +1 -0
- package/core/built/admin/assets/{growth-DajOBdN0.js → growth-ClNAldfL.js} +1 -1
- package/core/built/admin/assets/{growth-Dy2UOSay.js → growth-Dz8OSG06.js} +2 -2
- package/core/built/admin/assets/history-modal-DIVuQ5Ct.js +1 -0
- package/core/built/admin/assets/{hooks-CixO01lL.js → hooks-D6EE7BIt.js} +2 -2
- package/core/built/admin/assets/html-field-CVZ2sMxw.js +1 -0
- package/core/built/admin/assets/iframe-buffering-e8vH4-Qn.js +1 -0
- package/core/built/admin/assets/images-Ok8FFJ3A.js +1 -0
- package/core/built/admin/assets/{inbox-BUOLDoji.js → inbox-CMUVI6qb.js} +13 -13
- package/core/built/admin/assets/index-DUXM0NlS.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/{input-group-uSK3XT7b.js → input-group-2XU-iuh_.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-DrZ7xxrZ.js +1 -0
- package/core/built/admin/assets/isEmail-BOfC647y.js +1 -0
- package/core/built/admin/assets/koenig-editor-base-BPj4S760.js +1 -0
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +9254 -9251
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +124 -124
- package/core/built/admin/assets/{koenig-lexical-BFc4yFed.js → koenig-lexical-Cdjv-m3_.js} +98 -98
- package/core/built/admin/assets/{kpi-card-CCt-qeWD.js → kpi-card-J8Mj1sDX.js} +1 -1
- package/core/built/admin/assets/{kpi-card-BN-E6WYX.js → kpi-card-awos0BMH.js} +1 -1
- package/core/built/admin/assets/{kpi-helpers-DlxTbTs4.js → kpi-helpers-SEhvH-8E.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-BlcUttpn.js → kpi-tabs-R6BjsKhd.js} +1 -1
- package/core/built/admin/assets/{label-AVrxwmsz.js → label-BMC3dzLI.js} +1 -1
- package/core/built/admin/assets/label-picker-Bnlru5tO.js +1 -0
- package/core/built/admin/assets/{labels-DAIqKed7.js → labels-DAPjKPy2.js} +1 -1
- package/core/built/admin/assets/layout-yR9rlvEw.js +1 -0
- package/core/built/admin/assets/limit-modal-COmLevuY.js +1 -0
- package/core/built/admin/assets/{link-helpers-D9LsDOkG.js → link-helpers-Bzkk68GD.js} +1 -1
- package/core/built/admin/assets/member-custom-fields-BvFKEpxJ.js +1 -0
- package/core/built/admin/assets/member-detail-CWdPAe3p.js +1 -0
- package/core/built/admin/assets/members-Bnt0ih2R.js +11 -0
- package/core/built/admin/assets/modal-page-BMTzFHVO.js +1 -0
- package/core/built/admin/assets/{moderation-CQ3urTFE.js → moderation-mPjYHgZW.js} +1 -1
- package/core/built/admin/assets/multi-select-combobox-CsoyHAiX.js +1 -0
- package/core/built/admin/assets/navigation-modal-D_c0badj.js +1 -0
- package/core/built/admin/assets/newsletter-DR49NyGS.js +1 -0
- package/core/built/admin/assets/newsletter-detail-modal-B45pL1pb.js +1 -0
- package/core/built/admin/assets/newsletter-emails-B-OLfrvr.js +1 -0
- package/core/built/admin/assets/{newsletters-vt3Ni1Ov.js → newsletters-DGs5BaKH.js} +1 -1
- package/core/built/admin/assets/{newsletters-BXJbJgA-.js → newsletters-Du0lzskS.js} +1 -1
- package/core/built/admin/assets/{note-Dk877BTv.js → note-CSVsUKBm.js} +1 -1
- package/core/built/admin/assets/{notifications-CPlLjPxO.js → notifications-DV9FRtDw.js} +1 -1
- package/core/built/admin/assets/{offers-DEniVRD5.js → offers-Um6ab71O.js} +1 -1
- package/core/built/admin/assets/offers-container-modal-B0hFS8nk.js +1 -0
- package/core/built/admin/assets/{onboarding-route-DdStOtjy.js → onboarding-route--jL5KLPD.js} +1 -1
- package/core/built/admin/assets/overview-B-ZPNqMo.js +1 -0
- package/core/built/admin/assets/overview-D96iwoJi.js +1 -0
- package/core/built/admin/assets/{page-header-pp4dWmOS.js → page-header-DAvDYKO9.js} +1 -1
- package/core/built/admin/assets/{pages-B4goRPUn.js → pages-BVMnpI2O.js} +1 -1
- package/core/built/admin/assets/pintura-modal-jiUCxX0Q.js +1 -0
- package/core/built/admin/assets/portal-frame-D4D4nEO6.js +1 -0
- package/core/built/admin/assets/portal-modal-BLFdYeDH.js +1 -0
- package/core/built/admin/assets/{post-analytics-BMJrI81R.js → post-analytics-BhAtiFJp.js} +1 -1
- package/core/built/admin/assets/post-analytics-header-DK27VjJm.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-Btml5vJI.js → post-analytics-provider-DGg0FUqs.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-AcUZkbfL.js → post-share-modal-DtFAC132.js} +1 -1
- package/core/built/admin/assets/{posts-DhXE7b6r.js → posts-CxG7v0-M.js} +1 -1
- package/core/built/admin/assets/preferences-CuWLISHa.js +5 -0
- package/core/built/admin/assets/preview-chrome-BOVO6_PE.js +1 -0
- package/core/built/admin/assets/preview-modal-DctYx20m.js +1 -0
- package/core/built/admin/assets/profile-BsKYYtuL.js +1 -0
- package/core/built/admin/assets/radio-group-CI5_Kf_d.js +1 -0
- package/core/built/admin/assets/{referrers-pmJT4k3-.js → referrers-B9QDAfYm.js} +1 -1
- package/core/built/admin/assets/{router-provider-BmEZf6c6.js → router-provider-DSQfprxH.js} +1 -1
- package/core/built/admin/assets/select-CkXqNJDJ.js +1 -0
- package/core/built/admin/assets/settings-B4vM-rgV.js +4 -0
- package/core/built/admin/assets/{settings-BGgk7oCI.js → settings-ZCj-W56o.js} +1 -1
- package/core/built/admin/assets/settings-breadcrumbs-DIq31FwA.js +1 -0
- package/core/built/admin/assets/{share-modal-BeuydaBf.js → share-modal-BNj14g79.js} +1 -1
- package/core/built/admin/assets/slack-modal-C6SpMQ-q.js +1 -0
- package/core/built/admin/assets/social-urls-CHQ9cTAK.js +1 -0
- package/core/built/admin/assets/{sort-button-Df4t2AL8.js → sort-button-B-k54ow2.js} +1 -1
- package/core/built/admin/assets/{source-icon-ajoaKb41.js → source-icon-Cej3sZM6.js} +1 -1
- package/core/built/admin/assets/{stats-C_lfxZAw.js → stats-B2KW8E2y.js} +1 -1
- package/core/built/admin/assets/{stats-view-Cxot1iD8.js → stats-view-CVzU2-p5.js} +1 -1
- package/core/built/admin/assets/{step-1-ogAA4lDr.js → step-1-Dbn1iIMU.js} +1 -1
- package/core/built/admin/assets/{step-2-C5Xini-7.js → step-2-BahnSuN6.js} +1 -1
- package/core/built/admin/assets/{step-3-CZv1FV81.js → step-3-CW04e2ZJ.js} +2 -2
- package/core/built/admin/assets/stripe-button-BkBZqaGG.js +1 -0
- package/core/built/admin/assets/stripe-connect-modal-CYDDrSJm.js +1 -0
- package/core/built/admin/assets/switch-CE2NuMn-.js +1 -0
- package/core/built/admin/assets/{table-DFj1wiYv.js → table-BtLcEi1M.js} +1 -1
- package/core/built/admin/assets/tabs-BaWRfg8j.js +1 -0
- package/core/built/admin/assets/tag-detail-CmqiC32L.js +1 -0
- package/core/built/admin/assets/tags-BbygUvNB.js +1 -0
- package/core/built/admin/assets/tags-BwCgmr5v.js +1 -0
- package/core/built/admin/assets/testimonials-modal--bdzppyh.js +1 -0
- package/core/built/admin/assets/tier-detail-modal-BZmAsIy4.js +1 -0
- package/core/built/admin/assets/{tiers-DYsa4DCo.js → tiers-Da6kxHQP.js} +1 -1
- package/core/built/admin/assets/toggle-group-CVl-Xigr.js +1 -0
- package/core/built/admin/assets/{topic-filter-DGDxDalo.js → topic-filter-BoRHRbL1.js} +1 -1
- package/core/built/admin/assets/transistor-modal-CBPDqEGy.js +1 -0
- package/core/built/admin/assets/unsplash-modal-CixoRAO2.js +1 -0
- package/core/built/admin/assets/{use-analytics-data-C76v4LIp.js → use-analytics-data-BiWvcf1u.js} +1 -1
- package/core/built/admin/assets/use-check-theme-limit-error-B14lZukR.js +2 -0
- package/core/built/admin/assets/use-feature-flag-BmJoc5WN.js +1 -0
- package/core/built/admin/assets/use-filter-params-ChoV72Hw.js +1 -0
- 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-aNXJOmQr.js → use-growth-stats-Dom8xVzo.js} +1 -1
- package/core/built/admin/assets/use-hash-link-navigation-guard-B9j6HIrV.js +1 -0
- package/core/built/admin/assets/use-limiter-Dwg8ivgQ.js +2 -0
- package/core/built/admin/assets/use-pintura-config-Bj4QmiVk.js +1 -0
- package/core/built/admin/assets/use-pintura-editor-DvrslmPE.js +1 -0
- package/core/built/admin/assets/{use-post-referrers-CG0SZioj.js → use-post-referrers-pxIHTXFs.js} +1 -1
- package/core/built/admin/assets/use-query-params-DHhhu8wi.js +1 -0
- package/core/built/admin/assets/{use-reply-chain-data-RLdPKRmS.js → use-reply-chain-data-CmFe0N6F.js} +1 -1
- package/core/built/admin/assets/use-setting-group-Cmp_sy-d.js +1 -0
- package/core/built/admin/assets/use-settings-navigation-BmROPrls.js +1 -0
- package/core/built/admin/assets/{use-simple-pagination-DgcF69KI.js → use-simple-pagination-D5rL3YwO.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-query-C1R_Kbmy.js → use-tinybird-query-DP2dMaq7.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-C9fPjVM_.js → use-tinybird-token-DrK_skPQ.js} +1 -1
- package/core/built/admin/assets/use-url-input-ChrUZBFf.js +5 -0
- package/core/built/admin/assets/user-detail-modal-XODpOqNC.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-BmTUzeoF.js → virtual-list-Dnoafqbk.js} +1 -1
- package/core/built/admin/assets/web-D71mjB2r.js +1 -0
- package/core/built/admin/assets/web-z71lA1Y3.js +2 -0
- package/core/built/admin/assets/zapier-modal-Bp507Um7.js +1 -0
- package/core/built/admin/index.html +16 -14
- 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/proxy.js +1 -1
- package/core/frontend/services/routing/events.js +20 -6
- package/core/frontend/services/routing/permalink-adapter.js +0 -13
- package/core/frontend/services/routing/permalink-adapter.ts +0 -13
- package/core/frontend/services/routing/router-manager.js +15 -9
- 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/frontend/web/middleware/admin-toolbar.js +1 -1
- package/core/frontend/web/middleware/serve-indexnow-key.js +1 -7
- 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/index.js +2 -2
- 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/themes.js +6 -1
- 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.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/schema.js +24 -5
- package/core/server/lib/common/to-plain.ts +1 -1
- package/core/server/models/base/plugins/overrides.js +7 -5
- package/core/server/models/post.js +2 -2
- 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 +28 -0
- package/core/server/services/adapter-manager/adapter-paths.ts +26 -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 +11 -2
- package/core/server/services/audience-feedback/audience-feedback-service.js +2 -2
- package/core/server/services/audience-feedback/feedback-repository.js +4 -1
- package/core/server/services/auth/session/session-service.js +18 -3
- package/core/server/services/automations/database-automations-repository.js +5 -5
- package/core/server/services/automations/database-automations-repository.ts +12 -12
- package/core/server/services/automations/database-date.js +52 -3
- package/core/server/services/automations/database-date.ts +23 -2
- package/core/server/services/automations/service.js +2 -2
- package/core/server/services/automations/service.ts +2 -2
- 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 +12 -3
- package/core/server/services/email-analytics/email-analytics-service.js +4 -3
- package/core/server/services/email-analytics/index.js +2 -3
- package/core/server/services/email-analytics/index.ts +1 -2
- package/core/server/services/email-analytics/lib/queries.js +91 -124
- package/core/server/services/email-analytics/lib/queries.ts +341 -0
- 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/indexnow-ping/index.js +0 -2
- package/core/server/services/indexnow-ping/index.ts +0 -2
- package/core/server/services/indexnow-ping/indexnow-ping-service.js +6 -8
- package/core/server/services/indexnow-ping/indexnow-ping-service.ts +9 -17
- package/core/server/services/lib/mailgun-client.js +3 -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/service.js +72 -12
- package/core/server/services/members/api.js +1 -1
- 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 +26 -6
- package/core/server/services/members/import-export/import/row.ts +5 -1
- package/core/server/services/members/import-export/index.js +1 -5
- package/core/server/services/members/import-export/index.ts +2 -6
- package/core/server/services/members/members-api/repositories/member-repository.js +51 -0
- 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 +18 -27
- package/core/server/services/members-custom-fields/schema.ts +17 -27
- 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 -219
- package/core/server/services/members-custom-fields/values-service.ts +110 -238
- package/core/server/services/mentions/resource-service.js +1 -1
- package/core/server/services/mentions/service.js +2 -2
- package/core/server/services/route-settings/dynamic-routing-service.js +9 -59
- package/core/server/services/slack-notifications/slack-notifications.js +2 -2
- package/core/server/services/slack-ping/index.js +1 -1
- package/core/server/services/slack-ping/index.ts +1 -1
- package/core/server/services/slack-ping/slack-ping-service.js +3 -2
- package/core/server/services/slack-ping/slack-ping-service.ts +4 -5
- package/core/server/services/stats/content-stats-service.js +3 -3
- package/core/server/services/stats/members-stats-service.js +0 -7
- package/core/server/services/stats/posts-stats-service.js +2 -2
- package/core/server/services/themes/storage.js +19 -2
- 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 +12 -2
- package/core/server/web/api/endpoints/admin/routes.js +2 -2
- package/core/shared/config/env/config.testing-mysql.json +1 -2
- package/core/shared/config/env/config.testing.json +1 -2
- package/core/shared/custom-theme-settings-cache/custom-theme-settings-bread-service.js +4 -0
- package/core/shared/custom-theme-settings-cache/custom-theme-settings-service.js +39 -0
- package/core/shared/events/index.js +1 -2
- package/core/shared/labs.js +4 -4
- package/package.json +10 -11
- package/pnpm-lock.yaml +44 -47
- package/pnpm-workspace.yaml +19 -19
- package/tsconfig.json +1 -0
- package/components/tryghost-adapter-base-scheduling-0.2.0.tgz +0 -0
- package/components/tryghost-kg-converters-1.2.3.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/core/built/admin/assets/Combination-BCm9FL4o.js +0 -41
- package/core/built/admin/assets/activitypub/layout-CTgrezN7.js +0 -1
- package/core/built/admin/assets/automated-emails-6t2bIRWf.js +0 -7
- package/core/built/admin/assets/checkbox-BIl077a0.js +0 -1
- package/core/built/admin/assets/chunk.524.87b52f4fada529df2c4c.js +0 -25
- package/core/built/admin/assets/chunk.582.340bcca873e806cbd4bb.js +0 -27
- package/core/built/admin/assets/code-editor-view-C0aWNwdl.js +0 -1
- package/core/built/admin/assets/comments-qwhIjH3c.js +0 -1
- package/core/built/admin/assets/disabled-sources-indicator-DYAXW-6V.js +0 -1
- package/core/built/admin/assets/dist-BUKmD51u.js +0 -1
- package/core/built/admin/assets/dist-CNlMj7ML.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/editor-DDYSOXGN.js +0 -8
- package/core/built/admin/assets/esm-CFX93H5M.js +0 -2
- package/core/built/admin/assets/filters-CLehPo_D.js +0 -1
- package/core/built/admin/assets/index-08n05-rc.js +0 -67
- package/core/built/admin/assets/index-DgUzgFvS.css +0 -1
- package/core/built/admin/assets/label-picker-DVnpu_IF.js +0 -1
- package/core/built/admin/assets/layout-B0YLh7R9.js +0 -1
- package/core/built/admin/assets/member-custom-fields-C8NpZYza.js +0 -1
- package/core/built/admin/assets/member-detail-CIVAdKXf.js +0 -1
- package/core/built/admin/assets/members-oLPiSkXD.js +0 -10
- package/core/built/admin/assets/modals-CB7v-k3T.js +0 -31
- package/core/built/admin/assets/newsletter-rbeDX9Wk.js +0 -1
- package/core/built/admin/assets/overview--4V-kx9c.js +0 -1
- package/core/built/admin/assets/overview-DSLv30oz.js +0 -1
- package/core/built/admin/assets/post-analytics-header-pU2m9ZuV.js +0 -1
- package/core/built/admin/assets/preferences-Ru4CRPHC.js +0 -5
- package/core/built/admin/assets/profile-BNb1oj7D.js +0 -1
- package/core/built/admin/assets/select-B0HteHrp.js +0 -1
- package/core/built/admin/assets/settings-B1C9Q3wo.js +0 -4
- package/core/built/admin/assets/social-urls-DA2lXrhW.js +0 -8
- package/core/built/admin/assets/switch-CyQcwMAv.js +0 -1
- package/core/built/admin/assets/tabs-D4y1gqUd.js +0 -1
- package/core/built/admin/assets/tags-Dmft-Y4O.js +0 -1
- package/core/built/admin/assets/toggle-group-R2QMFvXW.js +0 -1
- package/core/built/admin/assets/use-filter-params-WrdhAISv.js +0 -1
- package/core/built/admin/assets/web-BqW0b0_C.js +0 -1
- package/core/built/admin/assets/web-Cy5GKMWM.js +0 -2
- package/core/server/services/email-analytics/jobs/update-member-email-analytics/index.js +0 -14
- 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
- /package/core/built/admin/assets/{chunk.211.6f7328d63c41536451b3.js.LICENSE.txt → chunk.571.40f093ed6d883993714e.js.LICENSE.txt} +0 -0
- /package/core/built/admin/assets/{dist-CqO2lWS0.js → dist-CqO2lWS02.js} +0 -0
- /package/core/server/api/endpoints/{gift-reminders.js → gifts.js} +0 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
import _ from 'lodash';
|
|
2
|
+
import debugFactory from '@tryghost/debug';
|
|
3
|
+
// @ts-expect-error This module lacks type definitions.
|
|
4
|
+
import db from '../../../data/db';
|
|
5
|
+
import logging from '@tryghost/logging';
|
|
6
|
+
import ObjectID from 'bson-objectid';
|
|
7
|
+
|
|
8
|
+
const debug = debugFactory('services:email-analytics');
|
|
9
|
+
|
|
10
|
+
const MIN_EMAIL_COUNT_FOR_OPEN_RATE = 5;
|
|
11
|
+
|
|
12
|
+
type EmailAnalyticsJobName = string;
|
|
13
|
+
type EmailAnalyticsEvent = 'delivered' | 'opened' | 'failed';
|
|
14
|
+
type CursorSeed = {
|
|
15
|
+
tableName: string;
|
|
16
|
+
eventColumns: Partial<Record<EmailAnalyticsEvent, string>>;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Creates a job in the jobs table if it does not already exist.
|
|
21
|
+
* @param jobName - The name of the job to create.
|
|
22
|
+
*/
|
|
23
|
+
async function createJobIfNotExists(jobName: EmailAnalyticsJobName): Promise<void> {
|
|
24
|
+
await db.knex('jobs').insert({
|
|
25
|
+
id: new ObjectID().toHexString(),
|
|
26
|
+
name: jobName,
|
|
27
|
+
started_at: new Date(),
|
|
28
|
+
created_at: new Date(),
|
|
29
|
+
status: 'started'
|
|
30
|
+
}).onConflict('name').ignore();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export const queries = {
|
|
34
|
+
/**
|
|
35
|
+
* Retrieves the timestamp of the last seen event for the specified email analytics events.
|
|
36
|
+
* @param jobName - The name of the job to update.
|
|
37
|
+
* @param events - The email analytics events to consider.
|
|
38
|
+
* @param cursorSeed - Recipient table and timestamp columns to read the initial cursor from. Used when the job has no stored timestamp yet.
|
|
39
|
+
* @returns The timestamp of the last seen event, or null if no events are found.
|
|
40
|
+
*/
|
|
41
|
+
async getLastEventTimestamp(
|
|
42
|
+
jobName: EmailAnalyticsJobName,
|
|
43
|
+
events: EmailAnalyticsEvent[],
|
|
44
|
+
cursorSeed: CursorSeed
|
|
45
|
+
): Promise<Date | null> {
|
|
46
|
+
const startDate = new Date();
|
|
47
|
+
|
|
48
|
+
let timestamps: (Date | string | null)[] = [];
|
|
49
|
+
const lastJobRunTimestamp = await this.getLastJobRunTimestamp(jobName);
|
|
50
|
+
|
|
51
|
+
if (lastJobRunTimestamp) {
|
|
52
|
+
debug(`Using job data for ${jobName}`);
|
|
53
|
+
timestamps = [lastJobRunTimestamp];
|
|
54
|
+
} else {
|
|
55
|
+
debug(`Job data not found for ${jobName}, using ${cursorSeed.tableName} data`);
|
|
56
|
+
logging.info(`Job data not found for ${jobName}, using ${cursorSeed.tableName} data`);
|
|
57
|
+
|
|
58
|
+
for (const event of events) {
|
|
59
|
+
const columnName = cursorSeed.eventColumns[event];
|
|
60
|
+
if (!columnName) {
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
const row = await db.knex(cursorSeed.tableName)
|
|
64
|
+
.select(db.knex.raw('MAX(??) as maxTimestamp', [columnName]))
|
|
65
|
+
.first();
|
|
66
|
+
timestamps.push(row.maxTimestamp);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
await createJobIfNotExists(jobName);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Convert string dates to Date objects for SQLite compatibility
|
|
73
|
+
const normalizedTimestamps = timestamps.map(date => {
|
|
74
|
+
if (!date) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
return date instanceof Date ? date : new Date(date);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
const lastSeenEventTimestamp = _.max(normalizedTimestamps) ?? null;
|
|
81
|
+
debug(`getLastEventTimestamp: finished in ${Date.now() - startDate.getTime()}ms`);
|
|
82
|
+
|
|
83
|
+
return lastSeenEventTimestamp;
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Retrieves the job data for the specified job name.
|
|
88
|
+
* @param jobName - The name of the job to retrieve data for.
|
|
89
|
+
* @returns The job data, or null if no job data is found.
|
|
90
|
+
*/
|
|
91
|
+
async getJobData(jobName: EmailAnalyticsJobName) {
|
|
92
|
+
return await db.knex('jobs').select('finished_at', 'started_at', 'metadata').where('name', jobName).first();
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Retrieves the timestamp of the last job run for the specified job name.
|
|
97
|
+
* @param jobName - The name of the job to retrieve the last run timestamp for.
|
|
98
|
+
* @returns The timestamp of the last job run, or null if no job data is found.
|
|
99
|
+
*/
|
|
100
|
+
async getLastJobRunTimestamp(jobName: EmailAnalyticsJobName): Promise<Date | null> {
|
|
101
|
+
const jobData = await this.getJobData(jobName);
|
|
102
|
+
return jobData ? jobData.finished_at || jobData.started_at : null;
|
|
103
|
+
},
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Sets the timestamp of the last seen event for the specified email analytics events.
|
|
107
|
+
* @param jobName - The name of the job to update.
|
|
108
|
+
* @param field - The field to update.
|
|
109
|
+
* @param date - The timestamp of the last seen event.
|
|
110
|
+
* @description
|
|
111
|
+
* Updates the `finished_at` or `started_at` column of the specified job in the `jobs` table with the provided timestamp.
|
|
112
|
+
* This is used to keep track of the last time the job was run to avoid expensive queries following reboot.
|
|
113
|
+
*/
|
|
114
|
+
async setJobTimestamp(jobName: EmailAnalyticsJobName, field: 'finished' | 'started', date: Date): Promise<void> {
|
|
115
|
+
// Convert string dates to Date objects for SQLite compatibility
|
|
116
|
+
try {
|
|
117
|
+
debug(`Setting ${field} timestamp for job ${jobName} to ${date}`);
|
|
118
|
+
const updateField = field === 'finished' ? 'finished_at' : 'started_at';
|
|
119
|
+
const status = field === 'finished' ? 'finished' : 'started';
|
|
120
|
+
const result = await db.knex('jobs').update({[updateField]: date, updated_at: new Date(), status: status}).where('name', jobName);
|
|
121
|
+
if (result === 0) {
|
|
122
|
+
await db.knex('jobs').insert({
|
|
123
|
+
id: new ObjectID().toHexString(),
|
|
124
|
+
name: jobName,
|
|
125
|
+
[updateField]: date.toISOString(), // force to iso string for sqlite
|
|
126
|
+
updated_at: date.toISOString(), // force to iso string for sqlite
|
|
127
|
+
status: status
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
} catch (err: unknown) {
|
|
131
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
132
|
+
debug(`Error setting ${field} timestamp for job ${jobName}: ${message}`);
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Retrieves and parses the metadata JSON for the specified job.
|
|
138
|
+
* @param jobName - The name of the job.
|
|
139
|
+
* @returns The parsed metadata object, or null.
|
|
140
|
+
*/
|
|
141
|
+
async getJobMetadata(jobName: EmailAnalyticsJobName): Promise<object | null> {
|
|
142
|
+
try {
|
|
143
|
+
const row = await db.knex('jobs').select('metadata').where('name', jobName).first();
|
|
144
|
+
if (row && row.metadata) {
|
|
145
|
+
return JSON.parse(row.metadata);
|
|
146
|
+
}
|
|
147
|
+
} catch (err: unknown) {
|
|
148
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
149
|
+
logging.error(`Error reading metadata for job ${jobName}: ${message}`);
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
},
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Writes metadata JSON for the specified job.
|
|
156
|
+
* @param jobName - The name of the job.
|
|
157
|
+
* @param metadata - The metadata to store, or null to clear.
|
|
158
|
+
*/
|
|
159
|
+
async setJobMetadata(jobName: EmailAnalyticsJobName, metadata: object | null): Promise<void> {
|
|
160
|
+
try {
|
|
161
|
+
const value = metadata ? JSON.stringify(metadata) : null;
|
|
162
|
+
await db.knex.transaction(async (trx: typeof db.knex) => {
|
|
163
|
+
const result = await trx('jobs').update({metadata: value, updated_at: new Date()}).where('name', jobName);
|
|
164
|
+
if (result === 0 && metadata) {
|
|
165
|
+
await trx('jobs').insert({
|
|
166
|
+
id: new ObjectID().toHexString(),
|
|
167
|
+
name: jobName,
|
|
168
|
+
metadata: value,
|
|
169
|
+
created_at: new Date(),
|
|
170
|
+
status: 'queued'
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
} catch (err: unknown) {
|
|
175
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
176
|
+
logging.error(`Error setting metadata for job ${jobName}: ${message}`);
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Sets the status of the specified email analytics job.
|
|
182
|
+
* @param jobName - The name of the job to update.
|
|
183
|
+
* @param status - The new status of the job.
|
|
184
|
+
* @description
|
|
185
|
+
* Updates the `status` column of the specified job in the `jobs` table with the provided status.
|
|
186
|
+
* This is used to keep track of the current state of the job.
|
|
187
|
+
*/
|
|
188
|
+
async setJobStatus(jobName: EmailAnalyticsJobName, status: 'started' | 'finished' | 'failed'): Promise<void> {
|
|
189
|
+
debug(`Setting status for job ${jobName} to ${status}`);
|
|
190
|
+
try {
|
|
191
|
+
const result = await db.knex('jobs')
|
|
192
|
+
.update({
|
|
193
|
+
status: status,
|
|
194
|
+
updated_at: new Date()
|
|
195
|
+
})
|
|
196
|
+
.where('name', jobName);
|
|
197
|
+
|
|
198
|
+
if (result === 0) {
|
|
199
|
+
await db.knex('jobs').insert({
|
|
200
|
+
id: new ObjectID().toHexString(),
|
|
201
|
+
name: jobName,
|
|
202
|
+
status: status,
|
|
203
|
+
created_at: new Date(),
|
|
204
|
+
updated_at: new Date()
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
} catch (err: unknown) {
|
|
208
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
209
|
+
debug(`Error setting status for job ${jobName}: ${message}`);
|
|
210
|
+
throw err;
|
|
211
|
+
}
|
|
212
|
+
},
|
|
213
|
+
|
|
214
|
+
async aggregateEmailStats(emailId: string, updateOpenedCount: boolean): Promise<void> {
|
|
215
|
+
const [deliveredCount] = await db.knex('email_recipients').count('id as count').whereRaw('email_id = ? AND delivered_at IS NOT NULL', [emailId]);
|
|
216
|
+
const [failedCount] = await db.knex('email_recipients').count('id as count').whereRaw('email_id = ? AND failed_at IS NOT NULL', [emailId]);
|
|
217
|
+
|
|
218
|
+
const updateData: Record<string, string | number> = {
|
|
219
|
+
delivered_count: deliveredCount.count,
|
|
220
|
+
failed_count: failedCount.count
|
|
221
|
+
};
|
|
222
|
+
|
|
223
|
+
if (updateOpenedCount) {
|
|
224
|
+
const [openedCount] = await db.knex('email_recipients').count('id as count').whereRaw('email_id = ? AND opened_at IS NOT NULL', [emailId]);
|
|
225
|
+
updateData.opened_count = openedCount.count;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
await db.knex('emails').update(updateData).where('id', emailId);
|
|
229
|
+
},
|
|
230
|
+
|
|
231
|
+
async aggregateMemberStats(memberId: string): Promise<void> {
|
|
232
|
+
const {trackedEmailCount} = await db.knex('email_recipients')
|
|
233
|
+
.select(db.knex.raw('COUNT(email_recipients.id) as trackedEmailCount'))
|
|
234
|
+
.leftJoin('emails', 'email_recipients.email_id', 'emails.id')
|
|
235
|
+
.where('email_recipients.member_id', memberId)
|
|
236
|
+
.where('emails.track_opens', true)
|
|
237
|
+
.first() || {};
|
|
238
|
+
|
|
239
|
+
const [emailCount] = await db.knex('email_recipients').count('id as count').whereRaw('member_id = ?', [memberId]);
|
|
240
|
+
const [emailOpenedCount] = await db.knex('email_recipients').count('id as count').whereRaw('member_id = ? AND opened_at IS NOT NULL', [memberId]);
|
|
241
|
+
|
|
242
|
+
const updateQuery: Record<string, string | number> = {
|
|
243
|
+
email_count: emailCount.count,
|
|
244
|
+
email_opened_count: emailOpenedCount.count
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
if (trackedEmailCount >= MIN_EMAIL_COUNT_FOR_OPEN_RATE) {
|
|
248
|
+
updateQuery.email_open_rate = Math.round(emailOpenedCount.count / trackedEmailCount * 100);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
await db.knex('members')
|
|
252
|
+
.update(updateQuery)
|
|
253
|
+
.where('id', memberId);
|
|
254
|
+
},
|
|
255
|
+
|
|
256
|
+
async aggregateMemberStatsBatch(memberIds: string[]): Promise<void> {
|
|
257
|
+
if (!memberIds || memberIds.length === 0) {
|
|
258
|
+
return;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Batch query to get stats for all members at once
|
|
262
|
+
const stats = await db.knex('email_recipients')
|
|
263
|
+
.leftJoin('emails', 'emails.id', 'email_recipients.email_id')
|
|
264
|
+
.select(
|
|
265
|
+
'email_recipients.member_id',
|
|
266
|
+
db.knex.raw('COUNT(email_recipients.id) as email_count'),
|
|
267
|
+
db.knex.raw('SUM(CASE WHEN email_recipients.opened_at IS NOT NULL THEN 1 ELSE 0 END) as email_opened_count'),
|
|
268
|
+
db.knex.raw('SUM(CASE WHEN emails.track_opens = 1 THEN 1 ELSE 0 END) as tracked_count')
|
|
269
|
+
)
|
|
270
|
+
.whereIn('email_recipients.member_id', memberIds)
|
|
271
|
+
.groupBy('email_recipients.member_id');
|
|
272
|
+
|
|
273
|
+
// Build update data for each member
|
|
274
|
+
const memberStatsMap = new Map<string, {email_count: number; email_opened_count: number; email_open_rate: number | null}>();
|
|
275
|
+
for (const stat of stats) {
|
|
276
|
+
const emailOpenRate = stat.tracked_count >= MIN_EMAIL_COUNT_FOR_OPEN_RATE
|
|
277
|
+
? Math.round((stat.email_opened_count / stat.tracked_count) * 100)
|
|
278
|
+
: null;
|
|
279
|
+
|
|
280
|
+
memberStatsMap.set(stat.member_id, {
|
|
281
|
+
email_count: stat.email_count,
|
|
282
|
+
email_opened_count: stat.email_opened_count,
|
|
283
|
+
email_open_rate: emailOpenRate
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// Build CASE statements for batch update
|
|
288
|
+
const emailCountCases: string[] = [];
|
|
289
|
+
const emailOpenedCountCases: string[] = [];
|
|
290
|
+
const emailOpenRateCases: string[] = [];
|
|
291
|
+
const emailCountBindings: (string | number)[] = [];
|
|
292
|
+
const emailOpenedCountBindings: (string | number)[] = [];
|
|
293
|
+
const emailOpenRateBindings: (string | number)[] = [];
|
|
294
|
+
|
|
295
|
+
for (const memberId of memberIds) {
|
|
296
|
+
const memberStats = memberStatsMap.get(memberId) || {
|
|
297
|
+
email_count: 0,
|
|
298
|
+
email_opened_count: 0,
|
|
299
|
+
email_open_rate: null
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
emailCountCases.push(`WHEN ? THEN ?`);
|
|
303
|
+
emailCountBindings.push(memberId, memberStats.email_count);
|
|
304
|
+
|
|
305
|
+
emailOpenedCountCases.push(`WHEN ? THEN ?`);
|
|
306
|
+
emailOpenedCountBindings.push(memberId, memberStats.email_opened_count);
|
|
307
|
+
|
|
308
|
+
if (memberStats.email_open_rate !== null) {
|
|
309
|
+
emailOpenRateCases.push(`WHEN ? THEN ?`);
|
|
310
|
+
emailOpenRateBindings.push(memberId, memberStats.email_open_rate);
|
|
311
|
+
} else {
|
|
312
|
+
emailOpenRateCases.push(`WHEN ? THEN NULL`);
|
|
313
|
+
emailOpenRateBindings.push(memberId);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// Combine bindings in the order they appear in the SQL statement:
|
|
318
|
+
// 1. All bindings for email_count CASE statement
|
|
319
|
+
// 2. All bindings for email_opened_count CASE statement
|
|
320
|
+
// 3. All bindings for email_open_rate CASE statement
|
|
321
|
+
// 4. Member IDs for the WHERE IN clause
|
|
322
|
+
const bindings = [
|
|
323
|
+
...emailCountBindings,
|
|
324
|
+
...emailOpenedCountBindings,
|
|
325
|
+
...emailOpenRateBindings,
|
|
326
|
+
...memberIds
|
|
327
|
+
];
|
|
328
|
+
|
|
329
|
+
// Execute batched update with CASE statements
|
|
330
|
+
await db.knex.raw(`
|
|
331
|
+
UPDATE members
|
|
332
|
+
SET
|
|
333
|
+
email_count = CASE id ${emailCountCases.join(' ')} END,
|
|
334
|
+
email_opened_count = CASE id ${emailOpenedCountCases.join(' ')} END,
|
|
335
|
+
email_open_rate = CASE id ${emailOpenRateCases.join(' ')} END
|
|
336
|
+
WHERE id IN (${memberIds.map(() => '?').join(',')})
|
|
337
|
+
`, bindings);
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
export type Queries = typeof queries;
|
|
@@ -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
|
}
|
|
@@ -11,7 +11,6 @@ class IndexNowPingServiceWrapper {
|
|
|
11
11
|
// Wire up all the dependencies
|
|
12
12
|
const settingsCache = require('../../../shared/settings-cache');
|
|
13
13
|
const config = require('../../../shared/config');
|
|
14
|
-
const labs = require('../../../shared/labs');
|
|
15
14
|
const urlService = require('../url');
|
|
16
15
|
const urlUtils = require('../../../shared/url-utils').default;
|
|
17
16
|
const request = require('@tryghost/request');
|
|
@@ -20,7 +19,6 @@ class IndexNowPingServiceWrapper {
|
|
|
20
19
|
this.service = new indexnow_ping_service_1.IndexNowPingService({
|
|
21
20
|
settingsCache,
|
|
22
21
|
config,
|
|
23
|
-
labs,
|
|
24
22
|
urlService,
|
|
25
23
|
urlUtils,
|
|
26
24
|
request,
|
|
@@ -12,7 +12,6 @@ class IndexNowPingServiceWrapper {
|
|
|
12
12
|
// Wire up all the dependencies
|
|
13
13
|
const settingsCache = require('../../../shared/settings-cache');
|
|
14
14
|
const config = require('../../../shared/config');
|
|
15
|
-
const labs = require('../../../shared/labs');
|
|
16
15
|
const urlService = require('../url');
|
|
17
16
|
const urlUtils = require('../../../shared/url-utils').default;
|
|
18
17
|
const request = require('@tryghost/request');
|
|
@@ -22,7 +21,6 @@ class IndexNowPingServiceWrapper {
|
|
|
22
21
|
this.service = new IndexNowPingService({
|
|
23
22
|
settingsCache,
|
|
24
23
|
config,
|
|
25
|
-
labs,
|
|
26
24
|
urlService,
|
|
27
25
|
urlUtils,
|
|
28
26
|
request,
|
|
@@ -31,17 +31,15 @@ const constants_1 = require("./constants");
|
|
|
31
31
|
class IndexNowPingService {
|
|
32
32
|
settingsCache;
|
|
33
33
|
config;
|
|
34
|
-
labs;
|
|
35
34
|
urlService;
|
|
36
35
|
urlUtils;
|
|
37
36
|
request;
|
|
38
37
|
logging;
|
|
39
38
|
events;
|
|
40
39
|
listener;
|
|
41
|
-
constructor({ settingsCache, config,
|
|
40
|
+
constructor({ settingsCache, config, urlService, urlUtils, request, logging, events }) {
|
|
42
41
|
this.settingsCache = settingsCache;
|
|
43
42
|
this.config = config;
|
|
44
|
-
this.labs = labs;
|
|
45
43
|
this.urlService = urlService;
|
|
46
44
|
this.urlUtils = urlUtils;
|
|
47
45
|
this.request = request;
|
|
@@ -68,6 +66,10 @@ class IndexNowPingService {
|
|
|
68
66
|
* Ping IndexNow with a URL.
|
|
69
67
|
*/
|
|
70
68
|
async ping(post) {
|
|
69
|
+
// Skip if in development
|
|
70
|
+
if (this.config.get('env') === 'development') {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
71
73
|
// Skip pages - only ping for posts
|
|
72
74
|
if (post.type === 'page') {
|
|
73
75
|
return;
|
|
@@ -80,10 +82,6 @@ class IndexNowPingService {
|
|
|
80
82
|
if (this.config.isPrivacyDisabled('useIndexNow')) {
|
|
81
83
|
return;
|
|
82
84
|
}
|
|
83
|
-
// Skip if IndexNow is not enabled in labs
|
|
84
|
-
if (!this.labs.isSet('indexnow')) {
|
|
85
|
-
return;
|
|
86
|
-
}
|
|
87
85
|
// Don't ping for the default posts
|
|
88
86
|
if (post.slug && constants_1.defaultPostSlugs.indexOf(post.slug) > -1) {
|
|
89
87
|
return;
|
|
@@ -94,7 +92,7 @@ class IndexNowPingService {
|
|
|
94
92
|
// publishing.
|
|
95
93
|
let url = null;
|
|
96
94
|
try {
|
|
97
|
-
url = this.urlService.
|
|
95
|
+
url = this.urlService.getUrlForResource({ ...post, type: 'posts' }, { absolute: true });
|
|
98
96
|
if (!url || url.endsWith('/404/')) {
|
|
99
97
|
this.logging.warn({
|
|
100
98
|
event: { name: 'indexnow.unresolved_url' },
|
|
@@ -36,17 +36,12 @@ type SettingsCache = {
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
type Config = {
|
|
39
|
+
get(key: string): unknown;
|
|
39
40
|
isPrivacyDisabled(key: string): boolean;
|
|
40
41
|
};
|
|
41
42
|
|
|
42
|
-
type Labs = {
|
|
43
|
-
isSet(flag: string): boolean;
|
|
44
|
-
};
|
|
45
|
-
|
|
46
43
|
type UrlService = {
|
|
47
|
-
|
|
48
|
-
getUrlForResource(resource: Record<string, unknown>, options: {absolute: boolean}): string | null;
|
|
49
|
-
};
|
|
44
|
+
getUrlForResource(resource: Record<string, unknown>, options: {absolute: boolean}): string | null;
|
|
50
45
|
};
|
|
51
46
|
|
|
52
47
|
type UrlUtils = {
|
|
@@ -86,7 +81,6 @@ type Post = {
|
|
|
86
81
|
export type IndexNowPingServiceDeps = {
|
|
87
82
|
settingsCache: SettingsCache;
|
|
88
83
|
config: Config;
|
|
89
|
-
labs: Labs;
|
|
90
84
|
urlService: UrlService;
|
|
91
85
|
urlUtils: UrlUtils;
|
|
92
86
|
request: RequestFn;
|
|
@@ -97,7 +91,6 @@ export type IndexNowPingServiceDeps = {
|
|
|
97
91
|
export class IndexNowPingService {
|
|
98
92
|
settingsCache: SettingsCache;
|
|
99
93
|
config: Config;
|
|
100
|
-
labs: Labs;
|
|
101
94
|
urlService: UrlService;
|
|
102
95
|
urlUtils: UrlUtils;
|
|
103
96
|
request: RequestFn;
|
|
@@ -105,10 +98,9 @@ export class IndexNowPingService {
|
|
|
105
98
|
events: ModelEvents;
|
|
106
99
|
listener: ModelEventListener;
|
|
107
100
|
|
|
108
|
-
constructor({settingsCache, config,
|
|
101
|
+
constructor({settingsCache, config, urlService, urlUtils, request, logging, events}: IndexNowPingServiceDeps) {
|
|
109
102
|
this.settingsCache = settingsCache;
|
|
110
103
|
this.config = config;
|
|
111
|
-
this.labs = labs;
|
|
112
104
|
this.urlService = urlService;
|
|
113
105
|
this.urlUtils = urlUtils;
|
|
114
106
|
this.request = request;
|
|
@@ -139,6 +131,11 @@ export class IndexNowPingService {
|
|
|
139
131
|
* Ping IndexNow with a URL.
|
|
140
132
|
*/
|
|
141
133
|
async ping(post: Post): Promise<void> {
|
|
134
|
+
// Skip if in development
|
|
135
|
+
if (this.config.get('env') === 'development') {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
142
139
|
// Skip pages - only ping for posts
|
|
143
140
|
if (post.type === 'page') {
|
|
144
141
|
return;
|
|
@@ -154,11 +151,6 @@ export class IndexNowPingService {
|
|
|
154
151
|
return;
|
|
155
152
|
}
|
|
156
153
|
|
|
157
|
-
// Skip if IndexNow is not enabled in labs
|
|
158
|
-
if (!this.labs.isSet('indexnow')) {
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
154
|
// Don't ping for the default posts
|
|
163
155
|
if (post.slug && defaultPostSlugs.indexOf(post.slug) > -1) {
|
|
164
156
|
return;
|
|
@@ -170,7 +162,7 @@ export class IndexNowPingService {
|
|
|
170
162
|
// publishing.
|
|
171
163
|
let url: string | null = null;
|
|
172
164
|
try {
|
|
173
|
-
url = this.urlService.
|
|
165
|
+
url = this.urlService.getUrlForResource({...post, type: 'posts'}, {absolute: true});
|
|
174
166
|
|
|
175
167
|
if (!url || url.endsWith('/404/')) {
|
|
176
168
|
this.logging.warn({
|
|
@@ -85,9 +85,9 @@ module.exports = class MailgunClient {
|
|
|
85
85
|
messageData['v:email-id'] = message.id;
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
const tags = ['
|
|
89
|
-
if (
|
|
90
|
-
tags.push(
|
|
88
|
+
const tags = ['ghost-email'];
|
|
89
|
+
if (Array.isArray(message.tags)) {
|
|
90
|
+
tags.push(...message.tags);
|
|
91
91
|
}
|
|
92
92
|
messageData['o:tag'] = tags;
|
|
93
93
|
|
|
@@ -5,7 +5,6 @@ const errors = require('@tryghost/errors');
|
|
|
5
5
|
const logging = require('@tryghost/logging');
|
|
6
6
|
const string = require('@tryghost/string');
|
|
7
7
|
const path = require('path');
|
|
8
|
-
const convert = require('heic-convert');
|
|
9
8
|
|
|
10
9
|
let fileTypeFromBuffer;
|
|
11
10
|
|
|
@@ -102,20 +101,25 @@ class ExternalMediaInliner {
|
|
|
102
101
|
}
|
|
103
102
|
|
|
104
103
|
// If the file is heic or heif, attempt to convert it to jpeg
|
|
105
|
-
|
|
106
|
-
|
|
104
|
+
if (extension === 'heic' || extension === 'heif') {
|
|
105
|
+
// Lazy: pulls in libheif-js, a WASM codec costing ~50ms to load at boot.
|
|
106
|
+
// Deliberately outside the try — a missing codec is not a conversion error,
|
|
107
|
+
// and swallowing it would store an unconvertible .heic in its place.
|
|
108
|
+
const convert = require('heic-convert');
|
|
109
|
+
|
|
110
|
+
try {
|
|
107
111
|
body = await convert({
|
|
108
112
|
buffer: body,
|
|
109
113
|
format: 'JPEG'
|
|
110
114
|
});
|
|
111
115
|
|
|
112
116
|
extension = 'jpg';
|
|
117
|
+
} catch (error) {
|
|
118
|
+
logging.error(`Error converting file to JPEG: ${requestURL}`);
|
|
119
|
+
logging.error(new errors.DataImportError({
|
|
120
|
+
err: error
|
|
121
|
+
}));
|
|
113
122
|
}
|
|
114
|
-
} catch (error) {
|
|
115
|
-
logging.error(`Error converting file to JPEG: ${requestURL}`);
|
|
116
|
-
logging.error(new errors.DataImportError({
|
|
117
|
-
err: error
|
|
118
|
-
}));
|
|
119
123
|
}
|
|
120
124
|
|
|
121
125
|
const removeExtRegExp = new RegExp(`.${extension}`, '');
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @typedef {Object} UrlService
|
|
3
|
-
* @prop {
|
|
4
|
-
*
|
|
5
|
-
* resolveUrl: (path: string) => Promise<Object | null>
|
|
6
|
-
* }} facade
|
|
3
|
+
* @prop {(resource: Object, options: Object) => string} getUrlForResource
|
|
4
|
+
* @prop {(path: string) => Promise<Object | null>} resolveUrl
|
|
7
5
|
*/
|
|
8
6
|
|
|
9
7
|
const {toPlain} = require('../../lib/common/to-plain');
|
|
@@ -106,7 +104,7 @@ class UrlTranslator {
|
|
|
106
104
|
// surface as an error.
|
|
107
105
|
let resource;
|
|
108
106
|
try {
|
|
109
|
-
resource = await this.urlService.
|
|
107
|
+
resource = await this.urlService.resolveUrl(path);
|
|
110
108
|
} catch (err) {
|
|
111
109
|
return;
|
|
112
110
|
}
|
|
@@ -155,10 +153,10 @@ class UrlTranslator {
|
|
|
155
153
|
}
|
|
156
154
|
// Lazy URL service evaluates permalink templates against resource fields
|
|
157
155
|
// (slug, published_at, primary_tag, ...). Caller already loaded the model,
|
|
158
|
-
// so spread its plain data so those fields reach the
|
|
156
|
+
// so spread its plain data so those fields reach the URL service.
|
|
159
157
|
const data = toPlain(model);
|
|
160
158
|
const resource = {...data, id, type: TYPE_TO_RESOURCE[type]};
|
|
161
|
-
return this.urlService.
|
|
159
|
+
return this.urlService.getUrlForResource(resource, {absolute});
|
|
162
160
|
}
|
|
163
161
|
|
|
164
162
|
async getResourceById(id, type) {
|