2020-11-04 13:55:47 +03:00
|
|
|
const Promise = require('bluebird');
|
2019-06-20 14:19:22 +03:00
|
|
|
const fs = require('fs-extra');
|
2021-09-27 17:34:15 +03:00
|
|
|
const crypto = require('crypto');
|
2021-10-18 17:27:57 +03:00
|
|
|
const urlService = require('../url');
|
2019-06-20 14:19:22 +03:00
|
|
|
|
2021-06-28 14:09:02 +03:00
|
|
|
const debug = require('@tryghost/debug')('services:route-settings');
|
2020-05-22 21:22:20 +03:00
|
|
|
const errors = require('@tryghost/errors');
|
2021-06-27 16:10:35 +03:00
|
|
|
const tpl = require('@tryghost/tpl');
|
2021-06-27 15:38:48 +03:00
|
|
|
const bridge = require('../../../bridge');
|
2019-06-20 14:19:22 +03:00
|
|
|
|
2021-06-27 16:10:35 +03:00
|
|
|
const messages = {
|
2021-12-01 14:47:03 +03:00
|
|
|
loadError: 'Could not load routes.yaml file.'
|
2021-06-27 16:10:35 +03:00
|
|
|
};
|
|
|
|
|
2019-06-20 14:34:22 +03:00
|
|
|
/**
|
|
|
|
* The `routes.yaml` file offers a way to configure your Ghost blog. It's currently a setting feature
|
|
|
|
* we have added. That's why the `routes.yaml` file is treated as a "setting" right now.
|
|
|
|
* If we want to add single permissions for this file (e.g. upload/download routes.yaml), we can add later.
|
|
|
|
*
|
|
|
|
* How does it work?
|
|
|
|
*
|
|
|
|
* - we first reset all url generators (each url generator belongs to one express router)
|
|
|
|
* - we don't destroy the resources, we only release them (this avoids reloading all resources from the db again)
|
|
|
|
* - then we reload the whole site app, which will reset all routers and re-create the url generators
|
|
|
|
*/
|
2021-06-27 15:38:48 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
class RouteSettings {
|
2021-11-23 19:17:38 +03:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
* @param {Object} options
|
|
|
|
* @param {Object} options.settingsLoader
|
|
|
|
* @param {String} options.settingsPath
|
|
|
|
* @param {String} options.backupPath
|
|
|
|
*/
|
|
|
|
constructor({settingsLoader, settingsPath, backupPath}) {
|
2021-09-30 22:07:38 +03:00
|
|
|
/**
|
|
|
|
* md5 hashes of default routes settings
|
|
|
|
* @private
|
|
|
|
*/
|
|
|
|
this.defaultRoutesSettingHash = '3d180d52c663d173a6be791ef411ed01';
|
2021-11-23 19:17:38 +03:00
|
|
|
|
|
|
|
this.settingsLoader = settingsLoader;
|
|
|
|
this.settingsPath = settingsPath;
|
|
|
|
this.backupPath = backupPath;
|
2021-09-30 22:07:38 +03:00
|
|
|
}
|
2019-06-20 14:19:22 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {String} settingsPath
|
|
|
|
* @param {String} backupPath
|
|
|
|
*/
|
|
|
|
async createBackupFile(settingsPath, backupPath) {
|
|
|
|
return await fs.copy(settingsPath, backupPath);
|
|
|
|
}
|
2021-06-27 16:10:35 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {String} settingsPath
|
|
|
|
* @param {String} backupPath
|
|
|
|
*/
|
|
|
|
async restoreBackupFile(settingsPath, backupPath) {
|
|
|
|
return await fs.copy(backupPath, settingsPath);
|
|
|
|
}
|
2021-06-27 16:10:35 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {String} filePath
|
|
|
|
* @param {String} settingsPath
|
|
|
|
*/
|
|
|
|
async saveFile(filePath, settingsPath) {
|
|
|
|
return await fs.copy(filePath, settingsPath);
|
|
|
|
}
|
2021-06-27 16:10:35 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
/**
|
|
|
|
* @private
|
|
|
|
* @param {String} settingsFilePath
|
|
|
|
*/
|
|
|
|
async readFile(settingsFilePath) {
|
|
|
|
return fs.readFile(settingsFilePath, 'utf-8')
|
|
|
|
.catch((err) => {
|
|
|
|
if (err.code === 'ENOENT') {
|
|
|
|
return Promise.resolve([]);
|
|
|
|
}
|
2019-06-20 14:23:58 +03:00
|
|
|
|
2021-12-01 13:22:01 +03:00
|
|
|
if (errors.utils.isGhostError(err)) {
|
2021-09-30 22:07:38 +03:00
|
|
|
throw err;
|
|
|
|
}
|
2019-06-20 14:23:58 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
throw new errors.NotFoundError({
|
|
|
|
err: err
|
|
|
|
});
|
2019-06-20 14:23:58 +03:00
|
|
|
});
|
2021-09-30 22:07:38 +03:00
|
|
|
}
|
2021-06-27 16:10:35 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
async setFromFilePath(filePath) {
|
2021-11-23 19:17:38 +03:00
|
|
|
await this.createBackupFile(this.settingsPath, this.backupPath);
|
|
|
|
await this.saveFile(filePath, this.settingsPath);
|
2021-06-27 16:10:35 +03:00
|
|
|
|
|
|
|
urlService.resetGenerators({releaseResourcesOnly: true});
|
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
const bringBackValidRoutes = async () => {
|
|
|
|
urlService.resetGenerators({releaseResourcesOnly: true});
|
2021-06-27 16:10:35 +03:00
|
|
|
|
2021-11-23 19:17:38 +03:00
|
|
|
await this.restoreBackupFile(this.settingsPath, this.backupPath);
|
2021-06-27 16:10:35 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
return bridge.reloadFrontend();
|
|
|
|
};
|
2021-06-27 16:10:35 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
try {
|
2021-12-01 15:23:49 +03:00
|
|
|
await bridge.reloadFrontend();
|
2021-09-30 22:07:38 +03:00
|
|
|
} catch (err) {
|
2021-06-27 16:10:35 +03:00
|
|
|
return bringBackValidRoutes()
|
|
|
|
.finally(() => {
|
|
|
|
throw err;
|
|
|
|
});
|
2021-09-30 22:07:38 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// @TODO: how can we get rid of this from here?
|
|
|
|
let tries = 0;
|
|
|
|
|
|
|
|
function isBlogRunning() {
|
|
|
|
debug('waiting for blog running');
|
|
|
|
return Promise.delay(1000)
|
|
|
|
.then(() => {
|
|
|
|
debug('waited for blog running');
|
|
|
|
if (!urlService.hasFinished()) {
|
|
|
|
if (tries > 5) {
|
|
|
|
throw new errors.InternalServerError({
|
2021-12-01 14:47:03 +03:00
|
|
|
message: tpl(messages.loadError)
|
2021-09-30 22:07:38 +03:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
tries = tries + 1;
|
|
|
|
return isBlogRunning();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
return isBlogRunning()
|
|
|
|
.catch((err) => {
|
|
|
|
return bringBackValidRoutes()
|
|
|
|
.finally(() => {
|
|
|
|
throw err;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2021-06-27 16:10:35 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
async get() {
|
2021-11-23 19:17:38 +03:00
|
|
|
return this.readFile(this.settingsPath);
|
2021-09-30 22:07:38 +03:00
|
|
|
}
|
2021-09-27 17:34:15 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
calculateHash(data) {
|
|
|
|
return crypto.createHash('md5')
|
|
|
|
.update(data, 'binary')
|
|
|
|
.digest('hex');
|
|
|
|
}
|
2021-09-27 17:34:15 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
getDefaultHash() {
|
|
|
|
return this.defaultRoutesSettingHash;
|
|
|
|
}
|
2021-09-27 17:34:15 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
async getCurrentHash() {
|
2021-11-23 19:17:38 +03:00
|
|
|
const data = await this.settingsLoader.loadSettings();
|
2021-09-27 17:34:15 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
return this.calculateHash(JSON.stringify(data));
|
|
|
|
}
|
|
|
|
}
|
2021-09-27 17:34:15 +03:00
|
|
|
|
2021-09-30 22:07:38 +03:00
|
|
|
module.exports = RouteSettings;
|