ghost 6.63.0 → 6.65.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/create-migration.js +15 -0
- package/bin/lib/duplicate-deps.js +204 -0
- package/bin/validate-adapters.js +50 -6
- package/components/tryghost-adapter-base-scheduling-0.2.4.tgz +0 -0
- package/components/tryghost-adapter-base-sso-0.1.3.tgz +0 -0
- package/components/tryghost-admin-api-schema-0.0.0.tgz +0 -0
- package/components/tryghost-api-framework-0.0.0.tgz +0 -0
- package/components/tryghost-i18n-0.0.0.tgz +0 -0
- package/components/{tryghost-kg-default-cards-10.3.5.tgz → tryghost-kg-default-cards-10.3.6.tgz} +0 -0
- package/components/tryghost-kg-default-nodes-2.2.1.tgz +0 -0
- package/components/tryghost-kg-html-to-lexical-1.4.0.tgz +0 -0
- package/components/{tryghost-kg-lexical-html-renderer-1.5.0.tgz → tryghost-kg-lexical-html-renderer-1.5.1.tgz} +0 -0
- package/components/tryghost-limit-service-0.0.0.tgz +0 -0
- package/components/tryghost-metafield-types-0.0.0.tgz +0 -0
- package/content/themes/casper/assets/built/casper.js +1 -1
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/css/screen.css +15 -2
- package/content/themes/casper/assets/js/init.js +37 -9
- package/content/themes/casper/package.json +8 -8
- package/content/themes/source/assets/built/screen.css +1 -1
- package/content/themes/source/assets/built/source.js +1 -1
- package/content/themes/source/assets/css/screen.css +15 -2
- package/content/themes/source/assets/js/main.js +24 -4
- package/content/themes/source/package.json +8 -8
- package/core/boot.js +27 -15
- package/core/bridge.js +1 -1
- package/core/built/admin/assets/Combination-DQ6f9A5P.js +41 -0
- package/core/built/admin/assets/{PolarAngleAxis-pkMyqz8g.js → PolarAngleAxis-BBaU7PRV.js} +2 -2
- package/core/built/admin/assets/RadialBarChart-Bp20JPnA.js +1 -0
- package/core/built/admin/assets/about-Dp43j18M.js +1 -0
- package/core/built/admin/assets/accordion-h62UzFhj.js +1 -0
- package/core/built/admin/assets/account-migration-CBTbtX6Q.js +1 -0
- package/core/built/admin/assets/action-list-DC9QpX-l.js +1 -0
- package/core/built/admin/assets/activitypub/{account-migration-KA6Jjf9L.js → account-migration-B1DUZS4Q.js} +8 -8
- package/core/built/admin/assets/activitypub/activitypub.js +24 -11
- package/core/built/admin/assets/activitypub/avatar-CRGud8yf.js +153 -0
- package/core/built/admin/assets/activitypub/{avatar-flipboard-csQaNeYK.js → avatar-flipboard-BZNFAXfm.js} +3 -3
- package/core/built/admin/assets/activitypub/{bluesky-sharing-C7nTDMvw.js → bluesky-sharing-Cg_v-LwS.js} +30 -31
- package/core/built/admin/assets/activitypub/{chunk-KS7C4IRE-Ck3O4oo2.js → chunk-KS7C4IRE-CTUL98Lc.js} +2498 -1885
- package/core/built/admin/assets/activitypub/{content-formatters-CLcjaBwV.js → content-formatters-D7GWP7nH.js} +23 -24
- package/core/built/admin/assets/activitypub/{copy-DcOh8kXw.js → copy-CgzTvoeR.js} +2 -2
- package/core/built/admin/assets/activitypub/{domain-fZ-X8i4-.js → domain-C3G_nkHU.js} +46 -46
- package/core/built/admin/assets/activitypub/{edit-profile-BqMLQV88.js → edit-profile-C8cCY-AK.js} +927 -928
- package/core/built/admin/assets/activitypub/{explore-B4yPoP8E.js → explore-NlDT4inq.js} +11 -11
- package/core/built/admin/assets/activitypub/{feed-BRC_mvAe.js → feed-DF22jCTV.js} +10 -10
- package/core/built/admin/assets/activitypub/{field-COacNgOt.js → field-95-iuTte.js} +4 -4
- package/core/built/admin/assets/activitypub/{hash-C7u-irQQ.js → hash-R7LMLnhY.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-CbTTdDQ3.js → inbox-DUJUiV6x.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-BW-yvUoN.js → inbox-DerExBzA.js} +416 -414
- package/core/built/admin/assets/activitypub/{label-kPMmYI7l.js → label-EwCn1QYc.js} +4 -4
- package/core/built/admin/assets/activitypub/layout-Dy6NnW1G.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-CuidJzc0.js → moderation-BSkfiVYo.js} +73 -75
- package/core/built/admin/assets/activitypub/{note-CAMRRSqD.js → note-DOnAJTXK.js} +9 -9
- package/core/built/admin/assets/activitypub/{notifications-BmNlXGgp.js → notifications-DZc6XTjG.js} +27 -27
- package/core/built/admin/assets/activitypub/{onboarding-Dmun9jr_.js → onboarding-8vEIwYTH.js} +3 -3
- package/core/built/admin/assets/activitypub/{onboarding-Bqg7fh_L.js → onboarding-DYVqbNmp.js} +2 -2
- package/core/built/admin/assets/activitypub/{preferences--oH19wf9.js → preferences-DDok6F2N.js} +1418 -1390
- package/core/built/admin/assets/activitypub/{profile-DH_tQx2D.js → profile-DjVa2hUk.js} +254 -255
- package/core/built/admin/assets/activitypub/{reply-Cap9PxSH.js → reply-DtXBpHxu.js} +2 -2
- package/core/built/admin/assets/activitypub/routes-Cw302e1c.js +4946 -0
- package/core/built/admin/assets/activitypub/{separator-CAxxu8VV.js → separator-FpRpVDd3.js} +8 -9
- package/core/built/admin/assets/activitypub/settings-yK-puALn.js +314 -0
- package/core/built/admin/assets/activitypub/{site-BYps63oh.js → site-CHw0R2PA.js} +2 -2
- package/core/built/admin/assets/activitypub/{step-1-CKjR2KAm.js → step-1-737bnvxk.js} +7 -7
- package/core/built/admin/assets/activitypub/{step-2-B6PKfOUQ.js → step-2-DBaIsAsM.js} +7 -7
- package/core/built/admin/assets/activitypub/{step-3-C-qBbcUo.js → step-3-BBmy9m9V.js} +23 -23
- package/core/built/admin/assets/activitypub/tabs-LQ2xirdm.js +1101 -0
- package/core/built/admin/assets/activitypub/{topic-filter-DsUOuu0k.js → topic-filter-75Nu0CKj.js} +4 -4
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-CX9snsLs.js → use-activity-pub-queries-CYI2QJMs.js} +58 -58
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-DsJTGDSS.js → use-navigate-with-base-path-B4jZ8hyg.js} +3924 -1403
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-AJi9zkzm.js → use-reply-chain-data-BHmFxc3U.js} +6 -6
- package/core/built/admin/assets/activitypub/users-CSjOwpT9.js +886 -0
- package/core/built/admin/assets/activitypub/{x-DR8AT3-4.js → x-B9ptWCds.js} +2 -2
- package/core/built/admin/assets/add-integration-modal-BwePhq95.js +1 -0
- package/core/built/admin/assets/add-newsletter-modal-Zv0I7Msm.js +1 -0
- package/core/built/admin/assets/add-recommendation-modal-cdLv0wHy.js +1 -0
- package/core/built/admin/assets/analytics-DY8_SaP7.js +1 -0
- package/core/built/admin/assets/announcement-bar-modal-C-2yWwag.js +1 -0
- package/core/built/admin/assets/api-keys-BHKATUqQ.js +1 -0
- package/core/built/admin/assets/{api-keys-BGPzLHzA.js → api-keys-omLA6iYn.js} +1 -1
- package/core/built/admin/assets/{audience-ceV3XVHq.js → audience-wRcSN0jV.js} +1 -1
- package/core/built/admin/assets/automated-emails-CzUa3v4d.js +1 -0
- package/core/built/admin/assets/automations-DupSsSOr.js +1 -0
- package/core/built/admin/assets/automations-hDVcRPjh.js +1 -0
- package/core/built/admin/assets/avatar-flipboard-DiAGgshK.js +1 -0
- package/core/built/admin/assets/badge-CI1Z0xyG.js +1 -0
- package/core/built/admin/assets/bluesky-sharing-B8PC1SBj.js +1 -0
- package/core/built/admin/assets/box-LUuYRT-1.js +1 -0
- package/core/built/admin/assets/brand-icon-CrmbX86T.js +1 -0
- package/core/built/admin/assets/breadcrumb-DDDpzLyo.js +1 -0
- package/core/built/admin/assets/button-D414TI3B.js +1 -0
- package/core/built/admin/assets/calendar-Cp_opo3i.js +1 -0
- package/core/built/admin/assets/card-C2dfd5u0.js +1 -0
- package/core/built/admin/assets/chart-B_UNLXse.js +63 -0
- package/core/built/admin/assets/chart-helpers-Czu_9QpJ.js +1 -0
- package/core/built/admin/assets/checkbox-CFFqf0Nh.js +1 -0
- package/core/built/admin/assets/{chunk-KS7C4IRE-DCsL4i4D.js → chunk-KS7C4IRE-DhOcHQOU.js} +1 -1
- package/core/built/admin/assets/{chunk.254.035b85ec456ebbd7336b.js → chunk.201.6961b57ed7a281ac193e.js} +165 -122
- package/core/built/admin/assets/chunk.524.0b850e72ec49a0fa3882.js +25 -0
- package/core/built/admin/assets/chunk.582.db2ae9e968256e8d58ac.js +27 -0
- package/core/built/admin/assets/{chunk.901.4ccc639821020230bcca.js → chunk.972.4b8c97f93d66db15e751.js} +10625 -10088
- package/core/built/admin/assets/clean-tracked-url-dkN05vOf.js +1 -0
- package/core/built/admin/assets/code-editor-view-Bd_nnHKk.js +1 -0
- package/core/built/admin/assets/code-editor-view-BeU3Yf9M.js +1 -0
- package/core/built/admin/assets/color-picker-Bzm1V7qg.js +3 -0
- package/core/built/admin/assets/color-picker-field-DP0moHz9.js +1 -0
- package/core/built/admin/assets/combobox-6_ruuTuj.js +1 -0
- package/core/built/admin/assets/command-B5wfm6pd.js +1 -0
- package/core/built/admin/assets/comments-BB26dUSl.js +1 -0
- package/core/built/admin/assets/components-CwL6a6As-CCRaoCeP.js +1 -0
- package/core/built/admin/assets/{config-kSHvwdTG.js → config-BPljH6wF.js} +1 -1
- package/core/built/admin/assets/confirmation-context-znVJD4pX.js +1 -0
- package/core/built/admin/assets/container-CHHif8km.js +1 -0
- package/core/built/admin/assets/content-api-modal-DOHwl0yZ.js +1 -0
- package/core/built/admin/assets/context-menu-BpY46Dlb.js +1 -0
- package/core/built/admin/assets/copy-field-DRBx6Ady.js +1 -0
- package/core/built/admin/assets/create-ghost-browse-value-source-eIXo5Qws.js +1 -0
- package/core/built/admin/assets/{custom-field-type-option-BAuW0F-E.js → custom-field-type-option-DrSlETOI.js} +1 -1
- package/core/built/admin/assets/custom-integration-modal-Dgo425Ec.js +1 -0
- package/core/built/admin/assets/data-list-DM8yqpXi.js +1 -0
- package/core/built/admin/assets/date-range-select-DdC23c13.js +1 -0
- package/core/built/admin/assets/design-and-theme-modal-CbSu9IT4.js +2 -0
- package/core/built/admin/assets/detail-page-CN4NdhpV.js +1 -0
- package/core/built/admin/assets/dirty-confirm-dialog-BT_Vq9TD.js +1 -0
- package/core/built/admin/assets/disabled-sources-indicator-C5xtW0Q2.js +1 -0
- package/core/built/admin/assets/dist-B40T-RCQ.js +1 -0
- package/core/built/admin/assets/dist-BRQkNYN1.js +11 -0
- package/core/built/admin/assets/dist-Bm-PB47a.js +6 -0
- package/core/built/admin/assets/{dist-B6bJuxL3.js → dist-C2lYC76d.js} +1 -1
- package/core/built/admin/assets/dist-Cm2qbZKL.js +1 -0
- package/core/built/admin/assets/dist-CrlaEoaK.js +1 -0
- package/core/built/admin/assets/{dist-BtaTA7Ii.js → dist-D7yMk931.js} +1 -1
- package/core/built/admin/assets/{dist-zwwGt8Yf.js → dist-DNAdeRyr.js} +1 -1
- package/core/built/admin/assets/dist-DS6zpxid.js +1 -0
- package/core/built/admin/assets/dist-DVVKsyPh.js +23 -0
- package/core/built/admin/assets/dist-DxfQlAmn.js +1 -0
- package/core/built/admin/assets/dist-DziNzWI2.js +1 -0
- package/core/built/admin/assets/dist-nmRzULcD.js +1 -0
- package/core/built/admin/assets/domain-BPReCZa8.js +1 -0
- package/core/built/admin/assets/dropzone-B2auMkf2.js +5 -0
- package/core/built/admin/assets/edit-profile-B7XTBzKe.js +1 -0
- package/core/built/admin/assets/editor-BcaC9dqm.js +8 -0
- package/core/built/admin/assets/editor-screen-FuMpItC8.js +27 -0
- package/core/built/admin/assets/email-previews-Bs-JDhIw.js +1 -0
- package/core/built/admin/assets/email-sending-status-context-C76KYL1V.js +1 -0
- package/core/built/admin/assets/email-sending-status-provider-9d81o1C2.js +1 -0
- package/core/built/admin/assets/{emails-vFMk4yYl.js → emails-CgvIIz3q.js} +1 -1
- package/core/built/admin/assets/embed-signup-form-modal-CbVbMsxl.js +8 -0
- package/core/built/admin/assets/empty-indicator-CSQts7GV.js +1 -0
- package/core/built/admin/assets/esm--rhhUGIG.js +2 -0
- package/core/built/admin/assets/explore-DbSXFTJN.js +1 -0
- package/core/built/admin/assets/feature-flag-overrides-context-CrcQKm9Z.js +1 -0
- package/core/built/admin/assets/feature-image-placeholder-DFXA4vfJ.js +1 -0
- package/core/built/admin/assets/feed-W7RlF-Gp.js +1 -0
- package/core/built/admin/assets/feedback-BTPBQLT5.js +1 -0
- package/core/built/admin/assets/field-BXEh3ptU.js +1 -0
- package/core/built/admin/assets/filter-sources-DHK2IoCk.js +1 -0
- package/core/built/admin/assets/filters-0SYNTqyQ.js +1 -0
- package/core/built/admin/assets/filters-DTtZKQyq.js +1 -0
- package/core/built/admin/assets/first-promoter-modal-DcoN_OXu.js +1 -0
- package/core/built/admin/assets/focus-koenig-editor-on-bottom-click-CkFA8EkI.js +1 -0
- package/core/built/admin/assets/framework-provider-DB-svAju.js +4 -0
- package/core/built/admin/assets/{ghost-b4e8a403baba61905cd7560e30b9732d.js → ghost-25495154132af7305cb7b646fd83f48f.js} +580 -523
- package/core/built/admin/assets/gift-link-modal-BX-Q1TOk.js +1 -0
- package/core/built/admin/assets/{gift-links-CfLSnst2.js → gift-links-D8gl_yJS.js} +1 -1
- package/core/built/admin/assets/global-data-context-3MItMry2.js +1 -0
- package/core/built/admin/assets/grid-BEUIDQX7.js +1 -0
- package/core/built/admin/assets/growth-G6PLO4aF.js +1 -0
- package/core/built/admin/assets/growth-tkD-fBOY.js +3 -0
- package/core/built/admin/assets/heading-ih-fOVqF.js +1 -0
- package/core/built/admin/assets/history-modal-Ca-gStoq.js +1 -0
- package/core/built/admin/assets/hooks-DYfTEQLk.js +2 -0
- package/core/built/admin/assets/html-field-B3CrKV9Y.js +1 -0
- package/core/built/admin/assets/iframe-buffering-Bq1el-gw.js +1 -0
- package/core/built/admin/assets/images-H-BIm8OQ.js +1 -0
- package/core/built/admin/assets/{inbox-gJHG7_xd.js → inbox-DBbDVMPz.js} +35 -35
- package/core/built/admin/assets/index-DOF2jDq0.js +4 -0
- package/core/built/admin/assets/index-JrrNchW1.css +2 -0
- package/core/built/admin/assets/index.es-Ct5XQNSj.js +1 -0
- package/core/built/admin/assets/input-group-CvH-3d3F.js +1 -0
- package/core/built/admin/assets/input-surface-CL23Mlh8.js +1 -0
- package/core/built/admin/assets/integration-header-C7GXGis_.js +1 -0
- package/core/built/admin/assets/{integrations-B4kuRBFY.js → integrations-DnJrZnHv.js} +1 -1
- package/core/built/admin/assets/invite-user-modal-EJ2_6RpV.js +1 -0
- package/core/built/admin/assets/isPlainObject-BzzgUf1E.js +1 -0
- package/core/built/admin/assets/jsx-runtime-BlNNh7jY.js +1 -0
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +3 -3
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +1 -1
- package/core/built/admin/assets/{koenig-lexical-CGXb0F8o.js → koenig-lexical-Dkwee5FG.js} +1 -1
- package/core/built/admin/assets/koenig-loader-BzXCXHoA.js +2 -0
- package/core/built/admin/assets/kpi-card-iCDUTgOR.js +1 -0
- package/core/built/admin/assets/kpi-card-jTnpLKGr.js +1 -0
- package/core/built/admin/assets/kpi-helpers-Criu0YAf.js +1 -0
- package/core/built/admin/assets/kpi-tabs-DbOmmyOo.js +1 -0
- package/core/built/admin/assets/label-Du0yqMqL.js +1 -0
- package/core/built/admin/assets/label-picker-Bq1XXr1D.js +1 -0
- package/core/built/admin/assets/labels-0x2NfLtC.js +1 -0
- package/core/built/admin/assets/layout-Ckwzsnsu.js +1 -0
- package/core/built/admin/assets/lib-CCz2mjm8.js +2 -0
- package/core/built/admin/assets/link-helpers-o9GgBTni.js +1 -0
- package/core/built/admin/assets/links-Ctujreuh.js +1 -0
- package/core/built/admin/assets/list-page-CH_wnDC3.js +1 -0
- package/core/built/admin/assets/luxon-BXCsmep3.js +1 -0
- package/core/built/admin/assets/member-activity-CsAh6MxK.js +1 -0
- package/core/built/admin/assets/member-activity-feed-0ytsQbtY.js +1 -0
- package/core/built/admin/assets/member-custom-fields-CTFGnnYv.js +1 -0
- package/core/built/admin/assets/member-detail-QU3vB1ML.js +1 -0
- package/core/built/admin/assets/members-CtMVO_AB.js +4 -0
- package/core/built/admin/assets/modal-page-8Ipfl51B.js +1 -0
- package/core/built/admin/assets/moderation-B8VtA-W0.js +1 -0
- package/core/built/admin/assets/multi-select-combobox-_9wEYmGv.js +1 -0
- package/core/built/admin/assets/navigation-modal-CGcjJvkc.js +1 -0
- package/core/built/admin/assets/newsletter-BpHjjqmq.js +1 -0
- package/core/built/admin/assets/newsletter-detail-modal-DzTbGOyo.js +1 -0
- package/core/built/admin/assets/newsletter-emails-BMKH4Osp.js +1 -0
- package/core/built/admin/assets/newsletters-BxC84_cL.js +1 -0
- package/core/built/admin/assets/{newsletters-Rq1M3GD-.js → newsletters-Dz77m__5.js} +1 -1
- package/core/built/admin/assets/note-BPtEzzZS.js +1 -0
- package/core/built/admin/assets/notifications-ng3ulEdp.js +1 -0
- package/core/built/admin/assets/{nql-BLpCn7qX.js → nql-DnDc7UuU.js} +1 -1
- package/core/built/admin/assets/nql-lang-CeTB_Bhv.js +19 -0
- package/core/built/admin/assets/{offers--e2nGvkW.js → offers-ByWSK0rR.js} +1 -1
- package/core/built/admin/assets/offers-container-modal-CwqnRa3T.js +1 -0
- package/core/built/admin/assets/onboarding-B4hfBi9a.js +1 -0
- package/core/built/admin/assets/onboarding-DV_XbHts.js +1 -0
- package/core/built/admin/assets/onboarding-route-CplZzd7F.js +1 -0
- package/core/built/admin/assets/overview-B0yD9OoX.js +1 -0
- package/core/built/admin/assets/overview-C4eJzQqi.js +1 -0
- package/core/built/admin/assets/page-header-CdCpekG9.js +1 -0
- package/core/built/admin/assets/pagemenu-CKQD9hoP.js +1 -0
- package/core/built/admin/assets/{pages-CjDY2CBI.js → pages-DZ_YGB1D.js} +1 -1
- package/core/built/admin/assets/pages-route-CKsnxIvw.js +1 -0
- package/core/built/admin/assets/papaparse.min-BlhnCUXn.js +10 -0
- package/core/built/admin/assets/pintura-modal-C5jzeyGk.js +1 -0
- package/core/built/admin/assets/portal-frame-DYo0bh0E.js +1 -0
- package/core/built/admin/assets/portal-modal-C0d8xjKa.js +1 -0
- package/core/built/admin/assets/post-analytics-C5C7IyAm.js +1 -0
- package/core/built/admin/assets/post-analytics-context-bt2YTBAC.js +1 -0
- package/core/built/admin/assets/post-analytics-header-DKWHwoK2.js +1 -0
- package/core/built/admin/assets/post-analytics-provider-BqCPPxmW.js +1 -0
- package/core/built/admin/assets/post-share-modal-wxmqbAv_.js +1 -0
- package/core/built/admin/assets/posts-list-screen-B978ZO0R.js +2 -0
- package/core/built/admin/assets/posts-route-CDqxKazl.js +1 -0
- package/core/built/admin/assets/{posts-pCPrhQeR.js → posts-tcoO3iRG.js} +1 -1
- package/core/built/admin/assets/preferences-CnejTRc4.js +5 -0
- package/core/built/admin/assets/preview-chrome-C7yZWVLZ.js +1 -0
- package/core/built/admin/assets/preview-modal-CFUxUGHp.js +1 -0
- package/core/built/admin/assets/profile-ClRGiOqA.js +1 -0
- package/core/built/admin/assets/prop-types-6lTeqvom.js +1 -0
- package/core/built/admin/assets/radio-group-CTmLo0h9.js +1 -0
- package/core/built/admin/assets/react-BXwhMTuc.js +1 -0
- package/core/built/admin/assets/{jsx-runtime-DX3WBWVO.js → react-dom-CMOtJ5MH.js} +2 -2
- package/core/built/admin/assets/{referrers-D8oHPmDB.js → referrers-1R8bPpoV.js} +1 -1
- package/core/built/admin/assets/router-provider-DDZvgS3V.js +1 -0
- package/core/built/admin/assets/select-D_4xWFYv.js +1 -0
- package/core/built/admin/assets/settings-BRl7-7a9.js +1 -0
- package/core/built/admin/assets/settings-BoQcPa-y.js +4 -0
- package/core/built/admin/assets/settings-breadcrumbs-C9Yxl81q.js +1 -0
- package/core/built/admin/assets/{dist-B3keLvjj.js → shade-scope-Cccj-c7I-D0XwkdKQ.js} +5 -3
- package/core/built/admin/assets/share-modal-DBqZobuR.js +1 -0
- package/core/built/admin/assets/simple-pagination-D5t_ox6x.js +1 -0
- package/core/built/admin/assets/skeleton-CPcTiTSX.js +1 -0
- package/core/built/admin/assets/slack-modal-BF9ex8cd.js +1 -0
- package/core/built/admin/assets/sort-button-C0EXGjnS.js +1 -0
- package/core/built/admin/assets/source-icon-BzzftCQ2.js +1 -0
- package/core/built/admin/assets/{stats-D9PDLS3x.js → stats-Cvp8Ep5m.js} +1 -1
- package/core/built/admin/assets/stats-view-CzpGoHzF.js +1 -0
- package/core/built/admin/assets/step-1-CBUYUPPF.js +1 -0
- package/core/built/admin/assets/step-2-CEIXUwQa.js +1 -0
- package/core/built/admin/assets/step-3-C1HA5i1N.js +2 -0
- package/core/built/admin/assets/stripe-button-DMFsLs80.js +1 -0
- package/core/built/admin/assets/stripe-connect-modal-CEvxCUTv.js +1 -0
- package/core/built/admin/assets/switch-DoOJF2yg.js +1 -0
- package/core/built/admin/assets/table-BDYbUHJz.js +1 -0
- package/core/built/admin/assets/tabs-BZyz4ZCH.js +1 -0
- package/core/built/admin/assets/tag-detail-CFzaz_9E.js +2 -0
- package/core/built/admin/assets/tag-picker-BKUCZuRc.js +1 -0
- package/core/built/admin/assets/tags-Bs1FiD7m.js +1 -0
- package/core/built/admin/assets/{tags-ChfdwJyS.js → tags-NwZ7BPK6.js} +1 -1
- package/core/built/admin/assets/testimonials-modal-CVm0_7E-.js +1 -0
- package/core/built/admin/assets/text-BLI0C8Mc.js +1 -0
- package/core/built/admin/assets/textarea-C5qolByg.js +1 -0
- package/core/built/admin/assets/themes-DtcEFQWm.js +1 -0
- package/core/built/admin/assets/tier-detail-modal-Cmwzi-e7.js +1 -0
- package/core/built/admin/assets/{tiers-BCpIOGQr.js → tiers-6mPzCXlM.js} +1 -1
- package/core/built/admin/assets/toggle-group-BTL1QsWY.js +1 -0
- package/core/built/admin/assets/topic-filter-BMjhquv7.js +1 -0
- package/core/built/admin/assets/transistor-modal-hMv53jF8.js +1 -0
- package/core/built/admin/assets/trend-badge-1w_HEzkG.js +1 -0
- package/core/built/admin/assets/unsplash-modal-jzC8lEB2.js +1 -0
- package/core/built/admin/assets/use-analytics-data-B3lGOxsC.js +1 -0
- package/core/built/admin/assets/use-availability-DnKqOyqd.js +1 -0
- package/core/built/admin/assets/{use-check-theme-limit-error-DRUjK4YM.js → use-check-theme-limit-error-C1tc2P4N.js} +2 -2
- package/core/built/admin/assets/use-email-sending-status-DNMtL5B9.js +1 -0
- package/core/built/admin/assets/use-filter-params-DU-aiaTb.js +1 -0
- package/core/built/admin/assets/use-filterable-api-CxCrnPxG.js +1 -0
- package/core/built/admin/assets/use-form-DJKZJhQh.js +1 -0
- package/core/built/admin/assets/use-growth-stats-E13rMQUE.js +1 -0
- package/core/built/admin/assets/use-host-limits-mpTkZIq8.js +1 -0
- package/core/built/admin/assets/use-limiter-DlL7au90.js +19 -0
- package/core/built/admin/assets/use-navigate-with-base-path-BzgNwMAx.js +1 -0
- package/core/built/admin/assets/use-pintura-config-CtEV1JQY.js +1 -0
- package/core/built/admin/assets/use-pintura-editor-CaxE64kD.js +1 -0
- package/core/built/admin/assets/use-post-referrers-DyPjyBSN.js +1 -0
- package/core/built/admin/assets/use-query-params-DlOHCo-R.js +1 -0
- package/core/built/admin/assets/use-reply-chain-data-BST2OgCM.js +1 -0
- package/core/built/admin/assets/use-router-history-entry-CKx9LhqD.js +1 -0
- package/core/built/admin/assets/use-sending-eta-DR9ueMH8.js +1 -0
- package/core/built/admin/assets/use-setting-group-ldtX1Vsk.js +1 -0
- package/core/built/admin/assets/use-settings-navigation-CzGU4sus.js +1 -0
- package/core/built/admin/assets/use-staff-users-DoeXaPsb.js +1 -0
- package/core/built/admin/assets/use-tinybird-query-BaBNCaj0.js +1 -0
- package/core/built/admin/assets/use-tinybird-token-CnZ19NDf.js +1 -0
- package/core/built/admin/assets/use-unsaved-changes-guard-CKVPWz_F.js +1 -0
- package/core/built/admin/assets/use-upgrade-route-CVjDlNSs.js +1 -0
- package/core/built/admin/assets/{use-url-input-fvp-TpPx.js → use-url-input-C5KHJw79.js} +1 -1
- package/core/built/admin/assets/user-detail-modal-Dm0KfPeR.js +1 -0
- package/core/built/admin/assets/{users-DVNWrqg9.js → users-C60_hpn6.js} +1 -1
- package/core/built/admin/assets/{validator-Cr0sMKuW.js → validator-DlFQP70j.js} +1 -1
- package/core/built/admin/assets/{vendor-2dd263291938d407802568d3357ec69e.js → vendor-97e91ff8dd1457c668f9ef103ee1a77b.js} +2748 -2977
- package/core/built/admin/assets/virtual-list-tAuPUICw.js +1 -0
- package/core/built/admin/assets/web-BQlKcW2x.js +2 -0
- package/core/built/admin/assets/web-NTT5DkEz.js +1 -0
- package/core/built/admin/assets/zapier-modal-DsHG9Oev.js +1 -0
- package/core/built/admin/index.html +35 -34
- package/core/cli/command.js +17 -2
- package/core/frontend/apps/private-blogging/index.js +2 -2
- package/core/frontend/apps/private-blogging/lib/middleware.js +1 -1
- package/core/frontend/apps/private-blogging/lib/router.js +1 -1
- package/core/frontend/helpers/cancel_link.js +3 -3
- package/core/frontend/helpers/content.js +2 -2
- package/core/frontend/helpers/content_api_url.js +1 -1
- package/core/frontend/helpers/date.js +1 -1
- package/core/frontend/helpers/foreach.js +5 -5
- package/core/frontend/helpers/get.js +7 -4
- package/core/frontend/helpers/ghost_head.js +2 -2
- package/core/frontend/helpers/has.js +1 -3
- package/core/frontend/helpers/link.js +7 -7
- package/core/frontend/helpers/link_class.js +2 -2
- package/core/frontend/helpers/navigation.js +1 -2
- package/core/frontend/helpers/recommendations.js +5 -6
- package/core/frontend/helpers/tiers.js +1 -1
- package/core/frontend/helpers/total_members.js +1 -1
- package/core/frontend/helpers/total_paid_members.js +1 -1
- package/core/frontend/meta/asset-url.js +1 -16
- package/core/frontend/meta/author-url.js +1 -1
- package/core/frontend/meta/modified-date.js +1 -1
- package/core/frontend/meta/published-date.js +1 -1
- package/core/frontend/meta/schema.js +3 -8
- package/core/frontend/meta/structured-data.js +1 -2
- package/core/frontend/services/asset-hash/index.js +32 -2
- package/core/frontend/services/assets-minification/minifier.js +1 -1
- package/core/frontend/services/data/entry-lookup.js +1 -1
- package/core/frontend/services/data/fetch-data.js +2 -2
- package/core/frontend/services/rendering/format-response.js +1 -1
- package/core/frontend/services/rendering/renderer.js +3 -0
- package/core/frontend/services/rendering/templates.js +1 -1
- package/core/frontend/services/routing/controllers/static.js +1 -1
- package/core/frontend/services/routing/controllers/unsubscribe.js +2 -1
- package/core/frontend/services/routing/parent-router.js +1 -1
- package/core/frontend/services/routing/registry.js +1 -1
- package/core/frontend/services/rss/generate-feed.js +1 -1
- package/core/frontend/services/sitemap/base-site-map-generator.js +68 -97
- package/core/frontend/services/sitemap/site-map-index-generator.js +16 -21
- package/core/frontend/services/sitemap/site-map-manager.js +82 -43
- package/core/frontend/services/sitemap/sitemap-xml.js +72 -0
- package/core/frontend/services/theme-engine/active.js +2 -4
- package/core/frontend/services/theme-engine/handlebars/utils.js +8 -8
- package/core/frontend/services/theme-engine/i18n/i18n.js +15 -6
- package/core/frontend/services/theme-engine/i18next/theme-i18n.js +1 -1
- package/core/frontend/services/theme-engine/middleware/update-global-template-options.js +1 -1
- package/core/frontend/services/theme-engine/preview.js +1 -1
- package/core/frontend/utils/member-count.js +2 -2
- package/core/frontend/web/middleware/llms-discovery.js +34 -43
- package/core/frontend/web/routers/serve-public-file.js +1 -1
- package/core/server/adapters/route-settings/S3RouteSettingsStore.js +1 -1
- package/core/server/adapters/scheduling/README.md +4 -0
- package/core/server/adapters/scheduling/get-scheduler-idempotency-key.js +21 -0
- package/core/server/adapters/storage/LocalFilesStorage.js +12 -2
- package/core/server/adapters/storage/LocalStorageBase.js +15 -1
- package/core/server/api/endpoints/actions.js +2 -1
- package/core/server/api/endpoints/automated-emails.js +11 -4
- package/core/server/api/endpoints/automations.js +77 -49
- package/core/server/api/endpoints/db.js +1 -1
- package/core/server/api/endpoints/emails.js +11 -6
- package/core/server/api/endpoints/index.js +1 -1
- package/core/server/api/endpoints/invites.js +2 -1
- package/core/server/api/endpoints/labels.js +6 -2
- package/core/server/api/endpoints/mail.js +1 -1
- package/core/server/api/endpoints/member-metafields.js +2 -2
- package/core/server/api/endpoints/member-signin-urls.js +1 -1
- package/core/server/api/endpoints/members.js +23 -7
- package/core/server/api/endpoints/oembed.js +1 -1
- package/core/server/api/endpoints/pages.js +2 -6
- package/core/server/api/endpoints/posts.js +4 -12
- package/core/server/api/endpoints/settings.js +2 -2
- package/core/server/api/endpoints/snippets.js +2 -1
- package/core/server/api/endpoints/tags.js +6 -2
- package/core/server/api/endpoints/themes.js +5 -5
- package/core/server/api/endpoints/users.js +2 -6
- package/core/server/api/endpoints/utils/api-filter-utils.js +62 -3
- package/core/server/api/endpoints/utils/serializers/input/pages.js +2 -2
- package/core/server/api/endpoints/utils/serializers/input/posts.js +2 -2
- package/core/server/api/endpoints/utils/serializers/input/tags.js +1 -1
- package/core/server/api/endpoints/utils/serializers/input/utils/slug-filter-order.js +4 -4
- package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +8 -5
- package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/pages.js +4 -4
- package/core/server/api/endpoints/utils/serializers/output/posts.js +4 -4
- package/core/server/api/endpoints/utils/serializers/output/search-index.js +11 -11
- package/core/server/api/endpoints/utils/serializers/output/settings.js +1 -1
- package/core/server/api/endpoints/utils/serializers/output/utils/clean.js +2 -0
- package/core/server/api/endpoints/utils/serializers/output/utils/extra-attrs.js +6 -3
- package/core/server/api/endpoints/utils/validators/input/automated_emails.js +23 -33
- package/core/server/api/endpoints/utils/validators/input/settings.js +18 -4
- package/core/server/data/exporter/exporter.js +3 -3
- package/core/server/data/exporter/table-lists.js +4 -5
- package/core/server/data/importer/handlers/image.js +1 -2
- package/core/server/data/importer/handlers/json.js +1 -3
- package/core/server/data/importer/handlers/markdown.js +4 -8
- package/core/server/data/importer/import-manager.js +1 -1
- package/core/server/data/importer/importers/content-file-importer.js +4 -8
- package/core/server/data/importer/importers/data/base.js +10 -10
- package/core/server/data/importer/importers/data/custom-theme-settings-importer.js +1 -1
- package/core/server/data/importer/importers/data/data-importer.js +3 -4
- package/core/server/data/importer/importers/data/posts-importer.js +10 -10
- package/core/server/data/importer/importers/data/products-importer.js +3 -3
- package/core/server/data/importer/importers/data/settings-importer.js +10 -4
- package/core/server/data/importer/importers/data/stripe-prices-importer.js +1 -1
- package/core/server/data/importer/importers/data/stripe-products-importer.js +1 -1
- package/core/server/data/importer/importers/data/tags-importer.js +1 -1
- package/core/server/data/importer/importers/data/users-importer.js +2 -2
- package/core/server/data/importer/importers/json-to-html.js +12 -9
- package/core/server/data/migrations/versions/5.0/2022-05-11-13-12-rename-settings.js +1 -1
- package/core/server/data/migrations/versions/6.64/2026-09-03-15-40-25-add-member-access-to-members-metafields.js +12 -0
- package/core/server/data/migrations/versions/6.64/2026-09-08-11-01-12-add-posts-auto-excerpt-and-reading-time-columns.js +17 -0
- package/core/server/data/migrations/versions/6.64/2026-09-09-13-56-18-add-automation-run-updated-at-indexes.js +6 -0
- package/core/server/data/migrations/versions/6.64/2026-09-09-13-56-18-add-tinybird-syncs-table.js +10 -0
- package/core/server/data/migrations/versions/6.65/2026-09-16-15-35-26-add-members-metafield-change-events.js +17 -0
- package/core/server/data/schema/fixtures/fixture-manager.js +3 -3
- package/core/server/data/schema/index.js +1 -1
- package/core/server/data/schema/schema.js +54 -2
- package/core/server/data/schema/validator.js +96 -123
- package/core/server/data/seeders/importers/automations-importer.js +0 -1
- package/core/server/data/seeders/importers/comments-importer.js +1 -1
- package/core/server/data/seeders/importers/email-recipients-importer.js +3 -3
- package/core/server/data/seeders/importers/emails-importer.js +1 -1
- package/core/server/data/seeders/importers/members-importer.js +1 -1
- package/core/server/data/seeders/importers/members-login-events-importer.js +2 -2
- package/core/server/data/seeders/importers/members-newsletters-importer.js +1 -1
- package/core/server/data/seeders/importers/members-paid-subscription-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-status-events-importer.js +1 -1
- package/core/server/data/seeders/importers/members-stripe-customers-importer.js +1 -1
- package/core/server/data/seeders/importers/members-stripe-customers-subscriptions-importer.js +2 -2
- package/core/server/data/seeders/importers/members-subscribe-events-importer.js +2 -2
- package/core/server/data/seeders/importers/members-subscription-created-events-importer.js +1 -1
- package/core/server/data/seeders/utils/blog-info.js +4 -3
- package/core/server/data/seeders/utils/event-generator.js +72 -46
- package/core/server/data/tinybird/datasources/_mv_automation_run_steps.datasource +10 -0
- package/core/server/data/tinybird/datasources/_mv_automation_runs.datasource +10 -0
- package/core/server/data/tinybird/datasources/_mv_pending_automation_runs.datasource +10 -0
- package/core/server/data/tinybird/datasources/automation_run_events.datasource +12 -0
- package/core/server/data/tinybird/datasources/automation_run_step_events.datasource +12 -0
- package/core/server/data/tinybird/endpoints/api_automation_browse_stats.pipe +43 -0
- package/core/server/data/tinybird/fixtures/automation_run_events.ndjson +7 -0
- package/core/server/data/tinybird/fixtures/automation_run_step_events.ndjson +8 -0
- package/core/server/data/tinybird/pipes/mv_automation_run_steps.pipe +13 -0
- package/core/server/data/tinybird/pipes/mv_automation_runs.pipe +15 -0
- package/core/server/data/tinybird/pipes/mv_hits.pipe +25 -20
- package/core/server/data/tinybird/pipes/mv_pending_automation_runs.pipe +7 -0
- package/core/server/data/tinybird/scripts/docker-analytics-manager.js +2 -2
- package/core/server/data/tinybird/tests/api_automation_browse_stats.yaml +18 -0
- package/core/server/ghost-server.js +4 -22
- package/core/server/lib/generate-password.js +54 -0
- package/core/server/lib/image/image-size.js +1 -3
- package/core/server/lib/public-jwk.js +77 -0
- package/core/server/lib/request-external.js +53 -117
- package/core/server/models/base/plugins/actions.js +2 -3
- package/core/server/models/base/plugins/crud.js +1 -2
- package/core/server/models/base/plugins/events.js +4 -4
- package/core/server/models/base/plugins/raw-knex.js +3 -3
- package/core/server/models/base/plugins/sanitize.js +1 -1
- package/core/server/models/benefit.js +1 -1
- package/core/server/models/collection.js +1 -1
- package/core/server/models/comment.js +3 -3
- package/core/server/models/invite.js +2 -5
- package/core/server/models/label.js +3 -6
- package/core/server/models/member.js +7 -10
- package/core/server/models/newsletter.js +1 -1
- package/core/server/models/permission.js +3 -6
- package/core/server/models/post.js +20 -23
- package/core/server/models/product.js +4 -1
- package/core/server/models/relations/authors.js +9 -12
- package/core/server/models/role.js +2 -5
- package/core/server/models/settings.js +3 -3
- package/core/server/models/tag.js +2 -5
- package/core/server/models/user.js +19 -19
- package/core/server/models/webhook.js +2 -4
- package/core/server/notify.js +2 -2
- package/core/server/services/adapter-manager/base-classes.js +21 -3
- package/core/server/services/adapter-manager/utils.js +6 -1
- package/core/server/services/api-version-compatibility/legacy-api-path-match.js +2 -2
- package/core/server/services/auth/api-key/admin.js +2 -2
- package/core/server/services/auth/api-key/content.js +1 -1
- package/core/server/services/auth/passwordreset.js +1 -1
- package/core/server/services/auth/setup.js +1 -3
- package/core/server/services/automations/README.md +19 -0
- package/core/server/services/automations/automations-api.js +46 -1
- package/core/server/services/automations/poll.js +3 -1
- package/core/server/services/automations/service.js +2 -2
- package/core/server/services/automations/tinybird-automation-stats.js +56 -0
- package/core/server/services/automations/welcome-email-automation-poll.js +199 -293
- package/core/server/services/comments/comments-controller.js +9 -30
- package/core/server/services/comments/comments-service-email-renderer.js +1 -1
- package/core/server/services/comments/comments-service-emails.js +1 -1
- package/core/server/services/comments/comments-service.js +27 -58
- package/core/server/services/comments/comments-stats-service.js +1 -1
- package/core/server/services/content-import/import/media.js +1 -1
- package/core/server/services/content-import/import/staged-file.js +9 -9
- package/core/server/services/content-import/index.js +1 -1
- package/core/server/services/custom-redirects/validation.js +1 -1
- package/core/server/services/email-analytics/email-analytics-service-wrapper.js +38 -84
- package/core/server/services/email-analytics/email-analytics-service.js +19 -3
- package/core/server/services/email-analytics/index.js +44 -23
- package/core/server/services/email-analytics/jobs/email-analytics-job-scheduler.js +31 -60
- package/core/server/services/email-rendering/finalize.js +7 -2
- package/core/server/services/email-service/README.md +38 -0
- package/core/server/services/email-service/batch-sending-service.js +13 -14
- package/core/server/services/email-service/email-renderer.js +57 -16
- package/core/server/services/email-service/email-service-wrapper.js +4 -3
- package/core/server/services/email-service/email-service.js +76 -42
- package/core/server/services/email-service/email-templates/partials/feedback-button.hbs +2 -1
- package/core/server/services/email-service/email-templates/partials/styles.hbs +0 -6
- package/core/server/services/email-service/email-templates/template.hbs +1 -1
- package/core/server/services/email-service/jobs/send-email-job.js +12 -0
- package/core/server/services/email-service/mailgun-email-provider.js +7 -2
- package/core/server/services/email-service/sending-service.js +1 -1
- package/core/server/services/email-service/sending-status.js +10 -4
- package/core/server/services/gifts/gift-email-service.js +10 -3
- package/core/server/services/gifts/gift-service.js +12 -3
- package/core/server/services/gifts/index.js +1 -2
- package/core/server/services/gifts/jobs/index.js +12 -24
- package/core/server/services/gifts/jobs/send-gift-reminders-job.js +6 -46
- package/core/server/services/i18n.js +1 -1
- package/core/server/services/identity-tokens/identity-token-service-wrapper.js +3 -4
- package/core/server/services/invitations/accept.js +2 -2
- package/core/server/services/jobs/job-service.js +0 -28
- package/core/server/services/jobs-service/jobs-service.js +5 -0
- package/core/server/services/jobs-service/register-job-handlers.js +17 -1
- package/core/server/services/lib/mailgun-client.js +16 -1
- package/core/server/services/lib/mailgun-recipient-message-id.js +49 -0
- package/core/server/services/limits.js +18 -2
- package/core/server/services/mail/ghost-mailer.js +1 -1
- package/core/server/services/member-attribution/member-attribution-service.js +1 -1
- package/core/server/services/member-attribution/referrer-translator.js +1 -1
- package/core/server/services/member-welcome-emails/service.js +1 -1
- package/core/server/services/members/account-service.js +3 -3
- package/core/server/services/members/api.js +1 -1
- package/core/server/services/members/import-export/import/importer.js +25 -16
- package/core/server/services/members/import-export/import/spool.js +11 -15
- package/core/server/services/members/import-export/index.js +11 -8
- package/core/server/services/members/jobs/index.js +1 -1
- package/core/server/services/members/jobs/members-import-job.js +18 -0
- package/core/server/services/members/member-avatar.js +18 -0
- package/core/server/services/members/members-api/controllers/member-controller.js +2 -2
- package/core/server/services/members/members-api/controllers/router-controller.js +7 -2
- package/core/server/services/members/members-api/controllers/well-known-controller.js +2 -1
- package/core/server/services/members/members-api/members-api.js +4 -4
- package/core/server/services/members/members-api/repositories/event-repository.js +40 -1
- package/core/server/services/members/members-api/repositories/member-repository.js +55 -17
- package/core/server/services/members/members-api/repositories/product-repository.js +1 -1
- package/core/server/services/members/members-api/services/member-bread-service.js +19 -19
- package/core/server/services/members/members-api/services/token-service.js +11 -12
- package/core/server/services/members/middleware.js +9 -2
- package/core/server/services/members/request-integrity-token-provider.js +2 -1
- package/core/server/services/members/service.js +7 -2
- package/core/server/services/members/stats/members-stats.js +2 -2
- package/core/server/services/members-events/event-storage.js +2 -2
- package/core/server/services/members-metafields/access.js +31 -20
- package/core/server/services/members-metafields/actions.js +24 -2
- package/core/server/services/members-metafields/bindings-service.js +5 -5
- package/core/server/services/members-metafields/codec.js +9 -2
- package/core/server/services/members-metafields/definitions-service.js +57 -52
- package/core/server/services/members-metafields/index.js +3 -2
- package/core/server/services/members-metafields/models.js +2 -0
- package/core/server/services/members-metafields/queries.js +45 -11
- package/core/server/services/members-metafields/schema.js +51 -1
- package/core/server/services/members-metafields/serializers.js +1 -0
- package/core/server/services/members-metafields/values-service.js +132 -21
- package/core/server/services/mentions/mention-discovery-service.js +1 -1
- package/core/server/services/mentions/mention-sending-service.js +3 -3
- package/core/server/services/mentions/mention.js +4 -5
- package/core/server/services/mentions/mentions-api.js +4 -1
- package/core/server/services/mentions-jobs/job-service.js +0 -28
- package/core/server/services/milestones/milestones-service.js +1 -1
- package/core/server/services/newsletters/newsletters-service.js +1 -1
- package/core/server/services/notifications/notifications.js +3 -3
- package/core/server/services/oembed/oembed-service.js +115 -14
- package/core/server/services/permissions/can-this.js +1 -2
- package/core/server/services/post-scheduling/post-scheduling.js +8 -1
- package/core/server/services/posts/post-scheduling.js +27 -0
- package/core/server/services/settings/settings-bread-service.js +3 -3
- package/core/server/services/settings/settings-utils.js +1 -1
- package/core/server/services/settings-helpers/settings-helpers.js +1 -1
- package/core/server/services/staff/staff-service-emails.js +14 -10
- package/core/server/services/stats/posts-stats-service.js +10 -10
- package/core/server/services/stats/referrers-stats-service.js +1 -1
- package/core/server/services/stats/stats-service.js +12 -3
- package/core/server/services/stats/subscription-stats-service.js +3 -3
- package/core/server/services/stats/utils/tinybird.js +8 -1
- package/core/server/services/stripe/stripe-api.js +3 -3
- package/core/server/services/stripe/stripe-migrations.js +6 -6
- package/core/server/services/themes/storage.js +21 -26
- package/core/server/services/themes/theme-storage.js +2 -2
- package/core/server/services/tier-checkout-config/service.js +17 -1
- package/core/server/services/tiers/tier.js +15 -15
- package/core/server/services/tinybird/tinybird-service.js +1 -0
- package/core/server/services/tinybird-sync/get-ingest-config.js +39 -0
- package/core/server/services/tinybird-sync/index.js +31 -0
- package/core/server/services/tinybird-sync/sync-table-to-tinybird.js +149 -0
- package/core/server/services/tinybird-sync/tinybird-sync-service.js +67 -0
- package/core/server/services/update-check/update-check-service.js +3 -3
- package/core/server/services/webhooks/serialize.js +1 -1
- package/core/server/web/api/app.js +0 -5
- package/core/server/web/api/endpoints/admin/routes.js +33 -34
- package/core/server/web/api/middleware/upload.js +29 -2
- package/core/server/web/parent/middleware/index.js +1 -1
- package/core/server/web/parent/middleware/log-request.js +5 -0
- package/core/server/web/parent/middleware/queue-request.js +104 -59
- package/core/server/web/shared/middleware/api/spam-prevention.js +2 -2
- package/core/server/web/well-known.js +12 -16
- package/core/shared/config/defaults.json +7 -3
- package/core/shared/custom-theme-settings-cache/custom-theme-settings-service.js +1 -1
- package/core/shared/{flush-logs.js → flush.js} +7 -3
- package/core/shared/labs.js +5 -2
- package/core/shared/settings-cache/cache-manager.js +1 -1
- package/core/shared/timing-safe-string-equal.js +26 -0
- package/nodemon.json +1 -1
- package/package.json +47 -48
- package/patches/@elastic__elasticsearch@8.19.2.patch +67 -0
- package/patches/juice@11.1.1.patch +46 -0
- package/pnpm-lock.yaml +571 -2107
- package/pnpm-workspace.yaml +38 -28
- package/scripts/prune.mts +70 -3
- package/tsconfig.json +1 -1
- package/components/tryghost-adapter-base-scheduling-0.2.3.tgz +0 -0
- package/core/built/admin/assets/Combination-B0l4MKVh.js +0 -41
- package/core/built/admin/assets/RadialBarChart-JAKoS4KT.js +0 -1
- package/core/built/admin/assets/_baseAssignValue-C_vooEQc.js +0 -1
- package/core/built/admin/assets/about-C1C3s5-g.js +0 -1
- package/core/built/admin/assets/accordion-CxGsDKwa.js +0 -1
- package/core/built/admin/assets/account-migration-CbUhW6gk.js +0 -1
- package/core/built/admin/assets/action-list-C4v0-du8.js +0 -1
- package/core/built/admin/assets/activitypub/avatar-DVJF5bxP.js +0 -261
- package/core/built/admin/assets/activitypub/layout-BL4_jtuV.js +0 -1
- package/core/built/admin/assets/activitypub/routes-DdyY9uFF.js +0 -7181
- package/core/built/admin/assets/activitypub/settings-B6EEcVkW.js +0 -315
- package/core/built/admin/assets/activitypub/tabs-DLhZtVde.js +0 -1083
- package/core/built/admin/assets/activitypub/users-inweyKL5.js +0 -1499
- package/core/built/admin/assets/add-integration-modal-BT_onDJy.js +0 -1
- package/core/built/admin/assets/add-newsletter-modal-Cx1kQpiY.js +0 -1
- package/core/built/admin/assets/add-recommendation-modal-B5nvAhvr.js +0 -1
- package/core/built/admin/assets/analytics-DjoJWPI7.js +0 -1
- package/core/built/admin/assets/announcement-bar-modal-Dwd0EGFP.js +0 -1
- package/core/built/admin/assets/api-keys-BoeacM-s.js +0 -1
- package/core/built/admin/assets/automated-emails-BLesE41L.js +0 -1
- package/core/built/admin/assets/automations-BN4u3GR8.js +0 -1
- package/core/built/admin/assets/automations-C-ZTUwW3.js +0 -1
- package/core/built/admin/assets/avatar-flipboard-C6Kj3Awu.js +0 -1
- package/core/built/admin/assets/bluesky-sharing-vzh-oPXL.js +0 -1
- package/core/built/admin/assets/box-CdfQAP_F.js +0 -1
- package/core/built/admin/assets/brand-icon-CrIGOA3Z.js +0 -1
- package/core/built/admin/assets/breadcrumb-CaLk2b7D.js +0 -1
- package/core/built/admin/assets/build-CsiH26uS.js +0 -1
- package/core/built/admin/assets/button-Dvcv1QSR.js +0 -1
- package/core/built/admin/assets/calendar-Bl9RLcZX.js +0 -1
- package/core/built/admin/assets/card--VNbfgo3.js +0 -1
- package/core/built/admin/assets/chart-BrhA41Zi.js +0 -63
- package/core/built/admin/assets/chart-helpers-FHywU0pj.js +0 -1
- package/core/built/admin/assets/checkbox-BtayFtjg.js +0 -1
- package/core/built/admin/assets/chunk.524.e6975095fccdf22d90f8.js +0 -32
- package/core/built/admin/assets/chunk.582.98b485f9b2802f540897.js +0 -34
- package/core/built/admin/assets/code-editor-view-C3ZfoF1X.js +0 -1
- package/core/built/admin/assets/code-editor-view-CAyRWy1s.js +0 -1
- package/core/built/admin/assets/color-picker-G4kGR7rp.js +0 -3
- package/core/built/admin/assets/color-picker-field-DQFVTNnT.js +0 -1
- package/core/built/admin/assets/combobox-BnYzgEB4.js +0 -1
- package/core/built/admin/assets/command-C_vyTikI.js +0 -1
- package/core/built/admin/assets/comments-aBrxfnZf.js +0 -1
- package/core/built/admin/assets/components-CwL6a6As-B0E0-NBJ.js +0 -1
- package/core/built/admin/assets/confirmation-context-DwXX_Y5h.js +0 -1
- package/core/built/admin/assets/container-BuQG1wuP.js +0 -1
- package/core/built/admin/assets/content-api-modal-D2G0nJP6.js +0 -1
- package/core/built/admin/assets/context-menu-DOVB6yaz.js +0 -1
- package/core/built/admin/assets/copy-field-DCJwh9qf.js +0 -1
- package/core/built/admin/assets/create-ghost-browse-value-source-5C53jJaX.js +0 -1
- package/core/built/admin/assets/custom-integration-modal-AbJGVyKc.js +0 -1
- package/core/built/admin/assets/data-list-CA2jYGPq.js +0 -1
- package/core/built/admin/assets/date-range-select-BY3K-ukj.js +0 -1
- package/core/built/admin/assets/design-and-theme-modal-BvEy4_pC.js +0 -2
- package/core/built/admin/assets/detail-page-Dnv-CdR1.js +0 -1
- package/core/built/admin/assets/dirty-confirm-dialog-DVPuGz3U.js +0 -1
- package/core/built/admin/assets/disabled-sources-indicator-Cy7Zp4c8.js +0 -1
- package/core/built/admin/assets/dist-4m6pHdc0.js +0 -23
- package/core/built/admin/assets/dist-8YJUlf3B.js +0 -1
- package/core/built/admin/assets/dist-CZgIBXQN.js +0 -1
- package/core/built/admin/assets/dist-CiHx7vA5.js +0 -6
- package/core/built/admin/assets/dist-DIC8yz5C.js +0 -1
- package/core/built/admin/assets/dist-Dcsslyo72.js +0 -1
- package/core/built/admin/assets/dist-Dgt2cr6a.js +0 -1
- package/core/built/admin/assets/dist-ECAUrdGc.js +0 -1
- package/core/built/admin/assets/dist-JLOy8o0Q.js +0 -1
- package/core/built/admin/assets/dist-Ndy-PuGN.js +0 -1
- package/core/built/admin/assets/dist-Z97pCNqS.js +0 -11
- package/core/built/admin/assets/domain-Dl61QGuk.js +0 -1
- package/core/built/admin/assets/dropzone-Dtz5Rmjt.js +0 -5
- package/core/built/admin/assets/edit-profile-DNJSMDwm.js +0 -1
- package/core/built/admin/assets/editor-DbAen9zf.js +0 -8
- package/core/built/admin/assets/editor-screen-BFWThIai.js +0 -26
- package/core/built/admin/assets/email-sending-status-context-CWDOD1FN.js +0 -1
- package/core/built/admin/assets/email-sending-status-provider-DCPkDICt.js +0 -1
- package/core/built/admin/assets/embed-signup-form-modal-DGqsORxS.js +0 -8
- package/core/built/admin/assets/empty-indicator-D9QdpuNX.js +0 -1
- package/core/built/admin/assets/en-B1jX3WMs.js +0 -1
- package/core/built/admin/assets/esm-CXo5SEPy.js +0 -2
- package/core/built/admin/assets/explore-nnRr1Fja.js +0 -1
- package/core/built/admin/assets/feature-flag-overrides-context-DQjdJFAk.js +0 -1
- package/core/built/admin/assets/feature-image-placeholder-BrIHmPs8.js +0 -1
- package/core/built/admin/assets/feed-BNUcNu-W.js +0 -1
- package/core/built/admin/assets/feedback-DJ4Zb9rZ.js +0 -1
- package/core/built/admin/assets/field-C93_Mg6j.js +0 -1
- package/core/built/admin/assets/filter-bar-Da1qxybu.js +0 -1
- package/core/built/admin/assets/filter-sources-CGJb4C5r.js +0 -1
- package/core/built/admin/assets/filters--kKP-EC8.js +0 -1
- package/core/built/admin/assets/filters-DxA_a4FP.js +0 -1
- package/core/built/admin/assets/first-promoter-modal-BYDtrz2S.js +0 -1
- package/core/built/admin/assets/focus-koenig-editor-on-bottom-click-BbSnGOtK.js +0 -1
- package/core/built/admin/assets/framework-provider-DqI25epG.js +0 -4
- package/core/built/admin/assets/gift-link-modal-B7u5bD4e.js +0 -1
- package/core/built/admin/assets/global-data-context-gAMDH9u1.js +0 -1
- package/core/built/admin/assets/grid-Dj29PY05.js +0 -1
- package/core/built/admin/assets/growth-Cd1uO9d2.js +0 -1
- package/core/built/admin/assets/growth-aG32xfdE.js +0 -3
- package/core/built/admin/assets/heading-Dh5SFVjL.js +0 -1
- package/core/built/admin/assets/history-modal-Df8lW8h-.js +0 -1
- package/core/built/admin/assets/hooks-Cy_-oZ4o.js +0 -2
- package/core/built/admin/assets/html-field-DA4eU7eq.js +0 -1
- package/core/built/admin/assets/iframe-buffering-B2OKJzbz.js +0 -1
- package/core/built/admin/assets/images-KXmyO3Ss.js +0 -1
- package/core/built/admin/assets/index-BHHXtR5C.css +0 -2
- package/core/built/admin/assets/index-CgMCwsxN.js +0 -4
- package/core/built/admin/assets/index.es-CY6-yuw9.js +0 -1
- package/core/built/admin/assets/inline-BWSs_VzR.js +0 -1
- package/core/built/admin/assets/input-group-BE8rM2y1.js +0 -1
- package/core/built/admin/assets/input-surface-BsrIZeSF.js +0 -1
- package/core/built/admin/assets/integration-header-Bm6IDb1A.js +0 -1
- package/core/built/admin/assets/invite-user-modal-B4CuNcx4.js +0 -1
- package/core/built/admin/assets/koenig-loader-BeM5rFPX.js +0 -2
- package/core/built/admin/assets/kpi-card-B21aToe8.js +0 -1
- package/core/built/admin/assets/kpi-card-pz6Wv4Ae.js +0 -1
- package/core/built/admin/assets/kpi-helpers-Dq5jelmP.js +0 -1
- package/core/built/admin/assets/kpi-tabs-DEZHpKUt.js +0 -1
- package/core/built/admin/assets/label-OLCzze8N.js +0 -1
- package/core/built/admin/assets/label-picker-BigOvMPV.js +0 -1
- package/core/built/admin/assets/labels-Dr0SgUQD.js +0 -1
- package/core/built/admin/assets/layout-DUTL8SH5.js +0 -1
- package/core/built/admin/assets/lib-7QPTI0FS.js +0 -1
- package/core/built/admin/assets/limit-service-BhiEvqcP.js +0 -20
- package/core/built/admin/assets/link-helpers-BRAg9SCj.js +0 -1
- package/core/built/admin/assets/links-BDHPTlyj.js +0 -1
- package/core/built/admin/assets/list-page-BucXJ0hi.js +0 -1
- package/core/built/admin/assets/luxon-D1RVjjpq.js +0 -1
- package/core/built/admin/assets/member-custom-fields-TRTHCAz9.js +0 -1
- package/core/built/admin/assets/member-detail-b6dRwc3C.js +0 -1
- package/core/built/admin/assets/members-Czt5lJSc.js +0 -4
- package/core/built/admin/assets/modal-page-xCivTMvI.js +0 -1
- package/core/built/admin/assets/moderation-ZWefD9uH.js +0 -1
- package/core/built/admin/assets/multi-select-combobox-CT13MTo-.js +0 -1
- package/core/built/admin/assets/navigation-modal-C8FEhBK5.js +0 -1
- package/core/built/admin/assets/newsletter-5VYW4RyJ.js +0 -1
- package/core/built/admin/assets/newsletter-detail-modal-ChjC0ZqH.js +0 -1
- package/core/built/admin/assets/newsletter-emails-DZH6I-PY.js +0 -1
- package/core/built/admin/assets/newsletters-DOC9KBcX.js +0 -1
- package/core/built/admin/assets/note-DIV-Pojr.js +0 -1
- package/core/built/admin/assets/notifications-s20-H49V.js +0 -1
- package/core/built/admin/assets/nql-lang-CaWrLesS.js +0 -19
- package/core/built/admin/assets/offers-container-modal-BmG5M5Tv.js +0 -1
- package/core/built/admin/assets/onboarding-CUuy9unR.js +0 -1
- package/core/built/admin/assets/onboarding-pXyct0Jm.js +0 -1
- package/core/built/admin/assets/onboarding-route-CK4pcXoR.js +0 -1
- package/core/built/admin/assets/overview-CaPsciMs.js +0 -1
- package/core/built/admin/assets/overview-a8GfcsKX.js +0 -1
- package/core/built/admin/assets/page-header-DM_KyoFI.js +0 -1
- package/core/built/admin/assets/pagemenu-vgZVUVKR.js +0 -1
- package/core/built/admin/assets/pages-route-BzKZYdV5.js +0 -1
- package/core/built/admin/assets/papaparse.min-cQwnxGCG.js +0 -10
- package/core/built/admin/assets/pintura-modal-DExCF1ii.js +0 -1
- package/core/built/admin/assets/portal-frame-Cqanidq4.js +0 -1
- package/core/built/admin/assets/portal-modal-oinBgA27.js +0 -1
- package/core/built/admin/assets/post-analytics-C5oJ1dWV.js +0 -1
- package/core/built/admin/assets/post-analytics-context-D0Mw-Pmv.js +0 -1
- package/core/built/admin/assets/post-analytics-header-OATo3X_r.js +0 -1
- package/core/built/admin/assets/post-analytics-provider-tOpIVGT5.js +0 -1
- package/core/built/admin/assets/post-share-modal-DvhC1XgV.js +0 -1
- package/core/built/admin/assets/posts-list-screen-o4ZjbgS3.js +0 -2
- package/core/built/admin/assets/posts-route-9F--xdIb.js +0 -1
- package/core/built/admin/assets/preferences--vNubsqD.js +0 -5
- package/core/built/admin/assets/preview-chrome-BljUBlOk.js +0 -1
- package/core/built/admin/assets/preview-modal-YYbzlYdO.js +0 -1
- package/core/built/admin/assets/profile-BllKV3dR.js +0 -1
- package/core/built/admin/assets/prop-types-DeUPn3oD.js +0 -1
- package/core/built/admin/assets/radio-group-DL8GI8rt.js +0 -1
- package/core/built/admin/assets/react-Bb3IsNbi.js +0 -1
- package/core/built/admin/assets/rolldown-runtime-DAXXjFlN.js +0 -1
- package/core/built/admin/assets/router-provider-BSvTXgZY.js +0 -1
- package/core/built/admin/assets/select-v5YbA3Mw.js +0 -1
- package/core/built/admin/assets/settings-PEsZupw1.js +0 -4
- package/core/built/admin/assets/settings-breadcrumbs-NwPnCTC7.js +0 -1
- package/core/built/admin/assets/settings-u_s1CRkr.js +0 -1
- package/core/built/admin/assets/shade-app-CcS3f8MZ-vwURazk8.js +0 -3
- package/core/built/admin/assets/share-modal-SskCmVGL.js +0 -1
- package/core/built/admin/assets/skeleton-Ce9GgUpf.js +0 -1
- package/core/built/admin/assets/slack-modal-XxSJiYgo.js +0 -1
- package/core/built/admin/assets/sort-button-Cx1KtINE.js +0 -1
- package/core/built/admin/assets/source-icon-Bdp9Im_5.js +0 -1
- package/core/built/admin/assets/stack-B2eszIiq.js +0 -1
- package/core/built/admin/assets/stats-view-W2g6Jsd-.js +0 -1
- package/core/built/admin/assets/step-1-BQYzvBzr.js +0 -1
- package/core/built/admin/assets/step-2-xES32xJV.js +0 -1
- package/core/built/admin/assets/step-3-DA_XK5kW.js +0 -2
- package/core/built/admin/assets/stripe-button-CVsLHelt.js +0 -1
- package/core/built/admin/assets/stripe-connect-modal-ClmLOMGS.js +0 -1
- package/core/built/admin/assets/switch-CI4gLWqb.js +0 -1
- package/core/built/admin/assets/table-BenqVupI.js +0 -1
- package/core/built/admin/assets/tabs-eBBlxPp8.js +0 -1
- package/core/built/admin/assets/tag-detail-Ce8Se2qd.js +0 -2
- package/core/built/admin/assets/tag-picker-C3UR-uJ-.js +0 -1
- package/core/built/admin/assets/tags-B-kJxCs7.js +0 -1
- package/core/built/admin/assets/testimonials-modal-9Q6fT06V.js +0 -1
- package/core/built/admin/assets/text-BQDSDsKs.js +0 -1
- package/core/built/admin/assets/textarea-DJ1Q5V39.js +0 -1
- package/core/built/admin/assets/tier-detail-modal-CDyYdqK7.js +0 -1
- package/core/built/admin/assets/toggle-group-CviIkyok.js +0 -1
- package/core/built/admin/assets/topic-filter-I2jCBGxm.js +0 -1
- package/core/built/admin/assets/transistor-modal-p53u3W8y.js +0 -1
- package/core/built/admin/assets/trend-badge-CideVzQd.js +0 -1
- package/core/built/admin/assets/types-uYQ1HgqL.js +0 -1
- package/core/built/admin/assets/unsplash-modal-DY8UcKcE.js +0 -1
- package/core/built/admin/assets/use-analytics-data-DAIGcr2f.js +0 -1
- package/core/built/admin/assets/use-filter-params-JkZhIS8L.js +0 -1
- package/core/built/admin/assets/use-filterable-api-BVEVMrhZ.js +0 -1
- package/core/built/admin/assets/use-form-vRjI0Kwk.js +0 -1
- package/core/built/admin/assets/use-growth-stats-937CUj6j.js +0 -1
- package/core/built/admin/assets/use-host-limits-D0Dv3tKA.js +0 -1
- package/core/built/admin/assets/use-limiter-CohWfzvK.js +0 -2
- package/core/built/admin/assets/use-navigate-with-base-path-BkxcPj5l.js +0 -1
- package/core/built/admin/assets/use-pintura-config-xgytjU35.js +0 -1
- package/core/built/admin/assets/use-pintura-editor-CaV1mbfO.js +0 -1
- package/core/built/admin/assets/use-post-referrers-BCQI7fDg.js +0 -1
- package/core/built/admin/assets/use-query-params-BfzuqbcD.js +0 -1
- package/core/built/admin/assets/use-reply-chain-data-CYRhoaxY.js +0 -1
- package/core/built/admin/assets/use-router-history-entry-NOKcd--l.js +0 -1
- package/core/built/admin/assets/use-setting-group-bLKMwoBT.js +0 -1
- package/core/built/admin/assets/use-settings-navigation-B-qrriKD.js +0 -1
- package/core/built/admin/assets/use-simple-pagination-CNPd6Rf6.js +0 -1
- package/core/built/admin/assets/use-staff-users-pRIMIyiW.js +0 -1
- package/core/built/admin/assets/use-tinybird-query-CmdbCS9X.js +0 -1
- package/core/built/admin/assets/use-tinybird-token-KLne235s.js +0 -1
- package/core/built/admin/assets/use-unsaved-changes-guard-7-ug0JdH.js +0 -1
- package/core/built/admin/assets/use-upgrade-route-Dm_qqKr0.js +0 -1
- package/core/built/admin/assets/user-detail-modal-D3KlGYv-.js +0 -1
- package/core/built/admin/assets/virtual-list-CZ3RK2Yf.js +0 -1
- package/core/built/admin/assets/web-ClEJsCAj.js +0 -2
- package/core/built/admin/assets/web-X9rdnrtq.js +0 -1
- package/core/built/admin/assets/zapier-modal-DfVhV17j.js +0 -1
- package/core/frontend/services/sitemap/utils.js +0 -17
- package/core/server/services/automations/get-scheduler-idempotency-key.js +0 -13
- package/core/server/web/api/testmode/index.js +0 -1
- package/core/server/web/api/testmode/jobs/cpu-hog.js +0 -15
- package/core/server/web/api/testmode/jobs/graceful-job.js +0 -50
- package/core/server/web/api/testmode/jobs/say-hello.js +0 -17
- package/core/server/web/api/testmode/routes.js +0 -120
- /package/core/built/admin/assets/{chunk.901.4ccc639821020230bcca.js.LICENSE.txt → chunk.972.4b8c97f93d66db15e751.js.LICENSE.txt} +0 -0
|
@@ -47,10 +47,11 @@ function randomFiveMinuteCron() {
|
|
|
47
47
|
const minutes = Math.floor(Math.random() * 5); // 0-4
|
|
48
48
|
return `${seconds} ${minutes}/5 * * * *`;
|
|
49
49
|
}
|
|
50
|
+
function thirtyDaysAgo() {
|
|
51
|
+
return moment_1.default.utc().subtract(30, 'days').toDate();
|
|
52
|
+
}
|
|
50
53
|
class EmailAnalyticsJobScheduler {
|
|
51
|
-
#
|
|
52
|
-
#hasScheduledAutomationsJob = false;
|
|
53
|
-
#hasScheduledGiftDeliveriesJob = false;
|
|
54
|
+
#scheduledJobNames = new Set();
|
|
54
55
|
#models;
|
|
55
56
|
#config;
|
|
56
57
|
#jobManager;
|
|
@@ -64,76 +65,46 @@ class EmailAnalyticsJobScheduler {
|
|
|
64
65
|
this.#config.get('backgroundJobs:emailAnalytics'));
|
|
65
66
|
}
|
|
66
67
|
async scheduleRecurringNewslettersJob(skipNewsletterEmailCheck = false) {
|
|
67
|
-
if (this.#hasScheduledNewslettersJob) {
|
|
68
|
-
return;
|
|
69
|
-
}
|
|
70
|
-
if (!this.#isConfigured()) {
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
68
|
// Don't register email analytics job if we have no emails,
|
|
74
69
|
// processor usage from many sites spinning up threads can be high.
|
|
75
70
|
// Mega service will re-run this scheduling task when an email is sent
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const at = randomFiveMinuteCron();
|
|
83
|
-
logging.info(`[Background Job] email-analytics-fetch-latest scheduled at ${at}`);
|
|
84
|
-
this.#jobManager.addJob({
|
|
85
|
-
at,
|
|
86
|
-
job: path.resolve(__dirname, 'fetch-latest/index.js'),
|
|
87
|
-
name: 'email-analytics-fetch-latest',
|
|
88
|
-
});
|
|
89
|
-
this.#hasScheduledNewslettersJob = true;
|
|
90
|
-
}
|
|
71
|
+
await this.#scheduleOnce({
|
|
72
|
+
name: 'email-analytics-fetch-latest',
|
|
73
|
+
workerPath: path.resolve(__dirname, 'fetch-latest/index.js'),
|
|
74
|
+
}, skipNewsletterEmailCheck, async () => Number(await this.#models.Email.where('created_at', '>', thirtyDaysAgo())
|
|
75
|
+
.where('status', '<>', 'failed')
|
|
76
|
+
.count()) > 0);
|
|
91
77
|
}
|
|
92
78
|
async scheduleRecurringAutomationsJob(skipAutomationEmailCheck = false) {
|
|
93
|
-
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
if (!this.#isConfigured()) {
|
|
97
|
-
return;
|
|
98
|
-
}
|
|
99
|
-
const hasAutomatedEmailRecipient = skipAutomationEmailCheck ||
|
|
100
|
-
Boolean(await this.#models.AutomatedEmailRecipient.query()
|
|
101
|
-
.where('created_at', '>', moment_1.default.utc().subtract(30, 'days').toDate())
|
|
102
|
-
.whereNotNull('mailgun_message_id')
|
|
103
|
-
.first('id'));
|
|
104
|
-
if (!hasAutomatedEmailRecipient || this.#hasScheduledAutomationsJob) {
|
|
105
|
-
return;
|
|
106
|
-
}
|
|
107
|
-
const at = randomFiveMinuteCron();
|
|
108
|
-
logging.info(`[Background Job] email-analytics-automation-fetch-latest scheduled at ${at}`);
|
|
109
|
-
this.#jobManager.addJob({
|
|
110
|
-
at,
|
|
111
|
-
job: path.resolve(__dirname, 'automation-fetch-latest/index.js'),
|
|
79
|
+
await this.#scheduleOnce({
|
|
112
80
|
name: 'email-analytics-automation-fetch-latest',
|
|
113
|
-
|
|
114
|
-
this.#
|
|
81
|
+
workerPath: path.resolve(__dirname, 'automation-fetch-latest/index.js'),
|
|
82
|
+
}, skipAutomationEmailCheck, async () => Boolean(await this.#models.AutomatedEmailRecipient.query()
|
|
83
|
+
.where('created_at', '>', thirtyDaysAgo())
|
|
84
|
+
.whereNotNull('mailgun_message_id')
|
|
85
|
+
.first('id')));
|
|
115
86
|
}
|
|
116
87
|
async scheduleRecurringGiftDeliveriesJob(skipGiftDeliveryCheck = false) {
|
|
117
|
-
|
|
88
|
+
await this.#scheduleOnce({
|
|
89
|
+
name: 'email-analytics-gift-fetch-latest',
|
|
90
|
+
workerPath: path.resolve(__dirname, 'gift-fetch-latest/index.js'),
|
|
91
|
+
}, skipGiftDeliveryCheck, async () => Boolean(await this.#models.GiftDelivery.query()
|
|
92
|
+
.where('email_sent_at', '>', thirtyDaysAgo())
|
|
93
|
+
.whereNotNull('email_provider_message_id')
|
|
94
|
+
.first('id')));
|
|
95
|
+
}
|
|
96
|
+
async #scheduleOnce(job, skipRecentSendsCheck, hasRecentSends) {
|
|
97
|
+
if (this.#scheduledJobNames.has(job.name) || !this.#isConfigured()) {
|
|
118
98
|
return;
|
|
119
99
|
}
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
(await this.#models.GiftDelivery.query()
|
|
123
|
-
.where('email_sent_at', '>', moment_1.default.utc().subtract(30, 'days').toDate())
|
|
124
|
-
.whereNotNull('email_provider_message_id')
|
|
125
|
-
.first('id')));
|
|
126
|
-
if (!hasGiftDelivery || this.#hasScheduledGiftDeliveriesJob) {
|
|
100
|
+
const shouldSchedule = skipRecentSendsCheck || (await hasRecentSends());
|
|
101
|
+
if (!shouldSchedule || this.#scheduledJobNames.has(job.name)) {
|
|
127
102
|
return;
|
|
128
103
|
}
|
|
129
104
|
const at = randomFiveMinuteCron();
|
|
130
|
-
logging.info(`[Background Job]
|
|
131
|
-
this.#jobManager.addJob({
|
|
132
|
-
|
|
133
|
-
job: path.resolve(__dirname, 'gift-fetch-latest/index.js'),
|
|
134
|
-
name: 'email-analytics-gift-fetch-latest',
|
|
135
|
-
});
|
|
136
|
-
this.#hasScheduledGiftDeliveriesJob = true;
|
|
105
|
+
logging.info(`[Background Job] ${job.name} scheduled at ${at}`);
|
|
106
|
+
this.#jobManager.addJob({ at, job: job.workerPath, name: job.name });
|
|
107
|
+
this.#scheduledJobNames.add(job.name);
|
|
137
108
|
}
|
|
138
109
|
}
|
|
139
110
|
exports.EmailAnalyticsJobScheduler = EmailAnalyticsJobScheduler;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const cheerio = require('cheerio');
|
|
1
|
+
const cheerio = require('cheerio/slim');
|
|
2
2
|
const juice = require('juice');
|
|
3
3
|
const htmlToPlaintext = require('@tryghost/html-to-plaintext');
|
|
4
4
|
|
|
@@ -12,7 +12,12 @@ const finalizeHtml = (html) => {
|
|
|
12
12
|
$('figcaption').addClass('kg-card-figcaption');
|
|
13
13
|
html = $.html();
|
|
14
14
|
|
|
15
|
-
|
|
15
|
+
// resolveCSSVariables crashes on nameless declarations in user-authored style attributes
|
|
16
|
+
const juicedHtml = juice(html, {
|
|
17
|
+
inlinePseudoElements: true,
|
|
18
|
+
removeStyleTags: true,
|
|
19
|
+
resolveCSSVariables: false,
|
|
20
|
+
});
|
|
16
21
|
|
|
17
22
|
// Many email clients, like Outlook and Yahoo, [lack support for <figure>
|
|
18
23
|
// and <figcaption>][0]. To work around this, change the tags to <div>s.
|
|
@@ -4,6 +4,41 @@ Renders newsletter emails, splits them into recipient batches, and submits the
|
|
|
4
4
|
batches to the configured email provider. Domain terms live in
|
|
5
5
|
[CONTEXT.md](CONTEXT.md).
|
|
6
6
|
|
|
7
|
+
## Job lifecycle
|
|
8
|
+
|
|
9
|
+
New sends, retries, and boot recovery dispatch a data-only `SendEmailJob`
|
|
10
|
+
containing the email ID through the class-based jobs service. Boot injects
|
|
11
|
+
that service and registers `EmailService.handleSendEmailJob`, which delegates
|
|
12
|
+
to `BatchSendingService.emailJob`. The batch sender refetches the email and
|
|
13
|
+
acquires its status lock before preparing or submitting batches. See the
|
|
14
|
+
[jobs guide](../../../../../../docs/codebase/jobs.md#queues) for queue isolation
|
|
15
|
+
and concurrency limits.
|
|
16
|
+
|
|
17
|
+
`scheduleEmail()` waits for dispatch to complete, not for submission. The
|
|
18
|
+
current in-memory backend provides no durable acceptance guarantee and drops
|
|
19
|
+
dispatches after shutdown starts without rejecting them. Email status records
|
|
20
|
+
the sending outcome; a job handler returning does not imply submission, since
|
|
21
|
+
it may have skipped the send, recorded a failure, or stopped for shutdown.
|
|
22
|
+
|
|
23
|
+
Shutdown first stops workers from claiming new batches. The batch sender's
|
|
24
|
+
separate cleanup task waits for active batch preparation and submission even
|
|
25
|
+
if the jobs backend's shutdown wait times out. This tracking currently ends
|
|
26
|
+
before the final email status write. On boot, recovery reschedules eligible
|
|
27
|
+
interrupted sends within `bulkEmail:resumeMaxAgeMs` and skips batches already
|
|
28
|
+
submitted.
|
|
29
|
+
|
|
30
|
+
## Testing
|
|
31
|
+
|
|
32
|
+
Tests that publish or retry a newsletter should mock the email provider and
|
|
33
|
+
await `waitForEmailStatus(emailId)` from
|
|
34
|
+
[`test/utils/batch-email-utils.js`](../../../../test/utils/batch-email-utils.js)
|
|
35
|
+
before restoring mocks or deleting fixtures. The helper returns on either
|
|
36
|
+
`submitted` or `failed`, so assert the expected status explicitly.
|
|
37
|
+
|
|
38
|
+
A terminal email status describes the send's outcome; it does not prove that
|
|
39
|
+
every competing retry or job attempting the status lock has finished. Tests
|
|
40
|
+
which create competing attempts must also wait for those attempts to settle.
|
|
41
|
+
|
|
7
42
|
## Sending status
|
|
8
43
|
|
|
9
44
|
The sending status served by the Admin API's `emails/:id/status` endpoint is
|
|
@@ -25,6 +60,9 @@ failed email.
|
|
|
25
60
|
The rough ETA measures recent recipient throughput, including work completed by
|
|
26
61
|
concurrent workers. It stays `null` until enough timing samples are available in
|
|
27
62
|
the current phase and attempt, so short sends may finish without showing an ETA.
|
|
63
|
+
The rate uses the last minute of completions, including the interval crossing
|
|
64
|
+
the window boundary. Early estimates use the available history. Sparse sends
|
|
65
|
+
retain at least two measured intervals.
|
|
28
66
|
Progress counts update independently of the estimate.
|
|
29
67
|
|
|
30
68
|
The ETA is always `null` for failed emails and `0` once no work remains in the
|
|
@@ -2,6 +2,7 @@ const logging = require('@tryghost/logging');
|
|
|
2
2
|
const ObjectID = require('bson-objectid').default;
|
|
3
3
|
const errors = require('@tryghost/errors');
|
|
4
4
|
const tpl = require('@tryghost/tpl');
|
|
5
|
+
const SendEmailJob = require('./jobs/send-email-job').default;
|
|
5
6
|
const messages = {
|
|
6
7
|
emailErrorPartialFailure:
|
|
7
8
|
'An error occurred, and your newsletter was only partially sent. Please retry sending the remaining emails.',
|
|
@@ -17,7 +18,7 @@ const SHUTDOWN_CODE = 'BULK_EMAIL_SHUTDOWN_IN_PROGRESS';
|
|
|
17
18
|
* @typedef {import('./email-renderer')} EmailRenderer
|
|
18
19
|
* @typedef {import('./domain-warming-service').DomainWarmingService} DomainWarmingService
|
|
19
20
|
* @typedef {import('./email-renderer').MemberLike} MemberLike
|
|
20
|
-
* @typedef {
|
|
21
|
+
* @typedef {import('../jobs-service/jobs-service').JobsService} JobsService
|
|
21
22
|
* @typedef {object} Email
|
|
22
23
|
* @typedef {object} Newsletter
|
|
23
24
|
* @typedef {object} Post
|
|
@@ -188,23 +189,20 @@ class BatchSendingService {
|
|
|
188
189
|
}
|
|
189
190
|
|
|
190
191
|
/**
|
|
191
|
-
*
|
|
192
|
+
* Dispatches the job that sends the email; the job itself only proceeds if the email
|
|
193
|
+
* is pending or failed.
|
|
194
|
+
* Resolves when dispatch completes, not when the email is sent.
|
|
192
195
|
* @param {Email} email
|
|
193
|
-
* @returns {void}
|
|
196
|
+
* @returns {Promise<void>}
|
|
194
197
|
*/
|
|
195
|
-
scheduleEmail(email) {
|
|
198
|
+
async scheduleEmail(email) {
|
|
199
|
+
await this.#jobsService.dispatch(new SendEmailJob({ emailId: email.id }));
|
|
196
200
|
logging.info(`[Background Job] batch-sending-service-job queued for email ${email.id}`);
|
|
197
|
-
return this.#jobsService.addJob({
|
|
198
|
-
name: 'batch-sending-service-job',
|
|
199
|
-
job: this.emailJob.bind(this),
|
|
200
|
-
data: { emailId: email.id },
|
|
201
|
-
offloaded: false,
|
|
202
|
-
});
|
|
203
201
|
}
|
|
204
202
|
|
|
205
203
|
/**
|
|
206
|
-
*
|
|
207
|
-
* @param {{emailId: string}} data
|
|
204
|
+
* Sends an email after refetching it and acquiring its status lock.
|
|
205
|
+
* @param {{emailId: string}} data Identifier of the email to refetch and lock.
|
|
208
206
|
*/
|
|
209
207
|
async emailJob({ emailId }) {
|
|
210
208
|
logging.info(`[Background Job] batch-sending-service-job started for email ${emailId}`);
|
|
@@ -270,6 +268,7 @@ class BatchSendingService {
|
|
|
270
268
|
{ ...this.#getAfterRetryConfig(), description: `email ${emailId} -> submitted` },
|
|
271
269
|
);
|
|
272
270
|
logging.info(
|
|
271
|
+
{ system: { event: 'send_email.submitted', email_id: emailId } },
|
|
273
272
|
`[Background Job] batch-sending-service-job completed for email ${emailId} in ${Date.now() - startTime}ms`,
|
|
274
273
|
);
|
|
275
274
|
} catch (e) {
|
|
@@ -820,7 +819,7 @@ class BatchSendingService {
|
|
|
820
819
|
let succeeded = false;
|
|
821
820
|
|
|
822
821
|
try {
|
|
823
|
-
|
|
822
|
+
const members = await this.retryDb(
|
|
824
823
|
async () => {
|
|
825
824
|
const m = await this.getBatchMembers(batch.id);
|
|
826
825
|
|
|
@@ -958,7 +957,7 @@ class BatchSendingService {
|
|
|
958
957
|
* @returns {Promise<MemberLike[]>}
|
|
959
958
|
*/
|
|
960
959
|
async getBatchMembers(batchId) {
|
|
961
|
-
|
|
960
|
+
const models = await this.#models.EmailRecipient.findAll({
|
|
962
961
|
filter: `batch_id:'${batchId}'`,
|
|
963
962
|
withRelated: ['member', 'member.stripeSubscriptions', 'member.products'],
|
|
964
963
|
});
|
|
@@ -65,6 +65,19 @@ function escapeHtml(unsafe) {
|
|
|
65
65
|
.replace(/'/g, ''');
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
+
/**
|
|
69
|
+
* @param {string} html
|
|
70
|
+
* @returns {string}
|
|
71
|
+
*/
|
|
72
|
+
function fixOutlookChars(html) {
|
|
73
|
+
return html
|
|
74
|
+
.replace(/'/g, ''')
|
|
75
|
+
.replace(/→/g, '→')
|
|
76
|
+
.replace(/–/g, '–')
|
|
77
|
+
.replace(/“/g, '“')
|
|
78
|
+
.replace(/”/g, '”');
|
|
79
|
+
}
|
|
80
|
+
|
|
68
81
|
/**
|
|
69
82
|
* @param {string} locale
|
|
70
83
|
* @returns {boolean}
|
|
@@ -192,7 +205,7 @@ function escapeRegExp(string) {
|
|
|
192
205
|
* @return {ReturnType<typeof cheerio.load>}
|
|
193
206
|
*/
|
|
194
207
|
function cheerioLoad(html) {
|
|
195
|
-
const cheerio = require('cheerio');
|
|
208
|
+
const cheerio = require('cheerio/slim');
|
|
196
209
|
return cheerio.load(html);
|
|
197
210
|
}
|
|
198
211
|
|
|
@@ -709,7 +722,12 @@ class EmailRenderer {
|
|
|
709
722
|
|
|
710
723
|
// Juice HTML (inline CSS)
|
|
711
724
|
const juice = require('juice');
|
|
712
|
-
|
|
725
|
+
// resolveCSSVariables crashes on nameless declarations in user-authored style attributes
|
|
726
|
+
html = juice(html, {
|
|
727
|
+
inlinePseudoElements: true,
|
|
728
|
+
removeStyleTags: true,
|
|
729
|
+
resolveCSSVariables: false,
|
|
730
|
+
});
|
|
713
731
|
|
|
714
732
|
// happens after inlining of CSS so we can change element types without worrying about styling
|
|
715
733
|
$ = cheerioLoad(html);
|
|
@@ -749,23 +767,52 @@ class EmailRenderer {
|
|
|
749
767
|
// Convert DOM back to HTML
|
|
750
768
|
html = $.html(); // () Fix for vscode syntax highlighter
|
|
751
769
|
|
|
770
|
+
// Personalize the entire name row after CSS inlining so an absent name also
|
|
771
|
+
// removes its label and markup, including in clients that discard stylesheets.
|
|
772
|
+
const nameRow = $('.subscription-details .subscription-name');
|
|
773
|
+
const nameHtml = nameRow.length ? $.html(nameRow) : '';
|
|
774
|
+
const nameText = nameRow.text();
|
|
775
|
+
const plaintextHtml = nameHtml
|
|
776
|
+
? html.replace(nameHtml, '<p>%%{subscription_name_text}%%</p>')
|
|
777
|
+
: html;
|
|
778
|
+
if (nameHtml) {
|
|
779
|
+
html = html.replace(nameHtml, '%%{subscription_name_html}%%');
|
|
780
|
+
}
|
|
781
|
+
|
|
752
782
|
// Replacement strings
|
|
753
783
|
const replacementDefinitions = this.buildReplacementDefinitions({
|
|
754
784
|
html,
|
|
755
785
|
newsletterUuid: newsletter.get('uuid'),
|
|
756
786
|
});
|
|
757
787
|
|
|
788
|
+
if (nameHtml) {
|
|
789
|
+
const outlookNameHtml = fixOutlookChars(nameHtml);
|
|
790
|
+
replacementDefinitions.push(
|
|
791
|
+
{
|
|
792
|
+
id: 'subscription_name_html',
|
|
793
|
+
token: /%%\{subscription_name_html\}%%/g,
|
|
794
|
+
getValue: (member) =>
|
|
795
|
+
member.name?.trim()
|
|
796
|
+
? outlookNameHtml.replace('%%{name}%%', () => escapeHtml(member.name))
|
|
797
|
+
: '',
|
|
798
|
+
trusted: true, // Member name is already HTML-escaped
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
id: 'subscription_name_text',
|
|
802
|
+
token: /%%\{subscription_name_text\}%%/g,
|
|
803
|
+
getValue: (member) =>
|
|
804
|
+
member.name?.trim() ? nameText.replace('%%{name}%%', () => member.name) : '',
|
|
805
|
+
},
|
|
806
|
+
);
|
|
807
|
+
}
|
|
808
|
+
|
|
758
809
|
// TODO: normalizeReplacementStrings (replace unsupported replacement strings)
|
|
759
810
|
|
|
760
811
|
// Convert HTML to plaintext
|
|
761
|
-
const plaintext = htmlToPlaintext.email(
|
|
812
|
+
const plaintext = htmlToPlaintext.email(plaintextHtml);
|
|
762
813
|
|
|
763
814
|
// Fix any unsupported chars in Outlook
|
|
764
|
-
html = html
|
|
765
|
-
html = html.replace(/→/g, '→');
|
|
766
|
-
html = html.replace(/–/g, '–');
|
|
767
|
-
html = html.replace(/“/g, '“');
|
|
768
|
-
html = html.replace(/”/g, '”');
|
|
815
|
+
html = fixOutlookChars(html);
|
|
769
816
|
|
|
770
817
|
return {
|
|
771
818
|
html,
|
|
@@ -808,7 +855,7 @@ class EmailRenderer {
|
|
|
808
855
|
isMemberTrialing(member) {
|
|
809
856
|
// Do we have an active subscription?
|
|
810
857
|
if (member.status === 'paid') {
|
|
811
|
-
|
|
858
|
+
const activeSubscription = member.subscriptions.find((subscription) => {
|
|
812
859
|
return subscription.status === 'trialing';
|
|
813
860
|
});
|
|
814
861
|
|
|
@@ -960,12 +1007,6 @@ class EmailRenderer {
|
|
|
960
1007
|
return member.name;
|
|
961
1008
|
},
|
|
962
1009
|
},
|
|
963
|
-
{
|
|
964
|
-
id: 'name_class',
|
|
965
|
-
getValue: (member) => {
|
|
966
|
-
return member.name ? '' : 'hidden';
|
|
967
|
-
},
|
|
968
|
-
},
|
|
969
1010
|
{
|
|
970
1011
|
id: 'email',
|
|
971
1012
|
getValue: (member) => {
|
|
@@ -1178,7 +1219,7 @@ class EmailRenderer {
|
|
|
1178
1219
|
*/
|
|
1179
1220
|
#getEmailPreheader(postModel, audience, html) {
|
|
1180
1221
|
let plaintext = postModel.get('plaintext');
|
|
1181
|
-
|
|
1222
|
+
const customExcerpt = postModel.get('custom_excerpt');
|
|
1182
1223
|
if (customExcerpt) {
|
|
1183
1224
|
return customExcerpt;
|
|
1184
1225
|
} else {
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
const assert = require('node:assert/strict');
|
|
1
2
|
const debug = require('@tryghost/debug')('i18n');
|
|
2
3
|
const url = require('../../api/endpoints/utils/serializers/output/utils/url');
|
|
3
4
|
const events = require('../../lib/common/events');
|
|
@@ -14,10 +15,11 @@ class EmailServiceWrapper {
|
|
|
14
15
|
return jsonModel.url;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
|
-
init({ ghostServer } = {}) {
|
|
18
|
+
init({ ghostServer, jobsService } = {}) {
|
|
18
19
|
if (this.service) {
|
|
19
20
|
return;
|
|
20
21
|
}
|
|
22
|
+
assert(jobsService, 'Email service requires the jobs service');
|
|
21
23
|
|
|
22
24
|
const EmailService = require('./email-service');
|
|
23
25
|
const EmailController = require('./email-controller');
|
|
@@ -36,7 +38,6 @@ class EmailServiceWrapper {
|
|
|
36
38
|
const configService = require('../../../shared/config');
|
|
37
39
|
const settingsCache = require('../../../shared/settings-cache');
|
|
38
40
|
const settingsHelpers = require('../settings-helpers');
|
|
39
|
-
const jobsService = require('../jobs');
|
|
40
41
|
const membersService = require('../members');
|
|
41
42
|
const db = require('../../data/db');
|
|
42
43
|
const sentry = require('../../../shared/sentry');
|
|
@@ -44,7 +45,7 @@ class EmailServiceWrapper {
|
|
|
44
45
|
const limitService = require('../limits');
|
|
45
46
|
const labs = require('../../../shared/labs');
|
|
46
47
|
const emailAddressService = require('../email-address');
|
|
47
|
-
const i18nLib = require('@tryghost/i18n');
|
|
48
|
+
const i18nLib = require('@tryghost/i18n').default;
|
|
48
49
|
const lexicalLib = require('../../lib/lexical');
|
|
49
50
|
const urlUtils = require('../../../shared/url-utils').default;
|
|
50
51
|
const memberAttribution = require('../member-attribution');
|
|
@@ -29,12 +29,15 @@ const messages = {
|
|
|
29
29
|
retryEmailNotFailed: 'Only failed emails can be retried',
|
|
30
30
|
};
|
|
31
31
|
|
|
32
|
-
// Resume scanner won't pick up `submitting` rows older than this. Rows beyond
|
|
33
|
-
// are flipped to `failed` on first boot so they surface in admin UI for operator
|
|
34
|
-
// rather than being silently resumed (and sending stale newsletters to current members).
|
|
32
|
+
// Resume scanner won't pick up `pending` or `submitting` rows older than this. Rows beyond
|
|
33
|
+
// the cutoff are flipped to `failed` on first boot so they surface in admin UI for operator
|
|
34
|
+
// review rather than being silently resumed (and sending stale newsletters to current members).
|
|
35
35
|
// Override via `bulkEmail:resumeMaxAgeMs` in config.
|
|
36
36
|
const DEFAULT_RESUME_MAX_AGE_MS = 24 * 60 * 60 * 1000;
|
|
37
37
|
|
|
38
|
+
// Non-terminal email statuses, both of which the boot scanner recovers.
|
|
39
|
+
const RESUMABLE_EMAIL_STATUSES = ['pending', 'submitting'];
|
|
40
|
+
|
|
38
41
|
class EmailService {
|
|
39
42
|
#batchSendingService;
|
|
40
43
|
#sendingService;
|
|
@@ -201,7 +204,7 @@ class EmailService {
|
|
|
201
204
|
});
|
|
202
205
|
|
|
203
206
|
try {
|
|
204
|
-
this.#batchSendingService.scheduleEmail(email);
|
|
207
|
+
await this.#batchSendingService.scheduleEmail(email);
|
|
205
208
|
} catch (e) {
|
|
206
209
|
await email.save(
|
|
207
210
|
{
|
|
@@ -223,28 +226,31 @@ class EmailService {
|
|
|
223
226
|
}
|
|
224
227
|
|
|
225
228
|
/**
|
|
226
|
-
* Boot-time scanner:
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
*
|
|
229
|
+
* Boot-time scanner: recovers newsletter emails a previous container left without work.
|
|
230
|
+
* Two shapes, both resumed through the normal emailJob path:
|
|
231
|
+
* - `submitting` — the send was cut off mid-flight.
|
|
232
|
+
* - `pending` — dispatch was dropped from the in-memory job queue before the job ever
|
|
233
|
+
* started, so no batch work exists and nothing else will ever pick the row up.
|
|
234
|
+
* Iterates sequentially; one failure does not skip others. Rows older than the configured
|
|
235
|
+
* max-age are flipped to `failed` (not resumed) so stale content does not get sent to
|
|
236
|
+
* current members.
|
|
230
237
|
*/
|
|
231
238
|
async resumeInterruptedSends() {
|
|
232
239
|
const maxAgeMs = this.#config?.get?.('bulkEmail:resumeMaxAgeMs') ?? DEFAULT_RESUME_MAX_AGE_MS;
|
|
233
240
|
const cutoffIso = new Date(Date.now() - maxAgeMs).toISOString();
|
|
234
241
|
|
|
235
242
|
// Stale rows: too old to safely resume. Flip to `failed` so they surface in admin UI
|
|
236
|
-
// for operator review instead of being silently
|
|
243
|
+
// for operator review instead of being silently stuck in their current status forever.
|
|
237
244
|
const stale = await this.#models.Email.findAll({
|
|
238
|
-
filter: `status:
|
|
245
|
+
filter: `status:[${RESUMABLE_EMAIL_STATUSES.join(',')}]+created_at:<'${cutoffIso}'`,
|
|
239
246
|
});
|
|
240
|
-
const
|
|
241
|
-
for (const email of staleList) {
|
|
247
|
+
for (const email of stale.models) {
|
|
242
248
|
try {
|
|
243
249
|
const locked = await this.#batchSendingService.updateStatusLock(
|
|
244
250
|
this.#models.Email,
|
|
245
251
|
email.id,
|
|
246
252
|
'failed',
|
|
247
|
-
['
|
|
253
|
+
[email.get('status')],
|
|
248
254
|
);
|
|
249
255
|
if (locked) {
|
|
250
256
|
logging.warn(
|
|
@@ -257,20 +263,19 @@ class EmailService {
|
|
|
257
263
|
}
|
|
258
264
|
|
|
259
265
|
// Fresh rows: within the cutoff. Resume through the normal emailJob path.
|
|
260
|
-
const
|
|
261
|
-
filter: `status:
|
|
266
|
+
const fresh = await this.#models.Email.findAll({
|
|
267
|
+
filter: `status:[${RESUMABLE_EMAIL_STATUSES.join(',')}]+created_at:>'${cutoffIso}'`,
|
|
262
268
|
});
|
|
263
|
-
|
|
264
|
-
if (staleList.length === 0 && list.length === 0) {
|
|
269
|
+
if (stale.models.length === 0 && fresh.models.length === 0) {
|
|
265
270
|
return;
|
|
266
271
|
}
|
|
267
|
-
if (
|
|
272
|
+
if (fresh.models.length > 0) {
|
|
268
273
|
logging.info(
|
|
269
|
-
`Email resume: found ${
|
|
274
|
+
`Email resume: found ${fresh.models.length} pending or submitting email(s) within max age (${maxAgeMs}ms)`,
|
|
270
275
|
);
|
|
271
276
|
}
|
|
272
277
|
|
|
273
|
-
for (const email of
|
|
278
|
+
for (const email of fresh.models) {
|
|
274
279
|
try {
|
|
275
280
|
await this.#resumeOneEmail(email);
|
|
276
281
|
} catch (e) {
|
|
@@ -279,23 +284,24 @@ class EmailService {
|
|
|
279
284
|
}
|
|
280
285
|
|
|
281
286
|
logging.info(
|
|
282
|
-
`Email resume scan complete: ${
|
|
287
|
+
`Email resume scan complete: ${stale.models.length} stale email(s) flipped to failed, ${fresh.models.length} fresh email(s) rescheduled`,
|
|
283
288
|
);
|
|
284
289
|
}
|
|
285
290
|
|
|
286
291
|
async #resumeOneEmail(email) {
|
|
292
|
+
const status = email.get('status');
|
|
287
293
|
const post = await email.getLazyRelation('post');
|
|
288
294
|
const postStatus = post ? post.get('status') : null;
|
|
289
295
|
const sendable = postStatus === 'published' || postStatus === 'sent';
|
|
290
296
|
|
|
291
297
|
if (!sendable) {
|
|
292
298
|
// Parent post was unpublished or deleted while the email was in flight.
|
|
293
|
-
// Can't resume — mark the email as failed so it stops showing as
|
|
299
|
+
// Can't resume — mark the email as failed so it stops showing as unsent.
|
|
294
300
|
const locked = await this.#batchSendingService.updateStatusLock(
|
|
295
301
|
this.#models.Email,
|
|
296
302
|
email.id,
|
|
297
303
|
'failed',
|
|
298
|
-
[
|
|
304
|
+
[status],
|
|
299
305
|
);
|
|
300
306
|
if (locked) {
|
|
301
307
|
logging.warn(
|
|
@@ -305,19 +311,22 @@ class EmailService {
|
|
|
305
311
|
return;
|
|
306
312
|
}
|
|
307
313
|
|
|
308
|
-
//
|
|
309
|
-
// If updateStatusLock returns undefined, another path
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
logging.info(
|
|
318
|
-
`Email resume: ${email.id} status changed before lock could be taken — skipping`,
|
|
314
|
+
// `pending` is already what emailJob's status lock (['pending', 'failed']) admits, so
|
|
315
|
+
// only `submitting` needs flipping. If updateStatusLock returns undefined, another path
|
|
316
|
+
// raced us — leave it to whoever won.
|
|
317
|
+
if (status === 'submitting') {
|
|
318
|
+
const locked = await this.#batchSendingService.updateStatusLock(
|
|
319
|
+
this.#models.Email,
|
|
320
|
+
email.id,
|
|
321
|
+
'pending',
|
|
322
|
+
['submitting'],
|
|
319
323
|
);
|
|
320
|
-
|
|
324
|
+
if (!locked) {
|
|
325
|
+
logging.info(
|
|
326
|
+
`Email resume: ${email.id} status changed before lock could be taken — skipping`,
|
|
327
|
+
);
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
321
330
|
}
|
|
322
331
|
|
|
323
332
|
// Structured breadcrumb so post-incident timing/batch state is recoverable from logs.
|
|
@@ -325,7 +334,7 @@ class EmailService {
|
|
|
325
334
|
logging.warn(`Email resume: scheduling ${email.id} for re-send ${JSON.stringify(breadcrumb)}`);
|
|
326
335
|
|
|
327
336
|
// Skip checkLimits — this email already passed limits when first sent.
|
|
328
|
-
this.#batchSendingService.scheduleEmail(email);
|
|
337
|
+
await this.#batchSendingService.scheduleEmail(email);
|
|
329
338
|
}
|
|
330
339
|
|
|
331
340
|
async #buildResumeBreadcrumb(email) {
|
|
@@ -383,10 +392,22 @@ class EmailService {
|
|
|
383
392
|
// so we have a immediate response when retrying an email (schedule can take a while to kick off sometimes)
|
|
384
393
|
await email.save({ status: 'pending' }, { patch: true });
|
|
385
394
|
|
|
386
|
-
|
|
395
|
+
try {
|
|
396
|
+
await this.#batchSendingService.scheduleEmail(email);
|
|
397
|
+
} catch (e) {
|
|
398
|
+
await email.save({ status: 'failed' }, { patch: true });
|
|
399
|
+
throw e;
|
|
400
|
+
}
|
|
387
401
|
return email;
|
|
388
402
|
}
|
|
389
403
|
|
|
404
|
+
/**
|
|
405
|
+
* @param {import('./jobs/send-email-job').default} job
|
|
406
|
+
*/
|
|
407
|
+
async handleSendEmailJob(job) {
|
|
408
|
+
await this.#batchSendingService.emailJob({ emailId: job.emailId });
|
|
409
|
+
}
|
|
410
|
+
|
|
390
411
|
/**
|
|
391
412
|
* @params {string|null} [audienceStatus] - the audience's free/paid status
|
|
392
413
|
* ('status:free' / 'status:-free'), see EmailRenderer#describeSegment
|
|
@@ -467,11 +488,24 @@ class EmailService {
|
|
|
467
488
|
* @return {string}
|
|
468
489
|
*/
|
|
469
490
|
replaceDefinitions(htmlOrPlaintext, replacements, member) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
htmlOrPlaintext = htmlOrPlaintext.replace(replacement.token, replacement.getValue(member));
|
|
491
|
+
if (!replacements.length) {
|
|
492
|
+
return htmlOrPlaintext;
|
|
473
493
|
}
|
|
474
|
-
|
|
494
|
+
const values = replacements.map(({ token, getValue }) => ({ token, value: getValue(member) }));
|
|
495
|
+
const tokens = new RegExp(
|
|
496
|
+
replacements.map(({ token }) => `(?:${token.source})`).join('|'),
|
|
497
|
+
'g',
|
|
498
|
+
);
|
|
499
|
+
|
|
500
|
+
// Only match the original body, never placeholders inside inserted member data.
|
|
501
|
+
return htmlOrPlaintext.replace(tokens, (match) => {
|
|
502
|
+
for (const { token, value } of values) {
|
|
503
|
+
if (match.search(token) !== -1) {
|
|
504
|
+
return value;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
return match;
|
|
508
|
+
});
|
|
475
509
|
}
|
|
476
510
|
|
|
477
511
|
/**
|
|
@@ -488,7 +522,7 @@ class EmailService {
|
|
|
488
522
|
const exampleMember = await this.getExampleMember(null, audience.status);
|
|
489
523
|
|
|
490
524
|
const subject = this.#emailRenderer.getSubject(post);
|
|
491
|
-
|
|
525
|
+
const { html, plaintext, replacements } = await this.#emailRenderer.renderBody(
|
|
492
526
|
post,
|
|
493
527
|
newsletter,
|
|
494
528
|
renderSegment,
|