feat(backend): Add one-click unsubscribe endpoints
This commit is contained in:
parent
c44dac26ec
commit
61f111fc8d
6 changed files with 93 additions and 11 deletions
|
@ -113,11 +113,13 @@ const send = async (test = true) => {
|
||||||
|
|
||||||
for (let sub of subscribers[lang]) {
|
for (let sub of subscribers[lang]) {
|
||||||
if (l > 0) {
|
if (l > 0) {
|
||||||
|
const unsubGet = `https://freesewing.org${
|
||||||
|
lang === 'en' ? '/' : '/' + lang + '/'
|
||||||
|
}newsletter/unsubscribe?x=${sub.ehash}`
|
||||||
|
const unsubPost = `https://backend3.freesewing.org/ocunsub/${sub.ehash}`
|
||||||
const body = mustache.render(template, {
|
const body = mustache.render(template, {
|
||||||
...i18n[lang],
|
...i18n[lang],
|
||||||
unsubscribe: `https://freesewing.org${
|
unsubscribe: unsubGet,
|
||||||
lang === 'en' ? '/' : '/' + lang + '/'
|
|
||||||
}newsletter/unsubscribe?x=${sub.ehash}`,
|
|
||||||
content,
|
content,
|
||||||
})
|
})
|
||||||
console.log(`[${lang}] ${l}/${subs} (${i}) Sending to ${sub.email}`)
|
console.log(`[${lang}] ${l}/${subs} (${i}) Sending to ${sub.email}`)
|
||||||
|
@ -141,6 +143,16 @@ const send = async (test = true) => {
|
||||||
Charset: 'utf-8',
|
Charset: 'utf-8',
|
||||||
Data: i18n[lang].title,
|
Data: i18n[lang].title,
|
||||||
},
|
},
|
||||||
|
Headers: [
|
||||||
|
{
|
||||||
|
Name: 'List-Unsubscribe',
|
||||||
|
Value: unsubPost,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: 'List-Unsubscribe-Post',
|
||||||
|
Value: 'List-Unsubscribe=One-Click',
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Destination: {
|
Destination: {
|
||||||
|
|
|
@ -1,4 +1,7 @@
|
||||||
import { SubscriberModel } from '../models/subscriber.mjs'
|
import { SubscriberModel } from '../models/subscriber.mjs'
|
||||||
|
// Catch-all page
|
||||||
|
import { html as ocunsubOk } from '../html/ocunsub-ok.mjs'
|
||||||
|
import { html as ocunsubKo } from '../html/ocunsub-ko.mjs'
|
||||||
|
|
||||||
export function SubscribersController() {}
|
export function SubscribersController() {}
|
||||||
|
|
||||||
|
@ -47,12 +50,16 @@ SubscribersController.prototype.confirm = async (req, res, tools) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Unsubscribe from the newsletter
|
* One-Click unsubscribe from the newsletter
|
||||||
* See: https://freesewing.dev/reference/backend/api
|
* See: https://freesewing.dev/reference/backend/api
|
||||||
*/
|
*/
|
||||||
SubscribersController.unsubscribe = async (req, res, tools) => {
|
SubscribersController.prototype.ocunsub = async (req, res, tools) => {
|
||||||
const Subscriber = new SubscriberModel(tools)
|
if (!res.params?.ehash) return res.set('Content-Type', 'text/html').status(200).send(ocunsubKo)
|
||||||
await Subscriber.unsubscribe(req)
|
|
||||||
|
|
||||||
return Subscriber.sendResponse(res)
|
const Subscriber = new SubscriberModel(tools)
|
||||||
|
const result = await Subscriber.ocunsub(req)
|
||||||
|
|
||||||
|
if (result) return res.set('Content-Type', 'text/html').status(200).send(ocunsubOk)
|
||||||
|
|
||||||
|
return res.set('Content-Type', 'text/html').status(200).send(okunsubKo)
|
||||||
}
|
}
|
||||||
|
|
9
sites/backend/src/html/ocunsub-ko.mjs
Normal file
9
sites/backend/src/html/ocunsub-ko.mjs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
import { wrapper } from './shared.mjs'
|
||||||
|
|
||||||
|
export const html = wrapper({
|
||||||
|
content: `
|
||||||
|
<h1><span role="img">🫤</span> FNpe</h1>
|
||||||
|
<p>Whatever you intended to do, it did not work.</p>
|
||||||
|
<p><a href="https://freesewing.org/support">Contact support</a></p>
|
||||||
|
`,
|
||||||
|
})
|
8
sites/backend/src/html/ocunsub-ok.mjs
Normal file
8
sites/backend/src/html/ocunsub-ok.mjs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
import { wrapper } from './shared.mjs'
|
||||||
|
|
||||||
|
export const html = wrapper({
|
||||||
|
content: `
|
||||||
|
<h1><span role="img">👋</span> Farewell</h1>
|
||||||
|
<p>You have been unsubscribed.</p>
|
||||||
|
`,
|
||||||
|
})
|
|
@ -173,6 +173,43 @@ SubscriberModel.prototype.unsubscribe = async function ({ params }) {
|
||||||
return this.setResponse(404)
|
return this.setResponse(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* One-click unsubscribe a user
|
||||||
|
* This is an unauthenticated route (has to for newsletter subscribers might not be users)
|
||||||
|
*
|
||||||
|
* @param {body} object - The request body
|
||||||
|
* @returns {SubscriberModal} object - The SubscriberModel
|
||||||
|
*/
|
||||||
|
SubscriberModel.prototype.ocunsub = async function ({ params }) {
|
||||||
|
const { ehash } = params
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Find the subscription record
|
||||||
|
*/
|
||||||
|
await this.read({ ehash })
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If found, remove the record
|
||||||
|
*/
|
||||||
|
if (this.record) {
|
||||||
|
await this.delete({ id: this.record.id })
|
||||||
|
|
||||||
|
return true
|
||||||
|
} else {
|
||||||
|
/*
|
||||||
|
* If not, perhaps it's an account ehash rather than subscriber ehash
|
||||||
|
*/
|
||||||
|
await this.User.read({ ehash })
|
||||||
|
if (this.User.record) {
|
||||||
|
await this.User.update({ newsletter: false })
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* A helper method to validate input and load the subscription record
|
* A helper method to validate input and load the subscription record
|
||||||
*
|
*
|
||||||
|
|
|
@ -17,4 +17,13 @@ export function subscribersRoutes(tools) {
|
||||||
|
|
||||||
// Unsubscribe from newsletter
|
// Unsubscribe from newsletter
|
||||||
app.delete('/subscriber/:ehash', (req, res) => Subscriber.unsubscribe(req, res, tools))
|
app.delete('/subscriber/:ehash', (req, res) => Subscriber.unsubscribe(req, res, tools))
|
||||||
|
|
||||||
|
// One-Click unsubscribe (ocunsub) from newsletter needs to be a POST request.
|
||||||
|
// See https://datatracker.ietf.org/doc/html/rfc8058
|
||||||
|
app.post('/ocunsub/:ehash', (req, res) => Subscriber.ocunsub(req, res, tools))
|
||||||
|
|
||||||
|
// Just in case somebody lands here with a GET request
|
||||||
|
app.get('/ocunsub/:ehash', (req, res) =>
|
||||||
|
res.redirect(`https://freesewing.org/newsletter/unsubscribe?i=${req.params.ehash}`)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue