ghost 5.82.3 → 5.82.4

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 (148) hide show
  1. package/components/{tryghost-adapter-cache-memory-ttl-5.82.3.tgz → tryghost-adapter-cache-memory-ttl-5.82.4.tgz} +0 -0
  2. package/components/{tryghost-adapter-cache-redis-5.82.3.tgz → tryghost-adapter-cache-redis-5.82.4.tgz} +0 -0
  3. package/components/{tryghost-adapter-manager-5.82.3.tgz → tryghost-adapter-manager-5.82.4.tgz} +0 -0
  4. package/components/{tryghost-announcement-bar-settings-5.82.3.tgz → tryghost-announcement-bar-settings-5.82.4.tgz} +0 -0
  5. package/components/{tryghost-api-framework-5.82.3.tgz → tryghost-api-framework-5.82.4.tgz} +0 -0
  6. package/components/{tryghost-api-version-compatibility-service-5.82.3.tgz → tryghost-api-version-compatibility-service-5.82.4.tgz} +0 -0
  7. package/components/{tryghost-audience-feedback-5.82.3.tgz → tryghost-audience-feedback-5.82.4.tgz} +0 -0
  8. package/components/tryghost-bookshelf-repository-5.82.4.tgz +0 -0
  9. package/components/{tryghost-bootstrap-socket-5.82.3.tgz → tryghost-bootstrap-socket-5.82.4.tgz} +0 -0
  10. package/components/tryghost-collections-5.82.4.tgz +0 -0
  11. package/components/{tryghost-constants-5.82.3.tgz → tryghost-constants-5.82.4.tgz} +0 -0
  12. package/components/{tryghost-custom-theme-settings-service-5.82.3.tgz → tryghost-custom-theme-settings-service-5.82.4.tgz} +0 -0
  13. package/components/{tryghost-data-generator-5.82.3.tgz → tryghost-data-generator-5.82.4.tgz} +0 -0
  14. package/components/tryghost-domain-events-5.82.4.tgz +0 -0
  15. package/components/tryghost-donations-5.82.4.tgz +0 -0
  16. package/components/tryghost-dynamic-routing-events-5.82.4.tgz +0 -0
  17. package/components/{tryghost-email-addresses-5.82.3.tgz → tryghost-email-addresses-5.82.4.tgz} +0 -0
  18. package/components/tryghost-email-analytics-provider-mailgun-5.82.4.tgz +0 -0
  19. package/components/tryghost-email-analytics-service-5.82.4.tgz +0 -0
  20. package/components/tryghost-email-content-generator-5.82.4.tgz +0 -0
  21. package/components/{tryghost-email-events-5.82.3.tgz → tryghost-email-events-5.82.4.tgz} +0 -0
  22. package/components/tryghost-email-service-5.82.4.tgz +0 -0
  23. package/components/tryghost-email-suppression-list-5.82.4.tgz +0 -0
  24. package/components/{tryghost-express-dynamic-redirects-5.82.3.tgz → tryghost-express-dynamic-redirects-5.82.4.tgz} +0 -0
  25. package/components/tryghost-external-media-inliner-5.82.4.tgz +0 -0
  26. package/components/tryghost-extract-api-key-5.82.4.tgz +0 -0
  27. package/components/tryghost-ghost-5.82.4.tgz +0 -0
  28. package/components/tryghost-html-to-plaintext-5.82.4.tgz +0 -0
  29. package/components/tryghost-i18n-5.82.4.tgz +0 -0
  30. package/components/tryghost-importer-handler-content-files-5.82.4.tgz +0 -0
  31. package/components/tryghost-importer-revue-5.82.4.tgz +0 -0
  32. package/components/tryghost-in-memory-repository-5.82.4.tgz +0 -0
  33. package/components/{tryghost-job-manager-5.82.3.tgz → tryghost-job-manager-5.82.4.tgz} +0 -0
  34. package/components/tryghost-link-redirects-5.82.4.tgz +0 -0
  35. package/components/{tryghost-link-replacer-5.82.3.tgz → tryghost-link-replacer-5.82.4.tgz} +0 -0
  36. package/components/{tryghost-link-tracking-5.82.3.tgz → tryghost-link-tracking-5.82.4.tgz} +0 -0
  37. package/components/{tryghost-magic-link-5.82.3.tgz → tryghost-magic-link-5.82.4.tgz} +0 -0
  38. package/components/{tryghost-mail-events-5.82.3.tgz → tryghost-mail-events-5.82.4.tgz} +0 -0
  39. package/components/tryghost-mailgun-client-5.82.4.tgz +0 -0
  40. package/components/{tryghost-member-attribution-5.82.3.tgz → tryghost-member-attribution-5.82.4.tgz} +0 -0
  41. package/components/tryghost-member-events-5.82.4.tgz +0 -0
  42. package/components/{tryghost-members-api-5.82.3.tgz → tryghost-members-api-5.82.4.tgz} +0 -0
  43. package/components/tryghost-members-csv-5.82.4.tgz +0 -0
  44. package/components/{tryghost-members-events-service-5.82.3.tgz → tryghost-members-events-service-5.82.4.tgz} +0 -0
  45. package/components/{tryghost-members-importer-5.82.3.tgz → tryghost-members-importer-5.82.4.tgz} +0 -0
  46. package/components/{tryghost-members-offers-5.82.3.tgz → tryghost-members-offers-5.82.4.tgz} +0 -0
  47. package/components/{tryghost-members-payments-5.82.3.tgz → tryghost-members-payments-5.82.4.tgz} +0 -0
  48. package/components/{tryghost-members-ssr-5.82.3.tgz → tryghost-members-ssr-5.82.4.tgz} +0 -0
  49. package/components/{tryghost-members-stripe-service-5.82.3.tgz → tryghost-members-stripe-service-5.82.4.tgz} +0 -0
  50. package/components/{tryghost-mentions-email-report-5.82.3.tgz → tryghost-mentions-email-report-5.82.4.tgz} +0 -0
  51. package/components/tryghost-milestones-5.82.4.tgz +0 -0
  52. package/components/tryghost-minifier-5.82.4.tgz +0 -0
  53. package/components/{tryghost-model-to-domain-event-interceptor-5.82.3.tgz → tryghost-model-to-domain-event-interceptor-5.82.4.tgz} +0 -0
  54. package/components/tryghost-mw-api-version-mismatch-5.82.4.tgz +0 -0
  55. package/components/tryghost-mw-cache-control-5.82.4.tgz +0 -0
  56. package/components/tryghost-mw-error-handler-5.82.4.tgz +0 -0
  57. package/components/{tryghost-mw-session-from-token-5.82.3.tgz → tryghost-mw-session-from-token-5.82.4.tgz} +0 -0
  58. package/components/tryghost-mw-update-user-last-seen-5.82.4.tgz +0 -0
  59. package/components/tryghost-mw-version-match-5.82.4.tgz +0 -0
  60. package/components/{tryghost-mw-vhost-5.82.3.tgz → tryghost-mw-vhost-5.82.4.tgz} +0 -0
  61. package/components/tryghost-nql-filter-expansions-5.82.4.tgz +0 -0
  62. package/components/tryghost-oembed-service-5.82.4.tgz +0 -0
  63. package/components/tryghost-package-json-5.82.4.tgz +0 -0
  64. package/components/{tryghost-post-events-5.82.3.tgz → tryghost-post-events-5.82.4.tgz} +0 -0
  65. package/components/{tryghost-post-revisions-5.82.3.tgz → tryghost-post-revisions-5.82.4.tgz} +0 -0
  66. package/components/tryghost-posts-service-5.82.4.tgz +0 -0
  67. package/components/tryghost-recommendations-5.82.4.tgz +0 -0
  68. package/components/tryghost-referrers-5.82.4.tgz +0 -0
  69. package/components/{tryghost-security-5.82.3.tgz → tryghost-security-5.82.4.tgz} +0 -0
  70. package/components/{tryghost-session-service-5.82.3.tgz → tryghost-session-service-5.82.4.tgz} +0 -0
  71. package/components/tryghost-settings-path-manager-5.82.4.tgz +0 -0
  72. package/components/tryghost-slack-notifications-5.82.4.tgz +0 -0
  73. package/components/tryghost-staff-service-5.82.4.tgz +0 -0
  74. package/components/tryghost-stats-service-5.82.4.tgz +0 -0
  75. package/components/tryghost-tiers-5.82.4.tgz +0 -0
  76. package/components/tryghost-update-check-service-5.82.4.tgz +0 -0
  77. package/components/{tryghost-verification-trigger-5.82.3.tgz → tryghost-verification-trigger-5.82.4.tgz} +0 -0
  78. package/components/{tryghost-version-notifications-data-service-5.82.3.tgz → tryghost-version-notifications-data-service-5.82.4.tgz} +0 -0
  79. package/components/tryghost-webmentions-5.82.4.tgz +0 -0
  80. package/core/boot.js +6 -0
  81. package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +6990 -0
  82. package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
  83. package/core/built/admin/assets/admin-x-demo/{index-aad00baa.mjs → index-e36f9f54.mjs} +3 -3
  84. package/core/built/admin/assets/admin-x-demo/{modals-925b7c32.mjs → modals-54e6eae1.mjs} +2 -2
  85. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-09fb8166.mjs → CodeEditorView-834e7ac8.mjs} +2 -2
  86. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
  87. package/core/built/admin/assets/admin-x-settings/{index-7c9fce83.mjs → index-957ece55.mjs} +94 -87
  88. package/core/built/admin/assets/admin-x-settings/{index-55c46922.mjs → index-f994fb82.mjs} +2 -2
  89. package/core/built/admin/assets/admin-x-settings/{modals-e97cff0c.mjs → modals-5264a854.mjs} +1033 -1000
  90. package/core/built/admin/assets/{chunk.524.7e89297e3fe4765e1581.js → chunk.524.bd8238874f4b73f48c54.js} +5 -5
  91. package/core/built/admin/assets/{chunk.582.d279749c1357df54ae8e.js → chunk.582.0c6120fa7382f5d485e0.js} +4 -4
  92. package/core/built/admin/assets/{chunk.682.9acd39e8ae4d98006602.js → chunk.799.51e503059770581f528f.js} +297 -275
  93. package/core/built/admin/assets/{ghost-804748f2e71b6155946284e9658bddd0.js → ghost-0723ddb4afa369fc0303017c40ffffe0.js} +233 -217
  94. package/core/built/admin/assets/koenig-lexical/index.css +1 -1
  95. package/core/built/admin/assets/koenig-lexical/koenig-lexical.js +8463 -8375
  96. package/core/built/admin/assets/koenig-lexical/koenig-lexical.umd.js +126 -126
  97. package/core/built/admin/assets/{vendor-f9bad0b8c0149699f84c0bd9d325068e.js → vendor-5ef343f0a55366497ef903095548a0d5.js} +5 -5
  98. package/core/built/admin/index.html +5 -5
  99. package/core/server/services/Users.js +52 -40
  100. package/core/server/services/link-redirection/LinkRedirectRepository.js +106 -7
  101. package/core/server/services/link-redirection/index.js +6 -1
  102. package/core/server/services/xmlrpc.js +0 -2
  103. package/core/shared/labs.js +3 -3
  104. package/package.json +151 -151
  105. package/yarn.lock +406 -313
  106. package/components/tryghost-bookshelf-repository-5.82.3.tgz +0 -0
  107. package/components/tryghost-collections-5.82.3.tgz +0 -0
  108. package/components/tryghost-domain-events-5.82.3.tgz +0 -0
  109. package/components/tryghost-donations-5.82.3.tgz +0 -0
  110. package/components/tryghost-dynamic-routing-events-5.82.3.tgz +0 -0
  111. package/components/tryghost-email-analytics-provider-mailgun-5.82.3.tgz +0 -0
  112. package/components/tryghost-email-analytics-service-5.82.3.tgz +0 -0
  113. package/components/tryghost-email-content-generator-5.82.3.tgz +0 -0
  114. package/components/tryghost-email-service-5.82.3.tgz +0 -0
  115. package/components/tryghost-email-suppression-list-5.82.3.tgz +0 -0
  116. package/components/tryghost-external-media-inliner-5.82.3.tgz +0 -0
  117. package/components/tryghost-extract-api-key-5.82.3.tgz +0 -0
  118. package/components/tryghost-ghost-5.82.3.tgz +0 -0
  119. package/components/tryghost-html-to-plaintext-5.82.3.tgz +0 -0
  120. package/components/tryghost-i18n-5.82.3.tgz +0 -0
  121. package/components/tryghost-importer-handler-content-files-5.82.3.tgz +0 -0
  122. package/components/tryghost-importer-revue-5.82.3.tgz +0 -0
  123. package/components/tryghost-in-memory-repository-5.82.3.tgz +0 -0
  124. package/components/tryghost-link-redirects-5.82.3.tgz +0 -0
  125. package/components/tryghost-mailgun-client-5.82.3.tgz +0 -0
  126. package/components/tryghost-member-events-5.82.3.tgz +0 -0
  127. package/components/tryghost-members-csv-5.82.3.tgz +0 -0
  128. package/components/tryghost-milestones-5.82.3.tgz +0 -0
  129. package/components/tryghost-minifier-5.82.3.tgz +0 -0
  130. package/components/tryghost-mw-api-version-mismatch-5.82.3.tgz +0 -0
  131. package/components/tryghost-mw-cache-control-5.82.3.tgz +0 -0
  132. package/components/tryghost-mw-error-handler-5.82.3.tgz +0 -0
  133. package/components/tryghost-mw-update-user-last-seen-5.82.3.tgz +0 -0
  134. package/components/tryghost-mw-version-match-5.82.3.tgz +0 -0
  135. package/components/tryghost-nql-filter-expansions-5.82.3.tgz +0 -0
  136. package/components/tryghost-oembed-service-5.82.3.tgz +0 -0
  137. package/components/tryghost-package-json-5.82.3.tgz +0 -0
  138. package/components/tryghost-posts-service-5.82.3.tgz +0 -0
  139. package/components/tryghost-recommendations-5.82.3.tgz +0 -0
  140. package/components/tryghost-referrers-5.82.3.tgz +0 -0
  141. package/components/tryghost-settings-path-manager-5.82.3.tgz +0 -0
  142. package/components/tryghost-slack-notifications-5.82.3.tgz +0 -0
  143. package/components/tryghost-staff-service-5.82.3.tgz +0 -0
  144. package/components/tryghost-stats-service-5.82.3.tgz +0 -0
  145. package/components/tryghost-tiers-5.82.3.tgz +0 -0
  146. package/components/tryghost-update-check-service-5.82.3.tgz +0 -0
  147. package/components/tryghost-webmentions-5.82.3.tgz +0 -0
  148. /package/core/built/admin/assets/{chunk.682.9acd39e8ae4d98006602.js.LICENSE.txt → chunk.799.51e503059770581f528f.js.LICENSE.txt} +0 -0
