ghost 6.54.1 → 6.55.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 (247) 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-cards-10.3.3.tgz +0 -0
  4. package/components/tryghost-kg-default-nodes-2.1.3.tgz +0 -0
  5. package/core/boot.js +3 -6
  6. package/core/built/admin/assets/{account-migration-BklF2I_I.js → account-migration-sjHLx5dy.js} +1 -1
  7. package/core/built/admin/assets/activitypub/{account-migration-CNovr6W-.js → account-migration-DoYXtnZ8.js} +4 -4
  8. package/core/built/admin/assets/activitypub/activitypub.js +1 -1
  9. package/core/built/admin/assets/activitypub/{bluesky-sharing-Ceslb1_M.js → bluesky-sharing-Bxeh6MKW.js} +4 -4
  10. package/core/built/admin/assets/activitypub/{dist-DHAjBcV3.js → dist-D8sFfdpC.js} +2 -2
  11. package/core/built/admin/assets/activitypub/{domain-C1qQvJ8C.js → domain-CWueFW6q.js} +5 -5
  12. package/core/built/admin/assets/activitypub/{edit-profile-BcH-jEfM.js → edit-profile-BGxPU7N5.js} +3 -3
  13. package/core/built/admin/assets/activitypub/{explore-AxwBh9dI.js → explore-DBK-u0Ch.js} +3 -3
  14. package/core/built/admin/assets/activitypub/{feed-BaOlbh7a.js → feed-Bcir5RQZ.js} +3 -3
  15. package/core/built/admin/assets/activitypub/{field-B7mYYQpN.js → field-BV-CIT3d.js} +2 -2
  16. package/core/built/admin/assets/activitypub/{inbox-vXez-5uJ.js → inbox-yC0nfvzl.js} +4 -4
  17. package/core/built/admin/assets/activitypub/{label-Chv5T8bw.js → label-DTRS9TDk.js} +2 -2
  18. package/core/built/admin/assets/activitypub/layout-qx-bUL1k.js +1 -0
  19. package/core/built/admin/assets/activitypub/{moderation-DtSM2i1I.js → moderation-Dwl9t0pb.js} +4 -4
  20. package/core/built/admin/assets/activitypub/{note-Dckp4S-2.js → note-B-zKsZ7r.js} +3 -3
  21. package/core/built/admin/assets/activitypub/{notifications-CG5T22vg.js → notifications-DjJZD54t.js} +3 -3
  22. package/core/built/admin/assets/activitypub/{preferences-D_799jdl.js → preferences-CoU8vs6d.js} +6 -6
  23. package/core/built/admin/assets/activitypub/{profile-D2JfXBQl.js → profile-BAsaJekx.js} +6 -6
  24. package/core/built/admin/assets/activitypub/{routes-CCHe2dQp.js → routes-CLH3OVTP.js} +18 -18
  25. package/core/built/admin/assets/activitypub/{settings-kx-9Enkz.js → settings-DA6pNJ-Q.js} +3 -3
  26. package/core/built/admin/assets/activitypub/{site-x4_IjVJ0.js → site-CQD42Pw5.js} +2 -2
  27. package/core/built/admin/assets/activitypub/{tabs-CyPRQQ1N.js → tabs-Cg0saow1.js} +7 -7
  28. package/core/built/admin/assets/activitypub/{use-reply-chain-data-BAB6fNGt.js → use-reply-chain-data-Br4aOWOh.js} +2 -2
  29. package/core/built/admin/assets/audience-DjEWNhp3.js +1 -0
  30. package/core/built/admin/assets/{automated-emails-of1MvVsv.js → automated-emails-DRy7qY-J.js} +2 -2
  31. package/core/built/admin/assets/{automations-DRFjW5fw.js → automations-Bx3VcFIL.js} +1 -1
  32. package/core/built/admin/assets/{automations-B7-y3dXt.js → automations-DhKeS_U5.js} +1 -1
  33. package/core/built/admin/assets/{bluesky-sharing-BbbjWdzs.js → bluesky-sharing-ClMd0-Ft.js} +1 -1
  34. package/core/built/admin/assets/{breadcrumb-B-M9Ip_2.js → breadcrumb-BD0U2ygg.js} +1 -1
  35. package/core/built/admin/assets/{chart-helpers-xiSsW8r0.js → chart-helpers-fjQKRVyY.js} +1 -1
  36. package/core/built/admin/assets/{checkbox-EhJKHFdZ.js → checkbox-CWa28lgO.js} +1 -1
  37. package/core/built/admin/assets/{chunk.361.3cdb584b973959e52ccc.js → chunk.481.6c7395a49484bb4583aa.js} +41 -38
  38. package/core/built/admin/assets/{chunk.524.8365cd9280405c0f6216.js → chunk.524.cffd399783a519e404a9.js} +4 -4
  39. package/core/built/admin/assets/{chunk.582.61d078937e6102769f2c.js → chunk.582.eebf0d0b2d202bb746a5.js} +6 -6
  40. package/core/built/admin/assets/{code-editor-view-CZtJFGhR.js → code-editor-view-CCr0rBWS.js} +1 -1
  41. package/core/built/admin/assets/comments-DyUSMUdj.js +1 -0
  42. package/core/built/admin/assets/{date-range-select-CTlyo8zW.js → date-range-select-BFp58EMk.js} +1 -1
  43. package/core/built/admin/assets/{disabled-sources-indicator-qs2GfLvl.js → disabled-sources-indicator-CUxMfiPa.js} +1 -1
  44. package/core/built/admin/assets/domain-C0bV2UV8.js +1 -0
  45. package/core/built/admin/assets/{dropzone-BmCXnw6J.js → dropzone-DlK-K2DQ.js} +1 -1
  46. package/core/built/admin/assets/{edit-profile-SGrJve5M.js → edit-profile-BKT-REJw.js} +1 -1
  47. package/core/built/admin/assets/editor-Dp865O-4.js +8 -0
  48. package/core/built/admin/assets/{explore-CUYLv9G9.js → explore-ClUpJZOp.js} +1 -1
  49. package/core/built/admin/assets/{feed-CPdJMttL.js → feed-DF92dUMP.js} +1 -1
  50. package/core/built/admin/assets/field-Do7lieBA.js +1 -0
  51. package/core/built/admin/assets/{filter-sources-Bieps75g.js → filter-sources-BV2rnpfA.js} +1 -1
  52. package/core/built/admin/assets/filters-CUXJ2Nm9.js +1 -0
  53. package/core/built/admin/assets/{filters-CuB-A7ma.js → filters-DfbIWc_l.js} +1 -1
  54. package/core/built/admin/assets/{ghost-3063eae29e6c9cc6d6e7131928a27da0.css → ghost-a9c788f6f0b1ea6cf0f14ca4b13f4496.css} +1 -1
  55. package/core/built/admin/assets/{ghost-05f9a7182ed56903dba05878a5b8422b.js → ghost-c4057474943effc568c5659ccc4909d7.js} +190 -190
  56. package/core/built/admin/assets/{ghost-dark-c8d0ab7f515b2010c173e23e9b32ea8a.css → ghost-dark-db36d795d21c61426837df40af270966.css} +1 -1
  57. package/core/built/admin/assets/gift-link-modal-f9VIMNDv.js +1 -0
  58. package/core/built/admin/assets/{gift-links-nBxJjZiQ.js → gift-links-CsUhefWG.js} +1 -1
  59. package/core/built/admin/assets/growth-B3I6gNLm.js +1 -0
  60. package/core/built/admin/assets/growth-CVw1My4S.js +3 -0
  61. package/core/built/admin/assets/{hooks-BApwwBE0.js → hooks-CixO01lL.js} +1 -1
  62. package/core/built/admin/assets/{inbox-Xx1CdnfO.js → inbox-CFBSu4Ed.js} +13 -13
  63. package/core/built/admin/assets/index-9Cw8z3rA.css +1 -0
  64. package/core/built/admin/assets/index-BGG7NckO.js +67 -0
  65. package/core/built/admin/assets/{input-group-CGOXcy4l.js → input-group-BSgG_lVw.js} +1 -1
  66. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +974 -937
  67. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +28 -28
  68. package/core/built/admin/assets/koenig-lexical/style.css +1 -1
  69. package/core/built/admin/assets/{koenig-lexical-B61IALf5.js → koenig-lexical-f8vHRYvz.js} +28 -28
  70. package/core/built/admin/assets/{kpi-card-DCdlyaSS.js → kpi-card-DSap9rAy.js} +1 -1
  71. package/core/built/admin/assets/{kpi-card-BlZaG1Lo.js → kpi-card-DU0rn9Cj.js} +1 -1
  72. package/core/built/admin/assets/{kpi-helpers-DpAuS1Nb.js → kpi-helpers-Dz33AV6h.js} +1 -1
  73. package/core/built/admin/assets/{kpi-tabs-Dme44bQS.js → kpi-tabs-BtTTrKvJ.js} +1 -1
  74. package/core/built/admin/assets/{label-dSs5aepE.js → label-DS8Mi6G5.js} +1 -1
  75. package/core/built/admin/assets/label-picker-1DvSuLA1.js +1 -0
  76. package/core/built/admin/assets/labels-CDSUt4J_.js +1 -0
  77. package/core/built/admin/assets/layout-yQEi2qDM.js +1 -0
  78. package/core/built/admin/assets/{link-helpers-tOlxmuH5.js → link-helpers-7EYqvvHs.js} +1 -1
  79. package/core/built/admin/assets/member-custom-fields-yOMHyAg9.js +1 -0
  80. package/core/built/admin/assets/member-detail-BAq7gDaa.js +1 -0
  81. package/core/built/admin/assets/members-CQpVFJu4.js +10 -0
  82. package/core/built/admin/assets/modals-ReORvWnu.js +31 -0
  83. package/core/built/admin/assets/moderation-DySbgyUS.js +1 -0
  84. package/core/built/admin/assets/newsletter-CJQvtHqN.js +1 -0
  85. package/core/built/admin/assets/newsletters-B3QQXfGT.js +1 -0
  86. package/core/built/admin/assets/{newsletters-D1jbtV-h.js → newsletters-BXJbJgA-.js} +1 -1
  87. package/core/built/admin/assets/note-C8kxrz6w.js +1 -0
  88. package/core/built/admin/assets/notifications-CgtSfX7t.js +1 -0
  89. package/core/built/admin/assets/{offers-CrY6nSRR.js → offers-DEniVRD5.js} +1 -1
  90. package/core/built/admin/assets/onboarding-route-4qtziA9d.js +1 -0
  91. package/core/built/admin/assets/overview-Bf2sbznq.js +1 -0
  92. package/core/built/admin/assets/overview-CBfePxcr.js +1 -0
  93. package/core/built/admin/assets/{page-header-op0eN36I.js → page-header-B3Wb9Jqv.js} +1 -1
  94. package/core/built/admin/assets/{pages-BbNWFcGj.js → pages-B4goRPUn.js} +1 -1
  95. package/core/built/admin/assets/{post-analytics-WwRDEnrV.js → post-analytics-We-ko5Cb.js} +1 -1
  96. package/core/built/admin/assets/post-analytics-header-Bmc9su6q.js +1 -0
  97. package/core/built/admin/assets/post-analytics-provider-B6UynTGb.js +1 -0
  98. package/core/built/admin/assets/{post-share-modal-Dx-MEjhI.js → post-share-modal-DFfO93_0.js} +1 -1
  99. package/core/built/admin/assets/{posts-EWpZIjkr.js → posts-DhXE7b6r.js} +1 -1
  100. package/core/built/admin/assets/preferences-DJPfHEGu.js +5 -0
  101. package/core/built/admin/assets/profile-DqBQp-gT.js +1 -0
  102. package/core/built/admin/assets/{referrers-BCfFYSHc.js → referrers-pmJT4k3-.js} +1 -1
  103. package/core/built/admin/assets/{select-C1qDZnW4.js → select-C5_xEt5b.js} +1 -1
  104. package/core/built/admin/assets/settings-BwiVIXau.js +4 -0
  105. package/core/built/admin/assets/{settings-H6HxFgfI.js → settings-WXNeWyU1.js} +1 -1
  106. package/core/built/admin/assets/{share-modal-BJCadJyp.js → share-modal-DsgW3wbg.js} +1 -1
  107. package/core/built/admin/assets/social-urls-BQPFEk1_.js +8 -0
  108. package/core/built/admin/assets/{sort-button-DyYlfYp7.js → sort-button-Bb_FCl7e.js} +1 -1
  109. package/core/built/admin/assets/{source-icon-_ubRDbg_.js → source-icon-BeaRxUn9.js} +1 -1
  110. package/core/built/admin/assets/{stats-BQZMGMvn.js → stats-Bjj_kmJ1.js} +1 -1
  111. package/core/built/admin/assets/{stats-view-Doei-qCX.js → stats-view-BxPbW6K1.js} +1 -1
  112. package/core/built/admin/assets/{step-1-IKiLBPoC.js → step-1-B1e3-1F8.js} +1 -1
  113. package/core/built/admin/assets/{step-2-jJ41PsDN.js → step-2-C5WaeBJo.js} +1 -1
  114. package/core/built/admin/assets/{step-3-CaqFAEqf.js → step-3-B46iVPwu.js} +2 -2
  115. package/core/built/admin/assets/{switch-t-WyENU2.js → switch-z5Y6Uzt6.js} +1 -1
  116. package/core/built/admin/assets/{table-DxUL78jU.js → table-CB-4_HBJ.js} +1 -1
  117. package/core/built/admin/assets/tabs-CCEgKYzq.js +1 -0
  118. package/core/built/admin/assets/{tags-CNujGwkT.js → tags-Cg2VJGmG.js} +1 -1
  119. package/core/built/admin/assets/{tiers-BgYLWRJv.js → tiers-DYsa4DCo.js} +1 -1
  120. package/core/built/admin/assets/{toggle-group-BeEpQ69p.js → toggle-group-b3IlxvhV.js} +1 -1
  121. package/core/built/admin/assets/{topic-filter-Bb5MOD-1.js → topic-filter-DAFT87tQ.js} +1 -1
  122. package/core/built/admin/assets/{use-analytics-data-Cds6XDjg.js → use-analytics-data-S0FTd43s.js} +1 -1
  123. package/core/built/admin/assets/use-filter-params-Gpoq2E1L.js +1 -0
  124. package/core/built/admin/assets/{use-growth-stats-D8uf98-G.js → use-growth-stats-CIaiHOVb.js} +1 -1
  125. package/core/built/admin/assets/{use-post-referrers-CRtgyuXB.js → use-post-referrers-Cq2vBPrA.js} +1 -1
  126. package/core/built/admin/assets/{use-reply-chain-data-DbKYX50v.js → use-reply-chain-data-DBDRtGu-.js} +1 -1
  127. package/core/built/admin/assets/{use-simple-pagination-DC01kpo0.js → use-simple-pagination-CjYNxdwe.js} +1 -1
  128. package/core/built/admin/assets/{use-tinybird-query-C7bQRJWw.js → use-tinybird-query-falHAtuK.js} +1 -1
  129. package/core/built/admin/assets/{use-tinybird-token-Az4tam7_.js → use-tinybird-token-D7-8ND1y.js} +1 -1
  130. package/core/built/admin/assets/{users-CcSCQFcQ.js → users-Dkv8NQZe.js} +1 -1
  131. package/core/built/admin/assets/{virtual-list-Dhuh9fGx.js → virtual-list-CvnC9zF2.js} +1 -1
  132. package/core/built/admin/assets/web-B_5GZRvH.js +1 -0
  133. package/core/built/admin/assets/web-jFYWCKDn.js +2 -0
  134. package/core/built/admin/index.html +9 -9
  135. package/core/frontend/services/proxy.js +7 -8
  136. package/core/frontend/services/routing/router-manager.js +14 -14
  137. package/core/frontend/services/routing/static-routes-router.js +8 -10
  138. package/core/server/api/endpoints/automation-action-links.js +68 -0
  139. package/core/server/api/endpoints/automation-action-links.ts +46 -0
  140. package/core/server/api/endpoints/index.js +4 -0
  141. package/core/server/api/endpoints/settings.js +0 -2
  142. package/core/server/api/endpoints/utils/serializers/output/utils/url.js +11 -1
  143. package/core/server/data/migrations/versions/6.55/2026-07-27-11-25-44-add-to-hash-to-redirects.js +51 -0
  144. package/core/server/data/migrations/versions/6.55/2026-07-28-12-08-00-remove-routes-hash-from-settings.js +5 -0
  145. package/core/server/data/schema/commands.js +5 -0
  146. package/core/server/data/schema/default-settings/default-settings.json +0 -4
  147. package/core/server/data/schema/schema.js +5 -1
  148. package/core/server/services/automations/automations-api.js +15 -0
  149. package/core/server/services/automations/automations-api.ts +19 -0
  150. package/core/server/services/automations/automations-repository.ts +18 -0
  151. package/core/server/services/automations/database-automations-repository.js +109 -26
  152. package/core/server/services/automations/database-automations-repository.ts +110 -7
  153. package/core/server/services/automations/database-date.js +10 -0
  154. package/core/server/services/automations/database-date.ts +5 -0
  155. package/core/server/services/automations/poll.js +5 -2
  156. package/core/server/services/automations/poll.ts +7 -2
  157. package/core/server/services/automations/scheduler-poll-time.js +40 -0
  158. package/core/server/services/automations/scheduler-poll-time.ts +38 -0
  159. package/core/server/services/automations/service.js +15 -3
  160. package/core/server/services/automations/service.ts +16 -3
  161. package/core/server/services/comments/comments-service-emails.js +10 -7
  162. package/core/server/services/comments/comments-service.js +9 -4
  163. package/core/server/services/email-rendering/partials/email-wrapper.hbs +3 -1
  164. package/core/server/services/email-service/email-service-wrapper.js +6 -1
  165. package/core/server/services/email-service/email-templates/partials/styles.hbs +8 -0
  166. package/core/server/services/email-service/newsletter-email-event-storage.js +41 -10
  167. package/core/server/services/lib/dynamic-redirect-manager.js +12 -3
  168. package/core/server/services/link-redirection/link-redirect-repository.js +41 -4
  169. package/core/server/services/link-redirection/link-redirect.js +2 -0
  170. package/core/server/services/link-redirection/link-redirect.ts +3 -1
  171. package/core/server/services/link-redirection/link-redirects-service.js +38 -2
  172. package/core/server/services/link-tracking/click-event.js +3 -0
  173. package/core/server/services/link-tracking/index.js +38 -6
  174. package/core/server/services/link-tracking/link-click-repository.js +30 -9
  175. package/core/server/services/link-tracking/link-click-tracking-service.js +44 -3
  176. package/core/server/services/member-attribution/outbound-link-tagger.js +3 -1
  177. package/core/server/services/member-welcome-emails/member-welcome-email-renderer.js +19 -4
  178. package/core/server/services/member-welcome-emails/service.js +11 -3
  179. package/core/server/services/members/import-export/csv/formula.js +16 -0
  180. package/core/server/services/members/import-export/csv/formula.ts +14 -0
  181. package/core/server/services/members/import-export/csv/index.js +3 -1
  182. package/core/server/services/members/import-export/csv/index.ts +1 -0
  183. package/core/server/services/members/import-export/import/completion-email.js +22 -3
  184. package/core/server/services/members/import-export/import/completion-email.ts +29 -7
  185. package/core/server/services/members/import-export/import/importer.js +25 -4
  186. package/core/server/services/members/import-export/import/importer.ts +42 -4
  187. package/core/server/services/members/import-export/index.js +9 -0
  188. package/core/server/services/members/import-export/index.ts +20 -1
  189. package/core/server/services/members/service.js +8 -1
  190. package/core/server/services/members-custom-fields/values-service.js +19 -7
  191. package/core/server/services/members-custom-fields/values-service.ts +19 -7
  192. package/core/server/services/mentions/service.js +5 -1
  193. package/core/server/services/route-settings/activation-bridge.js +59 -49
  194. package/core/server/services/route-settings/activation-bridge.ts +91 -51
  195. package/core/server/services/route-settings/dynamic-routing-service.js +22 -51
  196. package/core/server/services/route-settings/index.js +1 -16
  197. package/core/server/services/settings/settings-service.js +0 -18
  198. package/core/server/services/url/url-service-facade.js +1 -1
  199. package/core/server/services/url/url-service-facade.ts +8 -1
  200. package/core/server/web/api/endpoints/admin/routes.js +1 -0
  201. package/package.json +7 -7
  202. package/pnpm-lock.yaml +8 -8
  203. package/pnpm-workspace.yaml +5 -4
  204. package/core/built/admin/assets/Growth-BTP1LthE.js +0 -3
  205. package/core/built/admin/assets/Newsletters-CSEX0myc.js +0 -1
  206. package/core/built/admin/assets/Overview-BjACEsJF.js +0 -1
  207. package/core/built/admin/assets/Web-CxxxjCcv.js +0 -2
  208. package/core/built/admin/assets/activitypub/layout-CziktJXo.js +0 -1
  209. package/core/built/admin/assets/audience-X_XsZivI.js +0 -1
  210. package/core/built/admin/assets/comments-CvyXxOyN.js +0 -1
  211. package/core/built/admin/assets/constants-c-JNF9c2.js +0 -1
  212. package/core/built/admin/assets/domain-CPNXKVOV.js +0 -1
  213. package/core/built/admin/assets/editor-BOG2YPUm.js +0 -8
  214. package/core/built/admin/assets/field-D0XISCK2.js +0 -1
  215. package/core/built/admin/assets/filters-bXkkWro8.js +0 -1
  216. package/core/built/admin/assets/gift-link-modal-su8MsRdg.js +0 -1
  217. package/core/built/admin/assets/growth-Ba6S5FQX.js +0 -1
  218. package/core/built/admin/assets/index-D8PbzuT9.css +0 -1
  219. package/core/built/admin/assets/index-ukW_0Uie.js +0 -67
  220. package/core/built/admin/assets/label-picker-Uq91atex.js +0 -1
  221. package/core/built/admin/assets/labels-Cyayfd-e.js +0 -1
  222. package/core/built/admin/assets/layout-WUcz6tu1.js +0 -1
  223. package/core/built/admin/assets/member-custom-fields-NCzrMuba.js +0 -1
  224. package/core/built/admin/assets/member-detail-DPWDtYnN.js +0 -1
  225. package/core/built/admin/assets/members-B9rO7qr4.js +0 -10
  226. package/core/built/admin/assets/modals-CxRaan1J.js +0 -31
  227. package/core/built/admin/assets/moderation-D8zon60E.js +0 -1
  228. package/core/built/admin/assets/multi-select-combobox-1wYm3nle.js +0 -1
  229. package/core/built/admin/assets/newsletter-DbYMRxRS.js +0 -1
  230. package/core/built/admin/assets/note-PiWPnnrh.js +0 -1
  231. package/core/built/admin/assets/notifications-DEgqqB9G.js +0 -1
  232. package/core/built/admin/assets/onboarding-route-BzF6HeVn.js +0 -1
  233. package/core/built/admin/assets/overview-DixLeIqY.js +0 -1
  234. package/core/built/admin/assets/post-analytics-header-DsMoLx0R.js +0 -1
  235. package/core/built/admin/assets/post-analytics-provider-BJCPLMIb.js +0 -1
  236. package/core/built/admin/assets/preferences-D7-j6A4C.js +0 -5
  237. package/core/built/admin/assets/profile-Dl69QPp0.js +0 -1
  238. package/core/built/admin/assets/settings-BbmC_Iqq.js +0 -4
  239. package/core/built/admin/assets/social-urls-BPL1nADp.js +0 -8
  240. package/core/built/admin/assets/tabs-BQcJQ1ke.js +0 -1
  241. package/core/built/admin/assets/use-filter-params-DjXkhO54.js +0 -1
  242. package/core/built/admin/assets/web-LtHNiNBV.js +0 -1
  243. package/core/server/services/route-settings/default-settings-manager.js +0 -63
  244. package/core/server/services/route-settings/route-settings.js +0 -170
  245. package/core/server/services/route-settings/settings-loader.js +0 -60
  246. package/core/server/services/route-settings/settings-path-manager.js +0 -47
  247. package/core/server/services/route-settings/validate.js +0 -440
