2013-05-11 20:44:25 +04:00
|
|
|
/**
|
|
|
|
* Main controller for Ghost frontend
|
|
|
|
*/
|
|
|
|
|
|
|
|
/*global require, module */
|
|
|
|
|
2015-05-20 05:00:27 +03:00
|
|
|
var _ = require('lodash'),
|
2015-10-10 20:51:38 +03:00
|
|
|
api = require('../../api'),
|
|
|
|
rss = require('../../data/xml/rss'),
|
2015-05-20 05:00:27 +03:00
|
|
|
path = require('path'),
|
2015-10-10 20:51:38 +03:00
|
|
|
config = require('../../config'),
|
|
|
|
errors = require('../../errors'),
|
|
|
|
filters = require('../../filters'),
|
2015-05-20 05:00:27 +03:00
|
|
|
Promise = require('bluebird'),
|
2015-10-10 20:51:38 +03:00
|
|
|
template = require('../../helpers/template'),
|
2015-01-22 22:21:47 +03:00
|
|
|
routeMatch = require('path-match')(),
|
2015-10-10 20:51:38 +03:00
|
|
|
safeString = require('../../utils/index').safeString,
|
2015-10-21 14:51:01 +03:00
|
|
|
handleError = require('./error'),
|
|
|
|
fetchData = require('./fetch-data'),
|
|
|
|
formatResponse = require('./format-response'),
|
2015-10-25 23:00:29 +03:00
|
|
|
channelConfig = require('./channel-config'),
|
2015-10-10 20:51:38 +03:00
|
|
|
setResponseContext = require('./context'),
|
2015-10-21 14:51:01 +03:00
|
|
|
setRequestIsSecure = require('./secure'),
|
|
|
|
getActiveThemePaths = require('./theme-paths'),
|
2013-05-11 20:44:25 +04:00
|
|
|
|
2014-02-02 09:29:07 +04:00
|
|
|
frontendControllers,
|
2015-05-20 05:00:27 +03:00
|
|
|
staticPostPermalink = routeMatch('/:slug/:edit?');
|
2014-02-02 09:29:07 +04:00
|
|
|
|
2015-04-16 22:40:32 +03:00
|
|
|
/*
|
|
|
|
* Sets the response context around a post and renders it
|
|
|
|
* with the current theme's post view. Used by post preview
|
|
|
|
* and single post methods.
|
|
|
|
* Returns a function that takes the post to be rendered.
|
|
|
|
*/
|
|
|
|
function renderPost(req, res) {
|
2015-05-30 23:18:26 +03:00
|
|
|
return function renderPost(post) {
|
2015-10-30 22:02:06 +03:00
|
|
|
var paths = getActiveThemePaths(req),
|
|
|
|
view = template.getThemeViewForPost(paths, post),
|
|
|
|
response = formatResponse.single(post);
|
2015-04-16 22:40:32 +03:00
|
|
|
|
2015-10-30 22:02:06 +03:00
|
|
|
setResponseContext(req, res, response);
|
|
|
|
res.render(view, response);
|
2015-04-16 22:40:32 +03:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2015-05-05 08:21:29 +03:00
|
|
|
function renderChannel(channelOpts) {
|
|
|
|
return function renderChannel(req, res, next) {
|
2015-10-24 22:04:02 +03:00
|
|
|
// Parse the parameters we need from the URL
|
2013-12-06 12:51:35 +04:00
|
|
|
var pageParam = req.params.page !== undefined ? parseInt(req.params.page, 10) : 1,
|
2015-10-24 22:04:02 +03:00
|
|
|
slugParam = req.params.slug ? safeString(req.params.slug) : undefined;
|
|
|
|
|
2015-10-25 23:00:29 +03:00
|
|
|
// Ensure we at least have an empty object for postOptions
|
|
|
|
channelOpts.postOptions = channelOpts.postOptions || {};
|
2015-10-24 22:04:02 +03:00
|
|
|
// Set page on postOptions for the query made later
|
|
|
|
channelOpts.postOptions.page = pageParam;
|
2014-01-03 04:37:21 +04:00
|
|
|
|
2015-10-24 22:04:02 +03:00
|
|
|
// @TODO this should really use the url building code in config.url
|
2015-05-05 08:21:29 +03:00
|
|
|
function createUrl(page) {
|
|
|
|
var url = config.paths.subdir + channelOpts.route;
|
2014-02-13 07:26:56 +04:00
|
|
|
|
2015-10-24 22:04:02 +03:00
|
|
|
if (slugParam) {
|
|
|
|
url = url.replace(':slug', slugParam);
|
2015-05-05 08:21:29 +03:00
|
|
|
}
|
2014-02-13 07:26:56 +04:00
|
|
|
|
|
|
|
if (page && page > 1) {
|
|
|
|
url += 'page/' + page + '/';
|
2013-08-21 18:05:17 +04:00
|
|
|
}
|
|
|
|
|
2014-02-13 07:26:56 +04:00
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
2015-10-24 22:04:02 +03:00
|
|
|
// If the page parameter isn't something sensible, redirect
|
2014-02-13 07:26:56 +04:00
|
|
|
if (isNaN(pageParam) || pageParam < 1 || (req.params.page !== undefined && pageParam === 1)) {
|
2015-05-05 08:21:29 +03:00
|
|
|
return res.redirect(createUrl());
|
2014-02-13 07:26:56 +04:00
|
|
|
}
|
|
|
|
|
2015-10-24 22:04:02 +03:00
|
|
|
// Call fetchData to get everything we need from the API
|
|
|
|
return fetchData(channelOpts, slugParam).then(function handleResult(result) {
|
2013-08-18 21:41:55 +04:00
|
|
|
// If page is greater than number of pages we have, redirect to last page
|
2015-10-24 22:04:02 +03:00
|
|
|
if (pageParam > result.meta.pagination.pages) {
|
|
|
|
return res.redirect(createUrl(result.meta.pagination.pages));
|
2013-08-18 21:41:55 +04:00
|
|
|
}
|
|
|
|
|
2015-10-24 22:04:02 +03:00
|
|
|
// @TODO: figure out if this can be removed, it's supposed to ensure that absolutely URLs get generated
|
|
|
|
// correctly for the various objects, but I believe it doesn't work and a different approach is needed.
|
|
|
|
setRequestIsSecure(req, result.posts);
|
|
|
|
_.each(result.data, function (data) {
|
|
|
|
setRequestIsSecure(req, data);
|
|
|
|
});
|
2014-02-22 05:25:31 +04:00
|
|
|
|
2015-10-24 22:04:02 +03:00
|
|
|
// @TODO: properly design these filters
|
|
|
|
filters.doFilter('prePostsRender', result.posts, res.locals).then(function then(posts) {
|
2015-10-30 22:02:06 +03:00
|
|
|
var paths = getActiveThemePaths(req),
|
|
|
|
view = 'index';
|
|
|
|
|
|
|
|
// Calculate which template to use to render the data
|
|
|
|
if (channelOpts.firstPageTemplate && paths.hasOwnProperty(channelOpts.firstPageTemplate + '.hbs')) {
|
|
|
|
view = (pageParam > 1) ? 'index' : channelOpts.firstPageTemplate;
|
|
|
|
} else if (channelOpts.slugTemplate) {
|
|
|
|
view = template.getThemeViewForChannel(paths, channelOpts.name, slugParam);
|
|
|
|
} else if (paths.hasOwnProperty(channelOpts.name + '.hbs')) {
|
|
|
|
view = channelOpts.name;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Do final data formatting and then render
|
|
|
|
result.posts = posts;
|
|
|
|
result = formatResponse.channel(result);
|
|
|
|
setResponseContext(req, res);
|
|
|
|
res.render(view, result);
|
2014-07-20 20:32:14 +04:00
|
|
|
});
|
2014-08-17 10:17:23 +04:00
|
|
|
}).catch(handleError(next));
|
2015-05-05 08:21:29 +03:00
|
|
|
};
|
|
|
|
}
|
2014-07-20 20:32:14 +04:00
|
|
|
|
2015-05-05 08:21:29 +03:00
|
|
|
frontendControllers = {
|
2015-10-25 23:00:29 +03:00
|
|
|
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);
|
|
|
|
},
|
|
|
|
|
2015-05-30 23:18:26 +03:00
|
|
|
preview: function preview(req, res, next) {
|
2015-04-16 22:40:32 +03:00
|
|
|
var params = {
|
|
|
|
uuid: req.params.uuid,
|
|
|
|
status: 'all',
|
|
|
|
include: 'author,tags,fields'
|
|
|
|
};
|
|
|
|
|
2015-05-30 23:18:26 +03:00
|
|
|
api.posts.read(params).then(function then(result) {
|
2015-04-16 22:40:32 +03:00
|
|
|
var post = result.posts[0];
|
|
|
|
|
|
|
|
if (!post) {
|
|
|
|
return next();
|
|
|
|
}
|
|
|
|
|
|
|
|
if (post.status === 'published') {
|
|
|
|
return res.redirect(301, config.urlFor('post', {post: post}));
|
|
|
|
}
|
|
|
|
|
2015-10-21 14:51:01 +03:00
|
|
|
setRequestIsSecure(req, post);
|
2015-04-16 22:40:32 +03:00
|
|
|
|
|
|
|
filters.doFilter('prePostsRender', post, res.locals)
|
|
|
|
.then(renderPost(req, res));
|
2015-05-30 23:18:26 +03:00
|
|
|
}).catch(handleError(next));
|
2015-04-16 22:40:32 +03:00
|
|
|
},
|
2015-05-30 23:18:26 +03:00
|
|
|
single: function single(req, res, next) {
|
2015-05-20 05:00:27 +03:00
|
|
|
var postPath = req.path,
|
2014-02-02 09:29:07 +04:00
|
|
|
params,
|
|
|
|
usingStaticPermalink = false;
|
|
|
|
|
2015-05-30 23:18:26 +03:00
|
|
|
api.settings.read('permalinks').then(function then(response) {
|
2015-05-20 05:00:27 +03:00
|
|
|
var permalink = response.settings[0].value,
|
2015-01-22 22:21:47 +03:00
|
|
|
editFormat,
|
|
|
|
postLookup,
|
|
|
|
match;
|
2014-04-28 03:28:50 +04:00
|
|
|
|
2015-05-20 05:00:27 +03:00
|
|
|
editFormat = permalink.substr(permalink.length - 1) === '/' ? ':edit?' : '/:edit?';
|
2014-02-02 09:29:07 +04:00
|
|
|
|
2015-01-22 22:21:47 +03:00
|
|
|
// Convert saved permalink into a path-match function
|
2015-05-20 05:00:27 +03:00
|
|
|
permalink = routeMatch(permalink + editFormat);
|
|
|
|
match = permalink(postPath);
|
2014-02-02 09:29:07 +04:00
|
|
|
|
|
|
|
// Check if the path matches the permalink structure.
|
|
|
|
//
|
|
|
|
// If there are no matches found we then
|
|
|
|
// need to verify it's not a static post,
|
|
|
|
// and test against that permalink structure.
|
2015-01-22 22:21:47 +03:00
|
|
|
if (match === false) {
|
2015-05-20 05:00:27 +03:00
|
|
|
match = staticPostPermalink(postPath);
|
2014-02-02 09:29:07 +04:00
|
|
|
// If there are still no matches then return.
|
2015-01-22 22:21:47 +03:00
|
|
|
if (match === false) {
|
2014-05-05 17:51:21 +04:00
|
|
|
// Reject promise chain with type 'NotFound'
|
2014-08-17 10:17:23 +04:00
|
|
|
return Promise.reject(new errors.NotFoundError());
|
2014-02-02 09:29:07 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
usingStaticPermalink = true;
|
|
|
|
}
|
|
|
|
|
2015-01-22 22:21:47 +03:00
|
|
|
params = match;
|
2014-02-02 09:29:07 +04:00
|
|
|
|
|
|
|
// Sanitize params we're going to use to lookup the post.
|
2015-01-22 22:21:47 +03:00
|
|
|
postLookup = _.pick(params, 'slug', 'id');
|
2014-04-27 20:58:34 +04:00
|
|
|
// Add author, tag and fields
|
|
|
|
postLookup.include = 'author,tags,fields';
|
2014-02-02 09:29:07 +04:00
|
|
|
|
|
|
|
// Query database to find post
|
|
|
|
return api.posts.read(postLookup);
|
2015-05-30 23:18:26 +03:00
|
|
|
}).then(function then(result) {
|
2014-04-16 14:09:03 +04:00
|
|
|
var post = result.posts[0],
|
2015-05-20 05:00:27 +03:00
|
|
|
postUrl = (params.edit) ? postPath.replace(params.edit + '/', '') : postPath;
|
2014-02-02 09:29:07 +04:00
|
|
|
|
|
|
|
if (!post) {
|
|
|
|
return next();
|
|
|
|
}
|
2013-12-30 11:03:29 +04:00
|
|
|
|
|
|
|
function render() {
|
2014-01-20 01:08:39 +04:00
|
|
|
// If we're ready to render the page but the last param is 'edit' then we'll send you to the edit page.
|
2014-08-07 06:02:20 +04:00
|
|
|
if (params.edit) {
|
|
|
|
params.edit = params.edit.toLowerCase();
|
|
|
|
}
|
2014-04-20 08:48:14 +04:00
|
|
|
if (params.edit === 'edit') {
|
2014-07-17 18:33:21 +04:00
|
|
|
return res.redirect(config.paths.subdir + '/ghost/editor/' + post.id + '/');
|
2014-04-20 08:48:14 +04:00
|
|
|
} else if (params.edit !== undefined) {
|
2014-05-05 17:51:21 +04:00
|
|
|
// reject with type: 'NotFound'
|
2014-08-17 10:17:23 +04:00
|
|
|
return Promise.reject(new errors.NotFoundError());
|
2014-01-03 08:32:31 +04:00
|
|
|
}
|
2014-02-22 05:25:31 +04:00
|
|
|
|
2015-10-21 14:51:01 +03:00
|
|
|
setRequestIsSecure(req, post);
|
2014-02-22 05:25:31 +04:00
|
|
|
|
2015-04-16 22:40:32 +03:00
|
|
|
filters.doFilter('prePostsRender', post, res.locals)
|
|
|
|
.then(renderPost(req, res));
|
2013-09-17 04:54:36 +04:00
|
|
|
}
|
|
|
|
|
2014-02-02 09:29:07 +04:00
|
|
|
// If we've checked the path with the static permalink structure
|
|
|
|
// then the post must be a static post.
|
|
|
|
// If it is not then we must return.
|
|
|
|
if (usingStaticPermalink) {
|
2014-06-12 13:44:10 +04:00
|
|
|
if (post.page) {
|
2014-02-02 09:29:07 +04:00
|
|
|
return render();
|
|
|
|
}
|
2014-01-01 19:27:39 +04:00
|
|
|
return next();
|
|
|
|
}
|
|
|
|
|
2015-05-20 05:00:27 +03:00
|
|
|
// 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 {
|
2014-02-02 09:29:07 +04:00
|
|
|
return next();
|
|
|
|
}
|
2015-05-30 23:18:26 +03:00
|
|
|
}).catch(handleError(next));
|
2013-08-28 02:31:43 +04:00
|
|
|
},
|
2015-05-30 23:18:26 +03:00
|
|
|
private: function private(req, res) {
|
2015-10-30 22:02:06 +03:00
|
|
|
var defaultPage = path.resolve(config.paths.adminViews, 'private.hbs'),
|
|
|
|
paths = getActiveThemePaths(req),
|
|
|
|
data = {};
|
2015-05-13 12:26:49 +03:00
|
|
|
|
2015-10-30 22:02:06 +03:00
|
|
|
if (res.error) {
|
|
|
|
data.error = res.error;
|
|
|
|
}
|
|
|
|
|
|
|
|
setResponseContext(req, res);
|
|
|
|
if (paths.hasOwnProperty('private.hbs')) {
|
|
|
|
return res.render('private', data);
|
|
|
|
} else {
|
|
|
|
return res.render(defaultPage, data);
|
|
|
|
}
|
2015-03-26 10:01:39 +03:00
|
|
|
}
|
2013-06-25 15:43:15 +04:00
|
|
|
};
|
2013-05-11 20:44:25 +04:00
|
|
|
|
2013-10-18 21:18:49 +04:00
|
|
|
module.exports = frontendControllers;
|