ghost 5.3.1 → 5.5.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 (107) hide show
  1. package/components/tryghost-custom-theme-settings-service-0.0.0.tgz +0 -0
  2. package/components/tryghost-domain-events-0.0.0.tgz +0 -0
  3. package/components/tryghost-email-analytics-provider-mailgun-0.0.0.tgz +0 -0
  4. package/components/tryghost-email-analytics-service-0.0.0.tgz +0 -0
  5. package/components/tryghost-express-dynamic-redirects-0.0.0.tgz +0 -0
  6. package/components/tryghost-magic-link-0.0.0.tgz +0 -0
  7. package/components/tryghost-member-analytics-service-0.0.0.tgz +0 -0
  8. package/components/tryghost-member-events-0.0.0.tgz +0 -0
  9. package/components/tryghost-members-analytics-ingress-0.0.0.tgz +0 -0
  10. package/components/tryghost-members-api-0.0.0.tgz +0 -0
  11. package/components/tryghost-members-csv-0.0.0.tgz +0 -0
  12. package/components/tryghost-members-events-service-0.0.0.tgz +0 -0
  13. package/components/tryghost-members-importer-0.0.0.tgz +0 -0
  14. package/components/tryghost-members-offers-0.0.0.tgz +0 -0
  15. package/components/tryghost-members-payments-0.0.0.tgz +0 -0
  16. package/components/tryghost-members-ssr-0.0.0.tgz +0 -0
  17. package/components/tryghost-members-stripe-service-0.0.0.tgz +0 -0
  18. package/components/tryghost-verification-trigger-0.0.0.tgz +0 -0
  19. package/content/themes/casper/assets/built/global.css +1 -1
  20. package/content/themes/casper/assets/built/global.css.map +1 -1
  21. package/content/themes/casper/assets/built/screen.css +1 -1
  22. package/content/themes/casper/assets/built/screen.css.map +1 -1
  23. package/content/themes/casper/assets/css/screen.css +31 -8
  24. package/content/themes/casper/default.hbs +8 -5
  25. package/content/themes/casper/gulpfile.js +1 -1
  26. package/content/themes/casper/package.json +9 -9
  27. package/content/themes/casper/yarn.lock +1154 -1249
  28. package/core/boot.js +5 -0
  29. package/core/built/assets/{chunk.3.dc389a0f93cb5fabd695.js → chunk.3.550552fbc71864fb9738.js} +20 -20
  30. package/core/built/assets/fonts/Inter.ttf +0 -0
  31. package/core/built/assets/ghost-dark-5c2a961b35311d7298136e02289d98b2.css +1 -0
  32. package/core/built/assets/ghost.min-a89d10b3b58c1a5ebaca68cef93a404c.css +1 -0
  33. package/core/built/assets/{ghost.min-f4bba3a2a5ef256b82641345505d4f0f.js → ghost.min-c75f224decd20f9538179d7564cd2ab4.js} +3025 -2883
  34. package/core/built/assets/icons/event-comment.svg +3 -0
  35. package/core/built/assets/{vendor.min-4076498ccd6c8412365f43b156084ed8.js → vendor.min-cf3af99dca0c71937669305afb3686a1.js} +6122 -3197
  36. package/core/frontend/helpers/comments.js +22 -10
  37. package/core/frontend/helpers/ghost_head.js +22 -4
  38. package/core/frontend/helpers/total_members.js +17 -0
  39. package/core/frontend/helpers/total_paid_members.js +16 -0
  40. package/core/frontend/utils/frontend-apps.js +33 -0
  41. package/core/frontend/utils/member-count.js +50 -0
  42. package/core/frontend/web/middleware/cors.js +2 -1
  43. package/core/server/api/endpoints/comments-comments.js +50 -32
  44. package/core/server/api/endpoints/offers-public.js +2 -2
  45. package/core/server/api/endpoints/offers.js +8 -8
  46. package/core/server/api/endpoints/settings.js +62 -30
  47. package/core/server/api/endpoints/utils/serializers/input/settings.js +1 -0
  48. package/core/server/api/endpoints/utils/serializers/output/config.js +2 -1
  49. package/core/server/api/endpoints/utils/serializers/output/index.js +0 -4
  50. package/core/server/api/endpoints/utils/serializers/output/mappers/activity-feed-events.js +17 -0
  51. package/core/server/api/endpoints/utils/serializers/output/mappers/comments.js +18 -0
  52. package/core/server/api/endpoints/utils/serializers/output/mappers/index.js +2 -0
  53. package/core/server/api/endpoints/utils/serializers/output/mappers/offers.js +28 -0
  54. package/core/server/api/endpoints/utils/serializers/output/members.js +12 -1
  55. package/core/server/api/endpoints/utils/serializers/output/settings.js +2 -1
  56. package/core/server/api/endpoints/utils/validators/input/settings.js +22 -2
  57. package/core/server/data/exporter/table-lists.js +2 -1
  58. package/core/server/data/migrations/versions/5.5/2022-07-18-14-29-add-comment-reporting-permissions.js +10 -0
  59. package/core/server/data/migrations/versions/5.5/2022-07-18-14-31-drop-reports-reason.js +3 -0
  60. package/core/server/data/migrations/versions/5.5/2022-07-18-14-32-drop-nullable-member-id-from-likes.js +4 -0
  61. package/core/server/data/migrations/versions/5.5/2022-07-18-14-33-fix-comments-on-delete-foreign-keys.js +119 -0
  62. package/core/server/data/migrations/versions/5.5/2022-07-21-08-56-add-jobs-table.js +11 -0
  63. package/core/server/data/schema/commands.js +7 -2
  64. package/core/server/data/schema/fixtures/fixtures.json +5 -0
  65. package/core/server/data/schema/schema.js +12 -4
  66. package/core/server/ghost-server.js +0 -22
  67. package/core/server/models/comment-report.js +34 -0
  68. package/core/server/models/comment.js +8 -7
  69. package/core/server/models/job.js +9 -0
  70. package/core/server/models/tag.js +4 -0
  71. package/core/server/services/comments/email-templates/new-comment-reply.hbs +2 -2
  72. package/core/server/services/comments/email-templates/new-comment-reply.txt.js +7 -8
  73. package/core/server/services/comments/email-templates/new-comment.hbs +2 -2
  74. package/core/server/services/comments/email-templates/new-comment.txt.js +7 -6
  75. package/core/server/services/comments/email-templates/report.hbs +199 -0
  76. package/core/server/services/comments/email-templates/report.txt.js +16 -0
  77. package/core/server/services/comments/emails.js +57 -1
  78. package/core/server/services/comments/service.js +194 -2
  79. package/core/server/services/jobs/job-service.js +24 -1
  80. package/core/server/services/mail/GhostMailer.js +1 -0
  81. package/core/server/services/members/SingleUseTokenProvider.js +3 -3
  82. package/core/server/services/members/api.js +2 -1
  83. package/core/server/services/members/config.js +4 -1
  84. package/core/server/services/members/middleware.js +14 -2
  85. package/core/server/services/members/settings.js +4 -90
  86. package/core/server/services/public-config/config.js +2 -1
  87. package/core/server/services/settings/emails/verify-email.js +166 -0
  88. package/core/server/services/settings/settings-bread-service.js +170 -4
  89. package/core/server/services/settings/settings-service.js +9 -1
  90. package/core/server/services/stripe/service.js +9 -1
  91. package/core/server/services/webhooks/serialize.js +5 -0
  92. package/core/server/web/admin/views/default-prod.html +4 -4
  93. package/core/server/web/admin/views/default.html +4 -4
  94. package/core/server/web/api/endpoints/admin/routes.js +6 -0
  95. package/core/server/web/api/endpoints/content/routes.js +2 -1
  96. package/core/server/web/api/middleware/cors.js +2 -1
  97. package/core/server/web/api/testmode/jobs/graceful-job.js +2 -2
  98. package/core/server/web/api/testmode/routes.js +14 -0
  99. package/core/server/web/comments/routes.js +2 -0
  100. package/core/server/web/members/app.js +2 -4
  101. package/core/shared/config/defaults.json +15 -7
  102. package/core/shared/config/env/config.testing.json +3 -2
  103. package/package.json +75 -60
  104. package/yarn.lock +1812 -1832
  105. package/core/built/assets/ghost-dark-9e5d1f0dfae41232e5e34e4d0df53ae0.css +0 -1
  106. package/core/built/assets/ghost.min-e7cfbd1800f8e99b9158f74f1e39cd76.css +0 -1
  107. package/core/server/api/endpoints/utils/serializers/output/offers.js +0 -16