@@ -7030,11 +7030,11 @@ if(h&&!1!==d.startTransactionOnPageLoad&&!1!==d.instrumentPageLoad){const r=e.re
7030
7030
  u=i(p,{name:`route:${r.name}`,origin:"auto.pageload.ember",attributes:{[t.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]:"route"},tags:{url:h,toRoute:r.name,"routing.instrumentation":"@sentry/ember"}})}const f=(e,t)=>{t||(u?.end(),o().off("end",f))}
7031
7031
  !1===d.startTransactionOnLocationChange&&!1===d.instrumentNavigation||(e.on("routeWillChange",(r=>{const{fromRoute:n,toRoute:i}=function(e,t){const r=e?.from?.name
7032
7032
  return{fromRoute:r,toRoute:e?.to?.name||t.currentRouteName}}(r,e)
7033
- u?.end(),u=s(p,{name:`route:${i}`,origin:"auto.navigation.ember",attributes:{[t.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]:"route"},tags:{fromRoute:n,toRoute:i,"routing.instrumentation":"@sentry/ember"}}),c=(0,t.startInactiveSpan)({attributes:{[t.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]:"auto.ui.ember"},op:"ui.ember.transition",name:`route:${n} -> route:${i}`})})),e.on("routeDidChange",(()=>{c&&u&&(c.end(),a?u.end():o().on("end",f))})))}function a(e,t){const n={payload:e,now:(0,r.timestampInSeconds)()}
7033
+ u?.end(),u=s(p,{name:`route:${i}`,origin:"auto.navigation.ember",attributes:{[t.SEMANTIC_ATTRIBUTE_SENTRY_SOURCE]:"route"},tags:{fromRoute:n,toRoute:i,"routing.instrumentation":"@sentry/ember"}}),c=(0,t.startInactiveSpan)({attributes:{[t.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]:"auto.ui.ember"},op:"ui.ember.transition",name:`route:${n} -> route:${i}`,onlyIfParent:!0})})),e.on("routeDidChange",(()=>{c&&u&&(c.end(),a?u.end():o().on("end",f))})))}function a(e,t){const n={payload:e,now:(0,r.timestampInSeconds)()}
7034
7034
  t[e.object]=n}function l(e,n,i,o){const s=n[e.object]
7035
7035
  if(!s)return
7036
7036
  const a=(0,r.timestampInSeconds)()
7037
- 1e3*(a-s.now)>=o&&(0,t.startInactiveSpan)({name:e.containerKey||e.object,op:i,origin:"auto.ui.ember",startTimestamp:s.now})?.end(a)}function u(e){const n="@sentry/ember:initial-load-start",i="@sentry/ember:initial-load-end",{HAS_PERFORMANCE:o,HAS_PERFORMANCE_TIMING:s}=function(){const e=window.performance,t=Boolean(e&&e.clearMarks&&e.clearMeasures),n=Boolean(e.measure&&e.getEntriesByName&&void 0!==r.browserPerformanceTimeOrigin)
7037
+ 1e3*(a-s.now)>=o&&(0,t.startInactiveSpan)({name:e.containerKey||e.object,op:i,origin:"auto.ui.ember",startTimestamp:s.now,onlyIfParent:!0})?.end(a)}function u(e){const n="@sentry/ember:initial-load-start",i="@sentry/ember:initial-load-end",{HAS_PERFORMANCE:o,HAS_PERFORMANCE_TIMING:s}=function(){const e=window.performance,t=Boolean(e&&e.clearMarks&&e.clearMeasures),n=Boolean(e.measure&&e.getEntriesByName&&void 0!==r.browserPerformanceTimeOrigin)
7038
7038
  return{HAS_PERFORMANCE:t,HAS_PERFORMANCE_TIMING:n}}()
7039
7039
  if(!o)return
7040
7040
  const{performance:a}=window
@@ -7043,7 +7043,7 @@ if(!s||void 0===r.browserPerformanceTimeOrigin)return
7043
7043
  const l="@sentry/ember:initial-load",u=a.getEntriesByName(n).length>0,c=a.getEntriesByName(i).length>0
7044
7044
  if(!u||!c)return
7045
7045
  a.measure(l,n,i)
7046
- const d=a.getEntriesByName(l)[0],h=(d.startTime+r.browserPerformanceTimeOrigin)/1e3,p=h+d.duration/1e3;(0,t.startInactiveSpan)({op:"ui.ember.init",name:"init",attributes:{[t.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]:"auto.ui.ember"},startTimestamp:h})?.end(p),a.clearMarks(n),a.clearMarks(i),a.clearMeasures(l)}async function c(e){const i=n(),c=i.browserTracingOptions||i.sentry.browserTracingOptions||{},{browserTracingIntegration:d,startBrowserTracingNavigationSpan:h,startBrowserTracingPageLoadSpan:p}=await emberAutoImportDynamic("@sentry/browser"),f=d({idleTimeout:i.transitionTimeout||5e3,...c,instrumentNavigation:!1,instrumentPageLoad:!1}),m=(0,t.getClient)()
7046
+ const d=a.getEntriesByName(l)[0],h=(d.startTime+r.browserPerformanceTimeOrigin)/1e3,p=h+d.duration/1e3;(0,t.startInactiveSpan)({op:"ui.ember.init",name:"init",attributes:{[t.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]:"auto.ui.ember"},onlyIfParent:!0,startTimestamp:h})?.end(p),a.clearMarks(n),a.clearMarks(i),a.clearMeasures(l)}async function c(e){const i=n(),c=i.browserTracingOptions||i.sentry.browserTracingOptions||{},{browserTracingIntegration:d,startBrowserTracingNavigationSpan:h,startBrowserTracingPageLoadSpan:p}=await emberAutoImportDynamic("@sentry/browser"),f=d({idleTimeout:i.transitionTimeout||5e3,...c,instrumentNavigation:!1,instrumentPageLoad:!1}),m=(0,t.getClient)()
7047
7047
  m&&m.addIntegration&&m.addIntegration(f),function(e,t,r,n){const i=e.lookup("router:main")
7048
7048
  let o=e.lookup("service:router")
7049
7049
  o.externalRouter&&(o=o.externalRouter)
@@ -7057,7 +7057,7 @@ o().on("begin",((e,n)=>{if(n)return
7057
7057
  if(!(0,t.getActiveSpan)())return
7058
7058
  a&&a.end(),s=(0,r.timestampInSeconds)()
7059
7059
  const o=e=>{if(s){const n=(0,r.timestampInSeconds)()
7060
- 1e3*(n-s)>=(i??5)&&(0,t.startInactiveSpan)({attributes:{[t.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]:"auto.ui.ember"},name:"runloop",op:`ui.ember.runloop.${e}`,startTimestamp:s})?.end(n),s=void 0}(0,t.getActiveSpan)()&&(s=(0,r.timestampInSeconds)())}
7060
+ 1e3*(n-s)>=(i??5)&&(0,t.startInactiveSpan)({attributes:{[t.SEMANTIC_ATTRIBUTE_SENTRY_ORIGIN]:"auto.ui.ember"},name:"runloop",op:`ui.ember.runloop.${e}`,startTimestamp:s,onlyIfParent:!0})?.end(n),s=void 0}(0,t.getActiveSpan)()&&(s=(0,r.timestampInSeconds)())}
7061
7061
  l.forEach((e=>{Ember.run.scheduleOnce(e,null,o,e)}))})),o().on("end",((e,t)=>{t||a&&(a.end(),a=void 0)}))}(i),function(e){const{disableInstrumentComponents:t,minimumComponentRenderDuration:r,enableComponentDefinitions:n}=e
7062
7062
  if(t)return
7063
7063
  const i=r??2,o={},s={}
@@ -9924,4 +9924,4 @@ e.default=class{constructor(e){if(this._data=new t.default,e)for(let t=0;t<e.len
9924
9924
  return this}get(e){let t=this._data[e]
9925
9925
  return t===r.UNDEFINED_KEY?void 0:t}set(e,t){return this._data[e]=t,this}delete(e){return this._data[e]=r.UNDEFINED_KEY,!0}}}))
