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
@@ -3,6 +3,7 @@ const config = require('../../../shared/config');
3
3
  const errors = require('@tryghost/errors');
4
4
  const tpl = require('@tryghost/tpl');
5
5
  const logging = require('@tryghost/logging');
6
+ const moment = require('moment');
6
7
  const models = require('../../models');
7
8
  const mail = require('../mail');
8
9
 
@@ -16,6 +17,11 @@ const messages = {
16
17
  failedThemeInstall: 'Theme {themeName} didn\'t install because of the error: {error}'
17
18
  };
18
19
 
20
+ const postSetupFixtures = {
21
+ 'coming-soon': '{"version":"0.3.1","atoms":[],"cards":[],"markups":[["a",["href","#/portal/"]]],"sections":[[1,"p",[[0,[],0,"This is {{site.title}}, a brand new site by {{author.name}} that\'s just getting started. Things will be up and running here shortly, but you can "],[0,[0],1,"subscribe"],[0,[],0," in the meantime if you\'d like to stay up to date and receive emails when new content is published!"]]]],"ghostVersion":"4.0"}',
22
+ about: '{"version":"0.3.1","atoms":[],"cards":[["hr",{}]],"markups":[["a",["href","https://ghost.org"]]],"sections":[[1,"p",[[0,[],0,"{{site.title}} is an independent publication launched in {{date}} by {{author.name}}. If you subscribe today, you\'ll get full access to the website as well as email newsletters about new content when it\'s available. Your subscription makes this site possible, and allows {{site.title}} to continue to exist. Thank you!"]]],[1,"h3",[[0,[],0,"Access all areas"]]],[1,"p",[[0,[],0,"By signing up, you\'ll get access to the full archive of everything that\'s been published before and everything that\'s still to come. Your very own private library."]]],[1,"h3",[[0,[],0,"Fresh content, delivered"]]],[1,"p",[[0,[],0,"Stay up to date with new content sent straight to your inbox! No more worrying about whether you missed something because of a pesky algorithm or news feed."]]],[1,"h3",[[0,[],0,"Meet people like you"]]],[1,"p",[[0,[],0,"Join a community of other subscribers who share the same interests."]]],[10,0],[1,"h3",[[0,[],0,"Start your own thing"]]],[1,"p",[[0,[],0,"Enjoying the experience? Get started for free and set up your very own subscription business using "],[0,[0],1,"Ghost"],[0,[],0,", the same platform that powers this website."]]]],"ghostVersion":"4.0"}'
23
+ };
24
+
19
25
  /**
20
26
  * Returns setup status
21
27
  *
@@ -110,14 +116,15 @@ async function doProduct(data, productsAPI) {
110
116
  return user;
111
117
  }
112
118
  try {
113
- const page = await productsAPI.browse({limit: 1});
119
+ const page = await productsAPI.browse({limit: 'all'});
120
+
121
+ const product = page.products.find(p => p.slug === 'default-product');
114
122
 
115
- const [product] = page.products;
116
123
  if (!product) {
117
124
  return data;
118
125
  }
119
126
 
120
- productsAPI.edit({products: [{name: blogTitle.trim()}]}, {context: context.context, id: product.id});
127
+ await productsAPI.edit({products: [{name: blogTitle.trim()}]}, {context: context.context, id: product.id});
121
128
  } catch (e) {
122
129
  return data;
123
130
  }
@@ -125,6 +132,22 @@ async function doProduct(data, productsAPI) {
125
132
  return data;
126
133
  }
127
134
 
135
+ async function doFixtures(data) {
136
+ const date = moment().format('MMMM YYYY');
137
+
138
+ _.each(postSetupFixtures, async (mobiledoc, key) => {
139
+ // Using very simple find and replace because we control the fixtures
140
+ mobiledoc = mobiledoc.replace(/{{site.title}}/g, data.userData.blogTitle);
141
+ mobiledoc = mobiledoc.replace(/{{author.name}}/g, data.userData.name);
142
+ mobiledoc = mobiledoc.replace(/{{date}}/, date);
143
+
144
+ const post = await models.Post.findOne({slug: key});
145
+ await models.Post.edit({mobiledoc}, {id: post.id});
146
+ });
147
+
148
+ return data;
149
+ }
150
+
128
151
  function sendWelcomeEmail(email, mailAPI) {
129
152
  if (config.get('sendWelcomeEmail')) {
130
153
  const data = {
@@ -164,6 +187,11 @@ async function installTheme(data, api) {
164
187
  return data;
165
188
  }
166
189
 
190
+ if (themeName.toLowerCase() === 'tryghost/casper') {
191
+ logging.warn('Skipping theme install as Casper is the default theme.');
192
+ return data;
193
+ }
194
+
167
195
  // Use the api instead of the services as the api performs extra logic
168
196
  try {
169
197
  const installResults = await api.themes.install({
@@ -180,14 +208,6 @@ async function installTheme(data, api) {
180
208
  } catch (error) {
181
209
  //Fallback to Casper by doing nothing as the theme setting update is the last step
182
210
  logging.warn(tpl(messages.failedThemeInstall, {themeName, error: error.message}));
183
-
184
- await api.notifications.add({
185
- notifications: [{
186
- custom: true, //avoids update-check from deleting the notification
187
- type: 'warn',
188
- message: 'The installation of the theme you have selected wasn\'t successful.'
189
- }]
190
- }, {context: {internal: true}});
191
211
  }
192
212
 
193
213
  return data;
@@ -199,6 +219,7 @@ module.exports = {
199
219
  setupUser: setupUser,
200
220
  doSettings: doSettings,
201
221
  doProduct: doProduct,
202
- sendWelcomeEmail: sendWelcomeEmail,
203
- installTheme: installTheme
222
+ installTheme: installTheme,
223
+ doFixtures: doFixtures,
224
+ sendWelcomeEmail: sendWelcomeEmail
204
225
  };
@@ -1,5 +1,5 @@
1
1
  const {EventProcessor} = require('@tryghost/email-analytics-service');
2
- const moment = require('moment');
2
+ const moment = require('moment-timezone');
3
3
 
4
4
  class GhostEventProcessor extends EventProcessor {
5
5
  constructor({db}) {
@@ -88,6 +88,23 @@ class GhostEventProcessor extends EventProcessor {
88
88
  opened_at: this.db.knex.raw('COALESCE(opened_at, ?)', [moment.utc(event.timestamp).format('YYYY-MM-DD HH:mm:ss')])
89
89
  });
90
90
 
91
+ // Using the default timezone set in https://github.com/TryGhost/Ghost/blob/2c5643623db0fc4db390f6997c81a73dca7ccacd/core/server/data/schema/default-settings/default-settings.json#L105
92
+ let timezone = 'Etc/UTC';
93
+ const timezoneData = await this.db.knex('settings').first('value').where('key', 'timezone');
94
+ if (timezoneData && timezoneData.value) {
95
+ timezone = timezoneData.value;
96
+ }
97
+
98
+ await this.db.knex('members')
99
+ .where('email', '=', event.recipientEmail)
100
+ .andWhere(builder => builder
101
+ .where('last_seen_at', '<', moment.utc(event.timestamp).tz(timezone).startOf('day').utc().format('YYYY-MM-DD HH:mm:ss'))
102
+ .orWhereNull('last_seen_at')
103
+ )
104
+ .update({
105
+ last_seen_at: moment.utc(event.timestamp).format('YYYY-MM-DD HH:mm:ss')
106
+ });
107
+
91
108
  return updateResult > 0;
92
109
  }
93
110
 
@@ -249,14 +249,14 @@ const retryFailedEmail = async (emailModel) => {
249
249
  async function handleUnsubscribeRequest(req) {
250
250
  if (!req.url) {
251
251
  throw new errors.BadRequestError({
252
- message: 'Unsubscribe failed! Could not find member'
252
+ message: 'Email address not found.'
253
253
  });
254
254
  }
255
255
 
256
256
  const {query} = url.parse(req.url, true);
257
257
  if (!query || !query.uuid) {
258
258
  throw new errors.BadRequestError({
259
- message: (query.preview ? 'Unsubscribe preview' : 'Unsubscribe failed! Could not find member')
259
+ message: (query.preview ? 'Unsubscribe preview' : 'Email address not found.')
260
260
  });
261
261
  }
262
262
 
@@ -266,7 +266,7 @@ async function handleUnsubscribeRequest(req) {
266
266
 
267
267
  if (!member) {
268
268
  throw new errors.BadRequestError({
269
- message: 'Unsubscribe failed! Could not find member'
269
+ message: 'Email address not found.'
270
270
  });
271
271
  }
272
272
 
@@ -276,7 +276,7 @@ async function handleUnsubscribeRequest(req) {
276
276
  } catch (err) {
277
277
  throw new errors.InternalServerError({
278
278
  err,
279
- message: 'Failed to unsubscribe member'
279
+ message: 'Failed to unsubscribe this email address'
280
280
  });
281
281
  }
282
282
  }
@@ -1216,7 +1216,7 @@ ${ templateSettings.showBadge ? `
1216
1216
 
1217
1217
  ${ templateSettings.showBadge ? `
1218
1218
  <tr>
1219
- <td class="footer-powered"><a href="https://ghost.org/"><img src="https://static.ghost.org/v4.0.0/images/powered.png" border="0" width="142" height="30" class="gh-powered" alt="Publish with Ghost"></a></td>
1219
+ <td class="footer-powered"><a href="https://ghost.org/"><img src="https://static.ghost.org/v4.0.0/images/powered.png" border="0" width="142" height="30" class="gh-powered" alt="Powered by Ghost"></a></td>
1220
1220
  </tr>
1221
1221
  ` : '' }
1222
1222
  </table>
@@ -1,4 +1,4 @@
1
- const nql = require('@nexes/nql');
1
+ const nql = require('@tryghost/nql');
2
2
 
3
3
  // @ts-check
4
4
  /** @typedef { boolean } AccessFlag */
