Switch RSS to use new filter param

refs #5943, #5091

- split out channel config
- use config.theme instead of api calls to grab title & desc
- wrap rss call in a function which sets channel config for RSS feeds
- change rss `getData` function to use the new multiple-query-handling fetchData functionality
- make sure channelConfig is set in all tests
This commit is contained in:
Hannah Wolfe 2015-10-25 20:00:29 +00:00
parent 91aaf81c08
commit ff7517b801
7 changed files with 180 additions and 128 deletions

View File

@ -0,0 +1,42 @@
var config = require('../../config'),
defaults;
defaults = {
index: {
name: 'home',
route: '/',
firstPageTemplate: 'home'
},
tag: {
name: 'tag',
route: '/' + config.routeKeywords.tag + '/:slug/',
postOptions: {
filter: 'tags:%s'
},
data: {
tag: {
type: 'read',
resource: 'tags',
options: {slug: '%s'}
}
},
slugTemplate: true
},
author: {
name: 'author',
route: '/' + config.routeKeywords.author + '/:slug/',
postOptions: {
filter: 'author:%s'
},
data: {
author: {
type: 'read',
resource: 'users',
options: {slug: '%s'}
}
},
slugTemplate: true
}
};
module.exports = defaults;

View File

@ -86,7 +86,17 @@ function processQuery(query, slugParam) {
* @returns {Promise} response
*/
function fetchData(channelOptions, slugParam) {
return fetchPostsPerPage(channelOptions.postOptions).then(function fetchData(pageOptions) {
// Temporary workaround to make RSS work, moving towards dynamic channels will provide opportunities to
// improve this, I hope :)
function handlePostsPerPage(channelOptions) {
if (channelOptions.isRSS) {
return Promise.resolve({options: channelOptions.postOptions});
} else {
return fetchPostsPerPage(channelOptions.postOptions);
}
}
return handlePostsPerPage(channelOptions).then(function fetchData(pageOptions) {
var postQuery,
props = {};

View File

@ -18,6 +18,7 @@ var _ = require('lodash'),
handleError = require('./error'),
fetchData = require('./fetch-data'),
formatResponse = require('./format-response'),
channelConfig = require('./channel-config'),
setResponseContext = require('./context'),
setRequestIsSecure = require('./secure'),
getActiveThemePaths = require('./theme-paths'),
@ -44,14 +45,13 @@ function renderPost(req, res) {
}
function renderChannel(channelOpts) {
// Ensure we at least have an empty object for postOptions
channelOpts.postOptions = channelOpts.postOptions || {};
return function renderChannel(req, res, next) {
// Parse the parameters we need from the URL
var pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
slugParam = req.params.slug ? safeString(req.params.slug) : undefined;
// Ensure we at least have an empty object for postOptions
channelOpts.postOptions = channelOpts.postOptions || {};
// Set page on postOptions for the query made later
channelOpts.postOptions.page = pageParam;
@ -114,41 +114,27 @@ function renderChannel(channelOpts) {
}
frontendControllers = {
homepage: renderChannel({
name: 'home',
route: '/',
firstPageTemplate: 'home'
}),
tag: renderChannel({
name: 'tag',
route: '/' + config.routeKeywords.tag + '/:slug/',
postOptions: {
filter: 'tags:%s'
},
data: {
tag: {
type: 'read',
resource: 'tags',
options: {slug: '%s'}
}
},
slugTemplate: true
}),
author: renderChannel({
name: 'author',
route: '/' + config.routeKeywords.author + '/:slug/',
postOptions: {
filter: 'author:%s'
},
data: {
author: {
type: 'read',
resource: 'users',
options: {slug: '%s'}
}
},
slugTemplate: true
}),
index: renderChannel(_.cloneDeep(channelConfig.index)),
tag: renderChannel(_.cloneDeep(channelConfig.tag)),
author: renderChannel(_.cloneDeep(channelConfig.author)),
rss: function (req, res, next) {
// Temporary hack, channels will allow us to resolve this better eventually
var tagPattern = new RegExp('^\\/' + config.routeKeywords.tag + '\\/.+'),
authorPattern = new RegExp('^\\/' + config.routeKeywords.author + '\\/.+');
if (tagPattern.test(res.locals.relativeUrl)) {
req.channelConfig = _.cloneDeep(channelConfig.tag);
} else if (authorPattern.test(res.locals.relativeUrl)) {
req.channelConfig = _.cloneDeep(channelConfig.author);
} else {
req.channelConfig = _.cloneDeep(channelConfig.index);
}
req.channelConfig.isRSS = true;
return rss(req, res, next);
},
preview: function preview(req, res, next) {
var params = {
uuid: req.params.uuid,
@ -173,7 +159,6 @@ frontendControllers = {
.then(renderPost(req, res));
}).catch(handleError(next));
},
single: function single(req, res, next) {
var postPath = req.path,
params,
@ -262,7 +247,6 @@ frontendControllers = {
}
}).catch(handleError(next));
},
rss: rss,
private: function private(req, res) {
var defaultPage = path.resolve(config.paths.adminViews, 'private.hbs');
return getActiveThemePaths().then(function then(paths) {

View File

@ -1,14 +1,15 @@
var _ = require('lodash'),
Promise = require('bluebird'),
cheerio = require('cheerio'),
crypto = require('crypto'),
downsize = require('downsize'),
RSS = require('rss'),
url = require('url'),
config = require('../../../config'),
api = require('../../../api'),
filters = require('../../../filters'),
// Really ugly temporary hack for location of things
fetchData = require('../../../controllers/frontend/fetch-data'),
generate,
generateFeed,
getFeedXml,
@ -28,37 +29,30 @@ function handleError(next) {
};
}
function getOptions(req, pageParam, slugParam) {
var options = {};
if (pageParam) { options.page = pageParam; }
if (isTag(req)) { options.tag = slugParam; }
if (isAuthor(req)) { options.author = slugParam; }
options.include = 'author,tags,fields';
return options;
}
function getData(options) {
var ops = {
title: api.settings.read('title'),
description: api.settings.read('description'),
permalinks: api.settings.read('permalinks'),
results: api.posts.browse(options)
function getData(channelOpts, slugParam) {
channelOpts.data = channelOpts.data || {};
channelOpts.data.permalinks = {
type: 'read',
resource: 'settings',
options: 'permalinks'
};
return Promise.props(ops).then(function (result) {
var titleStart = '';
if (options.tag) { titleStart = result.results.meta.filters.tags[0].name + ' - ' || ''; }
if (options.author) { titleStart = result.results.meta.filters.author.name + ' - ' || ''; }
return fetchData(channelOpts, slugParam).then(function (result) {
var response = {},
titleStart = '';
return {
title: titleStart + result.title.settings[0].value,
description: result.description.settings[0].value,
permalinks: result.permalinks.settings[0],
results: result.results
if (result.data.tag) { titleStart = result.data.tag[0].name + ' - ' || ''; }
if (result.data.author) { titleStart = result.data.author[0].name + ' - ' || ''; }
response.title = titleStart + config.theme.title;
response.description = config.theme.description;
response.permalinks = result.data.permalinks[0];
response.results = {
posts: result.posts,
meta: result.meta
};
return response;
});
}
@ -198,15 +192,19 @@ generate = function generate(req, res, next) {
// Initialize RSS
var pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
slugParam = req.params.slug,
baseUrl = getBaseUrl(req, slugParam),
options = getOptions(req, pageParam, slugParam);
baseUrl = getBaseUrl(req, slugParam);
// Ensure we at least have an empty object for postOptions
req.channelConfig.postOptions = req.channelConfig.postOptions || {};
// Set page on postOptions for the query made later
req.channelConfig.postOptions.page = pageParam;
// No negative pages, or page 1
if (isNaN(pageParam) || pageParam < 1 || (req.params.page !== undefined && pageParam === 1)) {
return res.redirect(baseUrl);
}
return getData(options).then(function then(data) {
return getData(req.channelConfig, slugParam).then(function then(data) {
var maxPage = data.results.meta.pagination.pages;
// If page is greater than number of pages we have, redirect to last page

View File

@ -55,8 +55,8 @@ frontendRoutes = function frontendRoutes(middleware) {
});
// Index
indexRouter.route('/').get(frontend.homepage);
indexRouter.route('/' + routeKeywords.page + '/:page/').get(frontend.homepage);
indexRouter.route('/').get(frontend.index);
indexRouter.route('/' + routeKeywords.page + '/:page/').get(frontend.index);
indexRouter.use(rssRouter);
// Tags

View File

@ -37,7 +37,7 @@ describe('Frontend Controller', function () {
};
}
describe('homepage redirects', function () {
describe('index redirects', function () {
var res,
req;
@ -68,7 +68,7 @@ describe('Frontend Controller', function () {
it('Redirects to home if page number is -1', function () {
req.params.page = -1;
frontend.homepage(req, res, null);
frontend.index(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/').should.be.true;
@ -78,7 +78,7 @@ describe('Frontend Controller', function () {
it('Redirects to home if page number is 0', function () {
req.params.page = 0;
frontend.homepage(req, res, null);
frontend.index(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/').should.be.true;
@ -88,7 +88,7 @@ describe('Frontend Controller', function () {
it('Redirects to home if page number is 1', function () {
req.params.page = 1;
frontend.homepage(req, res, null);
frontend.index(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/').should.be.true;
@ -102,7 +102,7 @@ describe('Frontend Controller', function () {
req.params.page = 0;
frontend.homepage(req, res, null);
frontend.index(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/blog/').should.be.true;
@ -116,7 +116,7 @@ describe('Frontend Controller', function () {
req.params.page = 1;
frontend.homepage(req, res, null);
frontend.index(req, res, null);
res.redirect.called.should.be.true;
res.redirect.calledWith('/blog/').should.be.true;
@ -126,7 +126,7 @@ describe('Frontend Controller', function () {
it('Redirects to last page if page number too big', function (done) {
req.params.page = 4;
frontend.homepage(req, res, done).then(function () {
frontend.index(req, res, done).then(function () {
res.redirect.called.should.be.true;
res.redirect.calledWith('/page/3/').should.be.true;
res.render.called.should.be.false;
@ -141,7 +141,7 @@ describe('Frontend Controller', function () {
req.params.page = 4;
frontend.homepage(req, res, done).then(function () {
frontend.index(req, res, done).then(function () {
res.redirect.calledOnce.should.be.true;
res.redirect.calledWith('/blog/page/3/').should.be.true;
res.render.called.should.be.false;
@ -150,7 +150,7 @@ describe('Frontend Controller', function () {
});
});
describe('homepage', function () {
describe('index', function () {
var req, res;
beforeEach(function () {
@ -200,7 +200,7 @@ describe('Frontend Controller', function () {
done();
};
frontend.homepage(req, res, failTest(done));
frontend.index(req, res, failTest(done));
});
it('Renders index.hbs template on 2nd page when home.hbs exists', function (done) {
@ -218,7 +218,7 @@ describe('Frontend Controller', function () {
done();
};
frontend.homepage(req, res, failTest(done));
frontend.index(req, res, failTest(done));
});
it('Renders index.hbs template when home.hbs doesn\'t exist', function (done) {
@ -231,7 +231,7 @@ describe('Frontend Controller', function () {
done();
};
frontend.homepage(req, res, failTest(done));
frontend.index(req, res, failTest(done));
});
});
@ -1165,18 +1165,6 @@ describe('Frontend Controller', function () {
apiUsersStub = sandbox.stub(api.users, 'read').returns(Promise.resolve({}));
apiSettingsStub = sandbox.stub(api.settings, 'read');
apiSettingsStub.withArgs('title').returns(Promise.resolve({
settings: [{
key: 'title',
value: 'Test'
}]
}));
apiSettingsStub.withArgs('description').returns(Promise.resolve({
settings: [{
key: 'description',
value: 'Some Text'
}]
}));
apiSettingsStub.withArgs('permalinks').returns(Promise.resolve({
settings: [{
key: 'permalinks',

View File

@ -6,6 +6,9 @@ var should = require('should'),
_ = require('lodash'),
Promise = require('bluebird'),
testUtils = require('../utils'),
channelConfig = require('../../server/controllers/frontend/channel-config'),
// Things that get overridden
api = require('../../server/api'),
config = require('../../server/config'),
@ -101,6 +104,8 @@ describe('RSS', function () {
done();
};
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, failTest(done));
});
@ -141,6 +146,8 @@ describe('RSS', function () {
done();
};
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, failTest(done));
});
@ -168,6 +175,8 @@ describe('RSS', function () {
done();
};
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, failTest(done));
});
@ -200,6 +209,8 @@ describe('RSS', function () {
done();
};
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, failTest(done));
});
@ -230,26 +241,16 @@ describe('RSS', function () {
done();
};
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, failTest(done));
});
});
describe('dataBuilder', function () {
var apiSettingsStub, apiBrowseStub;
var apiSettingsStub, apiBrowseStub, apiTagStub, apiUserStub;
beforeEach(function () {
apiSettingsStub = sandbox.stub(api.settings, 'read');
apiSettingsStub.withArgs('title').returns(Promise.resolve({
settings: [{
key: 'title',
value: 'Test'
}]
}));
apiSettingsStub.withArgs('description').returns(Promise.resolve({
settings: [{
key: 'description',
value: 'Some Text'
}]
}));
apiSettingsStub.withArgs('permalinks').returns(Promise.resolve({
settings: [{
key: 'permalinks',
@ -257,6 +258,18 @@ describe('RSS', function () {
}]
}));
apiBrowseStub = sandbox.stub(api.posts, 'browse', function () {
return Promise.resolve({posts: [], meta: {pagination: {pages: 3}}});
});
apiTagStub = sandbox.stub(api.tags, 'read', function () {
return Promise.resolve({tags: [{name: 'Magic'}]});
});
apiUserStub = sandbox.stub(api.users, 'read', function () {
return Promise.resolve({users: [{name: 'Joe Blogs'}]});
});
req = {
params: {},
originalUrl: '/rss/'
@ -269,37 +282,39 @@ describe('RSS', function () {
set: sinon.stub()
};
config.set({url: 'http://my-ghost-blog.com'});
config.set({url: 'http://my-ghost-blog.com', theme: {
title: 'Test',
description: 'Some Text'
}});
});
it('should process the data correctly for the index feed', function (done) {
apiBrowseStub = sandbox.stub(api.posts, 'browse', function () {
return Promise.resolve({posts: [], meta: {pagination: {pages: 3}}});
});
res.send = function send(xmlData) {
apiSettingsStub.calledThrice.should.be.true;
apiSettingsStub.calledOnce.should.be.true;
apiBrowseStub.calledOnce.should.be.true;
apiBrowseStub.calledWith({page: 1, include: 'author,tags,fields'}).should.be.true;
xmlData.should.match(/<channel><title><!\[CDATA\[Test\]\]><\/title>/);
done();
};
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, failTest(done));
});
it('should process the data correctly for a tag feed', function (done) {
// setup
apiBrowseStub = sandbox.stub(api.posts, 'browse', function () {
return Promise.resolve({posts: [], meta: {pagination: {pages: 3}, filters: {tags: [{name: 'Magic'}]}}});
});
req.originalUrl = '/tag/magic/rss/';
req.params.slug = 'magic';
req.channelConfig = channelConfig.tag;
req.channelConfig.isRSS = true;
// test
res.send = function send(xmlData) {
apiSettingsStub.calledThrice.should.be.true;
apiSettingsStub.calledOnce.should.be.true;
apiBrowseStub.calledOnce.should.be.true;
apiBrowseStub.calledWith({page: 1, tag: 'magic', include: 'author,tags,fields'}).should.be.true;
apiBrowseStub.calledWith({page: 1, filter: 'tags:magic', include: 'author,tags,fields'}).should.be.true;
apiTagStub.calledOnce.should.be.true;
xmlData.should.match(/<channel><title><!\[CDATA\[Magic - Test\]\]><\/title>/);
done();
};
@ -308,18 +323,17 @@ describe('RSS', function () {
});
it('should process the data correctly for an author feed', function (done) {
// setup
apiBrowseStub = sandbox.stub(api.posts, 'browse', function () {
return Promise.resolve({posts: [], meta: {pagination: {pages: 3}, filters: {author: {name: 'Joe Blogs'}}}});
});
req.originalUrl = '/author/joe/rss/';
req.params.slug = 'joe';
req.channelConfig = channelConfig.author;
req.channelConfig.isRSS = true;
// test
res.send = function send(xmlData) {
apiSettingsStub.calledThrice.should.be.true;
apiSettingsStub.calledOnce.should.be.true;
apiBrowseStub.calledOnce.should.be.true;
apiBrowseStub.calledWith({page: 1, author: 'joe', include: 'author,tags,fields'}).should.be.true;
apiBrowseStub.calledWith({page: 1, filter: 'author:joe', include: 'author,tags,fields'}).should.be.true;
apiUserStub.calledOnce.should.be.true;
xmlData.should.match(/<channel><title><!\[CDATA\[Joe Blogs - Test\]\]><\/title>/);
done();
};
@ -355,6 +369,8 @@ describe('RSS', function () {
results: {posts: [], meta: {pagination: {pages: 1}}}
});
});
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
function secondCall() {
res.send = function sendFirst(data) {
@ -404,6 +420,8 @@ describe('RSS', function () {
it('Redirects to /rss/ if page number is -1', function () {
req = {params: {page: -1}, route: {path: '/rss/:page/'}};
req.originalUrl = req.route.path.replace(':page', req.params.page);
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, null);
@ -415,6 +433,8 @@ describe('RSS', function () {
it('Redirects to /rss/ if page number is 0', function () {
req = {params: {page: 0}, route: {path: '/rss/:page/'}};
req.originalUrl = req.route.path.replace(':page', req.params.page);
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, null);
@ -426,6 +446,8 @@ describe('RSS', function () {
it('Redirects to /rss/ if page number is 1', function () {
req = {params: {page: 1}, route: {path: '/rss/:page/'}};
req.originalUrl = req.route.path.replace(':page', req.params.page);
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, null);
@ -439,6 +461,8 @@ describe('RSS', function () {
req = {params: {page: 0}, route: {path: '/rss/:page/'}};
req.originalUrl = req.route.path.replace(':page', req.params.page);
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, null);
@ -452,6 +476,8 @@ describe('RSS', function () {
req = {params: {page: 1}, route: {path: '/rss/:page/'}};
req.originalUrl = req.route.path.replace(':page', req.params.page);
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, null);
@ -465,6 +491,8 @@ describe('RSS', function () {
req = {params: {page: 4}, route: {path: '/rss/:page/'}};
req.originalUrl = req.route.path.replace(':page', req.params.page);
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, failTest(done)).then(function () {
res.redirect.called.should.be.true;
@ -479,6 +507,8 @@ describe('RSS', function () {
req = {params: {page: 4}, route: {path: '/rss/:page/'}};
req.originalUrl = req.route.path.replace(':page', req.params.page);
req.channelConfig = channelConfig.index;
req.channelConfig.isRSS = true;
rss(req, res, failTest(done)).then(function () {
res.redirect.calledOnce.should.be.true;