Implements new Themes JSON API

closes #2592
- Add themes browse/read endpoint
- Add new permissions for themes (only admin by default)
- Add integration tests
This commit is contained in:
Fabian Becker 2014-05-13 14:33:34 +00:00
parent 169a984372
commit 628654961a
7 changed files with 203 additions and 1 deletions

View File

@ -10,6 +10,7 @@ var _ = require('lodash'),
posts = require('./posts'),
users = require('./users'),
tags = require('./tags'),
themes = require('./themes'),
mail = require('./mail'),
requestHandler,
init;
@ -167,6 +168,7 @@ module.exports = {
posts: posts,
users: users,
tags: tags,
themes: themes,
notifications: notifications,
settings: settings,
db: db,

88
core/server/api/themes.js Normal file
View File

@ -0,0 +1,88 @@
var when = require('when'),
_ = require('lodash'),
canThis = require('../permissions').canThis,
config = require('../config'),
errors = require('../errors'),
settings = require('./settings'),
when = require('when'),
themes;
// ## Themes
themes = {
browse: function browse() {
// **returns:** a promise for a collection of themes in a json object
return canThis(this).browse.theme().then(function () {
return when.all([
settings.read.call({ internal: true }, 'activeTheme'),
config().paths.availableThemes
]).then(function (result) {
var activeTheme = result[0].settings[0].value,
availableThemes = result[1],
themes = [],
themeKeys = Object.keys(availableThemes);
_.each(themeKeys, function (key) {
if (key.indexOf('.') !== 0
&& key !== '_messages'
&& key !== 'README.md'
) {
var item = {
uuid: key
};
if (availableThemes[key].hasOwnProperty('package.json')) {
item = _.merge(item, availableThemes[key]['package.json']);
}
item.active = item.uuid === activeTheme;
themes.push(item);
}
});
return { themes: themes };
});
}, function () {
return when.reject(new errors.NoPermissionError('You do not have permission to browse themes.'));
});
},
edit: function edit(themeData) {
var self = this,
themeName;
// Check whether the request is properly formatted.
if (!_.isArray(themeData.themes)) {
return when.reject({type: 'BadRequest', message: 'Invalid request.'});
}
themeName = themeData.themes[0].uuid;
return canThis(this).edit.theme().then(function () {
return themes.browse.call(self).then(function (availableThemes) {
var theme;
// Check if the theme exists
theme = _.find(availableThemes.themes, function (currentTheme) {
return currentTheme.uuid === themeName;
});
if (!theme) {
return when.reject(new errors.BadRequestError('Theme does not exist.'));
}
// Activate the theme
return settings.edit.call({ internal: true }, 'activeTheme', themeName).then(function () {
theme.active = true;
return { themes: [theme]};
});
});
}, function () {
return when.reject(new errors.NoPermissionError('You do not have permission to edit themes.'));
});
}
};
module.exports = themes;

View File

@ -124,6 +124,16 @@ var fixtures = {
"name": "Edit settings",
"action_type": "edit",
"object_type": "setting"
},
{
"name": "Browse themes",
"action_type": "browse",
"object_type": "theme"
},
{
"name": "Edit themes",
"action_type": "edit",
"object_type": "theme"
}
]
};

View File

@ -20,6 +20,9 @@ module.exports = function (server) {
server.put('/ghost/api/v0.1/users/:id/', api.requestHandler(api.users.edit));
// #### Tags
server.get('/ghost/api/v0.1/tags/', api.requestHandler(api.tags.browse));
// #### Themes
server.get('/ghost/api/v0.1/themes/', api.requestHandler(api.themes.browse));
server.put('/ghost/api/v0.1/themes/:name', api.requestHandler(api.themes.edit));
// #### Notifications
server.del('/ghost/api/v0.1/notifications/:id', api.requestHandler(api.notifications.destroy));
server.post('/ghost/api/v0.1/notifications/', api.requestHandler(api.notifications.add));

View File

@ -0,0 +1,98 @@
/*globals describe, before, beforeEach, afterEach, it */
var _ = require('lodash'),
testUtils = require('../../utils'),
rewire = require('rewire'),
should = require('should'),
sinon = require('sinon'),
when = require('when'),
// Stuff we are testing
permissions = require('../../../server/permissions'),
settings = require('../../../server/api/settings'),
ThemeAPI = rewire('../../../server/api/themes');
describe('Themes API', function () {
var configStub,
sandbox,
settingsReadStub;
before(function (done) {
testUtils.clearData().then(function () {
done();
}, done);
});
beforeEach(function (done) {
testUtils.initData().then(function () {
return testUtils.insertDefaultFixtures();
}).then(function () {
return permissions.init();
}).then(function () {
sandbox = sinon.sandbox.create();
// Override settings.read for activeTheme
settingsReadStub = sandbox.stub(settings, 'read', function () {
return when({ settings: [{value: 'casper'}] });
});
configStub = sandbox.stub().returns({
'paths': {
'subdir': '',
'availableThemes': {
'casper': {
'package.json': { name: 'Casper', version: '0.9.3' }
},
'rasper': {
'package.json': { name: 'Rasper', version: '0.9.6' }
}
}
}
});
done();
}, done);
});
afterEach(function (done) {
testUtils.clearData().then(function () {
sandbox.restore();
done();
}, done);
});
it('can browse', function (done) {
var config;
config = ThemeAPI.__get__('config');
_.extend(configStub, config);
ThemeAPI.__set__('config', configStub);
ThemeAPI.browse.call({user: 1}).then(function (result) {
should.exist(result);
result.themes.length.should.be.above(0);
testUtils.API.checkResponse(result.themes[0], 'theme');
done();
}, function (error) {
done(new Error(JSON.stringify(error)));
})
});
it('can edit', function (done) {
var config;
config = ThemeAPI.__get__('config');
_.extend(configStub, config);
ThemeAPI.__set__('config', configStub);
ThemeAPI.edit.call({user: 1}, {themes: [{uuid: 'rasper', active: true }]}).then(function (result) {
should.exist(result);
should.exist(result.themes);
result.themes.length.should.be.above(0);
testUtils.API.checkResponse(result.themes[0], 'theme');
result.themes[0].uuid.should.equal('rasper');
done();
}, function (error) {
done(new Error(JSON.stringify(error)));
})
})
});

View File

@ -104,7 +104,7 @@ describe('Permissions', function () {
.then(function (actionsMap) {
should.exist(actionsMap);
actionsMap.edit.sort().should.eql(['post', 'tag', 'user', 'page', 'setting'].sort());
actionsMap.edit.sort().should.eql(['post', 'tag', 'user', 'page', 'theme', 'setting'].sort());
actionsMap.should.equal(permissions.actionsMap);

View File

@ -14,6 +14,7 @@ var url = require('url'),
setting: ['id', 'uuid', 'key', 'value', 'type', 'created_at', 'created_by', 'updated_at', 'updated_by'],
tag: ['id', 'uuid', 'name', 'slug', 'description', 'parent',
'meta_title', 'meta_description', 'created_at', 'created_by', 'updated_at', 'updated_by'],
theme: ['uuid', 'name', 'version', 'active'],
user: ['id', 'uuid', 'name', 'slug', 'email', 'image', 'cover', 'bio', 'website',
'location', 'accessibility', 'status', 'language', 'meta_title', 'meta_description', 'last_login',
'created_at', 'created_by', 'updated_at', 'updated_by'],