ghost 5.33.7 → 5.34.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 (175) hide show
  1. package/components/tryghost-adapter-cache-redis-5.34.0.tgz +0 -0
  2. package/components/tryghost-adapter-manager-5.34.0.tgz +0 -0
  3. package/components/{tryghost-api-framework-5.33.7.tgz → tryghost-api-framework-5.34.0.tgz} +0 -0
  4. package/components/tryghost-api-version-compatibility-service-5.34.0.tgz +0 -0
  5. package/components/tryghost-audience-feedback-5.34.0.tgz +0 -0
  6. package/components/tryghost-bootstrap-socket-5.34.0.tgz +0 -0
  7. package/components/tryghost-constants-5.34.0.tgz +0 -0
  8. package/components/tryghost-custom-theme-settings-service-5.34.0.tgz +0 -0
  9. package/components/tryghost-data-generator-5.34.0.tgz +0 -0
  10. package/components/tryghost-domain-events-5.34.0.tgz +0 -0
  11. package/components/tryghost-dynamic-routing-events-5.34.0.tgz +0 -0
  12. package/components/tryghost-email-analytics-provider-mailgun-5.34.0.tgz +0 -0
  13. package/components/tryghost-email-analytics-service-5.34.0.tgz +0 -0
  14. package/components/tryghost-email-content-generator-5.34.0.tgz +0 -0
  15. package/components/tryghost-email-events-5.34.0.tgz +0 -0
  16. package/components/tryghost-email-service-5.34.0.tgz +0 -0
  17. package/components/tryghost-email-suppression-list-5.34.0.tgz +0 -0
  18. package/components/{tryghost-express-dynamic-redirects-5.33.7.tgz → tryghost-express-dynamic-redirects-5.34.0.tgz} +0 -0
  19. package/components/tryghost-extract-api-key-5.34.0.tgz +0 -0
  20. package/components/tryghost-html-to-plaintext-5.34.0.tgz +0 -0
  21. package/components/tryghost-i18n-5.34.0.tgz +0 -0
  22. package/components/tryghost-importer-revue-5.34.0.tgz +0 -0
  23. package/components/{tryghost-job-manager-5.33.7.tgz → tryghost-job-manager-5.34.0.tgz} +0 -0
  24. package/components/tryghost-link-redirects-5.34.0.tgz +0 -0
  25. package/components/tryghost-link-replacer-5.34.0.tgz +0 -0
  26. package/components/{tryghost-link-tracking-5.33.7.tgz → tryghost-link-tracking-5.34.0.tgz} +0 -0
  27. package/components/tryghost-magic-link-5.34.0.tgz +0 -0
  28. package/components/tryghost-mailgun-client-5.34.0.tgz +0 -0
  29. package/components/tryghost-member-attribution-5.34.0.tgz +0 -0
  30. package/components/tryghost-member-events-5.34.0.tgz +0 -0
  31. package/components/tryghost-members-api-5.34.0.tgz +0 -0
  32. package/components/tryghost-members-csv-5.34.0.tgz +0 -0
  33. package/components/{tryghost-members-events-service-5.33.7.tgz → tryghost-members-events-service-5.34.0.tgz} +0 -0
  34. package/components/{tryghost-members-importer-5.33.7.tgz → tryghost-members-importer-5.34.0.tgz} +0 -0
  35. package/components/tryghost-members-offers-5.34.0.tgz +0 -0
  36. package/components/tryghost-members-payments-5.34.0.tgz +0 -0
  37. package/components/{tryghost-members-ssr-5.33.7.tgz → tryghost-members-ssr-5.34.0.tgz} +0 -0
  38. package/components/tryghost-members-stripe-service-5.34.0.tgz +0 -0
  39. package/components/tryghost-milestone-emails-5.34.0.tgz +0 -0
  40. package/components/tryghost-minifier-5.34.0.tgz +0 -0
  41. package/components/tryghost-mw-api-version-mismatch-5.34.0.tgz +0 -0
  42. package/components/tryghost-mw-cache-control-5.34.0.tgz +0 -0
  43. package/components/{tryghost-mw-error-handler-5.33.7.tgz → tryghost-mw-error-handler-5.34.0.tgz} +0 -0
  44. package/components/tryghost-mw-session-from-token-5.34.0.tgz +0 -0
  45. package/components/tryghost-mw-update-user-last-seen-5.34.0.tgz +0 -0
  46. package/components/tryghost-mw-vhost-5.34.0.tgz +0 -0
  47. package/components/tryghost-oembed-service-5.34.0.tgz +0 -0
  48. package/components/{tryghost-package-json-5.33.7.tgz → tryghost-package-json-5.34.0.tgz} +0 -0
  49. package/components/tryghost-referrers-5.34.0.tgz +0 -0
  50. package/components/{tryghost-security-5.33.7.tgz → tryghost-security-5.34.0.tgz} +0 -0
  51. package/components/tryghost-session-service-5.34.0.tgz +0 -0
  52. package/components/{tryghost-settings-path-manager-5.33.7.tgz → tryghost-settings-path-manager-5.34.0.tgz} +0 -0
  53. package/components/tryghost-staff-service-5.34.0.tgz +0 -0
  54. package/components/tryghost-stats-service-5.34.0.tgz +0 -0
  55. package/components/tryghost-tags-public-5.34.0.tgz +0 -0
  56. package/components/tryghost-tiers-5.34.0.tgz +0 -0
  57. package/components/tryghost-update-check-service-5.34.0.tgz +0 -0
  58. package/components/tryghost-verification-trigger-5.34.0.tgz +0 -0
  59. package/components/tryghost-version-notifications-data-service-5.34.0.tgz +0 -0
  60. package/components/tryghost-webmentions-5.34.0.tgz +0 -0
  61. package/core/boot.js +8 -1
  62. package/core/built/admin/assets/{chunk.143.9057a1659075c0ee7336.js → chunk.143.a07da1be864f2e500e18.js} +14 -14
  63. package/core/built/admin/assets/{chunk.178.1d2c848ebe2c7baa38dd.js → chunk.178.9ed5500c900a9032f6c2.js} +4 -4
  64. package/core/built/admin/assets/{chunk.963.e47ead5abeca4cf69fed.js → chunk.616.181e1ad6c33f0bec7a65.js} +3519 -3512
  65. package/core/built/admin/assets/{chunk.963.e47ead5abeca4cf69fed.js.LICENSE.txt → chunk.616.181e1ad6c33f0bec7a65.js.LICENSE.txt} +0 -0
  66. package/core/built/admin/assets/{chunk.79.c3c2c05ea7ff7707fcad.js → chunk.79.ec143a398298020c87e6.js} +112 -112
  67. package/core/built/admin/assets/codemirror/{codemirror-a81c0653d8e57286b75c5a1792f80779.js → codemirror-6c43f4894cbd8db73d7f35cde836c58e.js} +810 -809
  68. package/core/built/admin/assets/{ghost-fb9fb8adbcaf1603ad4006dd2d49e401.css → ghost-558c1e319d6e025bfab2054bc0f7fe83.css} +1 -1
  69. package/core/built/admin/assets/{ghost-904f203ff1f5f6bff4e55a03f39a4fbf.js → ghost-ad40d109653288e74a7cd922341fb33d.js} +115 -90
  70. package/core/built/admin/assets/{ghost-dark-ac0cb221eddc8652a0e7c263ed6513dc.css → ghost-dark-a15754df1f9070dc2525482ce22e2251.css} +1 -1
  71. package/core/built/admin/assets/simplemde/{simplemde-2885e4a40ed66fcae974595584efe50b.js → simplemde-28049a9bd7f432b0648747eb26958a33.js} +836 -836
  72. package/core/built/admin/assets/{vendor-0441964c34d58f2aacd5a04bbe240243.js → vendor-253d6527ca6353855164ef65f896f762.js} +1208 -1233
  73. package/core/built/admin/index.html +6 -6
  74. package/core/cli/generate-data.js +21 -3
  75. package/core/frontend/services/routing/router-manager.js +1 -1
  76. package/core/frontend/web/middleware/handle-image-sizes.js +6 -21
  77. package/core/server/adapters/cache/Redis.js +3 -0
  78. package/core/server/adapters/storage/LocalStorageBase.js +11 -1
  79. package/core/server/api/endpoints/images.js +47 -6
  80. package/core/server/api/endpoints/mentions.js +1 -1
  81. package/core/server/api/endpoints/tags-public.js +2 -1
  82. package/core/server/api/endpoints/utils/serializers/output/email-posts.js +1 -1
  83. package/core/server/api/endpoints/utils/serializers/output/mappers/mentions.js +1 -1
  84. package/core/server/api/endpoints/utils/serializers/output/pages.js +1 -1
  85. package/core/server/api/endpoints/utils/serializers/output/posts.js +1 -1
  86. package/core/server/api/endpoints/utils/serializers/output/previews.js +1 -1
  87. package/core/server/api/endpoints/utils/serializers/output/utils/clean.js +1 -0
  88. package/core/server/data/migrations/versions/5.34/2023-01-30-07-27-add-mentions-permission.js +10 -0
  89. package/core/server/data/migrations/versions/5.34/2023-02-08-03-08-add-mentions-notifications-column.js +7 -0
  90. package/core/server/data/migrations/versions/5.34/2023-02-08-22-32-add-mentions-delete-column.js +7 -0
  91. package/core/server/data/schema/fixtures/fixtures.json +9 -2
  92. package/core/server/data/schema/schema.js +3 -1
  93. package/core/server/lib/image/image-size.js +23 -5
  94. package/core/server/lib/lexical.js +36 -3
  95. package/core/server/models/member.js +3 -0
  96. package/core/server/models/mention.js +7 -1
  97. package/core/server/models/post.js +1 -1
  98. package/core/server/models/user.js +4 -1
  99. package/core/server/services/adapter-manager/index.js +7 -0
  100. package/core/server/services/email-analytics/events/StartEmailAnalyticsJobEvent.js +22 -0
  101. package/core/server/services/email-analytics/index.js +2 -22
  102. package/core/server/services/email-analytics/jobs/fetch-latest/index.js +6 -12
  103. package/core/server/services/email-analytics/wrapper.js +99 -0
  104. package/core/server/services/email-service/wrapper.js +3 -14
  105. package/core/server/services/mega/post-email-serializer.js +1 -1
  106. package/core/server/services/members/jobs/index.js +4 -2
  107. package/core/server/services/mentions/BookshelfMentionRepository.js +3 -2
  108. package/core/server/services/mentions/MentionController.js +78 -12
  109. package/core/server/services/mentions/service.js +43 -3
  110. package/core/server/services/milestone-emails/MilestoneQueries.js +58 -0
  111. package/core/server/services/milestone-emails/index.js +1 -0
  112. package/core/server/services/milestone-emails/service.js +58 -0
  113. package/core/server/services/tags-public/index.js +1 -0
  114. package/core/server/services/tags-public/service.js +31 -0
  115. package/core/server/web/api/endpoints/admin/routes.js +0 -1
  116. package/core/server/web/api/middleware/index.js +0 -1
  117. package/core/server/web/shared/middleware/api/spam-prevention.js +31 -1
  118. package/core/server/web/shared/middleware/brute.js +13 -0
  119. package/core/server/web/webmentions/routes.js +3 -0
  120. package/core/shared/config/defaults.json +17 -1
  121. package/core/shared/config/env/config.development.json +0 -3
  122. package/core/shared/config/env/config.testing-browser.json +6 -0
  123. package/core/shared/config/env/config.testing-mysql.json +7 -0
  124. package/core/shared/config/env/config.testing.json +6 -0
  125. package/core/shared/labs.js +3 -3
  126. package/package.json +121 -114
  127. package/yarn.lock +327 -242
  128. package/components/tryghost-adapter-manager-5.33.7.tgz +0 -0
  129. package/components/tryghost-api-version-compatibility-service-5.33.7.tgz +0 -0
  130. package/components/tryghost-audience-feedback-5.33.7.tgz +0 -0
  131. package/components/tryghost-bootstrap-socket-5.33.7.tgz +0 -0
  132. package/components/tryghost-constants-5.33.7.tgz +0 -0
  133. package/components/tryghost-custom-theme-settings-service-5.33.7.tgz +0 -0
  134. package/components/tryghost-data-generator-5.33.7.tgz +0 -0
  135. package/components/tryghost-domain-events-5.33.7.tgz +0 -0
  136. package/components/tryghost-dynamic-routing-events-5.33.7.tgz +0 -0
  137. package/components/tryghost-email-analytics-provider-mailgun-5.33.7.tgz +0 -0
  138. package/components/tryghost-email-analytics-service-5.33.7.tgz +0 -0
  139. package/components/tryghost-email-content-generator-5.33.7.tgz +0 -0
  140. package/components/tryghost-email-events-5.33.7.tgz +0 -0
  141. package/components/tryghost-email-service-5.33.7.tgz +0 -0
  142. package/components/tryghost-email-suppression-list-5.33.7.tgz +0 -0
  143. package/components/tryghost-extract-api-key-5.33.7.tgz +0 -0
  144. package/components/tryghost-html-to-plaintext-5.33.7.tgz +0 -0
  145. package/components/tryghost-i18n-5.33.7.tgz +0 -0
  146. package/components/tryghost-importer-revue-5.33.7.tgz +0 -0
  147. package/components/tryghost-link-redirects-5.33.7.tgz +0 -0
  148. package/components/tryghost-link-replacer-5.33.7.tgz +0 -0
  149. package/components/tryghost-magic-link-5.33.7.tgz +0 -0
  150. package/components/tryghost-mailgun-client-5.33.7.tgz +0 -0
  151. package/components/tryghost-member-attribution-5.33.7.tgz +0 -0
  152. package/components/tryghost-member-events-5.33.7.tgz +0 -0
  153. package/components/tryghost-members-api-5.33.7.tgz +0 -0
  154. package/components/tryghost-members-csv-5.33.7.tgz +0 -0
  155. package/components/tryghost-members-offers-5.33.7.tgz +0 -0
  156. package/components/tryghost-members-payments-5.33.7.tgz +0 -0
  157. package/components/tryghost-members-stripe-service-5.33.7.tgz +0 -0
  158. package/components/tryghost-minifier-5.33.7.tgz +0 -0
  159. package/components/tryghost-mw-api-version-mismatch-5.33.7.tgz +0 -0
  160. package/components/tryghost-mw-cache-control-5.33.7.tgz +0 -0
  161. package/components/tryghost-mw-session-from-token-5.33.7.tgz +0 -0
  162. package/components/tryghost-mw-update-user-last-seen-5.33.7.tgz +0 -0
  163. package/components/tryghost-mw-vhost-5.33.7.tgz +0 -0
  164. package/components/tryghost-oembed-service-5.33.7.tgz +0 -0
  165. package/components/tryghost-referrers-5.33.7.tgz +0 -0
  166. package/components/tryghost-session-service-5.33.7.tgz +0 -0
  167. package/components/tryghost-staff-service-5.33.7.tgz +0 -0
  168. package/components/tryghost-stats-service-5.33.7.tgz +0 -0
  169. package/components/tryghost-tiers-5.33.7.tgz +0 -0
  170. package/components/tryghost-update-check-service-5.33.7.tgz +0 -0
  171. package/components/tryghost-verification-trigger-5.33.7.tgz +0 -0
  172. package/components/tryghost-version-notifications-data-service-5.33.7.tgz +0 -0
  173. package/components/tryghost-webmentions-5.33.7.tgz +0 -0
  174. package/core/server/services/email-analytics/jobs/fetch-latest/run.js +0 -57
  175. package/core/server/web/api/middleware/normalize-image.js +0 -42
