mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-01-27 22:49:56 -05:00
abda6e6338
closes #10773 - The refactoring is a substitute for `urlService.utils` used previously throughout the codebase and now extracted into the separate module in Ghost-SDK - Added url-utils stubbing utility for test suites - Some tests had to be refactored to avoid double mocks (when url's are being reset inside of rested 'describe' groups)
205 lines
6.5 KiB
JavaScript
205 lines
6.5 KiB
JavaScript
const debug = require('ghost-ignition').debug('services:routing:collection-router');
|
|
const common = require('../../lib/common');
|
|
const urlUtils = require('../../lib/url-utils');
|
|
const ParentRouter = require('./ParentRouter');
|
|
|
|
const controllers = require('./controllers');
|
|
const middlewares = require('./middlewares');
|
|
const RSSRouter = require('./RSSRouter');
|
|
|
|
/**
|
|
* @description Collection Router for post resource.
|
|
*
|
|
* Fundamental router to define where resources live and how their url structure is.
|
|
*/
|
|
class CollectionRouter extends ParentRouter {
|
|
constructor(mainRoute, object, RESOURCE_CONFIG) {
|
|
super('CollectionRouter');
|
|
|
|
this.RESOURCE_CONFIG = RESOURCE_CONFIG.QUERY.post;
|
|
|
|
this.routerName = mainRoute === '/' ? 'index' : mainRoute.replace(/\//g, '');
|
|
|
|
// NOTE: this.route === index/parent route e.g. /, /podcast/, /magic/
|
|
this.route = {
|
|
value: mainRoute
|
|
};
|
|
|
|
this.rss = object.rss !== false;
|
|
|
|
this.permalinks = {
|
|
originalValue: object.permalink,
|
|
value: object.permalink
|
|
};
|
|
|
|
// @NOTE: see helpers/templates - we use unshift to prepend the templates
|
|
this.templates = (object.templates || []).reverse();
|
|
|
|
this.filter = object.filter;
|
|
this.data = object.data || {query: {}, router: {}};
|
|
this.order = object.order;
|
|
this.limit = object.limit;
|
|
|
|
this.permalinks.getValue = (options) => {
|
|
options = options || {};
|
|
|
|
// @NOTE: url options are only required when registering urls in express.
|
|
// e.g. the UrlService will access the routes and doesn't want to know about possible url options
|
|
if (options.withUrlOptions) {
|
|
return urlUtils.urlJoin(this.permalinks.value, '/:options(edit)?/');
|
|
}
|
|
|
|
return this.permalinks.value;
|
|
};
|
|
|
|
this.context = [this.routerName];
|
|
|
|
debug(this.name, this.route, this.permalinks);
|
|
|
|
this._registerRoutes();
|
|
this._listeners();
|
|
}
|
|
|
|
/**
|
|
* @description Register all routes of this router.
|
|
* @private
|
|
*/
|
|
_registerRoutes() {
|
|
// REGISTER: context middleware for this collection
|
|
this.router().use(this._prepareEntriesContext.bind(this));
|
|
|
|
// REGISTER: collection route e.g. /, /podcast/
|
|
this.mountRoute(this.route.value, controllers.collection);
|
|
|
|
// REGISTER: enable pagination by default
|
|
this.router().param('page', middlewares.pageParam);
|
|
this.mountRoute(urlUtils.urlJoin(this.route.value, 'page', ':page(\\d+)'), controllers.collection);
|
|
|
|
// 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));
|
|
|
|
// REGISTER: page/post resource redirects
|
|
this.router().param('slug', this._respectDominantRouter.bind(this));
|
|
|
|
// REGISTER: permalinks e.g. /:slug/, /podcast/:slug
|
|
this.mountRoute(this.permalinks.getValue({withUrlOptions: true}), controllers.entry);
|
|
|
|
common.events.emit('router.created', this);
|
|
}
|
|
|
|
/**
|
|
* @description Prepare index context for further middlewares/controllers.
|
|
*/
|
|
_prepareEntriesContext(req, res, next) {
|
|
res.routerOptions = {
|
|
type: 'collection',
|
|
filter: this.filter,
|
|
limit: this.limit,
|
|
order: this.order,
|
|
permalinks: this.permalinks.getValue({withUrlOptions: true}),
|
|
resourceType: this.getResourceType(),
|
|
query: this.RESOURCE_CONFIG,
|
|
context: this.context,
|
|
frontPageTemplate: 'home',
|
|
templates: this.templates,
|
|
identifier: this.identifier,
|
|
name: this.routerName,
|
|
data: this.data.query
|
|
};
|
|
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* @description Prepare entry context for further middlewares/controllers.
|
|
*/
|
|
_prepareEntryContext(req, res, next) {
|
|
res.routerOptions.context = ['post'];
|
|
res.routerOptions.type = 'entry';
|
|
next();
|
|
}
|
|
|
|
/**
|
|
* @description This router has listeners to react on changes which happen in Ghost.
|
|
* @private
|
|
*/
|
|
_listeners() {
|
|
/**
|
|
* CASE: timezone changes
|
|
*
|
|
* If your permalink contains a date reference, we have to regenerate the urls.
|
|
*
|
|
* e.g. /:year/:month/:day/:slug/ or /:day/:slug/
|
|
*/
|
|
this._onTimezoneEditedListener = this._onTimezoneEdited.bind(this);
|
|
common.events.on('settings.active_timezone.edited', this._onTimezoneEditedListener);
|
|
}
|
|
|
|
/**
|
|
* @description Helper function to handle a timezone change.
|
|
* @param settingModel
|
|
* @private
|
|
*/
|
|
_onTimezoneEdited(settingModel) {
|
|
const newTimezone = settingModel.attributes.value,
|
|
previousTimezone = settingModel._previousAttributes.value;
|
|
|
|
if (newTimezone === previousTimezone) {
|
|
return;
|
|
}
|
|
|
|
if (this.getPermalinks().getValue().match(/:year|:month|:day/)) {
|
|
debug('_onTimezoneEdited: trigger regeneration');
|
|
|
|
// @NOTE: The connected url generator will listen on this event and regenerate urls.
|
|
this.emit('updated');
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @description Get resource type of this router (always "posts")
|
|
* @returns {string}
|
|
*/
|
|
getResourceType() {
|
|
// @TODO: resourceAlias can be removed? We removed it. Looks like a last left over. Needs double checking.
|
|
return this.RESOURCE_CONFIG.resourceAlias || this.RESOURCE_CONFIG.resource;
|
|
}
|
|
|
|
/**
|
|
* @description Get index route e.g. /, /blog/
|
|
* @param {Object} options
|
|
* @returns {String}
|
|
*/
|
|
getRoute(options) {
|
|
options = options || {};
|
|
|
|
return urlUtils.createUrl(this.route.value, options.absolute, options.secure);
|
|
}
|
|
|
|
/**
|
|
* @description Generate rss url.
|
|
* @param {Object} options
|
|
* @returns {String}
|
|
*/
|
|
getRssUrl(options) {
|
|
if (!this.rss) {
|
|
return null;
|
|
}
|
|
|
|
return urlUtils.createUrl(urlUtils.urlJoin(this.route.value, this.rssRouter.route.value), options.absolute, options.secure);
|
|
}
|
|
|
|
reset() {
|
|
if (this._onTimezoneEditedListener) {
|
|
common.events.removeListener('settings.active_timezone.edited', this._onTimezoneEditedListener);
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = CollectionRouter;
|