diff --git a/core/server/controllers/frontend/channel-config.js b/core/server/controllers/frontend/channel-config.js new file mode 100644 index 0000000000..6966fd4e63 --- /dev/null +++ b/core/server/controllers/frontend/channel-config.js @@ -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; diff --git a/core/server/controllers/frontend/fetch-data.js b/core/server/controllers/frontend/fetch-data.js index a0d4503394..b63176b262 100644 --- a/core/server/controllers/frontend/fetch-data.js +++ b/core/server/controllers/frontend/fetch-data.js @@ -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 = {}; diff --git a/core/server/controllers/frontend/index.js b/core/server/controllers/frontend/index.js index e92e33ec0e..472086968a 100644 --- a/core/server/controllers/frontend/index.js +++ b/core/server/controllers/frontend/index.js @@ -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) { diff --git a/core/server/data/xml/rss/index.js b/core/server/data/xml/rss/index.js index 0c251b4124..7bdcc3347f 100644 --- a/core/server/data/xml/rss/index.js +++ b/core/server/data/xml/rss/index.js @@ -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 diff --git a/core/server/routes/frontend.js b/core/server/routes/frontend.js index df24c6f9b8..1bf7feb31e 100644 --- a/core/server/routes/frontend.js +++ b/core/server/routes/frontend.js @@ -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 diff --git a/core/test/unit/controllers/frontend/index_spec.js b/core/test/unit/controllers/frontend/index_spec.js index 3ef95b8f60..372bc6ee9b 100644 --- a/core/test/unit/controllers/frontend/index_spec.js +++ b/core/test/unit/controllers/frontend/index_spec.js @@ -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', diff --git a/core/test/unit/rss_spec.js b/core/test/unit/rss_spec.js index f0037ef183..34967f6c91 100644 --- a/core/test/unit/rss_spec.js +++ b/core/test/unit/rss_spec.js @@ -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(/<!\[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;