0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-06 22:40:14 -05:00

Merge pull request #5297 from acburdine/frontend-single

Refactor permalink verification in single post controller
This commit is contained in:
Hannah Wolfe 2015-05-25 17:56:02 +01:00
commit 1883bdb496
2 changed files with 101 additions and 124 deletions

View file

@ -4,23 +4,19 @@
/*global require, module */
var moment = require('moment'),
rss = require('../data/xml/rss'),
_ = require('lodash'),
Promise = require('bluebird'),
var _ = require('lodash'),
api = require('../api'),
config = require('../config'),
filters = require('../filters'),
template = require('../helpers/template'),
errors = require('../errors'),
routeMatch = require('path-match')(),
rss = require('../data/xml/rss'),
path = require('path'),
config = require('../config'),
errors = require('../errors'),
filters = require('../filters'),
Promise = require('bluebird'),
template = require('../helpers/template'),
routeMatch = require('path-match')(),
frontendControllers,
staticPostPermalink;
// Cache static post permalink regex
staticPostPermalink = routeMatch('/:slug/:edit?');
staticPostPermalink = routeMatch('/:slug/:edit?');
function getPostPage(options) {
return api.settings.read('postsPerPage').then(function (response) {
@ -277,21 +273,21 @@ frontendControllers = {
},
single: function (req, res, next) {
var path = req.path,
var postPath = req.path,
params,
usingStaticPermalink = false;
api.settings.read('permalinks').then(function (response) {
var permalink = response.settings[0],
var permalink = response.settings[0].value,
editFormat,
postLookup,
match;
editFormat = permalink.value[permalink.value.length - 1] === '/' ? ':edit?' : '/:edit?';
editFormat = permalink.substr(permalink.length - 1) === '/' ? ':edit?' : '/:edit?';
// Convert saved permalink into a path-match function
permalink = routeMatch(permalink.value + editFormat);
match = permalink(path);
permalink = routeMatch(permalink + editFormat);
match = permalink(postPath);
// Check if the path matches the permalink structure.
//
@ -299,7 +295,7 @@ frontendControllers = {
// need to verify it's not a static post,
// and test against that permalink structure.
if (match === false) {
match = staticPostPermalink(path);
match = staticPostPermalink(postPath);
// If there are still no matches then return.
if (match === false) {
// Reject promise chain with type 'NotFound'
@ -320,8 +316,7 @@ frontendControllers = {
return api.posts.read(postLookup);
}).then(function (result) {
var post = result.posts[0],
slugDate = [],
slugFormat = [];
postUrl = (params.edit) ? postPath.replace(params.edit + '/', '') : postPath;
if (!post) {
return next();
@ -352,49 +347,17 @@ frontendControllers = {
if (post.page) {
return render();
}
return next();
}
// If there is an author parameter in the slug, check that the
// post is actually written by the given author\
if (params.author) {
if (post.author.slug === params.author) {
return render();
}
// Check if the url provided with the post object matches req.path
// If it does, render the post
// If not, return 404
if (post.url && post.url === postUrl) {
return render();
} else {
return next();
}
// If there is any date based parameter in the slug
// we will check it against the post published date
// to verify it's correct.
if (params.year || params.month || params.day) {
if (params.year) {
slugDate.push(params.year);
slugFormat.push('YYYY');
}
if (params.month) {
slugDate.push(params.month);
slugFormat.push('MM');
}
if (params.day) {
slugDate.push(params.day);
slugFormat.push('DD');
}
slugDate = slugDate.join('/');
slugFormat = slugFormat.join('/');
if (slugDate === moment(post.published_at).format(slugFormat)) {
return render();
}
return next();
}
return render();
}).catch(function (err) {
// If we've thrown an error message
// of type: 'NotFound' then we found

View file

@ -534,7 +534,8 @@ describe('Frontend Controller', function () {
name: 'Test User',
slug: 'test',
email: 'test@ghost.org'
}
},
url: '/test-static-page/'
}]
}, {
posts: [{
@ -566,7 +567,8 @@ describe('Frontend Controller', function () {
name: 'Test User',
slug: 'test',
email: 'test@ghost.org'
}
},
url: '/about/'
}]
}];
@ -620,7 +622,7 @@ describe('Frontend Controller', function () {
it('it will render custom page template if it exists', function (done) {
var req = {
path: '/' + mockPosts[2].posts[0].slug,
path: '/' + mockPosts[2].posts[0].slug + '/',
route: {
path: '*'
},
@ -634,6 +636,7 @@ describe('Frontend Controller', function () {
done();
}
};
mockPosts[2].posts[0].url = req.path;
frontend.single(req, res, failTest(done));
});
});
@ -647,9 +650,9 @@ describe('Frontend Controller', function () {
}));
});
it('will render static page via /:slug', function (done) {
it('will render static page via /:slug/', function (done) {
var req = {
path: '/' + mockPosts[0].posts[0].slug,
path: '/' + mockPosts[0].posts[0].slug + '/',
route: {
path: '*'
},
@ -669,7 +672,7 @@ describe('Frontend Controller', function () {
it('will NOT render static page via /YYY/MM/DD/:slug', function (done) {
var req = {
path: '/' + ['2012/12/30', mockPosts[0].posts[0].slug].join('/')
path: '/' + ['2012/12/30', mockPosts[0].posts[0].slug].join('/') + '/'
},
res = {
locals: {},
@ -684,7 +687,7 @@ describe('Frontend Controller', function () {
it('will NOT render static page via /:author/:slug', function (done) {
var req = {
path: '/' + ['test', mockPosts[0].posts[0].slug].join('/')
path: '/' + ['test', mockPosts[0].posts[0].slug].join('/') + '/'
},
res = {
locals: {},
@ -699,7 +702,7 @@ describe('Frontend Controller', function () {
it('will redirect static page to admin edit page via /:slug/edit', function (done) {
var req = {
path: '/' + [mockPosts[0].posts[0].slug, 'edit'].join('/')
path: '/' + [mockPosts[0].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -716,7 +719,7 @@ describe('Frontend Controller', function () {
it('will NOT redirect static page to admin edit page via /YYYY/MM/DD/:slug/edit', function (done) {
var req = {
path: '/' + ['2012/12/30', mockPosts[0].posts[0].slug, 'edit'].join('/')
path: '/' + ['2012/12/30', mockPosts[0].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -733,7 +736,7 @@ describe('Frontend Controller', function () {
it('will NOT redirect static page to admin edit page via /:author/:slug/edit', function (done) {
var req = {
path: '/' + ['test', mockPosts[0].posts[0].slug, 'edit'].join('/')
path: '/' + ['test', mockPosts[0].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -760,7 +763,7 @@ describe('Frontend Controller', function () {
it('will render static page via /:slug', function (done) {
var req = {
path: '/' + mockPosts[0].posts[0].slug,
path: '/' + mockPosts[0].posts[0].slug + '/',
route: {
path: '*'
},
@ -780,7 +783,7 @@ describe('Frontend Controller', function () {
it('will NOT render static page via /YYYY/MM/DD/:slug', function (done) {
var req = {
path: '/' + ['2012/12/30', mockPosts[0].posts[0].slug].join('/')
path: '/' + ['2012/12/30', mockPosts[0].posts[0].slug].join('/') + '/'
},
res = {
render: sinon.spy()
@ -794,7 +797,7 @@ describe('Frontend Controller', function () {
it('will redirect static page to admin edit page via /:slug/edit', function (done) {
var req = {
path: '/' + [mockPosts[0].posts[0].slug, 'edit'].join('/')
path: '/' + [mockPosts[0].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -811,7 +814,7 @@ describe('Frontend Controller', function () {
it('will NOT redirect static page to admin edit page via /YYYY/MM/DD/:slug/edit', function (done) {
var req = {
path: '/' + ['2012/12/30', mockPosts[0].posts[0].slug, 'edit'].join('/')
path: '/' + ['2012/12/30', mockPosts[0].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -833,14 +836,16 @@ describe('Frontend Controller', function () {
beforeEach(function () {
apiSettingsStub.withArgs('permalinks').returns(Promise.resolve({
settings: [{
value: '/:slug'
value: '/:slug/'
}]
}));
mockPosts[1].posts[0].url = '/' + mockPosts[1].posts[0].slug + '/';
});
it('will render post via /:slug', function (done) {
it('will render post via /:slug/', function (done) {
var req = {
path: '/' + mockPosts[1].posts[0].slug,
path: '/' + mockPosts[1].posts[0].slug + '/',
route: {
path: '*'
},
@ -861,7 +866,7 @@ describe('Frontend Controller', function () {
it('will NOT render post via /YYYY/MM/DD/:slug', function (done) {
var req = {
path: '/' + ['2012/12/30', mockPosts[1].posts[0].slug].join('/')
path: '/' + ['2012/12/30', mockPosts[1].posts[0].slug].join('/') + '/'
},
res = {
locals: {},
@ -876,7 +881,7 @@ describe('Frontend Controller', function () {
it('will NOT render post via /:author/:slug', function (done) {
var req = {
path: '/' + ['test', mockPosts[1].posts[0].slug].join('/')
path: '/' + ['test', mockPosts[1].posts[0].slug].join('/') + '/'
},
res = {
locals: {},
@ -892,7 +897,7 @@ describe('Frontend Controller', function () {
// Handle Edit append
it('will redirect post to admin edit page via /:slug/edit', function (done) {
var req = {
path: '/' + [mockPosts[1].posts[0].slug, 'edit'].join('/')
path: '/' + [mockPosts[1].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -909,7 +914,7 @@ describe('Frontend Controller', function () {
it('will NOT redirect post to admin edit page via /YYYY/MM/DD/:slug/edit', function (done) {
var req = {
path: '/' + ['2012/12/30', mockPosts[1].posts[0].slug, 'edit'].join('/')
path: '/' + ['2012/12/30', mockPosts[1].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -926,7 +931,7 @@ describe('Frontend Controller', function () {
it('will NOT redirect post to admin edit page via /:author/:slug/edit', function (done) {
var req = {
path: '/' + ['test', mockPosts[1].posts[0].slug, 'edit'].join('/')
path: '/' + ['test', mockPosts[1].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -946,15 +951,18 @@ describe('Frontend Controller', function () {
beforeEach(function () {
apiSettingsStub.withArgs('permalinks').returns(Promise.resolve({
settings: [{
value: '/:year/:month/:day/:slug'
value: '/:year/:month/:day/:slug/'
}]
}));
var date = moment(mockPosts[1].posts[0].published_at).format('YYYY/MM/DD');
mockPosts[1].posts[0].url = '/' + date + '/' + mockPosts[1].posts[0].slug + '/';
});
it('will render post via /YYYY/MM/DD/:slug', function (done) {
it('will render post via /YYYY/MM/DD/:slug/', function (done) {
var date = moment(mockPosts[1].posts[0].published_at).format('YYYY/MM/DD'),
req = {
path: '/' + [date, mockPosts[1].posts[0].slug].join('/'),
path: '/' + [date, mockPosts[1].posts[0].slug].join('/') + '/',
route: {
path: '*'
},
@ -973,10 +981,10 @@ describe('Frontend Controller', function () {
frontend.single(req, res, failTest(done));
});
it('will NOT render post via /YYYY/MM/DD/:slug with non-matching date in url', function (done) {
it('will NOT render post via /YYYY/MM/DD/:slug/ with non-matching date in url', function (done) {
var date = moment(mockPosts[1].published_at).subtract(1, 'days').format('YYYY/MM/DD'),
req = {
path: '/' + [date, mockPosts[1].posts[0].slug].join('/')
path: '/' + [date, mockPosts[1].posts[0].slug].join('/') + '/'
},
res = {
locals: {},
@ -989,9 +997,9 @@ describe('Frontend Controller', function () {
});
});
it('will NOT render post via /:slug', function (done) {
it('will NOT render post via /:slug/', function (done) {
var req = {
path: '/' + mockPosts[1].posts[0].slug
path: '/' + mockPosts[1].posts[0].slug + '/'
},
res = {
locals: {},
@ -1004,9 +1012,9 @@ describe('Frontend Controller', function () {
});
});
it('will NOT render post via /:author/:slug', function (done) {
it('will NOT render post via /:author/:slug/', function (done) {
var req = {
path: '/' + ['test', mockPosts[1].posts[0].slug].join('/')
path: '/' + ['test', mockPosts[1].posts[0].slug].join('/') + '/'
},
res = {
locals: {},
@ -1020,10 +1028,10 @@ describe('Frontend Controller', function () {
});
// Handle Edit append
it('will redirect post to admin edit page via /YYYY/MM/DD/:slug/edit', function (done) {
it('will redirect post to admin edit page via /YYYY/MM/DD/:slug/edit/', function (done) {
var dateFormat = moment(mockPosts[1].posts[0].published_at).format('YYYY/MM/DD'),
req = {
path: '/' + [dateFormat, mockPosts[1].posts[0].slug, 'edit'].join('/')
path: '/' + [dateFormat, mockPosts[1].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -1038,9 +1046,9 @@ describe('Frontend Controller', function () {
frontend.single(req, res, failTest(done));
});
it('will NOT redirect post to admin edit page via /:slug/edit', function (done) {
it('will NOT redirect post to admin edit page via /:slug/edit/', function (done) {
var req = {
path: '/' + [mockPosts[1].posts[0].slug, 'edit'].join('/')
path: '/' + [mockPosts[1].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -1055,9 +1063,9 @@ describe('Frontend Controller', function () {
});
});
it('will NOT redirect post to admin edit page via /:author/:slug/edit', function (done) {
it('will NOT redirect post to admin edit page via /:author/:slug/edit/', function (done) {
var req = {
path: '/' + ['test', mockPosts[1].posts[0].slug, 'edit'].join('/')
path: '/' + ['test', mockPosts[1].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -1077,14 +1085,17 @@ describe('Frontend Controller', function () {
beforeEach(function () {
apiSettingsStub.withArgs('permalinks').returns(Promise.resolve({
settings: [{
value: '/:author/:slug'
value: '/:author/:slug/'
}]
}));
// set post url to permalink-defined url
mockPosts[1].posts[0].url = '/test/' + mockPosts[1].posts[0].slug + '/';
});
it('will render post via /:author/:slug', function (done) {
it('will render post via /:author/:slug/', function (done) {
var req = {
path: '/' + ['test', mockPosts[1].posts[0].slug].join('/'),
path: '/' + ['test', mockPosts[1].posts[0].slug].join('/') + '/',
route: {
path: '*'
},
@ -1103,10 +1114,10 @@ describe('Frontend Controller', function () {
frontend.single(req, res, failTest(done));
});
it('will NOT render post via /YYYY/MM/DD/:slug', function (done) {
it('will NOT render post via /YYYY/MM/DD/:slug/', function (done) {
var date = moment(mockPosts[1].posts[0].published_at).format('YYYY/MM/DD'),
req = {
path: '/' + [date, mockPosts[1].posts[0].slug].join('/')
path: '/' + [date, mockPosts[1].posts[0].slug].join('/') + '/'
},
res = {
locals: {},
@ -1119,9 +1130,9 @@ describe('Frontend Controller', function () {
});
});
it('will NOT render post via /:author/:slug when author does not match post author', function (done) {
it('will NOT render post via /:author/:slug/ when author does not match post author', function (done) {
var req = {
path: '/' + ['test-2', mockPosts[1].posts[0].slug].join('/')
path: '/' + ['test-2', mockPosts[1].posts[0].slug].join('/') + '/'
},
res = {
locals: {},
@ -1134,9 +1145,9 @@ describe('Frontend Controller', function () {
});
});
it('will NOT render post via /:slug', function (done) {
it('will NOT render post via /:slug/', function (done) {
var req = {
path: '/' + mockPosts[1].posts[0].slug
path: '/' + mockPosts[1].posts[0].slug + '/'
},
res = {
locals: {},
@ -1150,9 +1161,9 @@ describe('Frontend Controller', function () {
});
// Handle Edit append
it('will redirect post to admin edit page via /:author/:slug/edit', function (done) {
it('will redirect post to admin edit page via /:author/:slug/edit/', function (done) {
var req = {
path: '/' + ['test', mockPosts[1].posts[0].slug, 'edit'].join('/')
path: '/' + ['test', mockPosts[1].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -1167,10 +1178,10 @@ describe('Frontend Controller', function () {
frontend.single(req, res, failTest(done));
});
it('will NOT redirect post to admin edit page via /YYYY/MM/DD/:slug/edit', function (done) {
it('will NOT redirect post to admin edit page via /YYYY/MM/DD/:slug/edit/', function (done) {
var date = moment(mockPosts[1].posts[0].published_at).format('YYYY/MM/DD'),
req = {
path: '/' + [date, mockPosts[1].posts[0].slug, 'edit'].join('/')
path: '/' + [date, mockPosts[1].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -1185,9 +1196,9 @@ describe('Frontend Controller', function () {
});
});
it('will NOT redirect post to admin edit page /:slug/edit', function (done) {
it('will NOT redirect post to admin edit page /:slug/edit/', function (done) {
var req = {
path: '/' + [mockPosts[1].posts[0].slug, 'edit'].join('/')
path: '/' + [mockPosts[1].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -1207,15 +1218,18 @@ describe('Frontend Controller', function () {
beforeEach(function () {
apiSettingsStub.withArgs('permalinks').returns(Promise.resolve({
settings: [{
value: '/:year/:slug'
value: '/:year/:slug/'
}]
}));
var date = moment(mockPosts[1].posts[0].published_at).format('YYYY');
mockPosts[1].posts[0].url = '/' + date + '/' + mockPosts[1].posts[0].slug + '/';
});
it('will render post via /:year/:slug', function (done) {
it('will render post via /:year/:slug/', function (done) {
var date = moment(mockPosts[1].posts[0].published_at).format('YYYY'),
req = {
path: '/' + [date, mockPosts[1].posts[0].slug].join('/'),
path: '/' + [date, mockPosts[1].posts[0].slug].join('/') + '/',
route: {
path: '*'
},
@ -1234,10 +1248,10 @@ describe('Frontend Controller', function () {
frontend.single(req, res, failTest(done));
});
it('will NOT render post via /YYYY/MM/DD/:slug', function (done) {
it('will NOT render post via /YYYY/MM/DD/:slug/', function (done) {
var date = moment(mockPosts[1].posts[0].published_at).format('YYYY/MM/DD'),
req = {
path: '/' + [date, mockPosts[1].posts[0].slug].join('/')
path: '/' + [date, mockPosts[1].posts[0].slug].join('/') + '/'
},
res = {
locals: {},
@ -1250,10 +1264,10 @@ describe('Frontend Controller', function () {
});
});
it('will NOT render post via /:year/slug when year does not match post year', function (done) {
it('will NOT render post via /:year/slug/ when year does not match post year', function (done) {
var date = moment(mockPosts[1].posts[0].published_at).subtract(1, 'years').format('YYYY'),
req = {
path: '/' + [date, mockPosts[1].posts[0].slug].join('/')
path: '/' + [date, mockPosts[1].posts[0].slug].join('/') + '/'
},
res = {
locals: {},
@ -1266,9 +1280,9 @@ describe('Frontend Controller', function () {
});
});
it('will NOT render post via /:slug', function (done) {
it('will NOT render post via /:slug/', function (done) {
var req = {
path: '/' + mockPosts[1].posts[0].slug
path: '/' + mockPosts[1].posts[0].slug + '/'
},
res = {
locals: {},
@ -1282,10 +1296,10 @@ describe('Frontend Controller', function () {
});
// Handle Edit append
it('will redirect post to admin edit page via /:year/:slug/edit', function (done) {
it('will redirect post to admin edit page via /:year/:slug/edit/', function (done) {
var date = moment(mockPosts[1].posts[0].published_at).format('YYYY'),
req = {
path: '/' + [date, mockPosts[1].posts[0].slug, 'edit'].join('/')
path: '/' + [date, mockPosts[1].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},
@ -1300,9 +1314,9 @@ describe('Frontend Controller', function () {
frontend.single(req, res, failTest(done));
});
it('will NOT redirect post to admin edit page /:slug/edit', function (done) {
it('will NOT redirect post to admin edit page /:slug/edit/', function (done) {
var req = {
path: '/' + [mockPosts[1].posts[0].slug, 'edit'].join('/')
path: '/' + [mockPosts[1].posts[0].slug, 'edit'].join('/') + '/'
},
res = {
locals: {},