ghost 4.22.2 → 4.24.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 +24 -0
- package/Gruntfile.js +0 -1
- package/content/public/README.md +3 -0
- package/core/app.js +12 -1
- package/core/boot.js +45 -26
- package/core/bridge.js +10 -10
- package/core/built/assets/{chunk.3.324fd0cc598c73650219.js → chunk.3.8f95b516d88ff4eec64c.js} +18 -18
- package/core/built/assets/{ghost-dark-39fb496d051565531062d7e047d1c0b1.css → ghost-dark-e7b57ab951512c5719aee89b16b9a448.css} +1 -1
- package/core/built/assets/{ghost.min-4207edfc1ae0a3f9f6505ca00d20b0c0.css → ghost.min-7f3603dbeb5ebf0ec09e207ae82fb4e3.css} +1 -1
- package/core/built/assets/{ghost.min-7da921f6c6cac3fe10da1ba104575440.js → ghost.min-d5595f9c71ebc534ccf9ac78483d357c.js} +138 -105
- package/core/built/assets/icons/powered-by-tenor.svg +35 -0
- package/core/built/assets/icons/tenor.svg +7 -0
- package/core/built/assets/{vendor.min-413f887176a041e6dbf88214ca9a7481.js → vendor.min-1a84ac3ef74edf31c6e86810b45221cc.js} +2964 -2434
- package/core/frontend/apps/amp/lib/views/amp.hbs +104 -0
- package/core/frontend/apps/private-blogging/lib/router.js +1 -1
- package/core/frontend/services/card-assets/index.js +0 -12
- package/core/frontend/services/card-assets/service.js +35 -26
- package/core/frontend/services/routing/CollectionRouter.js +4 -5
- package/core/frontend/services/routing/EmailRouter.js +1 -1
- package/core/frontend/services/routing/ParentRouter.js +0 -8
- package/core/frontend/services/routing/PreviewRouter.js +1 -1
- package/core/frontend/services/routing/StaticPagesRouter.js +1 -1
- package/core/frontend/services/routing/StaticRoutesRouter.js +4 -4
- package/core/frontend/services/routing/TaxonomyRouter.js +3 -3
- package/core/frontend/services/routing/{middlewares → middleware}/index.js +0 -0
- package/core/frontend/services/routing/{middlewares → middleware}/page-param.js +0 -0
- package/core/frontend/services/routing/router-manager.js +7 -2
- package/core/frontend/services/rss/generate-feed.js +2 -1
- package/core/frontend/src/cards/css/bookmark.css +72 -47
- package/core/frontend/src/cards/css/callout.css +41 -4
- package/core/frontend/src/cards/css/gallery.css +15 -10
- package/core/frontend/src/cards/css/nft.css +20 -11
- package/core/frontend/src/cards/css/toggle.css +58 -0
- package/core/frontend/src/cards/js/toggle.js +16 -0
- package/core/frontend/web/middleware/serve-public-file.js +39 -16
- package/core/frontend/web/site.js +11 -14
- package/core/server/api/canary/authentication.js +1 -1
- package/core/server/api/canary/utils/serializers/output/config.js +1 -1
- package/core/server/api/v2/authentication.js +1 -1
- package/core/server/api/v3/authentication.js +1 -1
- package/core/server/data/db/connection.js +7 -0
- package/core/server/data/importer/importers/data/data-importer.js +3 -3
- package/core/server/data/migrations/init/2-create-fixtures.js +3 -20
- package/core/server/data/migrations/versions/1.21/1-add-contributor-role.js +5 -5
- package/core/server/data/migrations/versions/2.15/2-insert-zapier-integration.js +3 -3
- package/core/server/data/migrations/versions/2.2/3-insert-admin-integration-role.js +5 -5
- package/core/server/data/migrations/versions/2.27/1-insert-ghost-db-backup-role.js +5 -6
- package/core/server/data/migrations/versions/2.27/2-insert-db-backup-integration.js +3 -4
- package/core/server/data/migrations/versions/2.28/3-insert-ghost-scheduler-role.js +7 -7
- package/core/server/data/migrations/versions/2.28/4-insert-scheduler-integration.js +3 -3
- package/core/server/data/migrations/versions/4.23/01-truncate-offer-names.js +58 -0
- package/core/server/data/schema/fixtures/fixture-manager.js +340 -0
- package/core/server/data/schema/fixtures/index.js +8 -2
- package/core/server/services/email-analytics/jobs/index.js +1 -1
- package/core/server/services/mega/post-email-serializer.js +5 -1
- package/core/server/services/mega/segment-parser.js +1 -2
- package/core/server/services/mega/template.js +52 -37
- package/core/server/services/nft-oembed.js +7 -21
- package/core/server/services/oembed.js +24 -24
- package/core/server/services/public-config/config.js +1 -1
- package/core/server/services/redirects/api.js +18 -23
- package/core/server/services/redirects/index.js +18 -10
- package/core/server/services/redirects/utils.js +14 -0
- package/core/server/services/redirects/validation.js +10 -0
- package/core/server/services/route-settings/index.js +40 -17
- package/core/server/services/route-settings/route-settings.js +127 -114
- package/core/server/services/route-settings/settings-loader.js +14 -32
- package/core/server/services/themes/activation-bridge.js +3 -3
- package/core/server/services/url/LocalFileCache.js +75 -0
- package/core/server/services/url/Resources.js +8 -2
- package/core/server/services/url/UrlGenerator.js +23 -20
- package/core/server/services/url/UrlService.js +75 -63
- package/core/server/services/url/index.js +17 -3
- package/core/server/web/admin/app.js +7 -10
- package/core/server/web/admin/controller.js +35 -12
- package/core/server/web/admin/middleware/redirect-admin-urls.js +15 -0
- 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 +1 -1
- package/core/server/web/api/canary/admin/app.js +3 -6
- package/core/server/web/api/canary/admin/middleware.js +6 -6
- package/core/server/web/api/canary/admin/routes.js +5 -5
- package/core/server/web/api/canary/content/app.js +3 -6
- package/core/server/web/api/canary/content/middleware.js +3 -3
- package/core/server/web/api/v2/admin/app.js +3 -6
- package/core/server/web/api/v2/admin/middleware.js +6 -6
- package/core/server/web/api/v2/admin/routes.js +5 -5
- package/core/server/web/api/v2/content/app.js +3 -6
- package/core/server/web/api/v2/content/middleware.js +3 -3
- package/core/server/web/api/v3/admin/app.js +3 -6
- package/core/server/web/api/v3/admin/middleware.js +6 -6
- package/core/server/web/api/v3/admin/routes.js +5 -5
- package/core/server/web/api/v3/content/app.js +3 -6
- package/core/server/web/api/v3/content/middleware.js +3 -3
- package/core/server/web/members/app.js +6 -9
- package/core/server/web/oauth/app.js +0 -4
- package/core/server/web/parent/app.js +17 -9
- package/core/server/web/parent/frontend.js +1 -1
- package/core/server/web/shared/index.js +2 -2
- package/core/server/web/shared/{middlewares → middleware}/api/index.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/api/spam-prevention.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/brute.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/cache-control.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/error-handler.js +70 -53
- package/core/server/web/shared/{middlewares → middleware}/index.js +0 -4
- package/core/server/web/shared/{middlewares → middleware}/pretty-urls.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/uncapitalise.js +0 -0
- package/core/server/web/shared/{middlewares → middleware}/url-redirects.js +0 -0
- package/core/shared/config/defaults.json +7 -1
- package/core/shared/config/helpers.js +42 -0
- package/core/shared/config/loader.js +1 -1
- package/core/shared/labs.js +7 -2
- package/loggingrc.js +19 -20
- package/package.json +35 -34
- package/yarn.lock +822 -345
- package/core/server/data/schema/fixtures/utils.js +0 -321
- package/core/server/web/parent/vhost-utils.js +0 -39
- package/core/server/web/shared/middlewares/maintenance.js +0 -25
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const errors = require('@tryghost/errors');
|
|
2
2
|
const tpl = require('@tryghost/tpl');
|
|
3
|
+
const logging = require('@tryghost/logging');
|
|
3
4
|
const {extract, hasProvider} = require('oembed-parser');
|
|
4
5
|
const cheerio = require('cheerio');
|
|
5
6
|
const _ = require('lodash');
|
|
@@ -62,6 +63,7 @@ class OEmbed {
|
|
|
62
63
|
*/
|
|
63
64
|
constructor({config, externalRequest}) {
|
|
64
65
|
this.config = config;
|
|
66
|
+
|
|
65
67
|
/** @type {IExternalRequest} */
|
|
66
68
|
this.externalRequest = async (url, requestConfig) => {
|
|
67
69
|
if (this.isIpOrLocalhost(url)) {
|
|
@@ -73,6 +75,7 @@ class OEmbed {
|
|
|
73
75
|
}
|
|
74
76
|
return response;
|
|
75
77
|
};
|
|
78
|
+
|
|
76
79
|
/** @type {ICustomProvider[]} */
|
|
77
80
|
this.customProviders = [];
|
|
78
81
|
}
|
|
@@ -107,24 +110,6 @@ class OEmbed {
|
|
|
107
110
|
}
|
|
108
111
|
}
|
|
109
112
|
|
|
110
|
-
/**
|
|
111
|
-
* @param {string} url
|
|
112
|
-
*/
|
|
113
|
-
errorHandler(url) {
|
|
114
|
-
/**
|
|
115
|
-
* @param {Error|errors.GhostError} err
|
|
116
|
-
*/
|
|
117
|
-
return async (err) => {
|
|
118
|
-
// allow specific validation errors through for better error messages
|
|
119
|
-
if (errors.utils.isIgnitionError(err) && err.errorType === 'ValidationError') {
|
|
120
|
-
throw err;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// default to unknown provider to avoid leaking any app specifics
|
|
124
|
-
return this.unknownProvider(url);
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
|
|
128
113
|
async fetchBookmarkData(url) {
|
|
129
114
|
const metascraper = require('metascraper')([
|
|
130
115
|
require('metascraper-url')(),
|
|
@@ -296,10 +281,9 @@ class OEmbed {
|
|
|
296
281
|
* @returns {Promise<Object>}
|
|
297
282
|
*/
|
|
298
283
|
async fetchOembedDataFromUrl(url, type) {
|
|
299
|
-
let data;
|
|
300
|
-
|
|
301
284
|
try {
|
|
302
285
|
const urlObject = new URL(url);
|
|
286
|
+
|
|
303
287
|
for (const provider of this.customProviders) {
|
|
304
288
|
if (await provider.canSupportRequest(urlObject)) {
|
|
305
289
|
const result = await provider.getOEmbedData(urlObject, this.externalRequest);
|
|
@@ -309,23 +293,39 @@ class OEmbed {
|
|
|
309
293
|
}
|
|
310
294
|
}
|
|
311
295
|
|
|
296
|
+
// fetch only bookmark when explicitly requested
|
|
312
297
|
if (type === 'bookmark') {
|
|
313
298
|
return this.fetchBookmarkData(url);
|
|
314
299
|
}
|
|
315
300
|
|
|
316
|
-
|
|
301
|
+
// attempt to fetch oembed
|
|
302
|
+
let data = await this.fetchOembedData(url);
|
|
317
303
|
|
|
304
|
+
// fallback to bookmark when we can't get oembed
|
|
318
305
|
if (!data && !type) {
|
|
319
306
|
data = await this.fetchBookmarkData(url);
|
|
320
307
|
}
|
|
321
308
|
|
|
309
|
+
// couldn't get anything, throw a validation error
|
|
322
310
|
if (!data) {
|
|
323
|
-
|
|
311
|
+
return this.unknownProvider(url);
|
|
324
312
|
}
|
|
325
313
|
|
|
326
314
|
return data;
|
|
327
|
-
} catch (
|
|
328
|
-
|
|
315
|
+
} catch (err) {
|
|
316
|
+
// allow specific validation errors through for better error messages
|
|
317
|
+
if (errors.utils.isIgnitionError(err) && err.errorType === 'ValidationError') {
|
|
318
|
+
throw err;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
// log the real error because we're going to throw a generic "Unknown provider" error
|
|
322
|
+
logging.error(new errors.GhostError({
|
|
323
|
+
message: 'Encountered error when fetching oembed',
|
|
324
|
+
err
|
|
325
|
+
}));
|
|
326
|
+
|
|
327
|
+
// default to unknown provider to avoid leaking any app specifics
|
|
328
|
+
return this.unknownProvider(url);
|
|
329
329
|
}
|
|
330
330
|
}
|
|
331
331
|
}
|
|
@@ -17,7 +17,7 @@ module.exports = function getConfigProperties() {
|
|
|
17
17
|
mailgunIsConfigured: config.get('bulkEmail') && config.get('bulkEmail').mailgun,
|
|
18
18
|
emailAnalytics: config.get('emailAnalytics'),
|
|
19
19
|
hostSettings: config.get('hostSettings'),
|
|
20
|
-
|
|
20
|
+
tenor: config.get('tenor')
|
|
21
21
|
};
|
|
22
22
|
|
|
23
23
|
const billingUrl = config.get('hostSettings:billing:enabled') ? config.get('hostSettings:billing:url') : '';
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const moment = require('moment-timezone');
|
|
4
3
|
const yaml = require('js-yaml');
|
|
5
4
|
|
|
6
5
|
const logging = require('@tryghost/logging');
|
|
7
6
|
const tpl = require('@tryghost/tpl');
|
|
8
7
|
const errors = require('@tryghost/errors');
|
|
9
8
|
|
|
10
|
-
const validation = require('./validation');
|
|
11
|
-
|
|
12
9
|
const messages = {
|
|
13
10
|
jsonParse: 'Could not parse JSON: {context}.',
|
|
14
11
|
yamlParse: 'Could not parse YAML: {context}.',
|
|
@@ -22,7 +19,7 @@ const messages = {
|
|
|
22
19
|
* @typedef {Object} RedirectConfig
|
|
23
20
|
* @property {String} from - Defines the relative incoming URL or pattern (regex)
|
|
24
21
|
* @property {String} to - Defines where the incoming traffic should be redirected to, which can be a static URL, or a dynamic value using regex (example: "to": "/$1/")
|
|
25
|
-
* @property {boolean} permanent - Can be defined with true for a permanent HTTP 301 redirect, or false for a temporary HTTP 302 redirect
|
|
22
|
+
* @property {boolean} [permanent] - Can be defined with true for a permanent HTTP 301 redirect, or false for a temporary HTTP 302 redirect
|
|
26
23
|
*/
|
|
27
24
|
|
|
28
25
|
/**
|
|
@@ -120,16 +117,6 @@ const parseRedirectsFile = (content, ext) => {
|
|
|
120
117
|
throw new errors.IncorrectUsageError();
|
|
121
118
|
};
|
|
122
119
|
|
|
123
|
-
/**
|
|
124
|
-
* @param {string} filePath
|
|
125
|
-
* @returns {string}
|
|
126
|
-
*/
|
|
127
|
-
const getBackupRedirectsFilePath = (filePath) => {
|
|
128
|
-
const {dir, name, ext} = path.parse(filePath);
|
|
129
|
-
|
|
130
|
-
return path.join(dir, `${name}-${moment().format('YYYY-MM-DD-HH-mm-ss')}${ext}`);
|
|
131
|
-
};
|
|
132
|
-
|
|
133
120
|
/**
|
|
134
121
|
* @typedef {object} IRedirectManager
|
|
135
122
|
*/
|
|
@@ -138,14 +125,22 @@ class CustomRedirectsAPI {
|
|
|
138
125
|
/**
|
|
139
126
|
* @param {object} config
|
|
140
127
|
* @param {string} config.basePath
|
|
141
|
-
*
|
|
142
|
-
* @param {
|
|
128
|
+
* @param {Function} config.validate - validates redirects configuration
|
|
129
|
+
* @param {Function} config.getBackupFilePath
|
|
130
|
+
* @param {IRedirectManager} config.redirectManager
|
|
143
131
|
*/
|
|
144
|
-
constructor(
|
|
132
|
+
constructor({basePath, validate, redirectManager, getBackupFilePath}) {
|
|
145
133
|
/** @private */
|
|
146
|
-
this.
|
|
134
|
+
this.basePath = basePath;
|
|
135
|
+
|
|
147
136
|
/** @private */
|
|
148
137
|
this.redirectManager = redirectManager;
|
|
138
|
+
|
|
139
|
+
/** @private */
|
|
140
|
+
this.validate = validate;
|
|
141
|
+
|
|
142
|
+
/** @private */
|
|
143
|
+
this.getBackupFilePath = getBackupFilePath;
|
|
149
144
|
}
|
|
150
145
|
|
|
151
146
|
async init() {
|
|
@@ -159,7 +154,7 @@ class CustomRedirectsAPI {
|
|
|
159
154
|
const content = await readRedirectsFile(filePath);
|
|
160
155
|
const ext = path.extname(filePath);
|
|
161
156
|
const redirects = parseRedirectsFile(content, ext);
|
|
162
|
-
|
|
157
|
+
this.validate(redirects);
|
|
163
158
|
|
|
164
159
|
this.redirectManager.removeAllRedirects();
|
|
165
160
|
for (const redirect of redirects) {
|
|
@@ -187,7 +182,7 @@ class CustomRedirectsAPI {
|
|
|
187
182
|
* @returns {string}
|
|
188
183
|
*/
|
|
189
184
|
createRedirectsFilePath(ext) {
|
|
190
|
-
return path.join(this.
|
|
185
|
+
return path.join(this.basePath, `redirects${ext}`);
|
|
191
186
|
}
|
|
192
187
|
|
|
193
188
|
/**
|
|
@@ -222,7 +217,7 @@ class CustomRedirectsAPI {
|
|
|
222
217
|
const redirectsFilePath = await this.getRedirectsFilePath();
|
|
223
218
|
|
|
224
219
|
if (redirectsFilePath) {
|
|
225
|
-
const backupRedirectsPath =
|
|
220
|
+
const backupRedirectsPath = this.getBackupFilePath(redirectsFilePath);
|
|
226
221
|
|
|
227
222
|
const backupExists = await fs.pathExists(backupRedirectsPath);
|
|
228
223
|
if (backupExists) {
|
|
@@ -234,10 +229,10 @@ class CustomRedirectsAPI {
|
|
|
234
229
|
|
|
235
230
|
const content = await readRedirectsFile(filePath);
|
|
236
231
|
const parsed = parseRedirectsFile(content, ext);
|
|
237
|
-
|
|
232
|
+
this.validate(parsed);
|
|
238
233
|
|
|
239
234
|
if (ext === '.json') {
|
|
240
|
-
await fs.writeFile(this.createRedirectsFilePath('.json'), JSON.stringify(
|
|
235
|
+
await fs.writeFile(this.createRedirectsFilePath('.json'), JSON.stringify(parsed), 'utf-8');
|
|
241
236
|
} else if (ext === '.yaml') {
|
|
242
237
|
await fs.copy(filePath, this.createRedirectsFilePath('.yaml'));
|
|
243
238
|
}
|
|
@@ -3,21 +3,27 @@ const urlUtils = require('../../../shared/url-utils');
|
|
|
3
3
|
|
|
4
4
|
const DynamicRedirectManager = require('@tryghost/express-dynamic-redirects');
|
|
5
5
|
const CustomRedirectsAPI = require('./api');
|
|
6
|
-
|
|
7
|
-
const
|
|
8
|
-
permanentMaxAge: config.get('caching:customRedirects:maxAge'),
|
|
9
|
-
getSubdirectoryURL: (pathname) => {
|
|
10
|
-
return urlUtils.urlJoin(urlUtils.getSubdir(), pathname);
|
|
11
|
-
}
|
|
12
|
-
});
|
|
6
|
+
const validation = require('./validation');
|
|
7
|
+
const {getBackupRedirectsFilePath} = require('./utils');
|
|
13
8
|
|
|
14
9
|
let customRedirectsAPI;
|
|
10
|
+
let redirectManager;
|
|
15
11
|
|
|
16
12
|
module.exports = {
|
|
17
13
|
init() {
|
|
14
|
+
redirectManager = new DynamicRedirectManager({
|
|
15
|
+
permanentMaxAge: config.get('caching:customRedirects:maxAge'),
|
|
16
|
+
getSubdirectoryURL: (pathname) => {
|
|
17
|
+
return urlUtils.urlJoin(urlUtils.getSubdir(), pathname);
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
18
21
|
customRedirectsAPI = new CustomRedirectsAPI({
|
|
19
|
-
basePath: config.getContentPath('data')
|
|
20
|
-
|
|
22
|
+
basePath: config.getContentPath('data'),
|
|
23
|
+
redirectManager,
|
|
24
|
+
getBackupFilePath: getBackupRedirectsFilePath,
|
|
25
|
+
validate: validation.validate.bind(validation)
|
|
26
|
+
});
|
|
21
27
|
|
|
22
28
|
return customRedirectsAPI.init();
|
|
23
29
|
},
|
|
@@ -26,5 +32,7 @@ module.exports = {
|
|
|
26
32
|
return customRedirectsAPI;
|
|
27
33
|
},
|
|
28
34
|
|
|
29
|
-
middleware
|
|
35
|
+
get middleware() {
|
|
36
|
+
return redirectManager.handleRequest;
|
|
37
|
+
}
|
|
30
38
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const moment = require('moment-timezone');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @param {string} filePath
|
|
6
|
+
* @returns {string}
|
|
7
|
+
*/
|
|
8
|
+
const getBackupRedirectsFilePath = (filePath) => {
|
|
9
|
+
const {dir, name, ext} = path.parse(filePath);
|
|
10
|
+
|
|
11
|
+
return path.join(dir, `${name}-${moment().format('YYYY-MM-DD-HH-mm-ss')}${ext}`);
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
module.exports.getBackupRedirectsFilePath = getBackupRedirectsFilePath;
|
|
@@ -7,9 +7,19 @@ const messages = {
|
|
|
7
7
|
invalidRedirectsFromRegex: 'Incorrect RegEx in redirects file.',
|
|
8
8
|
redirectsHelp: 'https://ghost.org/docs/themes/routing/#redirects'
|
|
9
9
|
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Redirect configuration object
|
|
13
|
+
* @typedef {Object} RedirectConfig
|
|
14
|
+
* @property {String} from - Defines the relative incoming URL or pattern (regex)
|
|
15
|
+
* @property {String} to - Defines where the incoming traffic should be redirected to, which can be a static URL, or a dynamic value using regex (example: "to": "/$1/")
|
|
16
|
+
* @property {boolean} [permanent] - Can be defined with true for a permanent HTTP 301 redirect, or false for a temporary HTTP 302 redirect
|
|
17
|
+
*/
|
|
18
|
+
|
|
10
19
|
/**
|
|
11
20
|
* Redirects are file based at the moment, but they will live in the database in the future.
|
|
12
21
|
* See V2 of https://github.com/TryGhost/Ghost/issues/7707.
|
|
22
|
+
* @param {RedirectConfig[]} redirects
|
|
13
23
|
*/
|
|
14
24
|
const validate = (redirects) => {
|
|
15
25
|
if (!_.isArray(redirects)) {
|
|
@@ -1,32 +1,55 @@
|
|
|
1
|
-
const routeSettings = require('./route-settings');
|
|
2
|
-
const SettingsLoader = require('./settings-loader');
|
|
3
1
|
const config = require('../../../shared/config');
|
|
4
2
|
const parseYaml = require('./yaml-parser');
|
|
5
|
-
const
|
|
3
|
+
const SettingsPathManager = require('@tryghost/settings-path-manager');
|
|
6
4
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
extension: '.yaml',
|
|
10
|
-
destinationFolderPath: config.getContentPath('settings'),
|
|
11
|
-
sourceFolderPath: config.get('paths').defaultSettings
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
const settingsLoader = new SettingsLoader({parseYaml});
|
|
5
|
+
let settingsLoader;
|
|
6
|
+
let routeSettings;
|
|
15
7
|
|
|
16
8
|
module.exports = {
|
|
17
9
|
init: async () => {
|
|
10
|
+
const RouteSettings = require('./route-settings');
|
|
11
|
+
const SettingsLoader = require('./settings-loader');
|
|
12
|
+
const DefaultSettingsManager = require('./default-settings-manager');
|
|
13
|
+
|
|
14
|
+
const settingsPathManager = new SettingsPathManager({type: 'routes', paths: [config.getContentPath('settings')]});
|
|
15
|
+
settingsLoader = new SettingsLoader({parseYaml, settingFilePath: settingsPathManager.getDefaultFilePath()});
|
|
16
|
+
routeSettings = new RouteSettings({
|
|
17
|
+
settingsLoader,
|
|
18
|
+
settingsPath: settingsPathManager.getDefaultFilePath(),
|
|
19
|
+
backupPath: settingsPathManager.getBackupFilePath()
|
|
20
|
+
});
|
|
21
|
+
const defaultSettingsManager = new DefaultSettingsManager({
|
|
22
|
+
type: 'routes',
|
|
23
|
+
extension: '.yaml',
|
|
24
|
+
destinationFolderPath: config.getContentPath('settings'),
|
|
25
|
+
sourceFolderPath: config.get('paths').defaultSettings
|
|
26
|
+
});
|
|
27
|
+
|
|
18
28
|
return await defaultSettingsManager.ensureSettingsFileExists();
|
|
19
29
|
},
|
|
20
30
|
|
|
21
|
-
loadRouteSettingsSync
|
|
22
|
-
|
|
23
|
-
|
|
31
|
+
get loadRouteSettingsSync() {
|
|
32
|
+
return settingsLoader.loadSettingsSync.bind(settingsLoader);
|
|
33
|
+
},
|
|
34
|
+
get loadRouteSettings() {
|
|
35
|
+
return settingsLoader.loadSettings.bind(settingsLoader);
|
|
36
|
+
},
|
|
37
|
+
get getDefaultHash() {
|
|
38
|
+
return routeSettings.getDefaultHash.bind(routeSettings);
|
|
39
|
+
},
|
|
40
|
+
|
|
24
41
|
/**
|
|
25
42
|
* Methods used in the API
|
|
26
43
|
*/
|
|
27
44
|
api: {
|
|
28
|
-
setFromFilePath
|
|
29
|
-
|
|
30
|
-
|
|
45
|
+
get setFromFilePath() {
|
|
46
|
+
return routeSettings.setFromFilePath.bind(routeSettings);
|
|
47
|
+
},
|
|
48
|
+
get get() {
|
|
49
|
+
return routeSettings.get.bind(routeSettings);
|
|
50
|
+
},
|
|
51
|
+
get getCurrentHash() {
|
|
52
|
+
return routeSettings.getCurrentHash.bind(routeSettings);
|
|
53
|
+
}
|
|
31
54
|
}
|
|
32
55
|
};
|
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
const Promise = require('bluebird');
|
|
2
|
-
const moment = require('moment-timezone');
|
|
3
2
|
const fs = require('fs-extra');
|
|
4
|
-
const path = require('path');
|
|
5
3
|
const crypto = require('crypto');
|
|
6
4
|
const urlService = require('../url');
|
|
7
5
|
|
|
8
6
|
const debug = require('@tryghost/debug')('services:route-settings');
|
|
9
7
|
const errors = require('@tryghost/errors');
|
|
10
8
|
const tpl = require('@tryghost/tpl');
|
|
11
|
-
const config = require('../../../shared/config');
|
|
12
9
|
const bridge = require('../../../bridge');
|
|
13
|
-
const SettingsLoader = require('./settings-loader');
|
|
14
|
-
const parseYaml = require('./yaml-parser');
|
|
15
|
-
|
|
16
|
-
const settingsLoader = new SettingsLoader({parseYaml});
|
|
17
10
|
|
|
18
11
|
const messages = {
|
|
19
12
|
loadError: 'Could not load {filename} file.'
|
|
@@ -31,134 +24,154 @@ const messages = {
|
|
|
31
24
|
* - then we reload the whole site app, which will reset all routers and re-create the url generators
|
|
32
25
|
*/
|
|
33
26
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
27
|
+
class RouteSettings {
|
|
28
|
+
/**
|
|
29
|
+
*
|
|
30
|
+
* @param {Object} options
|
|
31
|
+
* @param {Object} options.settingsLoader
|
|
32
|
+
* @param {String} options.settingsPath
|
|
33
|
+
* @param {String} options.backupPath
|
|
34
|
+
*/
|
|
35
|
+
constructor({settingsLoader, settingsPath, backupPath}) {
|
|
36
|
+
/**
|
|
37
|
+
* md5 hashes of default routes settings
|
|
38
|
+
* @private
|
|
39
|
+
*/
|
|
40
|
+
this.defaultRoutesSettingHash = '3d180d52c663d173a6be791ef411ed01';
|
|
41
|
+
/**
|
|
42
|
+
* @private
|
|
43
|
+
*/
|
|
44
|
+
this.filename = 'routes';
|
|
45
|
+
/**
|
|
46
|
+
* @private
|
|
47
|
+
*/
|
|
48
|
+
this.ext = 'yaml';
|
|
49
|
+
|
|
50
|
+
this.settingsLoader = settingsLoader;
|
|
51
|
+
this.settingsPath = settingsPath;
|
|
52
|
+
this.backupPath = backupPath;
|
|
53
|
+
}
|
|
46
54
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
55
|
+
/**
|
|
56
|
+
* @private
|
|
57
|
+
* @param {String} settingsPath
|
|
58
|
+
* @param {String} backupPath
|
|
59
|
+
*/
|
|
60
|
+
async createBackupFile(settingsPath, backupPath) {
|
|
61
|
+
return await fs.copy(settingsPath, backupPath);
|
|
62
|
+
}
|
|
50
63
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
64
|
+
/**
|
|
65
|
+
* @private
|
|
66
|
+
* @param {String} settingsPath
|
|
67
|
+
* @param {String} backupPath
|
|
68
|
+
*/
|
|
69
|
+
async restoreBackupFile(settingsPath, backupPath) {
|
|
70
|
+
return await fs.copy(backupPath, settingsPath);
|
|
71
|
+
}
|
|
54
72
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
73
|
+
/**
|
|
74
|
+
* @private
|
|
75
|
+
* @param {String} filePath
|
|
76
|
+
* @param {String} settingsPath
|
|
77
|
+
*/
|
|
78
|
+
async saveFile(filePath, settingsPath) {
|
|
79
|
+
return await fs.copy(filePath, settingsPath);
|
|
80
|
+
}
|
|
58
81
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
82
|
+
/**
|
|
83
|
+
* @private
|
|
84
|
+
* @param {String} settingsFilePath
|
|
85
|
+
*/
|
|
86
|
+
async readFile(settingsFilePath) {
|
|
87
|
+
return fs.readFile(settingsFilePath, 'utf-8')
|
|
88
|
+
.catch((err) => {
|
|
89
|
+
if (err.code === 'ENOENT') {
|
|
90
|
+
return Promise.resolve([]);
|
|
91
|
+
}
|
|
65
92
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
93
|
+
if (errors.utils.isIgnitionError(err)) {
|
|
94
|
+
throw err;
|
|
95
|
+
}
|
|
69
96
|
|
|
70
|
-
|
|
71
|
-
|
|
97
|
+
throw new errors.NotFoundError({
|
|
98
|
+
err: err
|
|
99
|
+
});
|
|
72
100
|
});
|
|
73
|
-
|
|
74
|
-
};
|
|
75
|
-
|
|
76
|
-
const setFromFilePath = async (filePath) => {
|
|
77
|
-
const settingsPath = getSettingsFilePath();
|
|
78
|
-
const backupPath = getBackupFilePath();
|
|
79
|
-
|
|
80
|
-
await createBackupFile(settingsPath, backupPath);
|
|
81
|
-
await saveFile(filePath, settingsPath);
|
|
101
|
+
}
|
|
82
102
|
|
|
83
|
-
|
|
103
|
+
async setFromFilePath(filePath) {
|
|
104
|
+
await this.createBackupFile(this.settingsPath, this.backupPath);
|
|
105
|
+
await this.saveFile(filePath, this.settingsPath);
|
|
84
106
|
|
|
85
|
-
const bringBackValidRoutes = async () => {
|
|
86
107
|
urlService.resetGenerators({releaseResourcesOnly: true});
|
|
87
108
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
return bridge.reloadFrontend();
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
try {
|
|
94
|
-
bridge.reloadFrontend();
|
|
95
|
-
} catch (err) {
|
|
96
|
-
return bringBackValidRoutes()
|
|
97
|
-
.finally(() => {
|
|
98
|
-
throw err;
|
|
99
|
-
});
|
|
100
|
-
}
|
|
109
|
+
const bringBackValidRoutes = async () => {
|
|
110
|
+
urlService.resetGenerators({releaseResourcesOnly: true});
|
|
101
111
|
|
|
102
|
-
|
|
103
|
-
let tries = 0;
|
|
104
|
-
|
|
105
|
-
function isBlogRunning() {
|
|
106
|
-
debug('waiting for blog running');
|
|
107
|
-
return Promise.delay(1000)
|
|
108
|
-
.then(() => {
|
|
109
|
-
debug('waited for blog running');
|
|
110
|
-
if (!urlService.hasFinished()) {
|
|
111
|
-
if (tries > 5) {
|
|
112
|
-
throw new errors.InternalServerError({
|
|
113
|
-
message: tpl(messages.loadError, {filename: `${filename}.${ext}`})
|
|
114
|
-
});
|
|
115
|
-
}
|
|
112
|
+
await this.restoreBackupFile(this.settingsPath, this.backupPath);
|
|
116
113
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
}
|
|
114
|
+
return bridge.reloadFrontend();
|
|
115
|
+
};
|
|
122
116
|
|
|
123
|
-
|
|
124
|
-
|
|
117
|
+
try {
|
|
118
|
+
bridge.reloadFrontend();
|
|
119
|
+
} catch (err) {
|
|
125
120
|
return bringBackValidRoutes()
|
|
126
121
|
.finally(() => {
|
|
127
122
|
throw err;
|
|
128
123
|
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// @TODO: how can we get rid of this from here?
|
|
127
|
+
let tries = 0;
|
|
128
|
+
|
|
129
|
+
function isBlogRunning() {
|
|
130
|
+
debug('waiting for blog running');
|
|
131
|
+
return Promise.delay(1000)
|
|
132
|
+
.then(() => {
|
|
133
|
+
debug('waited for blog running');
|
|
134
|
+
if (!urlService.hasFinished()) {
|
|
135
|
+
if (tries > 5) {
|
|
136
|
+
throw new errors.InternalServerError({
|
|
137
|
+
message: tpl(messages.loadError, {filename: `${this.filename}.${this.ext}`})
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
tries = tries + 1;
|
|
142
|
+
return isBlogRunning();
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return isBlogRunning()
|
|
148
|
+
.catch((err) => {
|
|
149
|
+
return bringBackValidRoutes()
|
|
150
|
+
.finally(() => {
|
|
151
|
+
throw err;
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
}
|
|
137
155
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const defaultRoutesSettingHash = '3d180d52c663d173a6be791ef411ed01';
|
|
156
|
+
async get() {
|
|
157
|
+
return this.readFile(this.settingsPath);
|
|
158
|
+
}
|
|
142
159
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
160
|
+
calculateHash(data) {
|
|
161
|
+
return crypto.createHash('md5')
|
|
162
|
+
.update(data, 'binary')
|
|
163
|
+
.digest('hex');
|
|
164
|
+
}
|
|
148
165
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
166
|
+
getDefaultHash() {
|
|
167
|
+
return this.defaultRoutesSettingHash;
|
|
168
|
+
}
|
|
152
169
|
|
|
153
|
-
|
|
154
|
-
|
|
170
|
+
async getCurrentHash() {
|
|
171
|
+
const data = await this.settingsLoader.loadSettings();
|
|
155
172
|
|
|
156
|
-
|
|
157
|
-
}
|
|
173
|
+
return this.calculateHash(JSON.stringify(data));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
158
176
|
|
|
159
|
-
module.exports =
|
|
160
|
-
getDefaultHash: getDefaultHash,
|
|
161
|
-
setFromFilePath: setFromFilePath,
|
|
162
|
-
get: get,
|
|
163
|
-
getCurrentHash: getCurrentHash
|
|
164
|
-
};
|
|
177
|
+
module.exports = RouteSettings;
|