ghost 5.17.2 → 5.19.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 (190) hide show
  1. package/README.md +3 -3
  2. package/components/tryghost-adapter-manager-5.19.0.tgz +0 -0
  3. package/components/{tryghost-api-framework-5.17.2.tgz → tryghost-api-framework-5.19.0.tgz} +0 -0
  4. package/components/tryghost-api-version-compatibility-service-5.19.0.tgz +0 -0
  5. package/components/tryghost-audience-feedback-5.19.0.tgz +0 -0
  6. package/components/tryghost-bootstrap-socket-5.19.0.tgz +0 -0
  7. package/components/tryghost-constants-5.19.0.tgz +0 -0
  8. package/components/{tryghost-custom-theme-settings-service-5.17.2.tgz → tryghost-custom-theme-settings-service-5.19.0.tgz} +0 -0
  9. package/components/tryghost-domain-events-5.19.0.tgz +0 -0
  10. package/components/tryghost-email-analytics-provider-mailgun-5.19.0.tgz +0 -0
  11. package/components/tryghost-email-analytics-service-5.19.0.tgz +0 -0
  12. package/components/tryghost-email-content-generator-5.19.0.tgz +0 -0
  13. package/components/tryghost-express-dynamic-redirects-5.19.0.tgz +0 -0
  14. package/components/tryghost-extract-api-key-5.19.0.tgz +0 -0
  15. package/components/tryghost-html-to-plaintext-5.19.0.tgz +0 -0
  16. package/components/tryghost-job-manager-5.19.0.tgz +0 -0
  17. package/components/tryghost-link-redirects-5.19.0.tgz +0 -0
  18. package/components/tryghost-link-replacer-5.19.0.tgz +0 -0
  19. package/components/tryghost-link-tracking-5.19.0.tgz +0 -0
  20. package/components/tryghost-magic-link-5.19.0.tgz +0 -0
  21. package/components/tryghost-mailgun-client-5.19.0.tgz +0 -0
  22. package/components/tryghost-member-analytics-service-5.19.0.tgz +0 -0
  23. package/components/tryghost-member-attribution-5.19.0.tgz +0 -0
  24. package/components/tryghost-member-events-5.19.0.tgz +0 -0
  25. package/components/tryghost-members-analytics-ingress-5.19.0.tgz +0 -0
  26. package/components/tryghost-members-api-5.19.0.tgz +0 -0
  27. package/components/tryghost-members-csv-5.19.0.tgz +0 -0
  28. package/components/tryghost-members-events-service-5.19.0.tgz +0 -0
  29. package/components/tryghost-members-importer-5.19.0.tgz +0 -0
  30. package/components/tryghost-members-offers-5.19.0.tgz +0 -0
  31. package/components/tryghost-members-payments-5.19.0.tgz +0 -0
  32. package/components/tryghost-members-ssr-5.19.0.tgz +0 -0
  33. package/components/tryghost-members-stripe-service-5.19.0.tgz +0 -0
  34. package/components/tryghost-minifier-5.19.0.tgz +0 -0
  35. package/components/tryghost-mw-api-version-mismatch-5.19.0.tgz +0 -0
  36. package/components/tryghost-mw-cache-control-5.19.0.tgz +0 -0
  37. package/components/{tryghost-mw-error-handler-5.17.2.tgz → tryghost-mw-error-handler-5.19.0.tgz} +0 -0
  38. package/components/tryghost-mw-session-from-token-5.19.0.tgz +0 -0
  39. package/components/tryghost-mw-update-user-last-seen-5.19.0.tgz +0 -0
  40. package/components/tryghost-mw-vhost-5.19.0.tgz +0 -0
  41. package/components/tryghost-oembed-service-5.19.0.tgz +0 -0
  42. package/components/tryghost-package-json-5.19.0.tgz +0 -0
  43. package/components/tryghost-referrers-5.19.0.tgz +0 -0
  44. package/components/tryghost-security-5.19.0.tgz +0 -0
  45. package/components/tryghost-session-service-5.19.0.tgz +0 -0
  46. package/components/tryghost-settings-path-manager-5.19.0.tgz +0 -0
  47. package/components/tryghost-staff-service-5.19.0.tgz +0 -0
  48. package/components/tryghost-stats-service-5.19.0.tgz +0 -0
  49. package/components/tryghost-update-check-service-5.19.0.tgz +0 -0
  50. package/components/tryghost-verification-trigger-5.19.0.tgz +0 -0
  51. package/components/tryghost-version-notifications-data-service-5.19.0.tgz +0 -0
  52. package/content/themes/casper/assets/built/screen.css +1 -1
  53. package/content/themes/casper/assets/built/screen.css.map +1 -1
  54. package/content/themes/casper/assets/css/screen.css +18 -21
  55. package/content/themes/casper/package.json +1 -1
  56. package/core/boot.js +3 -1
  57. package/core/built/admin/assets/{chunk.143.3f2f5cbbd1ef4b1425d9.js → chunk.143.eaf838fbf1470f018bf3.js} +6 -6
  58. package/core/built/admin/assets/{chunk.174.37fefc669899f0fd0064.js → chunk.174.3a133d51d9b45097c101.js} +160 -159
  59. package/core/built/admin/assets/{chunk.178.8fa31be80e639cbd2df2.js → chunk.178.44dae8a74f7f9d606e06.js} +4 -4
  60. package/core/built/admin/assets/{chunk.579.a9bccec4d650a7be727a.js → chunk.613.f1d519ad47e7f9024263.js} +8962 -8890
  61. package/core/built/admin/assets/{chunk.579.a9bccec4d650a7be727a.js.LICENSE.txt → chunk.613.f1d519ad47e7f9024263.js.LICENSE.txt} +0 -0
  62. package/core/built/admin/assets/{ghost-3577dfa3f54651db5ae5b9fd3d9b2824.js → ghost-5ce6f5a730c83c91fc258b12c537ea35.js} +2905 -2866
  63. package/core/built/admin/assets/ghost-982146a4ada3a5af1981d1919ae01d08.css +1 -0
  64. package/core/built/admin/assets/ghost-dark-41929e4857de411a23597a9de49a4e4f.css +1 -0
  65. package/core/built/admin/assets/{vendor-733135cd6cbca8126c6fa223d63a5bf3.css → vendor-3e6947aa681f0fb82b193090e520dc73.css} +24 -92
  66. package/core/built/admin/assets/{vendor-325e038b8609f0979f6578cae7a87f9e.js → vendor-5c7d7063620bec13668c4370145cd4b4.js} +561 -555
  67. package/core/built/admin/index.html +7 -7
  68. package/core/frontend/helpers/t.js +12 -0
  69. package/core/frontend/helpers/tpl/content-cta.hbs +1 -1
  70. package/core/frontend/public/ghost.min.css +1 -1
  71. package/core/frontend/public/robots.txt +1 -0
  72. package/core/frontend/services/sitemap/handler.js +1 -1
  73. package/core/frontend/services/sitemap/index-generator.js +1 -3
  74. package/core/frontend/web/middleware/static-theme.js +2 -0
  75. package/core/server/api/endpoints/feedback-members.js +23 -0
  76. package/core/server/api/endpoints/index.js +5 -1
  77. package/core/server/api/endpoints/utils/serializers/input/posts.js +3 -3
  78. package/core/server/api/endpoints/utils/serializers/output/mappers/posts.js +14 -13
  79. package/core/server/data/exporter/table-lists.js +3 -1
  80. package/core/server/data/importer/handlers/json.js +21 -23
  81. package/core/server/data/importer/importers/data/base.js +1 -1
  82. package/core/server/data/migrations/versions/4.0/05-add-members-subscribe-events-table.js +1 -1
  83. package/core/server/data/migrations/versions/4.0/06-populate-members-subscribe-events-table.js +1 -1
  84. package/core/server/data/migrations/versions/4.0/11-add-members-paid-subscription-events-table.js +1 -1
  85. package/core/server/data/migrations/versions/4.0/13-add-members-payment-events-table.js +1 -1
  86. package/core/server/data/migrations/versions/4.0/17-populate-members-status-events-table.js +1 -1
  87. package/core/server/data/migrations/versions/4.0/22-solve-orphaned-webhooks.js +1 -1
  88. package/core/server/data/migrations/versions/4.0/25-populate-members-paid-subscription-events-table.js +1 -1
  89. package/core/server/data/migrations/versions/4.11/02-add-email-verification-required-setting.js +1 -1
  90. package/core/server/data/migrations/versions/4.12/01-add-email-only-column-to-posts-meta-table.js +1 -1
  91. package/core/server/data/migrations/versions/4.3/03-add-default-product.js +1 -1
  92. package/core/server/data/migrations/versions/4.3/04-attach-members-to-product.js +1 -1
  93. package/core/server/data/migrations/versions/4.3/06-add-stripe-prices-table.js +2 -2
  94. package/core/server/data/migrations/versions/4.3/08-migrate-members-signup-setting.js +1 -1
  95. package/core/server/data/migrations/versions/4.33/2022-01-14-11-51-add-default-free-tier.js +1 -1
  96. package/core/server/data/migrations/versions/4.42/2022-03-21-17-17-add.js +2 -2
  97. package/core/server/data/migrations/versions/4.43/2022-03-28-19-26-recreate-newsletter-table.js +5 -5
  98. package/core/server/data/migrations/versions/4.46/2022-04-13-13-00-add-default-newsletter.js +1 -1
  99. package/core/server/data/migrations/versions/4.46/2022-04-20-08-39-map-subscribers-to-default-newsletter.js +1 -1
  100. package/core/server/data/migrations/versions/4.7/03-add-labs-setting.js +1 -1
  101. package/core/server/data/migrations/versions/4.8/03-add-default-product-portal-products.js +1 -1
  102. package/core/server/data/migrations/versions/4.8/04-migrate-show-newsletter-header-setting.js +1 -1
  103. package/core/server/data/migrations/versions/5.0/2022-05-06-13-22-add-frontend-integration.js +1 -1
  104. package/core/server/data/migrations/versions/5.17/2022-09-29-12-39-add-track-clicks-column-to-emails.js +2 -2
  105. package/core/server/data/migrations/versions/5.19/2022-09-02-20-25-add-columns-to-products-table.js +19 -0
  106. package/core/server/data/migrations/versions/5.19/2022-09-02-20-52-backfill-new-product-columns.js +37 -0
  107. package/core/server/data/migrations/versions/5.19/2022-10-10-06-58-add-subscriptions-table.js +19 -0
  108. package/core/server/data/migrations/versions/5.19/2022-10-10-10-05-add-members-feedback-table.js +10 -0
  109. package/core/server/data/migrations/versions/5.19/2022-10-11-10-38-add-feedback-enabled-column-to-newsletters.js +7 -0
  110. package/core/server/data/schema/commands.js +3 -3
  111. package/core/server/data/schema/fixtures/fixtures.json +4 -1
  112. package/core/server/data/schema/schema.js +90 -24
  113. package/core/server/data/schema/validator.js +1 -1
  114. package/core/server/models/base/bookshelf.js +3 -4
  115. package/core/server/models/base/plugins/data-manipulation.js +1 -1
  116. package/core/server/models/base/plugins/events.js +1 -1
  117. package/core/server/models/base/utils.js +1 -1
  118. package/core/server/models/member-feedback.js +22 -0
  119. package/core/server/models/newsletter.js +3 -2
  120. package/core/server/models/post.js +24 -0
  121. package/core/server/models/settings.js +1 -1
  122. package/core/server/models/user.js +1 -1
  123. package/core/server/services/audience-feedback/FeedbackRepository.js +67 -0
  124. package/core/server/services/audience-feedback/index.js +33 -0
  125. package/core/server/services/bulk-email/bulk-email-processor.js +7 -1
  126. package/core/server/services/mail/GhostMailer.js +17 -1
  127. package/core/server/services/mega/feedback-buttons.js +69 -0
  128. package/core/server/services/mega/mega.js +1 -1
  129. package/core/server/services/mega/post-email-serializer.js +24 -4
  130. package/core/server/services/mega/template.js +3 -0
  131. package/core/server/services/members/middleware.js +40 -0
  132. package/core/server/services/notifications/notifications.js +1 -1
  133. package/core/server/services/settings/settings-service.js +1 -1
  134. package/core/server/services/themes/storage.js +1 -1
  135. package/core/server/services/webhooks/serialize.js +1 -1
  136. package/core/server/web/admin/app.js +2 -0
  137. package/core/server/web/api/endpoints/admin/routes.js +1 -2
  138. package/core/server/web/members/app.js +12 -0
  139. package/core/shared/config/defaults.json +1 -1
  140. package/core/shared/labs.js +6 -4
  141. package/package.json +117 -117
  142. package/yarn.lock +4867 -1572
  143. package/components/tryghost-adapter-manager-5.17.2.tgz +0 -0
  144. package/components/tryghost-api-version-compatibility-service-5.17.2.tgz +0 -0
  145. package/components/tryghost-bootstrap-socket-5.17.2.tgz +0 -0
  146. package/components/tryghost-constants-5.17.2.tgz +0 -0
  147. package/components/tryghost-domain-events-5.17.2.tgz +0 -0
  148. package/components/tryghost-email-analytics-provider-mailgun-5.17.2.tgz +0 -0
  149. package/components/tryghost-email-analytics-service-5.17.2.tgz +0 -0
  150. package/components/tryghost-email-content-generator-5.17.2.tgz +0 -0
  151. package/components/tryghost-express-dynamic-redirects-5.17.2.tgz +0 -0
  152. package/components/tryghost-extract-api-key-5.17.2.tgz +0 -0
  153. package/components/tryghost-html-to-plaintext-5.17.2.tgz +0 -0
  154. package/components/tryghost-job-manager-5.17.2.tgz +0 -0
  155. package/components/tryghost-link-redirects-5.17.2.tgz +0 -0
  156. package/components/tryghost-link-replacer-5.17.2.tgz +0 -0
  157. package/components/tryghost-link-tracking-5.17.2.tgz +0 -0
  158. package/components/tryghost-magic-link-5.17.2.tgz +0 -0
  159. package/components/tryghost-mailgun-client-5.17.2.tgz +0 -0
  160. package/components/tryghost-member-analytics-service-5.17.2.tgz +0 -0
  161. package/components/tryghost-member-attribution-5.17.2.tgz +0 -0
  162. package/components/tryghost-member-events-5.17.2.tgz +0 -0
  163. package/components/tryghost-members-analytics-ingress-5.17.2.tgz +0 -0
  164. package/components/tryghost-members-api-5.17.2.tgz +0 -0
  165. package/components/tryghost-members-csv-5.17.2.tgz +0 -0
  166. package/components/tryghost-members-events-service-5.17.2.tgz +0 -0
  167. package/components/tryghost-members-importer-5.17.2.tgz +0 -0
  168. package/components/tryghost-members-offers-5.17.2.tgz +0 -0
  169. package/components/tryghost-members-payments-5.17.2.tgz +0 -0
  170. package/components/tryghost-members-ssr-5.17.2.tgz +0 -0
  171. package/components/tryghost-members-stripe-service-5.17.2.tgz +0 -0
  172. package/components/tryghost-minifier-5.17.2.tgz +0 -0
  173. package/components/tryghost-mw-api-version-mismatch-5.17.2.tgz +0 -0
  174. package/components/tryghost-mw-cache-control-5.17.2.tgz +0 -0
  175. package/components/tryghost-mw-session-from-token-5.17.2.tgz +0 -0
  176. package/components/tryghost-mw-update-user-last-seen-5.17.2.tgz +0 -0
  177. package/components/tryghost-mw-vhost-5.17.2.tgz +0 -0
  178. package/components/tryghost-oembed-service-5.17.2.tgz +0 -0
  179. package/components/tryghost-package-json-5.17.2.tgz +0 -0
  180. package/components/tryghost-referrers-5.17.2.tgz +0 -0
  181. package/components/tryghost-security-5.17.2.tgz +0 -0
  182. package/components/tryghost-session-service-5.17.2.tgz +0 -0
  183. package/components/tryghost-settings-path-manager-5.17.2.tgz +0 -0
  184. package/components/tryghost-staff-service-5.17.2.tgz +0 -0
  185. package/components/tryghost-stats-service-5.17.2.tgz +0 -0
  186. package/components/tryghost-update-check-service-5.17.2.tgz +0 -0
  187. package/components/tryghost-verification-trigger-5.17.2.tgz +0 -0
  188. package/components/tryghost-version-notifications-data-service-5.17.2.tgz +0 -0
  189. package/core/built/admin/assets/ghost-597fb8e8b1b91dd0ac4d9f2d75bd67fb.css +0 -1
  190. package/core/built/admin/assets/ghost-dark-e4ccecd9903d35d360d71fe859cbb3bf.css +0 -1
