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.
Files changed (118) hide show
  1. package/.c8rc.json +24 -0
  2. package/Gruntfile.js +0 -1
  3. package/content/public/README.md +3 -0
  4. package/core/app.js +12 -1
  5. package/core/boot.js +45 -26
  6. package/core/bridge.js +10 -10
  7. package/core/built/assets/{chunk.3.324fd0cc598c73650219.js → chunk.3.8f95b516d88ff4eec64c.js} +18 -18
  8. package/core/built/assets/{ghost-dark-39fb496d051565531062d7e047d1c0b1.css → ghost-dark-e7b57ab951512c5719aee89b16b9a448.css} +1 -1
  9. package/core/built/assets/{ghost.min-4207edfc1ae0a3f9f6505ca00d20b0c0.css → ghost.min-7f3603dbeb5ebf0ec09e207ae82fb4e3.css} +1 -1
  10. package/core/built/assets/{ghost.min-7da921f6c6cac3fe10da1ba104575440.js → ghost.min-d5595f9c71ebc534ccf9ac78483d357c.js} +138 -105
  11. package/core/built/assets/icons/powered-by-tenor.svg +35 -0
  12. package/core/built/assets/icons/tenor.svg +7 -0
  13. package/core/built/assets/{vendor.min-413f887176a041e6dbf88214ca9a7481.js → vendor.min-1a84ac3ef74edf31c6e86810b45221cc.js} +2964 -2434
  14. package/core/frontend/apps/amp/lib/views/amp.hbs +104 -0
  15. package/core/frontend/apps/private-blogging/lib/router.js +1 -1
  16. package/core/frontend/services/card-assets/index.js +0 -12
  17. package/core/frontend/services/card-assets/service.js +35 -26
  18. package/core/frontend/services/routing/CollectionRouter.js +4 -5
  19. package/core/frontend/services/routing/EmailRouter.js +1 -1
  20. package/core/frontend/services/routing/ParentRouter.js +0 -8
  21. package/core/frontend/services/routing/PreviewRouter.js +1 -1
  22. package/core/frontend/services/routing/StaticPagesRouter.js +1 -1
  23. package/core/frontend/services/routing/StaticRoutesRouter.js +4 -4
  24. package/core/frontend/services/routing/TaxonomyRouter.js +3 -3
  25. package/core/frontend/services/routing/{middlewares → middleware}/index.js +0 -0
  26. package/core/frontend/services/routing/{middlewares → middleware}/page-param.js +0 -0
  27. package/core/frontend/services/routing/router-manager.js +7 -2
  28. package/core/frontend/services/rss/generate-feed.js +2 -1
  29. package/core/frontend/src/cards/css/bookmark.css +72 -47
  30. package/core/frontend/src/cards/css/callout.css +41 -4
  31. package/core/frontend/src/cards/css/gallery.css +15 -10
  32. package/core/frontend/src/cards/css/nft.css +20 -11
  33. package/core/frontend/src/cards/css/toggle.css +58 -0
  34. package/core/frontend/src/cards/js/toggle.js +16 -0
  35. package/core/frontend/web/middleware/serve-public-file.js +39 -16
  36. package/core/frontend/web/site.js +11 -14
  37. package/core/server/api/canary/authentication.js +1 -1
  38. package/core/server/api/canary/utils/serializers/output/config.js +1 -1
  39. package/core/server/api/v2/authentication.js +1 -1
  40. package/core/server/api/v3/authentication.js +1 -1
  41. package/core/server/data/db/connection.js +7 -0
  42. package/core/server/data/importer/importers/data/data-importer.js +3 -3
  43. package/core/server/data/migrations/init/2-create-fixtures.js +3 -20
  44. package/core/server/data/migrations/versions/1.21/1-add-contributor-role.js +5 -5
  45. package/core/server/data/migrations/versions/2.15/2-insert-zapier-integration.js +3 -3
  46. package/core/server/data/migrations/versions/2.2/3-insert-admin-integration-role.js +5 -5
  47. package/core/server/data/migrations/versions/2.27/1-insert-ghost-db-backup-role.js +5 -6
  48. package/core/server/data/migrations/versions/2.27/2-insert-db-backup-integration.js +3 -4
  49. package/core/server/data/migrations/versions/2.28/3-insert-ghost-scheduler-role.js +7 -7
  50. package/core/server/data/migrations/versions/2.28/4-insert-scheduler-integration.js +3 -3
  51. package/core/server/data/migrations/versions/4.23/01-truncate-offer-names.js +58 -0
  52. package/core/server/data/schema/fixtures/fixture-manager.js +340 -0
  53. package/core/server/data/schema/fixtures/index.js +8 -2
  54. package/core/server/services/email-analytics/jobs/index.js +1 -1
  55. package/core/server/services/mega/post-email-serializer.js +5 -1
  56. package/core/server/services/mega/segment-parser.js +1 -2
  57. package/core/server/services/mega/template.js +52 -37
  58. package/core/server/services/nft-oembed.js +7 -21
  59. package/core/server/services/oembed.js +24 -24
  60. package/core/server/services/public-config/config.js +1 -1
  61. package/core/server/services/redirects/api.js +18 -23
  62. package/core/server/services/redirects/index.js +18 -10
  63. package/core/server/services/redirects/utils.js +14 -0
  64. package/core/server/services/redirects/validation.js +10 -0
  65. package/core/server/services/route-settings/index.js +40 -17
  66. package/core/server/services/route-settings/route-settings.js +127 -114
  67. package/core/server/services/route-settings/settings-loader.js +14 -32
  68. package/core/server/services/themes/activation-bridge.js +3 -3
  69. package/core/server/services/url/LocalFileCache.js +75 -0
  70. package/core/server/services/url/Resources.js +8 -2
  71. package/core/server/services/url/UrlGenerator.js +23 -20
  72. package/core/server/services/url/UrlService.js +75 -63
  73. package/core/server/services/url/index.js +17 -3
  74. package/core/server/web/admin/app.js +7 -10
  75. package/core/server/web/admin/controller.js +35 -12
  76. package/core/server/web/admin/middleware/redirect-admin-urls.js +15 -0
  77. package/core/server/web/admin/views/default-prod.html +4 -4
  78. package/core/server/web/admin/views/default.html +4 -4
  79. package/core/server/web/api/app.js +1 -1
  80. package/core/server/web/api/canary/admin/app.js +3 -6
  81. package/core/server/web/api/canary/admin/middleware.js +6 -6
  82. package/core/server/web/api/canary/admin/routes.js +5 -5
  83. package/core/server/web/api/canary/content/app.js +3 -6
  84. package/core/server/web/api/canary/content/middleware.js +3 -3
  85. package/core/server/web/api/v2/admin/app.js +3 -6
  86. package/core/server/web/api/v2/admin/middleware.js +6 -6
  87. package/core/server/web/api/v2/admin/routes.js +5 -5
  88. package/core/server/web/api/v2/content/app.js +3 -6
  89. package/core/server/web/api/v2/content/middleware.js +3 -3
  90. package/core/server/web/api/v3/admin/app.js +3 -6
  91. package/core/server/web/api/v3/admin/middleware.js +6 -6
  92. package/core/server/web/api/v3/admin/routes.js +5 -5
  93. package/core/server/web/api/v3/content/app.js +3 -6
  94. package/core/server/web/api/v3/content/middleware.js +3 -3
  95. package/core/server/web/members/app.js +6 -9
  96. package/core/server/web/oauth/app.js +0 -4
  97. package/core/server/web/parent/app.js +17 -9
  98. package/core/server/web/parent/frontend.js +1 -1
  99. package/core/server/web/shared/index.js +2 -2
  100. package/core/server/web/shared/{middlewares → middleware}/api/index.js +0 -0
  101. package/core/server/web/shared/{middlewares → middleware}/api/spam-prevention.js +0 -0
  102. package/core/server/web/shared/{middlewares → middleware}/brute.js +0 -0
  103. package/core/server/web/shared/{middlewares → middleware}/cache-control.js +0 -0
  104. package/core/server/web/shared/{middlewares → middleware}/error-handler.js +70 -53
  105. package/core/server/web/shared/{middlewares → middleware}/index.js +0 -4
  106. package/core/server/web/shared/{middlewares → middleware}/pretty-urls.js +0 -0
  107. package/core/server/web/shared/{middlewares → middleware}/uncapitalise.js +0 -0
  108. package/core/server/web/shared/{middlewares → middleware}/url-redirects.js +0 -0
  109. package/core/shared/config/defaults.json +7 -1
  110. package/core/shared/config/helpers.js +42 -0
  111. package/core/shared/config/loader.js +1 -1
  112. package/core/shared/labs.js +7 -2
  113. package/loggingrc.js +19 -20
  114. package/package.json +35 -34
  115. package/yarn.lock +822 -345
  116. package/core/server/data/schema/fixtures/utils.js +0 -321
  117. package/core/server/web/parent/vhost-utils.js +0 -39
  118. 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