@@ -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%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.33%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%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%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.34%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%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-3e6947aa681f0fb82b193090e520dc73.css">
40
- <link integrity="" rel="stylesheet" href="assets/ghost-fb9fb8adbcaf1603ad4006dd2d49e401.css" title="light">
40
+ <link integrity="" rel="stylesheet" href="assets/ghost-558c1e319d6e025bfab2054bc0f7fe83.css" title="light">
41
41
 
42
42
 
43
43
  </head>
@@ -56,9 +56,9 @@
56
56
 
57
57
  <div id="ember-basic-dropdown-wormhole"></div>
58
58
 
59
- <script src="assets/vendor-0441964c34d58f2aacd5a04bbe240243.js"></script>
60
- <script src="assets/chunk.963.e47ead5abeca4cf69fed.js"></script>
61
- <script src="assets/chunk.143.9057a1659075c0ee7336.js"></script>
62
- <script src="assets/ghost-904f203ff1f5f6bff4e55a03f39a4fbf.js"></script>
59
+ <script src="assets/vendor-253d6527ca6353855164ef65f896f762.js"></script>
60
+ <script src="assets/chunk.616.181e1ad6c33f0bec7a65.js"></script>
61
+ <script src="assets/chunk.143.a07da1be864f2e500e18.js"></script>
62
+ <script src="assets/ghost-ad40d109653288e74a7cd922341fb33d.js"></script>
63
63
  </body>