@@ -2,8 +2,8 @@
2
2
  <html lang="en">
3
3
  <head>
4
4
  <link rel="stylesheet" href="./assets/vendor-3bf602dfa6463c3400ffd81697767274.css">
5
- <link rel="stylesheet" href="./assets/ghost-3063eae29e6c9cc6d6e7131928a27da0.css">
6
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%226.54%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%22293973f65e%22%2C%22activitypubFilename%22%3A%22activitypub.js%22%2C%22activitypubHash%22%3A%22b9d03a4632%22%2C%22activitypubRemoteConfigUrl%22%3A%22%2F.ghost%2Factivitypub%2Fstable%2Fclient-config%22%7D">
5
+ <link rel="stylesheet" href="./assets/ghost-a9c788f6f0b1ea6cf0f14ca4b13f4496.css">
6
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22editorUrl%22%3A%22%22%2C%22rootURL%22%3A%22%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%226.55%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%2C%22editorFilename%22%3A%22koenig-lexical.umd.js%22%2C%22editorHash%22%3A%22e35f1aa34a%22%2C%22activitypubFilename%22%3A%22activitypub.js%22%2C%22activitypubHash%22%3A%22d7e993cdd1%22%2C%22activitypubRemoteConfigUrl%22%3A%22%2F.ghost%2Factivitypub%2Fstable%2Fclient-config%22%7D">
7
7
 
