Skip to content

Commit 0e2709c

Browse files
committed
[API] Retrieve next and previous post
closes #4262 - implementation based on #1545 - added integration test. Modified mocked posts because code requires published_at timestamps to be different. - fixed 2 broken tests that depended on mocked posts to have "new Date()" as their timestamps - added checks to only query db if next/previous post requested
1 parent 04180bc commit 0e2709c

File tree

6 files changed

+81
-9
lines changed

6 files changed

+81
-9
lines changed

core/server/api/posts.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ var Promise = require('bluebird'),
88
utils = require('./utils'),
99

1010
docName = 'posts',
11-
allowedIncludes = ['created_by', 'updated_by', 'published_by', 'author', 'tags', 'fields'],
11+
allowedIncludes = ['created_by', 'updated_by', 'published_by', 'author', 'tags', 'fields', 'next', 'previous'],
1212
posts;
1313

1414
// ## Helpers

core/server/models/post.js

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -525,6 +525,9 @@ Post = ghostBookshelf.Model.extend({
525525
findOne: function (data, options) {
526526
options = options || {};
527527

528+
var withNext = _.contains(options.include, 'next'),
529+
withPrev = _.contains(options.include, 'previous');
530+
528531
data = _.extend({
529532
status: 'published'
530533
}, data || {});
@@ -533,10 +536,50 @@ Post = ghostBookshelf.Model.extend({
533536
delete data.status;
534537
}
535538

536-
// Add related objects
537-
options.withRelated = _.union(options.withRelated, options.include);
539+
// Add related objects, excluding next and previous as they are not real db objects
540+
options.withRelated = _.union(options.withRelated, _.pull([].concat(options.include), 'next', 'previous'));
541+
542+
return ghostBookshelf.Model.findOne.call(this, data, options).then(function (post) {
543+
if ((withNext || withPrev) && post && !post.page) {
544+
var postData = post.toJSON(),
545+
publishedAt = postData.published_at,
546+
prev,
547+
next;
548+
549+
if (withNext) {
550+
next = Post.forge().query(function (qb) {
551+
qb.where('status', '=', 'published')
552+
.andWhere('page', '=', 0)
553+
.andWhere('published_at', '>', publishedAt)
554+
.orderBy('published_at', 'asc')
555+
.limit(1);
556+
}).fetch();
557+
}
558+
559+
if (withPrev) {
560+
prev = Post.forge().query(function (qb) {
561+
qb.where('status', '=', 'published')
562+
.andWhere('page', '=', 0)
563+
.andWhere('published_at', '<', publishedAt)
564+
.orderBy('published_at', 'desc')
565+
.limit(1);
566+
}).fetch();
567+
}
538568

539-
return ghostBookshelf.Model.findOne.call(this, data, options);
569+
return Promise.join(next, prev)
570+
.then(function (nextAndPrev) {
571+
if (nextAndPrev[0]) {
572+
post.relations.next = nextAndPrev[0];
573+
}
574+
if (nextAndPrev[1]) {
575+
post.relations.previous = nextAndPrev[1];
576+
}
577+
return post;
578+
});
579+
}
580+
581+
return post;
582+
});
540583
},
541584

542585
/**

core/test/functional/routes/api/posts_test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -235,6 +235,32 @@ describe('Post API', function () {
235235
});
236236
});
237237

238+
it('can retrieve next and previous posts', function (done) {
239+
request.get(testUtils.API.getApiQuery('posts/3/?include=next,previous'))
240+
.set('Authorization', 'Bearer ' + accesstoken)
241+
.expect('Content-Type', /json/)
242+
.expect('Cache-Control', testUtils.cacheRules['private'])
243+
.expect(200)
244+
.end(function (err, res) {
245+
if (err) {
246+
return done(err);
247+
}
248+
249+
should.not.exist(res.headers['x-cache-invalidate']);
250+
var jsonResponse = res.body;
251+
jsonResponse.should.exist;
252+
jsonResponse.posts.should.exist;
253+
testUtils.API.checkResponse(jsonResponse.posts[0], 'post', ['next', 'previous']);
254+
jsonResponse.posts[0].page.should.not.be.ok;
255+
256+
jsonResponse.posts[0].next.should.be.an.Object;
257+
testUtils.API.checkResponse(jsonResponse.posts[0].next, 'post');
258+
jsonResponse.posts[0].previous.should.be.an.Object;
259+
testUtils.API.checkResponse(jsonResponse.posts[0].previous, 'post');
260+
done();
261+
});
262+
});
263+
238264
it('can retrieve a static page', function (done) {
239265
request.get(testUtils.API.getApiQuery('posts/7/'))
240266
.set('Authorization', 'Bearer ' + accesstoken)

core/test/integration/model/model_posts_spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ describe('Post Model', function () {
162162

163163
it('can findOne, returning a dated permalink', function (done) {
164164
var firstPost = 1,
165-
today = new Date(),
165+
today = testUtils.DataGenerator.Content.posts[0].published_at,
166166
dd = ('0' + today.getDate()).slice(-2),
167167
mm = ('0' + (today.getMonth() + 1)).slice(-2),
168168
yyyy = today.getFullYear(),

core/test/unit/config_spec.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -242,7 +242,7 @@ describe('Config', function () {
242242
var permalinkSetting = '/:year/:month/:day/:slug/',
243243
/*jshint unused:false*/
244244
testData = testUtils.DataGenerator.Content.posts[2],
245-
today = new Date(),
245+
today = testData.published_at,
246246
dd = ('0' + today.getDate()).slice(-2),
247247
mm = ('0' + (today.getMonth() + 1)).slice(-2),
248248
yyyy = today.getFullYear(),

core/test/utils/fixtures/data-generator.js

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,18 +9,21 @@ DataGenerator.Content = {
99
{
1010
title: "HTML Ipsum",
1111
slug: "html-ipsum",
12-
markdown: "<h1>HTML Ipsum Presents</h1><p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href=\"#\">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p><h2>Header Level 2</h2><ol><li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li><li>Aliquam tincidunt mauris eu risus.</li></ol><blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote><h3>Header Level 3</h3><ul><li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li><li>Aliquam tincidunt mauris eu risus.</li></ul><pre><code>#header h1 a{display: block;width: 300px;height: 80px;}</code></pre>"
12+
markdown: "<h1>HTML Ipsum Presents</h1><p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href=\"#\">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p><h2>Header Level 2</h2><ol><li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li><li>Aliquam tincidunt mauris eu risus.</li></ol><blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote><h3>Header Level 3</h3><ul><li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li><li>Aliquam tincidunt mauris eu risus.</li></ul><pre><code>#header h1 a{display: block;width: 300px;height: 80px;}</code></pre>",
13+
published_at: new Date("2015-01-01")
1314
},
1415
{
1516
title: "Ghostly Kitchen Sink",
1617
slug: "ghostly-kitchen-sink",
17-
markdown: "<h1>HTML Ipsum Presents</h1><p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href=\"#\">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p><h2>Header Level 2</h2><ol><li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li><li>Aliquam tincidunt mauris eu risus.</li></ol><blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote><h3>Header Level 3</h3><ul><li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li><li>Aliquam tincidunt mauris eu risus.</li></ul><pre><code>#header h1 a{display: block;width: 300px;height: 80px;}</code></pre>"
18+
markdown: "<h1>HTML Ipsum Presents</h1><p><strong>Pellentesque habitant morbi tristique</strong> senectus et netus et malesuada fames ac turpis egestas. Vestibulum tortor quam, feugiat vitae, ultricies eget, tempor sit amet, ante. Donec eu libero sit amet quam egestas semper. <em>Aenean ultricies mi vitae est.</em> Mauris placerat eleifend leo. Quisque sit amet est et sapien ullamcorper pharetra. Vestibulum erat wisi, condimentum sed, <code>commodo vitae</code>, ornare sit amet, wisi. Aenean fermentum, elit eget tincidunt condimentum, eros ipsum rutrum orci, sagittis tempus lacus enim ac dui. <a href=\"#\">Donec non enim</a> in turpis pulvinar facilisis. Ut felis.</p><h2>Header Level 2</h2><ol><li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li><li>Aliquam tincidunt mauris eu risus.</li></ol><blockquote><p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus magna. Cras in mi at felis aliquet congue. Ut a est eget ligula molestie gravida. Curabitur massa. Donec eleifend, libero at sagittis mollis, tellus est malesuada tellus, at luctus turpis elit sit amet quam. Vivamus pretium ornare est.</p></blockquote><h3>Header Level 3</h3><ul><li>Lorem ipsum dolor sit amet, consectetuer adipiscing elit.</li><li>Aliquam tincidunt mauris eu risus.</li></ul><pre><code>#header h1 a{display: block;width: 300px;height: 80px;}</code></pre>",
19+
published_at: new Date("2015-01-02")
1820
},
1921
{
2022
title: "Short and Sweet",
2123
slug: "short-and-sweet",
2224
markdown: "## testing\n\nmctesters\n\n- test\n- line\n- items",
23-
html: "<h2 id=\"testing\">testing</h2>\n\n<p>mctesters</p>\n\n<ul>\n<li>test</li>\n<li>line</li>\n<li>items</li>\n</ul>"
25+
html: "<h2 id=\"testing\">testing</h2>\n\n<p>mctesters</p>\n\n<ul>\n<li>test</li>\n<li>line</li>\n<li>items</li>\n</ul>",
26+
published_at: new Date("2015-01-03")
2427
},
2528
{
2629
title: "Not finished yet",

0 commit comments

Comments
 (0)