0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-03 23:00:14 -05:00

Merge pull request #4686 from jaswilli/tag-api-post-count

Finish up post count support in tags API
This commit is contained in:
Hannah Wolfe 2014-12-20 11:39:29 +00:00
commit 7cdfd95098
4 changed files with 133 additions and 57 deletions

View file

@ -8,8 +8,17 @@ var Promise = require('bluebird'),
utils = require('./utils'), utils = require('./utils'),
docName = 'tags', docName = 'tags',
allowedIncludes = ['post_count'],
tags; tags;
// ## Helpers
function prepareInclude(include) {
include = include || '';
include = _.intersection(include.split(','), allowedIncludes);
return include;
}
/** /**
* ## Tags API Methods * ## Tags API Methods
* *
@ -22,7 +31,13 @@ tags = {
* @returns {Promise(Tags)} Tags Collection * @returns {Promise(Tags)} Tags Collection
*/ */
browse: function browse(options) { browse: function browse(options) {
options = options || {};
return canThis(options.context).browse.tag().then(function () { return canThis(options.context).browse.tag().then(function () {
if (options.include) {
options.include = prepareInclude(options.include);
}
return dataProvider.Tag.findPage(options); return dataProvider.Tag.findPage(options);
}, function () { }, function () {
return Promise.reject(new errors.NoPermissionError('You do not have permission to browse tags.')); return Promise.reject(new errors.NoPermissionError('You do not have permission to browse tags.'));
@ -35,9 +50,16 @@ tags = {
* @return {Promise(Tag)} Tag * @return {Promise(Tag)} Tag
*/ */
read: function read(options) { read: function read(options) {
options = options || {};
var attrs = ['id', 'slug'], var attrs = ['id', 'slug'],
data = _.pick(options, attrs); data = _.pick(options, attrs);
return canThis(options.context).read.tag().then(function () { return canThis(options.context).read.tag().then(function () {
if (options.include) {
options.include = prepareInclude(options.include);
}
return dataProvider.Tag.findOne(data, options).then(function (result) { return dataProvider.Tag.findOne(data, options).then(function (result) {
if (result) { if (result) {
return {tags: [result.toJSON()]}; return {tags: [result.toJSON()]};
@ -59,6 +81,10 @@ tags = {
options = options || {}; options = options || {};
return canThis(options.context).add.tag(object).then(function () { return canThis(options.context).add.tag(object).then(function () {
if (options.include) {
options.include = prepareInclude(options.include);
}
return utils.checkObject(object, docName).then(function (checkedTagData) { return utils.checkObject(object, docName).then(function (checkedTagData) {
return dataProvider.Tag.add(checkedTagData.tags[0], options); return dataProvider.Tag.add(checkedTagData.tags[0], options);
}).then(function (result) { }).then(function (result) {
@ -80,7 +106,13 @@ tags = {
* @return {Promise(Tag)} Edited Tag * @return {Promise(Tag)} Edited Tag
*/ */
edit: function edit(object, options) { edit: function edit(object, options) {
options = options || {};
return canThis(options.context).edit.tag(options.id).then(function () { return canThis(options.context).edit.tag(options.id).then(function () {
if (options.include) {
options.include = prepareInclude(options.include);
}
return utils.checkObject(object, docName).then(function (checkedTagData) { return utils.checkObject(object, docName).then(function (checkedTagData) {
return dataProvider.Tag.edit(checkedTagData.tags[0], options); return dataProvider.Tag.edit(checkedTagData.tags[0], options);
}).then(function (result) { }).then(function (result) {
@ -105,6 +137,8 @@ tags = {
* @return {Promise(Tag)} Deleted Tag * @return {Promise(Tag)} Deleted Tag
*/ */
destroy: function destroy(options) { destroy: function destroy(options) {
options = options || {};
return canThis(options.context).destroy.tag(options.id).then(function () { return canThis(options.context).destroy.tag(options.id).then(function () {
return tags.read(options).then(function (result) { return tags.read(options).then(function (result) {
return dataProvider.Tag.destroy(options).then(function () { return dataProvider.Tag.destroy(options).then(function () {

View file

@ -7,6 +7,17 @@ var _ = require('lodash'),
Tag, Tag,
Tags; Tags;
function addPostCount(options, obj) {
if (options.include && options.include.indexOf('post_count') > -1) {
obj.query('select', 'tags.*');
obj.query('count', 'posts_tags.id as post_count');
obj.query('leftJoin', 'posts_tags', 'tag_id', 'tags.id');
obj.query('groupBy', 'tag_id', 'tags.id');
options.include = _.pull([].concat(options.include), 'post_count');
}
}
Tag = ghostBookshelf.Model.extend({ Tag = ghostBookshelf.Model.extend({
tableName: 'tags', tableName: 'tags',
@ -70,13 +81,32 @@ Tag = ghostBookshelf.Model.extend({
return options; return options;
}, },
/**
* ### Find One
* @overrides ghostBookshelf.Model.findOne
*/
findOne: function (data, options) {
options = options || {};
options = this.filterOptions(options, 'findOne');
data = this.filterData(data, 'findOne');
var tag = this.forge(data);
addPostCount(options, tag);
// Add related objects
options.withRelated = _.union(options.withRelated, options.include);
return tag.fetch(options);
},
findPage: function (options) { findPage: function (options) {
options = options || {}; options = options || {};
var tagCollection = Tags.forge(), var tagCollection = Tags.forge(),
collectionPromise, collectionPromise,
totalPromise,
countPromise,
qb; qb;
if (options.limit && options.limit !== 'all') { if (options.limit && options.limit !== 'all') {
@ -102,6 +132,8 @@ Tag = ghostBookshelf.Model.extend({
.query('offset', options.limit * (options.page - 1)); .query('offset', options.limit * (options.page - 1));
} }
addPostCount(options, tagCollection);
collectionPromise = tagCollection.fetch(_.omit(options, 'page', 'limit')); collectionPromise = tagCollection.fetch(_.omit(options, 'page', 'limit'));
// Find total number of tags // Find total number of tags
@ -112,22 +144,10 @@ Tag = ghostBookshelf.Model.extend({
qb.where(options.where); qb.where(options.where);
} }
totalPromise = qb.count('tags.id as aggregate'); return Promise.join(collectionPromise, qb.count('tags.id as aggregate')).then(function (results) {
// Fetch post count information
if (options.include) {
if (options.include.indexOf('post_count') > -1) {
qb = ghostBookshelf.knex('posts_tags');
countPromise = qb.select('tag_id').count('* as postCount').groupBy('tag_id');
}
}
return Promise.join(collectionPromise, totalPromise, countPromise).then(function (results) {
var totalTags = results[1][0].aggregate, var totalTags = results[1][0].aggregate,
calcPages = Math.ceil(totalTags / options.limit) || 0, calcPages = Math.ceil(totalTags / options.limit) || 0,
tagCollection = results[0], tagCollection = results[0],
postsPerTagCollection = results[2],
pagination = {}, pagination = {},
meta = {}, meta = {},
data = {}; data = {};
@ -140,19 +160,6 @@ Tag = ghostBookshelf.Model.extend({
pagination.prev = null; pagination.prev = null;
data.tags = tagCollection.toJSON(); data.tags = tagCollection.toJSON();
if (postsPerTagCollection) {
// Merge two result sets
_.each(data.tags, function (tag) {
var postsPerTag = _.find(postsPerTagCollection, function (obj) { return obj.tag_id === tag.id; });
if (postsPerTag) {
tag.post_count = postsPerTag.postCount;
} else {
tag.post_count = 0;
}
});
}
data.meta = meta; data.meta = meta;
meta.pagination = pagination; meta.pagination = pagination;

View file

@ -13,7 +13,7 @@ describe('Tags API', function () {
// Keep the DB clean // Keep the DB clean
before(testUtils.teardown); before(testUtils.teardown);
afterEach(testUtils.teardown); afterEach(testUtils.teardown);
beforeEach(testUtils.setup('users:roles', 'tag', 'perms:tag', 'perms:init')); beforeEach(testUtils.setup('users:roles', 'perms:tag', 'perms:init', 'posts'));
should.exist(TagAPI); should.exist(TagAPI);
@ -161,5 +161,34 @@ describe('Tags API', function () {
done(); done();
}).catch(done); }).catch(done);
}); });
it('with include post_count', function (done) {
TagAPI.browse({context: {user: 1}, include: 'post_count'}).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag', 'post_count');
should.exist(results.tags[0].post_count);
done();
}).catch(done);
});
});
describe('Read', function () {
it('returns post_count with include post_count', function (done) {
TagAPI.read({context: {user: 1}, include: 'post_count', slug: 'kitchen-sink'}).then(function (results) {
should.exist(results);
should.exist(results.tags);
results.tags.length.should.be.above(0);
testUtils.API.checkResponse(results.tags[0], 'tag', 'post_count');
should.exist(results.tags[0].post_count);
results.tags[0].post_count.should.equal(2);
done();
}).catch(done);
});
}); });
}); });

View file

@ -37,10 +37,26 @@ describe('Tag Model', function () {
}).catch(done); }).catch(done);
}); });
it('can findPage with limit all', function (done) { it('returns post_count if include post_count', function (done) {
testUtils.fixtures.insertPosts().then(function () { testUtils.fixtures.insertPosts().then(function () {
return TagModel.findPage({limit: 'all'}); TagModel.findOne({slug: 'kitchen-sink'}, {include: 'post_count'}).then(function (tag) {
}).then(function (results) { should.exist(tag);
tag.get('post_count').should.equal(2);
done();
}).catch(done);
});
});
describe('findPage', function () {
beforeEach(function (done) {
testUtils.fixtures.insertPosts().then(function () {
done();
}).catch(done);
});
it('with limit all', function (done) {
TagModel.findPage({limit: 'all'}).then(function (results) {
results.meta.pagination.page.should.equal(1); results.meta.pagination.page.should.equal(1);
results.meta.pagination.limit.should.equal('all'); results.meta.pagination.limit.should.equal('all');
results.meta.pagination.pages.should.equal(1); results.meta.pagination.pages.should.equal(1);
@ -50,27 +66,17 @@ describe('Tag Model', function () {
}).catch(done); }).catch(done);
}); });
it('can findPage with post_count include', function (done) { it('with include post_count', function (done) {
testUtils.fixtures.insertPosts().then(function () { TagModel.findPage({limit: 'all', include: 'post_count'}).then(function (results) {
return TagModel.findPage({include: 'post_count'}); results.meta.pagination.page.should.equal(1);
}).then(function (results) { results.meta.pagination.limit.should.equal('all');
_.each(results.tags, function (tag) { tag.should.have.property('post_count'); }); results.meta.pagination.pages.should.equal(1);
_.findWhere(results.tags, {slug: 'kitchen-sink'}).post_count.should.equal(2); results.tags.length.should.equal(5);
_.findWhere(results.tags, {slug: 'pollo'}).post_count.should.equal(1); should.exist(results.tags[0].post_count);
_.findWhere(results.tags, {slug: 'injection'}).post_count.should.equal(0);
done(); done();
}).catch(done); }).catch(done);
}); });
it('can findPage with post_count include and limit less then total_tags', function (done) {
testUtils.fixtures.insertPosts().then(function () {
return TagModel.findPage({include: 'post_count', limit: 2});
}).then(function (results) {
_.each(results.tags, function (tag) { tag.should.have.property('post_count'); });
done();
}).catch(done);
}); });
describe('a Post', function () { describe('a Post', function () {