@@ -1,15 +1,28 @@
1
1
  const {SafeString} = require('../services/handlebars');
2
- const {config, urlUtils, getFrontendKey, labs} = require('../services/proxy');
2
+ const {urlUtils, getFrontendKey, labs, settingsCache} = require('../services/proxy');
3
+ const {getFrontendAppConfig, getDataAttributes} = require('../utils/frontend-apps');
3
4
 
4
5
  async function comments(options) {
5
6
  // todo: For now check on the comment id to exclude normal pages (we probably have a better way to do this)
6
7
 
7
8
  const commentId = this.comment_id;
8
-
9
+
9
10
  if (!commentId) {
10
11
  return;
11
12
  }
12
-
13
+
14
+ /**
15
+ * We need to check if comments enabled, because the theme might not be using the other available helpers to check
16
+ * if comments is enabled + the member has access
17
+ * @type {'all'|'paid'|'off'}
18
+ */
19
+ const commentsEnabled = settingsCache.get('comments_enabled');
20
+ const hasAccess = !!this.access;
21
+
22
+ if (commentsEnabled === 'off' || !hasAccess) {
23
+ return;
24
+ }
25
+
13
26
  let colorScheme = 'auto';
14
27
  if (options.hash.color_scheme === 'dark' || options.hash.color_scheme === 'light') {
15
28
  colorScheme = options.hash.color_scheme;
@@ -26,28 +39,27 @@ async function comments(options) {
26
39
  }
27
40
 
28
41
  const frontendKey = await getFrontendKey();
42
+ const {scriptUrl, stylesUrl, appVersion} = getFrontendAppConfig('comments');
29
43
 
30
44
  const data = {
31
45
  'ghost-comments': urlUtils.getSiteUrl(),
32
46
  api: urlUtils.urlFor('api', {type: 'content'}, true),
33
47
  admin: urlUtils.urlFor('admin', true),
34
48
  key: frontendKey,
49
+ styles: stylesUrl,
35
50
  'post-id': this.id,
36
51
  'sentry-dsn': '', /* todo: insert sentry dsn key here */
37
52
  'color-scheme': colorScheme,
38
53
  'avatar-saturation': avatarSaturation,
39
54
  'accent-color': accentColor,
40
- 'app-version': config.get('comments:version')
55
+ 'app-version': appVersion,
56
+ 'comments-enabled': commentsEnabled
41
57
  };
42
58
 
43
- let dataAttributes = '';
44
-
45
- Object.entries(data).forEach(([key, value]) => {
46
- dataAttributes += `data-${key}="${value}" `;
47
- });
59
+ const dataAttributes = getDataAttributes(data);
48
60
 
49
61
  return new SafeString(`
50
- <script defer src="${config.get('comments:url')}" ${dataAttributes} crossorigin="anonymous"></script>
62
+ <script defer src="${scriptUrl}" ${dataAttributes} crossorigin="anonymous"></script>
51
63
  `);
52
64
  }
53
65
 
@@ -13,6 +13,7 @@ const logging = require('@tryghost/logging');
13
13
  const _ = require('lodash');
14
14
  const debug = require('@tryghost/debug')('ghost_head');
15
15
  const templateStyles = require('./tpl/styles');
16
+ const {getFrontendAppConfig, getDataAttributes} = require('../utils/frontend-apps');
16
17
 
17
18
  const {get: getMetaData, getAssetUrl} = metaData;
18
19
 
@@ -47,9 +48,20 @@ function getMembersHelper(data, frontendKey) {
47
48
  if (!settingsCache.get('members_enabled')) {
48
49
  return '';
49
50
  }
51
+ const {scriptUrl} = getFrontendAppConfig('portal');
52
+
53
+ const colorString = (_.has(data, 'site._preview') && data.site.accent_color) ? data.site.accent_color : '';
54
+ const attributes = {
55
+ ghost: urlUtils.getSiteUrl(),
56
+ key: frontendKey,
57
+ api: urlUtils.urlFor('api', {type: 'content'}, true)
58
+ };
59
+ if (colorString) {
60
+ attributes['accent-color'] = colorString;
61
+ }
62
+ const dataAttributes = getDataAttributes(attributes);
50
63
 
51
- const colorString = _.has(data, 'site._preview') && data.site.accent_color ? ` data-accent-color="${data.site.accent_color}"` : '';
52
- let membersHelper = `<script defer src="${config.get('portal:url')}" data-ghost="${urlUtils.getSiteUrl()}"${colorString} data-key="${frontendKey}" data-api="${urlUtils.urlFor('api', {type: 'content'}, true)}" crossorigin="anonymous"></script>`;
64
+ let membersHelper = `<script defer src="${scriptUrl}" ${dataAttributes} crossorigin="anonymous"></script>`;
53
65
  membersHelper += (`<style id="gh-members-styles">${templateStyles}</style>`);
54
66
  if (settingsCache.get('paid_members_enabled')) {
55
67
  membersHelper += '<script async src="https://js.stripe.com/v3/"></script>';
@@ -59,8 +71,14 @@ function getMembersHelper(data, frontendKey) {
59
71
 
60
72
  function getSearchHelper(frontendKey) {
61
73
  const adminUrl = urlUtils.getAdminUrl() || urlUtils.getSiteUrl();
62
-
63
- let helper = `<script defer src="${config.get('sodoSearch:url')}" data-sodo-search="${adminUrl}" data-version="${config.get('sodoSearch:version')}" data-key="${frontendKey}" crossorigin="anonymous"></script>`;
74
+ const {scriptUrl, stylesUrl} = getFrontendAppConfig('sodoSearch');
75
+ const attrs = {
76
+ key: frontendKey,
77
+ styles: stylesUrl,
78
+ 'sodo-search': adminUrl
79
+ };
80
+ const dataAttrs = getDataAttributes(attrs);
81
+ let helper = `<script defer src="${scriptUrl}" ${dataAttrs} crossorigin="anonymous"></script>`;
64
82
 
65
83
  return helper;
66
84
  }
@@ -0,0 +1,17 @@
1
+ // # Total Members Helper
2
+ // Usage: `{{total_members}}`
3
+
4
+ const {SafeString} = require('../services/handlebars');
5
+ const {memberCountRounding, getMemberStats} = require('../utils/member-count');
6
+
7
+ module.exports = async function total_members () { //eslint-disable-line
8
+ if (this.total) {
9
+ return new SafeString(memberCountRounding(this.total));
10
+ } else {
11
+ let memberStats = await getMemberStats();
12
+ const {total} = memberStats;
13
+ return new SafeString(total > 0 ? memberCountRounding(total) : 0);
14
+ }
15
+ };
16
+
17
+ module.exports.async = true;
@@ -0,0 +1,16 @@
1
+ // {{total_paid_members}} helper
2
+
3
+ const {SafeString} = require('../services/handlebars');
4
+ const {memberCountRounding, getMemberStats} = require('../utils/member-count');
5
+
6
+ module.exports = async function total_paid_members () { //eslint-disable-line
7
+ if (this.paid) {
8
+ return new SafeString(memberCountRounding(this.paid));
9
+ } else {
10
+ let memberStats = await getMemberStats();
11
+ const {paid} = memberStats;
12
+ return new SafeString(paid > 0 ? memberCountRounding(paid) : 0);
13
+ }
14
+ };
15
+
16
+ module.exports.async = true;
@@ -0,0 +1,33 @@
1
+ const {config} = require('../services/proxy');
2
+
3
+ function getFrontendAppConfig(app) {
4
+ const appVersion = config.get(`${app}:version`);
5
+ let scriptUrl = config.get(`${app}:url`);
6
+ let stylesUrl = config.get(`${app}:styles`);
7
+ if (scriptUrl.includes('{version}')) {
8
+ scriptUrl = scriptUrl.replace('{version}', appVersion);
9
+ }
10
+ if (stylesUrl?.includes('{version}')) {
11
+ stylesUrl = stylesUrl.replace('{version}', appVersion);
12
+ }
13
+ return {
14
+ scriptUrl,
15
+ stylesUrl,
16
+ appVersion
17
+ };
18
+ }
19
+
20
+ function getDataAttributes(data) {
21
+ let dataAttributes = '';
22
+
23
+ if (!data) {
24
+ return dataAttributes;
25
+ }
26
+ Object.entries(data).forEach(([key, value]) => {
27
+ dataAttributes += `data-${key}="${value}" `;
28
+ });
29
+
30
+ return dataAttributes.trim();
31
+ }
32
+
33
+ module.exports = {getFrontendAppConfig, getDataAttributes};
@@ -0,0 +1,50 @@
1
+ const humanNumber = require('human-number');
2
+ const {api} = require('../services/proxy');
3
+
4
+ async function getMemberStats() {
5
+ let memberStats = this.data || await api.stats.memberCountHistory.query();
6
+ const {free, paid, comped} = memberStats.meta.totals;
7
+ let total = free + paid + comped;
8
+ return {free, paid, comped, total};
9
+ }
10
+
11
+ const numberWithCommas = (n) => {
12
+ return n.toLocaleString();
13
+ };
14
+
15
+ const rounding = (n, roundTo) => {
16
+ return Math.floor(n / roundTo) * roundTo;
17
+ };
18
+
19
+ // Rounding https://github.com/TryGhost/Team/issues/1667
20
+ const memberCountRounding = (memberCount) => {
21
+ if (memberCount <= 50) {
22
+ return memberCount;
23
+ }
24
+
25
+ if (memberCount > 50 && memberCount <= 100) {
26
+ return `${numberWithCommas(rounding(memberCount, 10))}+`;
27
+ }
28
+
29
+ if (memberCount > 100 && memberCount <= 1000) {
30
+ return `${numberWithCommas(rounding(memberCount, 50))}+`;
31
+ }
32
+
33
+ if (memberCount > 1000 && memberCount <= 10000) {
34
+ return `${numberWithCommas(rounding(memberCount, 100))}+`;
35
+ }
36
+
37
+ if (memberCount > 10000 && memberCount <= 100000) {
38
+ return `${numberWithCommas(rounding(memberCount, 1000))}+`;
39
+ }
40
+
41
+ if (memberCount > 100000 && memberCount <= 1000000) {
42
+ return `${humanNumber(rounding(memberCount, 10000)).toLowerCase()}+`;
43
+ }
44
+
45
+ if (memberCount > 1000000) {
46
+ return `${humanNumber(rounding(memberCount, 100000)).toLowerCase()}+`;
47
+ }
48
+ };
49
+
50
+ module.exports = {memberCountRounding, getMemberStats};
@@ -13,7 +13,8 @@ function corsOptionsDelegate(req, callback) {
13
13
  const origin = req.header('Origin');
14
14
  const corsOptions = {
15
15
  origin: false, // disallow cross-origin requests by default
16
- credentials: true // required to allow admin-client to login to private sites
16
+ credentials: true, // required to allow admin-client to login to private sites
17
+ maxAge: config.get('caching:cors:maxAge')
17
18
  };
18
19
 
19
20
  if (!origin || origin === 'null') {
@@ -3,6 +3,7 @@ const tpl = require('@tryghost/tpl');
3
3
  const errors = require('@tryghost/errors');
4
4
  const models = require('../../models');
5
5
  const db = require('../../data/db');
6
+ const commentsService = require('../../services/comments');
6
7
  const ALLOWED_INCLUDES = ['post', 'member', 'likes', 'replies'];
7
8
  const UNSAFE_ATTRS = ['status'];
8
9
 
@@ -51,17 +52,8 @@ module.exports = {
51
52
  }
52
53
  },
53
54
  permissions: true,
54
- query(frame) {
55
- return models.Comment.findOne(frame.data, frame.options)
56
- .then((model) => {
57
- if (!model) {
58
- return Promise.reject(new errors.NotFoundError({
59
- message: tpl(messages.commentNotFound)
60
- }));
61
- }
62
-
63
- return model;
64
- });
55
+ async query(frame) {
56
+ return await commentsService.api.getCommentByID(frame.data.id, frame.options);
65
57
  }
66
58
  },
67
59
 
@@ -82,17 +74,21 @@ module.exports = {
82
74
  }
83
75
  },
84
76
  permissions: true,
85
- query(frame) {
86
- return models.Comment.edit(frame.data.comments[0], frame.options)
87
- .then((model) => {
88
- if (!model) {
89
- return Promise.reject(new errors.NotFoundError({
90
- message: tpl(messages.commentNotFound)
91
- }));
92
- }
77
+ async query(frame) {
78
+ if (frame.data.comments[0].status === 'deleted') {
79
+ return await commentsService.api.deleteComment(
80
+ frame.options.id,
81
+ frame?.options?.context?.member?.id,
82
+ frame.options
83
+ );
84
+ }
93
85
 
94
- return model;
95
- });
86
+ return await commentsService.api.editCommentContent(
87
+ frame.options.id,
88
+ frame?.options?.context?.member?.id,
89
+ frame.data.comments[0].html,
90
+ frame.options
91
+ );
96
92
  }
97
93
  },
98
94
 
@@ -115,20 +111,24 @@ module.exports = {
115
111
  permissions: {
116
112
  unsafeAttrs: UNSAFE_ATTRS
117
113
  },
118
- query(frame) {
119
- // TODO: move to comment service
114
+ async query(frame) {
120
115
  const data = frame.data.comments[0];
121
116
 
122
- if (frame.options?.context?.member?.id) {
123
- data.member_id = frame.options.context.member.id;
124
-
125
- // todo: add validation that the parent comment is on the same post, and not deleted
126
- return models.Comment.add(data, frame.options);
127
- } else {
128
- return Promise.reject(new errors.NotFoundError({
129
- message: tpl(messages.memberNotFound)
130
- }));
117
+ if (data.parent_id) {
118
+ return await commentsService.api.replyToComment(
119
+ data.parent_id,
120
+ frame.options.context.member.id,
121
+ data.html,
122
+ frame.options
123
+ );
131
124
  }
125
+
126
+ return await commentsService.api.commentOnPost(
127
+ data.post_id,
128
+ frame.options.context.member.id,
129
+ data.html,
130
+ frame.options
131
+ );
132
132
  }
133
133
  },
134
134
 
@@ -243,5 +243,23 @@ module.exports = {
243
243
  }));
244
244
  }
245
245
  }
246
+ },
247
+
248
+ report: {
249
+ statusCode: 204,
250
+ options: [
251
+ 'id'
252
+ ],
253
+ validation: {},
254
+ permissions: true,
255
+ async query(frame) {
256
+ if (!frame.options?.context?.member?.id) {
257
+ return Promise.reject(new errors.UnauthorizedError({
258
+ message: tpl(messages.memberNotFound)
259
+ }));
260
+ }
261
+
262
+ await commentsService.api.reportComment(frame.options.id, frame.options?.context?.member);
263
+ }
246
264
  }
247
265
  };