9926
9926
 
9927
- //# sourceMappingURL=vendor-e2df5680ee343a169ab5cb0eec968016.map
9927
+ //# sourceMappingURL=vendor-5b3c871639e8269236f1a32144698b0d.map
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%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%225.82%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22ember-websockets%22%3A%7B%22socketIO%22%3Atrue%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%220bc388c18b%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%223e7af416ea%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%22dc1d300e0c%22%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22cdnUrl%22%3A%22%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%225.82%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22ember-websockets%22%3A%7B%22socketIO%22%3Atrue%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%22d4e7286cae%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%221338bf2f0d%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%227898cabecf%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%22f5c6e4344f%22%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -56,9 +56,9 @@
56
56
 
57
57
  <div id="ember-basic-dropdown-wormhole"></div>
58
58
 
59
- <script src="assets/vendor-f9bad0b8c0149699f84c0bd9d325068e.js"></script>
60
- <script src="assets/chunk.682.9acd39e8ae4d98006602.js"></script>
61
- <script src="assets/chunk.524.7e89297e3fe4765e1581.js"></script>
62
- <script src="assets/ghost-804748f2e71b6155946284e9658bddd0.js"></script>
59
+ <script src="assets/vendor-5ef343f0a55366497ef903095548a0d5.js"></script>
60
+ <script src="assets/chunk.799.51e503059770581f528f.js"></script>
61
+ <script src="assets/chunk.524.bd8238874f4b73f48c54.js"></script>
62
+ <script src="assets/ghost-0723ddb4afa369fc0303017c40ffffe0.js"></script>
63
63
  </body>
