Skip to content

Commit

Permalink
Dynamic Routing Beta: Added redirect middleware to ParentRouter
Browse files Browse the repository at this point in the history
refs #9601

- middleware to control dominant router redirects
  • Loading branch information
kirrg001 committed Jun 24, 2018
1 parent 1952c55 commit 51e6286
Show file tree
Hide file tree
Showing 2 changed files with 275 additions and 0 deletions.
44 changes: 44 additions & 0 deletions core/server/services/routing/ParentRouter.js
Expand Up @@ -10,6 +10,7 @@ const debug = require('ghost-ignition').debug('services:routing:ParentRouter'),
EventEmitter = require('events').EventEmitter,
express = require('express'),
_ = require('lodash'),
url = require('url'),
setPrototypeOf = require('setprototypeof'),
security = require('../../lib/security'),
urlService = require('../url'),
Expand Down Expand Up @@ -47,6 +48,49 @@ class ParentRouter extends EventEmitter {
this._router = GhostRouter({mergeParams: true, parent: this});
}

_getSiteRouter(req) {
let siteRouter = null;

req.app._router.stack.every((router) => {
if (router.name === 'SiteRouter') {
siteRouter = router;
return false;
}

return true;
});

return siteRouter;
}

_respectDominantRouter(req, res, next, slug) {
let siteRouter = this._getSiteRouter(req);
let targetRoute = null;

siteRouter.handle.stack.every((router) => {
if (router.handle.parent && router.handle.parent.isRedirectEnabled && router.handle.parent.isRedirectEnabled(this.getType(), slug)) {
targetRoute = router.handle.parent.getRoute();
return false;
}

return true;
});

if (targetRoute) {
debug('_respectDominantRouter');

const matchPath = this.permalinks.getValue().replace(':slug', '[a-zA-Z0-9-_]+');
const toAppend = req.url.replace(new RegExp(matchPath), '');

return urlService.utils.redirect301(res, url.format({
pathname: urlService.utils.createUrl(urlService.utils.urlJoin(targetRoute, toAppend), false, false, true),
search: url.parse(req.originalUrl).search
}));
}

next();
}

