2014-02-05 03:40:30 -05:00
|
|
|
var _ = require('lodash'),
|
2013-12-06 03:51:35 -05:00
|
|
|
dataProvider = require('../models'),
|
|
|
|
when = require('when'),
|
|
|
|
config = require('../config'),
|
2014-05-06 19:49:25 -05:00
|
|
|
canThis = require('../permissions').canThis,
|
2014-05-09 05:11:29 -05:00
|
|
|
errors = require('../errors'),
|
2013-12-06 03:51:35 -05:00
|
|
|
settings,
|
|
|
|
settingsFilter,
|
|
|
|
updateSettingsCache,
|
|
|
|
readSettingsResult,
|
2014-02-23 07:32:35 -05:00
|
|
|
filterPaths,
|
2014-04-27 18:28:50 -05:00
|
|
|
settingsResult,
|
2013-12-06 03:51:35 -05:00
|
|
|
// Holds cached settings
|
|
|
|
settingsCache = {};
|
|
|
|
|
|
|
|
// ### Helpers
|
|
|
|
|
|
|
|
// Filters an object based on a given filter object
|
|
|
|
settingsFilter = function (settings, filter) {
|
|
|
|
return _.object(_.filter(_.pairs(settings), function (setting) {
|
|
|
|
if (filter) {
|
|
|
|
return _.some(filter.split(','), function (f) {
|
|
|
|
return setting[1].type === f;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}));
|
|
|
|
};
|
|
|
|
|
|
|
|
// Maintain the internal cache of the settings object
|
|
|
|
updateSettingsCache = function (settings) {
|
|
|
|
settings = settings || {};
|
|
|
|
|
|
|
|
if (!_.isEmpty(settings)) {
|
|
|
|
_.map(settings, function (setting, key) {
|
2014-04-27 18:28:50 -05:00
|
|
|
settingsCache[key] = setting;
|
2013-12-06 03:51:35 -05:00
|
|
|
});
|
2014-04-27 18:28:50 -05:00
|
|
|
|
|
|
|
return when(settingsCache);
|
2013-12-06 03:51:35 -05:00
|
|
|
}
|
|
|
|
|
2014-04-27 18:28:50 -05:00
|
|
|
return dataProvider.Settings.findAll()
|
|
|
|
.then(function (result) {
|
|
|
|
settingsCache = readSettingsResult(result.models);
|
|
|
|
|
|
|
|
return settingsCache;
|
2014-02-23 07:32:35 -05:00
|
|
|
});
|
2014-04-27 18:28:50 -05:00
|
|
|
};
|
|
|
|
|
|
|
|
readSettingsResult = function (settingsModels) {
|
|
|
|
var settings = _.reduce(settingsModels, function (memo, member) {
|
|
|
|
if (!memo.hasOwnProperty(member.attributes.key)) {
|
|
|
|
memo[member.attributes.key] = member.attributes;
|
|
|
|
}
|
|
|
|
|
|
|
|
return memo;
|
|
|
|
}, {}),
|
|
|
|
themes = config().paths.availableThemes,
|
|
|
|
apps = config().paths.availableApps,
|
|
|
|
res;
|
|
|
|
|
|
|
|
if (settings.activeTheme) {
|
|
|
|
res = filterPaths(themes, settings.activeTheme.value);
|
|
|
|
|
|
|
|
settings.availableThemes = {
|
|
|
|
key: 'availableThemes',
|
|
|
|
value: res,
|
|
|
|
type: 'theme'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
if (settings.activeApps) {
|
|
|
|
res = filterPaths(apps, JSON.parse(settings.activeApps.value));
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 10:18:38 -05:00
|
|
|
|
2014-04-27 18:28:50 -05:00
|
|
|
settings.availableApps = {
|
|
|
|
key: 'availableApps',
|
|
|
|
value: res,
|
|
|
|
type: 'app'
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return settings;
|
2014-02-23 07:32:35 -05:00
|
|
|
};
|
|
|
|
|
2014-04-21 13:04:20 -05:00
|
|
|
|
|
|
|
// Normalizes paths read by require-tree so that the apps and themes modules can use them.
|
|
|
|
// Creates an empty array (res), and populates it with useful info about the read packages
|
|
|
|
// like name, whether they're active (comparison with the second argument), and if they
|
|
|
|
// have a package.json, that, otherwise false
|
|
|
|
// @param {object} paths as returned by require-tree()
|
|
|
|
// @param {array/string} active as read from the settings object
|
|
|
|
// @return {array} of objects with useful info about apps / themes
|
|
|
|
|
2014-02-23 07:32:35 -05:00
|
|
|
filterPaths = function (paths, active) {
|
|
|
|
var pathKeys = Object.keys(paths),
|
|
|
|
res = [],
|
|
|
|
item;
|
|
|
|
|
|
|
|
// turn active into an array (so themes and apps can be checked the same)
|
|
|
|
if (!Array.isArray(active)) {
|
|
|
|
active = [active];
|
|
|
|
}
|
|
|
|
|
|
|
|
_.each(pathKeys, function (key) {
|
|
|
|
//do not include hidden files or _messages
|
2014-04-27 18:28:50 -05:00
|
|
|
if (key.indexOf('.') !== 0 &&
|
|
|
|
key !== '_messages' &&
|
|
|
|
key !== 'README.md'
|
2014-02-23 07:32:35 -05:00
|
|
|
) {
|
|
|
|
item = {
|
|
|
|
name: key
|
|
|
|
};
|
|
|
|
if (paths[key].hasOwnProperty('package.json')) {
|
|
|
|
item.package = paths[key]['package.json'];
|
|
|
|
} else {
|
|
|
|
item.package = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (_.indexOf(active, key) !== -1) {
|
|
|
|
item.active = true;
|
|
|
|
}
|
|
|
|
res.push(item);
|
|
|
|
}
|
2013-12-06 03:51:35 -05:00
|
|
|
});
|
2014-02-23 07:32:35 -05:00
|
|
|
return res;
|
2013-12-06 03:51:35 -05:00
|
|
|
};
|
|
|
|
|
2014-04-27 18:28:50 -05:00
|
|
|
settingsResult = function (settings, type) {
|
|
|
|
var filteredSettings = _.values(settingsFilter(settings, type)),
|
|
|
|
result = {
|
|
|
|
settings: filteredSettings
|
|
|
|
};
|
|
|
|
|
|
|
|
if (type) {
|
|
|
|
result.meta = {
|
|
|
|
filters: {
|
|
|
|
type: type
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
};
|
|
|
|
|
2013-12-06 03:51:35 -05:00
|
|
|
settings = {
|
|
|
|
// #### Browse
|
|
|
|
|
|
|
|
// **takes:** options object
|
|
|
|
browse: function browse(options) {
|
2014-05-06 19:49:25 -05:00
|
|
|
var self = this;
|
|
|
|
|
2013-12-06 03:51:35 -05:00
|
|
|
// **returns:** a promise for a settings json object
|
2014-05-06 19:49:25 -05:00
|
|
|
return canThis(this).browse.setting().then(function () {
|
|
|
|
var result = settingsResult(settingsCache, options.type);
|
|
|
|
|
|
|
|
// Omit core settings unless internal request
|
|
|
|
if (!self.internal) {
|
|
|
|
result.settings = _.filter(result.settings, function (setting) { return setting.type !== 'core'; });
|
|
|
|
}
|
|
|
|
|
|
|
|
return result;
|
|
|
|
});
|
2013-12-06 03:51:35 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
// #### Read
|
|
|
|
|
|
|
|
// **takes:** either a json object containing a key, or a single key string
|
|
|
|
read: function read(options) {
|
|
|
|
if (_.isString(options)) {
|
|
|
|
options = { key: options };
|
|
|
|
}
|
|
|
|
|
2014-05-06 19:49:25 -05:00
|
|
|
var self = this;
|
2014-04-27 18:28:50 -05:00
|
|
|
|
2014-05-06 19:49:25 -05:00
|
|
|
return canThis(this).read.setting(options.key).then(function () {
|
|
|
|
var setting = settingsCache[options.key],
|
|
|
|
result = {};
|
|
|
|
|
|
|
|
if (!setting) {
|
2014-05-09 05:11:29 -05:00
|
|
|
return when.reject(new errors.NotFoundError('Unable to find setting: ' + options.key));
|
2014-05-06 19:49:25 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!self.internal && setting.type === 'core') {
|
2014-05-09 05:11:29 -05:00
|
|
|
return when.reject(new errors.NoPermissionError('Attempted to access core setting on external request'));
|
2014-05-06 19:49:25 -05:00
|
|
|
}
|
Consistency in model method naming
- The API has the BREAD naming for methods
- The model now has findAll, findOne, findPage (where needed), edit, add and destroy, meaning it is similar but with a bit more flexibility
- browse, read, update, create, and delete, which were effectively just aliases, have all been removed.
- added jsDoc for the model methods
2014-05-05 10:18:38 -05:00
|
|
|
|
2014-05-06 19:49:25 -05:00
|
|
|
result[options.key] = setting;
|
2014-04-27 18:28:50 -05:00
|
|
|
|
2014-05-06 19:49:25 -05:00
|
|
|
return settingsResult(result);
|
|
|
|
});
|
2013-12-06 03:51:35 -05:00
|
|
|
},
|
|
|
|
|
|
|
|
// #### Edit
|
|
|
|
|
|
|
|
// **takes:** either a json object representing a collection of settings, or a key and value pair
|
|
|
|
edit: function edit(key, value) {
|
2014-04-03 08:03:09 -05:00
|
|
|
var self = this,
|
2014-05-06 19:49:25 -05:00
|
|
|
type,
|
|
|
|
canEditAllSettings = function (settingsInfo) {
|
|
|
|
var checks = _.map(settingsInfo, function (settingInfo) {
|
|
|
|
var setting = settingsCache[settingInfo.key];
|
|
|
|
|
|
|
|
if (!setting) {
|
2014-05-09 05:11:29 -05:00
|
|
|
return when.reject(new errors.NotFoundError('Unable to find setting: ' + settingInfo.key));
|
2014-05-06 19:49:25 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!self.internal && setting.type === 'core') {
|
2014-05-09 05:11:29 -05:00
|
|
|
return when.reject(new errors.NoPermissionError('Attempted to access core setting on external request'));
|
2014-05-06 19:49:25 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
return canThis(self).edit.setting(settingInfo.key);
|
|
|
|
});
|
|
|
|
|
|
|
|
return when.all(checks);
|
|
|
|
};
|
2014-04-03 08:03:09 -05:00
|
|
|
|
2014-05-12 23:20:57 -05:00
|
|
|
if (!_.isString(value)) {
|
|
|
|
value = JSON.stringify(value);
|
|
|
|
}
|
|
|
|
|
2014-05-06 13:52:11 -05:00
|
|
|
// Allow shorthand syntax
|
|
|
|
if (_.isString(key)) {
|
|
|
|
key = { settings: [{ key: key, value: value }]};
|
2013-12-06 03:51:35 -05:00
|
|
|
}
|
2014-04-27 18:28:50 -05:00
|
|
|
|
2014-05-06 13:52:11 -05:00
|
|
|
//clean data
|
|
|
|
type = _.find(key.settings, function (setting) { return setting.key === 'type'; });
|
|
|
|
if (_.isObject(type)) {
|
|
|
|
type = type.value;
|
|
|
|
}
|
|
|
|
|
|
|
|
key = _.reject(key.settings, function (setting) {
|
|
|
|
return setting.key === 'type' || setting.key === 'availableThemes' || setting.key === 'availableApps';
|
|
|
|
});
|
|
|
|
|
2014-05-06 19:49:25 -05:00
|
|
|
return canEditAllSettings(key).then(function () {
|
|
|
|
return dataProvider.Settings.edit(key, {user: self.user});
|
|
|
|
}).then(function (result) {
|
2014-05-06 13:52:11 -05:00
|
|
|
var readResult = readSettingsResult(result);
|
|
|
|
|
|
|
|
return updateSettingsCache(readResult).then(function () {
|
|
|
|
return config.theme.update(settings, config().url);
|
|
|
|
}).then(function () {
|
|
|
|
return settingsResult(readResult, type);
|
|
|
|
});
|
2014-05-06 19:49:25 -05:00
|
|
|
}).catch(function (error) {
|
|
|
|
// Pass along API error
|
|
|
|
return when.reject(error);
|
2013-12-06 03:51:35 -05:00
|
|
|
});
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
module.exports = settings;
|
2014-05-12 23:20:57 -05:00
|
|
|
module.exports.updateSettingsCache = updateSettingsCache;
|