64
64
  </html>
@@ -2,10 +2,13 @@ const Command = require('./command');
2
2
  const DataGenerator = require('@tryghost/data-generator');
3
3
  const config = require('../shared/config');
4
4
 
5
- module.exports = class REPL extends Command {
5
+ module.exports = class DataGeneratorCommand extends Command {
6
6
  setup() {
7
7
  this.help('Generates random data to populate the database for development & testing');
8
8
  this.argument('--base-data-pack', {type: 'string', defaultValue: '', desc: 'Base data pack file location, imported instead of random content'});
9
+ this.argument('--scale', {type: 'string', defaultValue: 'small', desc: 'Scale of the data to generate. `small` for a quick run, `large` for more content'});
10
+ this.argument('--single-table', {type: 'string', desc: 'Import a single table'});
11
+ this.argument('--quantity', {type: 'number', desc: 'When importing a single table, the quantity to import'});
9
12
  }
10
13
 
11
14
  initializeContext(context) {
@@ -27,6 +30,17 @@ module.exports = class REPL extends Command {
27
30
  async handle(argv = {}) {
28
31
  const knex = require('../server/data/db/connection');
29
32
  const {tables: schema} = require('../server/data/schema/index');
33
+
34
+ const modelQuantities = {};
35
+ if (argv.scale) {
36
+ if (argv.scale === 'small') {
37
+ modelQuantities.members = 200;
38
+ modelQuantities.membersLoginEvents = 1;
39
+ modelQuantities.posts = 10;
40
+ }
41
+ // Defaults in data-generator package make a large set
42
+ }
43
+
30
44
  const dataGenerator = new DataGenerator({
31
45
  baseDataPack: argv['base-data-pack'],
32
46
  knex,
@@ -40,11 +54,15 @@ module.exports = class REPL extends Command {
40
54
  fatal: this.fatal,
41
55
  debug: this.debug
42
56
  },
43
- modelQuantities: {},
57
+ modelQuantities,
44
58
  baseUrl: config.getSiteUrl()
45
59
  });
46
60
  try {
47
- await dataGenerator.importData();
61
+ if (argv['single-table']) {
62
+ await dataGenerator.importSingleTable(argv['single-table'], argv.quantity ?? undefined);
63
+ } else {
64
+ await dataGenerator.importData();
65
+ }
48
66
  } catch (error) {
49
67
  this.fatal('Failed while generating data: ', error);
50
68
  }
@@ -177,7 +177,7 @@ class RouterManager {
177
177
 
178
178
  // NOTE: timezone change only affects the collection router with dated permalinks
179
179
  const collectionRouter = this.registry.getRouterByName('CollectionRouter');
180
- if (collectionRouter.getPermalinks().getValue().match(/:year|:month|:day/)) {
180
+ if (collectionRouter && collectionRouter.getPermalinks().getValue().match(/:year|:month|:day/)) {
181
181
  debug('handleTimezoneEdit: trigger regeneration');
182
182
 
183
183
  this.urlService.onRouterUpdated(collectionRouter.identifier);
@@ -5,6 +5,7 @@ const imageTransform = require('@tryghost/image-transform');
5
5
  const storage = require('../../../server/adapters/storage');
6
6
  const activeTheme = require('../../services/theme-engine/active');
7
7
  const config = require('../../../shared/config');
8
+ const {imageSize} = require('../../../server/lib/image');
8
9
 
9
10
  const SIZE_PATH_REGEX = /^\/size\/([^/]+)\//;
10
11
  const FORMAT_PATH_REGEX = /^\/format\/([^./]+)\//;
@@ -21,7 +22,7 @@ module.exports = function (req, res, next) {
21
22
  }
22
23
 
23
24
  const requestedDimension = req.url.match(SIZE_PATH_REGEX)[1];
24
-
25
+
25
26
  // Note that we don't use sizeImageDir because we need to keep the trailing slash
26
27
  let imagePath = req.url.replace(`/size/${requestedDimension}`, '');
27
28
 
@@ -39,7 +40,7 @@ module.exports = function (req, res, next) {
39
40
  // We need to keep the first slash here
40
41
  let url = req.originalUrl
41
42
  .replace(`/size/${requestedDimension}`, '');
42
-
43
+
43
44
  if (format) {
44
45
  url = url.replace(`/format/${format}`, '');
45
46
  }
@@ -81,7 +82,7 @@ module.exports = function (req, res, next) {
81
82
  return redirectToOriginal();
82
83
  }
83
84
 
84
- if (format) {
85
+ if (format) {
85
86
  // CASE: When formatting, we need to check if the imageTransform package supports this specific format
86
87
  if (!imageTransform.canTransformToFormat(format)) {
87
88
  // transform not supported
@@ -89,7 +90,7 @@ module.exports = function (req, res, next) {
89
90
  }
90
91
  }
91
92
 
92
- // CASE: when transforming is supported, we need to check if it is desired
93
+ // CASE: when transforming is supported, we need to check if it is desired
93
94
  // (e.g. it is not desired to resize SVGs when not formatting them to a different type)
94
95
  if (!format && !imageTransform.shouldResizeFileExtension(requestUrlFileExtension)) {
95
96
  return redirectToOriginal();
@@ -111,23 +112,7 @@ module.exports = function (req, res, next) {
111
112
  return;
112
113
  }
113
114
 
114
- const {dir, name, ext} = path.parse(imagePath);
115
- const [imageNameMatched, imageName, imageNumber] = name.match(/^(.+?)(-\d+)?$/) || [null];
116
-
117
- if (!imageNameMatched) {
118
- // CASE: Image name does not contain any characters?
119
- // RESULT: Hand off to `next()` which will 404
120
- return;
121
- }
122
- const unoptimizedImagePath = path.join(dir, `${imageName}_o${imageNumber || ''}${ext}`);
123
-
124
- return storageInstance.exists(unoptimizedImagePath)
125
- .then((unoptimizedImageExists) => {
126
- if (unoptimizedImageExists) {
127
- return unoptimizedImagePath;
128
- }
129
- return imagePath;
130
- })
115
+ return imageSize.getOriginalImagePath(imagePath)
131
116
  .then((storagePath) => {
132
117
  return storageInstance.read({path: storagePath});
133
118
  })
@@ -0,0 +1,3 @@
1
+ const RedisCache = require('@tryghost/adapter-cache-redis');
2
+
3
+ module.exports = RedisCache;
@@ -83,9 +83,19 @@ class LocalStorageBase extends StorageBase {
83
83
  urlToPath(url) {
84
84
  let filePath;
85
85
 
86
- if (url.match(this.staticFileUrl)) {
86
+ const prefix = urlUtils.urlJoin('/',
87
+ urlUtils.getSubdir(),
88
+ this.staticFileURLPrefix
89
+ );
90
+
91
+ if (url.startsWith(this.staticFileUrl)) {
92
+ // CASE: full path that includes the site url
87
93
  filePath = url.replace(this.staticFileUrl, '');
88
94
  filePath = path.join(this.storagePath, filePath);
95
+ } else if (url.startsWith(prefix)) {
96
+ // CASE: The result of the save method doesn't include the site url. So we need to handle this case.
97
+ filePath = url.replace(prefix, '');
98
+ filePath = path.join(this.storagePath, filePath);
89
99
  } else {
90
100
  throw new errors.IncorrectUsageError({
91
101
  message: tpl(messages.invalidUrlParameter, {url})
@@ -1,19 +1,60 @@
1
- const Promise = require('bluebird');
1
+ /* eslint-disable ghost/ghost-custom/max-api-complexity */
2
2
  const storage = require('../../adapters/storage');
3
+ const imageTransform = require('@tryghost/image-transform');
4
+ const config = require('../../../shared/config');
5
+ const path = require('path');
3
6
 
4
7
  module.exports = {
5
8
  docName: 'images',
6
9
  upload: {
7
10
  statusCode: 201,
8
11
  permissions: false,
9
- query(frame) {
12
+ async query(frame) {
10
13
  const store = storage.getStorage('images');
11
14
 
12
- if (frame.files) {
13
- return Promise
14
- .map(frame.files, file => store.save(file))
15
- .then(paths => paths[0]);
15
+ // Normalize
16
+ const imageOptimizationOptions = config.get('imageOptimization');
17
+
18
+ // Trim _o from file name (not allowed suffix)
19
+ frame.file.name = frame.file.name.replace(/_o(\.\w+?)$/, '$1');
20
+
21
+ // CASE: image transform is not capable of transforming file (e.g. .gif)
22
+ if (imageTransform.shouldResizeFileExtension(frame.file.ext) && imageOptimizationOptions.resize) {
23
+ const out = `${frame.file.path}_processed`;
24
+ const originalPath = frame.file.path;
25
+
26
+ const options = Object.assign({
27
+ in: originalPath,
28
+ out,
29
+ ext: frame.file.ext,
30
+ width: config.get('imageOptimization:defaultMaxWidth')
31
+ }, imageOptimizationOptions);
32
+
33
+ await imageTransform.resizeFromPath(options);
34
+
35
+ // Store the processed/optimized image
36
+ const processedImageUrl = await store.save({
37
+ ...frame.file,
38
+ path: out
39
+ });
40
+ const processedImagePath = store.urlToPath(processedImageUrl);
41
+
42
+ // Get the path and name of the processed image
43
+ // We want to store the original image on the same name + _o
44
+ // So we need to wait for the first store to finish before generating the name of the original image
45
+ const processedImageName = path.basename(processedImagePath);
46
+ const processedImageDir = path.dirname(processedImagePath);
47
+
48
+ // Store the original image
49
+ await store.save({
50
+ ...frame.file,
51
+ path: originalPath,
52
+ name: imageTransform.generateOriginalImageName(processedImageName)
53
+ }, processedImageDir);
54
+
55
+ return processedImageUrl;
16
56
  }
57
+
17
58
  return store.save(frame.file);
18
59
  }
19
60
  }
@@ -11,7 +11,7 @@ module.exports = {
11
11
  'page',
12
12
  'debug'
13
13
  ],
14
- permissions: false,
14
+ permissions: true,
15
15
  query(frame) {
16
16
  return mentions.controller.browse(frame);
17
17
  }
@@ -2,6 +2,7 @@ const Promise = require('bluebird');
2
2
  const tpl = require('@tryghost/tpl');
3
3
  const errors = require('@tryghost/errors');
4
4
  const models = require('../../models');
5
+ const tagsPublicService = require('../../services/tags-public');
5
6
 
6
7
  const ALLOWED_INCLUDES = ['count.posts'];
7
8
 
@@ -31,7 +32,7 @@ module.exports = {
31
32
  },
32
33
  permissions: true,
33
34
  query(frame) {
34
- return models.TagPublic.findPage(frame.options);
35
+ return tagsPublicService.api.browse(frame.options);
35
36
  }
36
37
  },
37
38
 
@@ -5,7 +5,7 @@ const membersService = require('../../../../../services/members');
5
5
  module.exports = {
6
6
  async read(model, apiConfig, frame) {
7
7
  const tiersModels = await membersService.api.productRepository.list({
8
- withRelated: ['monthlyPrice', 'yearlyPrice']
8
+ limit: 'all'
9
9
  });
10
10
  const tiers = tiersModels.data && tiersModels.data.map(tierModel => tierModel.toJSON());
11
11
 
@@ -7,7 +7,7 @@ module.exports = (model) => {
7
7
  target: json.target,
8
8
  timestamp: json.timestamp,
9
9
  payload: json.payload,
10
- resource_id: json.resourceId,
10
+ resource: json.resource,
11
11
  source_title: json.sourceTitle,
12
12
  source_site_title: json.sourceSiteTitle,
13
13
  source_excerpt: json.sourceExcerpt,
@@ -13,7 +13,7 @@ module.exports = {
13
13
  let pages = [];
14
14
 
15
15
  const tiersModels = await membersService.api.productRepository.list({
16
- withRelated: ['monthlyPrice', 'yearlyPrice']
16
+ limit: 'all'
17
17
  });
18
18
  const tiers = tiersModels.data ? tiersModels.data.map(tierModel => tierModel.toJSON()) : [];
19
19
 
@@ -13,7 +13,7 @@ module.exports = {
13
13
  let posts = [];
14
14
 
15
15
  const tiersModels = await membersService.api.productRepository.list({
16
- withRelated: ['monthlyPrice', 'yearlyPrice']
16
+ limit: 'all'
17
17
  });
18
18
  const tiers = tiersModels.data ? tiersModels.data.map(tierModel => tierModel.toJSON()) : [];
19
19
  if (models.meta) {
@@ -4,7 +4,7 @@ const membersService = require('../../../../../services/members');
4
4
  module.exports = {
5
5
  async all(model, apiConfig, frame) {
6
6
  const tiersModels = await membersService.api.productRepository.list({
7
- withRelated: ['monthlyPrice', 'yearlyPrice']
7
+ limit: 'all'
8
8
  });
9
9
  const tiers = tiersModels.data ? tiersModels.data.map(tierModel => tierModel.toJSON()) : [];
10
10
 
@@ -35,6 +35,7 @@ const author = (attrs, frame) => {
35
35
  delete attrs.free_member_signup_notification;
36
36
  delete attrs.paid_subscription_started_notification;
37
37
  delete attrs.paid_subscription_canceled_notification;
38
+ delete attrs.mention_notifications;
38
39
 
39
40
  // @NOTE: used for night shift
40
41
  delete attrs.accessibility;
@@ -0,0 +1,10 @@
1
+ const {addPermissionWithRoles} = require('../../utils');
2
+
3
+ module.exports = addPermissionWithRoles({
4
+ name: 'Browse mentions',
5
+ action: 'browse',
6
+ object: 'mention'
7
+ }, [
8
+ 'Administrator',
9
+ 'Admin Integration'
10
+ ]);
@@ -0,0 +1,7 @@
1
+ const {createAddColumnMigration} = require('../../utils');
2
+
3
+ module.exports = createAddColumnMigration('users', 'mention_notifications', {
4
+ type: 'boolean',
5
+ nullable: false,
6
+ defaultTo: true
7
+ });
@@ -0,0 +1,7 @@
1
+ const {createAddColumnMigration} = require('../../utils');
2
+
3
+ module.exports = createAddColumnMigration('mentions', 'deleted', {
4
+ type: 'boolean',
5
+ nullable: false,
6
+ defaultTo: false
7
+ });
@@ -633,6 +633,11 @@
633
633
  "name": "Edit links",
634
634
  "action_type": "edit",
635
635
  "object_type": "link"
636
+ },
637
+ {
638
+ "name": "Browse mentions",
639
+ "action_type": "browse",
640
+ "object_type": "mention"
636
641
  }
637
642
  ]
638
643
  },
@@ -763,7 +768,8 @@
763
768
  "newsletter": "all",
764
769
  "explore": "read",
765
770
  "comment": "all",
766
- "link": "all"
771
+ "link": "all",
772
+ "mention": "browse"
767
773
  },
768
774
  "DB Backup Integration": {
769
775
  "db": "all"
@@ -798,7 +804,8 @@
798
804
  "newsletter": ["browse", "read", "add", "edit"],
799
805
  "explore": "read",
800
806
  "comment": "all",
801
- "link": "all"
807
+ "link": "all",
808
+ "mention": "browse"
802
809
  },
803
810
  "Editor": {
804
811
  "notification": "all",
@@ -154,6 +154,7 @@ module.exports = {
154
154
  free_member_signup_notification: {type: 'boolean', nullable: false, defaultTo: true},
155
155
  paid_subscription_started_notification: {type: 'boolean', nullable: false, defaultTo: true},
156
156
  paid_subscription_canceled_notification: {type: 'boolean', nullable: false, defaultTo: false},
157
+ mention_notifications: {type: 'boolean', nullable: false, defaultTo: true},
157
158
  created_at: {type: 'dateTime', nullable: false},
158
159
  created_by: {type: 'string', maxlength: 24, nullable: false},
159
160
  updated_at: {type: 'dateTime', nullable: true},
@@ -993,6 +994,7 @@ module.exports = {
993
994
  resource_id: {type: 'string', maxlength: 24, nullable: true},
994
995
  resource_type: {type: 'string', maxlength: 50, nullable: true},
995
996
  created_at: {type: 'dateTime', nullable: false},
996
- payload: {type: 'text', maxlength: 65535, nullable: true}
997
+ payload: {type: 'text', maxlength: 65535, nullable: true},
998
+ deleted: {type: 'boolean', nullable: false, defaultTo: false}
997
999
  }
998
1000
  };
@@ -270,18 +270,36 @@ class ImageSize {
270
270
  });
271
271
  }
272
272
 
273
- async getOriginalImageSizeFromStoragePath(imagePath) {
273
+ /**
274
+ * Returns the path of the original image for a given image path (we always store the original image in a separate file, suffixed with _o, while we store a resized version of the image on the original name)
275
+ * TODO: Preferrably we want to move this to a separate image utils package. Currently not really a good place to put it in image lib.
276
+ */
277
+ async getOriginalImagePath(imagePath) {
274
278
  const {dir, name, ext} = path.parse(imagePath);
279
+ const storageInstance = this.storage.getStorage('images');
280
+
281
+ const preferredUnoptimizedImagePath = path.join(dir, `${name}_o${ext}`);
282
+ const preferredUnoptimizedImagePathExists = await storageInstance.exists(preferredUnoptimizedImagePath);
283
+ if (preferredUnoptimizedImagePathExists) {
284
+ return preferredUnoptimizedImagePath;
285
+ }
286
+
287
+ // Legacy format did some magic with the numbers that could cause bugs. We still need to support it for old files.
288
+ // refs https://github.com/TryGhost/Team/issues/481
275
289
  const [imageNameMatched, imageName, imageNumber] = name.match(/^(.+?)(-\d+)?$/) || [null];
276
290
 
277
291
  if (!imageNameMatched) {
278
- return this.getImageSizeFromStoragePath(imagePath);
292
+ return imagePath;
279
293
  }
280
294
 
281
- const originalImagePath = path.join(dir, `${imageName}_o${imageNumber || ''}${ext}`);
282
- const originalImageExists = await this.storage.getStorage('images').exists(originalImagePath);
295
+ const legacyOriginalImagePath = path.join(dir, `${imageName}_o${imageNumber || ''}${ext}`);
296
+ const legacyOriginalImageExists = await storageInstance.exists(legacyOriginalImagePath);
283
297
 
284
- return this.getImageSizeFromStoragePath(originalImageExists ? originalImagePath : imagePath);
298
+ return legacyOriginalImageExists ? legacyOriginalImagePath : imagePath;
299
+ }
300
+
301
+ async getOriginalImageSizeFromStoragePath(imagePath) {
302
+ return this.getImageSizeFromStoragePath(await this.getOriginalImagePath(imagePath));
285
303
  }
286
304
 
287
305
  _getPathFromUrl(imageUrl) {
@@ -1,23 +1,56 @@
1
+ const path = require('path');
1
2
  const urlUtils = require('../../shared/url-utils');
3
+ const config = require('../../shared/config');
4
+ const storage = require('../adapters/storage');
2
5
 
3
6
  let nodes;
4
7
  let lexicalHtmlRenderer;
5
8
  let urlTransformMap;
6
9
 
10
+ function populateNodes() {
11
+ const {DEFAULT_NODES} = require('@tryghost/kg-default-nodes');
12
+ nodes = DEFAULT_NODES;
13
+ }
14
+
7
15
  module.exports = {
8
16
  get lexicalHtmlRenderer() {
9
17
  if (!lexicalHtmlRenderer) {
18
+ if (!nodes) {
19
+ populateNodes();
20
+ }
21
+
10
22
  const LexicalHtmlRenderer = require('@tryghost/kg-lexical-html-renderer');
11
- lexicalHtmlRenderer = new LexicalHtmlRenderer();
23
+ lexicalHtmlRenderer = new LexicalHtmlRenderer({nodes});
12
24
  }
13
25
 
14
26
  return lexicalHtmlRenderer;
15
27
  },
16
28
 
29
+ render(lexical, userOptions = {}) {
30
+ const options = Object.assign({
31
+ siteUrl: config.get('url'),
32
+ imageOptimization: config.get('imageOptimization'),
33
+ canTransformImage(storagePath) {
34
+ const imageTransform = require('@tryghost/image-transform');
35
+ const {ext} = path.parse(storagePath);
36
+
37
+ // NOTE: the "saveRaw" check is smelly
38
+ return imageTransform.canTransformFiles()
39
+ && imageTransform.shouldResizeFileExtension(ext)
40
+ && typeof storage.getStorage('images').saveRaw === 'function';
41
+ },
42
+ createDocument() {
43
+ const {JSDOM} = require('jsdom');
44
+ return (new JSDOM()).window.document;
45
+ }
46
+ }, userOptions);
47
+
48
+ return this.lexicalHtmlRenderer.render(lexical, options);
49
+ },
50
+
17
51
  get nodes() {
18
52
  if (!nodes) {
19
- const {DEFAULT_NODES} = require('@tryghost/kg-default-nodes');
20
- nodes = DEFAULT_NODES;
53
+ populateNodes();
21
54
  }
22
55
 
23
56
  return nodes;
@@ -37,6 +37,9 @@ const Member = ghostBookshelf.Model.extend({
37
37
  key: 'tiers',
38
38
  replacement: 'products.slug'
39
39
  }, {
40
+ key: 'tier_id',
41
+ replacement: 'products.id'
42
+ },{
40
43
  key: 'newsletters',
41
44
  replacement: 'newsletters.slug'
42
45
  }, {
@@ -1,7 +1,13 @@
1
1
  const ghostBookshelf = require('./base');
2
2
 
3
3
  const Mention = ghostBookshelf.Model.extend({
4
- tableName: 'mentions'
4
+ tableName: 'mentions',
5
+ defaults: {
6
+ deleted: false
7
+ },
8
+ enforcedFilters() {
9
+ return 'deleted:false';
10
+ }
5
11
  });
6
12
 
7
13
  module.exports = {
@@ -681,7 +681,7 @@ Post = ghostBookshelf.Model.extend({
681
681
  )
682
682
  ) {
683
683
  try {
684
- this.set('html', lexicalLib.lexicalHtmlRenderer.render(this.get('lexical')));
684
+ this.set('html', lexicalLib.render(this.get('lexical')));
685
685
  } catch (err) {
686
686
  throw new errors.ValidationError({
687
687
  message: tpl(messages.invalidLexicalStructure),
@@ -68,7 +68,8 @@ User = ghostBookshelf.Model.extend({
68
68
  comment_notifications: true,
69
69
  free_member_signup_notification: true,
70
70
  paid_subscription_started_notification: true,
71
- paid_subscription_canceled_notification: false
71
+ paid_subscription_canceled_notification: false,
72
+ mention_notifications: true
72
73
  };
73
74
  },
74
75
 
@@ -504,6 +505,8 @@ User = ghostBookshelf.Model.extend({
504
505
  filter += '+paid_subscription_started_notification:true';
505
506
  } else if (type === 'paid-canceled') {
506
507
  filter += '+paid_subscription_canceled_notification:true';
508
+ } else if (type === 'mention-received') {
509
+ filter += '+mention_notifications:true';
507
510
  }
508
511
  const updatedOptions = _.merge({}, options, {filter, withRelated: ['roles']});
509
512
  return this.findAll(updatedOptions).then((users) => {
@@ -29,5 +29,12 @@ module.exports = {
29
29
  const {adapterClassName, adapterConfig} = resolveAdapterOptions(name, adapterServiceConfig);
30
30
 
31
31
  return adapterManager.getAdapter(name, adapterClassName, adapterConfig);
32
+ },
33
+
34
+ /**
35
+ * Force recreation of all instances instead of reusing cached instances. Use when editing config file during tests.
36
+ */
37
+ clearCache() {
38
+ adapterManager.clearInstanceCache();
32
39
  }
33
40
  };
@@ -0,0 +1,22 @@
1
+ /**
2
+ * This is an event that is used to circumvent the job manager that currently isn't able to run scheduled jobs on the main thread (not offloaded).
3
+ * We simply emit this event in the job manager and listen for it on the main thread.
4
+ */
5
+ module.exports = class StartEmailAnalyticsJobEvent {
6
+ /**
7
+ * @param {any} data
8
+ * @param {Date} timestamp
9
+ */
10
+ constructor(data, timestamp) {
11
+ this.data = data;
12
+ this.timestamp = timestamp;
13
+ }
14
+
15
+ /**
16
+ * @param {any} [data]
17
+ * @param {Date} [timestamp]
18
+ */
19
+ static create(data, timestamp) {
20
+ return new StartEmailAnalyticsJobEvent(data, timestamp ?? new Date);
21
+ }
22
+ };
@@ -1,23 +1,3 @@
1
- const config = require('../../../shared/config');
2
- const db = require('../../data/db');
3
- const settings = require('../../../shared/settings-cache');
4
- const {EmailAnalyticsService} = require('@tryghost/email-analytics-service');
5
- const {EmailEventProcessor} = require('@tryghost/email-service');
6
- const MailgunProvider = require('@tryghost/email-analytics-provider-mailgun');
7
- const queries = require('./lib/queries');
8
- const DomainEvents = require('@tryghost/domain-events');
1
+ const EmailAnalyticsServiceWrapper = require('./wrapper');
9
2
 
10
- const eventProcessor = new EmailEventProcessor({
11
- domainEvents: DomainEvents,
12
- db
13
- });
14
-
15
- module.exports = new EmailAnalyticsService({
16
- config,
17
- settings,
18
- eventProcessor,
19
- providers: [
20
- new MailgunProvider({config, settings})
21
- ],
22
- queries
23
- });
3
+ module.exports = new EmailAnalyticsServiceWrapper();