Merge pull request #4406 from RaoHai/tag-endpoints

full BREAD Tag endpoints and Tag api tests
This commit is contained in:
Sebastian Gierlinger 2014-11-13 19:25:36 +01:00
commit 819a978192
4 changed files with 347 additions and 56 deletions

View File

@ -1,9 +1,13 @@
// # Tag API
// RESTful API for the Tag resource
var Promise = require('bluebird'),
canThis = require('../permissions').canThis,
var Promise = require('bluebird'),
_ = require('lodash'),
canThis = require('../permissions').canThis,
dataProvider = require('../models'),
errors = require('../errors'),
errors = require('../errors'),
utils = require('./utils'),
docName = 'tags',
tags;
/**
@ -15,16 +19,107 @@ tags = {
/**
* ### Browse
* @param {{context}} options
* @returns {Promise(Tags)}
* @returns {Promise(Tags)} Tags Collection
*/
browse: function browse(options) {
return canThis(options.context).browse.tag().then(function () {
return dataProvider.Tag.findAll(options).then(function (result) {
return {tags: result.toJSON()};
});
if (options.limit && options.limit === 'all') {
return dataProvider.Tag.findAll(options).then(function (result) {
return {tags: result.toJSON()};
});
}
return dataProvider.Tag.findPage(options);
}, function () {
return Promise.reject(new errors.NoPermissionError('You do not have permission to browse tags.'));
});
},
/**
* ### Read
* @param {{id}} options
* @return {Promise(Tag)} Tag
*/
read: function read(options) {
var attrs = ['id', 'slug'],
data = _.pick(options, attrs);
return canThis(options.context).read.tag().then(function () {
return dataProvider.Tag.findOne(data, options).then(function (result) {
if (result) {
return {tags: [result.toJSON()]};
}
return Promise.reject(new errors.NotFoundError('Tag not found.'));
});
}, function () {
return Promise.reject(new errors.NoPermissionError('You do not have permission to read tags.'));
});
},
/**
* ### Add tag
* @param {Tag} object the tag to create
* @returns {Promise(Tag)} Newly created Tag
*/
add: function add(object, options) {
options = options || {};
return canThis(options.context).add.tag(object).then(function () {
return utils.checkObject(object, docName).then(function (checkedTagData) {
return dataProvider.Tag.add(checkedTagData.tags[0], options);
}).then(function (result) {
var tag = result.toJSON();
return {tags: [tag]};
});
}, function () {
return Promise.reject(new errors.NoPermissionError('You do not have permission to add tags.'));
});
},
/**
* ### edit tag
*
* @public
* @param {Tag} object Tag or specific properties to update
* @param {{id (required), context, include,...}} options
* @return {Promise(Tag)} Edited Tag
*/
edit: function edit(object, options) {
return canThis(options.context).edit.tag(options.id).then(function () {
return utils.checkObject(object, docName).then(function (checkedTagData) {
return dataProvider.Tag.edit(checkedTagData.tags[0], options);
}).then(function (result) {
if (result) {
var tag = result.toJSON();
return {tags: [tag]};
}
return Promise.reject(new errors.NotFoundError('Tag not found.'));
});
}, function () {
return Promise.reject(new errors.NoPermissionError('You do not have permission to edit tags.'));
});
},
/**
* ### Destroy
*
* @public
* @param {{id (required), context,...}} options
* @return {Promise(Tag)} Deleted Tag
*/
destroy: function destroy(options) {
return canThis(options.context).destroy.tag(options.id).then(function () {
return tags.read(options).then(function (result) {
return dataProvider.Tag.destroy(options).then(function () {
return result;
});
});
}, function () {
return Promise.reject(new errors.NoPermissionError('You do not have permission to remove tags.'));
});
}
};

View File

