ghost 4.23.0 → 4.26.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/.eslintrc.js +39 -0
- package/content/themes/casper/assets/built/casper.js +1 -1
- package/content/themes/casper/assets/built/casper.js.map +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 +6 -1
- package/content/themes/casper/assets/css/screen.css +50 -215
- package/content/themes/casper/default.hbs +2 -2
- package/content/themes/casper/package.json +3 -2
- package/content/themes/casper/post.hbs +1 -1
- package/content/themes/casper/yarn.lock +173 -123
- package/core/app.js +20 -5
- package/core/boot.js +77 -36
- package/core/bridge.js +10 -10
- package/core/built/assets/ghost-dark-ef86e3bc7f0fb83d39d3d6a49bff8dd5.css +1 -0
- package/core/built/assets/ghost.min-57c1e677f42d596942d317ce93e8a62c.css +1 -0
- package/core/built/assets/{ghost.min-cccc107e881b74c7aaf1a73e1e5e0dee.js → ghost.min-f3c6886e191d34450e9ffca0c8fa056e.js} +543 -618
- package/core/built/assets/icons/audio-upload.svg +8 -0
- package/core/built/assets/{vendor.min-c9002845b6c30ac978abdadde9f33d7c.js → vendor.min-b6b8d2a31d61830c2d8f65c5ba54236a.js} +2656 -2118
- package/core/frontend/apps/amp/lib/helpers/amp_content.js +2 -2
- package/core/frontend/apps/amp/lib/views/amp.hbs +75 -0
- package/core/frontend/apps/private-blogging/index.js +1 -1
- package/core/frontend/helpers/url.js +18 -1
- package/core/frontend/services/apps/index.js +1 -1
- package/core/frontend/services/apps/loader.js +3 -3
- package/core/frontend/services/card-assets/index.js +0 -12
- package/core/frontend/services/card-assets/service.js +22 -21
- package/core/frontend/services/helpers/handlebars.js +1 -1
- package/core/frontend/services/theme-engine/middleware/ensure-active-theme.js +34 -0
- package/core/frontend/services/theme-engine/middleware/index.js +6 -0
- package/core/frontend/services/theme-engine/middleware/update-global-template-options.js +116 -0
- package/core/frontend/services/theme-engine/middleware/update-local-template-data.js +9 -0
- package/core/frontend/services/theme-engine/middleware/update-local-template-options.js +57 -0
- package/core/frontend/src/cards/css/audio.css +186 -0
- package/core/frontend/src/cards/css/blockquote.css +27 -0
- package/core/frontend/src/cards/css/bookmark.css +7 -0
- package/core/frontend/src/cards/css/button.css +4 -0
- package/core/frontend/src/cards/css/callout.css +23 -15
- package/core/frontend/src/cards/css/gallery.css +13 -3
- package/core/frontend/src/cards/css/toggle.css +48 -15
- package/core/frontend/src/cards/js/audio.js +137 -0
- package/core/frontend/web/middleware/error-handler.js +93 -0
- package/core/frontend/web/middleware/handle-image-sizes.js +3 -6
- package/core/frontend/web/middleware/index.js +1 -0
- package/core/frontend/web/middleware/serve-public-file.js +25 -8
- package/core/frontend/web/site.js +2 -5
- package/core/server/adapters/scheduling/SchedulingDefault.js +2 -2
- package/core/server/adapters/storage/LocalStorageBase.js +2 -2
- package/core/server/api/canary/db.js +2 -2
- package/core/server/api/canary/media.js +3 -2
- package/core/server/api/canary/oembed.js +16 -1
- package/core/server/api/canary/session.js +1 -1
- package/core/server/api/canary/slugs.js +1 -1
- package/core/server/api/canary/utils/permissions.js +2 -2
- package/core/server/api/canary/utils/serializers/output/config.js +2 -6
- package/core/server/api/v2/db.js +2 -2
- package/core/server/api/v2/session.js +1 -1
- package/core/server/api/v2/slugs.js +1 -1
- package/core/server/api/v2/utils/permissions.js +2 -2
- package/core/server/api/v3/db.js +2 -2
- package/core/server/api/v3/session.js +1 -1
- package/core/server/api/v3/slugs.js +1 -1
- package/core/server/api/v3/utils/permissions.js +2 -2
- package/core/server/data/db/state-manager.js +4 -4
- package/core/server/data/exporter/export-filename.js +1 -1
- package/core/server/data/importer/handlers/json.js +1 -1
- package/core/server/data/importer/import-manager.js +1 -1
- package/core/server/data/importer/importers/data/base.js +1 -1
- package/core/server/data/migrations/utils.js +2 -2
- package/core/server/data/migrations/versions/1.25/1-update-koenig-beta-html.js +1 -0
- package/core/server/data/migrations/versions/3.1/08-add-uuid-values-to-members.js +1 -0
- package/core/server/data/migrations/versions/3.22/02-settings-key-renames.js +2 -0
- package/core/server/data/migrations/versions/3.22/05-migrate-members-subscription-settings.js +3 -0
- package/core/server/data/migrations/versions/3.22/06-migrate-stripe-connect-settings.js +2 -0
- package/core/server/data/migrations/versions/3.23/01-migrate-bulk-email-settings.js +1 -0
- package/core/server/data/migrations/versions/3.29/01-remove-duplicate-subscriptions.js +2 -0
- package/core/server/data/migrations/versions/3.29/02-remove-duplicate-customers.js +2 -0
- package/core/server/data/migrations/versions/3.38/04-populate-recipient-filter-column.js +2 -0
- package/core/server/data/migrations/versions/4.0/01-update-mobiledoc.js +2 -0
- package/core/server/data/migrations/versions/4.0/03-populate-status-column-for-members.js +4 -0
- package/core/server/data/migrations/versions/4.0/06-populate-members-subscribe-events-table.js +1 -0
- package/core/server/data/migrations/versions/4.0/17-populate-members-status-events-table.js +1 -0
- package/core/server/data/migrations/versions/4.0/18-transform-urls-absolute-to-transform-ready.js +5 -0
- package/core/server/data/migrations/versions/4.0/22-solve-orphaned-webhooks.js +1 -0
- package/core/server/data/migrations/versions/4.0/23-regenerate-posts-html.js +1 -0
- package/core/server/data/migrations/versions/4.0/25-populate-members-paid-subscription-events-table.js +2 -1
- package/core/server/data/migrations/versions/4.12/02-fix-member-statuses.js +1 -0
- package/core/server/data/migrations/versions/4.14/01-fix-comped-member-statuses.js +3 -0
- package/core/server/data/migrations/versions/4.14/02-fix-free-members-status-events.js +1 -0
- package/core/server/data/migrations/versions/4.20/05-remove-not-null-constraint-from-portal-title.js +2 -0
- package/core/server/data/migrations/versions/4.23/01-truncate-offer-names.js +1 -0
- package/core/server/data/migrations/versions/4.3/04-attach-members-to-product.js +1 -0
- package/core/server/data/migrations/versions/4.4/01-restore-free-members-signup-setting-from-backup.js +1 -0
- package/core/server/data/migrations/versions/4.6/01-remove-comped-status.js +1 -0
- package/core/server/data/migrations/versions/4.8/04-migrate-show-newsletter-header-setting.js +1 -0
- package/core/server/data/migrations/versions/4.9/05-fix-missed-mobiledoc-url-transforms.js +1 -0
- package/core/server/data/migrations/versions/4.9/06-add-comped-status.js +1 -0
- package/core/server/data/migrations/versions/4.9/07-update-comped-members-status-events.js +1 -0
- package/core/server/data/schema/commands.js +2 -2
- package/core/server/ghost-server.js +2 -2
- package/core/server/lib/image/image-size.js +2 -2
- package/core/server/models/base/listeners.js +2 -2
- package/core/server/models/member-email-change-event.js +2 -2
- package/core/server/models/member-login-event.js +2 -2
- package/core/server/models/member-paid-subscription-event.js +3 -3
- package/core/server/models/member-payment-event.js +3 -3
- package/core/server/models/member-product-event.js +6 -6
- package/core/server/models/member-status-event.js +5 -3
- package/core/server/models/member-subscribe-event.js +9 -3
- package/core/server/models/relations/authors.js +1 -1
- package/core/server/models/settings.js +1 -1
- package/core/server/notify.js +1 -2
- package/core/server/services/auth/passwordreset.js +1 -1
- package/core/server/services/auth/setup.js +1 -1
- package/core/server/services/email-analytics/jobs/index.js +1 -1
- package/core/server/services/mega/mega.js +6 -4
- package/core/server/services/mega/template.js +47 -17
- package/core/server/services/members/api.js +22 -0
- package/core/server/services/members/config.js +1 -1
- package/core/server/services/members/emails/signup-paid.js +168 -0
- package/core/server/services/members/service.js +6 -2
- package/core/server/services/members/stripe-connect.js +4 -2
- package/core/server/services/nft-oembed.js +7 -2
- package/core/server/services/oembed.js +15 -3
- package/core/server/services/permissions/can-this.js +1 -1
- package/core/server/services/redirects/api.js +20 -25
- 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/default-settings-manager.js +1 -1
- package/core/server/services/route-settings/index.js +40 -17
- package/core/server/services/route-settings/route-settings.js +120 -115
- package/core/server/services/route-settings/settings-loader.js +18 -36
- package/core/server/services/route-settings/yaml-parser.js +1 -1
- package/core/server/services/slack.js +1 -1
- package/core/server/services/themes/activation-bridge.js +3 -3
- package/core/server/services/themes/storage.js +2 -2
- package/core/server/services/twitter-embed.js +81 -0
- package/core/server/services/url/LocalFileCache.js +75 -0
- package/core/server/services/url/UrlService.js +15 -47
- package/core/server/services/url/index.js +17 -4
- package/core/server/services/xmlrpc.js +2 -2
- package/core/server/web/admin/app.js +2 -5
- 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/canary/admin/app.js +0 -3
- package/core/server/web/api/canary/admin/middleware.js +1 -1
- package/core/server/web/api/canary/content/app.js +0 -3
- package/core/server/web/api/v2/admin/app.js +0 -3
- package/core/server/web/api/v2/admin/middleware.js +1 -1
- package/core/server/web/api/v2/content/app.js +0 -3
- package/core/server/web/api/v3/admin/app.js +0 -3
- package/core/server/web/api/v3/admin/middleware.js +1 -1
- package/core/server/web/api/v3/content/app.js +0 -3
- package/core/server/web/members/app.js +0 -3
- package/core/server/web/oauth/app.js +0 -4
- package/core/server/web/parent/app.js +2 -13
- package/core/server/web/parent/backend.js +2 -0
- package/core/server/web/shared/middleware/error-handler.js +57 -162
- package/core/server/web/shared/middleware/index.js +0 -4
- package/core/shared/config/defaults.json +7 -1
- package/core/shared/express.js +1 -1
- package/core/shared/labs.js +12 -7
- package/core/shared/sentry.js +1 -1
- package/package.json +41 -40
- package/yarn.lock +799 -948
- package/content/themes/casper/assets/js/gallery-card.js +0 -24
- package/core/built/assets/ghost-dark-42cf6e0c730578940ec069bda45aea41.css +0 -1
- package/core/built/assets/ghost.min-fcf6a0738421f86c47c55f20d00c5ba9.css +0 -1
- package/core/frontend/services/theme-engine/middleware.js +0 -209
- package/core/server/web/shared/middleware/maintenance.js +0 -25
|
@@ -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)) {
|
|
@@ -48,7 +48,7 @@ class DefaultSettingsManager {
|
|
|
48
48
|
});
|
|
49
49
|
}).catch((error) => {
|
|
50
50
|
// CASE: we might have a permission error, as we can't access the directory
|
|
51
|
-
throw new errors.
|
|
51
|
+
throw new errors.InternalServerError({
|
|
52
52
|
message: tpl(messages.ensureSettings, {
|
|
53
53
|
path: this.destinationFolderPath
|
|
54
54
|
}),
|
|
@@ -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,22 +1,15 @@
|
|
|
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
|
-
loadError: 'Could not load
|
|
12
|
+
loadError: 'Could not load routes.yaml file.'
|
|
20
13
|
};
|
|
21
14
|
|
|
22
15
|
/**
|
|
@@ -31,134 +24,146 @@ 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
|
+
this.settingsLoader = settingsLoader;
|
|
43
|
+
this.settingsPath = settingsPath;
|
|
44
|
+
this.backupPath = backupPath;
|
|
45
|
+
}
|
|
46
46
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
47
|
+
/**
|
|
48
|
+
* @private
|
|
49
|
+
* @param {String} settingsPath
|
|
50
|
+
* @param {String} backupPath
|
|
51
|
+
*/
|
|
52
|
+
async createBackupFile(settingsPath, backupPath) {
|
|
53
|
+
return await fs.copy(settingsPath, backupPath);
|
|
54
|
+
}
|
|
50
55
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
56
|
+
/**
|
|
57
|
+
* @private
|
|
58
|
+
* @param {String} settingsPath
|
|
59
|
+
* @param {String} backupPath
|
|
60
|
+
*/
|
|
61
|
+
async restoreBackupFile(settingsPath, backupPath) {
|
|
62
|
+
return await fs.copy(backupPath, settingsPath);
|
|
63
|
+
}
|
|
54
64
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
}
|
|
65
|
+
/**
|
|
66
|
+
* @private
|
|
67
|
+
* @param {String} filePath
|
|
68
|
+
* @param {String} settingsPath
|
|
69
|
+
*/
|
|
70
|
+
async saveFile(filePath, settingsPath) {
|
|
71
|
+
return await fs.copy(filePath, settingsPath);
|
|
72
|
+
}
|
|
58
73
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
74
|
+
/**
|
|
75
|
+
* @private
|
|
76
|
+
* @param {String} settingsFilePath
|
|
77
|
+
*/
|
|
78
|
+
async readFile(settingsFilePath) {
|
|
79
|
+
return fs.readFile(settingsFilePath, 'utf-8')
|
|
80
|
+
.catch((err) => {
|
|
81
|
+
if (err.code === 'ENOENT') {
|
|
82
|
+
return Promise.resolve([]);
|
|
83
|
+
}
|
|
65
84
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
85
|
+
if (errors.utils.isGhostError(err)) {
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
69
88
|
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
throw new errors.NotFoundError({
|
|
90
|
+
err: err
|
|
91
|
+
});
|
|
72
92
|
});
|
|
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);
|
|
93
|
+
}
|
|
82
94
|
|
|
83
|
-
|
|
95
|
+
async setFromFilePath(filePath) {
|
|
96
|
+
await this.createBackupFile(this.settingsPath, this.backupPath);
|
|
97
|
+
await this.saveFile(filePath, this.settingsPath);
|
|
84
98
|
|
|
85
|
-
const bringBackValidRoutes = async () => {
|
|
86
99
|
urlService.resetGenerators({releaseResourcesOnly: true});
|
|
87
100
|
|
|
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
|
-
}
|
|
101
|
+
const bringBackValidRoutes = async () => {
|
|
102
|
+
urlService.resetGenerators({releaseResourcesOnly: true});
|
|
101
103
|
|
|
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
|
-
}
|
|
104
|
+
await this.restoreBackupFile(this.settingsPath, this.backupPath);
|
|
116
105
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
});
|
|
121
|
-
}
|
|
106
|
+
return bridge.reloadFrontend();
|
|
107
|
+
};
|
|
122
108
|
|
|
123
|
-
|
|
124
|
-
|
|
109
|
+
try {
|
|
110
|
+
await bridge.reloadFrontend();
|
|
111
|
+
} catch (err) {
|
|
125
112
|
return bringBackValidRoutes()
|
|
126
113
|
.finally(() => {
|
|
127
114
|
throw err;
|
|
128
115
|
});
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// @TODO: how can we get rid of this from here?
|
|
119
|
+
let tries = 0;
|
|
120
|
+
|
|
121
|
+
function isBlogRunning() {
|
|
122
|
+
debug('waiting for blog running');
|
|
123
|
+
return Promise.delay(1000)
|
|
124
|
+
.then(() => {
|
|
125
|
+
debug('waited for blog running');
|
|
126
|
+
if (!urlService.hasFinished()) {
|
|
127
|
+
if (tries > 5) {
|
|
128
|
+
throw new errors.InternalServerError({
|
|
129
|
+
message: tpl(messages.loadError)
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
tries = tries + 1;
|
|
134
|
+
return isBlogRunning();
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return isBlogRunning()
|
|
140
|
+
.catch((err) => {
|
|
141
|
+
return bringBackValidRoutes()
|
|
142
|
+
.finally(() => {
|
|
143
|
+
throw err;
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
}
|
|
137
147
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
const defaultRoutesSettingHash = '3d180d52c663d173a6be791ef411ed01';
|
|
148
|
+
async get() {
|
|
149
|
+
return this.readFile(this.settingsPath);
|
|
150
|
+
}
|
|
142
151
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
}
|
|
152
|
+
calculateHash(data) {
|
|
153
|
+
return crypto.createHash('md5')
|
|
154
|
+
.update(data, 'binary')
|
|
155
|
+
.digest('hex');
|
|
156
|
+
}
|
|
148
157
|
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
}
|
|
158
|
+
getDefaultHash() {
|
|
159
|
+
return this.defaultRoutesSettingHash;
|
|
160
|
+
}
|
|
152
161
|
|
|
153
|
-
|
|
154
|
-
|
|
162
|
+
async getCurrentHash() {
|
|
163
|
+
const data = await this.settingsLoader.loadSettings();
|
|
155
164
|
|
|
156
|
-
|
|
157
|
-
}
|
|
165
|
+
return this.calculateHash(JSON.stringify(data));
|
|
166
|
+
}
|
|
167
|
+
}
|
|
158
168
|
|
|
159
|
-
module.exports =
|
|
160
|
-
getDefaultHash: getDefaultHash,
|
|
161
|
-
setFromFilePath: setFromFilePath,
|
|
162
|
-
get: get,
|
|
163
|
-
getCurrentHash: getCurrentHash
|
|
164
|
-
};
|
|
169
|
+
module.exports = RouteSettings;
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
const fs = require('fs-extra');
|
|
2
|
-
const path = require('path');
|
|
3
2
|
const debug = require('@tryghost/debug')('frontend:services:settings:settings-loader');
|
|
4
3
|
const tpl = require('@tryghost/tpl');
|
|
5
4
|
const errors = require('@tryghost/errors');
|
|
6
|
-
const config = require('../../../shared/config');
|
|
7
5
|
const validate = require('./validate');
|
|
8
6
|
|
|
9
7
|
const messages = {
|
|
@@ -14,23 +12,12 @@ class SettingsLoader {
|
|
|
14
12
|
/**
|
|
15
13
|
* @param {Object} options
|
|
16
14
|
* @param {Function} options.parseYaml yaml parser
|
|
15
|
+
* @param {String} options.settingFilePath routes settings file path
|
|
17
16
|
*/
|
|
18
|
-
constructor({parseYaml}) {
|
|
17
|
+
constructor({parseYaml, settingFilePath}) {
|
|
19
18
|
this.parseYaml = parseYaml;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* NOTE: this method will have to go to an external module to reuse in redirects settings
|
|
24
|
-
* @param {String} setting type of the settings to load, e.g:'routes' or 'redirects'
|
|
25
|
-
* @returns {String} setting file path
|
|
26
|
-
*/
|
|
27
|
-
getSettingFilePath(setting) {
|
|
28
|
-
// we only support the `yaml` file extension. `yml` will be ignored.
|
|
29
|
-
const fileName = `${setting}.yaml`;
|
|
30
|
-
const contentPath = config.getContentPath('settings');
|
|
31
|
-
const filePath = path.join(contentPath, fileName);
|
|
32
19
|
|
|
33
|
-
|
|
20
|
+
this.settingFilePath = settingFilePath;
|
|
34
21
|
}
|
|
35
22
|
|
|
36
23
|
/**
|
|
@@ -40,27 +27,23 @@ class SettingsLoader {
|
|
|
40
27
|
* @returns {Promise<Object>} settingsFile
|
|
41
28
|
*/
|
|
42
29
|
async loadSettings() {
|
|
43
|
-
const setting = 'routes';
|
|
44
|
-
const filePath = this.getSettingFilePath(setting);
|
|
45
|
-
|
|
46
30
|
try {
|
|
47
|
-
const file = await fs.readFile(
|
|
48
|
-
debug('settings file found for',
|
|
31
|
+
const file = await fs.readFile(this.settingFilePath, 'utf8');
|
|
32
|
+
debug('routes settings file found for:', this.settingFilePath);
|
|
49
33
|
|
|
50
34
|
const object = this.parseYaml(file);
|
|
51
|
-
|
|
52
|
-
debug('YAML settings file parsed:', filePath);
|
|
35
|
+
debug('YAML settings file parsed:', this.settingFilePath);
|
|
53
36
|
|
|
54
37
|
return validate(object);
|
|
55
38
|
} catch (err) {
|
|
56
|
-
if (errors.utils.
|
|
39
|
+
if (errors.utils.isGhostError(err)) {
|
|
57
40
|
throw err;
|
|
58
41
|
}
|
|
59
42
|
|
|
60
|
-
throw new errors.
|
|
43
|
+
throw new errors.InternalServerError({
|
|
61
44
|
message: tpl(messages.settingsLoaderError, {
|
|
62
|
-
setting:
|
|
63
|
-
path:
|
|
45
|
+
setting: 'routes',
|
|
46
|
+
path: this.settingFilePath
|
|
64
47
|
}),
|
|
65
48
|
err: err
|
|
66
49
|
});
|
|
@@ -74,24 +57,23 @@ class SettingsLoader {
|
|
|
74
57
|
* @returns {Object} settingsFile in following format: {routes: {}, collections: {}, resources: {}}
|
|
75
58
|
*/
|
|
76
59
|
loadSettingsSync() {
|
|
77
|
-
const setting = 'routes';
|
|
78
|
-
const filePath = this.getSettingFilePath(setting);
|
|
79
|
-
|
|
80
60
|
try {
|
|
81
|
-
const file = fs.readFileSync(
|
|
82
|
-
debug('settings file found for',
|
|
61
|
+
const file = fs.readFileSync(this.settingFilePath, 'utf8');
|
|
62
|
+
debug('routes settings file found for:', this.settingFilePath);
|
|
83
63
|
|
|
84
64
|
const object = this.parseYaml(file);
|
|
65
|
+
debug('YAML settings file parsed:', this.settingFilePath);
|
|
66
|
+
|
|
85
67
|
return validate(object);
|
|
86
68
|
} catch (err) {
|
|
87
|
-
if (errors.utils.
|
|
69
|
+
if (errors.utils.isGhostError(err)) {
|
|
88
70
|
throw err;
|
|
89
71
|
}
|
|
90
72
|
|
|
91
|
-
throw new errors.
|
|
73
|
+
throw new errors.InternalServerError({
|
|
92
74
|
message: tpl(messages.settingsLoaderError, {
|
|
93
|
-
setting:
|
|
94
|
-
path:
|
|
75
|
+
setting: 'routes',
|
|
76
|
+
path: this.settingFilePath
|
|
95
77
|
}),
|
|
96
78
|
err: err
|
|
97
79
|
});
|
|
@@ -136,7 +136,7 @@ function ping(post) {
|
|
|
136
136
|
'Content-type': 'application/json'
|
|
137
137
|
}
|
|
138
138
|
}).catch(function (err) {
|
|
139
|
-
logging.error(new errors.
|
|
139
|
+
logging.error(new errors.InternalServerError({
|
|
140
140
|
err: err,
|
|
141
141
|
context: tpl(messages.requestFailedError, {service: 'slack'}),
|
|
142
142
|
help: tpl(messages.requestFailedHelp, {url: 'https://ghost.org/docs/'})
|
|
@@ -14,7 +14,7 @@ module.exports = {
|
|
|
14
14
|
if (labs.isSet('customThemeSettings')) {
|
|
15
15
|
await customThemeSettings.api.activateTheme(themeName, checkedTheme);
|
|
16
16
|
}
|
|
17
|
-
bridge.activateTheme(theme, checkedTheme);
|
|
17
|
+
await bridge.activateTheme(theme, checkedTheme);
|
|
18
18
|
},
|
|
19
19
|
activateFromAPI: async (themeName, theme, checkedTheme) => {
|
|
20
20
|
debug('Activating theme (method B on API "activate")', themeName);
|
|
@@ -22,7 +22,7 @@ module.exports = {
|
|
|
22
22
|
if (labs.isSet('customThemeSettings')) {
|
|
23
23
|
await customThemeSettings.api.activateTheme(themeName, checkedTheme);
|
|
24
24
|
}
|
|
25
|
-
bridge.activateTheme(theme, checkedTheme);
|
|
25
|
+
await bridge.activateTheme(theme, checkedTheme);
|
|
26
26
|
},
|
|
27
27
|
activateFromAPIOverride: async (themeName, theme, checkedTheme) => {
|
|
28
28
|
debug('Activating theme (method C on API "override")', themeName);
|
|
@@ -30,6 +30,6 @@ module.exports = {
|
|
|
30
30
|
if (labs.isSet('customThemeSettings')) {
|
|
31
31
|
await customThemeSettings.api.activateTheme(themeName, checkedTheme);
|
|
32
32
|
}
|
|
33
|
-
bridge.activateTheme(theme, checkedTheme);
|
|
33
|
+
await bridge.activateTheme(theme, checkedTheme);
|
|
34
34
|
}
|
|
35
35
|
};
|
|
@@ -112,7 +112,7 @@ module.exports = {
|
|
|
112
112
|
if (checkedTheme) {
|
|
113
113
|
fs.remove(checkedTheme.path)
|
|
114
114
|
.catch((err) => {
|
|
115
|
-
logging.error(new errors.
|
|
115
|
+
logging.error(new errors.InternalServerError({err: err}));
|
|
116
116
|
});
|
|
117
117
|
}
|
|
118
118
|
|
|
@@ -120,7 +120,7 @@ module.exports = {
|
|
|
120
120
|
getStorage()
|
|
121
121
|
.delete(backupName)
|
|
122
122
|
.catch((err) => {
|
|
123
|
-
logging.error(new errors.
|
|
123
|
+
logging.error(new errors.InternalServerError({err: err}));
|
|
124
124
|
});
|
|
125
125
|
}
|
|
126
126
|
},
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
const {extract} = require('oembed-parser');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @typedef {import('./oembed').ICustomProvider} ICustomProvider
|
|
5
|
+
* @typedef {import('./oembed').IExternalRequest} IExternalRequest
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const TWITTER_PATH_REGEX = /\/status\/(\d+)/;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* @implements ICustomProvider
|
|
12
|
+
*/
|
|
13
|
+
class TwitterOEmbedProvider {
|
|
14
|
+
/**
|
|
15
|
+
* @param {object} dependencies
|
|
16
|
+
*/
|
|
17
|
+
constructor(dependencies) {
|
|
18
|
+
this.dependencies = dependencies;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {URL} url
|
|
23
|
+
* @returns {Promise<boolean>}
|
|
24
|
+
*/
|
|
25
|
+
async canSupportRequest(url) {
|
|
26
|
+
return url.host === 'twitter.com' && TWITTER_PATH_REGEX.test(url.pathname);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* @param {URL} url
|
|
31
|
+
* @param {IExternalRequest} externalRequest
|
|
32
|
+
*
|
|
33
|
+
* @returns {Promise<object>}
|
|
34
|
+
*/
|
|
35
|
+
async getOEmbedData(url, externalRequest) {
|
|
36
|
+
const [match, tweetId] = url.pathname.match(TWITTER_PATH_REGEX);
|
|
37
|
+
if (!match) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** @type {object} */
|
|
42
|
+
const oembedData = await extract(url.href);
|
|
43
|
+
|
|
44
|
+
if (this.dependencies.config.bearerToken) {
|
|
45
|
+
const query = {
|
|
46
|
+
expansions: ['attachments.poll_ids', 'attachments.media_keys', 'author_id', 'entities.mentions.username', 'geo.place_id', 'in_reply_to_user_id', 'referenced_tweets.id', 'referenced_tweets.id.author_id'],
|
|
47
|
+
'media.fields': ['duration_ms', 'height', 'media_key', 'preview_image_url', 'type', 'url', 'width', 'public_metrics', 'alt_text'],
|
|
48
|
+
'place.fields': ['contained_within', 'country', 'country_code', 'full_name', 'geo', 'id', 'name', 'place_type'],
|
|
49
|
+
'poll.fields': ['duration_minutes', 'end_datetime', 'id', 'options', 'voting_status'],
|
|
50
|
+
'tweet.fields': ['attachments', 'author_id', 'context_annotations', 'conversation_id', 'created_at', 'entities', 'geo', 'id', 'in_reply_to_user_id', 'lang', 'public_metrics', 'possibly_sensitive', 'referenced_tweets', 'reply_settings', 'source', 'text', 'withheld'],
|
|
51
|
+
'user.fields': ['created_at', 'description', 'entities', 'id', 'location', 'name', 'pinned_tweet_id', 'profile_image_url', 'protected', 'public_metrics', 'url', 'username', 'verified', 'withheld']
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const queryString = Object.keys(query).map((key) => {
|
|
55
|
+
return `${key}=${query[key].join(',')}`;
|
|
56
|
+
}).join('&');
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
const result = await externalRequest(`https://api.twitter.com/2/tweets/${tweetId}?${queryString}`, {
|
|
60
|
+
responseType: 'json',
|
|
61
|
+
headers: {
|
|
62
|
+
Authorization: `Bearer ${this.dependencies.config.bearerToken}`
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
const body = JSON.parse(result.body);
|
|
67
|
+
|
|
68
|
+
oembedData.tweet_data = body.data;
|
|
69
|
+
oembedData.tweet_data.includes = body.includes;
|
|
70
|
+
} catch (err) {
|
|
71
|
+
this.dependencies.logging.error(err);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
oembedData.type = 'twitter';
|
|
76
|
+
|
|
77
|
+
return oembedData;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
module.exports = TwitterOEmbedProvider;
|