ghost 6.54.1 → 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.
Files changed (356) hide show
  1. package/components/tryghost-adapter-base-scheduling-0.2.0.tgz +0 -0
  2. package/components/tryghost-custom-field-types-0.0.0.tgz +0 -0
  3. package/components/tryghost-kg-default-nodes-2.1.4.tgz +0 -0
  4. package/components/tryghost-kg-default-transforms-1.3.3.tgz +0 -0
  5. package/components/tryghost-kg-html-to-lexical-1.3.3.tgz +0 -0
  6. package/components/tryghost-kg-lexical-html-renderer-1.4.3.tgz +0 -0
  7. package/components/tryghost-nql-string-0.0.0.tgz +0 -0
  8. package/core/boot.js +3 -6
  9. package/core/built/admin/assets/{Combination-CJG7Flp-.js → Combination-BCm9FL4o.js} +1 -1
  10. package/core/built/admin/assets/{PolarAngleAxis-BGjf7sBu.js → PolarAngleAxis-C4qtHy1B.js} +1 -1
  11. package/core/built/admin/assets/{RadialBarChart-zeFf9mBz.js → RadialBarChart-Bh09Stsj.js} +1 -1
  12. package/core/built/admin/assets/{account-migration-BklF2I_I.js → account-migration-D3O_yVck.js} +1 -1
  13. package/core/built/admin/assets/activitypub/{account-migration-CNovr6W-.js → account-migration-D8FbP-Z-.js} +7 -7
  14. package/core/built/admin/assets/activitypub/activitypub.js +6 -6
  15. package/core/built/admin/assets/activitypub/{avatar-B3DEDB1l.js → avatar-11EA1VSs.js} +2 -2
  16. package/core/built/admin/assets/activitypub/{avatar-flipboard-Cqrg0XMx.js → avatar-flipboard-jfbLG-xe.js} +2 -2
  17. package/core/built/admin/assets/activitypub/{bluesky-sharing-Ceslb1_M.js → bluesky-sharing-f1jTQe2P.js} +16 -16
  18. package/core/built/admin/assets/activitypub/{content-formatters-BpQARh28.js → content-formatters-nmTOPY9M.js} +3 -3
  19. package/core/built/admin/assets/activitypub/{copy-D9UWIAan.js → copy-BCwo9ep3.js} +2 -2
  20. package/core/built/admin/assets/activitypub/{dist-DHAjBcV3.js → dist-BEfX-0aW.js} +12 -12
  21. package/core/built/admin/assets/activitypub/{domain-C1qQvJ8C.js → domain-AaDlZlrA.js} +9 -9
  22. package/core/built/admin/assets/activitypub/{edit-profile-BcH-jEfM.js → edit-profile-DPi8Tl5M.js} +945 -945
  23. package/core/built/admin/assets/activitypub/{explore-AxwBh9dI.js → explore-CxTzjILQ.js} +8 -8
  24. package/core/built/admin/assets/activitypub/{feed-BaOlbh7a.js → feed-DPv5x2MX.js} +33 -33
  25. package/core/built/admin/assets/activitypub/{field-B7mYYQpN.js → field-DbgA8lat.js} +3 -3
  26. package/core/built/admin/assets/activitypub/{hash-BC9Q0N4c.js → hash-BWuZoYkA.js} +2 -2
  27. package/core/built/admin/assets/activitypub/{inbox-DQ4ZnC1m.js → inbox-35VDQ2K2.js} +2 -2
  28. package/core/built/admin/assets/activitypub/{inbox-vXez-5uJ.js → inbox-DSrF_i1O.js} +85 -85
  29. package/core/built/admin/assets/activitypub/{label-Chv5T8bw.js → label-CPjFBbo7.js} +3 -3
  30. package/core/built/admin/assets/activitypub/layout-CTgrezN7.js +1 -0
  31. package/core/built/admin/assets/activitypub/{moderation-DtSM2i1I.js → moderation-BkDC1XVZ.js} +11 -11
  32. package/core/built/admin/assets/activitypub/{note-Dckp4S-2.js → note-xEyH4xT-.js} +7 -7
  33. package/core/built/admin/assets/activitypub/{notifications-CG5T22vg.js → notifications-DClaL3ui.js} +9 -9
  34. package/core/built/admin/assets/activitypub/{preferences-D_799jdl.js → preferences-DpnOa-4Z.js} +20 -20
  35. package/core/built/admin/assets/activitypub/{profile-D2JfXBQl.js → profile-Dvi9mz-6.js} +237 -237
  36. package/core/built/admin/assets/activitypub/{reply-CpdMeAO5.js → reply-Cs9ySd6Z.js} +2 -2
  37. package/core/built/admin/assets/activitypub/{routes-CCHe2dQp.js → routes-DFBeTnfn.js} +1139 -1150
  38. package/core/built/admin/assets/activitypub/{separator-BI1FLsYM.js → separator-C5u_VjzY.js} +3 -3
  39. package/core/built/admin/assets/activitypub/{settings-kx-9Enkz.js → settings-CV2fFu6D.js} +13 -13
  40. package/core/built/admin/assets/activitypub/{site-x4_IjVJ0.js → site-_TgDd_EM.js} +81 -81
  41. package/core/built/admin/assets/activitypub/{step-1-DIDg4aHb.js → step-1-BfnNL2oF.js} +5 -5
  42. package/core/built/admin/assets/activitypub/{step-2-BfGe8BYt.js → step-2-DLndZkO-.js} +6 -6
  43. package/core/built/admin/assets/activitypub/{step-3-mE9spAqw.js → step-3-A-PZTtRE.js} +10 -10
  44. package/core/built/admin/assets/activitypub/{tabs-CyPRQQ1N.js → tabs-BUbz7tea.js} +321 -319
  45. package/core/built/admin/assets/activitypub/{topic-filter-DLHBZT93.js → topic-filter-BdX_xYYT.js} +3 -3
  46. package/core/built/admin/assets/activitypub/{use-activity-pub-queries-VzbN99u_.js → use-activity-pub-queries-EwuRemHh.js} +2 -2
  47. package/core/built/admin/assets/activitypub/{use-navigate-with-base-path-CAwXTmqH.js → use-navigate-with-base-path-cy4ACBc4.js} +92 -90
  48. package/core/built/admin/assets/activitypub/{use-reply-chain-data-BAB6fNGt.js → use-reply-chain-data-CyFcGfan.js} +5 -5
  49. package/core/built/admin/assets/activitypub/{x-C8tAnXly.js → x-7zk8mV2p.js} +2 -2
  50. package/core/built/admin/assets/audience-CkBb2LlO.js +1 -0
  51. package/core/built/admin/assets/{automated-emails-of1MvVsv.js → automated-emails-6t2bIRWf.js} +2 -2
  52. package/core/built/admin/assets/{automations-B7-y3dXt.js → automations-DhKeS_U5.js} +1 -1
  53. package/core/built/admin/assets/{automations-DRFjW5fw.js → automations-cMllU545.js} +1 -1
  54. package/core/built/admin/assets/{bluesky-sharing-BbbjWdzs.js → bluesky-sharing-DPGpyD-G.js} +1 -1
  55. package/core/built/admin/assets/{box-BWbhXh21.js → box-By_EgGtm.js} +1 -1
  56. package/core/built/admin/assets/{breadcrumb-B-M9Ip_2.js → breadcrumb-DIfcF5ow.js} +1 -1
  57. package/core/built/admin/assets/build-CsiH26uS.js +1 -0
  58. package/core/built/admin/assets/{card-DGoDJzrn.js → card-aUHX0hyR.js} +1 -1
  59. package/core/built/admin/assets/{chart-BuRT2Y6p.js → chart-BnXyfH11.js} +1 -1
  60. package/core/built/admin/assets/{chart-helpers-xiSsW8r0.js → chart-helpers-DV0FkdcD.js} +1 -1
  61. package/core/built/admin/assets/{checkbox-EhJKHFdZ.js → checkbox-BIl077a0.js} +1 -1
  62. package/core/built/admin/assets/{chunk.361.3cdb584b973959e52ccc.js → chunk.332.05991a101d67f732dec3.js} +16 -12
  63. package/core/built/admin/assets/chunk.524.87b52f4fada529df2c4c.js +25 -0
  64. package/core/built/admin/assets/chunk.582.340bcca873e806cbd4bb.js +27 -0
  65. package/core/built/admin/assets/{code-editor-view-CZtJFGhR.js → code-editor-view-C0aWNwdl.js} +1 -1
  66. package/core/built/admin/assets/comments-qwhIjH3c.js +1 -0
  67. package/core/built/admin/assets/{container-BYz2Ed39.js → container-LiZf3XdY.js} +1 -1
  68. package/core/built/admin/assets/{data-list-16rkJYYi.js → data-list-CupF7bjM.js} +1 -1
  69. package/core/built/admin/assets/{date-range-select-CTlyo8zW.js → date-range-select-dXc-GsVe.js} +1 -1
  70. package/core/built/admin/assets/{disabled-sources-indicator-qs2GfLvl.js → disabled-sources-indicator-DYAXW-6V.js} +1 -1
  71. package/core/built/admin/assets/dist-BUKmD51u.js +1 -0
  72. package/core/built/admin/assets/{dist-2Tnz2Yfq.js → dist-CNlMj7ML.js} +1 -1
  73. package/core/built/admin/assets/{dist-DGzPJ_bO.js → dist-Cb5RSHSg.js} +1 -1
  74. package/core/built/admin/assets/{dist-P_ohUlTi.js → dist-CqO2lWS0.js} +1 -1
  75. package/core/built/admin/assets/{domain-CPNXKVOV.js → domain-B5ogXUBa.js} +1 -1
  76. package/core/built/admin/assets/{dropzone-BmCXnw6J.js → dropzone-BnFG7dDP.js} +1 -1
  77. package/core/built/admin/assets/{edit-profile-SGrJve5M.js → edit-profile-B07jCZy9.js} +1 -1
  78. package/core/built/admin/assets/editor-DDYSOXGN.js +8 -0
  79. package/core/built/admin/assets/{empty-indicator-oX1u1Pkn.js → empty-indicator-WB1WgT9h.js} +1 -1
  80. package/core/built/admin/assets/{explore-CUYLv9G9.js → explore-Ca1y8HW_.js} +1 -1
  81. package/core/built/admin/assets/{feed-CPdJMttL.js → feed-x4iFMBP4.js} +1 -1
  82. package/core/built/admin/assets/field-ChCAabZg.js +1 -0
  83. package/core/built/admin/assets/filter-sources-LdtH1e-T.js +1 -0
  84. package/core/built/admin/assets/{filters-bXkkWro8.js → filters-CLehPo_D.js} +1 -1
  85. package/core/built/admin/assets/filters-CjVKRP8U.js +1 -0
  86. package/core/built/admin/assets/{ghost-05f9a7182ed56903dba05878a5b8422b.js → ghost-7f01131115186d1af84ead76e79646dd.js} +275 -255
  87. package/core/built/admin/assets/{ghost-3063eae29e6c9cc6d6e7131928a27da0.css → ghost-d38c30b866e59f2e06a357b5eb844fe5.css} +1 -1
  88. package/core/built/admin/assets/{ghost-dark-c8d0ab7f515b2010c173e23e9b32ea8a.css → ghost-dark-1bffd5099ba7a749303133efacf8480b.css} +1 -1
  89. package/core/built/admin/assets/gift-link-modal-Buqh2vXh.js +1 -0
  90. package/core/built/admin/assets/{gift-links-nBxJjZiQ.js → gift-links-CsUhefWG.js} +1 -1
  91. package/core/built/admin/assets/growth-DajOBdN0.js +1 -0
  92. package/core/built/admin/assets/growth-Dy2UOSay.js +3 -0
  93. package/core/built/admin/assets/{hooks-BApwwBE0.js → hooks-CixO01lL.js} +1 -1
  94. package/core/built/admin/assets/{inbox-Xx1CdnfO.js → inbox-BUOLDoji.js} +13 -13
  95. package/core/built/admin/assets/index-08n05-rc.js +67 -0
  96. package/core/built/admin/assets/index-DgUzgFvS.css +1 -0
  97. package/core/built/admin/assets/{inline-EMAm3okk.js → inline-Cw-gigDW.js} +1 -1
  98. package/core/built/admin/assets/input-group-uSK3XT7b.js +1 -0
  99. package/core/built/admin/assets/{input-surface-BpAjZXY5.js → input-surface-BdOe4KzO.js} +1 -1
  100. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +9132 -9087
  101. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +121 -121
  102. package/core/built/admin/assets/koenig-lexical/style.css +1 -1
  103. package/core/built/admin/assets/{koenig-lexical-B61IALf5.js → koenig-lexical-BFc4yFed.js} +82 -82
  104. package/core/built/admin/assets/{kpi-card-DCdlyaSS.js → kpi-card-BN-E6WYX.js} +1 -1
  105. package/core/built/admin/assets/{kpi-card-BlZaG1Lo.js → kpi-card-CCt-qeWD.js} +1 -1
  106. package/core/built/admin/assets/{kpi-helpers-DpAuS1Nb.js → kpi-helpers-DlxTbTs4.js} +1 -1
  107. package/core/built/admin/assets/{kpi-tabs-Dme44bQS.js → kpi-tabs-BlcUttpn.js} +1 -1
  108. package/core/built/admin/assets/{label-dSs5aepE.js → label-AVrxwmsz.js} +1 -1
  109. package/core/built/admin/assets/label-picker-DVnpu_IF.js +1 -0
  110. package/core/built/admin/assets/labels-DAIqKed7.js +1 -0
  111. package/core/built/admin/assets/layout-B0YLh7R9.js +1 -0
  112. package/core/built/admin/assets/link-helpers-D9LsDOkG.js +1 -0
  113. package/core/built/admin/assets/{list-page-B6ANtzkd.js → list-page-CdsnQR9N.js} +1 -1
  114. package/core/built/admin/assets/member-custom-fields-C8NpZYza.js +1 -0
  115. package/core/built/admin/assets/member-detail-CIVAdKXf.js +1 -0
  116. package/core/built/admin/assets/members-oLPiSkXD.js +10 -0
  117. package/core/built/admin/assets/modals-CB7v-k3T.js +31 -0
  118. package/core/built/admin/assets/moderation-CQ3urTFE.js +1 -0
  119. package/core/built/admin/assets/newsletter-rbeDX9Wk.js +1 -0
  120. package/core/built/admin/assets/{newsletters-D1jbtV-h.js → newsletters-BXJbJgA-.js} +1 -1
  121. package/core/built/admin/assets/newsletters-vt3Ni1Ov.js +1 -0
  122. package/core/built/admin/assets/note-Dk877BTv.js +1 -0
  123. package/core/built/admin/assets/notifications-CPlLjPxO.js +1 -0
  124. package/core/built/admin/assets/{offers-CrY6nSRR.js → offers-DEniVRD5.js} +1 -1
  125. package/core/built/admin/assets/onboarding-route-DdStOtjy.js +1 -0
  126. package/core/built/admin/assets/overview--4V-kx9c.js +1 -0
  127. package/core/built/admin/assets/overview-DSLv30oz.js +1 -0
  128. package/core/built/admin/assets/{page-header-op0eN36I.js → page-header-pp4dWmOS.js} +1 -1
  129. package/core/built/admin/assets/{pages-BbNWFcGj.js → pages-B4goRPUn.js} +1 -1
  130. package/core/built/admin/assets/{post-analytics-WwRDEnrV.js → post-analytics-BMJrI81R.js} +1 -1
  131. package/core/built/admin/assets/post-analytics-header-pU2m9ZuV.js +1 -0
  132. package/core/built/admin/assets/post-analytics-provider-Btml5vJI.js +1 -0
  133. package/core/built/admin/assets/{post-share-modal-Dx-MEjhI.js → post-share-modal-AcUZkbfL.js} +1 -1
  134. package/core/built/admin/assets/{posts-EWpZIjkr.js → posts-DhXE7b6r.js} +1 -1
  135. package/core/built/admin/assets/preferences-Ru4CRPHC.js +5 -0
  136. package/core/built/admin/assets/profile-BNb1oj7D.js +1 -0
  137. package/core/built/admin/assets/{referrers-BCfFYSHc.js → referrers-pmJT4k3-.js} +1 -1
  138. package/core/built/admin/assets/{router-provider-C1R4AZ2o.js → router-provider-BmEZf6c6.js} +1 -1
  139. package/core/built/admin/assets/select-B0HteHrp.js +1 -0
  140. package/core/built/admin/assets/settings-B1C9Q3wo.js +4 -0
  141. package/core/built/admin/assets/{settings-H6HxFgfI.js → settings-BGgk7oCI.js} +1 -1
  142. package/core/built/admin/assets/{shade-app-B3heOAfz-CGXQwWeO.js → shade-app-B3heOAfz-BhQXxNkT.js} +1 -1
  143. package/core/built/admin/assets/{share-modal-BJCadJyp.js → share-modal-BeuydaBf.js} +1 -1
  144. package/core/built/admin/assets/social-urls-DA2lXrhW.js +8 -0
  145. package/core/built/admin/assets/{sort-button-DyYlfYp7.js → sort-button-Df4t2AL8.js} +1 -1
  146. package/core/built/admin/assets/{source-icon-_ubRDbg_.js → source-icon-ajoaKb41.js} +1 -1
  147. package/core/built/admin/assets/{stack-xiN0eQ6Z.js → stack-CIfOAAVf.js} +1 -1
  148. package/core/built/admin/assets/{stats-BQZMGMvn.js → stats-C_lfxZAw.js} +1 -1
  149. package/core/built/admin/assets/{stats-view-Doei-qCX.js → stats-view-Cxot1iD8.js} +1 -1
  150. package/core/built/admin/assets/{step-1-IKiLBPoC.js → step-1-ogAA4lDr.js} +1 -1
  151. package/core/built/admin/assets/{step-2-jJ41PsDN.js → step-2-C5Xini-7.js} +1 -1
  152. package/core/built/admin/assets/{step-3-CaqFAEqf.js → step-3-CZv1FV81.js} +2 -2
  153. package/core/built/admin/assets/{switch-t-WyENU2.js → switch-CyQcwMAv.js} +1 -1
  154. package/core/built/admin/assets/{table-DxUL78jU.js → table-DFj1wiYv.js} +1 -1
  155. package/core/built/admin/assets/tabs-D4y1gqUd.js +1 -0
  156. package/core/built/admin/assets/{tags-CNujGwkT.js → tags-Dmft-Y4O.js} +1 -1
  157. package/core/built/admin/assets/{text-DwtjwGkh.js → text-DU9Hv33_.js} +1 -1
  158. package/core/built/admin/assets/textarea-d9qEkMOP.js +1 -0
  159. package/core/built/admin/assets/{tiers-BgYLWRJv.js → tiers-DYsa4DCo.js} +1 -1
  160. package/core/built/admin/assets/{toggle-group-BeEpQ69p.js → toggle-group-R2QMFvXW.js} +1 -1
  161. package/core/built/admin/assets/{topic-filter-Bb5MOD-1.js → topic-filter-DGDxDalo.js} +1 -1
  162. package/core/built/admin/assets/{use-analytics-data-Cds6XDjg.js → use-analytics-data-C76v4LIp.js} +1 -1
  163. package/core/built/admin/assets/use-filter-params-WrdhAISv.js +1 -0
  164. package/core/built/admin/assets/{use-growth-stats-D8uf98-G.js → use-growth-stats-aNXJOmQr.js} +1 -1
  165. package/core/built/admin/assets/{use-post-referrers-CRtgyuXB.js → use-post-referrers-CG0SZioj.js} +1 -1
  166. package/core/built/admin/assets/{use-reply-chain-data-DbKYX50v.js → use-reply-chain-data-RLdPKRmS.js} +1 -1
  167. package/core/built/admin/assets/{use-simple-pagination-DC01kpo0.js → use-simple-pagination-DgcF69KI.js} +1 -1
  168. package/core/built/admin/assets/{use-tinybird-query-C7bQRJWw.js → use-tinybird-query-C1R_Kbmy.js} +1 -1
  169. package/core/built/admin/assets/{use-tinybird-token-Az4tam7_.js → use-tinybird-token-C9fPjVM_.js} +1 -1
  170. package/core/built/admin/assets/{users-CcSCQFcQ.js → users-Dkv8NQZe.js} +1 -1
  171. package/core/built/admin/assets/{virtual-list-Dhuh9fGx.js → virtual-list-BmTUzeoF.js} +1 -1
  172. package/core/built/admin/assets/web-BqW0b0_C.js +1 -0
  173. package/core/built/admin/assets/web-Cy5GKMWM.js +2 -0
  174. package/core/built/admin/index.html +18 -18
  175. package/core/frontend/services/data/fetch-data.js +10 -5
  176. package/core/frontend/services/proxy.js +7 -8
  177. package/core/frontend/services/routing/api-adapter.js +115 -0
  178. package/core/frontend/services/routing/api-adapter.ts +157 -0
  179. package/core/frontend/services/routing/collection-router.js +5 -3
  180. package/core/frontend/services/routing/controllers/index.js +1 -1
  181. package/core/frontend/services/routing/controllers/rss.js +26 -31
  182. package/core/frontend/services/routing/controllers/rss.ts +66 -0
  183. package/core/frontend/services/routing/controllers/static.js +8 -7
  184. package/core/frontend/services/routing/parent-router.js +17 -4
  185. package/core/frontend/services/routing/permalink-adapter.js +26 -0
  186. package/core/frontend/services/routing/permalink-adapter.ts +23 -0
  187. package/core/frontend/services/routing/registry.js +59 -33
  188. package/core/frontend/services/routing/router-manager.js +14 -18
  189. package/core/frontend/services/routing/static-routes-router.js +11 -13
  190. package/core/frontend/services/routing/taxonomy-router.js +6 -2
  191. package/core/server/api/endpoints/automation-action-links.js +68 -0
  192. package/core/server/api/endpoints/automation-action-links.ts +46 -0
  193. package/core/server/api/endpoints/index.js +4 -0
  194. package/core/server/api/endpoints/settings.js +0 -2
  195. package/core/server/api/endpoints/utils/serializers/output/utils/url.js +11 -1
  196. package/core/server/data/migrations/versions/6.55/2026-07-27-11-25-44-add-to-hash-to-redirects.js +51 -0
  197. package/core/server/data/migrations/versions/6.55/2026-07-28-12-08-00-remove-routes-hash-from-settings.js +5 -0
  198. 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
  199. package/core/server/data/migrations/versions/6.56/2026-08-04-14-42-39-restrict-notification-add-permission-to-administrators.js +12 -0
  200. package/core/server/data/schema/commands.js +5 -0
  201. package/core/server/data/schema/default-settings/default-settings.json +0 -4
  202. package/core/server/data/schema/fixtures/fixtures.json +2 -2
  203. package/core/server/data/schema/schema.js +6 -1
  204. package/core/server/models/post.js +10 -1
  205. package/core/server/services/automations/automations-api.js +15 -0
  206. package/core/server/services/automations/automations-api.ts +19 -0
  207. package/core/server/services/automations/automations-repository.ts +20 -0
  208. package/core/server/services/automations/database-automations-repository.js +132 -30
  209. package/core/server/services/automations/database-automations-repository.ts +141 -12
  210. package/core/server/services/automations/database-date.js +10 -0
  211. package/core/server/services/automations/database-date.ts +5 -0
  212. package/core/server/services/automations/get-scheduler-idempotency-key.js +13 -0
  213. package/core/server/services/automations/get-scheduler-idempotency-key.ts +8 -0
  214. package/core/server/services/automations/poll.js +7 -2
  215. package/core/server/services/automations/poll.ts +10 -2
  216. package/core/server/services/automations/scheduler-poll-time.js +40 -0
  217. package/core/server/services/automations/scheduler-poll-time.ts +38 -0
  218. package/core/server/services/automations/service.js +19 -3
  219. package/core/server/services/automations/service.ts +20 -3
  220. package/core/server/services/comments/comments-service-emails.js +10 -7
  221. package/core/server/services/comments/comments-service.js +9 -4
  222. package/core/server/services/email-rendering/partials/email-wrapper.hbs +3 -1
  223. package/core/server/services/email-service/email-service-wrapper.js +6 -1
  224. package/core/server/services/email-service/email-templates/partials/styles.hbs +8 -0
  225. package/core/server/services/email-service/newsletter-email-event-storage.js +41 -10
  226. package/core/server/services/gift-links/CONTEXT.md +9 -0
  227. package/core/server/services/gifts/CONTEXT.md +55 -0
  228. package/core/server/services/gifts/README.md +28 -0
  229. package/core/server/services/gifts/gift-bookshelf-repository.js +65 -45
  230. package/core/server/services/gifts/gift-bookshelf-repository.ts +203 -74
  231. package/core/server/services/gifts/gift-checkout-adapter.js +59 -0
  232. package/core/server/services/gifts/gift-checkout-offer.js +74 -0
  233. package/core/server/services/gifts/gift-checkout-offer.ts +127 -0
  234. package/core/server/services/gifts/gift-codec.js +63 -0
  235. package/core/server/services/gifts/gift-codec.ts +62 -0
  236. package/core/server/services/gifts/gift-controller.js +6 -31
  237. package/core/server/services/gifts/gift-controller.ts +6 -69
  238. package/core/server/services/gifts/gift-email-service.ts +3 -2
  239. package/core/server/services/gifts/gift-schema.js +34 -0
  240. package/core/server/services/gifts/gift-schema.ts +39 -0
  241. package/core/server/services/gifts/gift-service-wrapper.js +13 -11
  242. package/core/server/services/gifts/gift-service.js +265 -52
  243. package/core/server/services/gifts/gift-service.ts +431 -97
  244. package/core/server/services/gifts/gift.ts +16 -39
  245. package/core/server/services/lib/dynamic-redirect-manager.js +12 -3
  246. package/core/server/services/link-redirection/link-redirect-repository.js +41 -4
  247. package/core/server/services/link-redirection/link-redirect.js +2 -0
  248. package/core/server/services/link-redirection/link-redirect.ts +3 -1
  249. package/core/server/services/link-redirection/link-redirects-service.js +38 -2
  250. package/core/server/services/link-tracking/click-event.js +3 -0
  251. package/core/server/services/link-tracking/index.js +38 -6
  252. package/core/server/services/link-tracking/link-click-repository.js +35 -9
  253. package/core/server/services/link-tracking/link-click-tracking-service.js +54 -4
  254. package/core/server/services/member-attribution/outbound-link-tagger.js +3 -1
  255. package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +20 -4
  256. package/core/server/services/member-welcome-emails/service.js +15 -3
  257. package/core/server/services/members/api.js +1 -2
  258. package/core/server/services/members/import-export/csv/formula.js +16 -0
  259. package/core/server/services/members/import-export/csv/formula.ts +14 -0
  260. package/core/server/services/members/import-export/csv/index.js +3 -1
  261. package/core/server/services/members/import-export/csv/index.ts +1 -0
  262. package/core/server/services/members/import-export/import/completion-email.js +22 -3
  263. package/core/server/services/members/import-export/import/completion-email.ts +29 -7
  264. package/core/server/services/members/import-export/import/importer.js +25 -4
  265. package/core/server/services/members/import-export/import/importer.ts +44 -5
  266. package/core/server/services/members/import-export/index.js +18 -1
  267. package/core/server/services/members/import-export/index.ts +32 -3
  268. package/core/server/services/members/members-api/controllers/router-controller.js +38 -36
  269. package/core/server/services/members/members-api/members-api.js +13 -7
  270. package/core/server/services/members/members-api/repositories/event-repository.js +4 -94
  271. package/core/server/services/members/members-api/services/member-bread-service.js +3 -3
  272. package/core/server/services/members/members-api/services/payments-service.js +6 -68
  273. package/core/server/services/members/service.js +8 -1
  274. package/core/server/services/members-custom-fields/schema.js +8 -1
  275. package/core/server/services/members-custom-fields/schema.ts +8 -0
  276. package/core/server/services/members-custom-fields/values-service.js +136 -8
  277. package/core/server/services/members-custom-fields/values-service.ts +152 -10
  278. package/core/server/services/mentions/service.js +5 -1
  279. package/core/server/services/oembed/oembed-service.js +44 -3
  280. package/core/server/services/posts/posts-service.js +11 -0
  281. package/core/server/services/route-settings/dynamic-routing-service.js +21 -52
  282. package/core/server/services/route-settings/index.js +1 -16
  283. package/core/server/services/route-settings/route-settings-parser.js +25 -25
  284. package/core/server/services/route-settings/route-settings-parser.ts +27 -27
  285. package/core/server/services/route-settings/validation-errors.js +68 -4
  286. package/core/server/services/route-settings/validation-errors.ts +79 -4
  287. package/core/server/services/settings/settings-service.js +0 -18
  288. package/core/server/services/stats/posts-stats-service.js +27 -23
  289. package/core/server/services/stripe/services/webhook/charge-refunded-event-service.js +1 -1
  290. package/core/server/services/stripe/services/webhook/checkout-session-event-service.js +16 -4
  291. package/core/server/services/stripe/stripe-api.js +10 -0
  292. package/core/server/services/url/url-service-facade.js +1 -1
  293. package/core/server/services/url/url-service-facade.ts +8 -1
  294. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  295. package/core/server/web/gift-preview/controller.js +10 -25
  296. package/core/shared/labs.js +2 -2
  297. package/package.json +8 -7
  298. package/pnpm-lock.yaml +28 -18
  299. package/pnpm-workspace.yaml +6 -4
  300. package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
  301. package/core/built/admin/assets/Growth-BTP1LthE.js +0 -3
  302. package/core/built/admin/assets/Newsletters-CSEX0myc.js +0 -1
  303. package/core/built/admin/assets/Overview-BjACEsJF.js +0 -1
  304. package/core/built/admin/assets/Web-CxxxjCcv.js +0 -2
  305. package/core/built/admin/assets/activitypub/layout-CziktJXo.js +0 -1
  306. package/core/built/admin/assets/audience-X_XsZivI.js +0 -1
  307. package/core/built/admin/assets/chunk.524.8365cd9280405c0f6216.js +0 -25
  308. package/core/built/admin/assets/chunk.582.61d078937e6102769f2c.js +0 -27
  309. package/core/built/admin/assets/comments-CvyXxOyN.js +0 -1
  310. package/core/built/admin/assets/constants-c-JNF9c2.js +0 -1
  311. package/core/built/admin/assets/dist-CjY-f5Z1.js +0 -1
  312. package/core/built/admin/assets/editor-BOG2YPUm.js +0 -8
  313. package/core/built/admin/assets/field-D0XISCK2.js +0 -1
  314. package/core/built/admin/assets/filter-sources-Bieps75g.js +0 -1
  315. package/core/built/admin/assets/filters-CuB-A7ma.js +0 -1
  316. package/core/built/admin/assets/gift-link-modal-su8MsRdg.js +0 -1
  317. package/core/built/admin/assets/growth-Ba6S5FQX.js +0 -1
  318. package/core/built/admin/assets/index-D8PbzuT9.css +0 -1
  319. package/core/built/admin/assets/index-ukW_0Uie.js +0 -67
  320. package/core/built/admin/assets/input-group-CGOXcy4l.js +0 -1
  321. package/core/built/admin/assets/label-picker-Uq91atex.js +0 -1
  322. package/core/built/admin/assets/labels-Cyayfd-e.js +0 -1
  323. package/core/built/admin/assets/layout-WUcz6tu1.js +0 -1
  324. package/core/built/admin/assets/link-helpers-tOlxmuH5.js +0 -1
  325. package/core/built/admin/assets/member-custom-fields-NCzrMuba.js +0 -1
  326. package/core/built/admin/assets/member-detail-DPWDtYnN.js +0 -1
  327. package/core/built/admin/assets/members-B9rO7qr4.js +0 -10
  328. package/core/built/admin/assets/modals-CxRaan1J.js +0 -31
  329. package/core/built/admin/assets/moderation-D8zon60E.js +0 -1
  330. package/core/built/admin/assets/multi-select-combobox-1wYm3nle.js +0 -1
  331. package/core/built/admin/assets/newsletter-DbYMRxRS.js +0 -1
  332. package/core/built/admin/assets/note-PiWPnnrh.js +0 -1
  333. package/core/built/admin/assets/notifications-DEgqqB9G.js +0 -1
  334. package/core/built/admin/assets/nql-BsgFMd93.js +0 -1
  335. package/core/built/admin/assets/onboarding-route-BzF6HeVn.js +0 -1
  336. package/core/built/admin/assets/overview-DixLeIqY.js +0 -1
  337. package/core/built/admin/assets/post-analytics-header-DsMoLx0R.js +0 -1
  338. package/core/built/admin/assets/post-analytics-provider-BJCPLMIb.js +0 -1
  339. package/core/built/admin/assets/preferences-D7-j6A4C.js +0 -5
  340. package/core/built/admin/assets/profile-Dl69QPp0.js +0 -1
  341. package/core/built/admin/assets/select-C1qDZnW4.js +0 -1
  342. package/core/built/admin/assets/settings-BbmC_Iqq.js +0 -4
  343. package/core/built/admin/assets/social-urls-BPL1nADp.js +0 -8
  344. package/core/built/admin/assets/tabs-BQcJQ1ke.js +0 -1
  345. package/core/built/admin/assets/textarea-Bjccvh_E.js +0 -1
  346. package/core/built/admin/assets/use-filter-params-DjXkhO54.js +0 -1
  347. package/core/built/admin/assets/web-LtHNiNBV.js +0 -1
  348. package/core/server/services/gifts/gift-repository.js +0 -2
  349. package/core/server/services/gifts/gift-repository.ts +0 -29
  350. package/core/server/services/route-settings/activation-bridge.js +0 -164
  351. package/core/server/services/route-settings/activation-bridge.ts +0 -210
  352. package/core/server/services/route-settings/default-settings-manager.js +0 -63
  353. package/core/server/services/route-settings/route-settings.js +0 -170
  354. package/core/server/services/route-settings/settings-loader.js +0 -60
  355. package/core/server/services/route-settings/settings-path-manager.js +0 -47
  356. package/core/server/services/route-settings/validate.js +0 -440