@@ -7,6 +7,7 @@ const errors = require('@tryghost/errors');
7
7
  const tpl = require('@tryghost/tpl');
8
8
  const settingsCache = require('../../../shared/settings-cache');
9
9
  const urlUtils = require('../../../shared/url-utils');
10
+ const metrics = require('@tryghost/metrics');
10
11
  const messages = {
11
12
  title: 'Ghost at {domain}',
12
13
  checkEmailConfigInstructions: 'Please see {url} for instructions on configuring email.',
@@ -83,7 +84,8 @@ module.exports = class GhostMailer {
83
84
  const options = config.get('mail') && _.clone(config.get('mail').options) || {};
84
85
 
85
86
  this.state = {
86
- usingDirect: transport === 'direct'
87
+ usingDirect: transport === 'direct',
88
+ usingMailgun: transport === 'mailgun'
87
89
  };
88
90
  this.transport = nodemailer(transport, options);
89
91
  }
@@ -121,10 +123,24 @@ module.exports = class GhostMailer {
121
123
  }
122
124
 
123
125
  async sendMail(message) {
126
+ const startTime = Date.now();
124
127
  try {
125
128
  const response = await this.transport.sendMail(message);
129
+ if (this.state.usingMailgun) {
130
+ metrics.metric('mailgun-send-transactional-mail', {
131
+ value: Date.now() - startTime,
132
+ statusCode: 200
133
+ });
134
+ }
135
+
126
136
  return response;
127
137
  } catch (err) {
138
+ if (this.state.usingMailgun) {
139
+ metrics.metric('mailgun-send-transactional-mail', {
140
+ value: Date.now() - startTime,
141
+ statusCode: err.status
142
+ });
143
+ }
128
144
  throw createMailError({
129
145
  message: tpl(messages.reason, {reason: err.message || err}),
130
146
  err
@@ -0,0 +1,69 @@
1
+ const {Color} = require('@tryghost/color-utils');
2
+ const audienceFeedback = require('../audience-feedback');
3
+
4
+ const templateStrings = {
5
+ like: '%{feedback_button_like}%',
6
+ dislike: '%{feedback_button_dislike}%'
7
+ };
8
+
9
+ const generateLinks = (postId, uuid, html) => {
10
+ const positiveLink = audienceFeedback.service.buildLink(
11
+ uuid,
12
+ postId,
13
+ 1
14
+ );
15
+ const negativeLink = audienceFeedback.service.buildLink(
16
+ uuid,
17
+ postId,
18
+ 0
19
+ );
20
+
21
+ html = html.replace(templateStrings.like, positiveLink.href);
22
+ html = html.replace(templateStrings.dislike, negativeLink.href);
23
+
24
+ return html;
25
+ };
26
+
27
+ const getTemplate = (accentColor) => {
28
+ const likeButtonHtml = getButtonHtml(templateStrings.like, 'More like this', accentColor);
29
+ const dislikeButtonHtml = getButtonHtml(templateStrings.dislike, 'Less like this', accentColor);
30
+
31
+ return (`
32
+ <tr>
33
+ <td dir="ltr" width="100%" style="background-color: #ffffff; text-align: center; padding: 40px 4px; border-bottom: 1px solid #e5eff5" align="center">
34
+ <h3 style="text-align: center; margin-bottom: 22px; font-size: 17px; letter-spacing: -0.2px; margin-top: 0 !important;">What did you think of this post?</h3>
35
+ <table role="presentation" border="0" cellpadding="0" cellspacing="0" style="margin: auto; width: auto !important;">
36
+ <tr>
37
+ ${likeButtonHtml}
38
+ ${dislikeButtonHtml}
39
+ </tr>
40
+ </table>
41
+ </td>
42
+ </tr>
43
+ `);
44
+ };
45
+
46
+ function getButtonHtml(href, buttonText, accentColor) {
47
+ const color = new Color(accentColor);
48
+ const bgColor = `${accentColor}10`;
49
+ const textColor = color.darken(0.6).hex();
50
+
51
+ return (`
52
+ <td dir="ltr" valign="top" align="center" style="font-family: inherit; font-size: 14px; text-align: center;" nowrap>
53
+ <table align="center" role="presentation" cellspacing="0" cellpadding="0" border="0" style="width: auto !important;">
54
+ <tr>
55
+ <td style="padding: 0 6px; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol';">
56
+ <a href=${href} style="background-color: ${bgColor}; color: ${textColor}; border-radius: 22px; font-family: inherit; padding: 12px 20px; border: none; font-size: 14px; font-weight: bold; line-height: 100%; text-decoration: none; display: block;">
57
+ ${buttonText}
58
+ </a>
59
+ </td>
60
+ </tr>
61
+ </table>
62
+ </td>
63
+ `);
64
+ }
65
+
66
+ module.exports = {
67
+ generateLinks,
68
+ getTemplate
69
+ };
@@ -3,7 +3,7 @@ const Promise = require('bluebird');
3
3
  const debug = require('@tryghost/debug')('mega');
4
4
  const tpl = require('@tryghost/tpl');
5
5
  const moment = require('moment');
6
- const ObjectID = require('bson-objectid');
6
+ const ObjectID = require('bson-objectid').default;
7
7
  const errors = require('@tryghost/errors');
8
8
  const logging = require('@tryghost/logging');
9
9
  const settingsCache = require('../../../shared/settings-cache');
@@ -16,11 +16,12 @@ const urlService = require('../../services/url');
16
16
  const linkReplacer = require('@tryghost/link-replacer');
17
17
  const linkTracking = require('../link-tracking');
18
18
  const memberAttribution = require('../member-attribution');
19
+ const feedbackButtons = require('./feedback-buttons');
19
20
 
20
21
  const ALLOWED_REPLACEMENTS = ['first_name', 'uuid'];
21
22
 
22
23
  const PostEmailSerializer = {
23
-
24
+
24
25
  // Format a full html document ready for email by inlining CSS, adjusting links,
25
26
  // and performing any client-specific fixes
26
27
  formatHtmlForEmail(html) {
@@ -107,6 +108,23 @@ const PostEmailSerializer = {
107
108
  return signupUrl.href;
108
109
  },
109
110
 
111
+ /**
112
+ * createUserLinks
113
+ *
114
+ * Generate personalised links for each user
115
+ *
116
+ * @param {string} memberUuid member uuid
117
+ * @param {Object} email
118
+ */
119
+ createUserLinks(email, memberUuid) {
120
+ const result = {...email};
121
+
122
+ result.html = feedbackButtons.generateLinks(result.post.id, memberUuid, result.html);
123
+ result.plaintext = htmlToPlaintext.email(result.html);
124
+
125
+ return result;
126
+ },
127
+
110
128
  // NOTE: serialization is needed to make sure we do post transformations such as image URL transformation from relative to absolute
111
129
  async serializePostModel(model) {
112
130
  // fetch mobiledoc rather than html and plaintext so we can render email-specific contents
@@ -206,6 +224,7 @@ const PostEmailSerializer = {
206
224
  titleAlignment: newsletter.get('title_alignment'),
207
225
  bodyFontCategory: newsletter.get('body_font_category'),
208
226
  showBadge: newsletter.get('show_badge'),
227
+ feedbackEnabled: newsletter.get('feedback_enabled'),
209
228
  footerContent: newsletter.get('footer_content'),
210
229
  showHeaderName: newsletter.get('show_header_name'),
211
230
  accentColor,
@@ -335,7 +354,7 @@ const PostEmailSerializer = {
335
354
  plaintext: post.plaintext
336
355
  };
337
356
 
338
- /**
357
+ /**
339
358
  * If a part of the email is members-only and the post is paid-only, add a paywall:
340
359
  * - Just before sending the email, we'll hide the paywall or paid content depending on the member segment it is sent to.
341
360
  * - We already need to do URL-replacement on the HTML here
@@ -369,7 +388,7 @@ const PostEmailSerializer = {
369
388
 
370
389
  // Add link click tracking
371
390
  url = await linkTracking.service.addTrackingToUrl(url, post, '--uuid--');
372
-
391
+
373
392
  // We need to convert to a string at this point, because we need invalid string characters in the URL
374
393
  const str = url.toString().replace(/--uuid--/g, '%%{uuid}%%');
375
394
  return str;
@@ -490,7 +509,7 @@ const PostEmailSerializer = {
490
509
  });
491
510
 
492
511
  result.html = this.formatHtmlForEmail($.html());
493
- result.plaintext = htmlToPlaintext.email(result.html);
512
+ result.plaintext = htmlToPlaintext.email(result.html);
494
513
  delete result.post;
495
514
 
496
515
  return result;
@@ -501,6 +520,7 @@ module.exports = {
501
520
  serialize: PostEmailSerializer.serialize.bind(PostEmailSerializer),
502
521
  createUnsubscribeUrl: PostEmailSerializer.createUnsubscribeUrl.bind(PostEmailSerializer),
503
522
  createPostSignupUrl: PostEmailSerializer.createPostSignupUrl.bind(PostEmailSerializer),
523
+ createUserLinks: PostEmailSerializer.createUserLinks.bind(PostEmailSerializer),
504
524
  renderEmailForSegment: PostEmailSerializer.renderEmailForSegment.bind(PostEmailSerializer),
505
525
  parseReplacements: PostEmailSerializer.parseReplacements.bind(PostEmailSerializer),
506
526
  // Export for tests
@@ -1,4 +1,5 @@
1
1
  const {escapeHtml: escape} = require('@tryghost/string');
2
+ const feedbackButtons = require('./feedback-buttons');
2
3
 
3
4
  /* eslint indent: warn, no-irregular-whitespace: warn */
4
5
  const iff = (cond, yes, no) => (cond ? yes : no);
@@ -1265,6 +1266,8 @@ ${ templateSettings.showBadge ? `
1265
1266
 
1266
1267
  <!-- END MAIN CONTENT AREA -->
1267
1268
 
1269
+ ${iff(templateSettings.feedbackEnabled, feedbackButtons.getTemplate(templateSettings.accentColor), '')}
1270
+
1268
1271
  <tr>
1269
1272
  <td class="wrapper" align="center">
1270
1273
  <table role="presentation" border="0" cellpadding="0" cellspacing="0" width="100%" style="padding-top: 40px; padding-bottom: 30px;">
@@ -5,6 +5,13 @@ const models = require('../../models');
5
5
  const urlUtils = require('../../../shared/url-utils');
6
6
  const spamPrevention = require('../../web/shared/middleware/api/spam-prevention');
7
7
  const {formattedMemberResponse} = require('./utils');
8
+ const errors = require('@tryghost/errors');
9
+ const tpl = require('@tryghost/tpl');
10
+
11
+ const messages = {
12
+ missingUuid: 'Missing uuid.',
13
+ invalidUuid: 'Invalid uuid.'
14
+ };
8
15
 
9
16
  // @TODO: This piece of middleware actually belongs to the frontend, not to the member app
10
17
  // Need to figure a way to separate these things (e.g. frontend actually talks to members API)
@@ -20,6 +27,38 @@ const loadMemberSession = async function (req, res, next) {
20
27
  }
21
28
  };
22
29
 
30
+ /**
31
+ * Require member authentication, and make it possible to authenticate via uuid.
32
+ * You can chain this after loadMemberSession to make it possible to authetnicate via both the uuid and the session.
33
+ */
34
+ const authMemberByUuid = async function (req, res, next) {
35
+ try {
36
+ const uuid = req.query.uuid;
37
+ if (!uuid) {
38
+ if (res.locals.member && req.member) {
39
+ // Already authenticated via session
40
+ return next();
41
+ }
42
+
43
+ throw new errors.UnauthorizedError({
44
+ messsage: tpl(messages.missingUuid)
45
+ });
46
+ }
47
+
48
+ const member = await membersService.api.memberBREADService.read({uuid});
49
+ if (!member) {
50
+ throw new errors.UnauthorizedError({
51
+ message: tpl(messages.invalidUuid)
52
+ });
53
+ }
54
+ Object.assign(req, {member});
55
+ res.locals.member = req.member;
56
+ next();
57
+ } catch (err) {
58
+ next(err);
59
+ }
60
+ };
61
+
23
62
  const getIdentityToken = async function (req, res) {
24
63
  try {
25
64
  const token = await membersService.ssr.getIdentityTokenForMemberFromSession(req, res);
@@ -216,6 +255,7 @@ const createSessionFromMagicLink = async function (req, res, next) {
216
255
  // Set req.member & res.locals.member if a cookie is set
217
256
  module.exports = {
218
257
  loadMemberSession,
258
+ authMemberByUuid,
219
259
  createSessionFromMagicLink,
220
260
  getIdentityToken,
221
261
  getMemberNewsletters,
@@ -5,7 +5,7 @@ const _ = require('lodash');
5
5
  const errors = require('@tryghost/errors');
6
6
  const ghostVersion = require('@tryghost/version');
7
7
  const tpl = require('@tryghost/tpl');
8
- const ObjectId = require('bson-objectid');
8
+ const ObjectId = require('bson-objectid').default;
9
9
 
10
10
  const messages = {
11
11
  noPermissionToDismissNotif: 'You do not have permission to dismiss this notification.',
@@ -13,7 +13,7 @@ const mail = require('../mail');
13
13
  const SingleUseTokenProvider = require('../members/SingleUseTokenProvider');
14
14
  const urlUtils = require('../../../shared/url-utils');
15
15
 
16
- const ObjectId = require('bson-objectid');
16
+ const ObjectId = require('bson-objectid').default;
17
17
  const settingsHelpers = require('../settings-helpers');
18
18
 
19
19
  const MAGIC_LINK_TOKEN_VALIDITY = 24 * 60 * 60 * 1000;
@@ -1,6 +1,6 @@
1
1
  const debug = require('@tryghost/debug')('themes');
2
2
  const fs = require('fs-extra');
3
- const ObjectID = require('bson-objectid');
3
+ const ObjectID = require('bson-objectid').default;
4
4
 
5
5
  const tpl = require('@tryghost/tpl');
6
6
  const logging = require('@tryghost/logging');
@@ -13,7 +13,7 @@ module.exports = (event, model) => {
13
13
  ops.push(() => {
14
14
  let frame = {options: {previous: false, context: {user: true}}};
15
15
 
16
- // NOTE: below options are lost in the during event processing, a more holistic approach would be
16
+ // @NOTE: below options are lost during event processing, a more holistic approach would be
17
17
  // to pass them somehow along with the model
18
18
  if (['posts', 'pages'].includes(docName)) {
19
19
  frame.options.formats = ['mobiledoc', 'html', 'plaintext'];
@@ -20,6 +20,8 @@ module.exports = function setupAdminApp() {
20
20
  // @NOTE: when we start working on HTTP/3 optimizations the immutable headers
21
21
  // produced below should be split into separate 'Cache-Control' entry.
22
22
  // For reference see: https://developer.mozilla.org/en-US/docs/Web/HTTP/Caching#validation_2
23
+ // @NOTE: the maxAge config passed below are in milliseconds and the config
24
+ // is specified in seconds. See https://github.com/expressjs/serve-static/issues/150 for more context
23
25
  adminApp.use('/assets', serveStatic(
24
26
  path.join(config.get('paths').adminAssets, 'assets'), {
25
27
  maxAge: (configMaxAge || configMaxAge === 0) ? configMaxAge : constants.ONE_YEAR_MS,
@@ -3,7 +3,6 @@ const api = require('../../../../api').endpoints;
3
3
  const {http} = require('@tryghost/api-framework');
4
4
  const apiMw = require('../../middleware');
5
5
  const mw = require('./middleware');
6
- const labs = require('../../../../../shared/labs');
7
6
 
8
7
  const shared = require('../../../shared');
9
8
 
@@ -310,7 +309,7 @@ module.exports = function apiRoutes() {
310
309
  router.put('/newsletters/verifications/', mw.authAdminApi, http(api.newsletters.verifyPropertyUpdate));
311
310
  router.put('/newsletters/:id', mw.authAdminApi, http(api.newsletters.edit));
312
311
 
313
- router.get('/links', labs.enabledMiddleware('emailClicks'), mw.authAdminApi, http(api.links.browse));
312
+ router.get('/links', mw.authAdminApi, http(api.links.browse));
314
313
 
315
314
  return router;
316
315
  };
@@ -10,6 +10,8 @@ const shared = require('../shared');
10
10
  const labs = require('../../../shared/labs');
11
11
  const errorHandler = require('@tryghost/mw-error-handler');
12
12
  const config = require('../../../shared/config');
13
+ const {http} = require('@tryghost/api-framework');
14
+ const api = require('../../api').endpoints;
13
15
 
14
16
  const commentRouter = require('../comments');
15
17
 
@@ -65,6 +67,16 @@ module.exports = function setupMembersApp() {
65
67
  // Comments
66
68
  membersApp.use('/api/comments', commentRouter());
67
69
 
70
+ // Feedback
71
+ membersApp.post(
72
+ '/api/feedback',
73
+ labs.enabledMiddleware('audienceFeedback'),
74
+ bodyParser.json({limit: '50mb'}),
75
+ middleware.loadMemberSession,
76
+ middleware.authMemberByUuid,
77
+ http(api.feedbackMembers.add)
78
+ );
79
+
68
80
  // API error handling
69
81
  membersApp.use('/api', errorHandler.resourceNotFound);
70
82
  membersApp.use('/api', errorHandler.handleJSONResponse(sentry));
@@ -168,7 +168,7 @@
168
168
  "comments": {
169
169
  "url": "https://cdn.jsdelivr.net/ghost/comments-ui@~{version}/umd/comments-ui.min.js",
170
170
  "styles": "https://cdn.jsdelivr.net/ghost/comments-ui@~{version}/umd/main.css",
171
- "version": "0.10.1"
171
+ "version": "0.10"
172
172
  },
173
173
  "editor": {
174
174
  "url": "https://unpkg.com/@tryghost/koenig-lexical@~{version}/dist/koenig-lexical.umd.js",
@@ -19,22 +19,24 @@ const GA_FEATURES = [
19
19
  'freeTrial',
20
20
  'compExpiring',
21
21
  'searchHelper',
22
- 'emailAlerts',
23
- 'emailClicks'
22
+ 'emailAlerts'
24
23
  ];
25
24
 
26
25
  // NOTE: this allowlist is meant to be used to filter out any unexpected
27
26
  // input for the "labs" setting value
28
27
  const BETA_FEATURES = [
29
28
  'activitypub',
29
+ 'sourceAttribution',
30
30
  'memberAttribution'
31
31
  ];
32
32
 
33
33
  const ALPHA_FEATURES = [
34
34
  'urlCache',
35
35
  'beforeAfterCard',
36
- 'sourceAttribution',
37
- 'lexicalEditor'
36
+ 'lexicalEditor',
37
+ 'exploreApp',
38
+ 'audienceFeedback',
39
+ 'fixNewsletterLinks'
38
40
  ];
39
41
 
40
42
  module.exports.GA_KEYS = [...GA_FEATURES];