mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-03-11 02:12:21 -05:00
Rework the themeService public API
refs: https://github.com/TryGhost/Team/issues/831 - This ultimately fixes the index.js file - It also makes it super clear what methods in the themeService are used by the API, and which are part of the service loading logic - It also moves the activate and init function into a single file in a way that highlights they are very similar - They are also very similar to what happens in storage.setFromZip but that code is mixed up with storage code at the moment
This commit is contained in:
parent
c3774a3fab
commit
4da7e7f0cb
6 changed files with 114 additions and 93 deletions
|
@ -16,7 +16,7 @@ module.exports = {
|
||||||
browse: {
|
browse: {
|
||||||
permissions: true,
|
permissions: true,
|
||||||
query() {
|
query() {
|
||||||
return themeService.getJSON();
|
return themeService.api.getJSON();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -47,14 +47,14 @@ module.exports = {
|
||||||
value: themeName
|
value: themeName
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return themeService.activate(themeName)
|
return themeService.api.activate(themeName)
|
||||||
.then((checkedTheme) => {
|
.then((checkedTheme) => {
|
||||||
// @NOTE: we use the model, not the API here, as we don't want to trigger permissions
|
// @NOTE: we use the model, not the API here, as we don't want to trigger permissions
|
||||||
return models.Settings.edit(newSettings, frame.options)
|
return models.Settings.edit(newSettings, frame.options)
|
||||||
.then(() => checkedTheme);
|
.then(() => checkedTheme);
|
||||||
})
|
})
|
||||||
.then((checkedTheme) => {
|
.then((checkedTheme) => {
|
||||||
return themeService.getJSON(themeName, checkedTheme);
|
return themeService.api.getJSON(themeName, checkedTheme);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -115,7 +115,7 @@ module.exports = {
|
||||||
path: downloadPath,
|
path: downloadPath,
|
||||||
name: zipName
|
name: zipName
|
||||||
};
|
};
|
||||||
const {theme, themeOverridden} = await themeService.storage.setFromZip(zip);
|
const {theme, themeOverridden} = await themeService.api.setFromZip(zip);
|
||||||
|
|
||||||
if (themeOverridden) {
|
if (themeOverridden) {
|
||||||
this.headers.cacheInvalidate = true;
|
this.headers.cacheInvalidate = true;
|
||||||
|
@ -160,7 +160,7 @@ module.exports = {
|
||||||
name: frame.file.originalname
|
name: frame.file.originalname
|
||||||
};
|
};
|
||||||
|
|
||||||
return themeService.storage.setFromZip(zip)
|
return themeService.api.setFromZip(zip)
|
||||||
.then(({theme, themeOverridden}) => {
|
.then(({theme, themeOverridden}) => {
|
||||||
if (themeOverridden) {
|
if (themeOverridden) {
|
||||||
// CASE: clear cache
|
// CASE: clear cache
|
||||||
|
@ -189,7 +189,7 @@ module.exports = {
|
||||||
query(frame) {
|
query(frame) {
|
||||||
let themeName = frame.options.name;
|
let themeName = frame.options.name;
|
||||||
|
|
||||||
return themeService.storage.getZip(themeName);
|
return themeService.api.getZip(themeName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -212,7 +212,7 @@ module.exports = {
|
||||||
query(frame) {
|
query(frame) {
|
||||||
let themeName = frame.options.name;
|
let themeName = frame.options.name;
|
||||||
|
|
||||||
return themeService.storage.destroy(themeName);
|
return themeService.api.destroy(themeName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@ module.exports = {
|
||||||
browse: {
|
browse: {
|
||||||
permissions: true,
|
permissions: true,
|
||||||
query() {
|
query() {
|
||||||
return themeService.getJSON();
|
return themeService.api.getJSON();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -40,14 +40,14 @@ module.exports = {
|
||||||
value: themeName
|
value: themeName
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return themeService.activate(themeName)
|
return themeService.api.activate(themeName)
|
||||||
.then((checkedTheme) => {
|
.then((checkedTheme) => {
|
||||||
// @NOTE: we use the model, not the API here, as we don't want to trigger permissions
|
// @NOTE: we use the model, not the API here, as we don't want to trigger permissions
|
||||||
return models.Settings.edit(newSettings, frame.options)
|
return models.Settings.edit(newSettings, frame.options)
|
||||||
.then(() => checkedTheme);
|
.then(() => checkedTheme);
|
||||||
})
|
})
|
||||||
.then((checkedTheme) => {
|
.then((checkedTheme) => {
|
||||||
return themeService.getJSON(themeName, checkedTheme);
|
return themeService.api.getJSON(themeName, checkedTheme);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -73,7 +73,7 @@ module.exports = {
|
||||||
name: frame.file.originalname
|
name: frame.file.originalname
|
||||||
};
|
};
|
||||||
|
|
||||||
return themeService.storage.setFromZip(zip)
|
return themeService.api.setFromZip(zip)
|
||||||
.then(({theme, themeOverridden}) => {
|
.then(({theme, themeOverridden}) => {
|
||||||
if (themeOverridden) {
|
if (themeOverridden) {
|
||||||
// CASE: clear cache
|
// CASE: clear cache
|
||||||
|
@ -102,7 +102,7 @@ module.exports = {
|
||||||
query(frame) {
|
query(frame) {
|
||||||
let themeName = frame.options.name;
|
let themeName = frame.options.name;
|
||||||
|
|
||||||
return themeService.storage.getZip(themeName);
|
return themeService.api.getZip(themeName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ module.exports = {
|
||||||
query(frame) {
|
query(frame) {
|
||||||
let themeName = frame.options.name;
|
let themeName = frame.options.name;
|
||||||
|
|
||||||
return themeService.storage.destroy(themeName);
|
return themeService.api.destroy(themeName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,7 +9,7 @@ module.exports = {
|
||||||
browse: {
|
browse: {
|
||||||
permissions: true,
|
permissions: true,
|
||||||
query() {
|
query() {
|
||||||
return themeService.getJSON();
|
return themeService.api.getJSON();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -40,14 +40,14 @@ module.exports = {
|
||||||
value: themeName
|
value: themeName
|
||||||
}];
|
}];
|
||||||
|
|
||||||
return themeService.activate(themeName)
|
return themeService.api.activate(themeName)
|
||||||
.then((checkedTheme) => {
|
.then((checkedTheme) => {
|
||||||
// @NOTE: we use the model, not the API here, as we don't want to trigger permissions
|
// @NOTE: we use the model, not the API here, as we don't want to trigger permissions
|
||||||
return models.Settings.edit(newSettings, frame.options)
|
return models.Settings.edit(newSettings, frame.options)
|
||||||
.then(() => checkedTheme);
|
.then(() => checkedTheme);
|
||||||
})
|
})
|
||||||
.then((checkedTheme) => {
|
.then((checkedTheme) => {
|
||||||
return themeService.getJSON(themeName, checkedTheme);
|
return themeService.api.getJSON(themeName, checkedTheme);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -71,7 +71,7 @@ module.exports = {
|
||||||
name: frame.file.originalname
|
name: frame.file.originalname
|
||||||
};
|
};
|
||||||
|
|
||||||
return themeService.storage.setFromZip(zip)
|
return themeService.api.setFromZip(zip)
|
||||||
.then(({theme, themeOverridden}) => {
|
.then(({theme, themeOverridden}) => {
|
||||||
if (themeOverridden) {
|
if (themeOverridden) {
|
||||||
// CASE: clear cache
|
// CASE: clear cache
|
||||||
|
@ -100,7 +100,7 @@ module.exports = {
|
||||||
query(frame) {
|
query(frame) {
|
||||||
let themeName = frame.options.name;
|
let themeName = frame.options.name;
|
||||||
|
|
||||||
return themeService.storage.getZip(themeName);
|
return themeService.api.getZip(themeName);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -123,7 +123,7 @@ module.exports = {
|
||||||
query(frame) {
|
query(frame) {
|
||||||
let themeName = frame.options.name;
|
let themeName = frame.options.name;
|
||||||
|
|
||||||
return themeService.storage.destroy(themeName);
|
return themeService.api.destroy(themeName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
62
core/server/services/themes/activate.js
Normal file
62
core/server/services/themes/activate.js
Normal file
|
@ -0,0 +1,62 @@
|
||||||
|
const debug = require('@tryghost/debug')('themes');
|
||||||
|
|
||||||
|
const errors = require('@tryghost/errors');
|
||||||
|
const logging = require('@tryghost/logging');
|
||||||
|
const tpl = require('@tryghost/tpl');
|
||||||
|
|
||||||
|
const activator = require('./activation-bridge');
|
||||||
|
const list = require('./list');
|
||||||
|
const themeLoader = require('./loader');
|
||||||
|
const validate = require('./validate');
|
||||||
|
|
||||||
|
const messages = {
|
||||||
|
activeThemeIsMissing: 'The currently active theme "{theme}" is missing.',
|
||||||
|
themeCannotBeActivated: '{themeName} cannot be activated because it was not found in the theme directory.'
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.loadAndActivate = async (themeName) => {
|
||||||
|
debug('loadAndActivate', themeName);
|
||||||
|
try {
|
||||||
|
// Just read the active theme for now
|
||||||
|
const loadedTheme = await themeLoader.loadOneTheme(themeName);
|
||||||
|
// Validate
|
||||||
|
// @NOTE: this is now the only usage of check, rather than checkSafe...
|
||||||
|
const checkedTheme = await validate.check(loadedTheme);
|
||||||
|
|
||||||
|
if (!validate.canActivate(checkedTheme)) {
|
||||||
|
logging.error(validate.getThemeValidationError('activeThemeHasFatalErrors', themeName, checkedTheme));
|
||||||
|
} else if (checkedTheme.results.error.length) {
|
||||||
|
// CASE: inform that the theme has errors, but not fatal (theme still works)
|
||||||
|
logging.warn(validate.getThemeValidationError('activeThemeHasErrors', themeName, checkedTheme));
|
||||||
|
}
|
||||||
|
|
||||||
|
activator.activateFromBoot(themeName, loadedTheme, checkedTheme);
|
||||||
|
} catch (err) {
|
||||||
|
if (err instanceof errors.NotFoundError) {
|
||||||
|
// CASE: active theme is missing, we don't want to exit because the admin panel will still work
|
||||||
|
err.message = tpl(messages.activeThemeIsMissing, {theme: themeName});
|
||||||
|
}
|
||||||
|
|
||||||
|
// CASE: theme threw an odd error, we don't want to exit because the admin panel will still work
|
||||||
|
// This is the absolute catch-all, at this point, we do not know what went wrong!
|
||||||
|
logging.error(err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.activate = async (themeName) => {
|
||||||
|
const loadedTheme = list.get(themeName);
|
||||||
|
|
||||||
|
if (!loadedTheme) {
|
||||||
|
throw new errors.ValidationError({
|
||||||
|
message: tpl(messages.themeCannotBeActivated, {themeName: themeName}),
|
||||||
|
errorDetails: themeName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate
|
||||||
|
const checkedTheme = await validate.checkSafe(themeName, loadedTheme);
|
||||||
|
// Activate
|
||||||
|
activator.activateFromAPI(themeName, loadedTheme, checkedTheme);
|
||||||
|
// Return the checked theme
|
||||||
|
return checkedTheme;
|
||||||
|
};
|
|
@ -1,73 +1,31 @@
|
||||||
const debug = require('@tryghost/debug')('themes');
|
const activate = require('./activate');
|
||||||
const logging = require('@tryghost/logging');
|
|
||||||
const errors = require('@tryghost/errors');
|
|
||||||
const tpl = require('@tryghost/tpl');
|
|
||||||
const themeLoader = require('./loader');
|
const themeLoader = require('./loader');
|
||||||
const validate = require('./validate');
|
const storage = require('./storage');
|
||||||
const list = require('./list');
|
const getJSON = require('./to-json');
|
||||||
const activator = require('./activation-bridge');
|
|
||||||
const settingsCache = require('../../../shared/settings-cache');
|
const settingsCache = require('../../../shared/settings-cache');
|
||||||
|
|
||||||
const messages = {
|
|
||||||
activeThemeIsMissing: 'The currently active theme "{theme}" is missing.',
|
|
||||||
themeCannotBeActivated: '{themeName} cannot be activated because it was not found in the theme directory.'
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
// Init themes module
|
/*
|
||||||
// TODO: move this once we're clear what needs to happen here
|
* Load the currently active theme
|
||||||
|
*/
|
||||||
init: async () => {
|
init: async () => {
|
||||||
const activeThemeName = settingsCache.get('active_theme');
|
const themeName = settingsCache.get('active_theme');
|
||||||
|
|
||||||
debug('init themes', activeThemeName);
|
return activate.loadAndActivate(themeName);
|
||||||
try {
|
|
||||||
// Just read the active theme for now
|
|
||||||
const theme = await themeLoader.loadOneTheme(activeThemeName);
|
|
||||||
// Validate
|
|
||||||
// @NOTE: this is now the only usage of check, rather than checkSafe...
|
|
||||||
const checkedTheme = await validate.check(theme);
|
|
||||||
|
|
||||||
if (!validate.canActivate(checkedTheme)) {
|
|
||||||
logging.error(validate.getThemeValidationError('activeThemeHasFatalErrors', activeThemeName, checkedTheme));
|
|
||||||
} else if (checkedTheme.results.error.length) {
|
|
||||||
// CASE: inform that the theme has errors, but not fatal (theme still works)
|
|
||||||
logging.warn(validate.getThemeValidationError('activeThemeHasErrors', activeThemeName, checkedTheme));
|
|
||||||
}
|
|
||||||
|
|
||||||
activator.activateFromBoot(activeThemeName, theme, checkedTheme);
|
|
||||||
} catch (err) {
|
|
||||||
if (err instanceof errors.NotFoundError) {
|
|
||||||
// CASE: active theme is missing, we don't want to exit because the admin panel will still work
|
|
||||||
err.message = tpl(messages.activeThemeIsMissing, {theme: activeThemeName});
|
|
||||||
}
|
|
||||||
|
|
||||||
// CASE: theme threw an odd error, we don't want to exit because the admin panel will still work
|
|
||||||
// This is the absolute catch-all, at this point, we do not know what went wrong!
|
|
||||||
logging.error(err);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
getJSON: require('./to-json'),
|
|
||||||
activate: async (themeName) => {
|
|
||||||
const loadedTheme = list.get(themeName);
|
|
||||||
|
|
||||||
if (!loadedTheme) {
|
|
||||||
throw new errors.ValidationError({
|
|
||||||
message: tpl(messages.themeCannotBeActivated, {themeName: themeName}),
|
|
||||||
errorDetails: themeName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const checkedTheme = await validate.checkSafe(themeName, loadedTheme);
|
|
||||||
|
|
||||||
activator.activateFromAPI(themeName, loadedTheme, checkedTheme);
|
|
||||||
|
|
||||||
return checkedTheme;
|
|
||||||
},
|
|
||||||
storage: require('./storage'),
|
|
||||||
/**
|
/**
|
||||||
* Load all inactive themes
|
* Load all inactive themes
|
||||||
*/
|
*/
|
||||||
loadInactiveThemes: async () => {
|
loadInactiveThemes: themeLoader.loadAllThemes,
|
||||||
return await themeLoader.loadAllThemes();
|
/**
|
||||||
|
* Methods used in the API
|
||||||
|
*/
|
||||||
|
api: {
|
||||||
|
getJSON,
|
||||||
|
activate: activate.activate,
|
||||||
|
getZip: storage.getZip,
|
||||||
|
setFromZip: storage.setFromZip,
|
||||||
|
destroy: storage.destroy
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -14,7 +14,6 @@ const activator = require('./activation-bridge');
|
||||||
const toJSON = require('./to-json');
|
const toJSON = require('./to-json');
|
||||||
|
|
||||||
const settingsCache = require('../../../shared/settings-cache');
|
const settingsCache = require('../../../shared/settings-cache');
|
||||||
const bridge = require('../../../bridge');
|
|
||||||
|
|
||||||
const messages = {
|
const messages = {
|
||||||
themeDoesNotExist: 'Theme does not exist.',
|
themeDoesNotExist: 'Theme does not exist.',
|
||||||
|
@ -47,8 +46,8 @@ module.exports = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
setFromZip: async (zip) => {
|
setFromZip: async (zip) => {
|
||||||
const shortName = getStorage().getSanitizedFileName(zip.name.split('.zip')[0]);
|
const themeName = getStorage().getSanitizedFileName(zip.name.split('.zip')[0]);
|
||||||
const backupName = `${shortName}_${ObjectID()}`;
|
const backupName = `${themeName}_${ObjectID()}`;
|
||||||
|
|
||||||
// check if zip name is casper.zip
|
// check if zip name is casper.zip
|
||||||
if (zip.name === 'casper.zip') {
|
if (zip.name === 'casper.zip') {
|
||||||
|
@ -62,38 +61,40 @@ module.exports = {
|
||||||
let renamedExisting = false;
|
let renamedExisting = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
checkedTheme = await validate.checkSafe(shortName, zip, true);
|
checkedTheme = await validate.checkSafe(themeName, zip, true);
|
||||||
const themeExists = await getStorage().exists(shortName);
|
const themeExists = await getStorage().exists(themeName);
|
||||||
// CASE: move the existing theme to a backup folder
|
// CASE: move the existing theme to a backup folder
|
||||||
if (themeExists) {
|
if (themeExists) {
|
||||||
|
debug('setFromZip Theme exists already');
|
||||||
renamedExisting = true;
|
renamedExisting = true;
|
||||||
await getStorage().rename(shortName, backupName);
|
await getStorage().rename(themeName, backupName);
|
||||||
}
|
}
|
||||||
|
|
||||||
// CASE: store extracted theme
|
// CASE: store extracted theme
|
||||||
await getStorage().save({
|
await getStorage().save({
|
||||||
name: shortName,
|
name: themeName,
|
||||||
path: checkedTheme.path
|
path: checkedTheme.path
|
||||||
});
|
});
|
||||||
// CASE: loads the theme from the fs & sets the theme on the themeList
|
// CASE: loads the theme from the fs & sets the theme on the themeList
|
||||||
const loadedTheme = await themeLoader.loadOneTheme(shortName);
|
const loadedTheme = await themeLoader.loadOneTheme(themeName);
|
||||||
overrideTheme = (shortName === settingsCache.get('active_theme'));
|
overrideTheme = (themeName === settingsCache.get('active_theme'));
|
||||||
// CASE: if this is the active theme, we are overriding
|
// CASE: if this is the active theme, we are overriding
|
||||||
if (overrideTheme) {
|
if (overrideTheme) {
|
||||||
activator.activateFromOverride(shortName, loadedTheme, checkedTheme);
|
debug('setFromZip Theme is active alreadu');
|
||||||
|
activator.activateFromOverride(themeName, loadedTheme, checkedTheme);
|
||||||
}
|
}
|
||||||
|
|
||||||
// @TODO: unify the name across gscan and Ghost!
|
// @TODO: unify the name across gscan and Ghost!
|
||||||
return {
|
return {
|
||||||
themeOverridden: overrideTheme,
|
themeOverridden: overrideTheme,
|
||||||
theme: toJSON(shortName, checkedTheme)
|
theme: toJSON(themeName, checkedTheme)
|
||||||
};
|
};
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// restore backup if we renamed an existing theme but saving failed
|
// restore backup if we renamed an existing theme but saving failed
|
||||||
if (renamedExisting) {
|
if (renamedExisting) {
|
||||||
return getStorage().exists(shortName).then((themeExists) => {
|
return getStorage().exists(themeName).then((themeExists) => {
|
||||||
if (!themeExists) {
|
if (!themeExists) {
|
||||||
return getStorage().rename(backupName, shortName).then(() => {
|
return getStorage().rename(backupName, themeName).then(() => {
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue