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

Updated Content API resource ordering to be same as slugs in filter

closes #11994

- Adds support for ordering based on slug filter  that contains a slug-is-in filter. It is applied only to Content API's resources - post, page, tag, author. The order is applied in the same order in which slugs appear in the filter.
- For, example providing following  query parameter filter for any of the above resources: `?filter=slug:[kitchen-sink,bacon,chorizo]`, would filter them by these slugs and order in the same way defined in the filter
- Can be used in handlebars templates in following way: `{{#get "tags" filter="slug:[slugs,of,the,tags,in,order]"}}`
- The property conteining this new order is assigned to `autoOrder` instead of `rawOrder` intentionally. This explicit asstignment would allow distinguishing where the 'orderRaw' comes from the model or the API layer. Apart from  adding necessary context this separation makes it easier to refactor separately model layer and API specific ordering in the future
- This commit also fixes default filtering for `author` resource in Content API. The serializer was never used before as it was missing from `serializers/index.js` module.
This commit is contained in:
Nazar Gargol 2020-07-10 18:33:00 +12:00
parent fe962345af
commit d6267340a1
11 changed files with 129 additions and 11 deletions

View file

@ -1,8 +1,13 @@
const debug = require('ghost-ignition').debug('api:canary:utils:serializers:input:authors');
const slugFilterOrder = require('./utils/slug-filter-order');
const utils = require('../../index');
function setDefaultOrder(frame) {
if (!frame.options.order) {
if (!frame.options.order && frame.options.filter) {
frame.options.autoOrder = slugFilterOrder('users', frame.options.filter);
}
if (!frame.options.order && !frame.options.autoOrder) {
frame.options.order = 'name asc';
}
}

View file

@ -23,6 +23,10 @@ module.exports = {
return require('./users');
},
get authors() {
return require('./authors');
},
get tags() {
return require('./tags');
},

View file

@ -3,6 +3,7 @@ const debug = require('ghost-ignition').debug('api:canary:utils:serializers:inpu
const mapNQLKeyValues = require('@nexes/nql').utils.mapKeyValues;
const mobiledoc = require('../../../../../lib/mobiledoc');
const url = require('./utils/url');
const slugFilterOrder = require('./utils/slug-filter-order');
const localUtils = require('../../index');
const postsMetaSchema = require('../../../../../data/schema').tables.posts_meta;
@ -48,7 +49,11 @@ function setDefaultOrder(frame) {
includesOrderedRelations = _.intersection(orderedRelations, frame.options.withRelated).length > 0;
}
if (!frame.options.order && !includesOrderedRelations) {
if (!frame.options.order && !includesOrderedRelations && frame.options.filter) {
frame.options.autoOrder = slugFilterOrder('posts', frame.options.filter);
}
if (!frame.options.order && !frame.options.autoOrder && !includesOrderedRelations) {
frame.options.order = 'title asc';
}
}

View file

@ -2,6 +2,7 @@ const _ = require('lodash');
const debug = require('ghost-ignition').debug('api:canary:utils:serializers:input:posts');
const mapNQLKeyValues = require('@nexes/nql').utils.mapKeyValues;
const url = require('./utils/url');
const slugFilterOrder = require('./utils/slug-filter-order');
const localUtils = require('../../index');
const mobiledoc = require('../../../../../lib/mobiledoc');
const postsMetaSchema = require('../../../../../data/schema').tables.posts_meta;
@ -48,7 +49,11 @@ function setDefaultOrder(frame) {
includesOrderedRelations = _.intersection(orderedRelations, frame.options.withRelated).length > 0;
}
if (!frame.options.order && !includesOrderedRelations) {
if (!frame.options.order && !includesOrderedRelations && frame.options.filter) {
frame.options.autoOrder = slugFilterOrder('posts', frame.options.filter);
}
if (!frame.options.order && !frame.options.autoOrder && !includesOrderedRelations) {
frame.options.order = 'published_at desc';
}
}

View file

@ -1,10 +1,17 @@
const debug = require('ghost-ignition').debug('api:canary:utils:serializers:input:tags');
const url = require('./utils/url');
const slugFilterOrder = require('./utils/slug-filter-order');
const utils = require('../../index');
function setDefaultOrder(frame) {
if (!frame.options.order) {
frame.options.order = 'name asc';
let defaultOrder = 'name asc';
if (!frame.options.order && frame.options.filter) {
frame.options.autoOrder = slugFilterOrder('tags', frame.options.filter);
}
if (!frame.options.order && !frame.options.autoOrder) {
frame.options.order = defaultOrder;
}
}

View file

@ -0,0 +1,18 @@
const slugFilterOrder = (table, filter) => {
let orderMatch = filter.match(/slug:\s?\[(.*)\]/);
if (orderMatch) {
let orderSlugs = orderMatch[1].split(',');
let order = 'CASE ';
orderSlugs.forEach((slug, index) => {
order += `WHEN \`${table}\`.\`slug\` = '${slug}' THEN ${index} `;
});
order += 'END ASC';
return order;
}
};
module.exports = slugFilterOrder;

View file

@ -696,7 +696,7 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
case 'findAll':
return baseOptions.concat(extraOptions, ['filter', 'columns', 'mongoTransformer']);
case 'findPage':
return baseOptions.concat(extraOptions, ['filter', 'order', 'page', 'limit', 'columns', 'mongoTransformer']);
return baseOptions.concat(extraOptions, ['filter', 'order', 'autoOrder', 'page', 'limit', 'columns', 'mongoTransformer']);
default:
return baseOptions.concat(extraOptions);
}
@ -907,6 +907,8 @@ ghostBookshelf.Model = ghostBookshelf.Model.extend({
if (options.order) {
options.order = this.parseOrderOption(options.order, options.withRelated);
} else if (options.autoOrder) {
options.orderRaw = options.autoOrder;
} else if (this.orderDefaultRaw) {
options.orderRaw = this.orderDefaultRaw(options);
} else if (this.orderDefaultOptions) {

View file

@ -38,4 +38,19 @@ describe('Authors Content API', function () {
localUtils.API.checkResponse(res.body.authors[0], 'author', null, null, ['id', 'name']);
});
});
it('browse authors with slug filter, should order in slug order', function () {
return request.get(localUtils.API.getApiQuery(`authors/?key=${validKey}&filter=slug:[joe-bloggs,ghost,slimer-mcectoplasm]`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
jsonResponse.authors.should.be.an.Array().with.lengthOf(3);
jsonResponse.authors[0].slug.should.equal('joe-bloggs');
jsonResponse.authors[1].slug.should.equal('ghost');
jsonResponse.authors[2].slug.should.equal('slimer-mcectoplasm');
});
});
});

View file

@ -9,6 +9,8 @@ const ghost = testUtils.startGhost;
let request;
describe('api/canary/content/pages', function () {
const key = localUtils.getValidKey();
before(function () {
return ghost()
.then(function () {
@ -24,7 +26,6 @@ describe('api/canary/content/pages', function () {
});
it('Can browse pages with page:false', function () {
const key = localUtils.getValidKey();
return request.get(localUtils.API.getApiQuery(`pages/?key=${key}&filter=page:false`))
.set('Origin', testUtils.API.getURL())
.expect('Content-Type', /json/)
@ -43,9 +44,20 @@ describe('api/canary/content/pages', function () {
});
});
it('can\'t read post', function () {
const key = localUtils.getValidKey();
it('browse pages with slug filter, should order in slug order', function () {
return request.get(localUtils.API.getApiQuery(`pages/?key=${key}&filter=slug:[static-page-test]`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
jsonResponse.pages.should.be.an.Array().with.lengthOf(1);
jsonResponse.pages[0].slug.should.equal('static-page-test');
});
});
it('can\'t read post', function () {
return request
.get(localUtils.API.getApiQuery(`pages/${testUtils.DataGenerator.Content.posts[0].id}/?key=${key}`))
.set('Origin', testUtils.API.getURL())

View file

@ -179,6 +179,36 @@ describe('api/canary/content/posts', function () {
});
});
it('browse posts with slug filter, should order in slug order', function () {
return request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&filter=slug:[themes,ghostly-kitchen-sink,the-editor]`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
jsonResponse.posts.should.be.an.Array().with.lengthOf(3);
jsonResponse.posts[0].slug.should.equal('themes');
jsonResponse.posts[1].slug.should.equal('ghostly-kitchen-sink');
jsonResponse.posts[2].slug.should.equal('the-editor');
});
});
it('browse posts with slug filter should order taking order parameter into account', function () {
return request.get(localUtils.API.getApiQuery(`posts/?key=${validKey}&order=slug%20DESC&filter=slug:[themes,ghostly-kitchen-sink,the-editor]`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
jsonResponse.posts.should.be.an.Array().with.lengthOf(3);
jsonResponse.posts[0].slug.should.equal('themes');
jsonResponse.posts[1].slug.should.equal('the-editor');
jsonResponse.posts[2].slug.should.equal('ghostly-kitchen-sink');
});
});
it('ensure origin header on redirect is not getting lost', function (done) {
// NOTE: force a redirect to the admin url
configUtils.set('admin:url', 'http://localhost:9999');

View file

@ -9,6 +9,8 @@ const ghost = testUtils.startGhost;
let request;
describe('api/canary/content/tags', function () {
const validKey = localUtils.getValidKey();
before(function () {
return ghost()
.then(function () {
@ -23,8 +25,6 @@ describe('api/canary/content/tags', function () {
configUtils.restore();
});
const validKey = localUtils.getValidKey();
it('can read tags with fields', function () {
return request
.get(localUtils.API.getApiQuery(`tags/${testUtils.DataGenerator.Content.tags[0].id}/?key=${validKey}&fields=name,slug`))
@ -36,4 +36,19 @@ describe('api/canary/content/tags', function () {
localUtils.API.checkResponse(res.body.tags[0], 'tag', null, null, ['id', 'name', 'slug']);
});
});
it('browse tags with slug filter, should order in slug order', function () {
return request.get(localUtils.API.getApiQuery(`tags/?key=${validKey}&filter=slug:[kitchen-sink,bacon,chorizo]`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
const jsonResponse = res.body;
jsonResponse.tags.should.be.an.Array().with.lengthOf(3);
jsonResponse.tags[0].slug.should.equal('kitchen-sink');
jsonResponse.tags[1].slug.should.equal('bacon');
jsonResponse.tags[2].slug.should.equal('chorizo');
});
});
});