mirror of
https://github.com/TryGhost/Ghost.git
synced 2025-04-08 02:52:39 -05:00
Added "email post" frontend routing
refs https://github.com/TryGhost/Team/issues/953 - Emails posts should be not explorable by the rest of the frontend similarly to the draft or scheduled posts. Email posts should also keep the content gating, so that specific parts of content can still be gated based on the post's visibility setup - A separate frontend router was chosen to implement this part of the system instead of a moutable express app due to increased complexity to introduce the latter approach. - All "sent" email-only posts will be accessible through the `/email/:slug/` route
This commit is contained in:
parent
2cae064575
commit
74280cfbea
8 changed files with 155 additions and 16 deletions
50
core/frontend/services/routing/EmailRouter.js
Normal file
50
core/frontend/services/routing/EmailRouter.js
Normal file
|
@ -0,0 +1,50 @@
|
|||
const ParentRouter = require('./ParentRouter');
|
||||
const urlUtils = require('../../../shared/url-utils');
|
||||
const controllers = require('./controllers');
|
||||
|
||||
/**
|
||||
* @description Preview Router.
|
||||
*/
|
||||
class EmailRouter extends ParentRouter {
|
||||
constructor(RESOURCE_CONFIG) {
|
||||
super('PreviewRouter');
|
||||
|
||||
this.RESOURCE_CONFIG = RESOURCE_CONFIG.QUERY.email;
|
||||
|
||||
// @NOTE: hardcoded, not configureable
|
||||
this.route = {value: '/email/'};
|
||||
|
||||
this._registerRoutes();
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Register all routes of this router.
|
||||
* @private
|
||||
*/
|
||||
_registerRoutes() {
|
||||
// REGISTER: prepare context
|
||||
this.router().use(this._prepareContext.bind(this));
|
||||
|
||||
// REGISTER: actual email route
|
||||
this.mountRoute(urlUtils.urlJoin(this.route.value, ':slug', ':options?'), controllers.email);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description Prepare context for further middlewares/controllers.
|
||||
* @param {Object} req
|
||||
* @param {Object} res
|
||||
* @param {Function} next
|
||||
* @private
|
||||
*/
|
||||
_prepareContext(req, res, next) {
|
||||
res.routerOptions = {
|
||||
type: 'entry',
|
||||
query: this.RESOURCE_CONFIG,
|
||||
context: ['emailPost']
|
||||
};
|
||||
|
||||
next();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EmailRouter;
|
|
@ -1,6 +1,5 @@
|
|||
const ParentRouter = require('./ParentRouter');
|
||||
const urlUtils = require('../../../shared/url-utils');
|
||||
const labs = require('../../../shared/labs');
|
||||
const controllers = require('./controllers');
|
||||
|
||||
/**
|
||||
|
@ -28,12 +27,6 @@ class PreviewRouter extends ParentRouter {
|
|||
|
||||
// REGISTER: actual preview route
|
||||
this.mountRoute(urlUtils.urlJoin(this.route.value, ':uuid', ':options?'), controllers.preview);
|
||||
|
||||
// NOTE: temporary hack aliasing /email/ route to /p/ preview route
|
||||
// /email/ will become it's own Router once the feature enters beta stage
|
||||
if (labs.isSet('emailOnlyPosts')) {
|
||||
this.mountRoute(urlUtils.urlJoin('/email/', ':uuid', ':options?'), controllers.preview);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
8
core/frontend/services/routing/bootstrap.js
vendored
8
core/frontend/services/routing/bootstrap.js
vendored
|
@ -6,8 +6,10 @@ const CollectionRouter = require('./CollectionRouter');
|
|||
const TaxonomyRouter = require('./TaxonomyRouter');
|
||||
const PreviewRouter = require('./PreviewRouter');
|
||||
const ParentRouter = require('./ParentRouter');
|
||||
const EmailRouter = require('./EmailRouter');
|
||||
const UnsubscribeRouter = require('./UnsubscribeRouter');
|
||||
|
||||
const labs = require('../../../shared/labs');
|
||||
// This emits its own routing events
|
||||
const events = require('../../../server/lib/common/events');
|
||||
|
||||
|
@ -71,6 +73,12 @@ module.exports.start = (apiVersion, routerSettings) => {
|
|||
siteRouter.mountRouter(unsubscribeRouter.router());
|
||||
registry.setRouter('unsubscribeRouter', unsubscribeRouter);
|
||||
|
||||
if (labs.isSet('emailOnlyPosts')) {
|
||||
const emailRouter = new EmailRouter(RESOURCE_CONFIG);
|
||||
siteRouter.mountRouter(emailRouter.router());
|
||||
registry.setRouter('emailRouter', emailRouter);
|
||||
}
|
||||
|
||||
const previewRouter = new PreviewRouter(RESOURCE_CONFIG);
|
||||
siteRouter.mountRouter(previewRouter.router());
|
||||
registry.setRouter('previewRouter', previewRouter);
|
||||
|
|
|
@ -36,6 +36,13 @@ module.exports.QUERY = {
|
|||
preview: {
|
||||
controller: 'preview',
|
||||
resource: 'preview'
|
||||
},
|
||||
email: {
|
||||
controller: 'emailPost',
|
||||
resource: 'email_posts',
|
||||
options: {
|
||||
slug: '%s'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -36,6 +36,13 @@ module.exports.QUERY = {
|
|||
preview: {
|
||||
controller: 'preview',
|
||||
resource: 'preview'
|
||||
},
|
||||
email: {
|
||||
controller: 'emailPost',
|
||||
resource: 'email_posts',
|
||||
options: {
|
||||
slug: '%s'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
|
65
core/frontend/services/routing/controllers/email-post.js
Normal file
65
core/frontend/services/routing/controllers/email-post.js
Normal file
|
@ -0,0 +1,65 @@
|
|||
const debug = require('@tryghost/debug')('services:routing:controllers:emailpost');
|
||||
const config = require('../../../../shared/config');
|
||||
const urlService = require('../../url');
|
||||
const urlUtils = require('../../../../shared/url-utils');
|
||||
const helpers = require('../helpers');
|
||||
|
||||
/**
|
||||
* @description Email Post Controller.
|
||||
* @param {Object} req
|
||||
* @param {Object} res
|
||||
* @param {Function} next
|
||||
* @returns {Promise}
|
||||
*/
|
||||
module.exports = function emailPostController(req, res, next) {
|
||||
debug('emailPostController');
|
||||
|
||||
const api = require('../../proxy').api[res.locals.apiVersion];
|
||||
|
||||
const params = {
|
||||
slug: req.params.slug,
|
||||
include: 'authors,tags',
|
||||
context: {
|
||||
member: res.locals.member
|
||||
}
|
||||
};
|
||||
|
||||
return api[res.routerOptions.query.controller]
|
||||
.read(params)
|
||||
.then(function then(result) {
|
||||
const post = result[res.routerOptions.query.resource][0];
|
||||
|
||||
if (!post) {
|
||||
return next();
|
||||
}
|
||||
|
||||
if (req.params.options && req.params.options.toLowerCase() === 'edit') {
|
||||
// CASE: last param of the url is /edit but admin redirects are disabled
|
||||
if (!config.get('admin:redirects')) {
|
||||
return next();
|
||||
}
|
||||
|
||||
// CASE: last param of the url is /edit, redirect to admin
|
||||
// NOTE: only 'post' resources support email-only mode
|
||||
return urlUtils.redirectToAdmin(302, res, `/#/editor/post/${post.id}`);
|
||||
} else if (req.params.options) {
|
||||
// CASE: unknown options param detected, ignore
|
||||
return next();
|
||||
}
|
||||
|
||||
if (post.status === 'published') {
|
||||
return urlUtils.redirect301(res, urlService.getUrlByResourceId(post.id, {withSubdirectory: true}));
|
||||
}
|
||||
|
||||
if (res.locals.apiVersion !== 'v0.1' && res.locals.apiVersion !== 'v2') {
|
||||
post.access = !!post.html;
|
||||
}
|
||||
|
||||
// @TODO: See helpers/secure
|
||||
helpers.secure(req, post);
|
||||
|
||||
const renderer = helpers.renderEntry(req, res);
|
||||
return renderer(post);
|
||||
})
|
||||
.catch(helpers.handleError(next));
|
||||
};
|
|
@ -15,6 +15,10 @@ module.exports = {
|
|||
return require('./preview');
|
||||
},
|
||||
|
||||
get email() {
|
||||
return require('./email-post');
|
||||
},
|
||||
|
||||
get channel() {
|
||||
return require('./channel');
|
||||
},
|
||||
|
|
|
@ -10,16 +10,15 @@ const cheerio = require('cheerio');
|
|||
const testUtils = require('../../utils');
|
||||
const config = require('../../../core/shared/config');
|
||||
const settingsCache = require('../../../core/shared/settings-cache');
|
||||
const bridge = require('../../../core/bridge');
|
||||
|
||||
describe('Frontend Routing: Email Routes', function () {
|
||||
let request;
|
||||
let emailPosts;
|
||||
|
||||
afterEach(function () {
|
||||
sinon.restore();
|
||||
});
|
||||
|
||||
before(async function () {
|
||||
sinon.stub(bridge, 'getFrontendApiVersion')
|
||||
.returns('v4');
|
||||
const originalSettingsCacheGetFn = settingsCache.get;
|
||||
|
||||
// NOTE: this wacky stubbing can be removed once emailOnlyPosts enters GA stage
|
||||
|
@ -39,6 +38,12 @@ describe('Frontend Routing: Email Routes', function () {
|
|||
|
||||
emailPosts = await testUtils.fixtures.insertPosts([{
|
||||
title: 'I am visible through email route!',
|
||||
status: 'sent',
|
||||
posts_meta: {
|
||||
email_only: true
|
||||
}
|
||||
}, {
|
||||
title: 'I am NOT visible through email route!',
|
||||
status: 'draft',
|
||||
posts_meta: {
|
||||
email_only: true
|
||||
|
@ -51,7 +56,7 @@ describe('Frontend Routing: Email Routes', function () {
|
|||
});
|
||||
|
||||
it('should display email_only post', async function () {
|
||||
const res = await request.get(`/email/${emailPosts[0].get('uuid')}/`)
|
||||
const res = await request.get(`/email/${emailPosts[0].get('slug')}/`)
|
||||
.expect('Content-Type', /html/)
|
||||
.expect(200);
|
||||
|
||||
|
@ -65,13 +70,13 @@ describe('Frontend Routing: Email Routes', function () {
|
|||
should.exist(res.headers.date);
|
||||
});
|
||||
|
||||
it('404s when accessed by slug', function () {
|
||||
return request.get(`/${emailPosts[0].get('slug')}/`)
|
||||
it('404s for draft email only post', function () {
|
||||
return request.get(`/email/${emailPosts[1].get('slug')}/`)
|
||||
.expect(404);
|
||||
});
|
||||
|
||||
it('404s unknown uuids', function () {
|
||||
return request.get('/email/aac6b4f6-e1f3-406c-9247-c94a0496d39f/')
|
||||
it('404s unknown slug', function () {
|
||||
return request.get('/email/random-slug/')
|
||||
.expect(404);
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue