mirror of
https://github.com/TryGhost/Ghost.git
synced 2024-12-12 06:25:51 +03:00
commit
a78ee06848
@ -23,7 +23,7 @@ utils = {
|
|||||||
// ### Manual Default Options
|
// ### Manual Default Options
|
||||||
// These must be provided by the endpoint
|
// These must be provided by the endpoint
|
||||||
// browseDefaultOptions - valid for all browse api endpoints
|
// 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 - valid whenever an id is valid
|
||||||
idDefaultOptions: ['id'],
|
idDefaultOptions: ['id'],
|
||||||
|
|
||||||
@ -114,6 +114,7 @@ utils = {
|
|||||||
page: {matches: /^\d+$/},
|
page: {matches: /^\d+$/},
|
||||||
limit: {matches: /^\d+|all$/},
|
limit: {matches: /^\d+|all$/},
|
||||||
fields: {matches: /^[\w, ]+$/},
|
fields: {matches: /^[\w, ]+$/},
|
||||||
|
order: {matches: /^[a-z0-9_,\. ]+$/i},
|
||||||
name: {}
|
name: {}
|
||||||
},
|
},
|
||||||
// these values are sanitised/validated separately
|
// 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
|
// TODO: this should just be done for all methods @ the API level
|
||||||
options.withRelated = _.union(options.withRelated, options.include);
|
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) {
|
return itemCollection.fetchPage(options).then(function formatResponse(response) {
|
||||||
var data = {};
|
var data = {};
|
||||||
@ -455,6 +459,36 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
|
|||||||
// Test for duplicate slugs.
|
// Test for duplicate slugs.
|
||||||
return checkIfSlugExists(slug);
|
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.
|
// these are the only options that can be passed to Bookshelf / Knex.
|
||||||
validOptions = {
|
validOptions = {
|
||||||
findOne: ['importing', 'withRelated'],
|
findOne: ['importing', 'withRelated'],
|
||||||
findPage: ['page', 'limit', 'columns', 'filter', 'status', 'staticPages'],
|
findPage: ['page', 'limit', 'columns', 'filter', 'order', 'status', 'staticPages'],
|
||||||
add: ['importing']
|
add: ['importing']
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -79,7 +79,7 @@ Tag = ghostBookshelf.Model.extend({
|
|||||||
// whitelists for the `options` hash argument on methods, by method name.
|
// whitelists for the `options` hash argument on methods, by method name.
|
||||||
// these are the only options that can be passed to Bookshelf / Knex.
|
// these are the only options that can be passed to Bookshelf / Knex.
|
||||||
validOptions = {
|
validOptions = {
|
||||||
findPage: ['page', 'limit', 'columns']
|
findPage: ['page', 'limit', 'columns', 'order']
|
||||||
};
|
};
|
||||||
|
|
||||||
if (validOptions[methodName]) {
|
if (validOptions[methodName]) {
|
||||||
|
@ -219,7 +219,7 @@ User = ghostBookshelf.Model.extend({
|
|||||||
findOne: ['withRelated', 'status'],
|
findOne: ['withRelated', 'status'],
|
||||||
setup: ['id'],
|
setup: ['id'],
|
||||||
edit: ['withRelated', 'id'],
|
edit: ['withRelated', 'id'],
|
||||||
findPage: ['page', 'limit', 'columns', 'status']
|
findPage: ['page', 'limit', 'columns', 'order', 'status']
|
||||||
};
|
};
|
||||||
|
|
||||||
if (validOptions[methodName]) {
|
if (validOptions[methodName]) {
|
||||||
|
@ -330,6 +330,54 @@ describe('Post API', function () {
|
|||||||
done();
|
done();
|
||||||
}).catch(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 () {
|
describe('Read', function () {
|
||||||
|
@ -9,6 +9,14 @@ var testUtils = require('../../utils'),
|
|||||||
|
|
||||||
TagAPI = require('../../../server/api/tags');
|
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 () {
|
describe('Tags API', function () {
|
||||||
// Keep the DB clean
|
// Keep the DB clean
|
||||||
before(testUtils.teardown);
|
before(testUtils.teardown);
|
||||||
@ -259,6 +267,52 @@ describe('Tags API', function () {
|
|||||||
done();
|
done();
|
||||||
}).catch(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 () {
|
describe('Read', function () {
|
||||||
|
@ -139,6 +139,52 @@ describe('Users API', function () {
|
|||||||
done();
|
done();
|
||||||
}).catch(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 () {
|
describe('Read', function () {
|
||||||
|
@ -18,7 +18,7 @@ describe('API Utils', function () {
|
|||||||
describe('Default Options', function () {
|
describe('Default Options', function () {
|
||||||
it('should provide a set of default options', function () {
|
it('should provide a set of default options', function () {
|
||||||
apiUtils.globalDefaultOptions.should.eql(['context', 'include']);
|
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.dataDefaultOptions.should.eql(['data']);
|
||||||
apiUtils.idDefaultOptions.should.eql(['id']);
|
apiUtils.idDefaultOptions.should.eql(['id']);
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user