@@ -20,8 +20,8 @@ module.exports = {
20
20
  });
21
21
  }
22
22
 
23
- frame.response = {
24
- offers: [offer]
23
+ return {
24
+ data: [offer]
25
25
  };
26
26
  }
27
27
  }
@@ -16,8 +16,8 @@ module.exports = {
16
16
  permissions: true,
17
17
  async query(frame) {
18
18
  const offers = await offersService.api.listOffers(frame.options);
19
- frame.response = {
20
- offers
19
+ return {
20
+ data: offers
21
21
  };
22
22
  }
23
23
  },
@@ -33,8 +33,8 @@ module.exports = {
33
33
  });
34
34
  }
35
35
 
36
- frame.response = {
37
- offers: [offer]
36
+ return {
37
+ data: [offer]
38
38
  };
39
39
  }
40
40
  },
@@ -57,8 +57,8 @@ module.exports = {
57
57
  });
58
58
  }
59
59
 
60
- frame.response = {
61
- offers: [offer]
60
+ return {
61
+ data: [offer]
62
62
  };
63
63
  }
64
64
  },
@@ -70,8 +70,8 @@ module.exports = {
70
70
  },
71
71
  async query(frame) {
72
72
  const offer = await offersService.api.createOffer(frame.data.offers[0]);
73
- frame.response = {
74
- offers: [offer]
73
+ return {
74
+ data: [offer]
75
75
  };
76
76
  }
77
77
  }