8
8
  <meta charset="UTF-8" />
9
9
 
@@ -20,13 +20,13 @@
20
20
 
21
21
  <link rel="shortcut icon" href="./assets/favicon-C_q1KHzv.ico" />
22
22
  <link rel="apple-touch-icon" href="./assets/apple-touch-icon-DhO18_4B.png" />
23
- <script type="module" crossorigin src="./assets/index-ukW_0Uie.js"></script>
23
+ <script type="module" crossorigin src="./assets/index-BGG7NckO.js"></script>
24
24
  <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-DAXXjFlN.js">
25
25
  <link rel="modulepreload" crossorigin href="./assets/react-Bb3IsNbi.js">
26
26
  <link rel="modulepreload" crossorigin href="./assets/jsx-runtime-DX3WBWVO.js">
27
27
  <link rel="modulepreload" crossorigin href="./assets/framework-provider-CmtnPXIT.js">
28
28
  <link rel="modulepreload" crossorigin href="./assets/dist-Ndy-PuGN.js">
29
- <link rel="modulepreload" crossorigin href="./assets/hooks-BApwwBE0.js">
29
+ <link rel="modulepreload" crossorigin href="./assets/hooks-CixO01lL.js">
30
30
  <link rel="modulepreload" crossorigin href="./assets/dist-DGzPJ_bO.js">
