ghost 5.96.2 → 5.97.1

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 (187) hide show
  1. package/components/tryghost-adapter-cache-memory-ttl-5.97.1.tgz +0 -0
  2. package/components/{tryghost-adapter-cache-redis-5.96.2.tgz → tryghost-adapter-cache-redis-5.97.1.tgz} +0 -0
  3. package/components/tryghost-adapter-manager-5.97.1.tgz +0 -0
  4. package/components/tryghost-announcement-bar-settings-5.97.1.tgz +0 -0
  5. package/components/{tryghost-api-framework-5.96.2.tgz → tryghost-api-framework-5.97.1.tgz} +0 -0
  6. package/components/tryghost-api-version-compatibility-service-5.97.1.tgz +0 -0
  7. package/components/tryghost-audience-feedback-5.97.1.tgz +0 -0
  8. package/components/tryghost-bookshelf-repository-5.97.1.tgz +0 -0
  9. package/components/tryghost-bootstrap-socket-5.97.1.tgz +0 -0
  10. package/components/tryghost-collections-5.97.1.tgz +0 -0
  11. package/components/tryghost-constants-5.97.1.tgz +0 -0
  12. package/components/tryghost-custom-theme-settings-service-5.97.1.tgz +0 -0
  13. package/components/{tryghost-data-generator-5.96.2.tgz → tryghost-data-generator-5.97.1.tgz} +0 -0
  14. package/components/tryghost-domain-events-5.97.1.tgz +0 -0
  15. package/components/tryghost-donations-5.97.1.tgz +0 -0
  16. package/components/tryghost-dynamic-routing-events-5.97.1.tgz +0 -0
  17. package/components/tryghost-email-addresses-5.97.1.tgz +0 -0
  18. package/components/tryghost-email-analytics-provider-mailgun-5.97.1.tgz +0 -0
  19. package/components/tryghost-email-analytics-service-5.97.1.tgz +0 -0
  20. package/components/{tryghost-email-content-generator-5.96.2.tgz → tryghost-email-content-generator-5.97.1.tgz} +0 -0
  21. package/components/tryghost-email-events-5.97.1.tgz +0 -0
  22. package/components/tryghost-email-service-5.97.1.tgz +0 -0
  23. package/components/tryghost-email-suppression-list-5.97.1.tgz +0 -0
  24. package/components/tryghost-express-dynamic-redirects-5.97.1.tgz +0 -0
  25. package/components/tryghost-external-media-inliner-5.97.1.tgz +0 -0
  26. package/components/tryghost-extract-api-key-5.97.1.tgz +0 -0
  27. package/components/{tryghost-ghost-5.96.2.tgz → tryghost-ghost-5.97.1.tgz} +0 -0
  28. package/components/tryghost-html-to-plaintext-5.97.1.tgz +0 -0
  29. package/components/tryghost-i18n-5.97.1.tgz +0 -0
  30. package/components/tryghost-importer-handler-content-files-5.97.1.tgz +0 -0
  31. package/components/tryghost-importer-revue-5.97.1.tgz +0 -0
  32. package/components/tryghost-in-memory-repository-5.97.1.tgz +0 -0
  33. package/components/{tryghost-job-manager-5.96.2.tgz → tryghost-job-manager-5.97.1.tgz} +0 -0
  34. package/components/tryghost-link-redirects-5.97.1.tgz +0 -0
  35. package/components/tryghost-link-replacer-5.97.1.tgz +0 -0
  36. package/components/tryghost-link-tracking-5.97.1.tgz +0 -0
  37. package/components/tryghost-magic-link-5.97.1.tgz +0 -0
  38. package/components/{tryghost-mail-events-5.96.2.tgz → tryghost-mail-events-5.97.1.tgz} +0 -0
  39. package/components/{tryghost-mailgun-client-5.96.2.tgz → tryghost-mailgun-client-5.97.1.tgz} +0 -0
  40. package/components/tryghost-member-attribution-5.97.1.tgz +0 -0
  41. package/components/tryghost-member-events-5.97.1.tgz +0 -0
  42. package/components/tryghost-members-api-5.97.1.tgz +0 -0
  43. package/components/tryghost-members-csv-5.97.1.tgz +0 -0
  44. package/components/{tryghost-members-events-service-5.96.2.tgz → tryghost-members-events-service-5.97.1.tgz} +0 -0
  45. package/components/tryghost-members-importer-5.97.1.tgz +0 -0
  46. package/components/{tryghost-members-offers-5.96.2.tgz → tryghost-members-offers-5.97.1.tgz} +0 -0
  47. package/components/tryghost-members-payments-5.97.1.tgz +0 -0
  48. package/components/tryghost-members-ssr-5.97.1.tgz +0 -0
  49. package/components/{tryghost-members-stripe-service-5.96.2.tgz → tryghost-members-stripe-service-5.97.1.tgz} +0 -0
  50. package/components/tryghost-mentions-email-report-5.97.1.tgz +0 -0
  51. package/components/tryghost-metrics-server-5.97.1.tgz +0 -0
  52. package/components/tryghost-milestones-5.97.1.tgz +0 -0
  53. package/components/tryghost-minifier-5.97.1.tgz +0 -0
  54. package/components/{tryghost-model-to-domain-event-interceptor-5.96.2.tgz → tryghost-model-to-domain-event-interceptor-5.97.1.tgz} +0 -0
  55. package/components/tryghost-mw-api-version-mismatch-5.97.1.tgz +0 -0
  56. package/components/tryghost-mw-cache-control-5.97.1.tgz +0 -0
  57. package/components/tryghost-mw-error-handler-5.97.1.tgz +0 -0
  58. package/components/tryghost-mw-session-from-token-5.97.1.tgz +0 -0
  59. package/components/tryghost-mw-update-user-last-seen-5.97.1.tgz +0 -0
  60. package/components/tryghost-mw-version-match-5.97.1.tgz +0 -0
  61. package/components/tryghost-mw-vhost-5.97.1.tgz +0 -0
  62. package/components/tryghost-nql-filter-expansions-5.97.1.tgz +0 -0
  63. package/components/{tryghost-oembed-service-5.96.2.tgz → tryghost-oembed-service-5.97.1.tgz} +0 -0
  64. package/components/tryghost-package-json-5.97.1.tgz +0 -0
  65. package/components/tryghost-post-events-5.97.1.tgz +0 -0
  66. package/components/tryghost-post-revisions-5.97.1.tgz +0 -0
  67. package/components/tryghost-posts-service-5.97.1.tgz +0 -0
  68. package/components/tryghost-recommendations-5.97.1.tgz +0 -0
  69. package/components/tryghost-referrers-5.97.1.tgz +0 -0
  70. package/components/{tryghost-security-5.96.2.tgz → tryghost-security-5.97.1.tgz} +0 -0
  71. package/components/tryghost-session-service-5.97.1.tgz +0 -0
  72. package/components/tryghost-settings-path-manager-5.97.1.tgz +0 -0
  73. package/components/tryghost-slack-notifications-5.97.1.tgz +0 -0
  74. package/components/{tryghost-staff-service-5.96.2.tgz → tryghost-staff-service-5.97.1.tgz} +0 -0
  75. package/components/{tryghost-stats-service-5.96.2.tgz → tryghost-stats-service-5.97.1.tgz} +0 -0
  76. package/components/{tryghost-tiers-5.96.2.tgz → tryghost-tiers-5.97.1.tgz} +0 -0
  77. package/components/tryghost-update-check-service-5.97.1.tgz +0 -0
  78. package/components/tryghost-verification-trigger-5.97.1.tgz +0 -0
  79. package/components/tryghost-version-notifications-data-service-5.97.1.tgz +0 -0
  80. package/components/{tryghost-webmentions-5.96.2.tgz → tryghost-webmentions-5.97.1.tgz} +0 -0
  81. package/core/built/admin/assets/admin-x-activitypub/admin-x-activitypub.js +1 -1
  82. package/core/built/admin/assets/admin-x-activitypub/{index-def21e23.mjs → index-ce1044b4.mjs} +522 -525
  83. package/core/built/admin/assets/admin-x-activitypub/{modals-09282c4e.mjs → modals-35dd1224.mjs} +2 -2
  84. package/core/built/admin/assets/admin-x-demo/admin-x-demo.js +1 -1
  85. package/core/built/admin/assets/admin-x-demo/{index-3d57e7aa.mjs → index-4e95bd22.mjs} +4 -4
  86. package/core/built/admin/assets/admin-x-demo/{modals-191eb359.mjs → modals-1dd517c9.mjs} +2 -2
  87. package/core/built/admin/assets/admin-x-settings/{CodeEditorView-85a3642c.mjs → CodeEditorView-56b3751e.mjs} +2 -2
  88. package/core/built/admin/assets/admin-x-settings/admin-x-settings.js +2 -2
  89. package/core/built/admin/assets/admin-x-settings/{index-ebbd8de9.mjs → index-a57b83d6.mjs} +5 -5
  90. package/core/built/admin/assets/admin-x-settings/{index-eeeca6f6.mjs → index-ae6dfa81.mjs} +2 -2
  91. package/core/built/admin/assets/admin-x-settings/{modals-8a98634b.mjs → modals-8dbfe675.mjs} +1322 -1305
  92. package/core/built/admin/assets/{chunk.524.e61cf6deaaec6bf6f9d2.js → chunk.524.8daf11dc6681a463afc3.js} +5 -5
  93. package/core/built/admin/assets/{chunk.582.6fb536052fa03fba4adb.js → chunk.582.8e8f4dd40e8a4c117a8c.js} +6 -6
  94. package/core/built/admin/assets/{chunk.874.e1d421507701daac30fb.js → chunk.874.5eb920d19e75683234c2.js} +6 -6
  95. package/core/built/admin/assets/ghost-84fc705038c0ea946e4e5048ccb1fea5.css +1 -0
  96. package/core/built/admin/assets/{ghost-286f9a114755e8c3c7e9c37550f624a9.js → ghost-b7702081322ac2a966ad205b1d07416c.js} +57 -62
  97. package/core/built/admin/assets/ghost-dark-b00268cf596f7bba8699dce557d65585.css +1 -0
  98. package/core/built/admin/index.html +5 -5
  99. package/core/frontend/services/assets-minification/AssetsMinificationBase.js +4 -4
  100. package/core/server/api/endpoints/comments.js +18 -2
  101. package/core/server/api/endpoints/utils/serializers/input/settings.js +3 -1
  102. package/core/server/api/endpoints/utils/serializers/output/members.js +2 -1
  103. package/core/server/data/db/DatabaseStateManager.js +29 -2
  104. package/core/server/data/migrations/versions/5.97/2024-10-08-14-25-27-added-body-font-settings.js +8 -0
  105. package/core/server/data/migrations/versions/5.97/2024-10-08-14-36-58-added-heading-font-setting.js +8 -0
  106. package/core/server/data/migrations/versions/5.97/2024-10-10-01-02-03-add-signin-urls-permissions.js +6 -0
  107. package/core/server/data/schema/default-settings/default-settings.json +8 -0
  108. package/core/server/data/schema/fixtures/fixtures.json +2 -1
  109. package/core/server/models/base/plugins/data-manipulation.js +11 -15
  110. package/core/server/models/base/plugins/raw-knex.js +3 -3
  111. package/core/server/models/base/plugins/sanitize.js +12 -13
  112. package/core/server/models/post.js +5 -3
  113. package/core/server/services/members/api.js +3 -1
  114. package/core/server/services/members/utils.js +1 -0
  115. package/core/server/services/settings-helpers/SettingsHelpers.js +25 -0
  116. package/core/server/services/url/Resource.js +1 -2
  117. package/core/server/services/url/Resources.js +12 -12
  118. package/core/server/services/url/UrlGenerator.js +1 -2
  119. package/core/server/services/url/config.js +9 -0
  120. package/core/server/web/api/middleware/upload.js +2 -1
  121. package/package.json +219 -153
  122. package/yarn.lock +203 -244
  123. package/components/tryghost-adapter-cache-memory-ttl-5.96.2.tgz +0 -0
  124. package/components/tryghost-adapter-manager-5.96.2.tgz +0 -0
  125. package/components/tryghost-announcement-bar-settings-5.96.2.tgz +0 -0
  126. package/components/tryghost-api-version-compatibility-service-5.96.2.tgz +0 -0
  127. package/components/tryghost-audience-feedback-5.96.2.tgz +0 -0
  128. package/components/tryghost-bookshelf-repository-5.96.2.tgz +0 -0
  129. package/components/tryghost-bootstrap-socket-5.96.2.tgz +0 -0
  130. package/components/tryghost-collections-5.96.2.tgz +0 -0
  131. package/components/tryghost-constants-5.96.2.tgz +0 -0
  132. package/components/tryghost-custom-theme-settings-service-5.96.2.tgz +0 -0
  133. package/components/tryghost-domain-events-5.96.2.tgz +0 -0
  134. package/components/tryghost-donations-5.96.2.tgz +0 -0
  135. package/components/tryghost-dynamic-routing-events-5.96.2.tgz +0 -0
  136. package/components/tryghost-email-addresses-5.96.2.tgz +0 -0
  137. package/components/tryghost-email-analytics-provider-mailgun-5.96.2.tgz +0 -0
  138. package/components/tryghost-email-analytics-service-5.96.2.tgz +0 -0
  139. package/components/tryghost-email-events-5.96.2.tgz +0 -0
  140. package/components/tryghost-email-service-5.96.2.tgz +0 -0
  141. package/components/tryghost-email-suppression-list-5.96.2.tgz +0 -0
  142. package/components/tryghost-express-dynamic-redirects-5.96.2.tgz +0 -0
  143. package/components/tryghost-external-media-inliner-5.96.2.tgz +0 -0
  144. package/components/tryghost-extract-api-key-5.96.2.tgz +0 -0
  145. package/components/tryghost-html-to-plaintext-5.96.2.tgz +0 -0
  146. package/components/tryghost-i18n-5.96.2.tgz +0 -0
  147. package/components/tryghost-importer-handler-content-files-5.96.2.tgz +0 -0
  148. package/components/tryghost-importer-revue-5.96.2.tgz +0 -0
  149. package/components/tryghost-in-memory-repository-5.96.2.tgz +0 -0
  150. package/components/tryghost-link-redirects-5.96.2.tgz +0 -0
  151. package/components/tryghost-link-replacer-5.96.2.tgz +0 -0
  152. package/components/tryghost-link-tracking-5.96.2.tgz +0 -0
  153. package/components/tryghost-magic-link-5.96.2.tgz +0 -0
  154. package/components/tryghost-member-attribution-5.96.2.tgz +0 -0
  155. package/components/tryghost-member-events-5.96.2.tgz +0 -0
  156. package/components/tryghost-members-api-5.96.2.tgz +0 -0
  157. package/components/tryghost-members-csv-5.96.2.tgz +0 -0
  158. package/components/tryghost-members-importer-5.96.2.tgz +0 -0
  159. package/components/tryghost-members-payments-5.96.2.tgz +0 -0
  160. package/components/tryghost-members-ssr-5.96.2.tgz +0 -0
  161. package/components/tryghost-mentions-email-report-5.96.2.tgz +0 -0
  162. package/components/tryghost-metrics-server-5.96.2.tgz +0 -0
  163. package/components/tryghost-milestones-5.96.2.tgz +0 -0
  164. package/components/tryghost-minifier-5.96.2.tgz +0 -0
  165. package/components/tryghost-mw-api-version-mismatch-5.96.2.tgz +0 -0
  166. package/components/tryghost-mw-cache-control-5.96.2.tgz +0 -0
  167. package/components/tryghost-mw-error-handler-5.96.2.tgz +0 -0
  168. package/components/tryghost-mw-session-from-token-5.96.2.tgz +0 -0
  169. package/components/tryghost-mw-update-user-last-seen-5.96.2.tgz +0 -0
  170. package/components/tryghost-mw-version-match-5.96.2.tgz +0 -0
  171. package/components/tryghost-mw-vhost-5.96.2.tgz +0 -0
  172. package/components/tryghost-nql-filter-expansions-5.96.2.tgz +0 -0
  173. package/components/tryghost-package-json-5.96.2.tgz +0 -0
  174. package/components/tryghost-post-events-5.96.2.tgz +0 -0
  175. package/components/tryghost-post-revisions-5.96.2.tgz +0 -0
  176. package/components/tryghost-posts-service-5.96.2.tgz +0 -0
  177. package/components/tryghost-recommendations-5.96.2.tgz +0 -0
  178. package/components/tryghost-referrers-5.96.2.tgz +0 -0
  179. package/components/tryghost-session-service-5.96.2.tgz +0 -0
  180. package/components/tryghost-settings-path-manager-5.96.2.tgz +0 -0
  181. package/components/tryghost-slack-notifications-5.96.2.tgz +0 -0
  182. package/components/tryghost-update-check-service-5.96.2.tgz +0 -0
  183. package/components/tryghost-verification-trigger-5.96.2.tgz +0 -0
  184. package/components/tryghost-version-notifications-data-service-5.96.2.tgz +0 -0
  185. package/core/built/admin/assets/ghost-c75b9d653495f9cfce528f6e10900b3f.css +0 -1
  186. package/core/built/admin/assets/ghost-dark-6ea629e177f77314f5d75c0ca97190ab.css +0 -1
  187. /package/core/built/admin/assets/{chunk.874.e1d421507701daac30fb.js.LICENSE.txt → chunk.874.5eb920d19e75683234c2.js.LICENSE.txt} +0 -0
