ghost 4.36.3 → 4.38.1

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 (98) hide show
  1. package/.c8rc.json +10 -0
  2. package/content/themes/casper/LICENSE +1 -1
  3. package/content/themes/casper/README.md +1 -1
  4. package/content/themes/casper/assets/built/global.css +1 -1
  5. package/content/themes/casper/assets/built/global.css.map +1 -1
  6. package/content/themes/casper/assets/built/screen.css +1 -1
  7. package/content/themes/casper/assets/built/screen.css.map +1 -1
  8. package/content/themes/casper/assets/css/global.css +14 -6
  9. package/content/themes/casper/assets/css/screen.css +9 -1
  10. package/content/themes/casper/package.json +2 -2
  11. package/content/themes/casper/partials/post-card.hbs +1 -1
  12. package/content/themes/casper/post.hbs +18 -19
  13. package/content/themes/casper/yarn.lock +186 -217
  14. package/core/built/assets/ghost-dark-9f760f16230b8bc52e188d6ce28516b0.css +1 -0
  15. package/core/built/assets/{ghost.min-801697772dc605c0dae0abfec54ec591.js → ghost.min-6386b02480494a69c3bfe66206754836.js} +375 -312
  16. package/core/built/assets/ghost.min-f4c59dd57a2136df8b0a34f87c099034.css +1 -0
  17. package/core/built/assets/icons/eye.svg +4 -1
  18. package/core/built/assets/icons/member-add.svg +3 -0
  19. package/core/built/assets/icons/pin.svg +4 -1
  20. package/core/built/assets/{vendor.min-2313642ee897688be83924a38d5e62f1.js → vendor.min-c814d3c4b3f543c4cd5ef3aacd0fc645.js} +40 -36
  21. package/core/frontend/helpers/excerpt.js +7 -4
  22. package/core/frontend/helpers/get.js +4 -0
  23. package/core/frontend/helpers/match.js +12 -0
  24. package/core/frontend/helpers/prev_post.js +11 -1
  25. package/core/frontend/helpers/tiers.js +59 -0
  26. package/core/frontend/helpers/tpl/content-cta.hbs +1 -1
  27. package/core/frontend/services/routing/router-manager.js +1 -1
  28. package/core/frontend/web/site.js +10 -0
  29. package/core/server/api/canary/authentication.js +2 -0
  30. package/core/server/api/canary/index.js +8 -0
  31. package/core/server/api/canary/members.js +2 -1
  32. package/core/server/api/canary/products.js +3 -6
  33. package/core/server/api/canary/tiers-public.js +34 -0
  34. package/core/server/api/canary/tiers.js +120 -0
  35. package/core/server/api/canary/utils/serializers/input/index.js +4 -0
  36. package/core/server/api/canary/utils/serializers/input/tiers.js +36 -0
  37. package/core/server/api/canary/utils/serializers/output/email-posts.js +7 -1
  38. package/core/server/api/canary/utils/serializers/output/index.js +4 -0
  39. package/core/server/api/canary/utils/serializers/output/members.js +5 -0
  40. package/core/server/api/canary/utils/serializers/output/pages.js +9 -2
  41. package/core/server/api/canary/utils/serializers/output/posts.js +8 -2
  42. package/core/server/api/canary/utils/serializers/output/preview.js +7 -1
  43. package/core/server/api/canary/utils/serializers/output/products.js +3 -1
  44. package/core/server/api/canary/utils/serializers/output/tiers.js +212 -0
  45. package/core/server/api/canary/utils/serializers/output/utils/mapper.js +17 -7
  46. package/core/server/api/canary/utils/validators/input/index.js +4 -0
  47. package/core/server/api/canary/utils/validators/input/tiers.js +6 -0
  48. package/core/server/api/v2/settings.js +2 -1
  49. package/core/server/data/db/connection.js +3 -2
  50. package/core/server/data/migrations/init/1-create-tables.js +4 -1
  51. package/core/server/data/migrations/versions/3.29/01-remove-duplicate-subscriptions.js +2 -1
  52. package/core/server/data/migrations/versions/3.29/02-remove-duplicate-customers.js +2 -1
  53. package/core/server/data/migrations/versions/3.29/03-remove-orphaned-customers.js +2 -1
  54. package/core/server/data/migrations/versions/3.29/04-remove-orphaned-subscriptions.js +2 -1
  55. package/core/server/data/migrations/versions/3.29/05-add-member-constraints.js +3 -2
  56. package/core/server/data/migrations/versions/3.39/06-add-email-recipient-index.js +4 -3
  57. package/core/server/data/migrations/versions/4.0/14-remove-orphaned-stripe-records.js +2 -1
  58. package/core/server/data/migrations/versions/4.0/26-add-cascade-on-delete.js +2 -1
  59. package/core/server/data/migrations/versions/4.0/29-fix-foreign-key-for-members-stripe-customers-subscriptions.js +2 -1
  60. package/core/server/data/migrations/versions/4.1/02-add-unique-constraint-for-member-stripe-tables.js +2 -1
  61. package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +3 -2
  62. package/core/server/data/migrations/versions/4.33/2022-01-18-09-07-remove-duplicate-offer-redemptions.js +2 -2
  63. package/core/server/data/migrations/versions/4.35/2022-02-01-11-48-update-email-recipient-filter-column-type.js +2 -1
  64. package/core/server/data/migrations/versions/4.35/2022-02-01-12-03-update-recipient-filter-column-type.js +2 -1
  65. package/core/server/data/migrations/versions/4.37/2022-02-21-09-53-backfill-members-last-seen-at-column.js +32 -0
  66. package/core/server/data/migrations/versions/4.38/2022-03-01-08-46-add-visibility-to-tiers.js +11 -0
  67. package/core/server/data/migrations/versions/4.38/2022-03-03-16-12-add-visibility-to-tiers.js +8 -0
  68. package/core/server/data/migrations/versions/4.38/2022-03-03-16-17-drop-tiers-visible-column.js +7 -0
  69. package/core/server/data/schema/clients/index.js +1 -1
  70. package/core/server/data/schema/clients/mysql.js +4 -4
  71. package/core/server/data/schema/commands.js +61 -70
  72. package/core/server/data/schema/{default-settings.json → default-settings/default-settings.json} +0 -0
  73. package/core/server/data/schema/default-settings/index.js +6 -0
  74. package/core/server/data/schema/fixtures/fixtures.json +4 -2
  75. package/core/server/data/schema/schema.js +7 -0
  76. package/core/server/models/product.js +2 -1
  77. package/core/server/services/auth/api-key/admin.js +15 -6
  78. package/core/server/services/auth/setup.js +13 -1
  79. package/core/server/services/email-analytics/lib/event-processor.js +18 -1
  80. package/core/server/services/members/middleware.js +4 -1
  81. package/core/server/services/members/service.js +21 -8
  82. package/core/server/services/route-settings/index.js +1 -1
  83. package/core/server/web/admin/views/default-prod.html +4 -4
  84. package/core/server/web/admin/views/default.html +4 -4
  85. package/core/server/web/api/app.js +3 -0
  86. package/core/server/web/api/canary/admin/middleware.js +2 -0
  87. package/core/server/web/api/canary/admin/routes.js +7 -0
  88. package/core/server/web/api/canary/content/routes.js +1 -0
  89. package/core/server/web/members/app.js +1 -1
  90. package/core/server/web/shared/middleware/uncapitalise.js +2 -2
  91. package/core/shared/config/defaults.json +3 -2
  92. package/core/shared/config/overrides.json +1 -1
  93. package/core/shared/config/utils.js +5 -1
  94. package/core/shared/labs.js +4 -1
  95. package/package.json +62 -61
  96. package/yarn.lock +729 -614
  97. package/core/built/assets/ghost-dark-25e568b14d76f6754fa8279cceb265ba.css +0 -1
  98. package/core/built/assets/ghost.min-75ed7451ca633bae1b345eb57e2c28e0.css +0 -1
