From bd8db968ea2a9d991b6b0051659d8a985b30ad34 Mon Sep 17 00:00:00 2001 From: Sebastian Gierlinger Date: Sun, 15 Sep 2013 18:03:31 +0200 Subject: [PATCH] Add setting filter closes #172 - added type to ghost.settings() - added /api/settings?type= - added availableThemes to settingsCache - removed cachedSettingsRequestHandler - removed /api/themes (including front end) - changed activePlugins to type "plugin" in default-settings.json --- core/client/models/settings.js | 2 +- core/client/models/themes.js | 9 -- core/client/views/settings.js | 23 +--- core/ghost.js | 95 ++++++++++------ core/server.js | 8 +- core/server/api.js | 145 ++++++++++--------------- core/server/controllers/frontend.js | 8 +- core/server/data/default-settings.json | 6 +- core/server/helpers/index.js | 2 +- core/server/mail.js | 2 +- core/server/views/default.hbs | 1 - core/shared/lang/i18n.js | 2 +- 12 files changed, 136 insertions(+), 167 deletions(-) delete mode 100644 core/client/models/themes.js diff --git a/core/client/models/settings.js b/core/client/models/settings.js index bf4a98978e..e2447b4da1 100644 --- a/core/client/models/settings.js +++ b/core/client/models/settings.js @@ -3,7 +3,7 @@ "use strict"; //id:0 is used to issue PUT requests Ghost.Models.Settings = Backbone.Model.extend({ - url: Ghost.settings.apiRoot + '/settings', + url: Ghost.settings.apiRoot + '/settings?type=blog,theme', id: "0" }); diff --git a/core/client/models/themes.js b/core/client/models/themes.js deleted file mode 100644 index 47549b758d..0000000000 --- a/core/client/models/themes.js +++ /dev/null @@ -1,9 +0,0 @@ -/*global window, document, Ghost, $, _, Backbone */ -(function () { - "use strict"; - - Ghost.Models.Themes = Backbone.Model.extend({ - url: Ghost.settings.apiRoot + '/themes' - }); - -}()); \ No newline at end of file diff --git a/core/client/views/settings.js b/core/client/views/settings.js index cbe0269cf4..c1ecf862f2 100644 --- a/core/client/views/settings.js +++ b/core/client/views/settings.js @@ -57,8 +57,7 @@ showContent: function (id) { var self = this, - model, - themes; + model; Ghost.router.navigate('/settings/' + id); Ghost.trigger('urlchange'); @@ -70,13 +69,9 @@ this.pane = new Settings[id]({ el: '.settings-content'}); if (!this.models.hasOwnProperty(this.pane.options.modelType)) { - themes = this.models.Themes = new Ghost.Models.Themes(); model = this.models[this.pane.options.modelType] = new Ghost.Models[this.pane.options.modelType](); - themes.fetch().then(function () { - model.fetch().then(function () { - model.set({availableThemes: themes.toJSON()}); - self.renderPane(model); - }); + model.fetch().then(function () { + self.renderPane(model); }); } else { model = this.models[this.pane.options.modelType]; @@ -157,8 +152,7 @@ }, saveSettings: function () { - var themes = this.model.get('availableThemes'), - title = this.$('#blog-title').val(), + var title = this.$('#blog-title').val(), description = this.$('#blog-description').val(), email = this.$('#email-address').val(), postsPerPage = this.$('#postsPerPage').val(); @@ -180,8 +174,6 @@ if (Ghost.Validate._errors.length > 0) { Ghost.Validate.handleErrors(); } else { - - this.model.unset('availableThemes'); this.model.save({ title: title, description: description, @@ -194,7 +186,6 @@ success: this.saveSuccess, error: this.saveError }); - this.model.set({availableThemes: themes}); } }, showLogo: function () { @@ -208,16 +199,12 @@ showUpload: function (id, key, src) { var self = this, upload = new Ghost.Models.uploadModal({'id': id, 'key': key, 'src': src, 'accept': { func: function () { // The function called on acceptance - var data = {}, - themes; + var data = {}; data[key] = this.$('.js-upload-target').attr('src'); - themes = self.model.get('availableThemes'); - self.model.unset('availableThemes'); self.model.save(data, { success: self.saveSuccess, error: self.saveError }); - self.model.set({availableThemes: themes}); self.render(); return true; }, diff --git a/core/ghost.js b/core/ghost.js index d84c1fdf9f..d822f46c63 100644 --- a/core/ghost.js +++ b/core/ghost.js @@ -100,10 +100,10 @@ Ghost = function () { * this data is what becomes globally available to themes */ return { url: instance.config().url, - title: instance.settings().title, - description: instance.settings().description, - logo: instance.settings().logo, - cover: instance.settings().cover + title: instance.settings().title.value, + description: instance.settings().description.value, + logo: instance.settings().logo.value, + cover: instance.settings().cover.value }; }, statuses: function () { return statuses; }, @@ -121,7 +121,7 @@ Ghost = function () { 'appRoot': appRoot, 'themePath': themePath, 'pluginPath': pluginPath, - 'activeTheme': path.join(themePath, !instance.settingsCache ? "" : instance.settingsCache.activeTheme), + 'activeTheme': path.join(themePath, !instance.settingsCache ? "" : instance.settingsCache.activeTheme.value), 'adminViews': path.join(appRoot, '/core/server/views/'), 'helperTemplates': path.join(appRoot, '/core/server/helpers/tpl/'), 'lang': path.join(appRoot, '/core/shared/lang/'), @@ -142,15 +142,14 @@ Ghost.prototype.init = function () { instance.dataProvider.init(), instance.getPaths(), instance.mail.init(self) + ).then(function () { return models.Settings.populateDefaults(); - }).then(function () { - // Initialize plugins - return self.initPlugins(); }).then(function () { // Initialize the settings cache return self.updateSettingsCache(); }).then(function () { + // Initialize plugins return self.initPlugins(); }).then(function () { // Initialize the permissions actions and objects @@ -164,7 +163,7 @@ Ghost.prototype.init = function () { }).otherwise(function (error) { // this is where all the "first run" functionality should go var dbhash = uuid.v4(); - return when(models.Settings.add({key: 'dbHash', value: dbhash})).then(function (returned) { + return when(models.Settings.add({key: 'dbHash', value: dbhash, type: 'core'})).then(function (returned) { self.dbHash = dbhash; return dbhash; }); @@ -179,33 +178,62 @@ Ghost.prototype.updateSettingsCache = function (settings) { settings = settings || {}; if (!_.isEmpty(settings)) { - self.settingsCache = settings; + _.map(settings, function (setting, key) { + self.settingsCache[key].value = setting.value; + }); } else { // TODO: this should use api.browse - return models.Settings.findAll().then(function (result) { - var settings = {}; - _.map(result.models, function (member) { - if (!settings.hasOwnProperty(member.attributes.key)) { - if (member.attributes.key === 'activeTheme') { - member.attributes.value = member.attributes.value.substring(member.attributes.value.lastIndexOf('/') + 1); - var settingsThemePath = path.join(themePath, member.attributes.value); - fs.exists(settingsThemePath, function (exists) { - if (!exists) { - member.attributes.value = "casper"; - } - settings[member.attributes.key] = member.attributes.value; - }); - return; - } - settings[member.attributes.key] = member.attributes.value; - } + return when(models.Settings.findAll()).then(function (result) { + return when(self.readSettingsResult(result)).then(function (s) { + self.settingsCache = s; }); - - self.settingsCache = settings; - }, errors.logAndThrowError); + }); } }; +Ghost.prototype.readSettingsResult = function (result) { + var settings = {}; + return when(_.map(result.models, function (member) { + if (!settings.hasOwnProperty(member.attributes.key)) { + var val = {}; + if (member.attributes.key === 'activeTheme') { + member.attributes.value = member.attributes.value.substring(member.attributes.value.lastIndexOf('/') + 1); + val.value = member.attributes.value; + val.type = member.attributes.type; + settings[member.attributes.key] = val; + } else { + val.value = member.attributes.value; + val.type = member.attributes.type; + settings[member.attributes.key] = val; + } + } + })).then(function () { + return when(instance.paths().availableThemes).then(function (themes) { + var themeKeys = Object.keys(themes), + res = [], + i, + item; + for (i = 0; i < themeKeys.length; i += 1) { + //do not include hidden files + if (themeKeys[i].indexOf('.') !== 0) { + item = {}; + item.name = themeKeys[i]; + //data about files currently not used + //item.details = themes[themeKeys[i]]; + if (themeKeys[i] === settings.activeTheme.value) { + item.active = true; + } + res.push(item); + } + } + settings.availableThemes = {}; + settings.availableThemes.value = res; + settings.availableThemes.type = 'theme'; + return settings; + }); + }); +}; + // ## Template utils // Compile a template for a handlebars helper @@ -303,7 +331,6 @@ Ghost.prototype.doFilter = function (name, args, callback) { // Initialise plugins. Will load from config.activePlugins by default Ghost.prototype.initPlugins = function (pluginsToLoad) { pluginsToLoad = pluginsToLoad || models.Settings.activePlugins; - var self = this; return plugins.init(this, pluginsToLoad).then(function (loadedPlugins) { @@ -326,11 +353,11 @@ Ghost.prototype.initTheme = function (app) { // self.globals is a hack til we have a better way of getting combined settings & config hbsOptions = {templateOptions: {data: {blog: self.blogGlobals()}}}; - if (!self.themeDirectories.hasOwnProperty(self.settings().activeTheme)) { + if (!self.themeDirectories.hasOwnProperty(self.settings().activeTheme.value)) { // Throw an error if the theme is not available... // TODO: move this to happen on app start - errors.logAndThrowError('The currently active theme ' + self.settings().activeTheme + ' is missing.'); - } else if (self.themeDirectories[self.settings().activeTheme].hasOwnProperty('partials')) { + errors.logAndThrowError('The currently active theme ' + self.settings().activeTheme.value + ' is missing.'); + } else if (self.themeDirectories[self.settings().activeTheme.value].hasOwnProperty('partials')) { // Check that the theme has a partials directory before trying to use it hbsOptions.partialsDir = path.join(self.paths().activeTheme, 'partials'); } diff --git a/core/server.js b/core/server.js index eeb83add7f..4d4549a452 100644 --- a/core/server.js +++ b/core/server.js @@ -192,11 +192,9 @@ when.all([ghost.init(), helpers.loadCoreHelpers(ghost)]).then(function () { server.put('/api/v0.1/posts/:id', authAPI, disableCachedResult, api.requestHandler(api.posts.edit)); server.del('/api/v0.1/posts/:id', authAPI, disableCachedResult, api.requestHandler(api.posts.destroy)); // #### Settings - server.get('/api/v0.1/settings', authAPI, disableCachedResult, api.cachedSettingsRequestHandler(api.settings.browse)); - server.get('/api/v0.1/settings/:key', authAPI, disableCachedResult, api.cachedSettingsRequestHandler(api.settings.read)); - server.put('/api/v0.1/settings', authAPI, disableCachedResult, api.cachedSettingsRequestHandler(api.settings.edit)); - // #### Themes - server.get('/api/v0.1/themes', authAPI, disableCachedResult, api.requestHandler(api.themes.browse)); + server.get('/api/v0.1/settings', authAPI, disableCachedResult, api.requestHandler(api.settings.browse)); + server.get('/api/v0.1/settings/:key', authAPI, disableCachedResult, api.requestHandler(api.settings.read)); + server.put('/api/v0.1/settings', authAPI, disableCachedResult, api.requestHandler(api.settings.edit)); // #### Users server.get('/api/v0.1/users', authAPI, disableCachedResult, api.requestHandler(api.users.browse)); server.get('/api/v0.1/users/:id', authAPI, disableCachedResult, api.requestHandler(api.users.read)); diff --git a/core/server/api.js b/core/server/api.js index 248a8b7194..5611a27681 100644 --- a/core/server/api.js +++ b/core/server/api.js @@ -17,9 +17,9 @@ var Ghost = require('../ghost'), settings, themes, requestHandler, - cachedSettingsRequestHandler, settingsObject, - settingsCollection; + settingsCollection, + settingsFilter; // ## Posts posts = { @@ -195,6 +195,16 @@ notifications = { // ### Helpers // Turn a settings collection into a single object/hashmap settingsObject = function (settings) { + if (_.isObject(settings)) { + return _.reduce(settings, function (res, item, key) { + if (_.isArray(item)) { + res[key] = item; + } else { + res[key] = item.value; + } + return res; + }, {}); + } return (settings.toJSON ? settings.toJSON() : settings).reduce(function (res, item) { if (item.toJSON) { item = item.toJSON(); } if (item.key) { res[item.key] = item.value; } @@ -208,13 +218,28 @@ settingsCollection = function (settings) { }); }; +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; + })); +}; + settings = { // #### Browse // **takes:** options object browse: function browse(options) { - // **returns:** a promise for a settings json object - return dataProvider.Settings.browse(options).then(settingsObject, errors.logAndThrowError); + // **returns:** a promise for a settings json object + if (ghost.settings()) { + return when(ghost.settings()).then(function (settings) { + return settingsObject(settingsFilter(settings, options.type)); + }, errors.logAndThrowError); + } }, // #### Read @@ -225,14 +250,17 @@ settings = { options = { key: options }; } - // **returns:** a promise for a single key-value pair - return dataProvider.Settings.read(options.key).then(function (setting) { - if (!setting) { - return when.reject("Unable to find setting: " + options.key); - } - - return _.pick(setting.toJSON(), 'key', 'value'); - }, errors.logAndThrowError); + if (ghost.settings()) { + return when(ghost.settings()[options.key]).then(function (setting) { + if (!setting) { + return when.reject("Unable to find setting: " + options.key); + } + var res = {}; + res.key = options.key; + res.value = setting.value; + return res; + }, errors.logAndThrowError); + } }, // #### Edit @@ -241,61 +269,36 @@ settings = { edit: function edit(key, value) { // Check for passing a collection of settings first if (_.isObject(key)) { + //clean data + var type = key.type; + delete key.type; + delete key.availableThemes; + key = settingsCollection(key); - - return dataProvider.Settings.edit(key).then(settingsObject, errors.logAndThrowError); + return dataProvider.Settings.edit(key).then(function (result) { + result.models = result; + return when(ghost.readSettingsResult(result)).then(function (settings) { + ghost.updateSettingsCache(settings); + return settingsObject(settingsFilter(ghost.settings(), type)); + }); + }, errors.logAndThrowError); } - - // **returns:** a promise for a settings json object return dataProvider.Settings.read(key).then(function (setting) { if (!setting) { return when.reject("Unable to find setting: " + key); } - if (!_.isString(value)) { value = JSON.stringify(value); } - setting.set('value', value); - - return dataProvider.Settings.edit(setting); - }, errors.logAndThrowError); - } -}; - -// ## Themes - -themes = { - // #### Browse - - // **takes:** options object - browse: function browse() { - // **returns:** a promise for a themes json object - return when(ghost.paths().availableThemes).then(function (themes) { - var themeKeys = Object.keys(themes), - res = [], - i, - activeTheme = ghost.paths().activeTheme.substring(ghost.paths().activeTheme.lastIndexOf('/') + 1), - item; - - for (i = 0; i < themeKeys.length; i += 1) { - //do not include hidden files - if (themeKeys[i].indexOf('.') !== 0) { - item = {}; - item.name = themeKeys[i]; - item.details = themes[themeKeys[i]]; - if (themeKeys[i] === activeTheme) { - item.active = true; - } - res.push(item); - } - } - return res; + return dataProvider.Settings.edit(setting).then(function (result) { + ghost.settings()[_.first(result).attributes.key].value = _.first(result).attributes.value; + return settingsObject(ghost.settings()); + }, errors.logAndThrowError); }); } }; - // ## Request Handlers // ### requestHandler @@ -317,41 +320,6 @@ requestHandler = function (apiMethod) { }; }; -// ### cachedSettingsRequestHandler -// Special request handler for settings to access the internal cache version of the settings object -cachedSettingsRequestHandler = function (apiMethod) { - if (!ghost.settings()) { - return requestHandler(apiMethod); - } - - return function (req, res) { - var options = _.extend(req.body, req.query, req.params), - promise; - - switch (apiMethod.name) { - case 'browse': - promise = when(ghost.settings()); - break; - case 'read': - promise = when(ghost.settings()[options.key]); - break; - case 'edit': - promise = apiMethod(options).then(function (result) { - ghost.updateSettingsCache(result); - return result; - }); - break; - default: - errors.logAndThrowError(new Error('Unknown method name for settings API: ' + apiMethod.name)); - } - return promise.then(function (result) { - res.json(result || {}); - }, function (error) { - res.json(400, {error: error}); - }); - }; -}; - // Public API module.exports.posts = posts; module.exports.users = users; @@ -360,4 +328,3 @@ module.exports.notifications = notifications; module.exports.settings = settings; module.exports.themes = themes; module.exports.requestHandler = requestHandler; -module.exports.cachedSettingsRequestHandler = cachedSettingsRequestHandler; diff --git a/core/server/controllers/frontend.js b/core/server/controllers/frontend.js index 0b8f725f14..90c9315791 100644 --- a/core/server/controllers/frontend.js +++ b/core/server/controllers/frontend.js @@ -15,7 +15,7 @@ frontendControllers = { 'homepage': function (req, res) { // Parse the page number var pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1, - postsPerPage = parseInt(ghost.settings().postsPerPage, 10), + postsPerPage = parseInt(ghost.settings().postsPerPage.value, 10), options = {}; // No negative pages @@ -67,10 +67,10 @@ frontendControllers = { // Initialize RSS var siteUrl = ghost.config().url, feed = new RSS({ - title: ghost.settings().title, - description: ghost.settings().description, + title: ghost.settings().title.value, + description: ghost.settings().description.value, generator: 'Ghost v' + res.locals.version, - author: ghost.settings().author, + author: ghost.settings().author.value, feed_url: siteUrl + '/rss/', site_url: siteUrl, ttl: '60' diff --git a/core/server/data/default-settings.json b/core/server/data/default-settings.json index f37b7d83db..d73607cc52 100644 --- a/core/server/data/default-settings.json +++ b/core/server/data/default-settings.json @@ -46,14 +46,14 @@ } }, "theme": { - "activePlugins": { - "defaultValue": "" - }, "activeTheme": { "defaultValue": "casper" } }, "plugin": { + "activePlugins": { + "defaultValue": "[]" + }, "installedPlugins": { "defaultValue": "[]" } diff --git a/core/server/helpers/index.js b/core/server/helpers/index.js index 26d3c9c6c8..ff09154f88 100644 --- a/core/server/helpers/index.js +++ b/core/server/helpers/index.js @@ -234,7 +234,7 @@ coreHelpers = function (ghost) { ghost.registerThemeHelper('e', function (key, defaultString, options) { var output; - if (ghost.settings().defaultLang === 'en' && _.isEmpty(options.hash) && !ghost.settings().forceI18n) { + if (ghost.settings().defaultLang.value === 'en' && _.isEmpty(options.hash) && !ghost.settings().forceI18n.value) { output = defaultString; } else { output = ghost.polyglot().t(key, options.hash); diff --git a/core/server/mail.js b/core/server/mail.js index def1af2bf5..5468918c98 100644 --- a/core/server/mail.js +++ b/core/server/mail.js @@ -96,7 +96,7 @@ GhostMailer.prototype.send = function (message) { } var from = 'ghost-mailer@' + url.parse(this.ghost.config().url).hostname, - to = message.to || this.ghost.settings().email, + to = message.to || this.ghost.settings().email.value, sendMail = nodefn.lift(this.transport.sendMail.bind(this.transport)); message = _.extend(message, { diff --git a/core/server/views/default.hbs b/core/server/views/default.hbs index f0e94a3530..446bb643fd 100644 --- a/core/server/views/default.hbs +++ b/core/server/views/default.hbs @@ -80,7 +80,6 @@ - diff --git a/core/shared/lang/i18n.js b/core/shared/lang/i18n.js index 596e67e56f..dc02e31f28 100644 --- a/core/shared/lang/i18n.js +++ b/core/shared/lang/i18n.js @@ -8,7 +8,7 @@ var fs = require('fs'), I18n = function (ghost) { // TODO: validate - var lang = ghost.settings().defaultLang, + var lang = ghost.settings().defaultLang.value, path = ghost.paths().lang, langFilePath = path + lang + '.json';