@@ -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.96%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%22ee37f0b9eb%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%22eb50ad82db%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%223e886898df%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%2268888cb677%22%2C%22adminXActivitypubCustomUrl%22%3A%22https%3A%2F%2Fcdn.jsdelivr.net%2Fnpm%2F%40tryghost%2Fadmin-x-activitypub%400%2Fdist%2Fadmin-x-activitypub.js%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.97%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%22ee37f0b9eb%22%2C%22adminXDemoFilename%22%3A%22admin-x-demo.js%22%2C%22adminXDemoHash%22%3A%2224d6ef8fbd%22%2C%22adminXSettingsFilename%22%3A%22admin-x-settings.js%22%2C%22adminXSettingsHash%22%3A%22b1e0cd5a92%22%2C%22adminXActivitypubFilename%22%3A%22admin-x-activitypub.js%22%2C%22adminXActivitypubHash%22%3A%229368ade858%22%2C%22adminXActivitypubCustomUrl%22%3A%22https%3A%2F%2Fcdn.jsdelivr.net%2Fnpm%2F%40tryghost%2Fadmin-x-activitypub%400%2Fdist%2Fadmin-x-activitypub.js%22%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -37,7 +37,7 @@
37
37
  </style>
38
38
 
39
39
  <link integrity="" rel="stylesheet" href="assets/vendor-0ede59da8efb5e28fa929557f7ff7154.css">
