diff --git a/core/server/services/routing/CollectionRouter.js b/core/server/services/routing/CollectionRouter.js index 070fde2dfa..0d5fa66611 100644 --- a/core/server/services/routing/CollectionRouter.js +++ b/core/server/services/routing/CollectionRouter.js @@ -19,6 +19,8 @@ class CollectionRouter extends ParentRouter { value: mainRoute }; + this.rss = object.rss !== false; + this.permalinks = { originalValue: object.permalink, value: object.permalink @@ -72,10 +74,11 @@ class CollectionRouter extends ParentRouter { this.router().param('page', middlewares.pageParam); this.mountRoute(urlService.utils.urlJoin(this.route.value, 'page', ':page(\\d+)'), controllers.collection); - this.rssRouter = new RSSRouter(); - - // REGISTER: enable rss by default - this.mountRouter(this.route.value, this.rssRouter.router()); + // REGISTER: is rss enabled? + if (this.rss) { + this.rssRouter = new RSSRouter(); + this.mountRouter(this.route.value, this.rssRouter.router()); + } // REGISTER: context middleware for entries this.router().use(this._prepareEntryContext.bind(this)); @@ -151,6 +154,10 @@ class CollectionRouter extends ParentRouter { } getRssUrl(options) { + if (!this.rss) { + return null; + } + return urlService.utils.createUrl(urlService.utils.urlJoin(this.route.value, this.rssRouter.route.value), options.absolute, options.secure); } diff --git a/core/server/services/routing/StaticRoutesRouter.js b/core/server/services/routing/StaticRoutesRouter.js index d0f8b3d861..d7f35dbe99 100644 --- a/core/server/services/routing/StaticRoutesRouter.js +++ b/core/server/services/routing/StaticRoutesRouter.js @@ -16,6 +16,7 @@ class StaticRoutesRouter extends ParentRouter { debug(this.route.value, this.templates); if (this.isChannel(object)) { + this.rss = object.rss !== false; this.filter = object.filter; this.limit = object.limit; this.order = object.order; @@ -26,6 +27,7 @@ class StaticRoutesRouter extends ParentRouter { debug(this.route.value, this.templates, this.filter, this.data); this._registerChannelRoutes(); } else { + this.contentType = object.content_type; debug(this.route.value, this.templates); this._registerStaticRoute(); } @@ -34,9 +36,11 @@ class StaticRoutesRouter extends ParentRouter { _registerChannelRoutes() { this.router().use(this._prepareChannelContext.bind(this)); - // REGISTER: enable rss by default - this.rssRouter = new RSSRouter(); - this.mountRouter(this.route.value, this.rssRouter.router()); + // REGISTER: is rss enabled? + if (this.rss) { + this.rssRouter = new RSSRouter(); + this.mountRouter(this.route.value, this.rssRouter.router()); + } // REGISTER: channel route this.mountRoute(this.route.value, controllers[this.controller]); @@ -76,7 +80,8 @@ class StaticRoutesRouter extends ParentRouter { templates: this.templates, defaultTemplate: 'default', data: this.data.query, - context: [] + context: [], + contentType: this.contentType }; next(); diff --git a/core/server/services/routing/helpers/renderer.js b/core/server/services/routing/helpers/renderer.js index 18f4f23ec5..1b14ce8275 100644 --- a/core/server/services/routing/helpers/renderer.js +++ b/core/server/services/routing/helpers/renderer.js @@ -13,5 +13,11 @@ module.exports = function renderer(req, res, data) { debug('Rendering template: ' + res._template + ' for: ' + req.originalUrl); debug('res.locals', res.locals); + if (res.routerOptions && res.routerOptions.contentType) { + if (res.routerOptions.templates.indexOf(res._template) !== -1) { + res.type(res.routerOptions.contentType); + } + } + res.render(res._template, data); }; diff --git a/core/test/integration/web/site_spec.js b/core/test/integration/web/site_spec.js index 91d8238490..bc9a485a14 100644 --- a/core/test/integration/web/site_spec.js +++ b/core/test/integration/web/site_spec.js @@ -1496,4 +1496,154 @@ describe('Integration - Web - Site', function () { }); }); }); + + describe('extended routes.yaml (5): rss override', function () { + before(function () { + sandbox.stub(settingsService, 'get').returns({ + routes: { + '/about/': 'about', + '/podcast/rss/': { + templates: ['podcast/rss'], + content_type: 'xml' + }, + '/cooking/': { + controller: 'channel', + rss: false + }, + '/flat/': { + controller: 'channel' + } + }, + + collections: { + '/podcast/': { + permalink: '/:slug/', + filter: 'featured:true', + templates: ['home'], + rss: false + }, + '/music/': { + permalink: '/:slug/', + rss: false + }, + '/': { + permalink: '/:slug/' + } + }, + + taxonomies: {} + }); + + testUtils.integrationTesting.urlService.resetGenerators(); + testUtils.integrationTesting.defaultMocks(sandbox, {theme: 'test-theme'}); + + return testUtils.integrationTesting.initGhost() + .then(function () { + app = siteApp(); + + return testUtils.integrationTesting.urlService.waitTillFinished(); + }); + }); + + beforeEach(function () { + testUtils.integrationTesting.overrideGhostConfig(configUtils); + }); + + afterEach(function () { + configUtils.restore(); + }); + + after(function () { + sandbox.restore(); + }); + + it('serve /rss/', function () { + const req = { + secure: true, + method: 'GET', + url: '/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); + + it('serve /music/rss/', function () { + const req = { + secure: true, + method: 'GET', + url: '/music/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + }); + }); + + it('serve /cooking/rss/', function () { + const req = { + secure: true, + method: 'GET', + url: '/cooking/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(404); + }); + }); + + it('serve /flat/rss/', function () { + const req = { + secure: true, + method: 'GET', + url: '/flat/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + }); + }); + + it('serve /podcast/rss/', function () { + const req = { + secure: true, + method: 'GET', + url: '/podcast/rss/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + response.statusCode.should.eql(200); + response.template.should.eql('podcast/rss'); + response.headers['content-type'].should.eql('text/xml; charset=utf-8'); + response.body.match(//g).length.should.eql(2); + }); + }); + + it('serve /podcast/', function () { + const req = { + secure: true, + method: 'GET', + url: '/podcast/', + host: 'example.com' + }; + + return testUtils.mocks.express.invoke(app, req) + .then(function (response) { + const $ = cheerio.load(response.body); + response.statusCode.should.eql(200); + $('head link')[2].attribs.href.should.eql('https://127.0.0.1:2369/rss/'); + }); + }); + }); }); diff --git a/core/test/unit/services/routing/StaticRoutesRouter_spec.js b/core/test/unit/services/routing/StaticRoutesRouter_spec.js index f4981ab7b8..6f73afdc10 100644 --- a/core/test/unit/services/routing/StaticRoutesRouter_spec.js +++ b/core/test/unit/services/routing/StaticRoutesRouter_spec.js @@ -86,7 +86,8 @@ describe('UNIT - services/routing/StaticRoutesRouter', function () { templates: [], defaultTemplate: 'default', context: [], - data: {} + data: {}, + contentType: undefined }); should.not.exist(res.locals.slug); }); diff --git a/core/test/utils/fixtures/themes/test-theme/home.hbs b/core/test/utils/fixtures/themes/test-theme/home.hbs index e92d84ba66..0c0f8fa074 100644 --- a/core/test/utils/fixtures/themes/test-theme/home.hbs +++ b/core/test/utils/fixtures/themes/test-theme/home.hbs @@ -1 +1,8 @@ -home.hbs \ No newline at end of file + + + {{ghost_head}} + + +home.hbs + + \ No newline at end of file diff --git a/core/test/utils/fixtures/themes/test-theme/podcast/rss.hbs b/core/test/utils/fixtures/themes/test-theme/podcast/rss.hbs new file mode 100644 index 0000000000..1b76e7115f --- /dev/null +++ b/core/test/utils/fixtures/themes/test-theme/podcast/rss.hbs @@ -0,0 +1,10 @@ + + + {{@blog.title}} + {{#get "posts" filter="featured:true" limit="20"}} + {{#foreach posts}} + {{url}} + {{/foreach}} + {{/get}} + + \ No newline at end of file diff --git a/package.json b/package.json index 897b1d3bd1..89614aeded 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,7 @@ "ghost-storage-base": "0.0.3", "glob": "5.0.15", "got": "7.1.0", - "gscan": "1.4.3", + "gscan": "1.5.0", "html-to-text": "3.3.0", "image-size": "0.6.3", "intl": "1.2.5", diff --git a/yarn.lock b/yarn.lock index 29450751d7..df2efe08a4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2533,9 +2533,9 @@ grunt@~0.4.0: underscore.string "~2.2.1" which "~1.0.5" -gscan@1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/gscan/-/gscan-1.4.3.tgz#ebea3c78106f4d8562225d46754d0a41c3e0a348" +gscan@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/gscan/-/gscan-1.5.0.tgz#b94446ab1ebc058c36e26f086ab0c0b97634832f" dependencies: "@tryghost/extract-zip" "1.6.6" bluebird "^3.4.6"