@@ -110,9 +110,13 @@ const getPortalProductPrices = async function () {
110
110
  monthlyPrice: product.monthlyPrice,
111
111
  yearlyPrice: product.yearlyPrice,
112
112
  benefits: product.benefits,
113
+ active: product.active,
113
114
  type: product.type,
115
+ visibility: product.visibility,
114
116
  prices: productPrices
115
117
  };
118
+ }).filter((product) => {
119
+ return !!product.active;
116
120
  });
117
121
  const defaultProduct = products.find((product) => {
118
122
  return product.type === 'paid';
@@ -16,6 +16,8 @@ const models = require('../../models');
16
16
  const {GhostMailer} = require('../mail');
17
17
  const jobsService = require('../jobs');
18
18
  const VerificationTrigger = require('@tryghost/verification-trigger');
19
+ const DomainEvents = require('@tryghost/domain-events');
20
+ const {LastSeenAtUpdater} = require('@tryghost/members-events-service');
19
21
  const events = require('../../lib/common/events');
20
22
 
21
23
  const messages = {
@@ -139,7 +141,7 @@ module.exports = {
139
141
  sendVerificationEmail: ({subject, message, amountImported}) => {
140
142
  const escalationAddress = config.get('hostSettings:emailVerification:escalationAddress');
141
143
  const fromAddress = config.get('user_email');
142
-
144
+
143
145
  if (escalationAddress) {
144
146
  ghostMailer.send({
145
147
  subject,
@@ -158,6 +160,16 @@ module.exports = {
158
160
  eventRepository: membersApi.events
159
161
  });
160
162
 
163
+ new LastSeenAtUpdater({
164
+ models: {
165
+ Member: models.Member
166
+ },
167
+ services: {
168
+ domainEvents: DomainEvents,
169
+ settingsCache
170
+ }
171
+ });
172
+
161
173
  (async () => {
162
174
  try {
163
175
  const collection = await models.SingleUseToken.fetchAll();
@@ -1,4 +1,4 @@
1
- const nql = require('@nexes/nql');
1
+ const nql = require('@tryghost/nql');
2
2
  const {BadRequestError} = require('@tryghost/errors');
3
3
  const tpl = require('@tryghost/tpl');
4
4
 
@@ -1,5 +1,5 @@
1
1
  const _ = require('lodash');
2
- const nql = require('@nexes/nql');
2
+ const nql = require('@tryghost/nql');
3
3
  const debug = require('@tryghost/debug')('services:url:generator');
4
4
  const localUtils = require('../../../shared/url-utils');
5
5
 
@@ -41,6 +41,8 @@ class WebhooksService {
41
41
  help: messages.nonExistingIntegrationIdProvided.help
42
42
  });
43
43
  }
44
+
45
+ throw error;
44
46
  }
45
47
  }
46
48
  }
@@ -44,7 +44,7 @@ body {
44
44
  flex-direction: column;
45
45
  justify-content: center;
46
46
  max-width: 500px;
47
- min-height: 500px;
47
+ min-height: 360px;
48
48
  margin: 0 0 4vmin;
49
49
  padding: 40px;
50
50
  text-align: center;
@@ -63,7 +63,7 @@ h1 {
63
63
  letter-spacing: -0.02em;
64
64
  }
65
65
  p {
66
- margin: 0 0 40px;
66
+ margin: 0;
67
67
  opacity: 0.7;
68
68
  font-weight: 400;
69
69
  }
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.37%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.39%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -38,7 +38,7 @@
38
38
 
39
39
 
40
40
  <link rel="stylesheet" href="assets/vendor.min-2c8ad32b7960bb605ebc20097fee5ebd.css">
41
- <link rel="stylesheet" href="assets/ghost.min-02a5f8954bd85fe28817b8c8b111b8aa.css" title="light">
41
+ <link rel="stylesheet" href="assets/ghost.min-77b93478f83b0def6ddc5a4f23ce963e.css" title="light">
42
42
 
43
43
 
44
44
 
@@ -56,8 +56,8 @@
56
56
  <div id="ember-basic-dropdown-wormhole"></div>
57
57
 
58
58
 
59
- <script src="assets/vendor.min-6dc30be68238b5c55df0cdc1f2dc8b8d.js"></script>
60
- <script src="assets/ghost.min-c1938f6ee696bf08bd6bf93cac341ea2.js"></script>
59
+ <script src="assets/vendor.min-c39476bced9adb98ee2b292d01c7a8f4.js"></script>
60
+ <script src="assets/ghost.min-e6559d901897066aa6a6d4145e3728ed.js"></script>
61
61
 
62
62
  </body>
63
63
  </html>
@@ -8,7 +8,7 @@
8
8
  <title>Ghost Admin</title>
9
9
 
10
10
 
11
- <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.37%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
11
+ <meta name="ghost-admin/config/environment" content="%7B%22modulePrefix%22%3A%22ghost-admin%22%2C%22environment%22%3A%22production%22%2C%22rootURL%22%3A%22%2F%22%2C%22locationType%22%3A%22trailing-hash%22%2C%22EmberENV%22%3A%7B%22FEATURES%22%3A%7B%7D%2C%22EXTEND_PROTOTYPES%22%3A%7B%22Date%22%3Afalse%2C%22Array%22%3Atrue%2C%22String%22%3Atrue%2C%22Function%22%3Afalse%7D%2C%22_APPLICATION_TEMPLATE_WRAPPER%22%3Afalse%2C%22_JQUERY_INTEGRATION%22%3Atrue%2C%22_TEMPLATE_ONLY_GLIMMER_COMPONENTS%22%3Atrue%7D%2C%22APP%22%3A%7B%22version%22%3A%224.39%22%2C%22name%22%3A%22ghost-admin%22%7D%2C%22ember-simple-auth%22%3A%7B%7D%2C%22moment%22%3A%7B%22includeTimezone%22%3A%22all%22%7D%2C%22%40sentry%2Fember%22%3A%7B%22disablePerformance%22%3Atrue%2C%22sentry%22%3A%7B%7D%7D%2C%22ember-cli-mirage%22%3A%7B%22usingProxy%22%3Afalse%2C%22useDefaultPassthroughs%22%3Atrue%7D%2C%22exportApplicationGlobal%22%3Afalse%2C%22ember-load%22%3A%7B%22loadingIndicatorClass%22%3A%22ember-load-indicator%22%7D%7D" />
12
12
 
13
13
  <meta name="HandheldFriendly" content="True" />
14
14
  <meta name="MobileOptimized" content="320" />
@@ -38,7 +38,7 @@
38
38
 
39
39
 
40
40
  <link rel="stylesheet" href="assets/vendor.min-2c8ad32b7960bb605ebc20097fee5ebd.css">
41
- <link rel="stylesheet" href="assets/ghost.min-02a5f8954bd85fe28817b8c8b111b8aa.css" title="light">
41
+ <link rel="stylesheet" href="assets/ghost.min-77b93478f83b0def6ddc5a4f23ce963e.css" title="light">
42
42
 
43
43
 
44
44
 
@@ -56,8 +56,8 @@
56
56
  <div id="ember-basic-dropdown-wormhole"></div>
57
57
 
58
58
 
59
- <script src="assets/vendor.min-6dc30be68238b5c55df0cdc1f2dc8b8d.js"></script>
60
- <script src="assets/ghost.min-c1938f6ee696bf08bd6bf93cac341ea2.js"></script>
59
+ <script src="assets/vendor.min-c39476bced9adb98ee2b292d01c7a8f4.js"></script>
60
+ <script src="assets/ghost.min-e6559d901897066aa6a6d4145e3728ed.js"></script>
61
61
 
62
62
  </body>
63
63
  </html>
@@ -25,6 +25,9 @@ module.exports = function setupApiApp() {
25
25
  apiApp.lazyUse(urlUtils.getVersionPath({version: 'canary', type: 'content'}), require('./canary/content/app'));
26
26
  apiApp.lazyUse(urlUtils.getVersionPath({version: 'canary', type: 'admin'}), require('./canary/admin/app'));
27
27
 
28
+ apiApp.lazyUse('/content/', require('./canary/content/app'));
29
+ apiApp.lazyUse('/admin/', require('./canary/admin/app'));
30
+
28
31
  // Error handling for requests to non-existent API versions
29
32
  apiApp.use(errorHandler.resourceNotFound);
30
33
  apiApp.use(errorHandler.handleJSONResponse(sentry));
@@ -31,6 +31,8 @@ const notImplemented = function (req, res, next) {
31
31
  members: ['GET', 'PUT', 'DELETE', 'POST'],
32
32
  config: ['GET'],
33
33
  schedules: ['PUT'],
34
+ files: ['POST'],
35
+ media: ['POST'],
34
36
  db: ['POST']
35
37
  };
36
38
 
@@ -34,6 +34,7 @@ module.exports = function apiRoutes() {
34
34
  router.get('/settings', mw.authenticatePublic, http(api.publicSettings.browse));
35
35
 
36
36
  router.get('/products', mw.authenticatePublic, http(api.productsPublic.browse));
37
+ router.get('/tiers', mw.authenticatePublic, http(api.tiersPublic.browse));
37
38
 
38
39
  return router;
39
40
  };
@@ -39,7 +39,7 @@ module.exports = function setupMembersApp() {
39
39
  membersApp.get('/api/session', middleware.getIdentityToken);
40
40
  membersApp.get('/api/offers/:id', middleware.getOfferData);
41
41
  membersApp.delete('/api/session', middleware.deleteSession);
42
- membersApp.get('/api/site', shared.middleware.cacheControl('public', {maxAge: 30}), middleware.getMemberSiteData);
42
+ membersApp.get('/api/site', middleware.getMemberSiteData);
43
43
 
44
44
  // NOTE: this is wrapped in a function to ensure we always go via the getter
45
45
  membersApp.post('/api/send-magic-link', bodyParser.json(), shared.middleware.brute.membersAuth, (req, res, next) => membersService.api.middleware.sendMagicLink(req, res, next));
@@ -1,5 +1,6 @@
1
1
  const debug = require('@tryghost/debug')('web:backend');
2
2
  const express = require('../../../shared/express');
3
+ const {BASE_API_PATH} = require('../../../shared/url-utils');
3
4
 
4
5
  /**
5
6
  *
@@ -11,7 +12,7 @@ module.exports = () => {
11
12
  // Wrap the admin and API apps into a single express app for use with vhost
12
13
  const backendApp = express('backend');
13
14
 
14
- backendApp.lazyUse('/ghost/api', require('../api'));
15
+ backendApp.lazyUse(BASE_API_PATH, require('../api'));
15
16
  backendApp.lazyUse('/ghost/oauth', require('../oauth'));
16
17
  backendApp.lazyUse('/ghost/.well-known', require('../well-known'));
17
18
 
@@ -27,13 +27,14 @@ const uncapitalise = (req, res, next) => {
27
27
  let decodedURI;
28
28
 
29
29
  const isSignupOrReset = pathToTest.match(/^(.*\/ghost\/(signup|reset)\/)/i);
30
- const isAPI = pathToTest.match(/^(.*\/ghost\/api\/(v[\d.]+|canary)\/.*?\/)/i);
30
+ const isAPIRegExp = new RegExp(`^(.*${urlUtils.BASE_API_PATH}(/(v[\\d.]+|canary))?/.*?/)`, 'i');
31
+ const isAPI = pathToTest.match(isAPIRegExp);
31
32
 
32
33
  if (isSignupOrReset) {
33
34
  pathToTest = isSignupOrReset[1];
34
35
  }
35
36
 
36
- // Do not lowercase anything after e.g. /api/v{X}/ to protect :key/:slug
37
+ // Do not lowercase anything after e.g. /ghost/api(/v{X})?/ to protect :key/:slug
37
38
  if (isAPI) {
38
39
  pathToTest = isAPI[1];
39
40
  }
@@ -128,8 +128,8 @@
128
128
  "emailAnalytics": true
129
129
  },
130
130
  "portal": {
131
- "url": "https://unpkg.com/@tryghost/portal@~1.14.0/umd/portal.min.js",
132
- "version": "1.14"
131
+ "url": "https://unpkg.com/@tryghost/portal@~1.16.0/umd/portal.min.js",
132
+ "version": "1.16"
133
133
  },
134
134
  "tenor": {
135
135
  "publicReadOnlyApiKey": null,
@@ -54,9 +54,13 @@ const checkUrlProtocol = function checkUrlProtocol(url) {
54
54
  * https://github.com/indexzero/nconf/issues/235#issuecomment-257606507
55
55
  */
56
56
  const sanitizeDatabaseProperties = function sanitizeDatabaseProperties(nconf) {
57
+ if (nconf.get('database:client') === 'mysql') {
58
+ nconf.set('database:client', 'mysql2');
59
+ }
60
+
57
61
  const database = nconf.get('database');
58
62
 
59
- if (nconf.get('database:client') === 'mysql') {
63
+ if (nconf.get('database:client') === 'mysql2') {
60
64
  delete database.connection.filename;
61
65
  } else {
62
66
  delete database.connection.host;
@@ -15,13 +15,18 @@ const messages = {
15
15
 
16
16
  // flags in this list always return `true`, allows quick global enable prior to full flag removal
17
17
  const GA_FEATURES = [
18
+ 'multipleProducts',
19
+ 'tierWelcomePages',
20
+ 'tierName',
21
+ 'selectablePortalLinks',
22
+ 'membersTableStatus',
23
+ 'improvedOnboarding'
18
24
  ];
19
25
 
20
26
  // NOTE: this allowlist is meant to be used to filter out any unexpected
21
27
  // input for the "labs" setting value
22
28
  const BETA_FEATURES = [
23
- 'activitypub',
24
- 'multipleProducts'
29
+ 'activitypub'
25
30
  ];
26
31
 
27
32
  const ALPHA_FEATURES = [
@@ -30,13 +35,7 @@ const ALPHA_FEATURES = [
30
35
  'urlCache',
31
36
  'beforeAfterCard',
32
37
  'tweetGridCard',
33
- 'membersActivityFeed',
34
- 'improvedOnboarding',
35
- 'tierWelcomePages',
36
- 'tierName',
37
- 'membersTableStatus',
38
- 'membersLastSeenFilter',
39
- 'selectablePortalLinks'
38
+ 'membersActivityFeed'
40
39
  ];
41
40
 
42
41
  module.exports.GA_KEYS = [...GA_FEATURES];
@@ -1,6 +1,8 @@
1
1
  const UrlUtils = require('@tryghost/url-utils');
2
2
  const config = require('./config');
3
3
 
4
+ const BASE_API_PATH = '/ghost/api';
5
+
4
6
  const urlUtils = new UrlUtils({
5
7
  getSubdir: config.getSubdir,
6
8
  getSiteUrl: config.getSiteUrl,
@@ -9,7 +11,8 @@ const urlUtils = new UrlUtils({
9
11
  defaultApiVersion: config.get('api:versions:default'),
10
12
  slugs: config.get('slugs').protected,
11
13
  redirectCacheMaxAge: config.get('caching:301:maxAge'),
12
- baseApiPath: '/ghost/api'
14
+ baseApiPath: BASE_API_PATH
13
15
  });
14
16
 
15
17
  module.exports = urlUtils;
18
+ module.exports.BASE_API_PATH = BASE_API_PATH;