mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-04 04:10:33 +03:00
commit
a78ee06848
@ -23,7 +23,7 @@ utils = {
|
||||
// ### Manual Default Options
|
||||
// These must be provided by the endpoint
|
||||
// browseDefaultOptions - valid for all browse api endpoints
|
||||
browseDefaultOptions: ['page', 'limit', 'fields', 'filter'],
|
||||
browseDefaultOptions: ['page', 'limit', 'fields', 'filter', 'order'],
|
||||
// idDefaultOptions - valid whenever an id is valid
|
||||
idDefaultOptions: ['id'],
|
||||
|
||||
@ -114,6 +114,7 @@ utils = {
|
||||
page: {matches: /^\d+$/},
|
||||
limit: {matches: /^\d+|all$/},
|
||||
fields: {matches: /^[\w, ]+$/},
|
||||
order: {matches: /^[a-z0-9_,\. ]+$/i},
|
||||
name: {}
|
||||
},
|
||||
// these values are sanitised/validated separately
|
||||
|
@ -301,7 +301,11 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
||||
// TODO: this should just be done for all methods @ the API level
|
||||
options.withRelated = _.union(options.withRelated, options.include);
|
||||
|
||||
options.order = self.orderDefaultOptions();
|
||||
if (options.order) {
|
||||
options.order = self.parseOrderOption(options.order);
|
||||
} else {
|
||||
options.order = self.orderDefaultOptions();
|
||||
}
|
||||
|
||||
return itemCollection.fetchPage(options).then(function formatResponse(response) {
|
||||
var data = {};
|
||||
@ -455,6 +459,36 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
||||
// Test for duplicate slugs.
|
||||
return checkIfSlugExists(slug);
|
||||
});
|
||||
},
|
||||
|
||||
parseOrderOption: function (order) {
|
||||
var permittedAttributes, result, rules;
|
||||
|
||||
permittedAttributes = this.prototype.permittedAttributes();
|
||||
result = {};
|
||||
rules = order.split(',');
|
||||
|
||||
_.each(rules, function (rule) {
|
||||
var match, field, direction;
|
||||
|
||||
match = /^([a-z0-9_\.]+)\s+(asc|desc)$/i.exec(rule.trim());
|
||||
|
||||
// invalid order syntax
|
||||
if (!match) {
|
||||
return;
|
||||
}
|
||||
|
||||
field = match[1].toLowerCase();
|
||||
direction = match[2].toUpperCase();
|
||||
|
||||
if (permittedAttributes.indexOf(field) === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
result[field] = direction;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
});
|
||||
|
@ -388,7 +388,7 @@ Post = ghostBookshelf.Model.extend({
|
||||
// these are the only options that can be passed to Bookshelf / Knex.
|
||||
validOptions = {
|
||||
findOne: ['importing', 'withRelated'],
|
||||
findPage: ['page', 'limit', 'columns', 'filter', 'status', 'staticPages'],
|
||||
findPage: ['page', 'limit', 'columns', 'filter', 'order', 'status', 'staticPages'],
|
||||
add: ['importing']
|
||||
};
|
||||
|
||||
|
@ -79,7 +79,7 @@ Tag = ghostBookshelf.Model.extend({
|
||||
// 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', 'columns']
|
||||
findPage: ['page', 'limit', 'columns', 'order']
|
||||
};
|
||||
|
||||
if (validOptions[methodName]) {
|
||||
|
@ -219,7 +219,7 @@ User = ghostBookshelf.Model.extend({
|
||||
findOne: ['withRelated', 'status'],
|
||||
setup: ['id'],
|
||||
edit: ['withRelated', 'id'],
|
||||
findPage: ['page', 'limit', 'columns', 'status']
|
||||
findPage: ['page', 'limit', 'columns', 'order', 'status']
|
||||
};
|
||||
|
||||
if (validOptions[methodName]) {
|
||||
|
@ -330,6 +330,54 @@ describe('Post API', function () {
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can order posts using asc', function (done) {
|
||||
var posts, expectedTitles;
|
||||
|
||||
posts = _(testUtils.DataGenerator.Content.posts).reject('page').value();
|
||||
expectedTitles = _(posts).pluck('title').sortBy().value();
|
||||
|
||||
PostAPI.browse({context: {user: 1}, status: 'all', order: 'title asc', fields: 'title'}).then(function (results) {
|
||||
should.exist(results.posts);
|
||||
|
||||
var titles = _.pluck(results.posts, 'title');
|
||||
titles.should.eql(expectedTitles);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can order posts using desc', function (done) {
|
||||
var posts, expectedTitles;
|
||||
|
||||
posts = _(testUtils.DataGenerator.Content.posts).reject('page').value();
|
||||
expectedTitles = _(posts).pluck('title').sortBy().reverse().value();
|
||||
|
||||
PostAPI.browse({context: {user: 1}, status: 'all', order: 'title DESC', fields: 'title'}).then(function (results) {
|
||||
should.exist(results.posts);
|
||||
|
||||
var titles = _.pluck(results.posts, 'title');
|
||||
titles.should.eql(expectedTitles);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can order posts and filter disallowed attributes', function (done) {
|
||||
var posts, expectedTitles;
|
||||
|
||||
posts = _(testUtils.DataGenerator.Content.posts).reject('page').value();
|
||||
expectedTitles = _(posts).pluck('title').sortBy().value();
|
||||
|
||||
PostAPI.browse({context: {user: 1}, status: 'all', order: 'bunny DESC, title ASC', fields: 'title'}).then(function (results) {
|
||||
should.exist(results.posts);
|
||||
|
||||
var titles = _.pluck(results.posts, 'title');
|
||||
titles.should.eql(expectedTitles);
|
||||
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Read', function () {
|
||||
|
@ -9,6 +9,14 @@ var testUtils = require('../../utils'),
|
||||
|
||||
TagAPI = require('../../../server/api/tags');
|
||||
|
||||
// there are some random generated tags in test database
|
||||
// which can't be sorted easily using _.sortBy()
|
||||
// so we filter them out and leave only pre-built fixtures
|
||||
// usage: tags.filter(onlyFixtures)
|
||||
function onlyFixtures(slug) {
|
||||
return testUtils.DataGenerator.Content.tags.indexOf(slug) >= 0;
|
||||
}
|
||||
|
||||
describe('Tags API', function () {
|
||||
// Keep the DB clean
|
||||
before(testUtils.teardown);
|
||||
@ -259,6 +267,52 @@ describe('Tags API', function () {
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can browse and order by slug using asc', function (done) {
|
||||
var expectedTags;
|
||||
|
||||
TagAPI.browse({context: {user: 1}})
|
||||
.then(function (results) {
|
||||
should.exist(results);
|
||||
|
||||
expectedTags = _(results.tags).pluck('slug').filter(onlyFixtures).sortBy().value();
|
||||
|
||||
return TagAPI.browse({context: {user: 1}, order: 'slug asc'});
|
||||
})
|
||||
.then(function (results) {
|
||||
var tags;
|
||||
|
||||
should.exist(results);
|
||||
|
||||
tags = _(results.tags).pluck('slug').filter(onlyFixtures).value();
|
||||
tags.should.eql(expectedTags);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('can browse and order by slug using desc', function (done) {
|
||||
var expectedTags;
|
||||
|
||||
TagAPI.browse({context: {user: 1}})
|
||||
.then(function (results) {
|
||||
should.exist(results);
|
||||
|
||||
expectedTags = _(results.tags).pluck('slug').filter(onlyFixtures).sortBy().reverse().value();
|
||||
|
||||
return TagAPI.browse({context: {user: 1}, order: 'slug desc'});
|
||||
})
|
||||
.then(function (results) {
|
||||
var tags;
|
||||
|
||||
should.exist(results);
|
||||
|
||||
tags = _(results.tags).pluck('slug').filter(onlyFixtures).value();
|
||||
tags.should.eql(expectedTags);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Read', function () {
|
||||
|
@ -139,6 +139,52 @@ describe('Users API', function () {
|
||||
done();
|
||||
}).catch(done);
|
||||
});
|
||||
|
||||
it('can browse and order by name using asc', function (done) {
|
||||
var expectedUsers;
|
||||
|
||||
UserAPI.browse(testUtils.context.admin)
|
||||
.then(function (results) {
|
||||
should.exist(results);
|
||||
|
||||
expectedUsers = _(results.users).pluck('slug').sortBy().value();
|
||||
|
||||
return UserAPI.browse(_.extend({}, testUtils.context.admin, {order: 'slug asc'}));
|
||||
})
|
||||
.then(function (results) {
|
||||
var users;
|
||||
|
||||
should.exist(results);
|
||||
|
||||
users = _.pluck(results.users, 'slug');
|
||||
users.should.eql(expectedUsers);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done);
|
||||
});
|
||||
|
||||
it('can browse and order by name using desc', function (done) {
|
||||
var expectedUsers;
|
||||
|
||||
UserAPI.browse(testUtils.context.admin)
|
||||
.then(function (results) {
|
||||
should.exist(results);
|
||||
|
||||
expectedUsers = _(results.users).pluck('slug').sortBy().reverse().value();
|
||||
|
||||
return UserAPI.browse(_.extend({}, testUtils.context.admin, {order: 'slug desc'}));
|
||||
})
|
||||
.then(function (results) {
|
||||
var users;
|
||||
|
||||
should.exist(results);
|
||||
|
||||
users = _.pluck(results.users, 'slug');
|
||||
users.should.eql(expectedUsers);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Read', function () {
|
||||
|
@ -18,7 +18,7 @@ describe('API Utils', function () {
|
||||
describe('Default Options', function () {
|
||||
it('should provide a set of default options', function () {
|
||||
apiUtils.globalDefaultOptions.should.eql(['context', 'include']);
|
||||
apiUtils.browseDefaultOptions.should.eql(['page', 'limit', 'fields', 'filter']);
|
||||
apiUtils.browseDefaultOptions.should.eql(['page', 'limit', 'fields', 'filter', 'order']);
|
||||
apiUtils.dataDefaultOptions.should.eql(['data']);
|
||||
apiUtils.idDefaultOptions.should.eql(['id']);
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user