64
64
  </html>
@@ -1,10 +1,13 @@
1
1
  // @ts-check
2
2
  const path = require('path');
3
+ const ObjectId = require('bson-objectid').default;
3
4
 
4
5
  /**
5
6
  * @TODO: pass these in as dependencies
6
7
  */
7
8
  const {PostRevisions} = require('@tryghost/post-revisions');
9
+ const DomainEvents = require('@tryghost/domain-events/lib/DomainEvents');
10
+ const {PostsBulkAddTagsEvent} = require('@tryghost/post-events');
8
11
 
9
12
  /**
10
13
  * @typedef {Object} IdbBackup
@@ -76,8 +79,7 @@ class Users {
76
79
  }
77
80
 
78
81
  async assignTagToUserPosts({id, context, transacting}) {
79
- // create an internal tag to assign to reassigned posts
80
- // in following format: `#{author_slug}`
82
+ // get author slug
81
83
  const author = await this.models.User.findOne({
82
84
  id
83
85
  }, {
@@ -85,13 +87,27 @@ class Users {
85
87
  context,
86
88
  transacting
87
89
  });
90
+
91
+ // get list of posts that need the tag assigned
92
+ const userPosts = await this.models.Base.knex('posts_authors')
93
+ .transacting(transacting)
94
+ .where('author_id', id)
95
+ .select('post_id');
96
+ let usersPostIds = userPosts.map(p => p.post_id);
97
+
98
+ if (usersPostIds.length === 0) {
99
+ return;
100
+ }
101
+
102
+ // create an internal tag to assign to reassigned posts
103
+ // in following format: `#{author_slug}`
104
+ let createdTag = false;
88
105
  let tag = await this.models.Tag.findOne({
89
106
  slug: `hash-${author.get('slug')}`
90
107
  }, {
91
108
  context,
92
109
  transacting
93
110
  });
94
-
95
111
  if (!tag) {
96
112
  tag = await this.models.Tag.add({
97
113
  slug: `#${author.get('slug')}`
@@ -99,45 +115,38 @@ class Users {
99
115
  context,
100
116
  transacting
101
117
  });
118
+ createdTag = true;
102
119
  }
103
120
 
104
- const userPosts = await this.models.Base.knex('posts_authors')
105
- .transacting(transacting)
106
- .where('author_id', id)
107
- .select('post_id');
108
- const usersPostIds = userPosts.map(p => p.post_id);
109
-
110
- // Add a tag to all posts that do not have the author tag yet
111
- // NOTE: the method is implemented in an iterative way to avoid
112
- // memory consumption in case the user has thousands of posts
113
- // assigned to them. Also, didn't have any "bulk" way to add
114
- // a tag to multiple posts as the "sort_order" needs custom
115
- // logic to be run for each post.
116
- // Rewrite this bit if/when we hit a performance bottleneck here
117
- for (const postId of usersPostIds) {
118
- const post = await this.models.Post.findOne({
119
- id: postId,
120
- status: 'all'
121
- }, {
122
- id: postId,
123
- withRelated: ['tags'],
124
- context,
125
- transacting
126
- });
127
-
128
- // check if tag already assigned to the post
129
- const existingTagSlugs = post.relations.tags.models.map(t => t.get('slug'));
130
-
131
- if (!existingTagSlugs.includes(tag.get('slug'))) {
132
- await this.models.Post.edit({
133
- tags: [...post.relations.tags.models, tag]
134
- }, {
135
- id: postId,
136
- context,
137
- transacting
138
- });
139
- }
121
+ // filter out posts that already have the tag if we didn't need to create one
122
+ if (!createdTag) {
123
+ const tagId = tag.get('id');
124
+ const taggedPostIds = await this.models.Base.knex('posts_tags')
125
+ .transacting(transacting)
126
+ .where('tag_id', tagId)
127
+ .select('post_id');
128
+ usersPostIds = userPosts
129
+ .map(p => p.post_id)
130
+ .filter(p => !taggedPostIds.includes(p));
140
131
  }
132
+
133
+ // assign tag to posts
134
+ // do bulk insert for performance reasons
135
+ // - go ahead and assign sort_order 0 to all of them
136
+ await this.models.Base.knex('posts_tags')
137
+ .transacting(transacting)
138
+ .insert(usersPostIds.map(postId => ({
139
+ id: (new ObjectId()).toHexString(),
140
+ post_id: postId,
141
+ tag_id: tag.get('id'),
142
+ sort_order: 0
143
+ })));
144
+
145
+ // manually add an entry in the Actions table that specifies the number of posts edited; see #bulkAddTags for similar logic
146
+ await this.models.Post.addActions('edited', usersPostIds, {transacting, context});
147
+
148
+ // dispatch event to ensure collections are updated
149
+ DomainEvents.dispatch(PostsBulkAddTagsEvent.create(usersPostIds));
141
150
  }
142
151
 
143
152
  /**
@@ -159,26 +168,29 @@ class Users {
159
168
  return this.models.Base.transaction(async (t) => {
160
169
  frameOptions.transacting = t;
161
170
 
171
+ // null author field for users' post revisions
162
172
  const postRevisions = new PostRevisions({
163
173
  model: this.models.PostRevision
164
174
  });
165
-
166
175
  await postRevisions.removeAuthorFromRevisions(frameOptions.id, {
167
176
  transacting: frameOptions.transacting
168
177
  });
169
178
 
179
+ // create a #author-slug tag and assign it to their posts
170
180
  await this.assignTagToUserPosts({
171
181
  id: frameOptions.id,
172
182
  context: frameOptions.context,
173
183
  transacting: frameOptions.transacting
174
184
  });
175
185
 
186
+ // reassign posts to owner user
176
187
  await this.models.Post.reassignByAuthor({
177
188
  id: frameOptions.id,
178
189
  context: frameOptions.context,
179
190
  transacting: frameOptions.transacting
180
191
  });
181
192
 
193
+ // delete user
182
194
  try {
183
195
  await this.models.ApiKey.destroy({
184
196
  ...frameOptions,
@@ -1,27 +1,51 @@
1
1
  const LinkRedirect = require('@tryghost/link-redirects').LinkRedirect;
2
2
  const ObjectID = require('bson-objectid').default;
3
+ const debug = require('@tryghost/debug')('LinkRedirectRepository');
3
4
 
4
5
  module.exports = class LinkRedirectRepository {
5
6
  /** @type {Object} */