31
31
  <link rel="modulepreload" crossorigin href="./assets/dist-CQJY6SVY.js">
32
32
  <link rel="modulepreload" crossorigin href="./assets/dist-P_ohUlTi.js">
@@ -40,8 +40,8 @@
40
40
  <link rel="modulepreload" crossorigin href="./assets/dist-2Tnz2Yfq.js">
41
41
  <link rel="modulepreload" crossorigin href="./assets/input-surface-BpAjZXY5.js">
42
42
  <link rel="modulepreload" crossorigin href="./assets/update-queries-C4CnvJaW.js">
43
- <link rel="modulepreload" crossorigin href="./assets/users-CcSCQFcQ.js">
44
- <link rel="stylesheet" crossorigin href="./assets/index-D8PbzuT9.css">
43
+ <link rel="modulepreload" crossorigin href="./assets/users-Dkv8NQZe.js">
44
+ <link rel="stylesheet" crossorigin href="./assets/index-9Cw8z3rA.css">
45
45
  </head>
46
46
  <body class="react-admin">
47
47
  <div id="ember-alerts-wormhole"></div>
@@ -62,8 +62,8 @@
62
62
  <div id="ember-notifications-wormhole"></div>
63
63
  <script src="./assets/vendor-25f0bb2b2de85265afdf33da79f14a44.js"></script>