@@ -2,17 +2,15 @@ const Promise = require('bluebird');
2
2
  const _ = require('lodash');
3
3
  const models = require('../../models');
4
4
  const routeSettings = require('../../services/route-settings');
5
- const tpl = require('@tryghost/tpl');
6
5
  const {BadRequestError} = require('@tryghost/errors');
7
6
  const settingsService = require('../../services/settings/settings-service');
8
7
  const membersService = require('../../services/members');
9
8
  const stripeService = require('../../services/stripe');
10
-
9
+ const tpl = require('@tryghost/tpl');
11
10
  const settingsBREADService = settingsService.getSettingsBREADServiceInstance();
12
11
 
13
12
  const messages = {
14
13
  failedSendingEmail: 'Failed Sending Email'
15
-
16
14
  };
17
15
 
18
16
  async function getStripeConnectData(frame) {
@@ -59,6 +57,65 @@ module.exports = {
59
57
  }
60
58
  },
61
59
 
60
+ verifyKeyUpdate: {
61
+ headers: {
62
+ cacheInvalidate: true
63
+ },
64
+ permissions: {
65
+ method: 'edit'
66
+ },
67
+ data: [
68
+ 'token'
69
+ ],
70
+ async query(frame) {
71
+ await settingsBREADService.verifyKeyUpdate(frame.data.token);
72
+
73
+ // We need to return all settings here, because we have calculated settings that might change
74
+ const browse = await settingsBREADService.browse(frame.options.context);
75
+
76
+ return browse;
77
+ }
78
+ },
79
+
80
+ /**
81
+ * @deprecated
82
+ */
83
+ updateMembersEmail: {
84
+ statusCode: 204,
85
+ permissions: {
86
+ method: 'edit'
87
+ },
88
+ data: [
89
+ 'email',
90
+ 'type'
91
+ ],
92
+ async query(frame) {
93
+ const {email, type} = frame.data;
94
+
95
+ try {
96
+ // Mapped internally to the newer method of changing emails
97
+ const actionToKeyMapping = {
98
+ supportAddressUpdate: 'members_support_address'
99
+ };
100
+ const edit = {
101
+ key: actionToKeyMapping[type],
102
+ value: email
103
+ };
104
+
105
+ await settingsBREADService.edit([edit], frame.options, null);
106
+ } catch (err) {
107
+ throw new BadRequestError({
108
+ err,
109
+ message: tpl(messages.failedSendingEmail)
110
+ });
111
+ }
112
+ }
113
+ },
114
+
115
+ /**
116
+ * @todo can get removed, since this is moved to verifyKeyUpdate
117
+ * @deprecated: keep to not break existing email verification links, but remove after 1 - 2 releases
118
+ */
62
119
  validateMembersEmailUpdate: {
63
120
  options: [
64
121
  'token',
@@ -108,33 +165,6 @@ module.exports = {
108
165
  }
109
166
  },
110
167
 
111
- updateMembersEmail: {
112
- statusCode: 204,
113
- permissions: {
114
- method: 'edit'
115
- },
116
- data: [
117
- 'email',
118
- 'type'
119
- ],
120
- async query(frame) {
121
- const {email, type} = frame.data;
122
-
123
- try {
124
- // Send magic link to update fromAddress
125
- await membersService.settings.sendEmailAddressUpdateMagicLink({
126
- email,
127
- type
128
- });
129
- } catch (err) {
130
- throw new BadRequestError({
131
- err,
132
- message: tpl(messages.failedSendingEmail)
133
- });
134
- }
135
- }
136
- },
137
-
138
168
  disconnectStripeConnectIntegration: {
139
169
  statusCode: 204,
140
170
  permissions: {
@@ -197,6 +227,8 @@ module.exports = {
197
227
 
198
228
  // We need to return all settings here, because we have calculated settings that might change
199
229
  const browse = await settingsBREADService.browse(frame.options.context);
230
+ browse.meta = result.meta || {};
231
+
200
232
  return browse;
201
233
  }
202
234
  },
@@ -31,6 +31,7 @@ const EDITABLE_SETTINGS = [
31
31
  'default_content_visibility',
32
32
  'default_content_visibility_tiers',
33
33
  'members_signup_access',
34
+ 'members_support_address',
34
35
  'stripe_secret_key',
35
36
  'stripe_publishable_key',
36
37
  'stripe_connect_integration_token',
@@ -18,7 +18,8 @@ module.exports = {
18
18
  'mailgunIsConfigured',
19
19
  'emailAnalytics',
20
20
  'hostSettings',
21
- 'tenor'
21
+ 'tenor',
22
+ 'editor'
22
23
  ];
23
24
 
24
25
  frame.response = {
@@ -125,10 +125,6 @@ module.exports = {
125
125
  return require('./session');
126
126
  },
127
127
 
128
- get offers() {
129
- return require('./offers');
130
- },
131
-
132
128
  get members_stripe_connect() {
133
129
  return require('./members-stripe-connect');
134
130
  }
@@ -0,0 +1,17 @@
1
+ const mapComment = require('./comments');
2
+
3
+ const commentEventMapper = (json, frame) => {
4
+ return {
5
+ ...json,
6
+ data: mapComment(json.data, frame)
7
+ };
8
+ };
9
+
10
+ const activityFeedMapper = (event, frame) => {
11
+ if (event.type === 'comment_event') {
12
+ return commentEventMapper(event, frame);
13
+ }
14
+ return event;
15
+ };
16
+
17
+ module.exports = activityFeedMapper;
@@ -1,4 +1,5 @@
1
1
  const _ = require('lodash');
2
+ const url = require('../utils/url');
2
3
 
3
4
  const commentFields = [
4
5
  'id',
@@ -16,6 +17,13 @@ const memberFields = [
16
17
  'avatar_image'
17
18
  ];
18
19
 
20
+ const postFields = [
21
+ 'id',
22
+ 'uuid',
23
+ 'title',
24
+ 'url'
25
+ ];
26
+
19
27
  const commentMapper = (model, frame) => {
20
28
  const jsonModel = model.toJSON ? model.toJSON(frame.options) : model;
21
29
 
@@ -37,6 +45,16 @@ const commentMapper = (model, frame) => {
37
45
  response.replies = jsonModel.replies.map(reply => commentMapper(reply, frame));
38
46
  }
39
47
 
48
+ if (jsonModel.parent) {
49
+ response.parent = commentMapper(jsonModel.parent, frame);
50
+ }
51
+
52
+ if (jsonModel.post) {
53
+ // We could use the post mapper here, but we need less field + don't need al the async behaviour support
54
+ url.forPost(jsonModel.post.id, jsonModel.post, frame);
55
+ response.post = _.pick(jsonModel.post, postFields);
56
+ }
57
+
40
58
  // todo
41
59
  response.liked = false;
42
60
  if (jsonModel.likes && frame.original.context.member && frame.original.context.member.id) {
@@ -1,5 +1,6 @@
1
1
  module.exports = {
2
2
  actions: require('./actions'),
3
+ activityFeedEvents: require('./activity-feed-events'),
3
4
  authors: require('./authors'),
4
5
  comments: require('./comments'),
5
6
  emails: require('./emails'),
@@ -10,6 +11,7 @@ module.exports = {
10
11
  settings: require('./settings'),
11
12
  snippets: require('./snippets'),
12
13
  tags: require('./tags'),
14
+ offers: require('./offers'),
13
15
  newsletters: require('./newsletters'),
14
16
  users: require('./users')
15
17
  };
@@ -0,0 +1,28 @@
1
+ const utils = require('../../../index');
2
+
3
+ module.exports = (model, frame) => {
4
+ // Offer data is already returned as json via members service
5
+ const jsonModel = model;
6
+
7
+ if (utils.isContentAPI(frame)) {
8
+ const serialized = {
9
+ id: jsonModel.id,
10
+ name: jsonModel.name,
11
+ display_title: jsonModel.display_title,
12
+ display_description: jsonModel.display_description,
13
+ type: jsonModel.type,
14
+ cadence: jsonModel.cadence,
15
+ amount: jsonModel.amount,
16
+ duration: jsonModel.duration,
17
+ duration_in_months: jsonModel.duration_in_months,
18
+ currency_restriction: jsonModel.currency_restriction,
19
+ currency: jsonModel.currency,
20
+ status: jsonModel.status,
21
+ tier: jsonModel.tier
22
+ };
23
+
24
+ return serialized;
25
+ }
26
+
27
+ return jsonModel;
28
+ };