@@ -7,13 +7,15 @@
7
7
  "name": "Free",
8
8
  "slug": "free",
9
9
  "type": "free",
10
- "active": true
10
+ "active": true,
11
+ "visibility": "public"
11
12
  },
12
13
  {
13
14
  "name": "Default Product",
14
15
  "slug": "default-product",
15
16
  "type": "paid",
16
- "active": true
17
+ "active": true,
18
+ "visibility": "public"
17
19
  }
18
20
  ]
19
21
  },
@@ -381,6 +381,13 @@ module.exports = {
381
381
  slug: {type: 'string', maxlength: 191, nullable: false, unique: true},
382
382
  active: {type: 'boolean', nullable: false, defaultTo: true},
383
383
  welcome_page_url: {type: 'string', maxlength: 2000, nullable: true},
384
+ visibility: {
385
+ type: 'string',
386
+ maxlength: 50,
387
+ nullable: false,
388
+ defaultTo: 'none',
389
+ validations: {isIn: [['public', 'none']]}
390
+ },
384
391
  monthly_price_id: {type: 'string', maxlength: 24, nullable: true},
385
392
  yearly_price_id: {type: 'string', maxlength: 24, nullable: true},
386
393
  description: {type: 'string', maxlength: 191, nullable: true},
@@ -5,7 +5,8 @@ const Product = ghostBookshelf.Model.extend({
5
5
  tableName: 'products',
6
6
 
7
7
  defaults: {
8
- active: true
8
+ active: true,
9
+ visibility: 'none'
9
10
  },
10
11
 
11
12
  relationships: ['benefits'],
@@ -3,6 +3,7 @@ const url = require('url');
3
3
  const models = require('../../../models');
4
4
  const errors = require('@tryghost/errors');
5
5
  const limitService = require('../../../services/limits');
6
+ const config = require('../../../../shared/config');
6
7
  const tpl = require('@tryghost/tpl');
7
8
  const _ = require('lodash');
8
9
 
@@ -139,12 +140,20 @@ const authenticateWithToken = async (req, res, next, {token, JWT_OPTIONS}) => {
139
140
  const secret = Buffer.from(apiKey.get('secret'), 'hex');
140
141
 
141
142
  const {pathname} = url.parse(req.originalUrl);
142
- const [hasMatch, version = 'v4', api = 'admin'] = pathname.match(/ghost\/api\/([^/]+)\/([^/]+)\/(.+)*/); // eslint-disable-line no-unused-vars
143
-
144
- // ensure the token was meant for this api version
145
- const options = Object.assign({
146
- audience: new RegExp(`\/?${version}\/${api}\/?$`) // eslint-disable-line no-useless-escape
147
- }, JWT_OPTIONS);
143
+ const [hasMatch, version, api] = pathname.match(/ghost\/api\/([^/]+)\/([^/]+)\/(.+)*/); // eslint-disable-line no-unused-vars
144
+
145
+ // ensure the token was meant for this api
146
+ let options;
147
+ if (!config.get('api:versions:all').includes(version)) {
148
+ // CASE: non-versioned api request
149
+ options = Object.assign({
150
+ audience: new RegExp(`\/?${version}\/?$`) // eslint-disable-line no-useless-escape
151
+ }, JWT_OPTIONS);
152
+ } else {
153
+ options = Object.assign({
154
+ audience: new RegExp(`\/?${version}\/${api}\/?$`) // eslint-disable-line no-useless-escape
155
+ }, JWT_OPTIONS);
156
+ }
148
157
 
149
158
  try {
150
159
  jwt.verify(token, secret, options);
@@ -77,6 +77,7 @@ async function doSettings(data, settingsAPI) {
77
77
  const context = {context: {user: data.user.id}};
78
78
  const user = data.user;
79
79
  const blogTitle = data.userData.blogTitle;
80
+ const description = data.userData.description ? data.userData.description.trim() : null;
80
81
 
81
82
  let userSettings;
82
83
 
@@ -86,9 +87,15 @@ async function doSettings(data, settingsAPI) {
86
87
 
87
88
  userSettings = [
88
89
  {key: 'title', value: blogTitle.trim()},
89
- {key: 'description', value: tpl(messages.sampleBlogDescription)}
90
+ {key: 'description', value: description || tpl(messages.sampleBlogDescription)}
90
91
  ];
91
92
 
93
+ if (data.userData.accentColor) {
94
+ userSettings.push({
95
+ key: 'accent_color', value: data.userData.accentColor
96
+ });
97
+ }
98
+
92
99
  await settingsAPI.edit({settings: userSettings}, context);
93
100
 
94
101
  return user;
@@ -157,6 +164,11 @@ async function installTheme(data, api) {
157
164
  return data;
158
165
  }
159
166
 
167
+ if (themeName.toLowerCase() === 'tryghost/casper') {
168
+ logging.warn('Skipping theme install as Casper is the default theme.');
169
+ return data;
170
+ }
171
+
160
172
  // Use the api instead of the services as the api performs extra logic
161
173
  try {
162
174
  const installResults = await api.themes.install({
@@ -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
 
@@ -110,9 +110,12 @@ 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,
114
115
  prices: productPrices
115
116
  };
117
+ }).filter((product) => {
118
+ return !!product.active;
116
119
  });
117
120
  const defaultProduct = products.find((product) => {
118
121
  return product.type === 'paid';
@@ -198,7 +201,7 @@ const createSessionFromMagicLink = async function (req, res, next) {
198
201
 
199
202
  const action = req.query.action;
200
203
 
201
- if (action === 'signup' || action === 'signup-paid') {
204
+ if (action === 'signup' || action === 'signup-paid' || action === 'subscribe') {
202
205
  let customRedirect = '';
203
206
  const mostRecentActiveSubscription = subscriptions
204
207
  .sort((a, b) => {
@@ -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 = {
@@ -125,6 +127,13 @@ module.exports = {
125
127
  });
126
128
  }
127
129
 
130
+ module.exports.ssr = MembersSSR({
131
+ cookieSecure: urlUtils.isSSL(urlUtils.getSiteUrl()),
132
+ cookieKeys: [settingsCache.get('theme_session_secret')],
133
+ cookieName: 'ghost-members-ssr',
134
+ getMembersApi: () => module.exports.api
135
+ });
136
+
128
137
  verificationTrigger = new VerificationTrigger({
129
138
  configThreshold: _.get(config.get('hostSettings'), 'emailVerification.importThreshold'),
130
139
  isVerified: () => config.get('hostSettings:emailVerification:verified') === true,
@@ -132,7 +141,7 @@ module.exports = {
132
141
  sendVerificationEmail: ({subject, message, amountImported}) => {
133
142
  const escalationAddress = config.get('hostSettings:emailVerification:escalationAddress');
134
143
  const fromAddress = config.get('user_email');
135
-
144
+
136
145
  if (escalationAddress) {
137
146
  ghostMailer.send({
138
147
  subject,
@@ -151,6 +160,16 @@ module.exports = {
151
160
  eventRepository: membersApi.events
152
161
  });
153
162
 
163
+ new LastSeenAtUpdater({
164
+ models: {
165
+ Member: models.Member
166
+ },
167
+ services: {
168
+ domainEvents: DomainEvents,
169
+ settingsCache
170
+ }
171
+ });
172
+
154
173
  (async () => {
155
174
  try {
156
175
  const collection = await models.SingleUseToken.fetchAll();
@@ -181,13 +200,7 @@ module.exports = {
181
200
  return membersSettings;
182
201
  },
183
202
 
184
- ssr: MembersSSR({
185
- cookieSecure: urlUtils.isSSL(urlUtils.getSiteUrl()),
186
- cookieKeys: [settingsCache.get('theme_session_secret')],
187
- cookieName: 'ghost-members-ssr',
188
- cookieCacheName: 'ghost-members-ssr-cache',
189
- getMembersApi: () => module.exports.api
190
- }),
203
+ ssr: null,
191
204
 
192
205
  stripeConnect: require('./stripe-connect'),
193
206
 
@@ -22,7 +22,7 @@ module.exports = {
22
22
  type: 'routes',
23
23
  extension: '.yaml',
24
24
  destinationFolderPath: config.getContentPath('settings'),
25
- sourceFolderPath: config.get('paths').defaultSettings
25
+ sourceFolderPath: config.get('paths').defaultRouteSettings
26
26
  });
27
27
 
28
28
  return await defaultSettingsManager.ensureSettingsFileExists();
@@ -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.36%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.38%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-75ed7451ca633bae1b345eb57e2c28e0.css" title="light">
41
+ <link rel="stylesheet" href="assets/ghost.min-f4c59dd57a2136df8b0a34f87c099034.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-2313642ee897688be83924a38d5e62f1.js"></script>
60
- <script src="assets/ghost.min-801697772dc605c0dae0abfec54ec591.js"></script>
59
+ <script src="assets/vendor.min-c814d3c4b3f543c4cd5ef3aacd0fc645.js"></script>
60
+ <script src="assets/ghost.min-6386b02480494a69c3bfe66206754836.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.36%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.38%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-75ed7451ca633bae1b345eb57e2c28e0.css" title="light">
41
+ <link rel="stylesheet" href="assets/ghost.min-f4c59dd57a2136df8b0a34f87c099034.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-2313642ee897688be83924a38d5e62f1.js"></script>
60
- <script src="assets/ghost.min-801697772dc605c0dae0abfec54ec591.js"></script>
59
+ <script src="assets/vendor.min-c814d3c4b3f543c4cd5ef3aacd0fc645.js"></script>
60
+ <script src="assets/ghost.min-6386b02480494a69c3bfe66206754836.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
 
@@ -88,11 +88,18 @@ module.exports = function apiRoutes() {
88
88
  router.del('/tags/:id', mw.authAdminApi, http(api.tags.destroy));
89
89
 
90
90
  // Products
91
+ // TODO Remove
91
92
  router.get('/products', mw.authAdminApi, http(api.products.browse));
92
93
  router.post('/products', mw.authAdminApi, http(api.products.add));
93
94
  router.get('/products/:id', mw.authAdminApi, http(api.products.read));
94
95
  router.put('/products/:id', mw.authAdminApi, http(api.products.edit));
95
96
 
97
+ // Tiers
98
+ router.get('/tiers', mw.authAdminApi, http(api.tiers.browse));
99
+ router.post('/tiers', mw.authAdminApi, http(api.tiers.add));
100
+ router.get('/tiers/:id', mw.authAdminApi, http(api.tiers.read));
101
+ router.put('/tiers/:id', mw.authAdminApi, http(api.tiers.edit));
102
+
96
103
  // ## Members
97
104
  router.get('/members', mw.authAdminApi, http(api.members.browse));
98
105
  router.post('/members', mw.authAdminApi, http(api.members.add));
@@ -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));
@@ -27,13 +27,13 @@ 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 isAPI = pathToTest.match(/^(.*\/ghost\/api(\/(v[\d.]+|canary))?\/.*?\/)/i);
31
31
 
32
32
  if (isSignupOrReset) {
33
33
  pathToTest = isSignupOrReset[1];
34
34
  }
35
35
 
36
- // Do not lowercase anything after e.g. /api/v{X}/ to protect :key/:slug
36
+ // Do not lowercase anything after e.g. /ghost/api(/v{X})?/ to protect :key/:slug
37
37
  if (isAPI) {
38
38
  pathToTest = isAPI[1];
39
39
  }
@@ -17,6 +17,7 @@
17
17
  "paths": {
18
18
  "contentPath": "content/",
19
19
  "fixtures": "core/server/data/schema/fixtures/fixtures",
20
+ "defaultSettings": "core/server/data/schema/default-settings/default-settings",
20
21
  "assetSrc": "core/frontend/src"
21
22
  },
22
23
  "adapters": {
@@ -127,8 +128,8 @@
127
128
  "emailAnalytics": true
128
129
  },
129
130
  "portal": {
130
- "url": "https://unpkg.com/@tryghost/portal@~1.14.0/umd/portal.min.js",
131
- "version": "1.14"
131
+ "url": "https://unpkg.com/@tryghost/portal@~1.15.0/umd/portal.min.js",
132
+ "version": "1.15"
132
133
  },
133
134
  "tenor": {
134
135
  "publicReadOnlyApiKey": null,
@@ -6,7 +6,7 @@
6
6
  "helperTemplates": "core/frontend/helpers/tpl/",
7
7
  "adminViews": "core/server/web/admin/views/",
8
8
  "defaultViews": "core/server/views/",
9
- "defaultSettings": "core/server/services/route-settings/",
9
+ "defaultRouteSettings": "core/server/services/route-settings/",
10
10
  "internalAppPath": "core/frontend/apps/",
11
11
  "internalAdaptersPath": "core/server/adapters/",
12
12
  "migrationPath": "core/server/data/migrations",
@@ -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;
@@ -34,7 +34,10 @@ const ALPHA_FEATURES = [
34
34
  'improvedOnboarding',
35
35
  'tierWelcomePages',
36
36
  'tierName',
37
- 'membersTableStatus'
37
+ 'membersTableStatus',
38
+ 'membersLastSeenFilter',
39
+ 'membersContainsFilters',
40
+ 'selectablePortalLinks'
38
41
  ];
39
42
 
40
43
  module.exports.GA_KEYS = [...GA_FEATURES];