ghost 4.42.1 → 4.44.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 (128) hide show
  1. package/content/themes/casper/package.json +2 -3
  2. package/content/themes/casper/partials/post-card.hbs +1 -1
  3. package/core/built/assets/ghost-dark-470c1ef06b10e5c40ad05f3a642eaaea.css +1 -0
  4. package/core/built/assets/{ghost.min-20096eef632760c3a2906e243adbd24b.js → ghost.min-1e7dce606e92a03207d15ae7eb3d3c23.js} +411 -323
  5. package/core/built/assets/ghost.min-d0c17e8314b5583c0df5d05fab3c051c.css +1 -0
  6. package/core/built/assets/{vendor.min-21f79c68a284acb1b70039f3f63e5507.js → vendor.min-fe2c9b1235b4119b5406b788db2db434.js} +88 -82
  7. package/core/frontend/apps/amp/lib/helpers/amp_analytics.js +1 -1
  8. package/core/frontend/apps/amp/lib/helpers/amp_components.js +1 -1
  9. package/core/frontend/apps/amp/lib/helpers/amp_content.js +1 -1
  10. package/core/frontend/apps/amp/lib/helpers/amp_style.js +1 -1
  11. package/core/frontend/apps/amp/lib/router.js +6 -5
  12. package/core/frontend/apps/private-blogging/lib/helpers/input_password.js +1 -1
  13. package/core/frontend/apps/private-blogging/lib/router.js +2 -2
  14. package/core/frontend/helpers/asset.js +1 -1
  15. package/core/frontend/helpers/author.js +1 -1
  16. package/core/frontend/helpers/authors.js +1 -1
  17. package/core/frontend/helpers/body_class.js +1 -1
  18. package/core/frontend/helpers/cancel_link.js +1 -1
  19. package/core/frontend/helpers/concat.js +1 -1
  20. package/core/frontend/helpers/content.js +1 -1
  21. package/core/frontend/helpers/date.js +1 -1
  22. package/core/frontend/helpers/encode.js +1 -1
  23. package/core/frontend/helpers/excerpt.js +1 -1
  24. package/core/frontend/helpers/facebook_url.js +1 -1
  25. package/core/frontend/helpers/foreach.js +2 -2
  26. package/core/frontend/helpers/get.js +1 -1
  27. package/core/frontend/helpers/ghost_foot.js +1 -1
  28. package/core/frontend/helpers/ghost_head.js +1 -1
  29. package/core/frontend/helpers/lang.js +1 -1
  30. package/core/frontend/helpers/link.js +1 -1
  31. package/core/frontend/helpers/link_class.js +1 -1
  32. package/core/frontend/helpers/match.js +1 -1
  33. package/core/frontend/helpers/navigation.js +1 -1
  34. package/core/frontend/helpers/pagination.js +1 -1
  35. package/core/frontend/helpers/plural.js +1 -1
  36. package/core/frontend/helpers/post_class.js +1 -1
  37. package/core/frontend/helpers/prev_post.js +6 -5
  38. package/core/frontend/helpers/products.js +1 -1
  39. package/core/frontend/helpers/reading_time.js +2 -2
  40. package/core/frontend/helpers/t.js +1 -1
  41. package/core/frontend/helpers/tags.js +1 -1
  42. package/core/frontend/helpers/tiers.js +1 -1
  43. package/core/frontend/helpers/title.js +1 -1
  44. package/core/frontend/helpers/twitter_url.js +1 -1
  45. package/core/frontend/helpers/url.js +1 -1
  46. package/core/frontend/meta/url.js +4 -4
  47. package/core/{server/data/schema → frontend/services/data}/checks.js +4 -4
  48. package/core/frontend/services/{routing/helpers → data}/entry-lookup.js +3 -3
  49. package/core/frontend/services/{routing/helpers → data}/fetch-data.js +3 -3
  50. package/core/frontend/services/data/index.js +5 -0
  51. package/core/frontend/services/{rendering.js → handlebars.js} +2 -1
  52. package/core/frontend/services/helpers/handlebars.js +1 -1
  53. package/core/frontend/services/proxy.js +2 -4
  54. package/core/frontend/services/{routing/helpers → rendering}/context.js +0 -0
  55. package/core/frontend/services/{routing/helpers → rendering}/error.js +0 -0
  56. package/core/frontend/services/{routing/helpers → rendering}/format-response.js +1 -1
  57. package/core/frontend/services/{routing/helpers → rendering}/index.js +0 -8
  58. package/core/frontend/services/{routing/helpers → rendering}/render-entries.js +1 -1
  59. package/core/frontend/services/{routing/helpers → rendering}/render-entry.js +1 -1
  60. package/core/frontend/services/{routing/helpers → rendering}/renderer.js +1 -1
  61. package/core/frontend/services/{routing/helpers → rendering}/secure.js +0 -0
  62. package/core/frontend/services/{routing/helpers → rendering}/templates.js +2 -2
  63. package/core/frontend/services/routing/CollectionRouter.js +1 -1
  64. package/core/frontend/services/routing/controllers/channel.js +9 -9
  65. package/core/frontend/services/routing/controllers/collection.js +9 -9
  66. package/core/frontend/services/routing/controllers/email-post.js +5 -6
  67. package/core/frontend/services/routing/controllers/entry.js +6 -6
  68. package/core/frontend/services/routing/controllers/preview.js +5 -6
  69. package/core/frontend/services/routing/controllers/rss.js +4 -3
  70. package/core/frontend/services/routing/controllers/static.js +5 -5
  71. package/core/frontend/services/routing/controllers/unsubscribe.js +2 -2
  72. package/core/frontend/services/routing/index.js +0 -4
  73. package/core/frontend/web/middleware/error-handler.js +2 -2
  74. package/core/server/api/canary/authentication.js +2 -2
  75. package/core/server/api/canary/posts.js +1 -0
  76. package/core/server/api/canary/stats.js +9 -0
  77. package/core/server/api/canary/utils/serializers/output/members.js +8 -0
  78. package/core/server/api/canary/utils/validators/input/index.js +6 -0
  79. package/core/server/api/shared/http.js +52 -51
  80. package/core/server/data/exporter/table-lists.js +1 -0
  81. package/core/server/data/migrations/utils.js +33 -1
  82. package/core/server/data/migrations/versions/4.42/2022-03-21-17-17-add.js +5 -0
  83. package/core/server/data/migrations/versions/4.43/2022-03-28-19-26-recreate-newsletter-table.js +29 -0
  84. package/core/server/data/migrations/versions/4.43/2022-03-29-14-45-add-members-newsletters-table.js +7 -0
  85. package/core/server/data/migrations/versions/4.43/2022-04-01-10-13-add-post-newsletter-relation.js +108 -0
  86. package/core/server/data/migrations/versions/4.43/2022-04-06-09-47-add-type-column-to-paid-subscription-events.js +7 -0
  87. package/core/server/data/migrations/versions/4.43/2022-04-06-14-56-add-email-newsletter-relation.js +8 -0
  88. package/core/server/data/migrations/versions/4.43/2022-04-08-10-45-add-subscription-id-to-mrr-events.js +7 -0
  89. package/core/server/data/migrations/versions/4.44/2022-04-06-15-22-populate-type-column-for-paid-subscription-events.js +21 -0
  90. package/core/server/data/migrations/versions/4.44/2022-04-08-11-54-add-cancelled-events.js +51 -0
  91. package/core/server/data/migrations/versions/4.44/2022-04-11-08-24-add-newsletter-permissions.js +33 -0
  92. package/core/server/data/migrations/versions/4.44/2022-04-11-10-54-add-mrr-to-subscriptions.js +8 -0
  93. package/core/server/data/migrations/versions/4.44/2022-04-12-07-33-fill-mrr.js +29 -0
  94. package/core/server/data/migrations/versions/4.44/2022-04-13-12-00-remove-newsletter-sender-name-not-null-constraint.js +33 -0
  95. package/core/server/data/migrations/versions/4.44/2022-04-15-07-53-add-offer-id-to-subscriptions.js +9 -0
  96. package/core/server/data/schema/commands.js +6 -1
  97. package/core/server/data/schema/fixtures/fixtures.json +26 -1
  98. package/core/server/data/schema/index.js +0 -1
  99. package/core/server/data/schema/schema.js +36 -16
  100. package/core/server/models/base/bookshelf.js +1 -1
  101. package/core/server/models/base/plugins/crud.js +8 -0
  102. package/core/server/models/member.js +21 -1
  103. package/core/server/models/newsletter.js +42 -1
  104. package/core/server/models/post.js +12 -2
  105. package/core/server/models/stripe-customer-subscription.js +4 -0
  106. package/core/server/services/auth/setup.js +21 -8
  107. package/core/server/services/mega/mega.js +3 -1
  108. package/core/server/services/members/api.js +3 -1
  109. package/core/server/services/members/middleware.js +13 -3
  110. package/core/server/services/members/service.js +3 -11
  111. package/core/server/services/members/utils.js +13 -1
  112. package/core/server/services/newsletters/index.js +10 -0
  113. package/core/server/services/newsletters/service.js +24 -0
  114. package/core/server/services/posts/posts-service.js +20 -1
  115. package/core/server/services/slack.js +11 -3
  116. package/core/server/services/stats/lib/members-stats-service.js +30 -34
  117. package/core/server/services/stats/lib/mrr-stats-service.js +154 -0
  118. package/core/server/services/stats/service.js +3 -1
  119. package/core/server/services/stripe/service.js +1 -0
  120. package/core/server/web/admin/views/default-prod.html +4 -4
  121. package/core/server/web/admin/views/default.html +4 -4
  122. package/core/server/web/api/canary/admin/routes.js +1 -0
  123. package/core/shared/config/defaults.json +2 -2
  124. package/package.json +39 -39
  125. package/yarn.lock +410 -369
  126. package/content/themes/casper/assets/css/csscomb.json +0 -240
  127. package/core/built/assets/ghost-dark-a93afb20027060d760ac6d78f115a76f.css +0 -1
  128. package/core/built/assets/ghost.min-ce35ef1b76d9a943ab912c076773b132.css +0 -1
