ghost 6.55.0 → 6.56.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/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
- package/components/tryghost-kg-default-cards-10.3.3.tgz +0 -0
- package/components/tryghost-kg-default-nodes-2.1.4.tgz +0 -0
- package/components/tryghost-kg-default-transforms-1.3.3.tgz +0 -0
- package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
- package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
- package/components/tryghost-nql-string-0.0.0.tgz +0 -0
- package/core/built/admin/assets/{Combination-CJG7Flp-.js → Combination-BCm9FL4o.js} +1 -1
- package/core/built/admin/assets/{PolarAngleAxis-BGjf7sBu.js → PolarAngleAxis-C4qtHy1B.js} +1 -1
- package/core/built/admin/assets/{RadialBarChart-zeFf9mBz.js → RadialBarChart-Bh09Stsj.js} +1 -1
- package/core/built/admin/assets/{account-migration-sjHLx5dy.js → account-migration-D3O_yVck.js} +1 -1
- package/core/built/admin/assets/activitypub/{account-migration-DoYXtnZ8.js → account-migration-D8FbP-Z-.js} +7 -7
- package/core/built/admin/assets/activitypub/activitypub.js +6 -6
- package/core/built/admin/assets/activitypub/{avatar-B3DEDB1l.js → avatar-11EA1VSs.js} +2 -2
- package/core/built/admin/assets/activitypub/{avatar-flipboard-Cqrg0XMx.js → avatar-flipboard-jfbLG-xe.js} +2 -2
- package/core/built/admin/assets/activitypub/{bluesky-sharing-Bxeh6MKW.js → bluesky-sharing-f1jTQe2P.js} +16 -16
- package/core/built/admin/assets/activitypub/{content-formatters-BpQARh28.js → content-formatters-nmTOPY9M.js} +3 -3
- package/core/built/admin/assets/activitypub/{copy-D9UWIAan.js → copy-BCwo9ep3.js} +2 -2
- package/core/built/admin/assets/activitypub/{dist-D8sFfdpC.js → dist-BEfX-0aW.js} +12 -12
- package/core/built/admin/assets/activitypub/{domain-CWueFW6q.js → domain-AaDlZlrA.js} +9 -9
- package/core/built/admin/assets/activitypub/{edit-profile-BGxPU7N5.js → edit-profile-DPi8Tl5M.js} +945 -945
- package/core/built/admin/assets/activitypub/{explore-DBK-u0Ch.js → explore-CxTzjILQ.js} +8 -8
- package/core/built/admin/assets/activitypub/{feed-Bcir5RQZ.js → feed-DPv5x2MX.js} +33 -33
- package/core/built/admin/assets/activitypub/{field-BV-CIT3d.js → field-DbgA8lat.js} +3 -3
- package/core/built/admin/assets/activitypub/{hash-BC9Q0N4c.js → hash-BWuZoYkA.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-DQ4ZnC1m.js → inbox-35VDQ2K2.js} +2 -2
- package/core/built/admin/assets/activitypub/{inbox-yC0nfvzl.js → inbox-DSrF_i1O.js} +85 -85
- package/core/built/admin/assets/activitypub/{label-DTRS9TDk.js → label-CPjFBbo7.js} +3 -3
- package/core/built/admin/assets/activitypub/layout-CTgrezN7.js +1 -0
- package/core/built/admin/assets/activitypub/{moderation-Dwl9t0pb.js → moderation-BkDC1XVZ.js} +11 -11
- package/core/built/admin/assets/activitypub/{note-B-zKsZ7r.js → note-xEyH4xT-.js} +7 -7
- package/core/built/admin/assets/activitypub/{notifications-DjJZD54t.js → notifications-DClaL3ui.js} +9 -9
- package/core/built/admin/assets/activitypub/{preferences-CoU8vs6d.js → preferences-DpnOa-4Z.js} +20 -20
- package/core/built/admin/assets/activitypub/{profile-BAsaJekx.js → profile-Dvi9mz-6.js} +237 -237
- package/core/built/admin/assets/activitypub/{reply-CpdMeAO5.js → reply-Cs9ySd6Z.js} +2 -2
- package/core/built/admin/assets/activitypub/{routes-CLH3OVTP.js → routes-DFBeTnfn.js} +1139 -1150
- package/core/built/admin/assets/activitypub/{separator-BI1FLsYM.js → separator-C5u_VjzY.js} +3 -3
- package/core/built/admin/assets/activitypub/{settings-DA6pNJ-Q.js → settings-CV2fFu6D.js} +13 -13
- package/core/built/admin/assets/activitypub/{site-CQD42Pw5.js → site-_TgDd_EM.js} +81 -81
- package/core/built/admin/assets/activitypub/{step-1-DIDg4aHb.js → step-1-BfnNL2oF.js} +5 -5
- package/core/built/admin/assets/activitypub/{step-2-BfGe8BYt.js → step-2-DLndZkO-.js} +6 -6
- package/core/built/admin/assets/activitypub/{step-3-mE9spAqw.js → step-3-A-PZTtRE.js} +10 -10
- package/core/built/admin/assets/activitypub/{tabs-Cg0saow1.js → tabs-BUbz7tea.js} +317 -315
- package/core/built/admin/assets/activitypub/{topic-filter-DLHBZT93.js → topic-filter-BdX_xYYT.js} +3 -3
- package/core/built/admin/assets/activitypub/{use-activity-pub-queries-VzbN99u_.js → use-activity-pub-queries-EwuRemHh.js} +2 -2
- package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-CAwXTmqH.js → use-navigate-with-base-path-cy4ACBc4.js} +92 -90
- package/core/built/admin/assets/activitypub/{use-reply-chain-data-Br4aOWOh.js → use-reply-chain-data-CyFcGfan.js} +5 -5
- package/core/built/admin/assets/activitypub/{x-C8tAnXly.js → x-7zk8mV2p.js} +2 -2
- package/core/built/admin/assets/{audience-DjEWNhp3.js → audience-CkBb2LlO.js} +1 -1
- package/core/built/admin/assets/{automated-emails-DRy7qY-J.js → automated-emails-6t2bIRWf.js} +1 -1
- package/core/built/admin/assets/{automations-Bx3VcFIL.js → automations-cMllU545.js} +1 -1
- package/core/built/admin/assets/{bluesky-sharing-ClMd0-Ft.js → bluesky-sharing-DPGpyD-G.js} +1 -1
- package/core/built/admin/assets/{box-BWbhXh21.js → box-By_EgGtm.js} +1 -1
- package/core/built/admin/assets/{breadcrumb-BD0U2ygg.js → breadcrumb-DIfcF5ow.js} +1 -1
- package/core/built/admin/assets/build-CsiH26uS.js +1 -0
- package/core/built/admin/assets/{card-DGoDJzrn.js → card-aUHX0hyR.js} +1 -1
- package/core/built/admin/assets/{chart-BuRT2Y6p.js → chart-BnXyfH11.js} +1 -1
- package/core/built/admin/assets/{chart-helpers-fjQKRVyY.js → chart-helpers-DV0FkdcD.js} +1 -1
- package/core/built/admin/assets/{checkbox-CWa28lgO.js → checkbox-BIl077a0.js} +1 -1
- package/core/built/admin/assets/{chunk.481.6c7395a49484bb4583aa.js → chunk.332.05991a101d67f732dec3.js} +43 -42
- package/core/built/admin/assets/chunk.524.87b52f4fada529df2c4c.js +25 -0
- package/core/built/admin/assets/chunk.582.340bcca873e806cbd4bb.js +27 -0
- package/core/built/admin/assets/{code-editor-view-CCr0rBWS.js → code-editor-view-C0aWNwdl.js} +1 -1
- package/core/built/admin/assets/comments-qwhIjH3c.js +1 -0
- package/core/built/admin/assets/{container-BYz2Ed39.js → container-LiZf3XdY.js} +1 -1
- package/core/built/admin/assets/{data-list-16rkJYYi.js → data-list-CupF7bjM.js} +1 -1
- package/core/built/admin/assets/{date-range-select-BFp58EMk.js → date-range-select-dXc-GsVe.js} +1 -1
- package/core/built/admin/assets/{disabled-sources-indicator-CUxMfiPa.js → disabled-sources-indicator-DYAXW-6V.js} +1 -1
- package/core/built/admin/assets/dist-BUKmD51u.js +1 -0
- package/core/built/admin/assets/{dist-2Tnz2Yfq.js → dist-CNlMj7ML.js} +1 -1
- package/core/built/admin/assets/{dist-DGzPJ_bO.js → dist-Cb5RSHSg.js} +1 -1
- package/core/built/admin/assets/{dist-P_ohUlTi.js → dist-CqO2lWS0.js} +1 -1
- package/core/built/admin/assets/domain-B5ogXUBa.js +1 -0
- package/core/built/admin/assets/{dropzone-DlK-K2DQ.js → dropzone-BnFG7dDP.js} +1 -1
- package/core/built/admin/assets/{edit-profile-BKT-REJw.js → edit-profile-B07jCZy9.js} +1 -1
- package/core/built/admin/assets/editor-DDYSOXGN.js +8 -0
- package/core/built/admin/assets/{empty-indicator-oX1u1Pkn.js → empty-indicator-WB1WgT9h.js} +1 -1
- package/core/built/admin/assets/{explore-ClUpJZOp.js → explore-Ca1y8HW_.js} +1 -1
- package/core/built/admin/assets/{feed-DF92dUMP.js → feed-x4iFMBP4.js} +1 -1
- package/core/built/admin/assets/{field-Do7lieBA.js → field-ChCAabZg.js} +1 -1
- package/core/built/admin/assets/filter-sources-LdtH1e-T.js +1 -0
- package/core/built/admin/assets/filters-CLehPo_D.js +1 -0
- package/core/built/admin/assets/filters-CjVKRP8U.js +1 -0
- package/core/built/admin/assets/{ghost-c4057474943effc568c5659ccc4909d7.js → ghost-7f01131115186d1af84ead76e79646dd.js} +149 -129
- package/core/built/admin/assets/{ghost-a9c788f6f0b1ea6cf0f14ca4b13f4496.css → ghost-d38c30b866e59f2e06a357b5eb844fe5.css} +1 -1
- package/core/built/admin/assets/{ghost-dark-db36d795d21c61426837df40af270966.css → ghost-dark-1bffd5099ba7a749303133efacf8480b.css} +1 -1
- package/core/built/admin/assets/{gift-link-modal-f9VIMNDv.js → gift-link-modal-Buqh2vXh.js} +1 -1
- package/core/built/admin/assets/{growth-B3I6gNLm.js → growth-DajOBdN0.js} +1 -1
- package/core/built/admin/assets/{growth-CVw1My4S.js → growth-Dy2UOSay.js} +2 -2
- package/core/built/admin/assets/{inbox-CFBSu4Ed.js → inbox-BUOLDoji.js} +12 -12
- package/core/built/admin/assets/index-08n05-rc.js +67 -0
- package/core/built/admin/assets/index-DgUzgFvS.css +1 -0
- package/core/built/admin/assets/{inline-EMAm3okk.js → inline-Cw-gigDW.js} +1 -1
- package/core/built/admin/assets/input-group-uSK3XT7b.js +1 -0
- package/core/built/admin/assets/{input-surface-BpAjZXY5.js → input-surface-BdOe4KzO.js} +1 -1
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +9535 -9527
- package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +124 -124
- package/core/built/admin/assets/{koenig-lexical-f8vHRYvz.js → koenig-lexical-BFc4yFed.js} +86 -86
- package/core/built/admin/assets/{kpi-card-DSap9rAy.js → kpi-card-BN-E6WYX.js} +1 -1
- package/core/built/admin/assets/{kpi-card-DU0rn9Cj.js → kpi-card-CCt-qeWD.js} +1 -1
- package/core/built/admin/assets/{kpi-helpers-Dz33AV6h.js → kpi-helpers-DlxTbTs4.js} +1 -1
- package/core/built/admin/assets/{kpi-tabs-BtTTrKvJ.js → kpi-tabs-BlcUttpn.js} +1 -1
- package/core/built/admin/assets/{label-DS8Mi6G5.js → label-AVrxwmsz.js} +1 -1
- package/core/built/admin/assets/{label-picker-1DvSuLA1.js → label-picker-DVnpu_IF.js} +1 -1
- package/core/built/admin/assets/labels-DAIqKed7.js +1 -0
- package/core/built/admin/assets/layout-B0YLh7R9.js +1 -0
- package/core/built/admin/assets/link-helpers-D9LsDOkG.js +1 -0
- package/core/built/admin/assets/{list-page-B6ANtzkd.js → list-page-CdsnQR9N.js} +1 -1
- package/core/built/admin/assets/member-custom-fields-C8NpZYza.js +1 -0
- package/core/built/admin/assets/member-detail-CIVAdKXf.js +1 -0
- package/core/built/admin/assets/{members-CQpVFJu4.js → members-oLPiSkXD.js} +2 -2
- package/core/built/admin/assets/modals-CB7v-k3T.js +31 -0
- package/core/built/admin/assets/{moderation-DySbgyUS.js → moderation-CQ3urTFE.js} +1 -1
- package/core/built/admin/assets/newsletter-rbeDX9Wk.js +1 -0
- package/core/built/admin/assets/newsletters-vt3Ni1Ov.js +1 -0
- package/core/built/admin/assets/{note-C8kxrz6w.js → note-Dk877BTv.js} +1 -1
- package/core/built/admin/assets/{notifications-CgtSfX7t.js → notifications-CPlLjPxO.js} +1 -1
- package/core/built/admin/assets/{onboarding-route-4qtziA9d.js → onboarding-route-DdStOtjy.js} +1 -1
- package/core/built/admin/assets/{overview-Bf2sbznq.js → overview--4V-kx9c.js} +1 -1
- package/core/built/admin/assets/overview-DSLv30oz.js +1 -0
- package/core/built/admin/assets/{page-header-B3Wb9Jqv.js → page-header-pp4dWmOS.js} +1 -1
- package/core/built/admin/assets/{post-analytics-We-ko5Cb.js → post-analytics-BMJrI81R.js} +1 -1
- package/core/built/admin/assets/post-analytics-header-pU2m9ZuV.js +1 -0
- package/core/built/admin/assets/{post-analytics-provider-B6UynTGb.js → post-analytics-provider-Btml5vJI.js} +1 -1
- package/core/built/admin/assets/{post-share-modal-DFfO93_0.js → post-share-modal-AcUZkbfL.js} +1 -1
- package/core/built/admin/assets/preferences-Ru4CRPHC.js +5 -0
- package/core/built/admin/assets/profile-BNb1oj7D.js +1 -0
- package/core/built/admin/assets/{router-provider-C1R4AZ2o.js → router-provider-BmEZf6c6.js} +1 -1
- package/core/built/admin/assets/select-B0HteHrp.js +1 -0
- package/core/built/admin/assets/settings-B1C9Q3wo.js +4 -0
- package/core/built/admin/assets/{settings-WXNeWyU1.js → settings-BGgk7oCI.js} +1 -1
- package/core/built/admin/assets/{shade-app-B3heOAfz-CGXQwWeO.js → shade-app-B3heOAfz-BhQXxNkT.js} +1 -1
- package/core/built/admin/assets/{share-modal-DsgW3wbg.js → share-modal-BeuydaBf.js} +1 -1
- package/core/built/admin/assets/social-urls-DA2lXrhW.js +8 -0
- package/core/built/admin/assets/{sort-button-Bb_FCl7e.js → sort-button-Df4t2AL8.js} +1 -1
- package/core/built/admin/assets/{source-icon-BeaRxUn9.js → source-icon-ajoaKb41.js} +1 -1
- package/core/built/admin/assets/{stack-xiN0eQ6Z.js → stack-CIfOAAVf.js} +1 -1
- package/core/built/admin/assets/{stats-Bjj_kmJ1.js → stats-C_lfxZAw.js} +1 -1
- package/core/built/admin/assets/{stats-view-BxPbW6K1.js → stats-view-Cxot1iD8.js} +1 -1
- package/core/built/admin/assets/{step-1-B1e3-1F8.js → step-1-ogAA4lDr.js} +1 -1
- package/core/built/admin/assets/{step-2-C5WaeBJo.js → step-2-C5Xini-7.js} +1 -1
- package/core/built/admin/assets/{step-3-B46iVPwu.js → step-3-CZv1FV81.js} +2 -2
- package/core/built/admin/assets/{switch-z5Y6Uzt6.js → switch-CyQcwMAv.js} +1 -1
- package/core/built/admin/assets/{table-CB-4_HBJ.js → table-DFj1wiYv.js} +1 -1
- package/core/built/admin/assets/{tabs-CCEgKYzq.js → tabs-D4y1gqUd.js} +1 -1
- package/core/built/admin/assets/{tags-Cg2VJGmG.js → tags-Dmft-Y4O.js} +1 -1
- package/core/built/admin/assets/{text-DwtjwGkh.js → text-DU9Hv33_.js} +1 -1
- package/core/built/admin/assets/textarea-d9qEkMOP.js +1 -0
- package/core/built/admin/assets/{toggle-group-b3IlxvhV.js → toggle-group-R2QMFvXW.js} +1 -1
- package/core/built/admin/assets/{topic-filter-DAFT87tQ.js → topic-filter-DGDxDalo.js} +1 -1
- package/core/built/admin/assets/{use-analytics-data-S0FTd43s.js → use-analytics-data-C76v4LIp.js} +1 -1
- package/core/built/admin/assets/use-filter-params-WrdhAISv.js +1 -0
- package/core/built/admin/assets/{use-growth-stats-CIaiHOVb.js → use-growth-stats-aNXJOmQr.js} +1 -1
- package/core/built/admin/assets/{use-post-referrers-Cq2vBPrA.js → use-post-referrers-CG0SZioj.js} +1 -1
- package/core/built/admin/assets/{use-reply-chain-data-DBDRtGu-.js → use-reply-chain-data-RLdPKRmS.js} +1 -1
- package/core/built/admin/assets/{use-simple-pagination-CjYNxdwe.js → use-simple-pagination-DgcF69KI.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-query-falHAtuK.js → use-tinybird-query-C1R_Kbmy.js} +1 -1
- package/core/built/admin/assets/{use-tinybird-token-D7-8ND1y.js → use-tinybird-token-C9fPjVM_.js} +1 -1
- package/core/built/admin/assets/{virtual-list-CvnC9zF2.js → virtual-list-BmTUzeoF.js} +1 -1
- package/core/built/admin/assets/{web-B_5GZRvH.js → web-BqW0b0_C.js} +1 -1
- package/core/built/admin/assets/web-Cy5GKMWM.js +2 -0
- package/core/built/admin/index.html +16 -16
- package/core/frontend/services/data/fetch-data.js +10 -5
- package/core/frontend/services/routing/api-adapter.js +115 -0
- package/core/frontend/services/routing/api-adapter.ts +157 -0
- package/core/frontend/services/routing/collection-router.js +5 -3
- package/core/frontend/services/routing/controllers/index.js +1 -1
- package/core/frontend/services/routing/controllers/rss.js +26 -31
- package/core/frontend/services/routing/controllers/rss.ts +66 -0
- package/core/frontend/services/routing/controllers/static.js +8 -7
- package/core/frontend/services/routing/parent-router.js +17 -4
- package/core/frontend/services/routing/permalink-adapter.js +26 -0
- package/core/frontend/services/routing/permalink-adapter.ts +23 -0
- package/core/frontend/services/routing/registry.js +59 -33
- package/core/frontend/services/routing/router-manager.js +9 -13
- package/core/frontend/services/routing/static-routes-router.js +4 -4
- package/core/frontend/services/routing/taxonomy-router.js +6 -2
- package/core/server/data/migrations/versions/6.56/2026-08-03-19-27-51-add-automation-run-step-id-to-automated-email-recipients.js +8 -0
- package/core/server/data/migrations/versions/6.56/2026-08-04-14-42-39-restrict-notification-add-permission-to-administrators.js +12 -0
- package/core/server/data/schema/fixtures/fixtures.json +2 -2
- package/core/server/data/schema/schema.js +1 -0
- package/core/server/models/post.js +10 -1
- package/core/server/services/automations/automations-repository.ts +3 -1
- package/core/server/services/automations/database-automations-repository.js +33 -14
- package/core/server/services/automations/database-automations-repository.ts +41 -15
- package/core/server/services/automations/get-scheduler-idempotency-key.js +13 -0
- package/core/server/services/automations/get-scheduler-idempotency-key.ts +8 -0
- package/core/server/services/automations/poll.js +3 -1
- package/core/server/services/automations/poll.ts +4 -1
- package/core/server/services/automations/service.js +5 -1
- package/core/server/services/automations/service.ts +5 -1
- package/core/server/services/gift-links/CONTEXT.md +9 -0
- package/core/server/services/gifts/CONTEXT.md +55 -0
- package/core/server/services/gifts/README.md +28 -0
- package/core/server/services/gifts/gift-bookshelf-repository.js +65 -45
- package/core/server/services/gifts/gift-bookshelf-repository.ts +203 -74
- package/core/server/services/gifts/gift-checkout-adapter.js +59 -0
- package/core/server/services/gifts/gift-checkout-offer.js +74 -0
- package/core/server/services/gifts/gift-checkout-offer.ts +127 -0
- package/core/server/services/gifts/gift-codec.js +63 -0
- package/core/server/services/gifts/gift-codec.ts +62 -0
- package/core/server/services/gifts/gift-controller.js +6 -31
- package/core/server/services/gifts/gift-controller.ts +6 -69
- package/core/server/services/gifts/gift-email-service.ts +3 -2
- package/core/server/services/gifts/gift-schema.js +34 -0
- package/core/server/services/gifts/gift-schema.ts +39 -0
- package/core/server/services/gifts/gift-service-wrapper.js +13 -11
- package/core/server/services/gifts/gift-service.js +265 -52
- package/core/server/services/gifts/gift-service.ts +431 -97
- package/core/server/services/gifts/gift.ts +16 -39
- package/core/server/services/link-tracking/link-click-repository.js +6 -1
- package/core/server/services/link-tracking/link-click-tracking-service.js +12 -3
- package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +4 -3
- package/core/server/services/member-welcome-emails/service.js +7 -3
- package/core/server/services/members/api.js +1 -2
- package/core/server/services/members/import-export/import/importer.ts +3 -2
- package/core/server/services/members/import-export/index.js +10 -2
- package/core/server/services/members/import-export/index.ts +14 -4
- package/core/server/services/members/members-api/controllers/router-controller.js +38 -36
- package/core/server/services/members/members-api/members-api.js +13 -7
- package/core/server/services/members/members-api/repositories/event-repository.js +4 -94
- package/core/server/services/members/members-api/services/member-bread-service.js +3 -3
- package/core/server/services/members/members-api/services/payments-service.js +6 -68
- package/core/server/services/members-custom-fields/schema.js +8 -1
- package/core/server/services/members-custom-fields/schema.ts +8 -0
- package/core/server/services/members-custom-fields/values-service.js +118 -2
- package/core/server/services/members-custom-fields/values-service.ts +134 -4
- package/core/server/services/oembed/oembed-service.js +44 -3
- package/core/server/services/posts/posts-service.js +11 -0
- package/core/server/services/route-settings/dynamic-routing-service.js +1 -3
- package/core/server/services/route-settings/route-settings-parser.js +25 -25
- package/core/server/services/route-settings/route-settings-parser.ts +27 -27
- package/core/server/services/route-settings/validation-errors.js +68 -4
- package/core/server/services/route-settings/validation-errors.ts +79 -4
- package/core/server/services/stats/posts-stats-service.js +27 -23
- package/core/server/services/stripe/services/webhook/charge-refunded-event-service.js +1 -1
- package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +16 -4
- package/core/server/services/stripe/stripe-api.js +10 -0
- package/core/server/web/gift-preview/controller.js +10 -25
- package/core/shared/labs.js +2 -2
- package/package.json +10 -9
- package/pnpm-lock.yaml +28 -18
- package/pnpm-workspace.yaml +5 -4
- package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
- package/core/built/admin/assets/activitypub/layout-qx-bUL1k.js +0 -1
- package/core/built/admin/assets/chunk.524.cffd399783a519e404a9.js +0 -25
- package/core/built/admin/assets/chunk.582.eebf0d0b2d202bb746a5.js +0 -27
- package/core/built/admin/assets/comments-DyUSMUdj.js +0 -1
- package/core/built/admin/assets/dist-CjY-f5Z1.js +0 -1
- package/core/built/admin/assets/domain-C0bV2UV8.js +0 -1
- package/core/built/admin/assets/editor-Dp865O-4.js +0 -8
- package/core/built/admin/assets/filter-sources-BV2rnpfA.js +0 -1
- package/core/built/admin/assets/filters-CUXJ2Nm9.js +0 -1
- package/core/built/admin/assets/filters-DfbIWc_l.js +0 -1
- package/core/built/admin/assets/index-9Cw8z3rA.css +0 -1
- package/core/built/admin/assets/index-BGG7NckO.js +0 -67
- package/core/built/admin/assets/input-group-BSgG_lVw.js +0 -1
- package/core/built/admin/assets/labels-CDSUt4J_.js +0 -1
- package/core/built/admin/assets/layout-yQEi2qDM.js +0 -1
- package/core/built/admin/assets/link-helpers-7EYqvvHs.js +0 -1
- package/core/built/admin/assets/member-custom-fields-yOMHyAg9.js +0 -1
- package/core/built/admin/assets/member-detail-BAq7gDaa.js +0 -1
- package/core/built/admin/assets/modals-ReORvWnu.js +0 -31
- package/core/built/admin/assets/newsletter-CJQvtHqN.js +0 -1
- package/core/built/admin/assets/newsletters-B3QQXfGT.js +0 -1
- package/core/built/admin/assets/nql-BsgFMd93.js +0 -1
- package/core/built/admin/assets/overview-CBfePxcr.js +0 -1
- package/core/built/admin/assets/post-analytics-header-Bmc9su6q.js +0 -1
- package/core/built/admin/assets/preferences-DJPfHEGu.js +0 -5
- package/core/built/admin/assets/profile-DqBQp-gT.js +0 -1
- package/core/built/admin/assets/select-C5_xEt5b.js +0 -1
- package/core/built/admin/assets/settings-BwiVIXau.js +0 -4
- package/core/built/admin/assets/social-urls-BQPFEk1_.js +0 -8
- package/core/built/admin/assets/textarea-Bjccvh_E.js +0 -1
- package/core/built/admin/assets/use-filter-params-Gpoq2E1L.js +0 -1
- package/core/built/admin/assets/web-jFYWCKDn.js +0 -2
- package/core/server/services/gifts/gift-repository.js +0 -2
- package/core/server/services/gifts/gift-repository.ts +0 -29
- package/core/server/services/route-settings/activation-bridge.js +0 -174
- package/core/server/services/route-settings/activation-bridge.ts +0 -250
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toExpressNotation = toExpressNotation;
|
|
4
|
+
exports.toDomainNotation = toDomainNotation;
|
|
5
|
+
/**
|
|
6
|
+
* Convert a domain-model permalink into Express / URL-service notation.
|
|
7
|
+
*
|
|
8
|
+
* @example toExpressNotation('/{slug}/') // => '/:slug/'
|
|
9
|
+
* @example toExpressNotation('/{primary_tag}/{slug}/') // => '/:primary_tag/:slug/'
|
|
10
|
+
* @example toExpressNotation('/:slug/') // => '/:slug/' (idempotent)
|
|
11
|
+
*/
|
|
12
|
+
function toExpressNotation(permalink) {
|
|
13
|
+
return permalink.replace(/{(\w+)}/g, ':$1');
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Convert an Express / URL-service permalink back into domain-model notation.
|
|
17
|
+
* Will be used on the download path when serialising the canonical shape back
|
|
18
|
+
* to YAML — no production caller yet.
|
|
19
|
+
*
|
|
20
|
+
* @example toDomainNotation('/:slug/') // => '/{slug}/'
|
|
21
|
+
* @example toDomainNotation('/:primary_tag/:slug/') // => '/{primary_tag}/{slug}/'
|
|
22
|
+
* @example toDomainNotation('/{slug}/') // => '/{slug}/' (idempotent)
|
|
23
|
+
*/
|
|
24
|
+
function toDomainNotation(permalink) {
|
|
25
|
+
return permalink.replace(/:(\w+)/g, '{$1}');
|
|
26
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Convert a domain-model permalink into Express / URL-service notation.
|
|
3
|
+
*
|
|
4
|
+
* @example toExpressNotation('/{slug}/') // => '/:slug/'
|
|
5
|
+
* @example toExpressNotation('/{primary_tag}/{slug}/') // => '/:primary_tag/:slug/'
|
|
6
|
+
* @example toExpressNotation('/:slug/') // => '/:slug/' (idempotent)
|
|
7
|
+
*/
|
|
8
|
+
export function toExpressNotation(permalink: string): string {
|
|
9
|
+
return permalink.replace(/{(\w+)}/g, ':$1');
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Convert an Express / URL-service permalink back into domain-model notation.
|
|
14
|
+
* Will be used on the download path when serialising the canonical shape back
|
|
15
|
+
* to YAML — no production caller yet.
|
|
16
|
+
*
|
|
17
|
+
* @example toDomainNotation('/:slug/') // => '/{slug}/'
|
|
18
|
+
* @example toDomainNotation('/:primary_tag/:slug/') // => '/{primary_tag}/{slug}/'
|
|
19
|
+
* @example toDomainNotation('/{slug}/') // => '/{slug}/' (idempotent)
|
|
20
|
+
*/
|
|
21
|
+
export function toDomainNotation(permalink: string): string {
|
|
22
|
+
return permalink.replace(/:(\w+)/g, '{$1}');
|
|
23
|
+
}
|
|
@@ -1,58 +1,82 @@
|
|
|
1
1
|
const _ = require('lodash');
|
|
2
|
-
let routes = [];
|
|
3
|
-
let routers = {};
|
|
4
2
|
|
|
5
3
|
/**
|
|
6
|
-
*
|
|
4
|
+
* Every registered router is a Ghost router — `ParentRouter` itself (the site
|
|
5
|
+
* and apps routers) or one of its subclasses (Collection, StaticRoutes,
|
|
6
|
+
* Taxonomy, StaticPages, Preview, Email, Unsubscribe). They wrap an Express
|
|
7
|
+
* router, reachable via `.router()`, but are not one themselves: the registry's
|
|
8
|
+
* own lookups read Ghost-side members (`.name`, `.routerName`, `.reset()`,
|
|
9
|
+
* `.getRssUrl()`) that an Express router does not have.
|
|
10
|
+
*
|
|
11
|
+
* Type-only import — no runtime require, so this does not close a cycle with
|
|
12
|
+
* `parent-router.js`, which requires this module.
|
|
13
|
+
*
|
|
14
|
+
* @typedef {import('./parent-router')} ParentRouter
|
|
7
15
|
*/
|
|
8
|
-
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @description The router registry is helpful for debugging purposes and lets you search existing routes and routers.
|
|
19
|
+
*
|
|
20
|
+
* Exported as a single shared instance so that every consumer
|
|
21
|
+
* (`routing/index.js`, `parent-router.js`) operates on the same registry, the
|
|
22
|
+
* same way the previous module-level state did — just held on an instance
|
|
23
|
+
* rather than in module-scoped `let` variables.
|
|
24
|
+
*/
|
|
25
|
+
class Registry {
|
|
26
|
+
constructor() {
|
|
27
|
+
/** @type {Array<{route: string, from: string}>} */
|
|
28
|
+
this.routes = [];
|
|
29
|
+
/** @type {Object<string, ParentRouter>} */
|
|
30
|
+
this.routers = {};
|
|
31
|
+
}
|
|
32
|
+
|
|
9
33
|
/**
|
|
10
|
-
* @description
|
|
34
|
+
* @description Gets called if you register a url pattern in express.
|
|
11
35
|
* @param {string} routerName
|
|
12
36
|
* @param {string} route
|
|
13
37
|
*/
|
|
14
38
|
setRoute(routerName, route) {
|
|
15
|
-
routes.push({route: route, from: routerName});
|
|
16
|
-
}
|
|
39
|
+
this.routes.push({route: route, from: routerName});
|
|
40
|
+
}
|
|
17
41
|
|
|
18
42
|
/**
|
|
19
|
-
* @description
|
|
43
|
+
* @description Gets called if you register a router in express.
|
|
20
44
|
* @param {string} name
|
|
21
|
-
* @param {
|
|
45
|
+
* @param {ParentRouter} router
|
|
22
46
|
*/
|
|
23
47
|
setRouter(name, router) {
|
|
24
|
-
routers[name] = router;
|
|
25
|
-
}
|
|
48
|
+
this.routers[name] = router;
|
|
49
|
+
}
|
|
26
50
|
|
|
27
51
|
/**
|
|
28
52
|
* @description Get all registered routes.
|
|
29
|
-
* @returns {Array}
|
|
53
|
+
* @returns {Array<{route: string, from: string}>}
|
|
30
54
|
*/
|
|
31
55
|
getAllRoutes() {
|
|
32
|
-
return _.cloneDeep(routes);
|
|
33
|
-
}
|
|
56
|
+
return _.cloneDeep(this.routes);
|
|
57
|
+
}
|
|
34
58
|
|
|
35
59
|
/**
|
|
36
60
|
* @description Get router by name.
|
|
37
61
|
* @param {string} name
|
|
38
|
-
* @returns {
|
|
62
|
+
* @returns {ParentRouter|undefined}
|
|
39
63
|
*/
|
|
40
64
|
getRouter(name) {
|
|
41
|
-
return routers[name];
|
|
42
|
-
}
|
|
65
|
+
return this.routers[name];
|
|
66
|
+
}
|
|
43
67
|
|
|
44
68
|
/**
|
|
45
|
-
* Gets a router by
|
|
69
|
+
* Gets a router by its internal router name
|
|
46
70
|
* @param {string} name internal router name
|
|
47
|
-
* @returns {
|
|
71
|
+
* @returns {ParentRouter|undefined}
|
|
48
72
|
*/
|
|
49
73
|
getRouterByName(name) {
|
|
50
|
-
for (let routerKey in routers) {
|
|
51
|
-
if (routers[routerKey].name === name) {
|
|
52
|
-
return routers[routerKey];
|
|
74
|
+
for (let routerKey in this.routers) {
|
|
75
|
+
if (this.routers[routerKey].name === name) {
|
|
76
|
+
return this.routers[routerKey];
|
|
53
77
|
}
|
|
54
78
|
}
|
|
55
|
-
}
|
|
79
|
+
}
|
|
56
80
|
|
|
57
81
|
/**
|
|
58
82
|
*
|
|
@@ -77,7 +101,7 @@ module.exports = {
|
|
|
77
101
|
getRssUrl(options) {
|
|
78
102
|
let rssUrl = null;
|
|
79
103
|
|
|
80
|
-
const collectionIndexRouter = _.find(routers, {name: 'CollectionRouter', routerName: 'index'});
|
|
104
|
+
const collectionIndexRouter = _.find(this.routers, {name: 'CollectionRouter', routerName: 'index'});
|
|
81
105
|
|
|
82
106
|
if (collectionIndexRouter) {
|
|
83
107
|
rssUrl = collectionIndexRouter.getRssUrl(options);
|
|
@@ -88,7 +112,7 @@ module.exports = {
|
|
|
88
112
|
}
|
|
89
113
|
}
|
|
90
114
|
|
|
91
|
-
const collectionRouters = _.filter(routers, {name: 'CollectionRouter'});
|
|
115
|
+
const collectionRouters = _.filter(this.routers, {name: 'CollectionRouter'});
|
|
92
116
|
|
|
93
117
|
if (collectionRouters && collectionRouters.length === 1) {
|
|
94
118
|
rssUrl = collectionRouters[0].getRssUrl(options);
|
|
@@ -108,32 +132,34 @@ module.exports = {
|
|
|
108
132
|
}
|
|
109
133
|
|
|
110
134
|
return rssUrl;
|
|
111
|
-
}
|
|
135
|
+
}
|
|
112
136
|
|
|
113
137
|
/**
|
|
114
138
|
* @description Reset all routes.
|
|
115
139
|
*/
|
|
116
140
|
resetAllRoutes() {
|
|
117
|
-
routes = [];
|
|
118
|
-
}
|
|
141
|
+
this.routes = [];
|
|
142
|
+
}
|
|
119
143
|
|
|
120
144
|
/**
|
|
121
145
|
* @description Reset all routers.
|
|
122
146
|
*/
|
|
123
147
|
resetAllRouters() {
|
|
124
|
-
_.each(routers, (value) => {
|
|
148
|
+
_.each(this.routers, (value) => {
|
|
125
149
|
if (value && typeof value.reset === 'function') {
|
|
126
150
|
value.reset();
|
|
127
151
|
}
|
|
128
152
|
});
|
|
129
153
|
|
|
130
|
-
routers = {};
|
|
131
|
-
}
|
|
154
|
+
this.routers = {};
|
|
155
|
+
}
|
|
132
156
|
|
|
133
157
|
/**
|
|
134
158
|
* @description Clear all routers (for testing).
|
|
135
159
|
*/
|
|
136
160
|
clearAllRouters() {
|
|
137
|
-
routers = {};
|
|
161
|
+
this.routers = {};
|
|
138
162
|
}
|
|
139
|
-
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = new Registry();
|
|
@@ -90,10 +90,10 @@ class RouterManager {
|
|
|
90
90
|
* 5. Static Pages: Weaker than collections, because we first try to find a post slug and fallback to lookup a static page.
|
|
91
91
|
* 6. Internal Apps: Weakest
|
|
92
92
|
*
|
|
93
|
-
* @param {
|
|
93
|
+
* @param {RouteSettings} routeSettings
|
|
94
94
|
*/
|
|
95
|
-
start(
|
|
96
|
-
debug('routing start',
|
|
95
|
+
start(routeSettings) {
|
|
96
|
+
debug('routing start', routeSettings);
|
|
97
97
|
const RESOURCE_CONFIG = require(`./config`);
|
|
98
98
|
|
|
99
99
|
const unsubscribeRouter = new UnsubscribeRouter();
|
|
@@ -110,14 +110,14 @@ class RouterManager {
|
|
|
110
110
|
this.siteRouter.mountRouter(previewRouter.router());
|
|
111
111
|
this.registry.setRouter('previewRouter', previewRouter);
|
|
112
112
|
|
|
113
|
-
for (const route of
|
|
113
|
+
for (const route of routeSettings.routes) {
|
|
114
114
|
const staticRoutesRouter = new StaticRoutesRouter(route.path, route, this.routerCreated.bind(this));
|
|
115
115
|
this.siteRouter.mountRouter(staticRoutesRouter.router());
|
|
116
116
|
|
|
117
117
|
this.registry.setRouter(staticRoutesRouter.identifier, staticRoutesRouter);
|
|
118
118
|
}
|
|
119
119
|
|
|
120
|
-
for (const collection of
|
|
120
|
+
for (const collection of routeSettings.collections) {
|
|
121
121
|
const collectionRouter = new CollectionRouter(collection.path, collection, RESOURCE_CONFIG, this.routerCreated.bind(this));
|
|
122
122
|
this.siteRouter.mountRouter(collectionRouter.router());
|
|
123
123
|
this.registry.setRouter(collectionRouter.identifier, collectionRouter);
|
|
@@ -128,8 +128,8 @@ class RouterManager {
|
|
|
128
128
|
|
|
129
129
|
this.registry.setRouter('staticPagesRouter', staticPagesRouter);
|
|
130
130
|
|
|
131
|
-
for (const
|
|
132
|
-
const taxonomyRouter = new TaxonomyRouter(
|
|
131
|
+
for (const [key, permalink] of Object.entries(routeSettings.taxonomies)) {
|
|
132
|
+
const taxonomyRouter = new TaxonomyRouter(key, permalink, RESOURCE_CONFIG, this.routerCreated.bind(this));
|
|
133
133
|
this.siteRouter.mountRouter(taxonomyRouter.router());
|
|
134
134
|
|
|
135
135
|
this.registry.setRouter(taxonomyRouter.identifier, taxonomyRouter);
|
|
@@ -180,16 +180,12 @@ module.exports = RouterManager;
|
|
|
180
180
|
|
|
181
181
|
/**
|
|
182
182
|
* @typedef {Object} RouterConfig
|
|
183
|
-
* @property {RouteSettings} [routeSettings] -
|
|
183
|
+
* @property {RouteSettings} [routeSettings] - parsed route settings domain model
|
|
184
184
|
* @property {URLServiceFacade} urlService - resource-based URL service facade
|
|
185
185
|
*/
|
|
186
186
|
|
|
187
187
|
/**
|
|
188
|
-
*
|
|
189
|
-
* a temporary reference into server code; when the bridge is removed (HKG-1898)
|
|
190
|
-
* it becomes the domain `RouteSettings` from @tryghost/adapter-base-route-settings.
|
|
191
|
-
*
|
|
192
|
-
* @typedef {import('../../../server/services/route-settings/activation-bridge').RouterSettings} RouteSettings
|
|
188
|
+
* @typedef {import('@tryghost/adapter-base-route-settings').RouteSettings} RouteSettings
|
|
193
189
|
*/
|
|
194
190
|
|
|
195
191
|
/**
|
|
@@ -15,7 +15,7 @@ class StaticRoutesRouter extends ParentRouter {
|
|
|
15
15
|
|
|
16
16
|
this.route = {value: mainRoute};
|
|
17
17
|
this.templates = object.templates || [];
|
|
18
|
-
this.data = object.data || {
|
|
18
|
+
this.data = object.data || {};
|
|
19
19
|
this.routerName = mainRoute === '/' ? 'index' : mainRoute.replace(/\//g, '');
|
|
20
20
|
this.routerCreated = routerCreated;
|
|
21
21
|
|
|
@@ -80,7 +80,7 @@ class StaticRoutesRouter extends ParentRouter {
|
|
|
80
80
|
filter: this.filter,
|
|
81
81
|
limit: this.limit,
|
|
82
82
|
order: this.order,
|
|
83
|
-
data: this.data
|
|
83
|
+
data: this.data,
|
|
84
84
|
templates: this.templates
|
|
85
85
|
};
|
|
86
86
|
|
|
@@ -117,7 +117,7 @@ class StaticRoutesRouter extends ParentRouter {
|
|
|
117
117
|
message: `Missing template ${res.routerOptions.templates.map(x => `${x}.hbs`).join(', ')} for route "${req.originalUrl}".`
|
|
118
118
|
});
|
|
119
119
|
},
|
|
120
|
-
data: this.data
|
|
120
|
+
data: this.data,
|
|
121
121
|
context: [this.routerName],
|
|
122
122
|
contentType: this.contentType
|
|
123
123
|
};
|
|
@@ -131,7 +131,7 @@ class StaticRoutesRouter extends ParentRouter {
|
|
|
131
131
|
* @returns {boolean}
|
|
132
132
|
*/
|
|
133
133
|
isChannel(object) {
|
|
134
|
-
//
|
|
134
|
+
// parseRouteSettings always sets `type` ('channel' | 'template') on
|
|
135
135
|
// every route, so a channel is simply `type === 'channel'`.
|
|
136
136
|
return object?.type === 'channel';
|
|
137
137
|
}
|
|
@@ -5,6 +5,7 @@ const RSSRouter = require('./rss-router');
|
|
|
5
5
|
const urlUtils = require('../../../shared/url-utils').default;
|
|
6
6
|
const controllers = require('./controllers');
|
|
7
7
|
const middleware = require('./middleware');
|
|
8
|
+
const {toExpressNotation} = require('./permalink-adapter');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* @description Taxonomies are groupings of posts based on a common relation.
|
|
@@ -17,8 +18,9 @@ class TaxonomyRouter extends ParentRouter {
|
|
|
17
18
|
this.taxonomyKey = key;
|
|
18
19
|
this.RESOURCE_CONFIG = RESOURCE_CONFIG;
|
|
19
20
|
|
|
21
|
+
// convert domain `{slug}` -> Express/URL-service `:slug` at this boundary
|
|
20
22
|
this.permalinks = {
|
|
21
|
-
value: permalinks
|
|
23
|
+
value: toExpressNotation(permalinks)
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
this.permalinks.getValue = () => {
|
|
@@ -74,7 +76,9 @@ class TaxonomyRouter extends ParentRouter {
|
|
|
74
76
|
type: 'channel',
|
|
75
77
|
name: this.taxonomyKey,
|
|
76
78
|
permalinks: this.permalinks.getValue(),
|
|
77
|
-
data
|
|
79
|
+
// Route data in domain form — the API adapter resolves it to a
|
|
80
|
+
// controller call, and fetch-data fills `%s` in from the request.
|
|
81
|
+
data: {[this.taxonomyKey]: {type: 'read', resource: this.getResourceType(), slug: '%s'}},
|
|
78
82
|
filter: this.RESOURCE_CONFIG.TAXONOMIES[this.taxonomyKey].filter,
|
|
79
83
|
resourceType: this.getResourceType(),
|
|
80
84
|
context: [this.taxonomyKey],
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
const {createAddColumnMigration} = require('../../utils');
|
|
2
|
+
|
|
3
|
+
module.exports = createAddColumnMigration('automated_email_recipients', 'automation_run_step_id', {
|
|
4
|
+
type: 'string',
|
|
5
|
+
maxlength: 24,
|
|
6
|
+
nullable: true,
|
|
7
|
+
references: 'automation_run_steps.id'
|
|
8
|
+
}, {algorithm: 'auto'});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
const {combineTransactionalMigrations, removePermissionFromRole} = require('../../utils');
|
|
2
|
+
|
|
3
|
+
module.exports = combineTransactionalMigrations(
|
|
4
|
+
removePermissionFromRole({
|
|
5
|
+
permission: 'Add notifications',
|
|
6
|
+
role: 'Editor'
|
|
7
|
+
}),
|
|
8
|
+
removePermissionFromRole({
|
|
9
|
+
permission: 'Add notifications',
|
|
10
|
+
role: 'Super Editor'
|
|
11
|
+
})
|
|
12
|
+
);
|
|
@@ -1066,7 +1066,7 @@
|
|
|
1066
1066
|
"gift_link": "manage"
|
|
1067
1067
|
},
|
|
1068
1068
|
"Super Editor": {
|
|
1069
|
-
"notification": "
|
|
1069
|
+
"notification": ["browse", "destroy"],
|
|
1070
1070
|
"post": "all",
|
|
1071
1071
|
"setting": ["browse", "read"],
|
|
1072
1072
|
"slug": "all",
|
|
@@ -1091,7 +1091,7 @@
|
|
|
1091
1091
|
"gift_link": "manage"
|
|
1092
1092
|
},
|
|
1093
1093
|
"Editor": {
|
|
1094
|
-
"notification": "
|
|
1094
|
+
"notification": ["browse", "destroy"],
|
|
1095
1095
|
"post": "all",
|
|
1096
1096
|
"setting": ["browse", "read"],
|
|
1097
1097
|
"slug": "all",
|
|
@@ -1339,6 +1339,7 @@ module.exports = {
|
|
|
1339
1339
|
id: {type: 'string', maxlength: 24, nullable: false, primary: true},
|
|
1340
1340
|
automated_email_id: {type: 'string', maxlength: 24, nullable: true, references: 'welcome_email_automated_emails.id'},
|
|
1341
1341
|
automation_action_revision_id: {type: 'string', maxlength: 24, nullable: true, references: 'automation_action_revisions.id'},
|
|
1342
|
+
automation_run_step_id: {type: 'string', maxlength: 24, nullable: true, references: 'automation_run_steps.id'},
|
|
1342
1343
|
member_id: {type: 'string', maxlength: 24, nullable: false, index: true},
|
|
1343
1344
|
member_uuid: {type: 'string', maxlength: 36, nullable: false},
|
|
1344
1345
|
member_email: {type: 'string', maxlength: 191, nullable: false},
|
|
@@ -1346,7 +1346,16 @@ Post = ghostBookshelf.Model.extend({
|
|
|
1346
1346
|
destroy: function destroy(unfilteredOptions) {
|
|
1347
1347
|
let options = this.filterOptions(unfilteredOptions, 'destroy', {extraAllowedProperties: ['id']});
|
|
1348
1348
|
|
|
1349
|
-
const destroyPost = () => {
|
|
1349
|
+
const destroyPost = async () => {
|
|
1350
|
+
// The `comments.in_reply_to_id` references form chains between a post's
|
|
1351
|
+
// comments, which MySQL cannot resolve while cascade-deleting them
|
|
1352
|
+
// alongside `comments.parent_id`. Clear the references first so the
|
|
1353
|
+
// `comments.post_id` cascade delete can do its job
|
|
1354
|
+
await ghostBookshelf.knex('comments')
|
|
1355
|
+
.where('post_id', options.id)
|
|
1356
|
+
.update('in_reply_to_id', null)
|
|
1357
|
+
.transacting(options.transacting);
|
|
1358
|
+
|
|
1350
1359
|
return ghostBookshelf.Model.destroy.call(this, options);
|
|
1351
1360
|
};
|
|
1352
1361
|
|
|
@@ -90,6 +90,7 @@ export type AutomatedEmailEvents = {
|
|
|
90
90
|
|
|
91
91
|
export type RecordEmailSentOptions = Readonly<{
|
|
92
92
|
automationActionRevisionId: string;
|
|
93
|
+
automationRunStepId: string;
|
|
93
94
|
mailgunMessageId?: string;
|
|
94
95
|
memberEmail: string;
|
|
95
96
|
memberId: string;
|
|
@@ -203,11 +204,12 @@ export interface AutomationsRepository {
|
|
|
203
204
|
eventsByAutomatedEmailRecipientId: ReadonlyDeep<Map<string, AutomatedEmailEvents>>
|
|
204
205
|
): Promise<void>;
|
|
205
206
|
/**
|
|
206
|
-
*
|
|
207
|
+
* Record the first click timestamp for the automated email recipient identified by a run step.
|
|
207
208
|
*
|
|
208
209
|
*/
|
|
209
210
|
trackEmailClicked(options: {
|
|
210
211
|
automationActionRevisionId: string;
|
|
212
|
+
automationRunStepId: string;
|
|
211
213
|
memberId: string;
|
|
212
214
|
clickedAt: Readonly<Date>;
|
|
213
215
|
}, transactionOptions?: {
|
|
@@ -135,6 +135,7 @@ function createDatabaseAutomationsRepository({ knex, fakeWaitHoursMultiplier })
|
|
|
135
135
|
member_email: options.memberEmail,
|
|
136
136
|
member_name: options.memberName,
|
|
137
137
|
automation_action_revision_id: options.automationActionRevisionId,
|
|
138
|
+
automation_run_step_id: options.automationRunStepId,
|
|
138
139
|
...(options.mailgunMessageId ? { mailgun_message_id: options.mailgunMessageId } : {}),
|
|
139
140
|
track_clicks: options.trackClicks,
|
|
140
141
|
track_opens: options.trackOpens,
|
|
@@ -157,6 +158,13 @@ function createDatabaseAutomationsRepository({ knex, fakeWaitHoursMultiplier })
|
|
|
157
158
|
return;
|
|
158
159
|
}
|
|
159
160
|
await knex.transaction(async (trx) => {
|
|
161
|
+
const revisionIds = new Set();
|
|
162
|
+
for (const { openedAt, automationActionRevisionId } of eventsByAutomatedEmailRecipientId.values()) {
|
|
163
|
+
if (openedAt) {
|
|
164
|
+
revisionIds.add(automationActionRevisionId);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
const orderedRevisionIds = await lockActionRevisions(trx, revisionIds);
|
|
160
168
|
const notYetOpened = await lockNotYetOpened(trx, eventsByAutomatedEmailRecipientId);
|
|
161
169
|
const newOpensPerRevision = new Map();
|
|
162
170
|
for (const [id, { deliveredAt, openedAt, automationActionRevisionId }] of eventsByAutomatedEmailRecipientId) {
|
|
@@ -177,10 +185,11 @@ function createDatabaseAutomationsRepository({ knex, fakeWaitHoursMultiplier })
|
|
|
177
185
|
newOpensPerRevision.set(automationActionRevisionId, (newOpensPerRevision.get(automationActionRevisionId) ?? 0) + 1);
|
|
178
186
|
}
|
|
179
187
|
}
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
188
|
+
for (const id of orderedRevisionIds) {
|
|
189
|
+
const opens = newOpensPerRevision.get(id);
|
|
190
|
+
if (!opens) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
184
193
|
await trx('automation_action_revisions')
|
|
185
194
|
.where({ id })
|
|
186
195
|
.update({
|
|
@@ -189,24 +198,17 @@ function createDatabaseAutomationsRepository({ knex, fakeWaitHoursMultiplier })
|
|
|
189
198
|
}
|
|
190
199
|
});
|
|
191
200
|
},
|
|
192
|
-
async trackEmailClicked({ automationActionRevisionId, memberId, clickedAt }, { transacting } = {}) {
|
|
201
|
+
async trackEmailClicked({ automationActionRevisionId, automationRunStepId, memberId, clickedAt }, { transacting } = {}) {
|
|
193
202
|
const trackClick = async (trx) => {
|
|
194
|
-
|
|
195
|
-
await trx('automation_action_revisions')
|
|
196
|
-
.select('id')
|
|
197
|
-
.where({ id: automationActionRevisionId })
|
|
198
|
-
.forUpdate()
|
|
199
|
-
.first();
|
|
203
|
+
await lockActionRevisions(trx, [automationActionRevisionId]);
|
|
200
204
|
const recipient = await trx('automated_email_recipients')
|
|
201
205
|
.select('id', 'clicked_at')
|
|
202
206
|
.where({
|
|
203
207
|
automation_action_revision_id: automationActionRevisionId,
|
|
208
|
+
automation_run_step_id: automationRunStepId,
|
|
204
209
|
member_id: memberId,
|
|
205
210
|
track_clicks: true
|
|
206
211
|
})
|
|
207
|
-
.where('created_at', '<=', (0, database_date_1.toDatabaseDate)(clickedAt))
|
|
208
|
-
.orderBy('created_at', 'desc')
|
|
209
|
-
.orderBy('id', 'desc')
|
|
210
212
|
.forUpdate()
|
|
211
213
|
.first();
|
|
212
214
|
if (!recipient || recipient.clicked_at !== null) {
|
|
@@ -233,6 +235,23 @@ function createDatabaseAutomationsRepository({ knex, fakeWaitHoursMultiplier })
|
|
|
233
235
|
}
|
|
234
236
|
};
|
|
235
237
|
}
|
|
238
|
+
/**
|
|
239
|
+
* Lock revisions before recipients because inserting a recipient takes a shared
|
|
240
|
+
* foreign-key lock on its revision. Updating that revision later can deadlock
|
|
241
|
+
* with another transaction that has already locked the revision and is waiting
|
|
242
|
+
* for the recipient. Keep multi-revision lock acquisition deterministic.
|
|
243
|
+
*/
|
|
244
|
+
async function lockActionRevisions(trx, revisionIds) {
|
|
245
|
+
const sortedRevisionIds = [...new Set(revisionIds)]
|
|
246
|
+
.sort((left, right) => left.localeCompare(right));
|
|
247
|
+
if (sortedRevisionIds.length > 0) {
|
|
248
|
+
await trx('automation_action_revisions')
|
|
249
|
+
.select('id')
|
|
250
|
+
.whereIn('id', sortedRevisionIds)
|
|
251
|
+
.forUpdate();
|
|
252
|
+
}
|
|
253
|
+
return sortedRevisionIds;
|
|
254
|
+
}
|
|
236
255
|
/**
|
|
237
256
|
* Which of these recipients have yet to open, and so should count towards their
|
|
238
257
|
* revision's open count. Locks them for the transaction, so a worker racing on
|
|
@@ -276,6 +276,7 @@ export function createDatabaseAutomationsRepository({
|
|
|
276
276
|
member_email: options.memberEmail,
|
|
277
277
|
member_name: options.memberName,
|
|
278
278
|
automation_action_revision_id: options.automationActionRevisionId,
|
|
279
|
+
automation_run_step_id: options.automationRunStepId,
|
|
279
280
|
...(options.mailgunMessageId ? {mailgun_message_id: options.mailgunMessageId} : {}),
|
|
280
281
|
track_clicks: options.trackClicks,
|
|
281
282
|
track_opens: options.trackOpens,
|
|
@@ -301,6 +302,15 @@ export function createDatabaseAutomationsRepository({
|
|
|
301
302
|
}
|
|
302
303
|
|
|
303
304
|
await knex.transaction(async (trx) => {
|
|
305
|
+
const revisionIds = new Set<string>();
|
|
306
|
+
for (const {openedAt, automationActionRevisionId} of eventsByAutomatedEmailRecipientId.values()) {
|
|
307
|
+
if (openedAt) {
|
|
308
|
+
revisionIds.add(automationActionRevisionId);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
const orderedRevisionIds = await lockActionRevisions(trx, revisionIds);
|
|
313
|
+
|
|
304
314
|
const notYetOpened = await lockNotYetOpened(trx, eventsByAutomatedEmailRecipientId);
|
|
305
315
|
const newOpensPerRevision = new Map<string, number>();
|
|
306
316
|
|
|
@@ -327,11 +337,11 @@ export function createDatabaseAutomationsRepository({
|
|
|
327
337
|
}
|
|
328
338
|
}
|
|
329
339
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
340
|
+
for (const id of orderedRevisionIds) {
|
|
341
|
+
const opens = newOpensPerRevision.get(id);
|
|
342
|
+
if (!opens) {
|
|
343
|
+
continue;
|
|
344
|
+
}
|
|
335
345
|
await trx('automation_action_revisions')
|
|
336
346
|
.where({id})
|
|
337
347
|
.update({
|
|
@@ -341,25 +351,18 @@ export function createDatabaseAutomationsRepository({
|
|
|
341
351
|
});
|
|
342
352
|
},
|
|
343
353
|
|
|
344
|
-
async trackEmailClicked({automationActionRevisionId, memberId, clickedAt}, {transacting} = {}) {
|
|
354
|
+
async trackEmailClicked({automationActionRevisionId, automationRunStepId, memberId, clickedAt}, {transacting} = {}) {
|
|
345
355
|
const trackClick = async (trx: Knex.Transaction) => {
|
|
346
|
-
|
|
347
|
-
await trx('automation_action_revisions')
|
|
348
|
-
.select('id')
|
|
349
|
-
.where({id: automationActionRevisionId})
|
|
350
|
-
.forUpdate()
|
|
351
|
-
.first();
|
|
356
|
+
await lockActionRevisions(trx, [automationActionRevisionId]);
|
|
352
357
|
|
|
353
358
|
const recipient = await trx('automated_email_recipients')
|
|
354
359
|
.select('id', 'clicked_at')
|
|
355
360
|
.where({
|
|
356
361
|
automation_action_revision_id: automationActionRevisionId,
|
|
362
|
+
automation_run_step_id: automationRunStepId,
|
|
357
363
|
member_id: memberId,
|
|
358
364
|
track_clicks: true
|
|
359
365
|
})
|
|
360
|
-
.where('created_at', '<=', toDatabaseDate(clickedAt))
|
|
361
|
-
.orderBy('created_at', 'desc')
|
|
362
|
-
.orderBy('id', 'desc')
|
|
363
366
|
.forUpdate()
|
|
364
367
|
.first();
|
|
365
368
|
|
|
@@ -394,6 +397,29 @@ export function createDatabaseAutomationsRepository({
|
|
|
394
397
|
};
|
|
395
398
|
}
|
|
396
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Lock revisions before recipients because inserting a recipient takes a shared
|
|
402
|
+
* foreign-key lock on its revision. Updating that revision later can deadlock
|
|
403
|
+
* with another transaction that has already locked the revision and is waiting
|
|
404
|
+
* for the recipient. Keep multi-revision lock acquisition deterministic.
|
|
405
|
+
*/
|
|
406
|
+
async function lockActionRevisions(
|
|
407
|
+
trx: Knex.Transaction,
|
|
408
|
+
revisionIds: Iterable<string>
|
|
409
|
+
): Promise<string[]> {
|
|
410
|
+
const sortedRevisionIds = [...new Set(revisionIds)]
|
|
411
|
+
.sort((left, right) => left.localeCompare(right));
|
|
412
|
+
|
|
413
|
+
if (sortedRevisionIds.length > 0) {
|
|
414
|
+
await trx('automation_action_revisions')
|
|
415
|
+
.select('id')
|
|
416
|
+
.whereIn('id', sortedRevisionIds)
|
|
417
|
+
.forUpdate();
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
return sortedRevisionIds;
|
|
421
|
+
}
|
|
422
|
+
|
|
397
423
|
/**
|
|
398
424
|
* Which of these recipients have yet to open, and so should count towards their
|
|
399
425
|
* revision's open count. Locks them for the transaction, so a worker racing on
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getSchedulerIdempotencyKey = getSchedulerIdempotencyKey;
|
|
7
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
8
|
+
function getSchedulerIdempotencyKey(date, url) {
|
|
9
|
+
const hash = node_crypto_1.default.createHash('sha256');
|
|
10
|
+
hash.update(date.toISOString());
|
|
11
|
+
hash.update(url.href);
|
|
12
|
+
return `ghost-automations-${hash.digest('hex')}`;
|
|
13
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
|
|
3
|
+
export function getSchedulerIdempotencyKey(date: Readonly<Date>, url: Readonly<URL>): string {
|
|
4
|
+
const hash = crypto.createHash('sha256');
|
|
5
|
+
hash.update(date.toISOString());
|
|
6
|
+
hash.update(url.href);
|
|
7
|
+
return `ghost-automations-${hash.digest('hex')}`;
|
|
8
|
+
}
|