6
7
  #LinkRedirect;
7
8
  /** @type {Object} */
8
9
  #urlUtils;
10
+ /** @type {Boolean} */
11
+ #cacheEnabled;
12
+ /** @type {Object} */
13
+ #cache;
9
14
 
10
15
  /**
11
16
  * @param {object} deps
12
- * @param {object} deps.LinkRedirect Bookshelf Model
17
+ * @param {object} deps.LinkRedirect - Bookshelf Model
13
18
  * @param {object} deps.urlUtils
19
+ * @param {object} deps.cacheAdapter - Cache Adapter instance, or null if cache is disabled
20
+ * @param {object} deps.EventRegistry
14
21
  */
15
22
  constructor(deps) {
23
+ debug('Creating LinkRedirectRepository');
16
24
  this.#LinkRedirect = deps.LinkRedirect;
17
25
  this.#urlUtils = deps.urlUtils;
26
+ this.#cache = null;
27
+ if (deps.cacheAdapter !== null) {
28
+ debug('Caching enabled with adapter:', deps.cacheAdapter.constructor.name);
29
+ this.#cache = deps.cacheAdapter;
30
+ // This is a bit of a blunt instrument, but it's the best we can do for now
31
+ // It covers all the cases we would need to invalidate the links cache
32
+ // We need to invalidate the cache when:
33
+ // - a redirect is edited
34
+ // - a site's subdirectory is changed (rare)
35
+ // - analytics settings are changed
36
+ deps.EventRegistry.on('site.changed', () => {
37
+ this.#cache.reset();
38
+ });
39
+ }
18
40
  }
