Added /unsubscribe/ route to the front-end (#11394)

no issue

- adds new router to the frontend for handling unsubscribe
- default template lives in `core/server/frontend/views/unsubscribe.hbs`
- `{{error}}` is present and contains the error message when unsubscribe fails
- `{{member}}` is present and contains the member email
- updated unsubscribe url to match the new format
This commit is contained in:
Kevin Ansfield 2019-11-15 09:36:49 +00:00 committed by GitHub
parent 52eb3ca9da
commit ee47dd4dae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 92 additions and 23 deletions

View File

@ -0,0 +1,27 @@
const ParentRouter = require('./ParentRouter');
const controllers = require('./controllers');
/**
* @description Unsubscribe Router.
*
* "/unsubscribe/" -> Unsubscribe Router
*/
class UnsubscribeRouter extends ParentRouter {
constructor() {
super('UnsubscribeRouter');
// @NOTE: hardcoded, not configurable
this.route = {value: '/unsubscribe/'};
this._registerRoutes();
}
/**
* @description Register all routes of this router.
* @private
*/
_registerRoutes() {
this.mountRoute(this.route.value, controllers.unsubscribe);
}
}
module.exports = UnsubscribeRouter;

View File

@ -9,6 +9,7 @@ const CollectionRouter = require('./CollectionRouter');
const TaxonomyRouter = require('./TaxonomyRouter');
const PreviewRouter = require('./PreviewRouter');
const ParentRouter = require('./ParentRouter');
const UnsubscribeRouter = require('./UnsubscribeRouter');
const registry = require('./registry');
let siteRouter;
@ -50,7 +51,7 @@ module.exports.init = (options = {start: false}) => {
* The routers are created in a specific order. This order defines who can get a resource first or
* who can dominant other routers.
*
* 1. Preview Router: Is the strongest and is an inbuilt feature, which you can never override.
* 1. Preview + Unsubscribe Routers: Strongest inbuilt features, which you can never override.
* 2. Static Routes: Very strong, because you can override any urls and redirect to a static route.
* 3. Taxonomies: Stronger than collections, because it's an inbuilt feature.
* 4. Collections
@ -61,8 +62,11 @@ module.exports.start = () => {
const apiVersion = themeService.getApiVersion();
const RESOURCE_CONFIG = require(`./config/${apiVersion}`);
const previewRouter = new PreviewRouter(RESOURCE_CONFIG);
const unsubscribeRouter = new UnsubscribeRouter();
siteRouter.mountRouter(unsubscribeRouter.router());
registry.setRouter('unsubscribeRouter', unsubscribeRouter);
const previewRouter = new PreviewRouter(RESOURCE_CONFIG);
siteRouter.mountRouter(previewRouter.router());
registry.setRouter('previewRouter', previewRouter);

View File

@ -21,5 +21,9 @@ module.exports = {
get static() {
return require('./static');
},
get unsubscribe() {
return require('./unsubscribe');
}
};

View File

@ -0,0 +1,31 @@
const debug = require('ghost-ignition').debug('services:routing:controllers:unsubscribe');
const path = require('path');
const megaService = require('../../../../server/services/mega');
const labsService = require('../../../../server/services/labs');
const helpers = require('../../../services/routing/helpers');
module.exports = async function unsubscribeController(req, res, next) {
debug('unsubscribeController');
if (!labsService.isSet('members')) {
return next();
}
let data = {};
try {
data.member = await megaService.mega.handleUnsubscribeRequest(req);
} catch (err) {
data.error = err.message;
}
const templateName = 'unsubscribe';
res.routerOptions = {
type: 'custom',
templates: templateName,
defaultTemplate: path.resolve(__dirname, '../../../views/', templateName)
};
return helpers.renderer(req, res, data);
};

View File

@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>
{{#if member}}Successfully Unsubscribed{{/if}}
{{#if error}}Unsubscribe Failed{{/if}}
</title>
</head>
<body>
<p><a href="{{@site.url}}">Back to site</a></p>
<p>
{{#if member}}Success! {{member.email}} has been successfully unsubscribed.{{/if}}
{{#if error}}Uh oh! Unsubscribe failed: "{{error}}"{{/if}}
</p>
</body>
</html>

View File

@ -108,8 +108,8 @@ const serialize = async (model) => {
function createUnsubscribeUrl(member) {
const siteUrl = urlUtils.getSiteUrl();
const unsubscribeUrl = new URL(siteUrl);
unsubscribeUrl.searchParams.set('action', 'unsubscribe');
unsubscribeUrl.searchParams.set('unsubscribe', member.uuid);
unsubscribeUrl.pathname = `${unsubscribeUrl.pathname}/unsubscribe/`.replace('//', '/');
unsubscribeUrl.searchParams.set('uuid', member.uuid);
return unsubscribeUrl.href;
}
@ -135,14 +135,14 @@ async function handleUnsubscribeRequest(req) {
}
const {query} = url.parse(req.url, true);
if (!query || !query.unsubscribe) {
if (!query || !query.uuid) {
throw new common.errors.BadRequestError({
message: 'Expected unsubscribe param containing token'
});
}
const member = await membersService.api.members.get({
uuid: query.unsubscribe
uuid: query.uuid
});
if (!member) {
@ -152,7 +152,7 @@ async function handleUnsubscribeRequest(req) {
}
try {
await membersService.api.members.update({subscribed: false}, {id: member.id});
return await membersService.api.members.update({subscribed: false}, {id: member.id});
} catch (err) {
throw new common.errors.InternalServerError({
message: 'Failed to unsubscribe member'

View File

@ -15,7 +15,6 @@ const labsService = require('../../services/labs');
const urlUtils = require('../../lib/url-utils');
const sitemapHandler = require('../../../frontend/services/sitemap/handler');
const themeMiddleware = require('../../../frontend/services/themes').middleware;
const megaService = require('../../services/mega');
const membersService = require('../../services/members');
const siteRoutes = require('./routes');
const shared = require('../shared');
@ -190,21 +189,6 @@ module.exports = function setupSiteApp(options = {}) {
return next();
}
});
siteApp.use(async function (req, res, next) {
if (!labsService.isSet('members')) {
return next();
}
if (!req.url.includes('unsubscribe=')) {
return next();
}
try {
await megaService.mega.handleUnsubscribeRequest(req);
next();
} catch (err) {
common.logging.warn(err.message);
return next();
}
});
siteApp.use(function (req, res, next) {
res.locals.member = req.member;
next();