mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-01 13:54:35 +03:00
Merge pull request #4406 from RaoHai/tag-endpoints
full BREAD Tag endpoints and Tag api tests
This commit is contained in:
commit
819a978192
@ -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.'));
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -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({
|
||||
|
@ -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));
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user