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.
- package/.c8rc.json +1 -1
- package/README.md +26 -18
- package/content/themes/casper/LICENSE +1 -1
- package/content/themes/casper/README.md +1 -1
- package/content/themes/casper/assets/built/global.css +1 -1
- package/content/themes/casper/assets/built/global.css.map +1 -1
- package/content/themes/casper/assets/built/screen.css +1 -1
- package/content/themes/casper/assets/built/screen.css.map +1 -1
- package/content/themes/casper/assets/css/global.css +14 -6
- package/content/themes/casper/assets/css/screen.css +9 -1
- package/content/themes/casper/package.json +2 -2
- package/content/themes/casper/partials/post-card.hbs +1 -1
- package/content/themes/casper/post.hbs +18 -19
- package/content/themes/casper/yarn.lock +186 -217
- package/core/built/assets/{chunk.3.4906cf0b01d6d8e33374.js → chunk.3.6e2ed2d00856e12bd81a.js} +19 -19
- package/core/built/assets/ghost-dark-498ff8339a89bb68c3f78f59bee4146e.css +1 -0
- package/core/built/assets/ghost.min-77b93478f83b0def6ddc5a4f23ce963e.css +1 -0
- package/core/built/assets/{ghost.min-c1938f6ee696bf08bd6bf93cac341ea2.js → ghost.min-e6559d901897066aa6a6d4145e3728ed.js} +466 -413
- package/core/built/assets/icons/{event-changed-subscription.svg → event-subscriptions.svg} +0 -1
- package/core/built/assets/icons/eye.svg +4 -1
- package/core/built/assets/icons/member-add.svg +3 -0
- package/core/built/assets/icons/member.svg +3 -0
- package/core/built/assets/icons/pin.svg +4 -1
- package/core/built/assets/{vendor.min-6dc30be68238b5c55df0cdc1f2dc8b8d.js → vendor.min-c39476bced9adb98ee2b292d01c7a8f4.js} +2303 -1372
- package/core/frontend/apps/private-blogging/lib/middleware.js +1 -1
- package/core/frontend/apps/private-blogging/lib/views/private.hbs +9 -10
- package/core/frontend/helpers/get.js +4 -0
- package/core/frontend/helpers/match.js +12 -0
- package/core/frontend/helpers/prev_post.js +11 -1
- package/core/frontend/helpers/tiers.js +59 -0
- package/core/frontend/helpers/tpl/content-cta.hbs +1 -1
- package/core/frontend/public/ghost.css +205 -143
- package/core/frontend/services/routing/router-manager.js +1 -1
- package/core/frontend/views/unsubscribe.hbs +28 -33
- package/core/frontend/web/middleware/error-handler.js +2 -2
- package/core/frontend/web/site.js +10 -0
- package/core/server/api/canary/authentication.js +7 -0
- package/core/server/api/canary/index.js +4 -0
- package/core/server/api/canary/members.js +9 -2
- package/core/server/api/canary/products.js +3 -6
- package/core/server/api/canary/tiers-public.js +34 -0
- package/core/server/api/canary/tiers.js +6 -7
- package/core/server/api/canary/utils/serializers/input/pages.js +1 -1
- package/core/server/api/canary/utils/serializers/input/posts.js +1 -1
- package/core/server/api/canary/utils/serializers/output/email-posts.js +7 -1
- package/core/server/api/canary/utils/serializers/output/pages.js +9 -2
- package/core/server/api/canary/utils/serializers/output/posts.js +8 -2
- package/core/server/api/canary/utils/serializers/output/preview.js +7 -1
- package/core/server/api/canary/utils/serializers/output/products.js +3 -1
- package/core/server/api/canary/utils/serializers/output/tiers.js +4 -2
- package/core/server/api/canary/utils/serializers/output/utils/mapper.js +17 -7
- package/core/server/api/shared/serializers/handle.js +2 -2
- package/core/server/api/v2/utils/serializers/input/pages.js +1 -1
- package/core/server/api/v2/utils/serializers/input/posts.js +1 -1
- package/core/server/api/v3/utils/serializers/input/pages.js +1 -1
- package/core/server/api/v3/utils/serializers/input/posts.js +1 -1
- package/core/server/data/db/connection.js +3 -2
- package/core/server/data/importer/import-manager.js +152 -113
- package/core/server/data/migrations/versions/3.29/01-remove-duplicate-subscriptions.js +2 -1
- package/core/server/data/migrations/versions/3.29/02-remove-duplicate-customers.js +2 -1
- package/core/server/data/migrations/versions/3.29/03-remove-orphaned-customers.js +2 -1
- package/core/server/data/migrations/versions/3.29/04-remove-orphaned-subscriptions.js +2 -1
- package/core/server/data/migrations/versions/3.29/05-add-member-constraints.js +3 -2
- package/core/server/data/migrations/versions/3.39/06-add-email-recipient-index.js +4 -3
- package/core/server/data/migrations/versions/4.0/14-remove-orphaned-stripe-records.js +2 -1
- package/core/server/data/migrations/versions/4.0/26-add-cascade-on-delete.js +2 -1
- package/core/server/data/migrations/versions/4.0/29-fix-foreign-key-for-members-stripe-customers-subscriptions.js +2 -1
- package/core/server/data/migrations/versions/4.1/02-add-unique-constraint-for-member-stripe-tables.js +2 -1
- package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +3 -2
- package/core/server/data/migrations/versions/4.33/2022-01-14-11-51-add-default-free-tier.js +3 -0
- package/core/server/data/migrations/versions/4.33/2022-01-18-09-07-remove-duplicate-offer-redemptions.js +2 -2
- package/core/server/data/migrations/versions/4.35/2022-02-01-11-48-update-email-recipient-filter-column-type.js +2 -1
- package/core/server/data/migrations/versions/4.35/2022-02-01-12-03-update-recipient-filter-column-type.js +2 -1
- package/core/server/data/migrations/versions/4.37/2022-02-21-09-53-backfill-members-last-seen-at-column.js +3 -2
- package/core/server/data/migrations/versions/4.38/2022-03-01-08-46-add-visibility-to-tiers.js +11 -0
- package/core/server/data/migrations/versions/4.38/2022-03-03-16-12-add-visibility-to-tiers.js +8 -0
- package/core/server/data/migrations/versions/4.38/2022-03-03-16-17-drop-tiers-visible-column.js +7 -0
- package/core/server/data/migrations/versions/4.39/2022-03-07-10-57-update-free-products-visibility-column.js +66 -0
- package/core/server/data/migrations/versions/4.39/2022-03-07-10-57-update-products-visibility-column.js +36 -0
- package/core/server/data/schema/clients/index.js +1 -1
- package/core/server/data/schema/clients/mysql.js +4 -4
- package/core/server/data/schema/commands.js +42 -50
- package/core/server/data/schema/default-settings/default-settings.json +2 -2
- package/core/server/data/schema/fixtures/fixtures.json +18 -161
- package/core/server/data/schema/schema.js +7 -0
- package/core/server/frontend/ghost.min.css +1 -1
- package/core/server/lib/image/image-size.js +12 -4
- package/core/server/models/base/plugins/generate-slug.js +13 -1
- package/core/server/models/base/plugins/raw-knex.js +1 -1
- package/core/server/models/post.js +16 -6
- package/core/server/models/product.js +2 -1
- package/core/server/models/user.js +1 -1
- package/core/server/services/auth/api-key/admin.js +15 -6
- package/core/server/services/auth/setup.js +34 -13
- package/core/server/services/email-analytics/lib/event-processor.js +18 -1
- package/core/server/services/mega/mega.js +4 -4
- package/core/server/services/mega/template.js +1 -1
- package/core/server/services/members/content-gating.js +1 -1
- package/core/server/services/members/middleware.js +4 -0
- package/core/server/services/members/service.js +13 -1
- package/core/server/services/posts/posts-service.js +1 -1
- package/core/server/services/url/UrlGenerator.js +1 -1
- package/core/server/services/webhooks/webhooks-service.js +2 -0
- package/core/server/views/maintenance.html +2 -2
- package/core/server/web/admin/views/default-prod.html +4 -4
- package/core/server/web/admin/views/default.html +4 -4
- package/core/server/web/api/app.js +3 -0
- package/core/server/web/api/canary/admin/middleware.js +2 -0
- package/core/server/web/api/canary/content/routes.js +1 -0
- package/core/server/web/members/app.js +1 -1
- package/core/server/web/parent/backend.js +2 -1
- package/core/server/web/shared/middleware/uncapitalise.js +3 -2
- package/core/shared/config/defaults.json +2 -2
- package/core/shared/config/utils.js +5 -1
- package/core/shared/labs.js +8 -9
- package/core/shared/url-utils.js +4 -1
- package/package.json +56 -52
- package/yarn.lock +809 -607
- package/core/built/assets/ghost-dark-d54723f7267e66fa2595f897076e86c2.css +0 -1
- package/core/built/assets/ghost.min-02a5f8954bd85fe28817b8c8b111b8aa.css +0 -1
- package/core/built/assets/icons/event-started-subscription.svg +0 -6
- package/core/built/assets/icons/locked-email-back.svg +0 -1
- package/core/built/assets/icons/locked-email-front.svg +0 -1
- package/core/built/assets/icons/locked-email-lock.svg +0 -1
- 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:
|
|
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
|
-
|
|
203
|
-
|
|
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: '
|
|
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' : '
|
|
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: '
|
|
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
|
|
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="
|
|
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>
|
|
@@ -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();
|
|
@@ -44,7 +44,7 @@ body {
|
|
|
44
44
|
flex-direction: column;
|
|
45
45
|
justify-content: center;
|
|
46
46
|
max-width: 500px;
|
|
47
|
-
min-height:
|
|
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
|
|
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.
|
|
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-
|
|
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-
|
|
60
|
-
<script src="assets/ghost.min-
|
|
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.
|
|
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-
|
|
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-
|
|
60
|
-
<script src="assets/ghost.min-
|
|
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));
|
|
@@ -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',
|
|
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(
|
|
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
|
|
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}
|
|
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.
|
|
132
|
-
"version": "1.
|
|
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') === '
|
|
63
|
+
if (nconf.get('database:client') === 'mysql2') {
|
|
60
64
|
delete database.connection.filename;
|
|
61
65
|
} else {
|
|
62
66
|
delete database.connection.host;
|
package/core/shared/labs.js
CHANGED
|
@@ -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];
|
package/core/shared/url-utils.js
CHANGED
|
@@ -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:
|
|
14
|
+
baseApiPath: BASE_API_PATH
|
|
13
15
|
});
|
|
14
16
|
|
|
15
17
|
module.exports = urlUtils;
|
|
18
|
+
module.exports.BASE_API_PATH = BASE_API_PATH;
|