mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-21 09:52:06 +03:00
cbec6aa49e
refs https://linear.app/tryghost/issue/CORE-35/refactor-route-and-redirect-settings - It's a step to making the module follow class+DI pattern before fully extracting it into an external libarary - Reminder, doing in Ghost repo instead of substituting big chunks all at once to have clear history of how the service evolved prior to the extraction into external lib!
186 lines
5.3 KiB
JavaScript
186 lines
5.3 KiB
JavaScript
const Promise = require('bluebird');
|
|
const fs = require('fs-extra');
|
|
const crypto = require('crypto');
|
|
const urlService = require('../url');
|
|
|
|
const debug = require('@tryghost/debug')('services:route-settings');
|
|
const errors = require('@tryghost/errors');
|
|
const tpl = require('@tryghost/tpl');
|
|
const config = require('../../../shared/config');
|
|
const bridge = require('../../../bridge');
|
|
const SettingsLoader = require('./settings-loader');
|
|
const parseYaml = require('./yaml-parser');
|
|
const SettingsPathManager = require('@tryghost/settings-path-manager');
|
|
|
|
const settingsPathManager = new SettingsPathManager({
|
|
type: 'routes',
|
|
paths: [config.getContentPath('settings')]
|
|
});
|
|
|
|
const settingsLoader = new SettingsLoader({
|
|
parseYaml,
|
|
storageFolderPath: config.getContentPath('settings')
|
|
});
|
|
|
|
const messages = {
|
|
loadError: 'Could not load {filename} file.'
|
|
};
|
|
|
|
/**
|
|
* 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
|
|
*/
|
|
|
|
class RouteSettings {
|
|
constructor() {
|
|
/**
|
|
* md5 hashes of default routes settings
|
|
* @private
|
|
*/
|
|
this.defaultRoutesSettingHash = '3d180d52c663d173a6be791ef411ed01';
|
|
/**
|
|
* @private
|
|
*/
|
|
this.filename = 'routes';
|
|
/**
|
|
* @private
|
|
*/
|
|
this.ext = 'yaml';
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* @param {String} settingsPath
|
|
* @param {String} backupPath
|
|
*/
|
|
async createBackupFile(settingsPath, backupPath) {
|
|
return await fs.copy(settingsPath, backupPath);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* @param {String} settingsPath
|
|
* @param {String} backupPath
|
|
*/
|
|
async restoreBackupFile(settingsPath, backupPath) {
|
|
return await fs.copy(backupPath, settingsPath);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* @param {String} filePath
|
|
* @param {String} settingsPath
|
|
*/
|
|
async saveFile(filePath, settingsPath) {
|
|
return await fs.copy(filePath, settingsPath);
|
|
}
|
|
|
|
/**
|
|
* @private
|
|
* @param {String} settingsFilePath
|
|
*/
|
|
async readFile(settingsFilePath) {
|
|
return fs.readFile(settingsFilePath, 'utf-8')
|
|
.catch((err) => {
|
|
if (err.code === 'ENOENT') {
|
|
return Promise.resolve([]);
|
|
}
|
|
|
|
if (errors.utils.isIgnitionError(err)) {
|
|
throw err;
|
|
}
|
|
|
|
throw new errors.NotFoundError({
|
|
err: err
|
|
});
|
|
});
|
|
}
|
|
|
|
async setFromFilePath(filePath) {
|
|
const settingsPath = settingsPathManager.getDefaultFilePath();
|
|
const backupPath = settingsPathManager.getBackupFilePath();
|
|
|
|
await this.createBackupFile(settingsPath, backupPath);
|
|
await this.saveFile(filePath, settingsPath);
|
|
|
|
urlService.resetGenerators({releaseResourcesOnly: true});
|
|
|
|
const bringBackValidRoutes = async () => {
|
|
urlService.resetGenerators({releaseResourcesOnly: true});
|
|
|
|
await this.restoreBackupFile(settingsPath, backupPath);
|
|
|
|
return bridge.reloadFrontend();
|
|
};
|
|
|
|
try {
|
|
bridge.reloadFrontend();
|
|
} catch (err) {
|
|
return bringBackValidRoutes()
|
|
.finally(() => {
|
|
throw err;
|
|
});
|
|
}
|
|
|
|
// @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({
|
|
message: tpl(messages.loadError, {filename: `${this.filename}.${this.ext}`})
|
|
});
|
|
}
|
|
|
|
tries = tries + 1;
|
|
return isBlogRunning();
|
|
}
|
|
});
|
|
}
|
|
|
|
return isBlogRunning()
|
|
.catch((err) => {
|
|
return bringBackValidRoutes()
|
|
.finally(() => {
|
|
throw err;
|
|
});
|
|
});
|
|
}
|
|
|
|
async get() {
|
|
const settingsFilePath = settingsPathManager.getDefaultFilePath();
|
|
|
|
return this.readFile(settingsFilePath);
|
|
}
|
|
|
|
calculateHash(data) {
|
|
return crypto.createHash('md5')
|
|
.update(data, 'binary')
|
|
.digest('hex');
|
|
}
|
|
|
|
getDefaultHash() {
|
|
return this.defaultRoutesSettingHash;
|
|
}
|
|
|
|
async getCurrentHash() {
|
|
const data = await settingsLoader.loadSettings();
|
|
|
|
return this.calculateHash(JSON.stringify(data));
|
|
}
|
|
}
|
|
|
|
module.exports = RouteSettings;
|