@ -1,4 +1,6 @@
var ghostBookshelf = require('./base'),
var _ = require('lodash'),
errors = require('../errors'),
ghostBookshelf = require('./base'),
Tag,
Tags;
@ -36,6 +38,107 @@ Tag = ghostBookshelf.Model.extend({
return attrs;
}
}, {
permittedOptions: function (methodName) {
var options = ghostBookshelf.Model.permittedOptions(),
// whitelists for the `options` hash argument on methods, by method name.
// these are the only options that can be passed to Bookshelf / Knex.
validOptions = {
findPage: ['page', 'limit']
};
if (validOptions[methodName]) {
options = options.concat(validOptions[methodName]);
}
return options;
},
findPage: function (options) {
options = options || {};
var tagCollection = Tags.forge();
if (options.limit) {
options.limit = parseInt(options.limit, 10) || 15;
}
if (options.page) {
options.page = parseInt(options.page, 10) || 1;
}
options = this.filterOptions(options, 'findPage');
// Set default settings for options
options = _.extend({
page: 1, // pagination page
limit: 15,
where: {}
}, options);
return tagCollection
.query('limit', options.limit)
.query('offset', options.limit * (options.page - 1))
.fetch(_.omit(options, 'page', 'limit'))
// Fetch pagination information
.then(function () {
var qb,
tableName = _.result(tagCollection, 'tableName'),
idAttribute = _.result(tagCollection, 'idAttribute');
// After we're done, we need to figure out what
// the limits are for the pagination values.
qb = ghostBookshelf.knex(tableName);
if (options.where) {
qb.where(options.where);
}
return qb.count(tableName + '.' + idAttribute + ' as aggregate');
})
// Format response of data
.then(function (resp) {
var totalTags = parseInt(resp[0].aggregate, 10),
calcPages = Math.ceil(totalTags / options.limit),
pagination = {},
meta = {},
data = {};
pagination.page = options.page;
pagination.limit = options.limit;
pagination.pages = calcPages === 0 ? 1 : calcPages;
pagination.total = totalTags;
pagination.next = null;
pagination.prev = null;
data.tags = tagCollection.toJSON();
data.meta = meta;
meta.pagination = pagination;
if (pagination.pages > 1) {
if (pagination.page === 1) {
pagination.next = pagination.page + 1;
} else if (pagination.page === pagination.pages) {
pagination.prev = pagination.page - 1;
} else {
pagination.next = pagination.page + 1;
pagination.prev = pagination.page - 1;
}
}
return data;
})
.catch(errors.logAndThrowError);
},
destroy: function (options) {
var id = options.id;
options = this.filterOptions(options, 'destroy');
return this.forge({id: id}).fetch({withRelated: ['posts']}).then(function destroyTagsAndPost(tag) {
return tag.related('posts').detach().then(function () {
return tag.destroy(options);
});
});
}
});
Tags = ghostBookshelf.Collection.extend({

View File

@ -38,6 +38,10 @@ apiRoutes = function (middleware) {
// ## Tags
router.get('/tags', api.http(api.tags.browse));
router.get('/tags/:id', api.http(api.tags.read));
router.post('/tags', api.http(api.tags.add));
router.put('/tags/:id', api.http(api.tags.edit));
router.del('/tags/:id', api.http(api.tags.destroy));
// ## Roles
router.get('/roles/', api.http(api.roles.browse));

View File

@ -2,8 +2,11 @@
/*jshint expr:true*/
var testUtils = require('../../utils'),
should = require('should'),
Promise = require('bluebird'),
_ = require('lodash'),
// Stuff we are testing
context = testUtils.context,
TagAPI = require('../../../server/api/tags');
describe('Tags API', function () {
@ -14,63 +17,149 @@ describe('Tags API', function () {
should.exist(TagAPI);
it('can browse (internal)', function (done) {
TagAPI.browse(testUtils.context.internal).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
describe('Add', function () {
var newTag;
done();
}).catch(done);
beforeEach(function () {
newTag = _.clone(testUtils.DataGenerator.forKnex.createTag(testUtils.DataGenerator.Content.tags[0]));
Promise.resolve(newTag);
});
it('can add a tag (admin)', function (done) {
TagAPI.add({tags: [newTag]}, testUtils.context.admin)
.then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
done();
}).catch(done);
});
it('can add a tag (editor)', function (done) {
TagAPI.add({tags: [newTag]}, testUtils.context.editor)
.then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
done();
}).catch(done);
});
it('No-auth CANNOT add tag', function (done) {
TagAPI.add({tags: [newTag]}).then(function () {
done(new Error('Add tag is not denied without authentication.'));
}, function () {
done();
}).catch(done);
});
});
it('can browse (owner)', function (done) {
TagAPI.browse({context: {user: 1}}).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
describe('Edit', function () {
var newTagName = 'tagNameUpdated',
firstTag = 1;
done();
}).catch(done);
it('can edit a tag (admin)', function (done) {
TagAPI.edit({tags: [{name: newTagName}]}, _.extend({}, context.admin, {id: firstTag}))
.then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
done();
}).catch(done);
});
it('can edit a tag (editor)', function (done) {
TagAPI.edit({tags: [{name: newTagName}]}, _.extend({}, context.editor, {id: firstTag}))
.then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
done();
}).catch(done);
});
it('No-auth CANNOT edit tag', function (done) {
TagAPI.edit({tags: [{name: newTagName}]}, _.extend({}, {id: firstTag}))
.then(function () {
done(new Error('Add tag is not denied without authentication.'));
}, function () {
done();
}).catch(done);
});
});
it('can browse (admin)', function (done) {
TagAPI.browse(testUtils.context.admin).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
done();
}).catch(done);
describe('Destroy', function () {
var firstTag = 1;
it('can destroy Tag', function (done) {
TagAPI.destroy(_.extend({}, testUtils.context.admin, {id: firstTag}))
.then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
done();
}).catch(done);
});
});
it('can browse (editor)', function (done) {
TagAPI.browse(testUtils.context.editor).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
describe('Browse', function () {
it('can browse (internal)', function (done) {
TagAPI.browse(testUtils.context.internal).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
done();
}).catch(done);
});
done();
}).catch(done);
});
it('can browse (author)', function (done) {
TagAPI.browse(testUtils.context.author).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
it('can browse (owner)', function (done) {
TagAPI.browse({context: {user: 1}}).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
done();
}).catch(done);
done();
}).catch(done);
});
it('can browse (admin)', function (done) {
TagAPI.browse(testUtils.context.admin).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
done();
}).catch(done);
});
it('can browse (editor)', function (done) {
TagAPI.browse(testUtils.context.editor).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
done();
}).catch(done);
});
it('can browse (author)', function (done) {
TagAPI.browse(testUtils.context.author).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag');
results.tags[0].created_at.should.be.an.instanceof(Date);
done();
}).catch(done);
});
});
});