@@ -13,6 +13,7 @@ const express = require('../../../shared/express');
13
13
  const _ = require('lodash');
14
14
  const url = require('url');
15
15
  const security = require('@tryghost/security');
16
+ const {resolveResourceRead} = require('./api-adapter');
16
17
  const urlUtils = require('../../../shared/url-utils').default;
17
18
  const registry = require('./registry');
18
19
 
@@ -197,6 +198,11 @@ class ParentRouter {
197
198
 
198
199
  /**
199
200
  * @description Figure out if the router has a redirect enabled.
201
+ *
202
+ * A route claims a resource by reading it: `data: tag.bacon` on this router
203
+ * means /tag/bacon/ should redirect here from wherever else it lives. Only
204
+ * `read` entries claim a slug, and an entry can opt out with `redirect: false`.
205
+ *
200
206
  * @param {string} routerType
201
207
  * @param {string} slug
202
208
  * @returns {boolean}
@@ -204,14 +210,21 @@ class ParentRouter {
204
210
  isRedirectEnabled(routerType, slug) {
205
211
  debug('isRedirectEnabled', this.name, this.route && this.route.value, routerType, slug);
206
212
 
207
- if (!this.data || !Object.keys(this.data.router)) {
213
+ if (!this.data) {
208
214
  return false;
209
215
  }
210
216
 
211
- return _.find(this.data.router, function (entries, type) {
212
- if (routerType === type) {
213
- return _.find(entries, {redirect: true, slug: slug});
217
+ // Top-level shorthand (`data: tag.bacon`) is a single unnamed entry.
218
+ const entries = typeof this.data === 'string' ? [this.data] : Object.values(this.data);
219
+
220
+ return entries.some((entry) => {
221
+ if (typeof entry !== 'string' && entry.redirect === false) {
222
+ return false;
214
223
  }
224
+
225
+ const read = resolveResourceRead(entry);
226
+
227
+ return !!read && read.resource === routerType && read.slug === slug;
215
228
  });
216
229
  }
217
230
 
@@ -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
- * @description The router registry is helpful for debugging purposes and let's you search existing routes and routers.
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
- module.exports = {
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 Get's called if you register a url pattern in express.
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 Get's called if you register a router in express.
43
+ * @description Gets called if you register a router in express.
20
44
  * @param {string} name
21
- * @param {import('express').Router} router
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 {import('express').Router}
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 it's internal router name
69
+ * Gets a router by its internal router name
46
70
  * @param {string} name internal router name
47
- * @returns {import('express').Router}
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();
@@ -1,5 +1,4 @@
1
1
  const debug = require('@tryghost/debug')('frontend:routing');
2
- const _ = require('lodash');
3
2
  const StaticRoutesRouter = require('./static-routes-router');
4
3
  const StaticPagesRouter = require('./static-pages-router');
5
4
  const CollectionRouter = require('./collection-router');
@@ -91,10 +90,10 @@ class RouterManager {
91
90
  * 5. Static Pages: Weaker than collections, because we first try to find a post slug and fallback to lookup a static page.
92
91
  * 6. Internal Apps: Weakest
93
92
  *
94
- * @param {object} routerSettings
93
+ * @param {RouteSettings} routeSettings
95
94
  */
96
- start(routerSettings) {
97
- debug('routing start', routerSettings);
95
+ start(routeSettings) {
96
+ debug('routing start', routeSettings);
98
97
  const RESOURCE_CONFIG = require(`./config`);
99
98
 
100
99
  const unsubscribeRouter = new UnsubscribeRouter();
@@ -111,30 +110,30 @@ class RouterManager {
111
110
  this.siteRouter.mountRouter(previewRouter.router());
112
111
  this.registry.setRouter('previewRouter', previewRouter);
113
112
 
114
- _.each(routerSettings.routes, (value, key) => {
115
- const staticRoutesRouter = new StaticRoutesRouter(key, value, this.routerCreated.bind(this));
113
+ for (const route of routeSettings.routes) {
114
+ const staticRoutesRouter = new StaticRoutesRouter(route.path, route, this.routerCreated.bind(this));
116
115
  this.siteRouter.mountRouter(staticRoutesRouter.router());
117
116
 
118
117
  this.registry.setRouter(staticRoutesRouter.identifier, staticRoutesRouter);
119
- });
118
+ }
120
119
 
121
- _.each(routerSettings.collections, (value, key) => {
122
- const collectionRouter = new CollectionRouter(key, value, RESOURCE_CONFIG, this.routerCreated.bind(this));
120
+ for (const collection of routeSettings.collections) {
121
+ const collectionRouter = new CollectionRouter(collection.path, collection, RESOURCE_CONFIG, this.routerCreated.bind(this));
123
122
  this.siteRouter.mountRouter(collectionRouter.router());
124
123
  this.registry.setRouter(collectionRouter.identifier, collectionRouter);
125
- });
124
+ }
126
125
 
127
126
  const staticPagesRouter = new StaticPagesRouter(RESOURCE_CONFIG, this.routerCreated.bind(this));
128
127
  this.siteRouter.mountRouter(staticPagesRouter.router());
129
128
 
130
129
  this.registry.setRouter('staticPagesRouter', staticPagesRouter);
131
130
 
132
- _.each(routerSettings.taxonomies, (value, key) => {
133
- const taxonomyRouter = new TaxonomyRouter(key, value, RESOURCE_CONFIG, this.routerCreated.bind(this));
131
+ for (const [key, permalink] of Object.entries(routeSettings.taxonomies)) {
132
+ const taxonomyRouter = new TaxonomyRouter(key, permalink, RESOURCE_CONFIG, this.routerCreated.bind(this));
134
133
  this.siteRouter.mountRouter(taxonomyRouter.router());
135
134
 
136
135
  this.registry.setRouter(taxonomyRouter.identifier, taxonomyRouter);
137
- });
136
+ }
138
137
 
139
138
  const appRouter = new ParentRouter('AppsRouter');
140
139
  this.siteRouter.mountRouter(appRouter.router());
@@ -181,15 +180,12 @@ module.exports = RouterManager;
181
180
 
182
181
  /**
183
182
  * @typedef {Object} RouterConfig
184
- * @property {RouteSettings} [routeSettings] - JSON config representing routes
183
+ * @property {RouteSettings} [routeSettings] - parsed route settings domain model
185
184
  * @property {URLServiceFacade} urlService - resource-based URL service facade
186
185
  */
187
186
 
188
187
  /**
189
- * @typedef {Object} RouteSettings
190
- * @property {Object} routes
191
- * @property {Object} collections
192
- * @property {Object} taxonomies
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 || {query: {}, router: {}};
18
+ this.data = object.data || {};
19
19
  this.routerName = mainRoute === '/' ? 'index' : mainRoute.replace(/\//g, '');
20
20
  this.routerCreated = routerCreated;
21
21
 
@@ -30,12 +30,12 @@ class StaticRoutesRouter extends ParentRouter {
30
30
  this.limit = object.limit;
31
31
  this.order = object.order;
32
32
 
33
- this.controller = object.controller;
33
+ this.type = object.type;
34
34
 
35
35
  debug(this.route.value, this.templates, this.filter, this.data);
36
36
  this._registerChannelRoutes();
37
37
  } else {
38
- this.contentType = object.content_type;
38
+ this.contentType = object.contentType;
39
39
  debug(this.route.value, this.templates);
40
40
  this._registerStaticRoute();
41
41
  }
@@ -56,11 +56,11 @@ class StaticRoutesRouter extends ParentRouter {
56
56
  }
57
57
 
58
58
  // REGISTER: channel route
59
- this.mountRoute(this.route.value, controllers[this.controller]);
59
+ this.mountRoute(this.route.value, controllers[this.type]);
60
60
 
61
61
  // REGISTER: pagination
62
62
  this.router().param('page', middleware.pageParam);
63
- this.mountRoute(urlUtils.urlJoin(this.route.value, 'page', ':page(\\d+)'), controllers[this.controller]);
63
+ this.mountRoute(urlUtils.urlJoin(this.route.value, 'page', ':page(\\d+)'), controllers[this.type]);
64
64
 
65
65
  this.routerCreated(this);
66
66
  }
@@ -74,13 +74,13 @@ class StaticRoutesRouter extends ParentRouter {
74
74
  */
75
75
  _prepareChannelContext(req, res, next) {
76
76
  res.routerOptions = {
77
- type: this.controller,
77
+ type: this.type,
78
78
  name: this.routerName,
79
79
  context: [this.routerName],
80
80
  filter: this.filter,
81
81
  limit: this.limit,
82
82
  order: this.order,
83
- data: this.data.query,
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.query,
120
+ data: this.data,
121
121
  context: [this.routerName],
122
122
  contentType: this.contentType
123
123
  };
@@ -131,11 +131,9 @@ class StaticRoutesRouter extends ParentRouter {
131
131
  * @returns {boolean}
132
132
  */
133
133
  isChannel(object) {
134
- if (object && object.controller && object.controller === 'channel') {
135
- return true;
136
- }
137
-
138
- return this.controller === 'channel';
134
+ // parseRouteSettings always sets `type` ('channel' | 'template') on
135
+ // every route, so a channel is simply `type === 'channel'`.
136
+ return object?.type === 'channel';
139
137
  }
140
138
  }
141
139
 
@@ -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: {[this.taxonomyKey]: this.RESOURCE_CONFIG.QUERY[this.taxonomyKey]},
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,68 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ const automationsApi = __importStar(require("../../services/automations/automations-api"));
37
+ const controller = {
38
+ docName: 'automation_action_links',
39
+ browse: {
40
+ headers: {
41
+ cacheInvalidate: false
42
+ },
43
+ options: [
44
+ 'automation_id',
45
+ 'action_id'
46
+ ],
47
+ validation: {
48
+ options: {
49
+ automation_id: {
50
+ required: true
51
+ },
52
+ action_id: {
53
+ required: true
54
+ }
55
+ }
56
+ },
57
+ permissions: {
58
+ docName: 'automations',
59
+ method: 'read'
60
+ },
61
+ async query(frame) {
62
+ return {
63
+ data: await automationsApi.browseActionLinks(frame.options.automation_id, frame.options.action_id)
64
+ };
65
+ }
66
+ }
67
+ };
68
+ module.exports = controller;
@@ -0,0 +1,46 @@
1
+ import * as automationsApi from '../../services/automations/automations-api';
2
+
3
+ type BrowseFrame = {
4
+ options: {
5
+ automation_id: string;
6
+ action_id: string;
7
+ };
8
+ };
9
+
10
+ const controller = {
11
+ docName: 'automation_action_links',
12
+
13
+ browse: {
14
+ headers: {
15
+ cacheInvalidate: false
16
+ },
17
+ options: [
18
+ 'automation_id',
19
+ 'action_id'
20
+ ],
21
+ validation: {
22
+ options: {
23
+ automation_id: {
24
+ required: true
25
+ },
26
+ action_id: {
27
+ required: true
28
+ }
29
+ }
30
+ },
31
+ permissions: {
32
+ docName: 'automations',
33
+ method: 'read'
34
+ },
35
+ async query(frame: BrowseFrame) {
36
+ return {
37
+ data: await automationsApi.browseActionLinks(
38
+ frame.options.automation_id,
39
+ frame.options.action_id
40
+ )
41
+ };
42
+ }
43
+ }
44
+ };
45
+
46
+ module.exports = controller;
@@ -11,6 +11,10 @@ module.exports = {
11
11
  return apiFramework.pipeline(require('./automations'), localUtils);
12
12
  },
13
13
 
14
+ get automationActionLinks() {
15
+ return apiFramework.pipeline(require('./automation-action-links'), localUtils);
16
+ },
17
+
14
18
  get automationEmailPreviews() {
15
19
  return apiFramework.pipeline(require('./automation-email-previews'), localUtils);
16
20
  },
@@ -174,8 +174,6 @@ const controller = {
174
174
  async query(frame) {
175
175
  const content = await fs.readFile(frame.file.path, 'utf8');
176
176
  await routeSettings.api.upload(content);
177
- const getRoutesHash = () => routeSettings.api.getCurrentHash();
178
- await settingsService.syncRoutesHash(getRoutesHash);
179
177
  }
180
178
  },
181
179
 
@@ -24,7 +24,17 @@ const forPost = (id, attrs, frame, type = 'posts') => {
24
24
  return attrs;
25
25
  }
26
26
 
27
- attrs.url = urlService.facade.getUrlForResource({...attrs, id, type}, {absolute: true});
27
+ // Diagnostic only: hand the compare-mode URL service the producing api
28
+ // endpoint. The parity/thin-resource logs capture a caller stack, but it
29
+ // truncates at the async api-framework boundary and never names the
30
+ // endpoint; the api-framework Frame carries it, so pass it through. Omitted
31
+ // for non-api callers (mentions, email) whose frame has no endpoint fields.
32
+ const options = {absolute: true};
33
+ if (frame && (frame.docName || frame.method)) {
34
+ options.serializerContext = {apiType: frame.apiType, docName: frame.docName, method: frame.method};
35
+ }
36
+
37
+ attrs.url = urlService.facade.getUrlForResource({...attrs, id, type}, options);
28
38
 
29
39
  /**
30
40
  * CASE: admin api should serve preview urls
@@ -0,0 +1,51 @@
1
+ const logging = require('@tryghost/logging');
2
+ const {combineNonTransactionalMigrations, createAddColumnMigration, createNonTransactionalMigration} = require('../../utils');
3
+ const {addIndex, addUnique, dropIndex, dropUnique, getIndexes} = require('../../../schema/commands');
4
+
5
+ const uniqueColumns = ['automation_action_revision_id', 'to_hash'];
6
+ const leftoverIndexName = 'redirects_automation_action_revision_id_index';
7
+
8
+ module.exports = combineNonTransactionalMigrations(
9
+ createAddColumnMigration('redirects', 'to_hash', {
10
+ type: 'binary',
11
+ maxlength: 32,
12
+ nullable: true
13
+ }, {algorithm: 'auto'}),
14
+ createNonTransactionalMigration(
15
+ async function up(knex) {
16
+ await addUnique('redirects', uniqueColumns, knex);
17
+
18
+ // A previous down() leaves a standalone index behind (see below).
19
+ // InnoDB drops the index it created for the foreign key itself once
20
+ // the composite unique can serve the constraint, but not one we
21
+ // created, so drop it here to keep a rolled-back-and-remigrated
22
+ // database in the same shape as every other one.
23
+ const indexes = await getIndexes('redirects', knex);
24
+
25
+ if (indexes.includes(leftoverIndexName)) {
26
+ await dropIndex('redirects', ['automation_action_revision_id'], knex);
27
+ }
28
+ },
29
+ async function down(knex) {
30
+ try {
31
+ await dropUnique('redirects', uniqueColumns, knex);
32
+ } catch (err) {
33
+ // The composite unique ends up as the only index covering the
34
+ // automation_action_revision_id FK: fresh installs create it
35
+ // that way, and on upgrades InnoDB drops the FK's own index
36
+ // once the composite can serve the constraint. MySQL won't
37
+ // drop the last index a foreign key depends on, so re-add a
38
+ // single-column one when it objects.
39
+ if (err.code === 'ER_DROP_INDEX_FK') {
40
+ logging.warn('Cannot drop unique constraint over redirects(automation_action_revision_id, to_hash) while it backs the foreign key, re-adding index for automation_action_revision_id');
41
+
42
+ await addIndex('redirects', ['automation_action_revision_id'], knex);
43
+ await dropUnique('redirects', uniqueColumns, knex);
44
+ return;
45
+ }
46
+
47
+ throw err;
48
+ }
49
+ }
50
+ )
51
+ );
@@ -0,0 +1,5 @@
1
+ const {combineTransactionalMigrations, removeSetting} = require('../../utils');
2
+
3
+ module.exports = combineTransactionalMigrations(
4
+ removeSetting('routes_hash')
5
+ );