- data = await this.fetchOembedData(url);
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
- data = await this.unknownProvider(url);
311
+ return this.unknownProvider(url);
324
312
  }
325
313
 
326
314
  return data;
327
- } catch (e) {
328
- return this.errorHandler(url);
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
- tenorApiKey: config.get('tenorApiKey')
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 {IRedirectManager} redirectManager
128
+ * @param {Function} config.validate - validates redirects configuration
129
+ * @param {Function} config.getBackupFilePath
130
+ * @param {IRedirectManager} config.redirectManager
143
131
  */
144
- constructor(config, redirectManager) {
132
+ constructor({basePath, validate, redirectManager, getBackupFilePath}) {
145
133
  /** @private */
146
- this.config = config;
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
- validation.validate(redirects);
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.config.basePath, `redirects${ext}`);
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 = getBackupRedirectsFilePath(redirectsFilePath);
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
- validation.validate(parsed);
232
+ this.validate(parsed);
238
233
 
239
234
  if (ext === '.json') {
240
- await fs.writeFile(this.createRedirectsFilePath('.json'), JSON.stringify(content), 'utf-8');
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 redirectManager = new DynamicRedirectManager({
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
- }, redirectManager);
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: redirectManager.handleRequest
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 DefaultSettingsManager = require('./default-settings-manager');
3
+ const SettingsPathManager = require('@tryghost/settings-path-manager');
6
4
 
7
- const defaultSettingsManager = new DefaultSettingsManager({
8
- type: 'routes',
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: settingsLoader.loadSettingsSync.bind(settingsLoader),
22
- loadRouteSettings: settingsLoader.loadSettings.bind(settingsLoader),
23
- getDefaultHash: routeSettings.getDefaultHash,
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: routeSettings.setFromFilePath,
29
- get: routeSettings.get,
30
- getCurrentHash: routeSettings.getCurrentHash
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
- const filename = 'routes';
35
- const ext = 'yaml';
36
-
37
- const getSettingsFilePath = () => {
38
- const settingsFolder = config.getContentPath('settings');
39
- return path.join(settingsFolder, `${filename}.${ext}`);
40
- };
41
-
42
- const getBackupFilePath = () => {
43
- const settingsFolder = config.getContentPath('settings');
44
- return path.join(settingsFolder, `${filename}-${moment().format('YYYY-MM-DD-HH-mm-ss')}.${ext}`);
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
- const createBackupFile = async (settingsPath, backupPath) => {
48
- return await fs.copy(settingsPath, backupPath);
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
- const restoreBackupFile = async (settingsPath, backupPath) => {
52
- return await fs.copy(backupPath, settingsPath);
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
- const saveFile = async (filePath, settingsPath) => {
56
- return await fs.copy(filePath, settingsPath);
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
- const readFile = (settingsFilePath) => {
60
- return fs.readFile(settingsFilePath, 'utf-8')
61
- .catch((err) => {
62
- if (err.code === 'ENOENT') {
63
- return Promise.resolve([]);
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
- if (errors.utils.isIgnitionError(err)) {
67
- throw err;
68
- }
93
+ if (errors.utils.isIgnitionError(err)) {
94
+ throw err;
95
+ }
69
96
 
70
- throw new errors.NotFoundError({
71
- err: err
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
- urlService.resetGenerators({releaseResourcesOnly: true});
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
- await restoreBackupFile(settingsPath, backupPath);
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
- // @TODO: how can we get rid of this from here?
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
- tries = tries + 1;
118
- return isBlogRunning();
119
- }
120
- });
121
- }
114
+ return bridge.reloadFrontend();
115
+ };
122
116
 
123
- return isBlogRunning()
124
- .catch((err) => {
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
- const get = async () => {
133
- const settingsFilePath = await getSettingsFilePath();
134
-
135
- return readFile(settingsFilePath);
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
- * md5 hashes of default routes settings
140
- */
141
- const defaultRoutesSettingHash = '3d180d52c663d173a6be791ef411ed01';
156
+ async get() {
157
+ return this.readFile(this.settingsPath);
158
+ }
142
159
 
143
- const calculateHash = (data) => {
144
- return crypto.createHash('md5')
145
- .update(data, 'binary')
146
- .digest('hex');
147
- };
160
+ calculateHash(data) {
161
+ return crypto.createHash('md5')
162
+ .update(data, 'binary')
163
+ .digest('hex');
164
+ }
148
165
 
149
- const getDefaultHash = () => {
150
- return defaultRoutesSettingHash;
151
- };
166
+ getDefaultHash() {
167
+ return this.defaultRoutesSettingHash;
168
+ }
152
169
 
153
- const getCurrentHash = async () => {
154
- const data = await settingsLoader.loadSettings();
170
+ async getCurrentHash() {
171
+ const data = await this.settingsLoader.loadSettings();
155
172
 
156
- return calculateHash(JSON.stringify(data));
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;