mountRouter(path, router) {
if (arguments.length === 1) {
router = path;
Expand Down
231 changes: 231 additions & 0 deletions core/test/unit/services/routing/ParentRouter_spec.js
@@ -1,7 +1,9 @@
const should = require('should'),
sinon = require('sinon'),
configUtils = require('../../../utils/configUtils'),
settingsCache = require('../../../../server/services/settings/cache'),
common = require('../../../../server/lib/common'),
urlService = require('../../../../server/services/url'),
ParentRouter = require('../../../../server/services/routing/ParentRouter'),
sandbox = sinon.sandbox.create();

Expand All @@ -14,7 +16,15 @@ describe('UNIT - services/routing/ParentRouter', function () {
sandbox.stub(common.events, 'emit');
sandbox.stub(common.events, 'on');

sandbox.stub(urlService.utils, 'redirect301');

req = sandbox.stub();
req.app = {
_router: {
stack: []
}
};

res = sandbox.stub();
next = sandbox.stub();

Expand All @@ -23,7 +33,228 @@ describe('UNIT - services/routing/ParentRouter', function () {

afterEach(function () {
sandbox.restore();
configUtils.restore();
});

describe('fn: _getSiteRouter', function () {
it('find site router', function () {
const parentRouter = new ParentRouter();

req.app = {
_router: {
stack: [{
name: 'SiteRouter'
}]
}
};

should.exist(parentRouter._getSiteRouter(req));
});
});

describe('fn: _respectDominantRouter', function () {
it('redirect', function () {
const parentRouter = new ParentRouter();
parentRouter.getType = sandbox.stub().returns('tags');
parentRouter.permalinks = {
getValue: sandbox.stub().returns('/tag/:slug/')
};

req.url = '/tag/bacon/';
req.originalUrl = '/tag/bacon/';

req.app._router.stack = [{
name: 'SiteRouter',
handle: {
stack: [{
name: 'StaticRoutesRouter',
handle: {
parent: {
isRedirectEnabled: sandbox.stub().returns(true),
getRoute: sandbox.stub().returns('/channel/')
}
}
}]
}
}];

parentRouter._respectDominantRouter(req, res, next, 'bacon');
next.called.should.eql(false);
urlService.utils.redirect301.withArgs(res, '/channel/').calledOnce.should.be.true();
});

it('redirect with query params', function () {
const parentRouter = new ParentRouter('tag', '/tag/:slug/');
parentRouter.getType = sandbox.stub().returns('tags');
parentRouter.permalinks = {
getValue: sandbox.stub().returns('/tag/:slug/')
};

req.url = '/tag/bacon/';
req.originalUrl = '/tag/bacon/?a=b';

req.app._router.stack = [{
name: 'SiteRouter',
handle: {
stack: [{
name: 'StaticRoutesRouter',
handle: {
parent: {
isRedirectEnabled: sandbox.stub().returns(true),
getRoute: sandbox.stub().returns('/channel/')
}
}
}]
}
}];

parentRouter._respectDominantRouter(req, res, next, 'bacon');
next.called.should.eql(false);
urlService.utils.redirect301.withArgs(res, '/channel/?a=b').calledOnce.should.be.true();
});

it('redirect rss', function () {
const parentRouter = new ParentRouter('tag', '/tag/:slug/');
parentRouter.getType = sandbox.stub().returns('tags');
parentRouter.permalinks = {
getValue: sandbox.stub().returns('/tag/:slug/')
};

req.url = '/tag/bacon/rss/';
req.originalUrl = '/tag/bacon/rss/';

req.app._router.stack = [{
name: 'SiteRouter',
handle: {
stack: [{
name: 'StaticRoutesRouter',
handle: {
parent: {
isRedirectEnabled: sandbox.stub().returns(true),
getRoute: sandbox.stub().returns('/channel/')
}
}
}]
}
}];

parentRouter._respectDominantRouter(req, res, next, 'bacon');
next.called.should.eql(false);
urlService.utils.redirect301.withArgs(res, '/channel/rss/').calledOnce.should.be.true();
});

it('redirect pagination', function () {
const parentRouter = new ParentRouter('tag', '/tag/:slug/');
parentRouter.getType = sandbox.stub().returns('tags');
parentRouter.permalinks = {
getValue: sandbox.stub().returns('/tag/:slug/')
};

req.url = '/tag/bacon/page/2/';
req.originalUrl = '/tag/bacon/page/2/';

req.app._router.stack = [{
name: 'SiteRouter',
handle: {
stack: [{
name: 'StaticRoutesRouter',
handle: {
parent: {
isRedirectEnabled: sandbox.stub().returns(true),
getRoute: sandbox.stub().returns('/channel/')
}
}
}]
}
}];

parentRouter._respectDominantRouter(req, res, next, 'bacon');
next.called.should.eql(false);
urlService.utils.redirect301.withArgs(res, '/channel/page/2/').calledOnce.should.be.true();
});

it('redirect correctly with subdirectory', function () {
configUtils.set('url', 'http://localhost:7777/blog/');

const parentRouter = new ParentRouter('tag', '/tag/:slug/');
parentRouter.getType = sandbox.stub().returns('tags');
parentRouter.permalinks = {
getValue: sandbox.stub().returns('/tag/:slug/')
};

req.url = '/tag/bacon/';
req.originalUrl = '/blog/tag/bacon/';

req.app._router.stack = [{
name: 'SiteRouter',
handle: {
stack: [{
name: 'StaticRoutesRouter',
handle: {
parent: {
isRedirectEnabled: sandbox.stub().returns(true),
getRoute: sandbox.stub().returns('/channel/')
}
}
}]
}
}];

parentRouter._respectDominantRouter(req, res, next, 'bacon');
next.called.should.eql(false);
urlService.utils.redirect301.withArgs(res, '/blog/channel/').calledOnce.should.be.true();
});

it('no redirect: different data key', function () {
const parentRouter = new ParentRouter('tag', '/tag/:slug/');
parentRouter.getType = sandbox.stub().returns('tags');
parentRouter.permalinks = {
getValue: sandbox.stub().returns('/tag/:slug/')
};

req.app._router.stack = [{
name: 'SiteRouter',
handle: {
stack: [{
name: 'StaticRoutesRouter',
handle: {
parent: {
isRedirectEnabled: sandbox.stub().returns(false),
getRoute: sandbox.stub().returns('/channel/')
}
}
}]
}
}];

parentRouter._respectDominantRouter(req, res, next, 'bacon');
next.called.should.eql(true);
urlService.utils.redirect301.called.should.be.false();
});

it('no redirect: no channel defined', function () {
const parentRouter = new ParentRouter('tag', '/tag/:slug/');
parentRouter.getType = sandbox.stub().returns('tags');
parentRouter.permalinks = {
getValue: sandbox.stub().returns('/tag/:slug/')
};

req.app._router.stack = [{
name: 'SiteRouter',
handle: {
stack: [{
name: 'StaticPagesRouter',
handle: {}
}]
}
}];

parentRouter._respectDominantRouter(req, res, next, 'bacon');
next.called.should.eql(true);
urlService.utils.redirect301.called.should.be.false();
});
});

describe('fn: isRedirectEnabled', function () {
it('no data key defined', function () {
const parentRouter = new ParentRouter();
Expand Down

0 comments on commit 51e6286

Please sign in to comment.