64
64
  <script src="./assets/chunk.211.6f7328d63c41536451b3.js"></script>
65
- <script src="./assets/chunk.361.3cdb584b973959e52ccc.js"></script>
66
- <script src="./assets/chunk.524.8365cd9280405c0f6216.js"></script>
67
- <script src="./assets/ghost-05f9a7182ed56903dba05878a5b8422b.js"></script>
65
+ <script src="./assets/chunk.481.6c7395a49484bb4583aa.js"></script>
66
+ <script src="./assets/chunk.524.cffd399783a519e404a9.js"></script>
67
+ <script src="./assets/ghost-c4057474943effc568c5659ccc4909d7.js"></script>
68
68
  </body>
69
69
  </html>
@@ -16,9 +16,10 @@ const FRONTEND_SUBSCRIBABLE_EVENTS = ['site.changed', 'url.added', 'url.removed'
16
16
  // Require from the handlebars framework
17
17
  const {SafeString} = require('./handlebars');
18
18
 
19
- const createDOMPurify = require('dompurify');
20
- const {JSDOM} = require('jsdom');
21
- const DOMPurify = createDOMPurify(new JSDOM('').window);
19
+ // sanitize-html over DOMPurify here: DOMPurify needs a DOM, and jsdom costs
20
+ // ~116MB RSS to load. This module is on the boot path, so that was paid by
21
+ // every process including ones that never render a caption.
22
+ const sanitizeHtml = require('sanitize-html');
22
23
 
23
24
  module.exports = {
24
25
  getFrontendKey: async () => {
@@ -48,11 +49,9 @@ module.exports = {
48
49
  (Array.isArray(data) ? data : [data]).forEach((resource) => {
49
50
  // feature_image_caption contains HTML, making it a SafeString spares theme devs from triple-curlies
50
51
  if (resource.feature_image_caption) {
51
- const sanitizedCaption = DOMPurify.sanitize(resource.feature_image_caption, {
52
- ALLOWED_TAGS: ['a', 'b', 'i', 'span'],
53
- ALLOWED_ATTR: ['href', 'style'],
54
- ALLOW_DATA_ATTR: false,
55
- ALLOW_ARIA_ATTR: false
52
+ const sanitizedCaption = sanitizeHtml(resource.feature_image_caption, {
53
+ allowedTags: ['a', 'b', 'i', 'span'],
54
+ allowedAttributes: {'*': ['href', 'style']}
56
55
  });
57
56
  resource.feature_image_caption = new SafeString(sanitizedCaption);
58
57
  }
@@ -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');
@@ -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 routerSettings.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 routerSettings.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 taxonomy of routerSettings.taxonomies) {
132
+ const taxonomyRouter = new TaxonomyRouter(taxonomy.key, taxonomy.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());
@@ -186,10 +185,11 @@ module.exports = RouterManager;
186
185
  */
187
186
 
188
187
  /**
189
- * @typedef {Object} RouteSettings
190
- * @property {Object} routes
191
- * @property {Object} collections
192
- * @property {Object} taxonomies
188
+ * The shape RouterManager consumes — produced by the activation bridge. This is
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
193
193
  */
194
194
 
195
195
  /**
@@ -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,7 +74,7 @@ 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,
@@ -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
+ // buildRouterSettings 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
 
@@ -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
+ );
@@ -25,6 +25,11 @@ function addTableColumn(tableName, tableBuilder, columnName, columnSpec = schema
25
25
  // creation distinguishes between text with fieldtype, string with maxlength and all others
26
26
  if (columnSpec.type === 'text' && Object.prototype.hasOwnProperty.call(columnSpec, 'fieldtype')) {
27
27
  column = tableBuilder[columnSpec.type](columnName, columnSpec.fieldtype);
28
+ } else if (columnSpec.type === 'binary' && Object.prototype.hasOwnProperty.call(columnSpec, 'maxlength')) {
29
+ // knex emits an unbounded `blob` for a length-less binary column, which
30
+ // MySQL can only index with a prefix. Passing the length gives us
31
+ // `varbinary(N)`, which is indexable in full.
32
+ column = tableBuilder[columnSpec.type](columnName, columnSpec.maxlength);
28
33
  } else if (columnSpec.type === 'string') {
29
34
  if (Object.prototype.hasOwnProperty.call(columnSpec, 'maxlength')) {
30
35
  column = tableBuilder[columnSpec.type](columnName, columnSpec.maxlength);
@@ -8,10 +8,6 @@
8
8
  "defaultValue": null,
9
9
  "type": "string"
10
10
  },
11
- "routes_hash": {
12
- "defaultValue": null,
13
- "type": "string"
14
- },
15
11
  "next_update_check": {
16
12
  "defaultValue": null,
17
13
  "type": "number"
@@ -1263,8 +1263,12 @@ module.exports = {
1263
1263
  to: {type: 'string', maxlength: 2000, nullable: false},
1264
1264
  post_id: {type: 'string', maxlength: 24, nullable: true, unique: false, references: 'posts.id', setNullDelete: true},
1265
1265
  automation_action_revision_id: {type: 'string', maxlength: 24, nullable: true, references: 'automation_action_revisions.id', setNullDelete: true},
1266
+ to_hash: {type: 'binary', maxlength: 32, nullable: true},
1266
1267
  created_at: {type: 'dateTime', nullable: false},
1267
- updated_at: {type: 'dateTime', nullable: true}
1268
+ updated_at: {type: 'dateTime', nullable: true},
1269
+ '@@UNIQUE_CONSTRAINTS@@': [
1270
+ ['automation_action_revision_id', 'to_hash']
1271
+ ]
1268
1272
  },
1269
1273
  members_click_events: {
1270
1274
  id: {type: 'string', maxlength: 24, nullable: false, primary: true},
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.browse = browse;
7
7
  exports.read = read;
8
+ exports.browseActionLinks = browseActionLinks;
8
9
  exports.edit = edit;
9
10
  exports.requestPoll = requestPoll;
10
11
  exports.trigger = trigger;
@@ -15,6 +16,7 @@ exports.retryStep = retryStep;
15
16
  exports.recordEmailSent = recordEmailSent;
16
17
  exports.getAutomatedEmailRecipientsByMailgunIds = getAutomatedEmailRecipientsByMailgunIds;
17
18
  exports.trackEmailDeliveredAndOpened = trackEmailDeliveredAndOpened;
19
+ exports.trackEmailClicked = trackEmailClicked;
18
20
  const errors_1 = __importDefault(require("@tryghost/errors"));
19
21
  const tpl_1 = __importDefault(require("@tryghost/tpl"));
20
22
  const bson_objectid_1 = __importDefault(require("bson-objectid"));
@@ -30,6 +32,7 @@ const StartAutomationsPollEvent = require('./events/start-automations-poll-event
30
32
  const MAX_AUTOMATION_ACTIONS = 20;
31
33
  const messages = {
32
34
  automationNotFound: 'Automation not found.',
35
+ automationActionNotFound: 'Automation action not found.',
33
36
  invalidAutomationPayload: 'Automation edit payload must include status, actions, and edges.',
34
37
  invalidAutomationStatus: 'Automation status must be one of: active, inactive.',
35
38
  duplicateAutomationActionIdentity: 'Automation action identifiers must be unique.',
@@ -86,6 +89,15 @@ async function read(automationId) {
86
89
  }
87
90
  return automation;
88
91
  }
92
+ async function browseActionLinks(automationId, actionId) {
93
+ const links = await repository.getAutomationActionLinks(automationId, actionId);
94
+ if (!links) {
95
+ throw new errors_1.default.NotFoundError({
96
+ message: (0, tpl_1.default)(messages.automationActionNotFound)
97
+ });
98
+ }
99
+ return links;
100
+ }
89
101
  async function edit(automationId, data) {
90
102
  const parsedData = await validateEditData(data);
91
103
  const automation = await repository.edit(automationId, parsedData);
@@ -311,3 +323,6 @@ async function getAutomatedEmailRecipientsByMailgunIds(...args) {
311
323
  async function trackEmailDeliveredAndOpened(...args) {
312
324
  return await repository.trackEmailDeliveredAndOpened(...args);
313
325
  }
326
+ async function trackEmailClicked(...args) {
327
+ await repository.trackEmailClicked(...args);
328
+ }
@@ -20,6 +20,7 @@ const MAX_AUTOMATION_ACTIONS = 20;
20
20
 
21
21
  const messages = {
22
22
  automationNotFound: 'Automation not found.',
23
+ automationActionNotFound: 'Automation action not found.',
23
24
  invalidAutomationPayload: 'Automation edit payload must include status, actions, and edges.',
24
25
  invalidAutomationStatus: 'Automation status must be one of: active, inactive.',
25
26
  duplicateAutomationActionIdentity: 'Automation action identifiers must be unique.',
@@ -87,6 +88,18 @@ export async function read(automationId: string) {
87
88
  return automation;
88
89
  }
89
90
 
91
+ export async function browseActionLinks(automationId: string, actionId: string) {
92
+ const links = await repository.getAutomationActionLinks(automationId, actionId);
93
+
94
+ if (!links) {
95
+ throw new errors.NotFoundError({
96
+ message: tpl(messages.automationActionNotFound)
97
+ });
98
+ }
99
+
100
+ return links;
101
+ }
102
+
90
103
  export async function edit(automationId: string, data: unknown) {
91
104
  const parsedData = await validateEditData(data);
92
105
 
@@ -375,3 +388,9 @@ export async function trackEmailDeliveredAndOpened(
375
388
  ) {
376
389
  return await repository.trackEmailDeliveredAndOpened(...args);
377
390
  }
391
+
392
+ export async function trackEmailClicked(
393
+ ...args: Parameters<AutomationsRepository['trackEmailClicked']>
394
+ ) {
395
+ await repository.trackEmailClicked(...args);
396
+ }
@@ -1,4 +1,5 @@
1
1
  import type {ReadonlyDeep} from 'type-fest';
2
+ import type {Knex} from 'knex';
2
3
 
3
4
  export interface Pagination {
4
5
  page: number;
@@ -32,6 +33,11 @@ export interface AutomationEmailStats {
32
33
  clicked_rate: number | null;
33
34
  }
34
35
 
36
+ export interface AutomationActionLink {
37
+ url: string;
38
+ clicked_count: number;
39
+ }
40
+
35
41
  export interface SendEmailAction {
36
42
  id: string;
37
43
  type: 'send_email';
@@ -131,6 +137,7 @@ export type AutomationStepTerminalStatus =
131
137
  export interface AutomationsRepository {
132
138
  browse(): Promise<Page<AutomationSummary>>;
133
139
  getById(id: string): Promise<Automation | null>;
140
+ getAutomationActionLinks(automationId: string, actionId: string): Promise<AutomationActionLink[] | null>;
134
141
  edit(id: string, data: EditAutomationData): Promise<Automation | null>;
135
142
  trigger(options: {
136
143
  memberEmail: string;
@@ -195,4 +202,15 @@ export interface AutomationsRepository {
195
202
  trackEmailDeliveredAndOpened(
196
203
  eventsByAutomatedEmailRecipientId: ReadonlyDeep<Map<string, AutomatedEmailEvents>>
197
204
  ): Promise<void>;
205
+ /**
206
+ * Track the first click for an automated email recipient.
207
+ *
208
+ */
209
+ trackEmailClicked(options: {
210
+ automationActionRevisionId: string;
211
+ memberId: string;
212
+ clickedAt: Readonly<Date>;
213
+ }, transactionOptions?: {
214
+ transacting?: Knex.Transaction;
215
+ }): Promise<void>;
198
216
  }