40
- <link integrity="" rel="stylesheet" href="assets/ghost-c75b9d653495f9cfce528f6e10900b3f.css" title="light">
40
+ <link integrity="" rel="stylesheet" href="assets/ghost-84fc705038c0ea946e4e5048ccb1fea5.css" title="light">
41
41
 
42
42
 
43
43
  </head>
@@ -57,8 +57,8 @@
57
57
  <div id="ember-basic-dropdown-wormhole"></div>
58
58
 
59
59
  <script src="assets/vendor-88cd9f5cf5eba65221e2d2a636531eaa.js"></script>
60
- <script src="assets/chunk.874.e1d421507701daac30fb.js"></script>
61
- <script src="assets/chunk.524.e61cf6deaaec6bf6f9d2.js"></script>
62
- <script src="assets/ghost-286f9a114755e8c3c7e9c37550f624a9.js"></script>
60
+ <script src="assets/chunk.874.5eb920d19e75683234c2.js"></script>
61
+ <script src="assets/chunk.524.8daf11dc6681a463afc3.js"></script>
62
+ <script src="assets/ghost-b7702081322ac2a966ad205b1d07416c.js"></script>
63
63
  </body>
64
64
  </html>
@@ -48,16 +48,16 @@ module.exports = class AssetsMinificationBase {
48
48
 
49
49
  async minify(globs, options) {
50
50
  try {
51
- return await this.minifier.minify(globs, options);
51
+ const result = await this.minifier.minify(globs, options);
52
+ this.ready = true;
53
+ return result;
52
54
  } catch (error) {
53
55
  if (error.code === 'EACCES') {
54
56
  logging.error('Ghost was not able to write asset files due to permissions.');
55
57
  return;
56
58
  }
57
59
 
58
- throw error;
59
- } finally {
60
- this.ready = true;
60
+ throw error;
61
61
  }
62
62
  }
63
63
 
@@ -1,5 +1,17 @@
1
1
  const models = require('../../models');
2
2
 
3
+ function handleCacheHeaders(model, frame) {
4
+ if (model) {
5
+ const postId = model.get('post_id');
6
+ const parentId = model.get('parent_id');
7
+ const pathsToInvalidate = [
8
+ postId ? `/api/members/comments/post/${postId}/` : null,
9
+ parentId ? `/api/members/comments/${parentId}/replies/` : null
10
+ ].filter(path => path !== null);
11
+ frame.setHeader('X-Cache-Invalidate', pathsToInvalidate.join(', '));
12
+ }
13
+ }
14
+
3
15
  /** @type {import('@tryghost/api-framework').Controller} */
4
16
  const controller = {
5
17
  docName: 'comments',
@@ -19,11 +31,15 @@ const controller = {
19
31
  }
20
32
  },
21
33
  permissions: true,
22
- query(frame) {
23
- return models.Comment.edit({
34
+ async query(frame) {
35
+ const result = await models.Comment.edit({
24
36
  id: frame.data.comments[0].id,
25
37
  status: frame.data.comments[0].status
26
38
  }, frame.options);
39
+
40
+ handleCacheHeaders(result, frame);
41
+
42
+ return result;
27
43
  }
28
44
  }
29
45
  };
@@ -72,7 +72,9 @@ const EDITABLE_SETTINGS = [
72
72
  'pintura_css_url',
73
73
  'donations_currency',
74
74
  'donations_suggested_amount',
75
- 'recommendations_enabled'
75
+ 'recommendations_enabled',
76
+ 'body_font',
77
+ 'heading_font'
76
78
  ];
77
79
 
78
80
  module.exports = {
@@ -167,7 +167,8 @@ function serializeMember(member, options) {
167
167
  email_recipients: json.email_recipients,
168
168
  status: json.status,
169
169
  last_seen_at: json.last_seen_at,
170
- attribution: serializeAttribution(json.attribution)
170
+ attribution: serializeAttribution(json.attribution),
171
+ unsubscribe_url: json.unsubscribe_url
171
172
  };
172
173
 
173
174
  if (json.products) {
@@ -1,6 +1,9 @@
1
1
  const KnexMigrator = require('knex-migrator');
2
2
  const errors = require('@tryghost/errors');
3
3
  const logging = require('@tryghost/logging');
4
+ const metrics = require('@tryghost/metrics');
5
+
6
+ const sentry = require('../../../shared/sentry');
4
7
 
5
8
  const states = {
6
9
  READY: 0,
@@ -61,9 +64,14 @@ class DatabaseStateManager {
61
64
  // CASE: database connection errors, unknown cases
62
65
  let errorToThrow = error;
63
66
  if (!errors.utils.isGhostError(errorToThrow)) {
64
- errorToThrow = new errors.InternalServerError({message: errorToThrow.message, err: errorToThrow});
67
+ errorToThrow = new errors.InternalServerError({
68
+ code: 'DATABASE_ERROR',
69
+ message: errorToThrow.message,
70
+ err: errorToThrow
71
+ });
65
72
  }
66
73
 
74
+ sentry.captureException(errorToThrow);
67
75
  throw errorToThrow;
68
76
  }
69
77
  }
@@ -79,11 +87,25 @@ class DatabaseStateManager {
79
87
  }
80
88
 
81
89
  if (state === states.NEEDS_INITIALISATION) {
90
+ const beforeInitializationTime = Date.now();
91
+
82
92
  await this.knexMigrator.init();
93
+
94
+ metrics.metric('migrations', {
95
+ value: Date.now() - beforeInitializationTime,
96
+ type: 'initialization'
97
+ });
83
98
  }
84
99
 
85
100
  if (state === states.NEEDS_MIGRATION) {
101
+ const beforeMigrationTime = Date.now();
102
+
86
103
  await this.knexMigrator.migrate();
104
+
105
+ metrics.metric('migrations', {
106
+ value: Date.now() - beforeMigrationTime,
107
+ type: 'migrations'
108
+ });
87
109
  }
88
110
 
89
111
  state = await this.getState();
@@ -92,9 +114,14 @@ class DatabaseStateManager {
92
114
  } catch (error) {
93
115
  let errorToThrow = error;
94
116
  if (!errors.utils.isGhostError(error)) {
95
- errorToThrow = new errors.InternalServerError({message: errorToThrow.message, err: errorToThrow});
117
+ errorToThrow = new errors.InternalServerError({
118
+ code: 'DATABASE_ERROR',
119
+ message: errorToThrow.message,
120
+ err: errorToThrow
121
+ });
96
122
  }
97
123
 
124
+ sentry.captureException(errorToThrow);
98
125
  throw errorToThrow;
99
126
  }
100
127
  }
@@ -0,0 +1,8 @@
1
+ const {addSetting} = require('../../utils');
2
+
3
+ module.exports = addSetting({
4
+ key: 'body_font',
5
+ value: '',
6
+ type: 'string',
7
+ group: 'site'
8
+ });
@@ -0,0 +1,8 @@
1
+ const {addSetting} = require('../../utils');
2
+
3
+ module.exports = addSetting({
4
+ key: 'heading_font',
5
+ value: '',
6
+ type: 'string',
7
+ group: 'site'
8
+ });
@@ -0,0 +1,6 @@
1
+ const {addPermissionToRole} = require('../../utils');
2
+
3
+ module.exports = addPermissionToRole({
4
+ permission: 'Read member signin urls',
5
+ role: 'Admin Integration'
6
+ });
@@ -82,6 +82,14 @@
82
82
  "flags": "PUBLIC",
83
83
  "type": "string"
84
84
  },
85
+ "heading_font": {
86
+ "defaultValue": "",
87
+ "type": "string"
88
+ },
89
+ "body_font": {
90
+ "defaultValue": "",
91
+ "type": "string"
92
+ },
85
93
  "logo": {
86
94
  "defaultValue": "",
87
95
  "type": "string"
@@ -907,7 +907,8 @@
907
907
  "link": "all",
908
908
  "mention": "browse",
909
909
  "collection": "all",
910
- "recommendation": "all"
910
+ "recommendation": "all",
911
+ "member_signin_url": "read"
911
912
  },
912
913
  "Editor": {
913
914
  "notification": "all",
@@ -26,7 +26,7 @@ module.exports = function (Bookshelf) {
26
26
  * before we insert dates into the database, we have to normalize
27
27
  * date format is now in each db the same
28
28
  */
29
- fixDatesWhenSave: function fixDates(attrs) {
29
+ fixDatesWhenSave: function fixDatesWhenSave(attrs) {
30
30
  const self = this;
31
31
 
32
32
  _.each(attrs, function each(value, key) {
@@ -49,15 +49,12 @@ module.exports = function (Bookshelf) {
49
49
  * mysql:
50
50
  * - knex wraps the UTC value into a local JS Date
51
51
  */
52
- fixDatesWhenFetch: function fixDates(attrs) {
53
- const self = this;
54
- let dateMoment;
52
+ fixDatesWhenFetch: function fixDatesWhenFetch(attrs) {
53
+ const tableDef = schema.tables[this.tableName];
55
54
 
56
- _.each(attrs, function each(value, key) {
57
- if (value !== null
58
- && Object.prototype.hasOwnProperty.call(schema.tables[self.tableName], key)
59
- && schema.tables[self.tableName][key].type === 'dateTime') {
60
- dateMoment = moment(value);
55
+ Object.keys(attrs).forEach((key) => {
56
+ if (attrs[key] && tableDef?.[key]?.type === 'dateTime') {
57
+ const dateMoment = moment(attrs[key]);
61
58
 
62
59
  // CASE: You are somehow able to store e.g. 0000-00-00 00:00:00
63
60
  // Protect the code base and return the current date time.
@@ -74,12 +71,11 @@ module.exports = function (Bookshelf) {
74
71
 
75
72
  // Convert integers to real booleans
76
73
  fixBools: function fixBools(attrs) {
77
- const self = this;
78
- _.each(attrs, function each(value, key) {
79
- const tableDef = schema.tables[self.tableName];
80
- const columnDef = tableDef ? tableDef[key] : null;
81
- if (columnDef?.type === 'boolean') {
82
- attrs[key] = value ? true : false;
74
+ const tableDef = schema.tables[this.tableName];
75
+
76
+ Object.keys(attrs).forEach((key) => {
77
+ if (tableDef?.[key]?.type === 'boolean') {
78
+ attrs[key] = !!attrs[key];
83
79
  }
84
80
  });
85
81
 
@@ -172,14 +172,14 @@ module.exports = function (Bookshelf) {
172
172
  const relationsToAttach = _.zipObject(_.keys(props), relationsToAttachArray);
173
173
 
174
174
  objects = _.map(objects, (object) => {
175
- _.each(Object.keys(relationsToAttach), (relation) => {
175
+ for (const relation in relationsToAttach) {
176
176
  if (!relationsToAttach[relation][object.id]) {
177
177
  object[relation] = [];
178
- return;
178
+ continue;
179
179
  }
180
180
 
181
181
  object[relation] = relationsToAttach[relation][object.id];
182
- });
182
+ }
183
183
 
184
184
  object = Bookshelf.registry.models[modelName].prototype.toJSON.bind({
185
185
  attributes: object,
@@ -35,21 +35,21 @@ module.exports = function (Bookshelf) {
35
35
 
36
36
  switch (methodName) {
37
37
  case 'toJSON':
38
- return baseOptions.concat('shallow', 'columns', 'previous');
38
+ return [...baseOptions, 'shallow', 'columns', 'previous'];
39
39
  case 'destroy':
40
- return baseOptions.concat(extraOptions, ['id', 'destroyBy', 'require']);
40
+ return [...baseOptions, ...extraOptions, 'id', 'destroyBy', 'require'];
41
41
  case 'add':
42
- return baseOptions.concat(extraOptions, ['autoRefresh']);
42
+ return [...baseOptions, ...extraOptions, 'autoRefresh'];
43
43
  case 'edit':
44
- return baseOptions.concat(extraOptions, ['id', 'require', 'autoRefresh']);
44
+ return [...baseOptions, ...extraOptions, 'id', 'require', 'autoRefresh'];
45
45
  case 'findOne':
46
- return baseOptions.concat(extraOptions, ['columns', 'require', 'mongoTransformer']);
46
+ return [...baseOptions, ...extraOptions, 'columns', 'require', 'mongoTransformer'];
47
47
  case 'findAll':
48
- return baseOptions.concat(extraOptions, ['filter', 'columns', 'mongoTransformer']);
48
+ return [...baseOptions, ...extraOptions, 'filter', 'columns', 'mongoTransformer'];
49
49
  case 'findPage':
50
- return baseOptions.concat(extraOptions, ['filter', 'order', 'autoOrder', 'page', 'limit', 'columns', 'mongoTransformer']);
50
+ return [...baseOptions, ...extraOptions, 'filter', 'order', 'autoOrder', 'page', 'limit', 'columns', 'mongoTransformer'];
51
51
  default:
52
- return baseOptions.concat(extraOptions);
52
+ return [...baseOptions, ...extraOptions];
53
53
  }
54
54
  },
55
55
 
@@ -164,11 +164,10 @@ module.exports = function (Bookshelf) {
164
164
 
165
165
  let options = _.cloneDeep(unfilteredOptions);
166
166
  const extraAllowedProperties = filterConfig.extraAllowedProperties || [];
167
- let permittedOptions;
168
-
169
- permittedOptions = this.permittedOptions(methodName, options);
170
- permittedOptions = _.union(permittedOptions, extraAllowedProperties);
171
- options = _.pick(options, permittedOptions);
167
+ const permittedOptions = [...new Set([...this.permittedOptions(methodName, options), ...extraAllowedProperties])];
168
+ options = Object.fromEntries(
169
+ Object.entries(options).filter(([key]) => permittedOptions.includes(key))
170
+ );
172
171
 
173
172
  if (this.defaultRelations) {
174
173
  options = this.defaultRelations(methodName, options);
@@ -1290,11 +1290,13 @@ Post = ghostBookshelf.Model.extend({
1290
1290
  options.withRelated = _.union(['authors', 'tags', 'post_revisions', 'post_revisions.author'], options.withRelated || []);
1291
1291
  }
1292
1292
 
1293
- const META_ATTRIBUTES = _.without(ghostBookshelf.model('PostsMeta').prototype.permittedAttributes(), 'id', 'post_id');
1294
-
1295
1293
  // NOTE: only include post_meta relation when requested in 'columns' or by default
1296
1294
  // optimization is needed to be able to perform .findAll on large SQLite datasets
1297
- if (!options.columns || (options.columns && _.intersection(META_ATTRIBUTES, options.columns).length)) {
1295
+ if (!options.columns
1296
+ || (
1297
+ options.columns
1298
+ && _.intersection(_.without(ghostBookshelf.model('PostsMeta').prototype.permittedAttributes(), 'id', 'post_id'), options.columns).length)
1299
+ ) {
1298
1300
  options.withRelated = _.union(['posts_meta'], options.withRelated || []);
1299
1301
  }
1300
1302
 
@@ -1,5 +1,6 @@
1
1
  const stripeService = require('../stripe');
2
2
  const settingsCache = require('../../../shared/settings-cache');
3
+ const settingsHelpers = require('../../services/settings-helpers');
3
4
  const MembersApi = require('@tryghost/members-api');
4
5
  const logging = require('@tryghost/logging');
5
6
  const mail = require('../mail');
@@ -236,7 +237,8 @@ function createApiInstance(config) {
236
237
  memberAttributionService: memberAttributionService.service,
237
238
  emailSuppressionList,
238
239
  settingsCache,
239
- sentry
240
+ sentry,
241
+ settingsHelpers
240
242
  });
241
243
 
242
244
  return membersApiInstance;
@@ -22,6 +22,7 @@ module.exports.formattedMemberResponse = function formattedMemberResponse(member
22
22
  firstname: member.name && member.name.split(' ')[0],
23
23
  expertise: member.expertise,
24
24
  avatar_image: member.avatar_image,
25
+ unsubscribe_url: member.unsubscribe_url,
25
26
  subscribed: !!member.subscribed,
26
27
  subscriptions: member.subscriptions || [],
27
28
  paid: member.status !== 'free',
@@ -2,6 +2,7 @@ const tpl = require('@tryghost/tpl');
2
2
  const errors = require('@tryghost/errors');
3
3
  const {EmailAddressParser} = require('@tryghost/email-addresses');
4
4
  const logging = require('@tryghost/logging');
5
+ const crypto = require('crypto');
5
6
 
6
7
  const messages = {
7
8
  incorrectKeyType: 'type must be one of "direct" or "connect".'
@@ -179,6 +180,30 @@ class SettingsHelpers {
179
180
  return this.#managedEmailEnabled() || this.labs.isSet('newEmailAddresses');
180
181
  }
181
182
 
183
+ createUnsubscribeUrl(uuid, options = {}) {
184
+ const siteUrl = this.urlUtils.urlFor('home', true);
185
+ const unsubscribeUrl = new URL(siteUrl);
186
+ const key = this.getMembersValidationKey();
187
+ unsubscribeUrl.pathname = `${unsubscribeUrl.pathname}/unsubscribe/`.replace('//', '/');
188
+ if (uuid) {
189
+ // hash key with member uuid for verification (and to not leak uuid) - it's possible to update member email prefs without logging in
190
+ // @ts-ignore
191
+ const hmac = crypto.createHmac('sha256', key).update(`${uuid}`).digest('hex');
192
+ unsubscribeUrl.searchParams.set('uuid', uuid);
193
+ unsubscribeUrl.searchParams.set('key', hmac);
194
+ } else {
195
+ unsubscribeUrl.searchParams.set('preview', '1');
196
+ }
197
+ if (options.newsletterUuid) {
198
+ unsubscribeUrl.searchParams.set('newsletter', options.newsletterUuid);
199
+ }
200
+ if (options.comments) {
201
+ unsubscribeUrl.searchParams.set('comments', '1');
202
+ }
203
+
204
+ return unsubscribeUrl.href;
205
+ }
206
+
182
207
  // PRIVATE
183
208
 
184
209
  #managedEmailEnabled() {
@@ -13,13 +13,12 @@ class Resource extends EventEmitter {
13
13
  constructor(type, obj) {
14
14
  super();
15
15
 
16
- this.data = {};
17
16
  this.config = {
18
17
  type: type,
19
18
  reserved: false
20
19
  };
21
20
 
22
- Object.assign(this.data, obj);
21
+ this.data = obj;
23
22
  }
24
23
 
25
24
  /**
@@ -116,7 +116,7 @@ class Resources {
116
116
  * @returns {Promise}
117
117
  * @private
118
118
  */
119
- _fetch(resourceConfig, options = {offset: 0, limit: 999}) {
119
+ async _fetch(resourceConfig, options = {offset: 0, limit: 999}) {
120
120
  debug('_fetch', resourceConfig.type, resourceConfig.modelOptions);
121
121
 
122
122
  let modelOptions = _.cloneDeep(resourceConfig.modelOptions);
@@ -128,19 +128,19 @@ class Resources {
128
128
  modelOptions.limit = options.limit;
129
129
  }
130
130
 
131
- return models.Base.Model.raw_knex.fetchAll(modelOptions)
132
- .then((objects) => {
133
- debug('fetched', resourceConfig.type, objects.length);
131
+ const objects = await models.Base.Model.raw_knex.fetchAll(modelOptions);
132
+ debug('fetched', resourceConfig.type, objects.length);
134
133
 
135
- _.each(objects, (object) => {
136
- this.data[resourceConfig.type].push(new Resource(resourceConfig.type, object));
137
- });
134
+ _.each(objects, (object) => {
135
+ this.data[resourceConfig.type].push(new Resource(resourceConfig.type, object));
136
+ });
138
137
 
139
- if (objects.length && isSQLite) {
140
- options.offset = options.offset + options.limit;
141
- return this._fetch(resourceConfig, {offset: options.offset, limit: options.limit});
142
- }
143
- });
138
+ if (objects.length && isSQLite) {
139
+ options.offset = options.offset + options.limit;
140
+ return this._fetch(resourceConfig, {offset: options.offset, limit: options.limit});
141
+ }
142
+
143
+ return objects;
144
144
  }
145
145
 
146
146
  /**
@@ -170,11 +170,10 @@ class UrlGenerator {
170
170
  return false;
171
171
  }
172
172
 
173
- const url = this._generateUrl(resource);
174
-
175
173
  // CASE 1: route has no custom filter, it will own the resource for sure
176
174
  // CASE 2: find out if my filter matches the resource
177
175
  if ((!this.filter) || (this.nql.queryJSON(resource.data))) {
176
+ const url = this._generateUrl(resource);
178
177
  this.urls.add({
179
178
  url: url,
180
179
  generatorId: this.uid,
@@ -3,6 +3,7 @@
3
3
  * They contain minimum filters for public accessibility of resources.
4
4
  */
5
5
 
6
+ // TODO: switch exclude lists to include lists to make this more explicit
6
7
  module.exports = [
7
8
  {
8
9
  type: 'posts',
@@ -32,6 +33,10 @@ module.exports = [
32
33
  'twitter_description',
33
34
  'custom_template',
34
35
  'locale',
36
+ 'newsletter_id',
37
+ 'show_title_and_feature_image',
38
+ 'email_recipient_filter',
39
+ 'comment_id',
35
40
  'tiers'
36
41
  ],
37
42
  withRelated: ['tags', 'authors'],
@@ -81,6 +86,10 @@ module.exports = [
81
86
  'authors',
82
87
  'primary_tag',
83
88
  'primary_author',
89
+ 'newsletter_id',
90
+ 'show_title_and_feature_image',
91
+ 'email_recipient_filter',
92
+ 'comment_id',
84
93
  'tiers'
85
94
  ],
86
95
  filter: 'status:published+type:page'
@@ -6,7 +6,6 @@ const errors = require('@tryghost/errors');
6
6
  const config = require('../../../../shared/config');
7
7
  const tpl = require('@tryghost/tpl');
8
8
  const logging = require('@tryghost/logging');
9
- const {JSDOM} = require('jsdom');
10
9
 
11
10
  const messages = {
12
11
  db: {
@@ -160,6 +159,8 @@ const checkFileIsValid = (fileData, types, extensions) => {
160
159
  *
161
160
  */
162
161
  const isSvgSafe = (filepath) => {
162
+ const {JSDOM} = require('jsdom');
163
+
163
164
  const fileContent = fs.readFileSync(filepath, 'utf8');
164
165
  const document = new JSDOM(fileContent).window.document;
165
166
  document.body.innerHTML = fileContent;