ghost 4.37.0 → 4.39.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 (125) hide show
  1. package/.c8rc.json +1 -1
  2. package/README.md +26 -18
  3. package/content/themes/casper/LICENSE +1 -1
  4. package/content/themes/casper/README.md +1 -1
  5. package/content/themes/casper/assets/built/global.css +1 -1
  6. package/content/themes/casper/assets/built/global.css.map +1 -1
  7. package/content/themes/casper/assets/built/screen.css +1 -1
  8. package/content/themes/casper/assets/built/screen.css.map +1 -1
  9. package/content/themes/casper/assets/css/global.css +14 -6
  10. package/content/themes/casper/assets/css/screen.css +9 -1
  11. package/content/themes/casper/package.json +2 -2
  12. package/content/themes/casper/partials/post-card.hbs +1 -1
  13. package/content/themes/casper/post.hbs +18 -19
  14. package/content/themes/casper/yarn.lock +186 -217
  15. package/core/built/assets/{chunk.3.4906cf0b01d6d8e33374.js → chunk.3.6e2ed2d00856e12bd81a.js} +19 -19
  16. package/core/built/assets/ghost-dark-498ff8339a89bb68c3f78f59bee4146e.css +1 -0
  17. package/core/built/assets/ghost.min-77b93478f83b0def6ddc5a4f23ce963e.css +1 -0
  18. package/core/built/assets/{ghost.min-c1938f6ee696bf08bd6bf93cac341ea2.js → ghost.min-e6559d901897066aa6a6d4145e3728ed.js} +466 -413
  19. package/core/built/assets/icons/{event-changed-subscription.svg → event-subscriptions.svg} +0 -1
  20. package/core/built/assets/icons/eye.svg +4 -1
  21. package/core/built/assets/icons/member-add.svg +3 -0
  22. package/core/built/assets/icons/member.svg +3 -0
  23. package/core/built/assets/icons/pin.svg +4 -1
  24. package/core/built/assets/{vendor.min-6dc30be68238b5c55df0cdc1f2dc8b8d.js → vendor.min-c39476bced9adb98ee2b292d01c7a8f4.js} +2303 -1372
  25. package/core/frontend/apps/private-blogging/lib/middleware.js +1 -1
  26. package/core/frontend/apps/private-blogging/lib/views/private.hbs +9 -10
  27. package/core/frontend/helpers/get.js +4 -0
  28. package/core/frontend/helpers/match.js +12 -0
  29. package/core/frontend/helpers/prev_post.js +11 -1
  30. package/core/frontend/helpers/tiers.js +59 -0
  31. package/core/frontend/helpers/tpl/content-cta.hbs +1 -1
  32. package/core/frontend/public/ghost.css +205 -143
  33. package/core/frontend/services/routing/router-manager.js +1 -1
  34. package/core/frontend/views/unsubscribe.hbs +28 -33
  35. package/core/frontend/web/middleware/error-handler.js +2 -2
  36. package/core/frontend/web/site.js +10 -0
  37. package/core/server/api/canary/authentication.js +7 -0
  38. package/core/server/api/canary/index.js +4 -0
  39. package/core/server/api/canary/members.js +9 -2
  40. package/core/server/api/canary/products.js +3 -6
  41. package/core/server/api/canary/tiers-public.js +34 -0
  42. package/core/server/api/canary/tiers.js +6 -7
  43. package/core/server/api/canary/utils/serializers/input/pages.js +1 -1
  44. package/core/server/api/canary/utils/serializers/input/posts.js +1 -1
  45. package/core/server/api/canary/utils/serializers/output/email-posts.js +7 -1
  46. package/core/server/api/canary/utils/serializers/output/pages.js +9 -2
  47. package/core/server/api/canary/utils/serializers/output/posts.js +8 -2
  48. package/core/server/api/canary/utils/serializers/output/preview.js +7 -1
  49. package/core/server/api/canary/utils/serializers/output/products.js +3 -1
  50. package/core/server/api/canary/utils/serializers/output/tiers.js +4 -2
  51. package/core/server/api/canary/utils/serializers/output/utils/mapper.js +17 -7
  52. package/core/server/api/shared/serializers/handle.js +2 -2
  53. package/core/server/api/v2/utils/serializers/input/pages.js +1 -1
  54. package/core/server/api/v2/utils/serializers/input/posts.js +1 -1
  55. package/core/server/api/v3/utils/serializers/input/pages.js +1 -1
  56. package/core/server/api/v3/utils/serializers/input/posts.js +1 -1
  57. package/core/server/data/db/connection.js +3 -2
  58. package/core/server/data/importer/import-manager.js +152 -113
  59. package/core/server/data/migrations/versions/3.29/01-remove-duplicate-subscriptions.js +2 -1
  60. package/core/server/data/migrations/versions/3.29/02-remove-duplicate-customers.js +2 -1
  61. package/core/server/data/migrations/versions/3.29/03-remove-orphaned-customers.js +2 -1
  62. package/core/server/data/migrations/versions/3.29/04-remove-orphaned-subscriptions.js +2 -1
  63. package/core/server/data/migrations/versions/3.29/05-add-member-constraints.js +3 -2
  64. package/core/server/data/migrations/versions/3.39/06-add-email-recipient-index.js +4 -3
  65. package/core/server/data/migrations/versions/4.0/14-remove-orphaned-stripe-records.js +2 -1
  66. package/core/server/data/migrations/versions/4.0/26-add-cascade-on-delete.js +2 -1
  67. package/core/server/data/migrations/versions/4.0/29-fix-foreign-key-for-members-stripe-customers-subscriptions.js +2 -1
  68. package/core/server/data/migrations/versions/4.1/02-add-unique-constraint-for-member-stripe-tables.js +2 -1
  69. package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +3 -2
  70. package/core/server/data/migrations/versions/4.33/2022-01-14-11-51-add-default-free-tier.js +3 -0
  71. package/core/server/data/migrations/versions/4.33/2022-01-18-09-07-remove-duplicate-offer-redemptions.js +2 -2
  72. package/core/server/data/migrations/versions/4.35/2022-02-01-11-48-update-email-recipient-filter-column-type.js +2 -1
  73. package/core/server/data/migrations/versions/4.35/2022-02-01-12-03-update-recipient-filter-column-type.js +2 -1
  74. package/core/server/data/migrations/versions/4.37/2022-02-21-09-53-backfill-members-last-seen-at-column.js +3 -2
  75. package/core/server/data/migrations/versions/4.38/2022-03-01-08-46-add-visibility-to-tiers.js +11 -0
  76. package/core/server/data/migrations/versions/4.38/2022-03-03-16-12-add-visibility-to-tiers.js +8 -0
  77. package/core/server/data/migrations/versions/4.38/2022-03-03-16-17-drop-tiers-visible-column.js +7 -0
  78. package/core/server/data/migrations/versions/4.39/2022-03-07-10-57-update-free-products-visibility-column.js +66 -0
  79. package/core/server/data/migrations/versions/4.39/2022-03-07-10-57-update-products-visibility-column.js +36 -0
  80. package/core/server/data/schema/clients/index.js +1 -1
  81. package/core/server/data/schema/clients/mysql.js +4 -4
  82. package/core/server/data/schema/commands.js +42 -50
  83. package/core/server/data/schema/default-settings/default-settings.json +2 -2
  84. package/core/server/data/schema/fixtures/fixtures.json +18 -161
  85. package/core/server/data/schema/schema.js +7 -0
  86. package/core/server/frontend/ghost.min.css +1 -1
  87. package/core/server/lib/image/image-size.js +12 -4
  88. package/core/server/models/base/plugins/generate-slug.js +13 -1
  89. package/core/server/models/base/plugins/raw-knex.js +1 -1
  90. package/core/server/models/post.js +16 -6
  91. package/core/server/models/product.js +2 -1
  92. package/core/server/models/user.js +1 -1
  93. package/core/server/services/auth/api-key/admin.js +15 -6
  94. package/core/server/services/auth/setup.js +34 -13
  95. package/core/server/services/email-analytics/lib/event-processor.js +18 -1
  96. package/core/server/services/mega/mega.js +4 -4
  97. package/core/server/services/mega/template.js +1 -1
  98. package/core/server/services/members/content-gating.js +1 -1
  99. package/core/server/services/members/middleware.js +4 -0
  100. package/core/server/services/members/service.js +13 -1
  101. package/core/server/services/posts/posts-service.js +1 -1
  102. package/core/server/services/url/UrlGenerator.js +1 -1
  103. package/core/server/services/webhooks/webhooks-service.js +2 -0
  104. package/core/server/views/maintenance.html +2 -2
  105. package/core/server/web/admin/views/default-prod.html +4 -4
  106. package/core/server/web/admin/views/default.html +4 -4
  107. package/core/server/web/api/app.js +3 -0
  108. package/core/server/web/api/canary/admin/middleware.js +2 -0
  109. package/core/server/web/api/canary/content/routes.js +1 -0
  110. package/core/server/web/members/app.js +1 -1
  111. package/core/server/web/parent/backend.js +2 -1
  112. package/core/server/web/shared/middleware/uncapitalise.js +3 -2
  113. package/core/shared/config/defaults.json +2 -2
  114. package/core/shared/config/utils.js +5 -1
  115. package/core/shared/labs.js +8 -9
  116. package/core/shared/url-utils.js +4 -1
  117. package/package.json +56 -52
  118. package/yarn.lock +809 -607
  119. package/core/built/assets/ghost-dark-d54723f7267e66fa2595f897076e86c2.css +0 -1
  120. package/core/built/assets/ghost.min-02a5f8954bd85fe28817b8c8b111b8aa.css +0 -1
  121. package/core/built/assets/icons/event-started-subscription.svg +0 -6
  122. package/core/built/assets/icons/locked-email-back.svg +0 -1
  123. package/core/built/assets/icons/locked-email-front.svg +0 -1
  124. package/core/built/assets/icons/locked-email-lock.svg +0 -1
  125. package/core/built/assets/img/ghost-logo-de2acf283f53ba1fd1149928faeaaa74.png +0 -0