@@ -1,4 +1,4 @@
1
- const debug = require('@tryghost/debug')('services:routing:helpers:render-entries');
1
+ const debug = require('@tryghost/debug')('services:routing:renderer:render-entries');
2
2
  const formatResponse = require('./format-response');
3
3
  const renderer = require('./renderer');
4
4
 
@@ -1,4 +1,4 @@
1
- const debug = require('@tryghost/debug')('services:routing:helpers:render-post');
1
+ const debug = require('@tryghost/debug')('services:routing:renderer:render-post');
2
2
  const formatResponse = require('./format-response');
3
3
  const renderer = require('./renderer');
4
4
  /**
@@ -1,4 +1,4 @@
1
- const debug = require('@tryghost/debug')('services:routing:helpers:renderer');
1
+ const debug = require('@tryghost/debug')('services:routing:renderer:renderer');
2
2
  const setContext = require('./context');
3
3
  const templates = require('./templates');
4
4
 
@@ -6,8 +6,8 @@ const _ = require('lodash');
6
6
 
7
7
  const path = require('path');
8
8
  const url = require('url');
9
- const config = require('../../../../shared/config');
10
- const themeEngine = require('../../theme-engine');
9
+ const config = require('../../../shared/config');
10
+ const themeEngine = require('../theme-engine');
11
11
  const _private = {};
12
12
 
13
13
  /**
@@ -30,7 +30,7 @@ class CollectionRouter extends ParentRouter {
30
30
  value: object.permalink
31
31
  };
32
32
 
33
- // @NOTE: see helpers/templates - we use unshift to prepend the templates
33
+ // @NOTE: see renderer/templates - we use unshift to prepend the templates
34
34
  this.templates = (object.templates || []).reverse();
35
35
 
36
36
  this.filter = object.filter;
@@ -4,7 +4,8 @@ const tpl = require('@tryghost/tpl');
4
4
  const errors = require('@tryghost/errors');
5
5
  const security = require('@tryghost/security');
6
6
  const themeEngine = require('../../theme-engine');
7
- const helpers = require('../helpers');
7
+ const dataService = require('../../data');
8
+ const renderer = require('../../rendering');
8
9
 
9
10
  const messages = {
10
11
  pageNotFound: 'Page not found.'
@@ -50,7 +51,7 @@ module.exports = function channelController(req, res, next) {
50
51
  }
51
52
  }
52
53
 
53
- return helpers.fetchData(pathOptions, res.routerOptions, res.locals)
54
+ return dataService.fetchData(pathOptions, res.routerOptions, res.locals)
54
55
  .then(function handleResult(result) {
55
56
  // CASE: requested page is greater than number of pages we have
56
57
  if (pathOptions.page > result.meta.pagination.pages) {
@@ -60,16 +61,15 @@ module.exports = function channelController(req, res, next) {
60
61
  }
61
62
 
62
63
  // Format data 1
63
- // @TODO: See helpers/secure for explanation.
64
- helpers.secure(req, result.posts);
64
+ // @TODO: See renderer/secure for explanation.
65
+ renderer.secure(req, result.posts);
65
66
 
66
- // @TODO: See helpers/secure for explanation.
67
+ // @TODO: See renderer/secure for explanation.
67
68
  _.each(result.data, function (data) {
68
- helpers.secure(req, data);
69
+ renderer.secure(req, data);
69
70
  });
70
71
 
71
- const renderer = helpers.renderEntries(req, res);
72
- return renderer(result);
72
+ return renderer.renderEntries(req, res)(result);
73
73
  })
74
- .catch(helpers.handleError(next));
74
+ .catch(renderer.handleError(next));
75
75
  };
@@ -5,7 +5,8 @@ const errors = require('@tryghost/errors');
5
5
  const security = require('@tryghost/security');
6
6
  const {routerManager} = require('../');
7
7
  const themeEngine = require('../../theme-engine');
8
- const helpers = require('../helpers');
8
+ const renderer = require('../../rendering');
9
+ const dataService = require('../../data');
9
10
 
10
11
  const messages = {
11
12
  pageNotFound: 'Page not found.'
@@ -49,7 +50,7 @@ module.exports = function collectionController(req, res, next) {
49
50
  }
50
51
 
51
52
  debug('fetching data');
52
- return helpers.fetchData(pathOptions, res.routerOptions, res.locals)
53
+ return dataService.fetchData(pathOptions, res.routerOptions, res.locals)
53
54
  .then(function handleResult(result) {
54
55
  // CASE: requested page is greater than number of pages we have
55
56
  if (pathOptions.page > result.meta.pagination.pages) {
@@ -80,16 +81,15 @@ module.exports = function collectionController(req, res, next) {
80
81
  });
81
82
 
82
83
  // Format data 1
83
- // @TODO: See helpers/secure for explanation.
84
- helpers.secure(req, result.posts);
84
+ // @TODO: See renderer/secure for explanation.
85
+ renderer.secure(req, result.posts);
85
86
 
86
- // @TODO: See helpers/secure for explanation.
87
+ // @TODO: See renderer/secure for explanation.
87
88
  _.each(result.data, function (data) {
88
- helpers.secure(req, data);
89
+ renderer.secure(req, data);
89
90
  });
90
91
 
91
- const renderer = helpers.renderEntries(req, res);
92
- return renderer(result);
92
+ return renderer.renderEntries(req, res)(result);
93
93
  })
94
- .catch(helpers.handleError(next));
94
+ .catch(renderer.handleError(next));
95
95
  };
@@ -2,7 +2,7 @@ const debug = require('@tryghost/debug')('services:routing:controllers:emailpost
2
2
  const config = require('../../../../shared/config');
3
3
  const {routerManager} = require('../');
4
4
  const urlUtils = require('../../../../shared/url-utils');
5
- const helpers = require('../helpers');
5
+ const renderer = require('../../rendering');
6
6
 
7
7
  /**
8
8
  * @description Email Post Controller.
@@ -55,11 +55,10 @@ module.exports = function emailPostController(req, res, next) {
55
55
  post.access = !!post.html;
56
56
  }
57
57
 
58
- // @TODO: See helpers/secure
59
- helpers.secure(req, post);
58
+ // @TODO: See renderer/secure
59
+ renderer.secure(req, post);
60
60
 
61
- const renderer = helpers.renderEntry(req, res);
62
- return renderer(post);
61
+ return renderer.renderEntry(req, res)(post);
63
62
  })
64
- .catch(helpers.handleError(next));
63
+ .catch(renderer.handleError(next));
65
64
  };
@@ -3,7 +3,8 @@ const url = require('url');
3
3
  const config = require('../../../../shared/config');
4
4
  const {routerManager} = require('../');
5
5
  const urlUtils = require('../../../../shared/url-utils');
6
- const helpers = require('../helpers');
6
+ const dataService = require('../../data');
7
+ const renderer = require('../../rendering');
7
8
 
8
9
  /**
9
10
  * @description Entry controller.
@@ -15,7 +16,7 @@ const helpers = require('../helpers');
15
16
  module.exports = function entryController(req, res, next) {
16
17
  debug('entryController', res.routerOptions);
17
18
 
18
- return helpers.entryLookup(req.path, res.routerOptions, res.locals)
19
+ return dataService.entryLookup(req.path, res.routerOptions, res.locals)
19
20
  .then(function then(lookup) {
20
21
  // Format data 1
21
22
  const entry = lookup ? lookup.entry : false;
@@ -83,10 +84,9 @@ module.exports = function entryController(req, res, next) {
83
84
  }));
84
85
  }
85
86
 
86
- helpers.secure(req, entry);
87
+ renderer.secure(req, entry);
87
88
 
88
- const renderer = helpers.renderEntry(req, res);
89
- return renderer(entry);
89
+ return renderer.renderEntry(req, res)(entry);
90
90
  })
91
- .catch(helpers.handleError(next));
91
+ .catch(renderer.handleError(next));
92
92
  };
@@ -2,7 +2,7 @@ const debug = require('@tryghost/debug')('services:routing:controllers:preview')
2
2
  const config = require('../../../../shared/config');
3
3
  const {routerManager} = require('../');
4
4
  const urlUtils = require('../../../../shared/url-utils');
5
- const helpers = require('../helpers');
5
+ const renderer = require('../../rendering');
6
6
 
7
7
  /**
8
8
  * @description Preview Controller.
@@ -57,11 +57,10 @@ module.exports = function previewController(req, res, next) {
57
57
  post.access = !!post.html;
58
58
  }
59
59
 
60
- // @TODO: See helpers/secure
61
- helpers.secure(req, post);
60
+ // @TODO: See renderer/secure
61
+ renderer.secure(req, post);
62
62
 
63
- const renderer = helpers.renderEntry(req, res);
64
- return renderer(post);
63
+ return renderer.renderEntry(req, res)(post);
65
64
  })
66
- .catch(helpers.handleError(next));
65
+ .catch(renderer.handleError(next));
67
66
  };
@@ -4,7 +4,8 @@ const url = require('url');
4
4
  const security = require('@tryghost/security');
5
5
  const settingsCache = require('../../../../shared/settings-cache');
6
6
  const rssService = require('../../rss');
7
- const helpers = require('../helpers');
7
+ const renderer = require('../../rendering');
8
+ const dataService = require('../../data');
8
9
 
9
10
  // @TODO: is this really correct? Should we be using meta data title?
10
11
  function getTitle(relatedData) {
@@ -35,7 +36,7 @@ module.exports = function rssController(req, res, next) {
35
36
  // @TODO: This belongs to the rss service O_o
36
37
  const baseUrl = url.parse(req.originalUrl).pathname;
37
38
 
38
- helpers.fetchData(pathOptions, res.routerOptions, res.locals)
39
+ dataService.fetchData(pathOptions, res.routerOptions, res.locals)
39
40
  .then(function formatResult(result) {
40
41
  const response = _.pick(result, ['posts', 'meta']);
41
42
 
@@ -47,5 +48,5 @@ module.exports = function rssController(req, res, next) {
47
48
  .then(function (data) {
48
49
  return rssService.render(res, baseUrl, data);
49
50
  })
50
- .catch(helpers.handleError(next));
51
+ .catch(renderer.handleError(next));
51
52
  };
@@ -1,7 +1,7 @@
1
1
  const _ = require('lodash');
2
2
  const Promise = require('bluebird');
3
3
  const debug = require('@tryghost/debug')('services:routing:controllers:static');
4
- const helpers = require('../helpers');
4
+ const renderer = require('../../rendering');
5
5
 
6
6
  function processQuery(query, locals) {
7
7
  const api = require('../../proxy').api[locals.apiVersion];
@@ -60,12 +60,12 @@ module.exports = function staticController(req, res, next) {
60
60
  });
61
61
  }
62
62
 
63
- // @TODO: See helpers/secure for more context.
63
+ // @TODO: See renderer/secure for more context.
64
64
  _.each(response.data, function (data) {
65
- helpers.secure(req, data);
65
+ renderer.secure(req, data);
66
66
  });
67
67
 
68
- helpers.renderer(req, res, helpers.formatResponse.entries(response));
68
+ renderer.renderer(req, res, renderer.formatResponse.entries(response));
69
69
  })
70
- .catch(helpers.handleError(next));
70
+ .catch(renderer.handleError(next));
71
71
  };
@@ -1,7 +1,7 @@
1
1
  const debug = require('@tryghost/debug')('services:routing:controllers:unsubscribe');
2
2
  const path = require('path');
3
3
  const megaService = require('../../../../server/services/mega');
4
- const helpers = require('../../../services/routing/helpers');
4
+ const renderer = require('../../rendering');
5
5
 
6
6
  module.exports = async function unsubscribeController(req, res) {
7
7
  debug('unsubscribeController');
@@ -22,5 +22,5 @@ module.exports = async function unsubscribeController(req, res) {
22
22
  defaultTemplate: path.resolve(__dirname, '../../../views/', templateName)
23
23
  };
24
24
 
25
- return helpers.renderer(req, res, data);
25
+ return renderer.renderer(req, res, data);
26
26
  };
@@ -7,9 +7,5 @@ module.exports = {
7
7
 
8
8
  get registry() {
9
9
  return registry;
10
- },
11
-
12
- get helpers() {
13
- return require('./helpers');
14
10
  }
15
11
  };
@@ -4,7 +4,7 @@ const tpl = require('@tryghost/tpl');
4
4
  const sentry = require('../../../shared/sentry');
5
5
 
6
6
  const config = require('../../../shared/config');
7
- const helpers = require('../../services/routing/helpers');
7
+ const renderer = require('../../services/rendering');
8
8
 
9
9
  // @TODO: make this properly shared code
10
10
  const {prepareError, prepareStack} = require('@tryghost/mw-error-handler');
@@ -55,7 +55,7 @@ const themeErrorRenderer = (err, req, res, next) => {
55
55
 
56
56
  // Template
57
57
  // @TODO: very dirty !!!!!!
58
- helpers.templates.setTemplate(req, res);
58
+ renderer.templates.setTemplate(req, res);
59
59
 
60
60
  // It can be that something went wrong with the theme or otherwise loading handlebars
61
61
  // This ensures that no matter what res.render will work here
@@ -51,14 +51,14 @@ module.exports = {
51
51
  })
52
52
  .then((data) => {
53
53
  try {
54
- return auth.setup.doFixtures(data, api.products);
54
+ return auth.setup.doFixtures(data);
55
55
  } catch (e) {
56
56
  return data;
57
57
  }
58
58
  })
59
59
  .then((data) => {
60
60
  try {
61
- return auth.setup.doProduct(data, api.products);
61
+ return auth.setup.doProductAndNewsletter(data, api);
62
62
  } catch (e) {
63
63
  return data;
64
64
  }
@@ -129,6 +129,7 @@ module.exports = {
129
129
  'formats',
130
130
  'source',
131
131
  'email_recipient_filter',
132
+ 'newsletter_id',
132
133
  'send_email_when_published',
133
134
  'force_rerender',
134
135
  // NOTE: only for internal context
@@ -10,5 +10,14 @@ module.exports = {
10
10
  async query() {
11
11
  return await statsService.members.getCountHistory();
12
12
  }
13
+ },
14
+ mrr: {
15
+ permissions: {
16
+ docName: 'members',
17
+ method: 'browse'
18
+ },
19
+ async query() {
20
+ return await statsService.mrr.getHistory();
21
+ }
13
22
  }
14
23
  };
@@ -1,6 +1,7 @@
1
1
  //@ts-check
2
2
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:members');
3
3
  const {unparse} = require('@tryghost/members-csv');
4
+ const labsService = require('../../../../../../shared/labs');
4
5
 
5
6
  module.exports = {
6
7
  hasActiveStripeSubscriptions: createSerializer('hasActiveStripeSubscriptions', passthrough),
@@ -131,6 +132,13 @@ function serializeMember(member, options) {
131
132
  serialized.products = json.products;
132
133
  }
133
134
 
135
+ if (json.newsletters && labsService.isSet('multipleNewsletters')) {
136
+ json.newsletters.sort((a, b) => {
137
+ return a.sort_order - b.sort_order;
138
+ });
139
+ serialized.newsletters = json.newsletters;
140
+ }
141
+
134
142
  return serialized;
135
143
  }
136
144
 
@@ -1,3 +1,9 @@
1
+ // ESLint Override Notice
2
+ // This is a valid index.js file - it just exports a lot of stuff!
3
+ // Long term we would like to change the API architecture to reduce this file,
4
+ // but that's not the problem the index.js max - line eslint "proxy" rule is there to solve.
5
+ /* eslint-disable max-lines */
6
+
1
7
  module.exports = {
2
8
  get passwordreset() {
3
9
  return require('./passwordreset');
@@ -13,7 +13,7 @@ const models = require('../../models');
13
13
  * @return {Function}
14
14
  */
15
15
  const http = (apiImpl) => {
16
- return (req, res, next) => {
16
+ return async (req, res, next) => {
17
17
  debug(`External API request to ${req.url}`);
18
18
  let apiKey = null;
19
19
  let integration = null;
@@ -60,69 +60,70 @@ const http = (apiImpl) => {
60
60
  data: apiImpl.data
61
61
  });
62
62
 
63
- apiImpl(frame)
64
- .then((result) => {
65
- debug(`External API request to ${frame.docName}.${frame.method}`);
66
- return shared.headers.get(result, apiImpl.headers, frame)
67
- .then(headers => ({result, headers}));
68
- })
69
- .then(({result, headers}) => {
70
- // CASE: api ctrl wants to handle the express response (e.g. streams)
71
- if (typeof result === 'function') {
72
- debug('ctrl function call');
73
- return result(req, res, next);
74
- }
63
+ try {
64
+ const result = await apiImpl(frame);
75
65
 
76
- let statusCode = 200;
77
- if (typeof apiImpl.statusCode === 'function') {
78
- statusCode = apiImpl.statusCode(result);
79
- } else if (apiImpl.statusCode) {
80
- statusCode = apiImpl.statusCode;
81
- }
66
+ debug(`External API request to ${frame.docName}.${frame.method}`);
67
+ const headers = await shared.headers.get(result, apiImpl.headers, frame) || {};
82
68
 
83
- res.status(statusCode);
69
+ // CASE: api ctrl wants to handle the express response (e.g. streams)
70
+ if (typeof result === 'function') {
71
+ debug('ctrl function call');
72
+ return result(req, res, next);
73
+ }
84
74
 
85
- // CASE: generate headers based on the api ctrl configuration
86
- res.set(headers);
75
+ let statusCode = 200;
76
+ if (typeof apiImpl.statusCode === 'function') {
77
+ statusCode = apiImpl.statusCode(result);
78
+ } else if (apiImpl.statusCode) {
79
+ statusCode = apiImpl.statusCode;
80
+ }
87
81
 
88
- const send = (format) => {
89
- if (format === 'plain') {
90
- debug('plain text response');
91
- return res.send(result);
92
- }
82
+ res.status(statusCode);
93
83
 
94
- debug('json response');
95
- res.json(result || {});
96
- };
84
+ // CASE: generate headers based on the api ctrl configuration
85
+ if (req && req.headers && req.headers['accept-version'] && res.locals) {
86
+ headers['Content-Version'] = `v${res.locals.safeVersion}`;
87
+ }
88
+ res.set(headers);
97
89
 
98
- let responseFormat;
90
+ const send = (format) => {
91
+ if (format === 'plain') {
92
+ debug('plain text response');
93
+ return res.send(result);
94
+ }
99
95
 
100
- if (apiImpl.response){
101
- if (typeof apiImpl.response.format === 'function') {
102
- const apiResponseFormat = apiImpl.response.format();
96
+ debug('json response');
97
+ res.json(result || {});
98
+ };
99
+
100
+ let responseFormat;
101
+
102
+ if (apiImpl.response){
103
+ if (typeof apiImpl.response.format === 'function') {
104
+ const apiResponseFormat = apiImpl.response.format();
103
105
 
104
- if (apiResponseFormat.then) { // is promise
105
- return apiResponseFormat.then((formatName) => {
106
- send(formatName);
107
- });
108
- } else {
109
- responseFormat = apiResponseFormat;
110
- }
106
+ if (apiResponseFormat.then) { // is promise
107
+ return apiResponseFormat.then((formatName) => {
108
+ send(formatName);
109
+ });
111
110
  } else {
112
- responseFormat = apiImpl.response.format;
111
+ responseFormat = apiResponseFormat;
113
112
  }
113
+ } else {
114
+ responseFormat = apiImpl.response.format;
114
115
  }
116
+ }
115
117
 
116
- send(responseFormat);
117
- })
118
- .catch((err) => {
119
- req.frameOptions = {
120
- docName: frame.docName,
121
- method: frame.method
122
- };
118
+ send(responseFormat);
119
+ } catch (err) {
120
+ req.frameOptions = {
121
+ docName: frame.docName,
122
+ method: frame.method
123
+ };
123
124
 
124
- next(err);
125
- });
125
+ next(err);
126
+ }
126
127
  };
127
128
  };
128
129
 
@@ -40,6 +40,7 @@ const BACKUP_TABLES = [
40
40
  'members_paid_subscription_events',
41
41
  'members_subscribe_events',
42
42
  'members_product_events',
43
+ 'members_newsletters',
43
44
  'offers',
44
45
  'offer_redemptions'
45
46
  ];
@@ -65,6 +65,36 @@ function dropTables(names) {
65
65
  );
66
66
  }
67
67
 
68
+ /**
69
+ * Creates a migration which will drop an existing table and then re-add a new table based on provided spec
70
+ * @param {string} name - table name
71
+ * @param {Object} tableSpec - copy of table schema definition as defined in schema.js at the moment of writing the migration,
72
+ * this parameter MUST be present, otherwise @daniellockyer will hunt you down
73
+ *
74
+ * @returns {Object} migration object returning config/up/down properties
75
+ */
76
+ function recreateTable(name, tableSpec) {
77
+ return createNonTransactionalMigration(
78
+ async function up(connection) {
79
+ const exists = await connection.schema.hasTable(name);
80
+
81
+ if (!exists) {
82
+ logging.warn(`Failed to drop table: ${name} - table does not exist`);
83
+ } else {
84
+ logging.info(`Dropping table: ${name}`);
85
+ await commands.deleteTable(name, connection);
86
+ logging.info(`Re-adding table: ${name}`);
87
+ await commands.createTable(name, connection, tableSpec);
88
+ }
89
+ },
90
+ async function down() {
91
+ // noop: we cannot go back to old table schema
92
+ logging.warn(`Ignoring rollback for table recreate: ${name}`);
93
+ return Promise.resolve();
94
+ }
95
+ );
96
+ }
97
+
68
98
  /**
69
99
  * Creates a migration which will add a permission to the database
70
100
  *
@@ -375,7 +405,8 @@ function createAddColumnMigration(table, column, columnDefinition) {
375
405
  column,
376
406
  dbIsInCorrectState: hasColumn => hasColumn === false,
377
407
  operation: commands.dropColumn,
378
- operationVerb: 'Removing'
408
+ operationVerb: 'Removing',
409
+ columnDefinition
379
410
  })
380
411
  );
381
412
  }
@@ -462,6 +493,7 @@ function addSetting({key, value, type, group}) {
462
493
  module.exports = {
463
494
  addTable,
464
495
  dropTables,
496
+ recreateTable,
465
497
  addPermission,
466
498
  addPermissionToRole,
467
499
  addPermissionWithRoles,
@@ -1,3 +1,8 @@
1
+ // ESLint Override Notice
2
+ // This file was named incorrectly and it didn't flag up in our eslint rules.
3
+ // The ESLint match-regex rule has now been updated to catch this, but this file has to be excluded.
4
+ /* eslint-disable ghost/filenames/match-regex */
5
+
1
6
  const {addTable} = require('../../utils');
2
7
 
3
8
  module.exports = addTable('newsletters', {
@@ -0,0 +1,29 @@
1
+ const {recreateTable} = require('../../utils');
2
+
3
+ module.exports = recreateTable('newsletters', {
4
+ id: {type: 'string', maxlength: 24, nullable: false, primary: true},
5
+ name: {type: 'string', maxlength: 191, nullable: false, unique: true},
6
+ description: {type: 'string', maxlength: 2000, nullable: true},
7
+ slug: {type: 'string', maxlength: 191, nullable: false, unique: true},
8
+ sender_name: {type: 'string', maxlength: 191, nullable: false},
9
+ sender_email: {type: 'string', maxlength: 191, nullable: true},
10
+ sender_reply_to: {type: 'string', maxlength: 191, nullable: false, defaultTo: 'newsletter', validations: {isIn: [['newsletter', 'support']]}},
11
+ status: {type: 'string', maxlength: 50, nullable: false, defaultTo: 'active'},
12
+ visibility: {
13
+ type: 'string',
14
+ maxlength: 50,
15
+ nullable: false,
16
+ defaultTo: 'members'
17
+ },
18
+ subscribe_on_signup: {type: 'bool', nullable: false, defaultTo: true},
19
+ sort_order: {type: 'integer', nullable: false, unsigned: true, defaultTo: 0},
20
+ header_image: {type: 'string', maxlength: 2000, nullable: true},
21
+ show_header_icon: {type: 'bool', nullable: false, defaultTo: true},
22
+ show_header_title: {type: 'bool', nullable: false, defaultTo: true},
23
+ title_font_category: {type: 'string', maxlength: 191, nullable: false, defaultTo: 'sans_serif', validations: {isIn: [['serif', 'sans_serif']]}},
24
+ title_alignment: {type: 'string', maxlength: 191, nullable: false, defaultTo: 'center', validations: {isIn: [['center', 'left']]}},
25
+ show_feature_image: {type: 'bool', nullable: false, defaultTo: true},
26
+ body_font_category: {type: 'string', maxlength: 191, nullable: false, defaultTo: 'sans_serif', validations: {isIn: [['serif', 'sans_serif']]}},
27
+ footer_content: {type: 'text', maxlength: 1000000000, nullable: true},
28
+ show_badge: {type: 'bool', nullable: false, defaultTo: true}
29
+ });
@@ -0,0 +1,7 @@
1
+ const {addTable} = require('../../utils');
2
+
3
+ module.exports = addTable('members_newsletters', {
4
+ id: {type: 'string', maxlength: 24, nullable: false, primary: true},
5
+ member_id: {type: 'string', maxlength: 24, nullable: false, references: 'members.id', cascadeDelete: true},
6
+ newsletter_id: {type: 'string', maxlength: 24, nullable: false, references: 'newsletters.id', cascadeDelete: true}
7
+ });