mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-01 13:54:35 +03:00
f8b498d6e7
no issue 🎨 Switch themes API to use config.availableThemes - this gets rid of the only places where settings.availableThemes are used 🔥 Get rid of settings.availableThemes - this is no longer used anywhere - also get rid of every related call to updateSettingsCache 🔥 Replace config.availableThemes with theme cache - Creates a tailor-made in-memory cache for themes inside the theme module - Add methods for getting & setting items on the cache - Move all references to config.availableThemes to use the new cache - This can be abstracted later to support other kinds of caches? 🎨 Start improving theme lib's API Still TODO: simplifying/clarifying: - what is the structure of the internal list - what is the difference between a package list, and a theme list? - what is the difference between reading a theme and loading it? - how do we update the theme list (add/remove) - how do we refresh the theme list? (hot reload?!) - how do we get from an internal list, to one that is sent as part of the API? - how are we going to handle theme storage: read/write, such that the path is configurable 🎨 Use themeList consistently 🎨 Update list after storage
275 lines
8.0 KiB
JavaScript
275 lines
8.0 KiB
JavaScript
// # Settings API
|
|
// RESTful API for the Setting resource
|
|
var _ = require('lodash'),
|
|
dataProvider = require('../models'),
|
|
Promise = require('bluebird'),
|
|
canThis = require('../permissions').canThis,
|
|
errors = require('../errors'),
|
|
utils = require('./utils'),
|
|
i18n = require('../i18n'),
|
|
|
|
docName = 'settings',
|
|
settings,
|
|
|
|
settingsCache = require('../settings/cache'),
|
|
|
|
updateSettingsCache,
|
|
settingsFilter,
|
|
readSettingsResult,
|
|
settingsResult,
|
|
canEditAllSettings,
|
|
|
|
// @TODO simplify this!
|
|
updateSettingsCache = function updateSettingsCache(settings, options) {
|
|
options = options || {};
|
|
settings = settings || {};
|
|
|
|
if (!_.isEmpty(settings)) {
|
|
_.map(settings, function (setting, key) {
|
|
settingsCache.set(key, setting);
|
|
});
|
|
|
|
return Promise.resolve(settingsCache.getAll());
|
|
}
|
|
|
|
return dataProvider.Settings.findAll(options)
|
|
.then(function (result) {
|
|
// keep reference and update all keys
|
|
_.each(readSettingsResult(result.models), function (setting, key) {
|
|
settingsCache.set(key, setting);
|
|
});
|
|
|
|
return settingsCache.getAll();
|
|
});
|
|
};
|
|
|
|
// ## Helpers
|
|
|
|
/**
|
|
* ### Settings Filter
|
|
* Filters an object based on a given filter object
|
|
* @private
|
|
* @param {Object} settings
|
|
* @param {String} filter
|
|
* @returns {*}
|
|
*/
|
|
settingsFilter = function (settings, filter) {
|
|
return _.fromPairs(_.filter(_.toPairs(settings), function (setting) {
|
|
if (filter) {
|
|
return _.some(filter.split(','), function (f) {
|
|
return setting[1].type === f;
|
|
});
|
|
}
|
|
return true;
|
|
}));
|
|
};
|
|
|
|
/**
|
|
* ### Read Settings Result
|
|
*
|
|
* Converts the models to keyed JSON
|
|
* E.g.
|
|
* dbHash: {
|
|
* id: '123abc',
|
|
* key: 'dbash',
|
|
* value: 'xxxx',
|
|
* type: 'core',
|
|
* timestamps
|
|
* }
|
|
*
|
|
* @private
|
|
* @param {Array} settingsModels
|
|
* @returns {Settings}
|
|
*/
|
|
readSettingsResult = function (settingsModels) {
|
|
var settings = _.reduce(settingsModels, function (memo, member) {
|
|
if (!memo.hasOwnProperty(member.attributes.key)) {
|
|
memo[member.attributes.key] = member.attributes;
|
|
}
|
|
|
|
return memo;
|
|
}, {});
|
|
|
|
return settings;
|
|
};
|
|
|
|
/**
|
|
* ### Settings Result
|
|
* @private
|
|
* @param {Object} settings
|
|
* @param {String} type
|
|
* @returns {{settings: *}}
|
|
*/
|
|
settingsResult = function (settings, type) {
|
|
var filteredSettings = _.values(settingsFilter(settings, type)),
|
|
result = {
|
|
settings: filteredSettings,
|
|
meta: {}
|
|
};
|
|
|
|
if (type) {
|
|
result.meta.filters = {
|
|
type: type
|
|
};
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
/**
|
|
* ### Can Edit All Settings
|
|
* Check that this edit request is allowed for all settings requested to be updated
|
|
* @private
|
|
* @param {Object} settingsInfo
|
|
* @returns {*}
|
|
*/
|
|
canEditAllSettings = function (settingsInfo, options) {
|
|
var checkSettingPermissions = function checkSettingPermissions(setting) {
|
|
if (setting.type === 'core' && !(options.context && options.context.internal)) {
|
|
return Promise.reject(
|
|
new errors.NoPermissionError({message: i18n.t('errors.api.settings.accessCoreSettingFromExtReq')})
|
|
);
|
|
}
|
|
|
|
return canThis(options.context).edit.setting(setting.key).catch(function () {
|
|
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.api.settings.noPermissionToEditSettings')}));
|
|
});
|
|
},
|
|
checks = _.map(settingsInfo, function (settingInfo) {
|
|
var setting = settingsCache.get(settingInfo.key, {resolve: false});
|
|
|
|
if (!setting) {
|
|
return Promise.reject(new errors.NotFoundError(
|
|
{message: i18n.t('errors.api.settings.problemFindingSetting', {key: settingInfo.key})}
|
|
));
|
|
}
|
|
|
|
return checkSettingPermissions(setting);
|
|
});
|
|
|
|
return Promise.all(checks);
|
|
};
|
|
|
|
/**
|
|
* ## Settings API Methods
|
|
*
|
|
* **See:** [API Methods](index.js.html#api%20methods)
|
|
*/
|
|
settings = {
|
|
|
|
/**
|
|
* ### Browse
|
|
* @param {Object} options
|
|
* @returns {*}
|
|
*/
|
|
browse: function browse(options) {
|
|
options = options || {};
|
|
|
|
var result = settingsResult(settingsCache.getAll(), options.type);
|
|
|
|
// If there is no context, return only blog settings
|
|
if (!options.context) {
|
|
return Promise.resolve(_.filter(result.settings, function (setting) { return setting.type === 'blog'; }));
|
|
}
|
|
|
|
// Otherwise return whatever this context is allowed to browse
|
|
return canThis(options.context).browse.setting().then(function () {
|
|
// Omit core settings unless internal request
|
|
if (!options.context.internal) {
|
|
result.settings = _.filter(result.settings, function (setting) { return setting.type !== 'core'; });
|
|
}
|
|
|
|
return result;
|
|
});
|
|
},
|
|
|
|
/**
|
|
* ### Read
|
|
* @param {Object} options
|
|
* @returns {*}
|
|
*/
|
|
read: function read(options) {
|
|
if (_.isString(options)) {
|
|
options = {key: options};
|
|
}
|
|
|
|
var setting = settingsCache.get(options.key, {resolve: false}),
|
|
result = {};
|
|
|
|
if (!setting) {
|
|
return Promise.reject(new errors.NotFoundError(
|
|
{message: i18n.t('errors.api.settings.problemFindingSetting', {key: options.key})}
|
|
));
|
|
}
|
|
|
|
result[options.key] = setting;
|
|
|
|
if (setting.type === 'core' && !(options.context && options.context.internal)) {
|
|
return Promise.reject(
|
|
new errors.NoPermissionError({message: i18n.t('errors.api.settings.accessCoreSettingFromExtReq')})
|
|
);
|
|
}
|
|
|
|
if (setting.type === 'blog') {
|
|
return Promise.resolve(settingsResult(result));
|
|
}
|
|
|
|
return canThis(options.context).read.setting(options.key).then(function () {
|
|
return settingsResult(result);
|
|
}, function () {
|
|
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.api.settings.noPermissionToReadSettings')}));
|
|
});
|
|
},
|
|
|
|
/**
|
|
* ### Edit
|
|
* Update properties of a setting
|
|
* @param {{settings: }} object Setting or a single string name
|
|
* @param {{id (required), include,...}} options (optional) or a single string value
|
|
* @return {Promise(Setting)} Edited Setting
|
|
*/
|
|
edit: function edit(object, options) {
|
|
options = options || {};
|
|
var self = this,
|
|
type;
|
|
|
|
// Allow shorthand syntax where a single key and value are passed to edit instead of object and options
|
|
if (_.isString(object)) {
|
|
object = {settings: [{key: object, value: options}]};
|
|
}
|
|
|
|
// clean data
|
|
_.each(object.settings, function (setting) {
|
|
if (!_.isString(setting.value)) {
|
|
setting.value = JSON.stringify(setting.value);
|
|
}
|
|
});
|
|
|
|
type = _.find(object.settings, function (setting) { return setting.key === 'type'; });
|
|
if (_.isObject(type)) {
|
|
type = type.value;
|
|
}
|
|
|
|
object.settings = _.reject(object.settings, function (setting) {
|
|
return setting.key === 'type';
|
|
});
|
|
|
|
return canEditAllSettings(object.settings, options).then(function () {
|
|
return utils.checkObject(object, docName).then(function (checkedData) {
|
|
options.user = self.user;
|
|
return dataProvider.Settings.edit(checkedData.settings, options);
|
|
}).then(function (result) {
|
|
var readResult = readSettingsResult(result);
|
|
|
|
return updateSettingsCache(readResult).then(function () {
|
|
return settingsResult(readResult, type);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
};
|
|
|
|
module.exports = settings;
|
|
|
|
module.exports.updateSettingsCache = updateSettingsCache;
|