@@ -5,8 +5,11 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
7
7
  <title>
8
- {{#if member}}Successfully Unsubscribed{{/if}}
9
- {{#if error}}Unsubscribe Failed{{/if}}
8
+ {{#if error}}
9
+ Unsubscribe Failed
10
+ {{else}}
11
+ Successfully Unsubscribed
12
+ {{/if}}
10
13
  </title>
11
14
  <link rel="stylesheet" href="{{asset "public/ghost.css" hasMinFile="true"}}" />
12
15
  </head>
@@ -15,42 +18,34 @@
15
18
  <div class="gh-viewport">
16
19
  <main class="gh-main" role="main">
17
20
  <div class="gh-flow">
18
- <header class="gh-flow-head gh-flow-head-unsubscribe">
19
- <nav class="gh-flow-nav">
20
- <a href="{{@site.url}}" class="gh-flow-back-plain">
21
- <!--?xml version="1.0" encoding="UTF-8" standalone="no"?-->
22
- <svg width="17px" height="27px" viewBox="0 0 17 27" version="1.1"
23
- xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
24
- <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd"
25
- stroke-linecap="round" stroke-linejoin="round">
26
- <g id="Stroke-42" transform="translate(2.000000, 2.000000)" stroke-width="3"
27
- stroke="#7d878a">
28
- <polyline points="13.5401 0.4256 0.3971 11.9256 13.5401 23.4256"></polyline>
29
- </g>
30
- </g>
31
- </svg>
32
- Back to {{@site.title}}
33
- </a>
34
- </nav>
35
- </header>
36
-
37
21
  <div class="gh-flow-content-wrap">
38
- <section class="gh-flow-content gh-flow-content-unsubscribe">
22
+ {{#if @site.icon}}
23
+ <img class="site-icon" src="{{img_url @site.icon absolute="true"}}" layout="fixed">
24
+ {{/if}}
25
+ <section class="gh-flow-content unsubscribe">
39
26
  {{#if error}}
27
+ <h1>Unsubscribe failed.</h1>
40
28
  <p>{{error}}</p>
41
- {{else}}
42
- {{#if member}}
43
- <p>
44
- <span class="gh-flow-em">{{member.email}}</span> has been successfully unsubscribed from emails.
45
- {{#match member.status "!=" "free"}}
46
- <br>
47
- Don't worry, this will not cancel your paid subscription to {{@site.title}}.
48
- {{/match}}
49
- </p>
50
- <p>Didn't mean to do this? Manage your account <a href="{{@site.url}}/#/portal/account">here</a>.</p>
51
- {{/if}}
29
+ {{else if member}}
30
+ <h1>Successfully unsubscribed.</h1>
31
+ <p>
32
+ <span class="gh-flow-em">{{member.email}}</span> will no longer receive this newsletter.
33
+ {{#match member.status "!=" "free"}}
34
+ This will not cancel your paid subscription{{#if @site.title}} to {{@site.title}}{{/if}}.
35
+ {{/match}}
36
+ </p>
52
37
  {{/if}}
53
38
  </section>
39
+ <div class="unsubscribe-footer">
40
+ {{#unless error}}
41
+ <p>Didn't mean to do this? Manage your account <a href="{{@site.url}}/#/portal/account">here</a>.</p>
42
+ {{/unless}}
43
+ {{#if @site.title}}
44
+ <a href="{{@site.url}}">{{@site.title}}</a>
45
+ {{else}}
46
+ <a href="{{@site.url}}">Visit site</a>
47
+ {{/if}}
48
+ </div>
54
49
  </div>
55
50
  </div>
56
51
  </main>
@@ -84,10 +84,10 @@ const themeErrorRenderer = (err, req, res, next) => {
84
84
  };
85
85
 
86
86
  module.exports.handleThemeResponse = [
87
- // Make sure the error can be served
88
- prepareError,
89
87
  // Handle the error in Sentry
90
88
  sentry.errorHandler,
89
+ // Make sure the error can be served
90
+ prepareError,
91
91
  // Render the error using theme template
92
92
  themeErrorRenderer
93
93
  ];
@@ -4,6 +4,8 @@ const express = require('../../shared/express');
4
4
  const cors = require('cors');
5
5
  const {URL} = require('url');
6
6
  const errors = require('@tryghost/errors');
7
+ const DomainEvents = require('@tryghost/domain-events');
8
+ const {MemberPageViewEvent} = require('@tryghost/member-events');
7
9
 
8
10
  // App requires
9
11
  const config = require('../../shared/config');
@@ -171,6 +173,14 @@ module.exports = function setupSiteApp(options = {}) {
171
173
  }
172
174
  });
173
175
 
176
+ siteApp.use(function (req, res, next) {
177
+ if (req.member) {
178
+ // This event needs memberLastSeenAt to avoid doing un-necessary database queries when updating `last_seen_at`
179
+ DomainEvents.dispatch(MemberPageViewEvent.create({url: req.url, memberId: req.member.id, memberLastSeenAt: req.member.last_seen_at}, new Date()));
180
+ }
181
+ next();
182
+ });
183
+
174
184
  debug('General middleware done');
175
185
 
176
186
  router = siteRoutes(options);
@@ -49,6 +49,13 @@ module.exports = {
49
49
 
50
50
  return auth.setup.setupUser(setupDetails);
51
51
  })
52
+ .then((data) => {
53
+ try {
54
+ return auth.setup.doFixtures(data, api.products);
55
+ } catch (e) {
56
+ return data;
57
+ }
58
+ })
52
59
  .then((data) => {
53
60
  try {
54
61
  return auth.setup.doProduct(data, api.products);
@@ -207,5 +207,9 @@ module.exports = {
207
207
 
208
208
  get productsPublic() {
209
209
  return shared.pipeline(require('./products-public'), localUtils, 'content');
210
+ },
211
+
212
+ get tiersPublic() {
213
+ return shared.pipeline(require('./tiers-public'), localUtils, 'content');
210
214
  }
211
215
  };
@@ -54,10 +54,17 @@ module.exports = {
54
54
  'order',
55
55
  'debug',
56
56
  'page',
57
- 'search'
57
+ 'search',
58
+ 'include'
58
59
  ],
59
60
  permissions: true,
60
- validation: {},
61
+ validation: {
62
+ options: {
63
+ include: {
64
+ values: allowedIncludes
65
+ }
66
+ }
67
+ },
61
68
  async query(frame) {
62
69
  const page = await membersService.api.memberBREADService.browse(frame.options);
63
70
 
@@ -93,7 +93,9 @@ module.exports = {
93
93
  options: [
94
94
  'id'
95
95
  ],
96
- headers: {},
96
+ headers: {
97
+ cacheInvalidate: true
98
+ },
97
99
  validation: {
98
100
  options: {
99
101
  id: {
@@ -108,11 +110,6 @@ module.exports = {
108
110
  frame.options
109
111
  );
110
112
 
111
- if (model.wasChanged()) {
112
- this.headers.cacheInvalidate = true;
113
- } else {
114
- this.headers.cacheInvalidate = false;
115
- }
116
113
  return model;
117
114
  }
118
115
  }
@@ -0,0 +1,34 @@
1
+ // NOTE: We must not cache references to membersService.api
2
+ // as it is a getter and may change during runtime.
3
+ const membersService = require('../../services/members');
4
+
5
+ const allowedIncludes = ['monthly_price', 'yearly_price', 'benefits'];
6
+
7
+ module.exports = {
8
+ docName: 'tiers',
9
+
10
+ browse: {
11
+ options: [
12
+ 'limit',
13
+ 'fields',
14
+ 'include',
15
+ 'filter',
16
+ 'order',
17
+ 'debug',
18
+ 'page'
19
+ ],
20
+ permissions: true,
21
+ validation: {
22
+ options: {
23
+ include: {
24
+ values: allowedIncludes
25
+ }
26
+ }
27
+ },
28
+ async query(frame) {
29
+ const page = await membersService.api.productRepository.list(frame.options);
30
+
31
+ return page;
32
+ }
33
+ }
34
+ };
@@ -54,7 +54,9 @@ module.exports = {
54
54
  }
55
55
  }
56
56
  },
57
- permissions: true,
57
+ permissions: {
58
+ docName: 'products'
59
+ },
58
60
  async query(frame) {
59
61
  const model = await membersService.api.productRepository.get(frame.data, frame.options);
60
62
 
@@ -95,7 +97,9 @@ module.exports = {
95
97
  options: [
96
98
  'id'
97
99
  ],
98
- headers: {},
100
+ headers: {
101
+ cacheInvalidate: true
102
+ },
99
103
  validation: {
100
104
  options: {
101
105
  id: {
@@ -112,11 +116,6 @@ module.exports = {
112
116
  frame.options
113
117
  );
114
118
 
115
- if (model.wasChanged()) {
116
- this.headers.cacheInvalidate = true;
117
- } else {
118
- this.headers.cacheInvalidate = false;
119
- }
120
119
  return model;
121
120
  }
122
121
  }
@@ -1,6 +1,6 @@
1
1
  const _ = require('lodash');
2
2
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:input:pages');
3
- const mapNQLKeyValues = require('@nexes/nql').utils.mapKeyValues;
3
+ const mapNQLKeyValues = require('@tryghost/nql').utils.mapKeyValues;
4
4
  const mobiledoc = require('../../../../../lib/mobiledoc');
5
5
  const url = require('./utils/url');
6
6
  const slugFilterOrder = require('./utils/slug-filter-order');
@@ -1,6 +1,6 @@
1
1
  const _ = require('lodash');
2
2
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:input:posts');
3
- const mapNQLKeyValues = require('@nexes/nql').utils.mapKeyValues;
3
+ const mapNQLKeyValues = require('@tryghost/nql').utils.mapKeyValues;
4
4
  const url = require('./utils/url');
5
5
  const slugFilterOrder = require('./utils/slug-filter-order');
6
6
  const localUtils = require('../../index');
@@ -1,9 +1,15 @@
1
1
  const mapper = require('./utils/mapper');
2
2
  const gating = require('./utils/post-gating');
3
+ const membersService = require('../../../../../services/members');
3
4
 
4
5
  module.exports = {
5
6
  async read(model, apiConfig, frame) {
6
- const emailPost = await mapper.mapPost(model, frame);
7
+ const tiersModels = await membersService.api.productRepository.list({
8
+ withRelated: ['monthlyPrice', 'yearlyPrice']
9
+ });
10
+ const tiers = tiersModels.data && tiersModels.data.map(tierModel => tierModel.toJSON());
11
+
12
+ const emailPost = await mapper.mapPost(model, frame, {tiers});
7
13
  gating.forPost(emailPost, frame);
8
14
 
9
15
  frame.response = {
@@ -1,5 +1,6 @@
1
1
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:pages');
2
2
  const mapper = require('./utils/mapper');
3
+ const membersService = require('../../../../../services/members');
3
4
 
4
5
  module.exports = {
5
6
  async all(models, apiConfig, frame) {
@@ -10,9 +11,15 @@ module.exports = {
10
11
  return;
11
12
  }
12
13
  let pages = [];
14
+
15
+ const tiersModels = await membersService.api.productRepository.list({
16
+ withRelated: ['monthlyPrice', 'yearlyPrice']
17
+ });
18
+ const tiers = tiersModels.data ? tiersModels.data.map(tierModel => tierModel.toJSON()) : [];
19
+
13
20
  if (models.meta) {
14
21
  for (let model of models.data) {
15
- let page = await mapper.mapPage(model, frame);
22
+ let page = await mapper.mapPage(model, frame, {tiers});
16
23
  pages.push(page);
17
24
  }
18
25
  frame.response = {
@@ -22,7 +29,7 @@ module.exports = {
22
29
 
23
30
  return;
24
31
  }
25
- let page = await mapper.mapPage(models, frame);
32
+ let page = await mapper.mapPage(models, frame, {tiers});
26
33
  frame.response = {
27
34
  pages: [page]
28
35
  };
@@ -1,5 +1,6 @@
1
1
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:posts');
2
2
  const mapper = require('./utils/mapper');
3
+ const membersService = require('../../../../../services/members');
3
4
 
4
5
  module.exports = {
5
6
  async all(models, apiConfig, frame) {
@@ -10,9 +11,14 @@ module.exports = {
10
11
  return;
11
12
  }
12
13
  let posts = [];
14
+
15
+ const tiersModels = await membersService.api.productRepository.list({
16
+ withRelated: ['monthlyPrice', 'yearlyPrice']
17
+ });
18
+ const tiers = tiersModels.data ? tiersModels.data.map(tierModel => tierModel.toJSON()) : [];
13
19
  if (models.meta) {
14
20
  for (let model of models.data) {
15
- let post = await mapper.mapPost(model, frame);
21
+ let post = await mapper.mapPost(model, frame, {tiers});
16
22
  posts.push(post);
17
23
  }
18
24
  frame.response = {
@@ -22,7 +28,7 @@ module.exports = {
22
28
 
23
29
  return;
24
30
  }
25
- let post = await mapper.mapPost(models, frame);
31
+ let post = await mapper.mapPost(models, frame, {tiers});
26
32
  frame.response = {
27
33
  posts: [post]
28
34
  };
@@ -1,8 +1,14 @@
1
1
  const mapper = require('./utils/mapper');
2
+ const membersService = require('../../../../../services/members');
2
3
 
3
4
  module.exports = {
4
5
  async all(model, apiConfig, frame) {
5
- const data = await mapper.mapPost(model, frame);
6
+ const tiersModels = await membersService.api.productRepository.list({
7
+ withRelated: ['monthlyPrice', 'yearlyPrice']
8
+ });
9
+ const tiers = tiersModels.data ? tiersModels.data.map(tierModel => tierModel.toJSON()) : [];
10
+
11
+ const data = await mapper.mapPost(model, frame, {tiers});
6
12
  frame.response = {
7
13
  preview: [data]
8
14
  };
@@ -1,6 +1,7 @@
1
1
  //@ts-check
2
2
  const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:products');
3
3
  const _ = require('lodash');
4
+ const utils = require('../../../../shared/utils');
4
5
 
5
6
  const allowedIncludes = ['stripe_prices', 'monthly_price', 'yearly_price'];
6
7
 
@@ -22,7 +23,7 @@ module.exports = {
22
23
  */
23
24
  function paginatedProducts(page, _apiConfig, frame) {
24
25
  const requestedQueryIncludes = frame.original && frame.original.query && frame.original.query.include && frame.original.query.include.split(',') || [];
25
- const requestedOptionsIncludes = frame.original && frame.original.options && frame.original.options.include || [];
26
+ const requestedOptionsIncludes = utils.options.trimAndLowerCase(frame.original && frame.original.options && frame.original.options.include || []);
26
27
  return {
27
28
  products: page.data.map((model) => {
28
29
  return cleanIncludes(
@@ -74,6 +75,7 @@ function serializeProduct(product, options, apiType) {
74
75
  description: json.description,
75
76
  slug: json.slug,
76
77
  active: json.active,
78
+ visibility: json.visibility,
77
79
  type: json.type,
78
80
  welcome_page_url: json.welcome_page_url,
79
81
  created_at: json.created_at,
@@ -3,6 +3,7 @@ const debug = require('@tryghost/debug')('api:canary:utils:serializers:output:ti
3
3
  const _ = require('lodash');
4
4
 
5
5
  const allowedIncludes = ['monthly_price', 'yearly_price'];
6
+ const utils = require('../../../../shared/utils');
6
7
 
7
8
  module.exports = {
8
9
  browse: createSerializer('browse', paginatedTiers),
@@ -22,7 +23,7 @@ module.exports = {
22
23
  */
23
24
  function paginatedTiers(page, _apiConfig, frame) {
24
25
  const requestedQueryIncludes = frame.original && frame.original.query && frame.original.query.include && frame.original.query.include.split(',') || [];
25
- const requestedOptionsIncludes = frame.original && frame.original.options && frame.original.options.include || [];
26
+ const requestedOptionsIncludes = utils.options.trimAndLowerCase(frame.original && frame.original.options && frame.original.options.include || []);
26
27
  return {
27
28
  tiers: page.data.map((model) => {
28
29
  return cleanIncludes(
@@ -81,7 +82,8 @@ function serializeTier(tier, options, apiType) {
81
82
  stripe_prices: json.stripePrices ? json.stripePrices.map(price => serializeStripePrice(price, hideStripeData)) : null,
82
83
  monthly_price: serializeStripePrice(json.monthlyPrice, hideStripeData),
83
84
  yearly_price: serializeStripePrice(json.yearlyPrice, hideStripeData),
84
- benefits: json.benefits || null
85
+ benefits: json.benefits || null,
86
+ visibility: json.visibility
85
87
  };
86
88
 
87
89
  return serialized;
@@ -31,7 +31,8 @@ const mapTag = (model, frame) => {
31
31
  return jsonModel;
32
32
  };
33
33
 
34
- const mapPost = async (model, frame) => {
34
+ const mapPost = async (model, frame, options = {}) => {
35
+ const {tiers: tiersData} = options || {};
35
36
  const extendedOptions = Object.assign(_.cloneDeep(frame.options), {
36
37
  extraProperties: ['canonical_url']
37
38
  });
@@ -45,12 +46,21 @@ const mapPost = async (model, frame) => {
45
46
  // Attach tiers to custom nql visibility filter
46
47
  if (labsService.isSet('multipleProducts')
47
48
  && jsonModel.visibility
48
- && !['members', 'public', 'paid', 'tiers'].includes(jsonModel.visibility)
49
49
  ) {
50
- const tiers = await postsService.getProductsFromVisibilityFilter(jsonModel.visibility);
50
+ if (['members', 'public'].includes(jsonModel.visibility) && jsonModel.tiers) {
51
+ jsonModel.tiers = tiersData || [];
52
+ }
53
+
54
+ if (jsonModel.visibility === 'paid' && jsonModel.tiers) {
55
+ jsonModel.tiers = tiersData ? tiersData.filter(t => t.type === 'paid') : [];
56
+ }
51
57
 
52
- jsonModel.visibility = 'tiers';
53
- jsonModel.tiers = tiers;
58
+ if (!['members', 'public', 'paid', 'tiers'].includes(jsonModel.visibility)) {
59
+ const tiers = await postsService.getProductsFromVisibilityFilter(jsonModel.visibility);
60
+
61
+ jsonModel.visibility = 'tiers';
62
+ jsonModel.tiers = tiers;
63
+ }
54
64
  }
55
65
 
56
66
  if (utils.isContentAPI(frame)) {
@@ -103,8 +113,8 @@ const mapPost = async (model, frame) => {
103
113
  return jsonModel;
104
114
  };
105
115
 
106
- const mapPage = async (model, frame) => {
107
- const jsonModel = await mapPost(model, frame);
116
+ const mapPage = async (model, frame, options) => {
117
+ const jsonModel = await mapPost(model, frame, options);
108
118
 
109
119
  delete jsonModel.email_subject;
110
120
  delete jsonModel.email_recipient_filter;
@@ -21,11 +21,11 @@ module.exports.input = (apiConfig, apiSerializers, frame) => {
21
21
  const tasks = [];
22
22
  const sharedSerializers = require('./input');
23
23
 
24
- if (!apiSerializers) {
24
+ if (!apiConfig) {
25
25
  return Promise.reject(new errors.IncorrectUsageError());
26
26
  }
27
27
 
28
- if (!apiConfig) {
28
+ if (!apiSerializers) {
29
29
  return Promise.reject(new errors.IncorrectUsageError());
30
30
  }
31
31
 
@@ -1,5 +1,5 @@
1
1
  const _ = require('lodash');
2
- const mapNQLKeyValues = require('@nexes/nql').utils.mapKeyValues;
2
+ const mapNQLKeyValues = require('@tryghost/nql').utils.mapKeyValues;
3
3
  const debug = require('@tryghost/debug')('api:v2:utils:serializers:input:pages');
4
4
  const mobiledoc = require('../../../../../lib/mobiledoc');
5
5
  const url = require('./utils/url');
@@ -1,5 +1,5 @@
1
1
  const _ = require('lodash');
2
- const mapNQLKeyValues = require('@nexes/nql').utils.mapKeyValues;
2
+ const mapNQLKeyValues = require('@tryghost/nql').utils.mapKeyValues;
3
3
  const debug = require('@tryghost/debug')('api:v2:utils:serializers:input:posts');
4
4
  const url = require('./utils/url');
5
5
  const localUtils = require('../../index');
@@ -1,6 +1,6 @@
1
1
  const _ = require('lodash');
2
2
  const debug = require('@tryghost/debug')('api:v3:utils:serializers:input:pages');
3
- const mapNQLKeyValues = require('@nexes/nql').utils.mapKeyValues;
3
+ const mapNQLKeyValues = require('@tryghost/nql').utils.mapKeyValues;
4
4
  const mobiledoc = require('../../../../../lib/mobiledoc');
5
5
  const url = require('./utils/url');
6
6
  const slugFilterOrder = require('./utils/slug-filter-order');
@@ -1,6 +1,6 @@
1
1
  const _ = require('lodash');
2
2
  const debug = require('@tryghost/debug')('api:v3:utils:serializers:input:posts');
3
- const mapNQLKeyValues = require('@nexes/nql').utils.mapKeyValues;
3
+ const mapNQLKeyValues = require('@tryghost/nql').utils.mapKeyValues;
4
4
  const url = require('./utils/url');
5
5
  const slugFilterOrder = require('./utils/slug-filter-order');
6
6
  const localUtils = require('../../index');
@@ -32,9 +32,10 @@ function configure(dbConfig) {
32
32
  process.env.BTHREADS_BACKEND = 'child_process';
33
33
  }
34
34
 
35
- if (client === 'mysql') {
36
- dbConfig.connection.timezone = 'UTC';
35
+ if (client === 'mysql2') {
36
+ dbConfig.connection.timezone = 'Z';
37
37
  dbConfig.connection.charset = 'utf8mb4';
38
+ dbConfig.connection.decimalNumbers = true;
38
39
 
39
40
  // NOTE: disabled so that worker processes can use the db without
40
41
  // requiring logging and causing file desriptor leaks.