19
41
 
20
42
  /**
43
+ * Save a new LinkRedirect to the DB
21
44
  * @param {InstanceType<LinkRedirect>} linkRedirect
22
45
  * @returns {Promise<void>}
23
46
  */
24
47
  async save(linkRedirect) {
48
+ debug('Saving link redirect', linkRedirect.from.pathname, '->', linkRedirect.to.href);
25
49
  const model = await this.#LinkRedirect.add({
26
50
  // Only store the pathname (no support for variable query strings)
27
51
  from: this.stripSubdirectoryFromPath(linkRedirect.from.pathname),
@@ -29,17 +53,31 @@ module.exports = class LinkRedirectRepository {
29
53
  }, {});
30
54
 
31
55
  linkRedirect.link_id = ObjectID.createFromHexString(model.id);
56
+ if (this.#cache) {
57
+ debug('Caching new link redirect', linkRedirect.from.pathname);
58
+ this.#cache.set(linkRedirect.from.pathname, this.#serialize(linkRedirect));
59
+ }
32
60
  }
33
61
 
62
+ /**
63
+ * Trim the leading slash from a URL path
64
+ * @param {string} url
65
+ * @returns {string} url without leading slash
66
+ */
34
67
  #trimLeadingSlash(url) {
35
68
  return url.replace(/^\//, '');
36
69
  }
37
70
 
71
+ /**
72
+ * Returns a LinkRedirect object from a model
73
+ * @param {object} model - Bookshelf model instance
74
+ * @returns {InstanceType<LinkRedirect>} LinkRedirect
75
+ */
38
76
  fromModel(model) {
39
77
  // Store if link has been edited
40
78
  // Note: in some edge cases updated_at is set directly after created_at, sometimes with a second difference, so we need to check for that
41
79
  const edited = model.get('updated_at')?.getTime() > (model.get('created_at')?.getTime() + 1000);
42
-
80
+
43
81
  return new LinkRedirect({
44
82
  id: model.id,
45
83
  from: new URL(this.#trimLeadingSlash(model.get('from')), this.#urlUtils.urlFor('home', true)),
@@ -48,6 +86,43 @@ module.exports = class LinkRedirectRepository {
48
86
  });
49
87
  }
50
88
 
89
+ /**
90
+ * Create a LinkRedirect object from a JSON object (e.g. from the cache)
91
+ * @param {object} serialized
92
+ * @param {string} serialized.link_id - string representation of ObjectID
93
+ * @param {string} serialized.from - path of the URL
94
+ * @param {string} serialized.to - URL to redirect to
95
+ * @param {boolean} serialized.edited - whether the link has been edited
96
+ * @returns {InstanceType<LinkRedirect>} LinkRedirect
97
+ */
98
+ #fromSerialized(serialized) {
99
+ return new LinkRedirect({
100
+ id: serialized.link_id,
101
+ from: new URL(this.#trimLeadingSlash(serialized.from), this.#urlUtils.urlFor('home', true)),
102
+ to: new URL(serialized.to),
103
+ edited: serialized.edited
104
+ });
105
+ }
106
+
107
+ /**
108
+ * Serialize a LinkRedirect object to a plain object (e.g. for caching)
109
+ * @param {InstanceType<LinkRedirect>} linkRedirect
110
+ * @returns {object} - serialized LinkRedirect
111
+ */
112
+ #serialize(linkRedirect) {
113
+ return {
114
+ link_id: linkRedirect.link_id.toHexString(),
115
+ from: linkRedirect.from.pathname,
116
+ to: linkRedirect.to.href,
117
+ edited: linkRedirect.edited
118
+ };
119
+ }
120
+
121
+ /**
122
+ * Get all LinkRedirects from the DB, with optional filters
123
+ * @param {object} options - options passed directly to LinkRedirect.findAll
124
+ * @returns {Promise<InstanceType<LinkRedirect>[]>} array of LinkRedirects
125
+ */
51
126
  async getAll(options) {
52
127
  const collection = await this.#LinkRedirect.findAll(options);
53
128
 
@@ -60,6 +135,11 @@ module.exports = class LinkRedirectRepository {
60
135
  return result;
61
136
  }
62
137
 
138
+ /**
139
+ * Get all LinkRedirect IDs from the DB, with optional filters
140
+ * @param {object} options - options passed directly to LinkRedirect.getFilteredCollectionQuery
141
+ * @returns {Promise<string[]>} array of LinkRedirect IDs
142
+ */
63
143
  async getFilteredIds(options) {
64
144
  const linkRows = await this.#LinkRedirect.getFilteredCollectionQuery(options)
65
145
  .select('redirects.id')
@@ -68,25 +148,44 @@ module.exports = class LinkRedirectRepository {
68
148
  }
69
149
 
70
150
  /**
71
- *
151
+ * Get a LinkRedirect by its URL
72
152
  * @param {URL} url
73
- * @returns {Promise<InstanceType<LinkRedirect>|undefined>} linkRedirect
153
+ * @returns {Promise<InstanceType<LinkRedirect>|undefined>} LinkRedirect
74
154
  */
75
155
  async getByURL(url) {
156
+ debug('Getting link redirect for', url.pathname);
76
157
  // Strip subdirectory from path
77
158
  const from = this.stripSubdirectoryFromPath(url.pathname);
78
159
 
79
- const linkRedirect = await this.#LinkRedirect.findOne({
160
+ if (this.#cache) {
161
+ const cachedLink = await this.#cache.get(from);
162
+ // Cache hit, serve from cache
163
+ if (cachedLink) {
164
+ debug('Cache hit for', from);
165
+ return this.#fromSerialized(cachedLink);
166
+ }
167
+ }
168
+
169
+ // Cache miss, fetch from the DB
170
+ const linkRedirectModel = await this.#LinkRedirect.findOne({
80
171
  from
81
172
  }, {});
82
173
 
83
- if (linkRedirect) {
84
- return this.fromModel(linkRedirect);
174
+ if (linkRedirectModel) {
175
+ const linkRedirect = this.fromModel(linkRedirectModel);
176
+ if (this.#cache) {
177
+ debug('Cache miss for', from, '. Caching');
178
+ // Cache the link
179
+ this.#cache.set(from, this.#serialize(linkRedirect));
180
+ }
181
+ return linkRedirect;
85
182
  }
86
183
  }
87
184
 
88
185
  /**
89
186
  * Convert root relative URLs to subdirectory relative URLs
187
+ * @param {string} path
188
+ * @returns {string} path without subdirectory
90
189
  */
91
190
  stripSubdirectoryFromPath(path) {
92
191
  // Bit weird, but only way to do it with the urlUtils atm
@@ -1,5 +1,8 @@
1
1
  const urlUtils = require('../../../shared/url-utils');
2
2
  const LinkRedirectRepository = require('./LinkRedirectRepository');
3
+ const adapterManager = require('../adapter-manager');
4
+ const config = require('../../../shared/config');
5
+ const EventRegistry = require('../../lib/common/events');
3
6
 
4
7
  class LinkRedirectsServiceWrapper {
5
8
  async init() {
@@ -15,7 +18,9 @@ class LinkRedirectsServiceWrapper {
15
18
 
16
19
  this.linkRedirectRepository = new LinkRedirectRepository({
17
20
  LinkRedirect: models.Redirect,
18
- urlUtils
21
+ urlUtils,
22
+ cacheAdapter: config.get('hostSettings:linkRedirectsPublicCache:enabled') ? adapterManager.getAdapter('cache:linkRedirectsPublic') : null,
23
+ EventRegistry
19
24
  });
20
25
 
21
26
  // Expose the service
@@ -7,7 +7,6 @@ const tpl = require('@tryghost/tpl');
7
7
  const logging = require('@tryghost/logging');
8
8
  const request = require('@tryghost/request');
9
9
  const settingsCache = require('../../shared/settings-cache');
10
- const sentry = require('../../shared/sentry');
11
10
 
12
11
  // Used to receive post.published model event
13
12
  const events = require('../lib/common/events');
@@ -110,7 +109,6 @@ function ping(post) {
110
109
  });
111
110
  }
112
111
  logging.error(error);
113
- sentry.captureException(error);
114
112
  });
115
113
  });
116
114
  }
@@ -34,6 +34,8 @@ const BETA_FEATURES = [
34
34
  'additionalPaymentMethods',
35
35
  'i18n',
36
36
  'activitypub',
37
+ 'internalLinking',
38
+ 'stripeAutomaticTax',
37
39
  'webmentions'
38
40
  ];
39
41
 
@@ -43,7 +45,6 @@ const ALPHA_FEATURES = [
43
45
  'urlCache',
44
46
  'lexicalMultiplayer',
45
47
  'websockets',
46
- 'stripeAutomaticTax',
47
48
  'emailCustomization',
48
49
  'mailEvents',
49
50
  'collectionsCard',
@@ -52,8 +53,7 @@ const ALPHA_FEATURES = [
52
53
  'lexicalIndicators',
53
54
  // 'adminXOffers',
54
55
  'adminXDemo',
55
- 'membersSpamPrevention',
56
- 'internalLinking'
56
+ 'membersSpamPrevention'
57
57
  ];
58
58
 
59
59
  module.exports.GA_KEYS = [...GA_FEATURES];