ghost 4.32.0 → 4.33.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/LICENSE +1 -1
- package/README.md +1 -1
- package/core/boot.js +3 -0
- package/core/built/assets/{chunk.3.8f95b516d88ff4eec64c.js → chunk.3.4906cf0b01d6d8e33374.js} +134 -130
- package/core/built/assets/{ghost-dark-43f5faa616791819b3ae91e128ec41f0.css → ghost-dark-661a50922267648a0362c3d367a22013.css} +1 -1
- package/core/built/assets/{ghost.min-c3f7cbabcc1a69476534453c6c747ee3.css → ghost.min-1f0218f33e08f8d69b2159977d0c9318.css} +1 -1
- package/core/built/assets/{ghost.min-2b20489c79323b165909749382adc158.js → ghost.min-501554f903f29164473a5dc620caaddb.js} +719 -726
- package/core/built/assets/img/apple-touch-icon-74680e326a7e87b159d366c7d4fb3d4b.png +0 -0
- package/core/built/assets/img/large-ac90af7c93a4b47e8d956fa9fef31d9d.png +0 -0
- package/core/built/assets/img/medium-fef07013cffd5c45a655a250912a0ad7.png +0 -0
- package/core/built/assets/img/small-b90396925485f17b2ca82c31be42de5f.png +0 -0
- package/core/built/assets/img/touch-icon-ipad-2e78629d62ad05746f980f14623dfadb.png +0 -0
- package/core/built/assets/img/touch-icon-iphone-93ed4382d391be9180093fd77ce8f410.png +0 -0
- package/core/built/assets/{vendor.min-987af30228885bce50f05c4723fe6f53.css → vendor.min-2c8ad32b7960bb605ebc20097fee5ebd.css} +1 -1
- package/core/built/assets/{vendor.min-992a9b07f7d0a67b5a4afd91319edf8b.js → vendor.min-d43620e98444a46441495445f4c155f8.js} +1407 -1455
- package/core/frontend/apps/amp/lib/views/amp.hbs +4 -4
- package/core/frontend/helpers/date.js +3 -4
- package/core/frontend/meta/description.js +3 -3
- package/core/frontend/services/routing/config/canary.js +1 -1
- package/core/frontend/services/routing/config/v4.js +1 -1
- package/core/frontend/services/sitemap/base-generator.js +21 -18
- package/core/frontend/services/sitemap/handler.js +13 -4
- package/core/frontend/services/sitemap/index-generator.js +20 -10
- package/core/frontend/services/sitemap/manager.js +8 -5
- package/core/frontend/services/theme-engine/middleware/update-global-template-options.js +3 -1
- package/core/frontend/services/theme-engine/middleware/update-local-template-options.js +1 -6
- package/core/frontend/src/cards/css/audio.css +5 -0
- package/core/frontend/src/cards/css/bookmark.css +5 -0
- package/core/frontend/src/cards/css/button.css +5 -0
- package/core/frontend/src/cards/css/callout.css +5 -0
- package/core/frontend/src/cards/css/file.css +6 -1
- package/core/frontend/src/cards/css/gallery.css +5 -0
- package/core/frontend/src/cards/css/header.css +5 -0
- package/core/frontend/src/cards/css/nft.css +5 -0
- package/core/frontend/src/cards/css/product.css +5 -0
- package/core/frontend/src/cards/css/toggle.css +5 -0
- package/core/frontend/src/cards/css/video.css +4 -0
- package/core/frontend/views/unsubscribe.hbs +12 -7
- package/core/frontend/web/site.js +7 -4
- package/core/server/api/canary/settings.js +2 -1
- package/core/server/api/canary/utils/serializers/output/products.js +4 -0
- package/core/server/data/db/info.js +4 -0
- package/core/server/data/migrations/versions/4.33/2022-01-14-11-50-add-type-column-to-products.js +12 -0
- package/core/server/data/migrations/versions/4.33/2022-01-14-11-51-add-default-free-tier.js +37 -0
- package/core/server/data/migrations/versions/4.33/2022-01-18-09-07-remove-duplicate-offer-redemptions.js +46 -0
- package/core/server/data/migrations/versions/4.33/2022-01-19-10-43-add-active-column-to-products-table.js +7 -0
- package/core/server/data/schema/default-settings.json +1 -1
- package/core/server/data/schema/fixtures/fixtures.json +9 -1
- package/core/server/data/schema/schema.js +2 -0
- package/core/server/models/base/plugins/data-manipulation.js +3 -2
- package/core/server/models/product.js +4 -0
- package/core/server/models/single-use-token.js +1 -1
- package/core/server/models/tag.js +8 -0
- package/core/server/services/mega/template.js +4 -2
- package/core/server/services/members/api.js +2 -16
- package/core/server/services/members/config.js +1 -9
- package/core/server/services/members/middleware.js +5 -3
- package/core/server/services/members/service.js +19 -46
- package/core/server/services/offers/service.js +1 -4
- package/core/server/services/public-config/config.js +3 -2
- package/core/server/services/stripe/config.js +24 -9
- package/core/server/services/stripe/index.js +36 -28
- package/core/server/services/themes/activation-bridge.js +3 -10
- package/core/server/services/themes/index.js +0 -21
- package/core/server/services/twitter-embed.js +1 -2
- package/core/server/update-check.js +2 -1
- package/core/server/web/admin/views/default-prod.html +10 -13
- package/core/server/web/admin/views/default.html +10 -13
- package/core/server/web/api/canary/admin/routes.js +2 -6
- package/core/server/web/members/app.js +3 -2
- package/core/server/web/shared/middleware/cache-control.js +12 -0
- package/core/shared/config/defaults.json +2 -2
- package/core/shared/labs.js +2 -14
- package/package.json +71 -69
- package/yarn.lock +2577 -2997
- package/core/built/assets/img/large-bf46e150380a4979a7389b45f5bb479d.png +0 -0
- package/core/built/assets/img/medium-7359075af28d69523987ff4c0e2067c5.png +0 -0
- package/core/built/assets/img/small-42ff134f320b8b5a6eca3781c4e4b2db.png +0 -0
- package/core/built/assets/img/touch-icon-ipad-3117c0fa950d0fc43c95becef61f4167.png +0 -0
- package/core/built/assets/img/touch-icon-iphone-d2790931c3477664981061ed9fa5242e.png +0 -0
|
@@ -777,11 +777,11 @@
|
|
|
777
777
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
|
|
778
778
|
font-size: 0.95em;
|
|
779
779
|
font-weight: 600;
|
|
780
|
-
text-decoration: none
|
|
780
|
+
text-decoration: none;
|
|
781
781
|
border-radius: 5px;
|
|
782
782
|
transition: opacity 0.2s ease-in-out;
|
|
783
783
|
background-color: var(--ghost-accent-color);
|
|
784
|
-
color: #ffffff
|
|
784
|
+
color: #ffffff;
|
|
785
785
|
margin: 1.75em 0 0;
|
|
786
786
|
}
|
|
787
787
|
|
|
@@ -800,12 +800,12 @@
|
|
|
800
800
|
.kg-header-card.kg-style-image a.kg-header-card-button,
|
|
801
801
|
.kg-header-card.kg-style-dark a.kg-header-card-button {
|
|
802
802
|
background: #ffffff;
|
|
803
|
-
color: #15171a
|
|
803
|
+
color: #15171a;
|
|
804
804
|
}
|
|
805
805
|
|
|
806
806
|
.kg-header-card.kg-style-accent a.kg-header-card-button {
|
|
807
807
|
background: #ffffff;
|
|
808
|
-
color: var(--ghost-accent-color)
|
|
808
|
+
color: var(--ghost-accent-color);
|
|
809
809
|
}
|
|
810
810
|
|
|
811
811
|
.kg-audio-card {
|
|
@@ -25,12 +25,11 @@ module.exports = function (...attrs) {
|
|
|
25
25
|
// ensure that date is undefined, not null, as that can cause errors
|
|
26
26
|
date = date === null ? undefined : date;
|
|
27
27
|
|
|
28
|
-
const timezone = options.data.site.timezone;
|
|
29
|
-
const locale = options.data.site.locale;
|
|
30
|
-
|
|
31
28
|
const {
|
|
32
29
|
format = 'll',
|
|
33
|
-
timeago
|
|
30
|
+
timeago,
|
|
31
|
+
timezone = options.data.site.timezone,
|
|
32
|
+
locale = options.data.site.locale
|
|
34
33
|
} = options.hash;
|
|
35
34
|
|
|
36
35
|
const timeNow = moment().tz(timezone);
|
|
@@ -51,7 +51,7 @@ function getDescription(data, root, options = {}) {
|
|
|
51
51
|
|| settingsCache.get('description')
|
|
52
52
|
|| '';
|
|
53
53
|
} else {
|
|
54
|
-
description = data.post.meta_description || '';
|
|
54
|
+
description = data.post.meta_description || data.post.custom_excerpt || '';
|
|
55
55
|
}
|
|
56
56
|
} else if (_.includes(context, 'page') && data.post) {
|
|
57
57
|
// Page description dependent on legacy object formatting (https://github.com/TryGhost/Ghost/issues/10042)
|
|
@@ -63,7 +63,7 @@ function getDescription(data, root, options = {}) {
|
|
|
63
63
|
|| settingsCache.get('description')
|
|
64
64
|
|| '';
|
|
65
65
|
} else {
|
|
66
|
-
description = data.post.meta_description || '';
|
|
66
|
+
description = data.post.meta_description || data.post.custom_excerpt || '';
|
|
67
67
|
}
|
|
68
68
|
} else if (_.includes(context, 'page') && data.page) {
|
|
69
69
|
if (options.property) {
|
|
@@ -74,7 +74,7 @@ function getDescription(data, root, options = {}) {
|
|
|
74
74
|
|| settingsCache.get('description')
|
|
75
75
|
|| '';
|
|
76
76
|
} else {
|
|
77
|
-
description = data.page.meta_description || '';
|
|
77
|
+
description = data.page.meta_description || data.page.custom_excerpt || '';
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
|
|
@@ -17,12 +17,12 @@ class BaseSiteMapGenerator {
|
|
|
17
17
|
constructor() {
|
|
18
18
|
this.nodeLookup = {};
|
|
19
19
|
this.nodeTimeLookup = {};
|
|
20
|
-
this.siteMapContent =
|
|
20
|
+
this.siteMapContent = new Map();
|
|
21
21
|
this.lastModified = 0;
|
|
22
|
-
this.
|
|
22
|
+
this.maxPerPage = 50000;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
generateXmlFromNodes() {
|
|
25
|
+
generateXmlFromNodes(page) {
|
|
26
26
|
// Get a mapping of node to timestamp
|
|
27
27
|
let nodesToProcess = _.map(this.nodeLookup, (node, id) => {
|
|
28
28
|
return {
|
|
@@ -33,20 +33,23 @@ class BaseSiteMapGenerator {
|
|
|
33
33
|
};
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
-
// Limit to 50k nodes - this is a quick fix to prevent errors in google console
|
|
37
|
-
if (this.maxNodes) {
|
|
38
|
-
nodesToProcess = nodesToProcess.slice(0, this.maxNodes);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
36
|
// Sort nodes by timestamp
|
|
42
37
|
nodesToProcess = _.sortBy(nodesToProcess, 'ts');
|
|
43
38
|
|
|
39
|
+
// Get the page of nodes that was requested
|
|
40
|
+
nodesToProcess = nodesToProcess.slice((page - 1) * this.maxPerPage, page * this.maxPerPage);
|
|
41
|
+
|
|
42
|
+
// Do not generate empty sitemaps
|
|
43
|
+
if (nodesToProcess.length === 0) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
44
47
|
// Grab just the nodes
|
|
45
|
-
|
|
48
|
+
const nodes = _.map(nodesToProcess, 'node');
|
|
46
49
|
|
|
47
50
|
const data = {
|
|
48
51
|
// Concat the elements to the _attr declaration
|
|
49
|
-
urlset: [XMLNS_DECLS].concat(
|
|
52
|
+
urlset: [XMLNS_DECLS].concat(nodes)
|
|
50
53
|
};
|
|
51
54
|
|
|
52
55
|
// Generate full xml
|
|
@@ -67,7 +70,7 @@ class BaseSiteMapGenerator {
|
|
|
67
70
|
this.updateLastModified(datum);
|
|
68
71
|
this.updateLookups(datum, node);
|
|
69
72
|
// force regeneration of xml
|
|
70
|
-
this.siteMapContent
|
|
73
|
+
this.siteMapContent.clear();
|
|
71
74
|
}
|
|
72
75
|
}
|
|
73
76
|
|
|
@@ -75,7 +78,7 @@ class BaseSiteMapGenerator {
|
|
|
75
78
|
this.removeFromLookups(datum);
|
|
76
79
|
|
|
77
80
|
// force regeneration of xml
|
|
78
|
-
this.siteMapContent
|
|
81
|
+
this.siteMapContent.clear();
|
|
79
82
|
this.lastModified = Date.now();
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -152,13 +155,13 @@ class BaseSiteMapGenerator {
|
|
|
152
155
|
return !!imageUrl;
|
|
153
156
|
}
|
|
154
157
|
|
|
155
|
-
getXml() {
|
|
156
|
-
if (this.siteMapContent) {
|
|
157
|
-
return this.siteMapContent;
|
|
158
|
+
getXml(page = 1) {
|
|
159
|
+
if (this.siteMapContent.has(page)) {
|
|
160
|
+
return this.siteMapContent.get(page);
|
|
158
161
|
}
|
|
159
162
|
|
|
160
|
-
const content = this.generateXmlFromNodes();
|
|
161
|
-
this.siteMapContent
|
|
163
|
+
const content = this.generateXmlFromNodes(page);
|
|
164
|
+
this.siteMapContent.set(page, content);
|
|
162
165
|
return content;
|
|
163
166
|
}
|
|
164
167
|
|
|
@@ -181,7 +184,7 @@ class BaseSiteMapGenerator {
|
|
|
181
184
|
reset() {
|
|
182
185
|
this.nodeLookup = {};
|
|
183
186
|
this.nodeTimeLookup = {};
|
|
184
|
-
this.siteMapContent
|
|
187
|
+
this.siteMapContent.clear();
|
|
185
188
|
}
|
|
186
189
|
}
|
|
187
190
|
|
|
@@ -5,7 +5,8 @@ const manager = new Manager();
|
|
|
5
5
|
// Responsible for handling requests for sitemap files
|
|
6
6
|
module.exports = function handler(siteApp) {
|
|
7
7
|
const verifyResourceType = function verifyResourceType(req, res, next) {
|
|
8
|
-
|
|
8
|
+
const resourceWithoutPage = req.params.resource.replace(/-\d+$/, '');
|
|
9
|
+
if (!Object.prototype.hasOwnProperty.call(manager, resourceWithoutPage)) {
|
|
9
10
|
return res.sendStatus(404);
|
|
10
11
|
}
|
|
11
12
|
|
|
@@ -22,14 +23,22 @@ module.exports = function handler(siteApp) {
|
|
|
22
23
|
});
|
|
23
24
|
|
|
24
25
|
siteApp.get('/sitemap-:resource.xml', verifyResourceType, function sitemapResourceXML(req, res) {
|
|
25
|
-
const type = req.params.resource;
|
|
26
|
-
const
|
|
26
|
+
const type = req.params.resource.replace(/-\d+$/, '');
|
|
27
|
+
const pageParam = (req.params.resource.match(/-(\d+)$/) || [null, null])[1];
|
|
28
|
+
const page = pageParam ? parseInt(pageParam, 10) : 1;
|
|
29
|
+
|
|
30
|
+
const content = manager.getSiteMapXml(type, page);
|
|
31
|
+
// Prevent x-1.xml as it is a duplicate of x.xml and empty sitemaps
|
|
32
|
+
// (except for the first page so that at least one sitemap exists per type)
|
|
33
|
+
if (pageParam === '1' || (!content && page !== 1)) {
|
|
34
|
+
return res.sendStatus(404);
|
|
35
|
+
}
|
|
27
36
|
|
|
28
37
|
res.set({
|
|
29
38
|
'Cache-Control': 'public, max-age=' + config.get('caching:sitemap:maxAge'),
|
|
30
39
|
'Content-Type': 'text/xml'
|
|
31
40
|
});
|
|
32
41
|
|
|
33
|
-
res.send(
|
|
42
|
+
res.send(content);
|
|
34
43
|
});
|
|
35
44
|
};
|
|
@@ -14,6 +14,7 @@ class SiteMapIndexGenerator {
|
|
|
14
14
|
constructor(options) {
|
|
15
15
|
options = options || {};
|
|
16
16
|
this.types = options.types;
|
|
17
|
+
this.maxPerPage = options.maxPerPage;
|
|
17
18
|
}
|
|
18
19
|
|
|
19
20
|
getXml() {
|
|
@@ -30,16 +31,25 @@ class SiteMapIndexGenerator {
|
|
|
30
31
|
|
|
31
32
|
generateSiteMapUrlElements() {
|
|
32
33
|
return _.map(this.types, (resourceType) => {
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
34
|
+
// `|| 1` = even if there are no items we still have an empty sitemap file
|
|
35
|
+
const noOfPages = Math.ceil(Object.keys(resourceType.nodeLookup).length / this.maxPerPage) || 1;
|
|
36
|
+
const pages = [];
|
|
37
|
+
|
|
38
|
+
for (let i = 0; i < noOfPages; i++) {
|
|
39
|
+
const page = i === 0 ? '' : `-${i + 1}`;
|
|
40
|
+
const url = urlUtils.urlFor({relativeUrl: '/sitemap-' + resourceType.name + page + '.xml'}, true);
|
|
41
|
+
const lastModified = resourceType.lastModified;
|
|
42
|
+
|
|
43
|
+
pages.push({
|
|
44
|
+
sitemap: [
|
|
45
|
+
{loc: url},
|
|
46
|
+
{lastmod: moment(lastModified).toISOString()}
|
|
47
|
+
]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return pages;
|
|
52
|
+
}).flat();
|
|
43
53
|
}
|
|
44
54
|
}
|
|
45
55
|
|
|
@@ -11,11 +11,13 @@ class SiteMapManager {
|
|
|
11
11
|
constructor(options) {
|
|
12
12
|
options = options || {};
|
|
13
13
|
|
|
14
|
+
options.maxPerPage = options.maxPerPage || 50000;
|
|
15
|
+
|
|
14
16
|
this.pages = options.pages || this.createPagesGenerator(options);
|
|
15
17
|
this.posts = options.posts || this.createPostsGenerator(options);
|
|
16
18
|
this.users = this.authors = options.authors || this.createUsersGenerator(options);
|
|
17
19
|
this.tags = options.tags || this.createTagsGenerator(options);
|
|
18
|
-
this.index = options.index || this.createIndexGenerator();
|
|
20
|
+
this.index = options.index || this.createIndexGenerator(options);
|
|
19
21
|
|
|
20
22
|
events.on('router.created', (router) => {
|
|
21
23
|
if (router.name === 'StaticRoutesRouter') {
|
|
@@ -43,14 +45,15 @@ class SiteMapManager {
|
|
|
43
45
|
});
|
|
44
46
|
}
|
|
45
47
|
|
|
46
|
-
createIndexGenerator() {
|
|
48
|
+
createIndexGenerator(options) {
|
|
47
49
|
return new IndexMapGenerator({
|
|
48
50
|
types: {
|
|
49
51
|
pages: this.pages,
|
|
50
52
|
posts: this.posts,
|
|
51
53
|
authors: this.authors,
|
|
52
54
|
tags: this.tags
|
|
53
|
-
}
|
|
55
|
+
},
|
|
56
|
+
maxPerPage: options.maxPerPage
|
|
54
57
|
});
|
|
55
58
|
}
|
|
56
59
|
|
|
@@ -74,8 +77,8 @@ class SiteMapManager {
|
|
|
74
77
|
return this.index.getXml();
|
|
75
78
|
}
|
|
76
79
|
|
|
77
|
-
getSiteMapXml(type) {
|
|
78
|
-
return this[type].getXml();
|
|
80
|
+
getSiteMapXml(type, page) {
|
|
81
|
+
return this[type].getXml(page);
|
|
79
82
|
}
|
|
80
83
|
}
|
|
81
84
|
|
|
@@ -28,7 +28,9 @@ function calculateLegacyPriceData(products) {
|
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
-
const defaultProduct = products
|
|
31
|
+
const defaultProduct = products.find((product) => {
|
|
32
|
+
return product.type === 'paid';
|
|
33
|
+
}) || {};
|
|
32
34
|
|
|
33
35
|
const monthlyPrice = makePriceObject(defaultProduct.monthly_price || defaultPrice);
|
|
34
36
|
|
|
@@ -2,7 +2,6 @@ const _ = require('lodash');
|
|
|
2
2
|
const hbs = require('../engine');
|
|
3
3
|
const urlUtils = require('../../../../shared/url-utils');
|
|
4
4
|
const customThemeSettingsCache = require('../../../../shared/custom-theme-settings-cache');
|
|
5
|
-
const labs = require('../../../../shared/labs');
|
|
6
5
|
const preview = require('../preview');
|
|
7
6
|
|
|
8
7
|
function updateLocalTemplateOptions(req, res, next) {
|
|
@@ -17,12 +16,8 @@ function updateLocalTemplateOptions(req, res, next) {
|
|
|
17
16
|
const previewData = preview.handle(req, Object.keys(customThemeSettingsCache.getAll()));
|
|
18
17
|
|
|
19
18
|
// strip custom off of preview data so it doesn't get merged into @site
|
|
20
|
-
const
|
|
19
|
+
const customData = previewData.custom;
|
|
21
20
|
delete previewData.custom;
|
|
22
|
-
let customData = {};
|
|
23
|
-
if (labs.isSet('customThemeSettings')) {
|
|
24
|
-
customData = customThemeSettingsPreviewData;
|
|
25
|
-
}
|
|
26
21
|
|
|
27
22
|
// update site data with any preview values from the request
|
|
28
23
|
Object.assign(siteData, previewData);
|
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
.kg-file-card,
|
|
2
|
+
.kg-file-card * {
|
|
3
|
+
box-sizing: border-box;
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
.kg-file-card {
|
|
2
7
|
display: flex;
|
|
3
8
|
}
|
|
@@ -41,7 +46,7 @@
|
|
|
41
46
|
}
|
|
42
47
|
|
|
43
48
|
.kg-file-card-title + .kg-file-card-caption {
|
|
44
|
-
margin-top: -
|
|
49
|
+
margin-top: -3px;
|
|
45
50
|
}
|
|
46
51
|
|
|
47
52
|
.kg-file-card-metadata {
|
|
@@ -33,16 +33,21 @@
|
|
|
33
33
|
</a>
|
|
34
34
|
</nav>
|
|
35
35
|
</header>
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
<div class="gh-flow-content-wrap">
|
|
38
38
|
<section class="gh-flow-content gh-flow-content-unsubscribe">
|
|
39
|
-
|
|
40
|
-
{{
|
|
41
|
-
|
|
42
|
-
{{
|
|
43
|
-
|
|
39
|
+
{{#if error}}
|
|
40
|
+
<p>{{error}}</p>
|
|
41
|
+
{{else}}
|
|
42
|
+
{{#if member}}
|
|
43
|
+
<p>
|
|
44
|
+
<span class="gh-flow-em">{{member.email}}</span> has been successfully unsubscribed from emails.
|
|
45
|
+
<br>
|
|
46
|
+
Don't worry, this will not cancel your paid subscription to {{@site.title}}.
|
|
47
|
+
</p>
|
|
48
|
+
<p>Didn't mean to do this? Manage your account <a href="{{@site.url}}/#/portal/account">here</a>.</p>
|
|
44
49
|
{{/if}}
|
|
45
|
-
|
|
50
|
+
{{/if}}
|
|
46
51
|
</section>
|
|
47
52
|
</div>
|
|
48
53
|
</div>
|
|
@@ -22,7 +22,6 @@ const siteRoutes = require('./routes');
|
|
|
22
22
|
const shared = require('../../server/web/shared');
|
|
23
23
|
const errorHandler = require('@tryghost/mw-error-handler');
|
|
24
24
|
const mw = require('./middleware');
|
|
25
|
-
const labs = require('../../shared/labs');
|
|
26
25
|
|
|
27
26
|
const STATIC_IMAGE_URL_PREFIX = `/${urlUtils.STATIC_IMAGE_URL_PREFIX}`;
|
|
28
27
|
const STATIC_MEDIA_URL_PREFIX = `/${constants.STATIC_MEDIA_URL_PREFIX}`;
|
|
@@ -118,15 +117,19 @@ module.exports = function setupSiteApp(options = {}) {
|
|
|
118
117
|
// Serve blog images using the storage adapter
|
|
119
118
|
siteApp.use(STATIC_IMAGE_URL_PREFIX, mw.handleImageSizes, storage.getStorage('images').serve());
|
|
120
119
|
// Serve blog media using the storage adapter
|
|
121
|
-
siteApp.use(STATIC_MEDIA_URL_PREFIX,
|
|
120
|
+
siteApp.use(STATIC_MEDIA_URL_PREFIX, storage.getStorage('media').serve());
|
|
122
121
|
// Serve blog files using the storage adapter
|
|
123
|
-
siteApp.use(STATIC_FILES_URL_PREFIX,
|
|
122
|
+
siteApp.use(STATIC_FILES_URL_PREFIX, storage.getStorage('files').serve());
|
|
124
123
|
|
|
125
124
|
// Global handling for member session, ensures a member is logged in to the frontend
|
|
126
125
|
siteApp.use(membersService.middleware.loadMemberSession);
|
|
127
126
|
|
|
128
127
|
// /member/.well-known/* serves files (e.g. jwks.json) so it needs to be mounted before the prettyUrl mw to avoid trailing slashes
|
|
129
|
-
siteApp.use(
|
|
128
|
+
siteApp.use(
|
|
129
|
+
'/members/.well-known',
|
|
130
|
+
shared.middleware.cacheControl('public', {maxAge: 60 * 60 * 24}),
|
|
131
|
+
(req, res, next) => membersService.api.middleware.wellKnown(req, res, next)
|
|
132
|
+
);
|
|
130
133
|
|
|
131
134
|
// setup middleware for internal apps
|
|
132
135
|
// @TODO: refactor this to be a proper app middleware hook for internal apps
|
|
@@ -6,6 +6,7 @@ const tpl = require('@tryghost/tpl');
|
|
|
6
6
|
const {BadRequestError} = require('@tryghost/errors');
|
|
7
7
|
const settingsService = require('../../services/settings');
|
|
8
8
|
const membersService = require('../../services/members');
|
|
9
|
+
const stripeService = require('../../services/stripe');
|
|
9
10
|
|
|
10
11
|
const settingsBREADService = settingsService.getSettingsBREADServiceInstance();
|
|
11
12
|
|
|
@@ -132,7 +133,7 @@ module.exports = {
|
|
|
132
133
|
});
|
|
133
134
|
}
|
|
134
135
|
|
|
135
|
-
await
|
|
136
|
+
await stripeService.disconnect();
|
|
136
137
|
|
|
137
138
|
return models.Settings.edit([{
|
|
138
139
|
key: 'stripe_connect_publishable_key',
|
|
@@ -73,6 +73,8 @@ function serializeProduct(product, options, apiType) {
|
|
|
73
73
|
name: json.name,
|
|
74
74
|
description: json.description,
|
|
75
75
|
slug: json.slug,
|
|
76
|
+
active: json.active,
|
|
77
|
+
type: json.type,
|
|
76
78
|
created_at: json.created_at,
|
|
77
79
|
updated_at: json.updated_at,
|
|
78
80
|
stripe_prices: json.stripePrices ? json.stripePrices.map(price => serializeStripePrice(price, hideStripeData)) : null,
|
|
@@ -160,6 +162,8 @@ function createSerializer(debugString, serialize) {
|
|
|
160
162
|
* @prop {string} name
|
|
161
163
|
* @prop {string} slug
|
|
162
164
|
* @prop {string} description
|
|
165
|
+
* @prop {boolean} active
|
|
166
|
+
* @prop {string} type
|
|
163
167
|
* @prop {Date} created_at
|
|
164
168
|
* @prop {Date} updated_at
|
|
165
169
|
* @prop {StripePrice[]} [stripe_prices]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
const {createTransactionalMigration} = require('../../utils');
|
|
2
|
+
const ObjectID = require('bson-objectid');
|
|
3
|
+
const {slugify} = require('@tryghost/string');
|
|
4
|
+
const logging = require('@tryghost/logging');
|
|
5
|
+
|
|
6
|
+
module.exports = createTransactionalMigration(
|
|
7
|
+
async function up(knex) {
|
|
8
|
+
const [result] = await knex
|
|
9
|
+
.count('id', {as: 'total'})
|
|
10
|
+
.from('products')
|
|
11
|
+
.where('type', 'free');
|
|
12
|
+
|
|
13
|
+
if (result.total !== 0) {
|
|
14
|
+
logging.warn(`Not adding default free tier, a free tier already exists`);
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const name = 'Free';
|
|
19
|
+
const id = ObjectID().toHexString();
|
|
20
|
+
|
|
21
|
+
logging.info(`Adding tier "${name}"`);
|
|
22
|
+
await knex('products')
|
|
23
|
+
.insert({
|
|
24
|
+
id: id,
|
|
25
|
+
name: name,
|
|
26
|
+
type: 'free',
|
|
27
|
+
slug: slugify(id),
|
|
28
|
+
created_at: knex.raw(`CURRENT_TIMESTAMP`)
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
async function down(knex) {
|
|
32
|
+
logging.info('Removing free tier');
|
|
33
|
+
await knex('products')
|
|
34
|
+
.where('type', 'free')
|
|
35
|
+
.del();
|
|
36
|
+
}
|
|
37
|
+
);
|