ghost 5.60.0 → 5.61.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 (202) hide show
  1. package/components/tryghost-adapter-cache-memory-ttl-5.61.0.tgz +0 -0
  2. package/components/tryghost-adapter-cache-redis-5.61.0.tgz +0 -0
  3. package/components/tryghost-adapter-manager-5.61.0.tgz +0 -0
  4. package/components/tryghost-announcement-bar-settings-5.61.0.tgz +0 -0
  5. package/components/tryghost-api-framework-5.61.0.tgz +0 -0
  6. package/components/tryghost-api-version-compatibility-service-5.61.0.tgz +0 -0
  7. package/components/tryghost-audience-feedback-5.61.0.tgz +0 -0
  8. package/components/tryghost-bookshelf-repository-5.61.0.tgz +0 -0
  9. package/components/tryghost-bootstrap-socket-5.61.0.tgz +0 -0
  10. package/components/tryghost-collections-5.61.0.tgz +0 -0
  11. package/components/tryghost-constants-5.61.0.tgz +0 -0
  12. package/components/tryghost-custom-theme-settings-service-5.61.0.tgz +0 -0
  13. package/components/{tryghost-data-generator-5.60.0.tgz → tryghost-data-generator-5.61.0.tgz} +0 -0
  14. package/components/tryghost-domain-events-5.61.0.tgz +0 -0
  15. package/components/tryghost-donations-5.61.0.tgz +0 -0
  16. package/components/tryghost-dynamic-routing-events-5.61.0.tgz +0 -0
  17. package/components/tryghost-email-analytics-provider-mailgun-5.61.0.tgz +0 -0
  18. package/components/{tryghost-email-analytics-service-5.60.0.tgz → tryghost-email-analytics-service-5.61.0.tgz} +0 -0
  19. package/components/tryghost-email-content-generator-5.61.0.tgz +0 -0
  20. package/components/tryghost-email-events-5.61.0.tgz +0 -0
  21. package/components/{tryghost-email-service-5.60.0.tgz → tryghost-email-service-5.61.0.tgz} +0 -0
  22. package/components/tryghost-email-suppression-list-5.61.0.tgz +0 -0
  23. package/components/tryghost-event-aware-cache-wrapper-5.61.0.tgz +0 -0
  24. package/components/tryghost-express-dynamic-redirects-5.61.0.tgz +0 -0
  25. package/components/{tryghost-external-media-inliner-5.60.0.tgz → tryghost-external-media-inliner-5.61.0.tgz} +0 -0
  26. package/components/{tryghost-extract-api-key-5.60.0.tgz → tryghost-extract-api-key-5.61.0.tgz} +0 -0
  27. package/components/tryghost-html-to-plaintext-5.61.0.tgz +0 -0
  28. package/components/tryghost-i18n-5.61.0.tgz +0 -0
  29. package/components/tryghost-importer-handler-content-files-5.61.0.tgz +0 -0
  30. package/components/{tryghost-importer-revue-5.60.0.tgz → tryghost-importer-revue-5.61.0.tgz} +0 -0
  31. package/components/tryghost-in-memory-repository-5.61.0.tgz +0 -0
  32. package/components/tryghost-job-manager-5.61.0.tgz +0 -0
  33. package/components/tryghost-link-redirects-5.61.0.tgz +0 -0
  34. package/components/tryghost-link-replacer-5.61.0.tgz +0 -0
  35. package/components/tryghost-link-tracking-5.61.0.tgz +0 -0
  36. package/components/tryghost-magic-link-5.61.0.tgz +0 -0
  37. package/components/tryghost-mail-events-5.61.0.tgz +0 -0
  38. package/components/tryghost-mailgun-client-5.61.0.tgz +0 -0
  39. package/components/{tryghost-member-attribution-5.60.0.tgz → tryghost-member-attribution-5.61.0.tgz} +0 -0
  40. package/components/tryghost-member-events-5.61.0.tgz +0 -0
  41. package/components/tryghost-members-api-5.61.0.tgz +0 -0
  42. package/components/tryghost-members-csv-5.61.0.tgz +0 -0
  43. package/components/tryghost-members-events-service-5.61.0.tgz +0 -0
  44. package/components/tryghost-members-importer-5.61.0.tgz +0 -0
  45. package/components/tryghost-members-offers-5.61.0.tgz +0 -0
  46. package/components/tryghost-members-payments-5.61.0.tgz +0 -0
  47. package/components/tryghost-members-ssr-5.61.0.tgz +0 -0
  48. package/components/{tryghost-members-stripe-service-5.60.0.tgz → tryghost-members-stripe-service-5.61.0.tgz} +0 -0
  49. package/components/tryghost-mentions-email-report-5.61.0.tgz +0 -0
  50. package/components/tryghost-milestones-5.61.0.tgz +0 -0
  51. package/components/tryghost-minifier-5.61.0.tgz +0 -0
  52. package/components/{tryghost-model-to-domain-event-interceptor-5.60.0.tgz → tryghost-model-to-domain-event-interceptor-5.61.0.tgz} +0 -0
  53. package/components/tryghost-mw-api-version-mismatch-5.61.0.tgz +0 -0
  54. package/components/tryghost-mw-cache-control-5.61.0.tgz +0 -0
  55. package/components/tryghost-mw-error-handler-5.61.0.tgz +0 -0
  56. package/components/tryghost-mw-session-from-token-5.61.0.tgz +0 -0
  57. package/components/tryghost-mw-update-user-last-seen-5.61.0.tgz +0 -0
  58. package/components/tryghost-mw-version-match-5.61.0.tgz +0 -0
  59. package/components/tryghost-mw-vhost-5.61.0.tgz +0 -0
  60. package/components/tryghost-nql-filter-expansions-5.61.0.tgz +0 -0
  61. package/components/tryghost-oembed-service-5.61.0.tgz +0 -0
  62. package/components/tryghost-package-json-5.61.0.tgz +0 -0
  63. package/components/tryghost-post-events-5.61.0.tgz +0 -0
  64. package/components/tryghost-post-revisions-5.61.0.tgz +0 -0
  65. package/components/tryghost-posts-service-5.61.0.tgz +0 -0
  66. package/components/tryghost-recommendations-5.61.0.tgz +0 -0
  67. package/components/tryghost-referrers-5.61.0.tgz +0 -0
  68. package/components/tryghost-security-5.61.0.tgz +0 -0
  69. package/components/tryghost-session-service-5.61.0.tgz +0 -0
  70. package/components/tryghost-settings-path-manager-5.61.0.tgz +0 -0
  71. package/components/tryghost-slack-notifications-5.61.0.tgz +0 -0
  72. package/components/tryghost-staff-service-5.61.0.tgz +0 -0
  73. package/components/tryghost-stats-service-5.61.0.tgz +0 -0
  74. package/components/{tryghost-tiers-5.60.0.tgz → tryghost-tiers-5.61.0.tgz} +0 -0
  75. package/components/tryghost-update-check-service-5.61.0.tgz +0 -0
  76. package/components/tryghost-verification-trigger-5.61.0.tgz +0 -0
  77. package/components/tryghost-version-notifications-data-service-5.61.0.tgz +0 -0
  78. package/components/tryghost-webmentions-5.61.0.tgz +0 -0
  79. package/core/boot.js +6 -1
  80. package/core/built/admin/assets/{chunk.143.88cad5b682130d4a2e2e.js → chunk.143.b297dc36977ec0b8aa47.js} +5 -5
  81. package/core/built/admin/assets/{chunk.178.cf63585e0c59dc316a42.js → chunk.178.308d80b60fce060ca358.js} +4 -4
  82. package/core/built/admin/assets/{ghost-fc7b85b759f27e607667914dcd685402.js → ghost-4416ac3815c54eac76c2590d1154a628.js} +33 -36
  83. package/core/built/admin/assets/ghost-8a4e981c272f793157133814ca7c7e84.css +1 -0
  84. package/core/built/admin/assets/ghost-dark-084169b0e968ef763dfbbf63b253e0c6.css +1 -0
  85. package/core/built/admin/assets/{vendor-fff5b0b3c122441beb3170947ae27b9d.js → vendor-3631184082d609038638c1e169a002e7.js} +666 -666
  86. package/core/built/admin/index.html +6 -6
  87. package/core/frontend/src/cards/css/header_v2.css +10 -9
  88. package/core/frontend/src/cards/css/signup.css +13 -12
  89. package/core/frontend/web/middleware/cors.js +1 -1
  90. package/core/frontend/web/middleware/handle-image-sizes.js +1 -1
  91. package/core/frontend/web/middleware/serve-public-file.js +4 -4
  92. package/core/frontend/web/site.js +5 -2
  93. package/core/server/api/endpoints/index.js +8 -0
  94. package/core/server/api/endpoints/recommendations-public.js +24 -0
  95. package/core/server/api/endpoints/recommendations.js +71 -0
  96. package/core/server/api/endpoints/utils/serializers/input/settings.js +2 -1
  97. package/core/server/data/exporter/table-lists.js +2 -1
  98. package/core/server/data/migrations/versions/5.61/2023-08-29-10-17-add-recommendations-crud-permissions.js +50 -0
  99. package/core/server/data/migrations/versions/5.61/2023-08-29-11-39-10-add-recommendations-table.js +18 -0
  100. package/core/server/data/migrations/versions/5.61/2023-08-30-07-37-04-add-recommendations-enabled-settings.js +8 -0
  101. package/core/server/data/schema/default-settings/default-settings.json +10 -0
  102. package/core/server/data/schema/fixtures/fixtures.json +35 -5
  103. package/core/server/data/schema/schema.js +12 -0
  104. package/core/server/models/base/plugins/raw-knex.js +4 -3
  105. package/core/server/models/post.js +1 -1
  106. package/core/server/models/recommendation.js +10 -0
  107. package/core/server/services/api-version-compatibility/index.js +2 -2
  108. package/core/server/services/api-version-compatibility/mw-version-rewrites.js +1 -1
  109. package/core/server/services/auth/api-key/admin.js +3 -3
  110. package/core/server/services/collections/service.js +5 -3
  111. package/core/server/services/members/middleware.js +10 -10
  112. package/core/server/services/mentions/WebmentionMetadata.js +2 -1
  113. package/core/server/services/mentions/service.js +4 -0
  114. package/core/server/services/{collections/intercept-events.js → model-to-domain-event-interceptor/index.js} +9 -1
  115. package/core/server/services/recommendations/RecommendationServiceWrapper.js +57 -0
  116. package/core/server/services/recommendations/index.js +3 -0
  117. package/core/server/web/admin/app.js +2 -2
  118. package/core/server/web/api/endpoints/admin/middleware.js +1 -1
  119. package/core/server/web/api/endpoints/admin/routes.js +9 -3
  120. package/core/server/web/api/endpoints/content/routes.js +3 -0
  121. package/core/server/web/api/middleware/cors.js +1 -1
  122. package/core/server/web/api/middleware/upload.js +2 -2
  123. package/core/server/web/parent/middleware/request-id.js +1 -1
  124. package/core/server/web/shared/middleware/uncapitalise.js +1 -1
  125. package/core/server/web/shared/middleware/url-redirects.js +3 -3
  126. package/core/server/web/well-known.js +1 -1
  127. package/core/shared/labs.js +2 -1
  128. package/core/shared/settings-cache/public.js +2 -1
  129. package/index.js +5 -0
  130. package/newrelic.js +53 -0
  131. package/package.json +162 -159
  132. package/yarn.lock +2513 -1166
  133. package/components/tryghost-adapter-cache-memory-ttl-5.60.0.tgz +0 -0
  134. package/components/tryghost-adapter-cache-redis-5.60.0.tgz +0 -0
  135. package/components/tryghost-adapter-manager-5.60.0.tgz +0 -0
  136. package/components/tryghost-announcement-bar-settings-5.60.0.tgz +0 -0
  137. package/components/tryghost-api-framework-5.60.0.tgz +0 -0
  138. package/components/tryghost-api-version-compatibility-service-5.60.0.tgz +0 -0
  139. package/components/tryghost-audience-feedback-5.60.0.tgz +0 -0
  140. package/components/tryghost-bootstrap-socket-5.60.0.tgz +0 -0
  141. package/components/tryghost-collections-5.60.0.tgz +0 -0
  142. package/components/tryghost-constants-5.60.0.tgz +0 -0
  143. package/components/tryghost-custom-theme-settings-service-5.60.0.tgz +0 -0
  144. package/components/tryghost-domain-events-5.60.0.tgz +0 -0
  145. package/components/tryghost-donations-5.60.0.tgz +0 -0
  146. package/components/tryghost-dynamic-routing-events-5.60.0.tgz +0 -0
  147. package/components/tryghost-email-analytics-provider-mailgun-5.60.0.tgz +0 -0
  148. package/components/tryghost-email-content-generator-5.60.0.tgz +0 -0
  149. package/components/tryghost-email-events-5.60.0.tgz +0 -0
  150. package/components/tryghost-email-suppression-list-5.60.0.tgz +0 -0
  151. package/components/tryghost-event-aware-cache-wrapper-5.60.0.tgz +0 -0
  152. package/components/tryghost-express-dynamic-redirects-5.60.0.tgz +0 -0
  153. package/components/tryghost-html-to-plaintext-5.60.0.tgz +0 -0
  154. package/components/tryghost-i18n-5.60.0.tgz +0 -0
  155. package/components/tryghost-importer-handler-content-files-5.60.0.tgz +0 -0
  156. package/components/tryghost-in-memory-repository-5.60.0.tgz +0 -0
  157. package/components/tryghost-job-manager-5.60.0.tgz +0 -0
  158. package/components/tryghost-link-redirects-5.60.0.tgz +0 -0
  159. package/components/tryghost-link-replacer-5.60.0.tgz +0 -0
  160. package/components/tryghost-link-tracking-5.60.0.tgz +0 -0
  161. package/components/tryghost-magic-link-5.60.0.tgz +0 -0
  162. package/components/tryghost-mail-events-5.60.0.tgz +0 -0
  163. package/components/tryghost-mailgun-client-5.60.0.tgz +0 -0
  164. package/components/tryghost-member-events-5.60.0.tgz +0 -0
  165. package/components/tryghost-members-api-5.60.0.tgz +0 -0
  166. package/components/tryghost-members-csv-5.60.0.tgz +0 -0
  167. package/components/tryghost-members-events-service-5.60.0.tgz +0 -0
  168. package/components/tryghost-members-importer-5.60.0.tgz +0 -0
  169. package/components/tryghost-members-offers-5.60.0.tgz +0 -0
  170. package/components/tryghost-members-payments-5.60.0.tgz +0 -0
  171. package/components/tryghost-members-ssr-5.60.0.tgz +0 -0
  172. package/components/tryghost-mentions-email-report-5.60.0.tgz +0 -0
  173. package/components/tryghost-milestones-5.60.0.tgz +0 -0
  174. package/components/tryghost-minifier-5.60.0.tgz +0 -0
  175. package/components/tryghost-mw-api-version-mismatch-5.60.0.tgz +0 -0
  176. package/components/tryghost-mw-cache-control-5.60.0.tgz +0 -0
  177. package/components/tryghost-mw-error-handler-5.60.0.tgz +0 -0
  178. package/components/tryghost-mw-session-from-token-5.60.0.tgz +0 -0
  179. package/components/tryghost-mw-update-user-last-seen-5.60.0.tgz +0 -0
  180. package/components/tryghost-mw-version-match-5.60.0.tgz +0 -0
  181. package/components/tryghost-mw-vhost-5.60.0.tgz +0 -0
  182. package/components/tryghost-nql-filter-expansions-5.60.0.tgz +0 -0
  183. package/components/tryghost-oembed-service-5.60.0.tgz +0 -0
  184. package/components/tryghost-package-json-5.60.0.tgz +0 -0
  185. package/components/tryghost-post-events-5.60.0.tgz +0 -0
  186. package/components/tryghost-post-revisions-5.60.0.tgz +0 -0
  187. package/components/tryghost-posts-service-5.60.0.tgz +0 -0
  188. package/components/tryghost-referrers-5.60.0.tgz +0 -0
  189. package/components/tryghost-security-5.60.0.tgz +0 -0
  190. package/components/tryghost-session-service-5.60.0.tgz +0 -0
  191. package/components/tryghost-settings-path-manager-5.60.0.tgz +0 -0
  192. package/components/tryghost-slack-notifications-5.60.0.tgz +0 -0
  193. package/components/tryghost-staff-service-5.60.0.tgz +0 -0
  194. package/components/tryghost-stats-service-5.60.0.tgz +0 -0
  195. package/components/tryghost-update-check-service-5.60.0.tgz +0 -0
  196. package/components/tryghost-verification-trigger-5.60.0.tgz +0 -0
  197. package/components/tryghost-version-notifications-data-service-5.60.0.tgz +0 -0
  198. package/components/tryghost-webmentions-5.60.0.tgz +0 -0
  199. package/core/built/admin/assets/chunk.208.735d31e95ce6fbd467b8.js +0 -12512
  200. package/core/built/admin/assets/chunk.208.735d31e95ce6fbd467b8.js.LICENSE.txt +0 -250
  201. package/core/built/admin/assets/ghost-c2c2b6cf53d999c72995cafa4166b9fb.css +0 -1
  202. package/core/built/admin/assets/ghost-dark-d44be1f4bc4e8920af2416d1e56a341f.css +0 -1
