mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-06 22:40:14 -05:00
Restructure Configuration API endpoint
refs #6421, #6525 - The configuration API endpoint was a bit of an animal: - It's used currently in two ways, once for general config, another for the about page. - These two things are different, and would require different permissions in future. - There was also both a browse and a read version, even though only browse was used. - The response from the browse was being artificially turned into many objects, when its really just one with multiple keys - The new version treats each type of config as a different single object with several keys - The new version therefore only has a 'read' request - A basic read request with no key will return basic config that any client would need - A read request with the about key returns the about config - A read request with a different key could therefore return some other config
This commit is contained in:
parent
72cbda3646
commit
ed16998461
7 changed files with 82 additions and 103 deletions
|
@ -31,13 +31,13 @@
|
||||||
<meta name="msapplication-square150x150logo" content="{{asset "img/medium.png" ghost="true"}}" />
|
<meta name="msapplication-square150x150logo" content="{{asset "img/medium.png" ghost="true"}}" />
|
||||||
<meta name="msapplication-square310x310logo" content="{{asset "img/large.png" ghost="true"}}" />
|
<meta name="msapplication-square310x310logo" content="{{asset "img/large.png" ghost="true"}}" />
|
||||||
|
|
||||||
{{#each configuration}}
|
{{#each configuration as |config key|}}
|
||||||
<meta name="env-{{this.key}}" content="{{this.value}}" data-type="{{this.type}}" />
|
<meta name="env-{{key}}" content="{{config.value}}" data-type="{{config.type}}" />
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
{{#unless skip_google_fonts}}
|
{{#if configuration.useGoogleFonts.value}}
|
||||||
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans:400,300,700" />
|
<link rel="stylesheet" type="text/css" href="//fonts.googleapis.com/css?family=Open+Sans:400,300,700" />
|
||||||
{{/unless}}
|
{{/if}}
|
||||||
|
|
||||||
<link rel="stylesheet" href="{{asset "vendor.css" ghost="true" minifyInProduction="true"}}" />
|
<link rel="stylesheet" href="{{asset "vendor.css" ghost="true" minifyInProduction="true"}}" />
|
||||||
<link rel="stylesheet" href="{{asset "ghost.css" ghost="true" minifyInProduction="true"}}" />
|
<link rel="stylesheet" href="{{asset "ghost.css" ghost="true" minifyInProduction="true"}}" />
|
||||||
|
|
|
@ -18,7 +18,7 @@ export default AuthenticatedRoute.extend(styleBody, {
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
let cachedConfig = this.get('cachedConfig');
|
let cachedConfig = this.get('cachedConfig');
|
||||||
let configUrl = this.get('ghostPaths.url').api('configuration');
|
let configUrl = this.get('ghostPaths.url').api('configuration', 'about');
|
||||||
|
|
||||||
if (cachedConfig) {
|
if (cachedConfig) {
|
||||||
return cachedConfig;
|
return cachedConfig;
|
||||||
|
@ -26,12 +26,8 @@ export default AuthenticatedRoute.extend(styleBody, {
|
||||||
|
|
||||||
return this.get('ajax').request(configUrl)
|
return this.get('ajax').request(configUrl)
|
||||||
.then((configurationResponse) => {
|
.then((configurationResponse) => {
|
||||||
let configKeyValues = configurationResponse.configuration;
|
let [cachedConfig] = configurationResponse.configuration;
|
||||||
|
|
||||||
cachedConfig = {};
|
|
||||||
configKeyValues.forEach((configKeyValue) => {
|
|
||||||
cachedConfig[configKeyValue.key] = configKeyValue.value;
|
|
||||||
});
|
|
||||||
this.set('cachedConfig', cachedConfig);
|
this.set('cachedConfig', cachedConfig);
|
||||||
|
|
||||||
return cachedConfig;
|
return cachedConfig;
|
||||||
|
|
|
@ -2,9 +2,7 @@
|
||||||
// RESTful API for browsing the configuration
|
// RESTful API for browsing the configuration
|
||||||
var _ = require('lodash'),
|
var _ = require('lodash'),
|
||||||
config = require('../config'),
|
config = require('../config'),
|
||||||
errors = require('../errors'),
|
|
||||||
Promise = require('bluebird'),
|
Promise = require('bluebird'),
|
||||||
i18n = require('../i18n'),
|
|
||||||
|
|
||||||
configuration;
|
configuration;
|
||||||
|
|
||||||
|
@ -15,28 +13,24 @@ function labsFlag(key) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getValidKeys() {
|
function getAboutConfig() {
|
||||||
var validKeys = {
|
return {
|
||||||
fileStorage: {value: (config.fileStorage !== false), type: 'bool'},
|
version: config.ghostVersion,
|
||||||
publicAPI: labsFlag('publicAPI'),
|
environment: process.env.NODE_ENV,
|
||||||
apps: {value: (config.apps === true), type: 'bool'},
|
database: config.database.client,
|
||||||
version: {value: config.ghostVersion, type: 'string'},
|
mail: _.isObject(config.mail) ? config.mail.transport : ''
|
||||||
environment: process.env.NODE_ENV,
|
};
|
||||||
database: config.database.client,
|
|
||||||
mail: _.isObject(config.mail) ? config.mail.transport : '',
|
|
||||||
blogUrl: {value: config.url.replace(/\/$/, ''), type: 'string'},
|
|
||||||
blogTitle: {value: config.theme.title, type: 'string'},
|
|
||||||
routeKeywords: {value: JSON.stringify(config.routeKeywords), type: 'json'}
|
|
||||||
};
|
|
||||||
|
|
||||||
return validKeys;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatConfigurationObject(val, key) {
|
function getBaseConfig() {
|
||||||
return {
|
return {
|
||||||
key: key,
|
fileStorage: {value: (config.fileStorage !== false), type: 'bool'},
|
||||||
value: (_.isObject(val) && _.has(val, 'value')) ? val.value : val,
|
useGoogleFonts: {value: !config.isPrivacyDisabled('useGoogleFonts'), type: 'bool'},
|
||||||
type: _.isObject(val) ? (val.type || null) : null
|
useGravatar: {value: !config.isPrivacyDisabled('useGravatar'), type: 'bool'},
|
||||||
|
publicAPI: labsFlag('publicAPI'),
|
||||||
|
blogUrl: {value: config.url.replace(/\/$/, ''), type: 'string'},
|
||||||
|
blogTitle: {value: config.theme.title, type: 'string'},
|
||||||
|
routeKeywords: {value: JSON.stringify(config.routeKeywords), type: 'json'}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -48,26 +42,23 @@ function formatConfigurationObject(val, key) {
|
||||||
configuration = {
|
configuration = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ### Browse
|
* Always returns {configuration: []}
|
||||||
* Fetch all configuration keys
|
* Sometimes the array contains configuration items
|
||||||
* @returns {Promise(Configurations)}
|
* @param {Object} options
|
||||||
*/
|
* @returns {Promise<Object>}
|
||||||
browse: function browse() {
|
|
||||||
return Promise.resolve({configuration: _.map(getValidKeys(), formatConfigurationObject)});
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ### Read
|
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
read: function read(options) {
|
read: function read(options) {
|
||||||
var data = getValidKeys();
|
options = options || {};
|
||||||
|
|
||||||
if (_.has(data, options.key)) {
|
if (!options.key) {
|
||||||
return Promise.resolve({configuration: [formatConfigurationObject(data[options.key], options.key)]});
|
return Promise.resolve({configuration: [getBaseConfig()]});
|
||||||
} else {
|
|
||||||
return Promise.reject(new errors.NotFoundError(i18n.t('errors.api.configuration.invalidKey')));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (options.key === 'about') {
|
||||||
|
return Promise.resolve({configuration: [getAboutConfig()]});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve({configuration: []});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
var _ = require('lodash'),
|
var _ = require('lodash'),
|
||||||
|
Promise = require('bluebird'),
|
||||||
api = require('../api'),
|
api = require('../api'),
|
||||||
errors = require('../errors'),
|
errors = require('../errors'),
|
||||||
updateCheck = require('../update-check'),
|
updateCheck = require('../update-check'),
|
||||||
config = require('../config'),
|
|
||||||
i18n = require('../i18n'),
|
i18n = require('../i18n'),
|
||||||
adminControllers;
|
adminControllers;
|
||||||
|
|
||||||
|
@ -14,22 +14,20 @@ adminControllers = {
|
||||||
/*jslint unparam:true*/
|
/*jslint unparam:true*/
|
||||||
|
|
||||||
function renderIndex() {
|
function renderIndex() {
|
||||||
var configuration;
|
var configuration,
|
||||||
return api.configuration.browse().then(function then(data) {
|
fetch = {
|
||||||
configuration = data.configuration;
|
configuration: api.configuration.read().then(function (res) { return res.configuration[0]; }),
|
||||||
}).then(function getAPIClient() {
|
client: api.clients.read({slug: 'ghost-admin'}).then(function (res) { return res.clients[0]; })
|
||||||
return api.clients.read({slug: 'ghost-admin'});
|
};
|
||||||
}).then(function renderIndex(adminClient) {
|
|
||||||
configuration.push({key: 'clientId', value: adminClient.clients[0].slug, type: 'string'});
|
|
||||||
configuration.push({key: 'clientSecret', value: adminClient.clients[0].secret, type: 'string'});
|
|
||||||
|
|
||||||
var apiConfig = _.omit(configuration, function omit(value) {
|
return Promise.props(fetch).then(function renderIndex(result) {
|
||||||
return _.contains(['environment', 'database', 'mail', 'version'], value.key);
|
configuration = result.configuration;
|
||||||
});
|
|
||||||
|
configuration.clientId = {value: result.client.slug, type: 'string'};
|
||||||
|
configuration.clientSecret = {value: result.client.secret, type: 'string'};
|
||||||
|
|
||||||
res.render('default', {
|
res.render('default', {
|
||||||
skip_google_fonts: config.isPrivacyDisabled('useGoogleFonts'),
|
configuration: configuration
|
||||||
configuration: apiConfig
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ apiRoutes = function apiRoutes(middleware) {
|
||||||
router.del = router.delete;
|
router.del = router.delete;
|
||||||
|
|
||||||
// ## Configuration
|
// ## Configuration
|
||||||
router.get('/configuration', authenticatePrivate, api.http(api.configuration.browse));
|
router.get('/configuration', authenticatePrivate, api.http(api.configuration.read));
|
||||||
router.get('/configuration/:key', authenticatePrivate, api.http(api.configuration.read));
|
router.get('/configuration/:key', authenticatePrivate, api.http(api.configuration.read));
|
||||||
|
|
||||||
// ## Posts
|
// ## Posts
|
||||||
|
|
|
@ -1,68 +1,63 @@
|
||||||
/*globals describe, before, afterEach, it */
|
/*globals describe, before, afterEach, it */
|
||||||
var testUtils = require('../../utils'),
|
var testUtils = require('../../utils'),
|
||||||
should = require('should'),
|
should = require('should'),
|
||||||
|
|
||||||
rewire = require('rewire'),
|
rewire = require('rewire'),
|
||||||
_ = require('lodash'),
|
|
||||||
config = rewire('../../../server/config'),
|
|
||||||
|
|
||||||
// Stuff we are testing
|
// Stuff we are testing
|
||||||
ConfigurationAPI = rewire('../../../server/api/configuration');
|
ConfigurationAPI = rewire('../../../server/api/configuration');
|
||||||
|
|
||||||
describe('Configuration API', function () {
|
describe('Configuration API', function () {
|
||||||
var newConfig = {
|
|
||||||
fileStorage: true,
|
|
||||||
apps: true,
|
|
||||||
version: '0.5.0',
|
|
||||||
environment: process.env.NODE_ENV,
|
|
||||||
database: {
|
|
||||||
client: 'mysql'
|
|
||||||
},
|
|
||||||
mail: {
|
|
||||||
transport: 'SMTP'
|
|
||||||
},
|
|
||||||
blogUrl: 'http://local.tryghost.org'
|
|
||||||
};
|
|
||||||
|
|
||||||
// Keep the DB clean
|
// Keep the DB clean
|
||||||
before(testUtils.teardown);
|
before(testUtils.teardown);
|
||||||
afterEach(testUtils.teardown);
|
afterEach(testUtils.teardown);
|
||||||
|
|
||||||
should.exist(ConfigurationAPI);
|
should.exist(ConfigurationAPI);
|
||||||
|
|
||||||
it('can browse config', function (done) {
|
it('can read basic config and get all expected properties', function (done) {
|
||||||
var updatedConfig = _.extend({}, config, newConfig);
|
ConfigurationAPI.read().then(function (response) {
|
||||||
config.set(updatedConfig);
|
var props;
|
||||||
ConfigurationAPI.__set__('config', updatedConfig);
|
|
||||||
|
|
||||||
ConfigurationAPI.browse(testUtils.context.owner).then(function (response) {
|
|
||||||
should.exist(response);
|
should.exist(response);
|
||||||
should.exist(response.configuration);
|
should.exist(response.configuration);
|
||||||
testUtils.API.checkResponse(response.configuration[0], 'configuration');
|
response.configuration.should.be.an.Array().with.lengthOf(1);
|
||||||
/*jshint unused:false */
|
props = response.configuration[0];
|
||||||
done();
|
|
||||||
}).catch(function (error) {
|
// Check the structure
|
||||||
console.log(JSON.stringify(error));
|
props.should.have.property('blogUrl').which.is.an.Object().with.properties('type', 'value');
|
||||||
|
props.should.have.property('blogTitle').which.is.an.Object().with.properties('type', 'value');
|
||||||
|
props.should.have.property('routeKeywords').which.is.an.Object().with.properties('type', 'value');
|
||||||
|
props.should.have.property('fileStorage').which.is.an.Object().with.properties('type', 'value');
|
||||||
|
props.should.have.property('useGoogleFonts').which.is.an.Object().with.properties('type', 'value');
|
||||||
|
props.should.have.property('useGravatar').which.is.an.Object().with.properties('type', 'value');
|
||||||
|
props.should.have.property('publicAPI').which.is.an.Object().with.properties('type', 'value');
|
||||||
|
|
||||||
|
// Check a few values
|
||||||
|
props.blogUrl.should.have.property('value', 'http://127.0.0.1:2369');
|
||||||
|
props.fileStorage.should.have.property('value', true);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('can read config', function (done) {
|
it('can read about config and get all expected properties', function (done) {
|
||||||
var updatedConfig = _.extend({}, config, newConfig);
|
ConfigurationAPI.read({key: 'about'}).then(function (response) {
|
||||||
config.set(updatedConfig);
|
var props;
|
||||||
ConfigurationAPI.__set__('config', updatedConfig);
|
|
||||||
|
|
||||||
ConfigurationAPI.read(_.extend({}, testUtils.context.owner, {key: 'database'})).then(function (response) {
|
|
||||||
should.exist(response);
|
should.exist(response);
|
||||||
should.exist(response.configuration);
|
should.exist(response.configuration);
|
||||||
testUtils.API.checkResponse(response.configuration[0], 'configuration');
|
response.configuration.should.be.an.Array().with.lengthOf(1);
|
||||||
response.configuration[0].key.should.equal('database');
|
props = response.configuration[0];
|
||||||
response.configuration[0].value.should.equal('mysql');
|
|
||||||
response.configuration[0].type.should.be.null();
|
// Check the structure
|
||||||
/*jshint unused:false */
|
props.should.have.property('version').which.is.a.String();
|
||||||
done();
|
props.should.have.property('environment').which.is.a.String();
|
||||||
}).catch(function (error) {
|
props.should.have.property('database').which.is.a.String();
|
||||||
console.log(JSON.stringify(error));
|
props.should.have.property('mail').which.is.a.String();
|
||||||
|
|
||||||
|
// Check a few values
|
||||||
|
props.environment.should.match(/^testing/);
|
||||||
|
props.version.should.eql(require('../../../../package.json').version);
|
||||||
|
|
||||||
done();
|
done();
|
||||||
}).catch(done);
|
}).catch(done);
|
||||||
});
|
});
|
||||||
|
|
|
@ -9,7 +9,6 @@ var _ = require('lodash'),
|
||||||
protocol = 'http://',
|
protocol = 'http://',
|
||||||
expectedProperties = {
|
expectedProperties = {
|
||||||
// API top level
|
// API top level
|
||||||
configuration: ['key', 'value', 'type'],
|
|
||||||
posts: ['posts', 'meta'],
|
posts: ['posts', 'meta'],
|
||||||
tags: ['tags', 'meta'],
|
tags: ['tags', 'meta'],
|
||||||
users: ['users', 'meta'],
|
users: ['users', 'meta'],
|
||||||
|
|
Loading…
Reference in a new issue