🐛🎨 Theme API minor improvements (#8197)

no issue

🎨 🐛 Ensure cache is updated correctly for themes
- Insure the cache invalidation headers are always set correctly for the themes API

📖  Theme API comments / function naming
- this is an update for clarity, so we can see what further improvements can be made

🐛 🎨 Add permissions to themes.browse
This commit is contained in:
Hannah Wolfe 2017-03-20 18:02:44 +00:00 committed by Katharina Irrgang
parent 4e3e1bdfc9
commit fad0ac7213
2 changed files with 58 additions and 24 deletions

View File

@ -36,8 +36,18 @@ var _ = require('lodash'),
contentDispositionHeaderExport, contentDispositionHeaderExport,
contentDispositionHeaderSubscribers; contentDispositionHeaderSubscribers;
function isActiveThemeOverride(method, endpoint, result) { function isActiveThemeUpdate(method, endpoint, result) {
return method === 'POST' && endpoint === 'themes' && result.themes && result.themes[0] && result.themes[0].active === true; if (endpoint === 'themes') {
if (method === 'PUT') {
return true;
}
if (method === 'POST' && result.themes && result.themes[0] && result.themes[0].active === true) {
return true;
}
}
return false;
} }
/** /**
@ -64,7 +74,7 @@ cacheInvalidationHeader = function cacheInvalidationHeader(req, result) {
hasStatusChanged, hasStatusChanged,
wasPublishedUpdated; wasPublishedUpdated;
if (isActiveThemeOverride(method, endpoint, result)) { if (isActiveThemeUpdate(method, endpoint, result)) {
// Special case for if we're overwriting an active theme // Special case for if we're overwriting an active theme
// @TODO: remove this crazy DIRTY HORRIBLE HACK // @TODO: remove this crazy DIRTY HORRIBLE HACK
req.app.set('activeTheme', null); req.app.set('activeTheme', null);

View File

@ -23,8 +23,15 @@ var debug = require('debug')('ghost:api:themes'),
* **See:** [API Methods](index.js.html#api%20methods) * **See:** [API Methods](index.js.html#api%20methods)
*/ */
themes = { themes = {
browse: function browse() { browse: function browse(options) {
return Promise.resolve(themeUtils.toJSON()); return apiUtils
// Permissions
.handlePermissions('themes', 'browse')(options)
// Main action
.then(function makeApiResult() {
// Return JSON result
return themeUtils.toJSON();
});
}, },
activate: function activate(options) { activate: function activate(options) {
@ -37,8 +44,10 @@ themes = {
checkedTheme; checkedTheme;
return apiUtils return apiUtils
// Permissions
.handlePermissions('themes', 'activate')(options) .handlePermissions('themes', 'activate')(options)
.then(function activateTheme() { // Validation
.then(function validateTheme() {
loadedTheme = themeList.get(themeName); loadedTheme = themeList.get(themeName);
if (!loadedTheme) { if (!loadedTheme) {
@ -50,16 +59,19 @@ themes = {
return themeUtils.validate.check(loadedTheme); return themeUtils.validate.check(loadedTheme);
}) })
.then(function haveValidTheme(_checkedTheme) { // Update setting
.then(function changeActiveThemeSetting(_checkedTheme) {
checkedTheme = _checkedTheme; checkedTheme = _checkedTheme;
// We use the model, not the API here, as we don't want to trigger permissions // We use the model, not the API here, as we don't want to trigger permissions
return settingsModel.edit(newSettings, options); return settingsModel.edit(newSettings, options);
}) })
// Call activate
.then(function hasEditedSetting() { .then(function hasEditedSetting() {
// Activate! (sort of) // Activate! (sort of)
debug('Activating theme (method B on API "activate")', themeName); debug('Activating theme (method B on API "activate")', themeName);
themeUtils.activate(loadedTheme, checkedTheme); themeUtils.activate(loadedTheme, checkedTheme);
// Return JSON result
return themeUtils.toJSON(themeName, checkedTheme); return themeUtils.toJSON(themeName, checkedTheme);
}); });
}, },
@ -83,22 +95,27 @@ themes = {
throw new errors.ValidationError({message: i18n.t('errors.api.themes.overrideCasper')}); throw new errors.ValidationError({message: i18n.t('errors.api.themes.overrideCasper')});
} }
return apiUtils.handlePermissions('themes', 'add')(options) return apiUtils
// Permissions
.handlePermissions('themes', 'add')(options)
// Validation
.then(function validateTheme() { .then(function validateTheme() {
return themeUtils.validate.check(zip, true); return themeUtils.validate.check(zip, true);
}) })
// More validation (existence check)
.then(function checkExists(_checkedTheme) { .then(function checkExists(_checkedTheme) {
checkedTheme = _checkedTheme; checkedTheme = _checkedTheme;
return storageAdapter.exists(utils.url.urlJoin(config.getContentPath('themes'), zip.shortName)); return storageAdapter.exists(utils.url.urlJoin(config.getContentPath('themes'), zip.shortName));
}) })
.then(function (themeExists) { // If the theme existed we need to delete it
.then(function removeOldTheme(themeExists) {
// delete existing theme // delete existing theme
if (themeExists) { if (themeExists) {
return storageAdapter.delete(zip.shortName, config.getContentPath('themes')); return storageAdapter.delete(zip.shortName, config.getContentPath('themes'));
} }
}) })
.then(function () { .then(function storeNewTheme() {
events.emit('theme.uploaded', zip.shortName); events.emit('theme.uploaded', zip.shortName);
// store extracted theme // store extracted theme
return storageAdapter.save({ return storageAdapter.save({
@ -106,12 +123,12 @@ themes = {
path: checkedTheme.path path: checkedTheme.path
}, config.getContentPath('themes')); }, config.getContentPath('themes'));
}) })
.then(function () { .then(function loadNewTheme() {
// Loads the theme from the filesystem // Loads the theme from the filesystem
// Sets the theme on the themeList // Sets the theme on the themeList
return themeUtils.loadOne(zip.shortName); return themeUtils.loadOne(zip.shortName);
}) })
.then(function (loadedTheme) { .then(function activateAndReturn(loadedTheme) {
// If this is the active theme, we are overriding // If this is the active theme, we are overriding
// This is a special case of activation // This is a special case of activation
if (zip.shortName === settingsCache.get('activeTheme')) { if (zip.shortName === settingsCache.get('activeTheme')) {
@ -153,8 +170,10 @@ themes = {
return Promise.reject(new errors.BadRequestError({message: i18n.t('errors.api.themes.invalidRequest')})); return Promise.reject(new errors.BadRequestError({message: i18n.t('errors.api.themes.invalidRequest')}));
} }
return apiUtils.handlePermissions('themes', 'read')(options) return apiUtils
.then(function () { // Permissions
.handlePermissions('themes', 'read')(options)
.then(function sendTheme() {
events.emit('theme.downloaded', themeName); events.emit('theme.downloaded', themeName);
return storageAdapter.serve({isTheme: true, name: themeName}); return storageAdapter.serve({isTheme: true, name: themeName});
}); });
@ -165,31 +184,36 @@ themes = {
* remove theme folder * remove theme folder
*/ */
destroy: function destroy(options) { destroy: function destroy(options) {
var name = options.name, var themeName = options.name,
theme, theme,
storageAdapter = storage.getStorage('themes'); storageAdapter = storage.getStorage('themes');
return apiUtils.handlePermissions('themes', 'destroy')(options) return apiUtils
.then(function () { // Permissions
if (name === 'casper') { .handlePermissions('themes', 'destroy')(options)
// Validation
.then(function validateTheme() {
if (themeName === 'casper') {
throw new errors.ValidationError({message: i18n.t('errors.api.themes.destroyCasper')}); throw new errors.ValidationError({message: i18n.t('errors.api.themes.destroyCasper')});
} }
if (name === settingsCache.get('activeTheme')) { if (themeName === settingsCache.get('activeTheme')) {
throw new errors.ValidationError({message: i18n.t('errors.api.themes.destroyActive')}); throw new errors.ValidationError({message: i18n.t('errors.api.themes.destroyActive')});
} }
theme = themeList.get(name); theme = themeList.get(themeName);
if (!theme) { if (!theme) {
throw new errors.NotFoundError({message: i18n.t('errors.api.themes.themeDoesNotExist')}); throw new errors.NotFoundError({message: i18n.t('errors.api.themes.themeDoesNotExist')});
} }
return storageAdapter.delete(name, config.getContentPath('themes')); // Actually do the deletion here
return storageAdapter.delete(themeName, config.getContentPath('themes'));
}) })
.then(function () { // And some extra stuff to maintain state here
themeList.del(name); .then(function deleteTheme() {
events.emit('theme.deleted', name); themeList.del(themeName);
events.emit('theme.deleted', themeName);
// Delete returns an empty 204 response // Delete returns an empty 204 response
}); });
} }