@@ -25,7 +25,7 @@ const init = () => {
25
25
  });
26
26
  };
27
27
 
28
- module.exports.errorHandler = (err, req, res, next) => {
28
+ module.exports.errorHandler = function apiVersionCompatibilityErrorHandler(err, req, res, next) {
29
29
  return versionMismatchHandler(serviceInstance)(err, req, res, next);
30
30
  };
31
31
 
@@ -38,7 +38,7 @@ module.exports.errorHandler = (err, req, res, next) => {
38
38
  * @param {import('express').Response} res
39
39
  * @param {import('express').NextFunction} next
40
40
  */
41
- module.exports.contentVersion = (req, res, next) => {
41
+ module.exports.contentVersion = function apiVersionCompatibilityContentVersion(req, res, next) {
42
42
  res.header('Content-Version', `v${ghostVersion.safe}`);
43
43
  res.vary('Accept-Version');
44
44
 
@@ -8,7 +8,7 @@ const urlUtils = require('../../../shared/url-utils');
8
8
  * @param {import('express').Response} res
9
9
  * @param {import('express').NextFunction} next
10
10
  */
11
- module.exports = (req, res, next) => {
11
+ module.exports = function mwVersionRewrites(req, res, next) {
12
12
  let {version} = legacyApiPathMatch(req.url);
13
13
 
14
14
  // If we don't match a valid version, carry on
@@ -44,7 +44,7 @@ const _extractTokenFromUrl = function extractTokenFromUrl(reqUrl) {
44
44
  return query.token;
45
45
  };
46
46
 
47
- const authenticate = (req, res, next) => {
47
+ const authenticate = function apiKeyAdminAuth(req, res, next) {
48
48
  // CASE: we don't have an Authorization header so allow fallthrough to other
49
49
  // auth middleware or final "ensure authenticated" check
50
50
  if (!req.headers || !req.headers.authorization) {
@@ -63,7 +63,7 @@ const authenticate = (req, res, next) => {
63
63
  return authenticateWithToken(req, res, next, {token, JWT_OPTIONS: JWT_OPTIONS_DEFAULTS});
64
64
  };
65
65
 
66
- const authenticateWithUrl = (req, res, next) => {
66
+ const authenticateWithUrl = function apiKeyAuthenticateWithUrl(req, res, next) {
67
67
  const token = _extractTokenFromUrl(req.originalUrl);
68
68
  if (!token) {
69
69
  return next(new errors.UnauthorizedError({
@@ -89,7 +89,7 @@ const authenticateWithUrl = (req, res, next) => {
89
89
  * - the "Audience" claim should match the requested API path
90
90
  * https://tools.ietf.org/html/rfc7519#section-4.1.3
91
91
  */
92
- const authenticateWithToken = async (req, res, next, {token, JWT_OPTIONS}) => {
92
+ const authenticateWithToken = async function apiKeyAuthenticateWithToken(req, res, next, {token, JWT_OPTIONS}) {
93
93
  const decoded = jwt.decode(token, {complete: true});
94
94
 
95
95
  if (!decoded || !decoded.header) {
@@ -2,7 +2,6 @@ const {
2
2
  CollectionsService
3
3
  } = require('@tryghost/collections');
4
4
  const BookshelfCollectionsRepository = require('./BookshelfCollectionsRepository');
5
- const labs = require('../../../shared/labs');
6
5
 
7
6
  let inited = false;
8
7
  class CollectionsServiceWrapper {
@@ -32,15 +31,18 @@ class CollectionsServiceWrapper {
32
31
  }
33
32
 
34
33
  async init() {
35
- if (!labs.isSet('collections')) {
34
+ const config = require('../../../shared/config');
35
+ const labs = require('../../../shared/labs');
36
+ // host setting OR labs "collections" flag has to be enabled to run collections service
37
+ if (!config.get('hostSettings:collections:enabled') && !(labs.isSet('collections'))) {
36
38
  return;
37
39
  }
40
+
38
41
  if (inited) {
39
42
  return;
40
43
  }
41
44
  inited = true;
42
45
  this.api.subscribeToEvents();
43
- require('./intercept-events')();
44
46
  }
45
47
  }
46
48
 
@@ -16,7 +16,7 @@ const messages = {
16
16
 
17
17
  // @TODO: This piece of middleware actually belongs to the frontend, not to the member app
18
18
  // Need to figure a way to separate these things (e.g. frontend actually talks to members API)
19
- const loadMemberSession = async function (req, res, next) {
19
+ const loadMemberSession = async function loadMemberSession(req, res, next) {
20
20
  try {
21
21
  const member = await membersService.ssr.getMemberDataFromSession(req, res);
22
22
  Object.assign(req, {member});
@@ -32,7 +32,7 @@ const loadMemberSession = async function (req, res, next) {
32
32
  * Require member authentication, and make it possible to authenticate via uuid.
33
33
  * You can chain this after loadMemberSession to make it possible to authenticate via both the uuid and the session.
34
34
  */
35
- const authMemberByUuid = async function (req, res, next) {
35
+ const authMemberByUuid = async function authMemberByUuid(req, res, next) {
36
36
  try {
37
37
  const uuid = req.query.uuid;
38
38
  if (!uuid) {
@@ -60,7 +60,7 @@ const authMemberByUuid = async function (req, res, next) {
60
60
  }
61
61
  };
62
62
 
63
- const getIdentityToken = async function (req, res) {
63
+ const getIdentityToken = async function getIdentityToken(req, res) {
64
64
  try {
65
65
  const token = await membersService.ssr.getIdentityTokenForMemberFromSession(req, res);
66
66
  res.writeHead(200);
@@ -71,7 +71,7 @@ const getIdentityToken = async function (req, res) {
71
71
  }
72
72
  };
73
73
 
74
- const deleteSession = async function (req, res) {
74
+ const deleteSession = async function deleteSession(req, res) {
75
75
  try {
76
76
  await membersService.ssr.deleteSession(req, res);
77
77
  res.writeHead(204);
@@ -87,7 +87,7 @@ const deleteSession = async function (req, res) {
87
87
  }
88
88
  };
89
89
 
90
- const getMemberData = async function (req, res) {
90
+ const getMemberData = async function getMemberData(req, res) {
91
91
  try {
92
92
  const member = await membersService.ssr.getMemberDataFromSession(req, res);
93
93
  if (member) {
@@ -101,7 +101,7 @@ const getMemberData = async function (req, res) {
101
101
  }
102
102
  };
103
103
 
104
- const deleteSuppression = async function (req, res) {
104
+ const deleteSuppression = async function deleteSuppression(req, res) {
105
105
  try {
106
106
  const member = await membersService.ssr.getMemberDataFromSession(req, res);
107
107
  const options = {
@@ -123,7 +123,7 @@ const deleteSuppression = async function (req, res) {
123
123
  }
124
124
  };
125
125
 
126
- const getMemberNewsletters = async function (req, res) {
126
+ const getMemberNewsletters = async function getMemberNewsletters(req, res) {
127
127
  try {
128
128
  const memberUuid = req.query.uuid;
129
129
 
@@ -151,7 +151,7 @@ const getMemberNewsletters = async function (req, res) {
151
151
  }
152
152
  };
153
153
 
154
- const updateMemberNewsletters = async function (req, res) {
154
+ const updateMemberNewsletters = async function updateMemberNewsletters(req, res) {
155
155
  try {
156
156
  const memberUuid = req.query.uuid;
157
157
  if (!memberUuid) {
@@ -182,7 +182,7 @@ const updateMemberNewsletters = async function (req, res) {
182
182
  }
183
183
  };
184
184
 
185
- const updateMemberData = async function (req, res) {
185
+ const updateMemberData = async function updateMemberData(req, res) {
186
186
  try {
187
187
  const data = _.pick(req.body, 'name', 'expertise', 'subscribed', 'newsletters', 'enable_comment_notifications');
188
188
  const member = await membersService.ssr.getMemberDataFromSession(req, res);
@@ -209,7 +209,7 @@ const updateMemberData = async function (req, res) {
209
209
  }
210
210
  };
211
211
 
212
- const createSessionFromMagicLink = async function (req, res, next) {
212
+ const createSessionFromMagicLink = async function createSessionFromMagicLink(req, res, next) {
213
213
  if (!req.url.includes('token=')) {
214
214
  return next();
215
215
  }
@@ -14,7 +14,8 @@ module.exports = class WebmentionMetadata {
14
14
  author: data.metadata.author,
15
15
  image: data.metadata.thumbnail ? new URL(data.metadata.thumbnail) : null,
16
16
  favicon: data.metadata.icon ? new URL(data.metadata.icon) : null,
17
- body: data.body
17
+ body: data.body,
18
+ contentType: data.contentType
18
19
  };
19
20
  return result;
20
21
  }
@@ -28,6 +28,8 @@ module.exports = {
28
28
  /** @type {import('@tryghost/webmentions/lib/MentionsAPI')} */
29
29
  api: null,
30
30
  controller: new MentionController(),
31
+ /** @type {import('@tryghost/webmentions/lib/MentionSendingService')} */
32
+ sendingService: null,
31
33
  didInit: false,
32
34
  async init() {
33
35
  if (this.didInit) {
@@ -107,5 +109,7 @@ module.exports = {
107
109
  }
108
110
  });
109
111
  sendingService.listen(events);
112
+
113
+ this.sendingService = sendingService;
110
114
  }
111
115
  };
@@ -1,4 +1,11 @@
1
- module.exports = () => {
1
+ let inited = false;
2
+
3
+ module.exports.init = async () => {
4
+ if (inited) {
5
+ return;
6
+ }
7
+ inited = true;
8
+
2
9
  const DomainEvents = require('@tryghost/domain-events/lib/DomainEvents');
3
10
  const {ModelToDomainEventInterceptor} = require('@tryghost/model-to-domain-event-interceptor');
4
11
  const events = require('../../lib/common/events');
@@ -6,5 +13,6 @@ module.exports = () => {
6
13
  ModelEvents: events,
7
14
  DomainEvents: DomainEvents
8
15
  });
16
+
9
17
  eventInterceptor.init();
10
18
  };
@@ -0,0 +1,57 @@
1
+ class RecommendationServiceWrapper {
2
+ /**
3
+ * @type {import('@tryghost/recommendations').RecommendationRepository}
4
+ */
5
+ repository;
6
+
7
+ /**
8
+ * @type {import('@tryghost/recommendations').RecommendationController}
9
+ */
10
+ controller;
11
+
12
+ /**
13
+ * @type {import('@tryghost/recommendations').RecommendationService}
14
+ */
15
+ service;
16
+
17
+ init() {
18
+ if (this.repository) {
19
+ return;
20
+ }
21
+
22
+ const config = require('../../../shared/config');
23
+ const urlUtils = require('../../../shared/url-utils');
24
+ const models = require('../../models');
25
+ const sentry = require('../../../shared/sentry');
26
+ const {BookshelfRecommendationRepository, RecommendationService, RecommendationController, WellknownService} = require('@tryghost/recommendations');
27
+
28
+ const mentions = require('../mentions');
29
+
30
+ if (!mentions.sendingService) {
31
+ // eslint-disable-next-line ghost/ghost-custom/no-native-error
32
+ throw new Error('MentionSendingService not intialized, but this is a dependency of RecommendationServiceWrapper. Check boot order.');
33
+ }
34
+
35
+ const wellknownService = new WellknownService({
36
+ dir: config.getContentPath('public'),
37
+ urlUtils
38
+ });
39
+
40
+ this.repository = new BookshelfRecommendationRepository(models.Recommendation, {
41
+ sentry
42
+ });
43
+ this.service = new RecommendationService({
44
+ repository: this.repository,
45
+ wellknownService,
46
+ mentionSendingService: mentions.sendingService
47
+ });
48
+ this.controller = new RecommendationController({
49
+ service: this.service
50
+ });
51
+
52
+ // eslint-disable-next-line no-console
53
+ this.service.init().catch(console.error);
54
+ }
55
+ }
56
+
57
+ module.exports = RecommendationServiceWrapper;
@@ -0,0 +1,3 @@
1
+ const RecommendationServiceWrapper = require('./RecommendationServiceWrapper');
2
+
3
+ module.exports = new RecommendationServiceWrapper();
@@ -25,7 +25,7 @@ module.exports = function setupAdminApp() {
25
25
  adminApp.use('/assets', serveStatic(
26
26
  path.join(config.get('paths').adminAssets, 'assets'), {
27
27
  maxAge: (configMaxAge || configMaxAge === 0) ? configMaxAge : constants.ONE_YEAR_MS,
28
- immutable: true,
28
+ immutable: true,
29
29
  fallthrough: false
30
30
  }
31
31
  ));
@@ -59,7 +59,7 @@ module.exports = function setupAdminApp() {
59
59
  // Finally, routing
60
60
  adminApp.get('*', require('./controller'));
61
61
 
62
- adminApp.use((err, req, res, next) => {
62
+ adminApp.use(function fourOhFourMw(err, req, res, next) {
63
63
  if (err.statusCode && err.statusCode === 404) {
64
64
  // Remove 404 errors for next middleware to inject
65
65
  next();
@@ -8,7 +8,7 @@ const messages = {
8
8
  notImplemented: 'The server does not support the functionality required to fulfill the request.'
9
9
  };
10
10
 
11
- const notImplemented = function (req, res, next) {
11
+ const notImplemented = function notImplemented(req, res, next) {
12
12
  // CASE: user is logged in, allow
13
13
  if (!req.api_key) {
14
14
  return next();
@@ -20,9 +20,9 @@ module.exports = function apiRoutes() {
20
20
  router.post('/mail_events', mw.publicAdminApi, http(api.mailEvents.add));
21
21
 
22
22
  // ## Collections
23
- router.get('/collections', mw.authAdminApi, labs.enabledMiddleware('collections'), http(api.collections.browse));
24
- router.get('/collections/:id', mw.authAdminApi, labs.enabledMiddleware('collections'), http(api.collections.read));
25
- router.get('/collections/slug/:slug', mw.authAdminApi, labs.enabledMiddleware('collections'), http(api.collections.read));
23
+ router.get('/collections', mw.authAdminApi, http(api.collections.browse));
24
+ router.get('/collections/:id', mw.authAdminApi, http(api.collections.read));
25
+ router.get('/collections/slug/:slug', mw.authAdminApi, http(api.collections.read));
26
26
  router.post('/collections', mw.authAdminApi, labs.enabledMiddleware('collections'), http(api.collections.add));
27
27
  router.put('/collections/:id', mw.authAdminApi, labs.enabledMiddleware('collections'), http(api.collections.edit));
28
28
  router.del('/collections/:id', mw.authAdminApi, labs.enabledMiddleware('collections'), http(api.collections.destroy));
@@ -347,5 +347,11 @@ module.exports = function apiRoutes() {
347
347
  router.get('/links', mw.authAdminApi, http(api.links.browse));
348
348
  router.put('/links/bulk', mw.authAdminApi, http(api.links.bulkEdit));
349
349
 
350
+ // Recommendations
351
+ router.get('/recommendations', mw.authAdminApi, http(api.recommendations.browse));
352
+ router.post('/recommendations', mw.authAdminApi, http(api.recommendations.add));
353
+ router.put('/recommendations/:id', mw.authAdminApi, http(api.recommendations.edit));
354
+ router.del('/recommendations/:id', mw.authAdminApi, http(api.recommendations.destroy));
355
+
350
356
  return router;
351
357
  };
@@ -42,5 +42,8 @@ module.exports = function apiRoutes() {
42
42
  router.get('/collections/:id', mw.authenticatePublic, http(api.collectionsPublic.readById));
43
43
  router.get('/collections/slug/:slug', mw.authenticatePublic, http(api.collectionsPublic.readBySlug));
44
44
 
45
+ // ## Recommendations
46
+ router.get('/recommendations', mw.authenticatePublic, http(api.recommendationsPublic.browse));
47
+
45
48
  return router;
46
49
  };
@@ -87,7 +87,7 @@ function corsOptionsDelegate(req, cb) {
87
87
  * @param {Express.Response} res
88
88
  * @param {Function} next
89
89
  */
90
- const handleCaching = (req, res, next) => {
90
+ const handleCaching = function handleCaching(req, res, next) {
91
91
  const method = req.method && req.method.toUpperCase && req.method.toUpperCase();
92
92
  if (method === 'OPTIONS') {
93
93
  // @NOTE: try to add native support for dynamic 'vary' header value in 'cors' module
@@ -47,7 +47,7 @@ const upload = multer({dest: os.tmpdir()});
47
47
 
48
48
  const deleteSingleFile = file => fs.unlink(file.path).catch(err => logging.error(err));
49
49
 
50
- const single = name => (req, res, next) => {
50
+ const single = name => function singleUploadFunction(req, res, next) {
51
51
  const singleUpload = upload.single(name);
52
52
 
53
53
  singleUpload(req, res, (err) => {
@@ -77,7 +77,7 @@ const single = name => (req, res, next) => {
77
77
  });
78
78
  };
79
79
 
80
- const media = (fileName, thumbName) => (req, res, next) => {
80
+ const media = (fileName, thumbName) => function mediaUploadFunction(req, res, next) {
81
81
  const mediaUpload = upload.fields([{
82
82
  name: fileName,
83
83
  maxCount: 1
@@ -3,7 +3,7 @@ const uuid = require('uuid');
3
3
  /**
4
4
  * @TODO: move this middleware to Framework monorepo?
5
5
  */
6
- module.exports = (req, res, next) => {
6
+ module.exports = function requestIdMw(req, res, next) {
7
7
  const requestId = req.get('X-Request-ID') || uuid.v4();
8
8
 
9
9
  // Set a value for internal use
@@ -21,7 +21,7 @@ const messages = {
21
21
  pageNotFound: 'Page not found"'
22
22
  };
23
23
 
24
- const uncapitalise = (req, res, next) => {
24
+ const uncapitalise = function uncapitalise(req, res, next) {
25
25
  let pathToTest = (req.baseUrl ? req.baseUrl : '') + req.path;
26
26
  let redirectPath;
27
27
  let decodedURI;
@@ -87,7 +87,7 @@ _private.getFrontendRedirectUrl = ({requestedHost, requestedUrl, queryParameters
87
87
  }
88
88
  };
89
89
 
90
- _private.redirect = (req, res, next, redirectFn) => {
90
+ _private.redirect = function urlRedirectsRedirect(req, res, next, redirectFn) {
91
91
  const redirectUrl = redirectFn({
92
92
  requestedHost: req.vhost ? req.vhost.host : req.get('host'),
93
93
  requestedUrl: url.parse(req.originalUrl || req.url).pathname,
@@ -104,11 +104,11 @@ _private.redirect = (req, res, next, redirectFn) => {
104
104
  next();
105
105
  };
106
106
 
107
- const frontendRedirect = (req, res, next) => {
107
+ const frontendRedirect = function frontendRedirect(req, res, next) {
108
108
  _private.redirect(req, res, next, _private.getFrontendRedirectUrl);
109
109
  };
110
110
 
111
- const adminRedirect = (req, res, next) => {
111
+ const adminRedirect = function adminRedirect(req, res, next) {
112
112
  _private.redirect(req, res, next, _private.getAdminRedirectUrl);
113
113
  };
114
114
 
@@ -18,7 +18,7 @@ module.exports = function setupWellKnownApp() {
18
18
 
19
19
  const cache = cacheControl('public', {maxAge: config.get('caching:wellKnown:maxAge')});
20
20
 
21
- wellKnownApp.get('/jwks.json', cache, async (req, res) => {
21
+ wellKnownApp.get('/jwks.json', cache, async function jwksMiddleware(req, res) {
22
22
  const jwks = await getSafePublicJWKS();
23
23
 
24
24
  // there's only one key in the store atm
@@ -42,7 +42,8 @@ const ALPHA_FEATURES = [
42
42
  'collectionsCard',
43
43
  'tipsAndDonations',
44
44
  'importMemberTier',
45
- 'convertToLexical'
45
+ 'recommendations',
46
+ 'lexicalIndicators'
46
47
  ];
47
48
 
48
49
  module.exports.GA_KEYS = [...GA_FEATURES];
@@ -39,5 +39,6 @@ module.exports = {
39
39
  portal_plans: 'portal_plans',
40
40
  portal_name: 'portal_name',
41
41
  portal_button: 'portal_button',
42
- comments_enabled: 'comments_enabled'
42
+ comments_enabled: 'comments_enabled',
43
+ recommendations_enabled: 'recommendations_enabled'
43
44
  };
package/index.js CHANGED
@@ -1 +1,6 @@
1
+ // Load New Relic
2
+ if (process.env.PRO_ENV) {
3
+ require('newrelic');
4
+ }
5
+
1
6
  require('./ghost');
package/newrelic.js ADDED
@@ -0,0 +1,53 @@
1
+ const config = require('./core/shared/config');
2
+
3
+ exports.config = {
4
+ app_name: ['Ghost'],
5
+ license_key: config.get('newRelic:licenseKey'),
6
+ labels: {
7
+ env: process.env.PRO_ENV || 'unknown',
8
+ site: config.get('hostSettings:siteId')
9
+ },
10
+ // Agent only imported when enabled, but this will further cement that
11
+ agent_enabled: config.get('newRelic:enabled'),
12
+ distributed_tracing: {
13
+ enabled: true
14
+ },
15
+ logging: {
16
+ level: 'info'
17
+ },
18
+ slow_sql: {
19
+ // Default is false.
20
+ enabled: true,
21
+ // Default is 10. Increasing this limit increases memory usage
22
+ // Defines the maximum number of slow queries the agent collects per minute.
23
+ // The agent discards additional queries after the limit is reached.
24
+ max_samples: 10
25
+ },
26
+ error_collector: {
27
+ ignore_classes: [
28
+ // @NOTE: add more error classes to ignore here
29
+ 'ValidationError',
30
+ 'NoPermissionError'
31
+ ]
32
+ },
33
+ transaction_tracer: {
34
+ enabled: true,
35
+ record_sql: 'obfuscated'
36
+ },
37
+ allow_all_headers: true,
38
+ attributes: {
39
+ exclude: [
40
+ // Default exclusions (TODO: add more!):
41
+ 'request.headers.cookie',
42
+ 'request.headers.authorization',
43
+ 'request.headers.proxyAuthorization',
44
+ 'request.headers.setCookie*',
45
+ 'request.headers.x*',
46
+ 'response.headers.cookie',
47
+ 'response.headers.authorization',
48
+ 'response.headers.proxyAuthorization',
49
+ 'response.headers.setCookie*',
50
+ 'response.headers.x*'
51
+ ]
52
+ }
53
+ };