mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-02-03 23:00:14 -05:00
- 🛠 add bunyan and prettyjson, remove morgan - ✨ add logging module - GhostLogger class that handles setup of bunyan - PrettyStream for stdout - ✨ config for logging - @TODO: testing level fatal? - ✨ log each request via GhostLogger (express middleware) - @TODO: add errors to output - 🔥 remove errors.updateActiveTheme - we can read the value from config - 🔥 remove 15 helper functions in core/server/errors/index.js - all these functions get replaced by modules: 1. logging 2. error middleware handling for html/json 3. error creation (which will be part of PR #7477) - ✨ add express error handler for html/json - one true error handler for express responses - contains still some TODO's, but they are not high priority for first implementation/integration - this middleware only takes responsibility of either rendering html responses or return json error responses - 🎨 use new express error handler in middleware/index - 404 and 500 handling - 🎨 return error instead of error message in permissions/index.js - the rule for error handling should be: if you call a unit, this unit should return a custom Ghost error - 🎨 wrap serve static module - rule: if you call a module/unit, you should always wrap this error - it's always the same rule - so the caller never has to worry about what comes back - it's always a clear error instance - in this case: we return our notfounderror if serve static does not find the resource - this avoid having checks everywhere - 🎨 replace usages of errors/index.js functions and adapt tests - use logging.error, logging.warn - make tests green - remove some usages of logging and throwing api errors -> because when a request is involved, logging happens automatically - 🐛 return errorDetails to Ghost-Admin - errorDetails is used for Theme error handling - 🎨 use 500er error for theme is missing error in theme-handler - 🎨 extend file rotation to 1w
170 lines
5.9 KiB
JavaScript
170 lines
5.9 KiB
JavaScript
// # Themes API
|
|
// RESTful API for Themes
|
|
var Promise = require('bluebird'),
|
|
_ = require('lodash'),
|
|
gscan = require('gscan'),
|
|
fs = require('fs-extra'),
|
|
config = require('../config'),
|
|
errors = require('../errors'),
|
|
events = require('../events'),
|
|
logging = require('../logging'),
|
|
storage = require('../storage'),
|
|
settings = require('./settings'),
|
|
apiUtils = require('./utils'),
|
|
utils = require('./../utils'),
|
|
i18n = require('../i18n'),
|
|
themes;
|
|
|
|
/**
|
|
* ## Themes API Methods
|
|
*
|
|
* **See:** [API Methods](index.js.html#api%20methods)
|
|
*/
|
|
themes = {
|
|
loadThemes: function () {
|
|
return utils.readThemes(config.getContentPath('themes'))
|
|
.then(function (result) {
|
|
config.set('paths:availableThemes', result);
|
|
});
|
|
},
|
|
|
|
upload: function upload(options) {
|
|
options = options || {};
|
|
|
|
// consistent filename uploads
|
|
options.originalname = options.originalname.toLowerCase();
|
|
|
|
var storageAdapter = storage.getStorage('themes'),
|
|
zip = {
|
|
path: options.path,
|
|
name: options.originalname,
|
|
shortName: storageAdapter.getSanitizedFileName(options.originalname.split('.zip')[0])
|
|
}, theme;
|
|
|
|
// check if zip name is casper.zip
|
|
if (zip.name === 'casper.zip') {
|
|
throw new errors.ValidationError(i18n.t('errors.api.themes.overrideCasper'));
|
|
}
|
|
|
|
return apiUtils.handlePermissions('themes', 'add')(options)
|
|
.then(function () {
|
|
return gscan.checkZip(zip, {keepExtractedDir: true});
|
|
})
|
|
.then(function (_theme) {
|
|
theme = _theme;
|
|
theme = gscan.format(theme);
|
|
|
|
if (!theme.results.error.length) {
|
|
return;
|
|
}
|
|
|
|
throw new errors.ThemeValidationError(
|
|
i18n.t('errors.api.themes.invalidTheme'),
|
|
theme.results.error
|
|
);
|
|
})
|
|
.then(function () {
|
|
return storageAdapter.exists(config.getContentPath('themes') + '/' + zip.shortName);
|
|
})
|
|
.then(function (themeExists) {
|
|
// delete existing theme
|
|
if (themeExists) {
|
|
return storageAdapter.delete(zip.shortName, config.getContentPath('themes'));
|
|
}
|
|
})
|
|
.then(function () {
|
|
events.emit('theme.uploaded', zip.shortName);
|
|
// store extracted theme
|
|
return storageAdapter.save({
|
|
name: zip.shortName,
|
|
path: theme.path
|
|
}, config.getContentPath('themes'));
|
|
})
|
|
.then(function () {
|
|
// force reload of availableThemes
|
|
// right now the logic is in the ConfigManager
|
|
// if we create a theme collection, we don't have to read them from disk
|
|
return themes.loadThemes();
|
|
})
|
|
.then(function () {
|
|
// the settings endpoint is used to fetch the availableThemes
|
|
// so we have to force updating the in process cache
|
|
return settings.updateSettingsCache();
|
|
})
|
|
.then(function (settings) {
|
|
// gscan theme structure !== ghost theme structure
|
|
var themeObject = _.find(settings.availableThemes.value, {name: zip.shortName}) || {};
|
|
if (theme.results.warning.length > 0) {
|
|
themeObject.warnings = _.cloneDeep(theme.results.warning);
|
|
}
|
|
return {themes: [themeObject]};
|
|
})
|
|
.finally(function () {
|
|
// remove zip upload from multer
|
|
// happens in background
|
|
Promise.promisify(fs.removeSync)(zip.path)
|
|
.catch(function (err) {
|
|
logging.error(err);
|
|
});
|
|
|
|
// remove extracted dir from gscan
|
|
// happens in background
|
|
if (theme) {
|
|
Promise.promisify(fs.removeSync)(theme.path)
|
|
.catch(function (err) {
|
|
logging.error(err);
|
|
});
|
|
}
|
|
});
|
|
},
|
|
|
|
download: function download(options) {
|
|
var themeName = options.name,
|
|
theme = config.get('paths').availableThemes[themeName],
|
|
storageAdapter = storage.getStorage('themes');
|
|
|
|
if (!theme) {
|
|
return Promise.reject(new errors.BadRequestError(i18n.t('errors.api.themes.invalidRequest')));
|
|
}
|
|
|
|
return apiUtils.handlePermissions('themes', 'read')(options)
|
|
.then(function () {
|
|
events.emit('theme.downloaded', themeName);
|
|
return storageAdapter.serve({isTheme: true, name: themeName});
|
|
});
|
|
},
|
|
|
|
/**
|
|
* remove theme zip
|
|
* remove theme folder
|
|
*/
|
|
destroy: function destroy(options) {
|
|
var name = options.name,
|
|
theme,
|
|
storageAdapter = storage.getStorage('themes');
|
|
|
|
return apiUtils.handlePermissions('themes', 'destroy')(options)
|
|
.then(function () {
|
|
if (name === 'casper') {
|
|
throw new errors.ValidationError(i18n.t('errors.api.themes.destroyCasper'));
|
|
}
|
|
|
|
theme = config.get('paths').availableThemes[name];
|
|
|
|
if (!theme) {
|
|
throw new errors.NotFoundError(i18n.t('errors.api.themes.themeDoesNotExist'));
|
|
}
|
|
|
|
events.emit('theme.deleted', name);
|
|
return storageAdapter.delete(name, config.getContentPath('themes'));
|
|
})
|
|
.then(function () {
|
|
return themes.loadThemes();
|
|
})
|
|
.then(function () {
|
|
return settings.updateSettingsCache();
|
|
});
|
|
}
|
|
};
|
|
|
|
module.exports = themes;
|