From 34b03934cc734f68ac8fa67f6f4907f26cab73d5 Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Sun, 21 Feb 2016 10:05:51 -0600 Subject: [PATCH 1/2] Added unsubscribe route and initial tests --- common/locales/en/api-v3.json | 4 +- .../POST-paymentId-subscribe-cancel.test.js | 68 +++++++++++++++++++ .../src/controllers/api-v3/unsubscription.js | 57 ++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 test/api/v3/integration/unsubscription/POST-paymentId-subscribe-cancel.test.js create mode 100644 website/src/controllers/api-v3/unsubscription.js diff --git a/common/locales/en/api-v3.json b/common/locales/en/api-v3.json index f4cfaa5272..06ba7afd75 100644 --- a/common/locales/en/api-v3.json +++ b/common/locales/en/api-v3.json @@ -88,5 +88,7 @@ "questNotPending": "There is no quest to start.", "questOrGroupLeaderOnlyStartQuest": "Only the quest leader or group leader can force start the quest", "noAdminAccess": "You don't have admin access.", - "pageMustBeNumber": "req.query.page must be a number" + "pageMustBeNumber": "req.query.page must be a number", + "missingUnsubscriptionCode": "Missing unsubscription code.", + "userNotFound": "User Not Found" } diff --git a/test/api/v3/integration/unsubscription/POST-paymentId-subscribe-cancel.test.js b/test/api/v3/integration/unsubscription/POST-paymentId-subscribe-cancel.test.js new file mode 100644 index 0000000000..359c0061df --- /dev/null +++ b/test/api/v3/integration/unsubscription/POST-paymentId-subscribe-cancel.test.js @@ -0,0 +1,68 @@ +import { + generateUser, + translate as t, +} from '../../../../helpers/api-v3-integration.helper'; +import { encrypt } from '../../../../../website/src/libs/api-v3/encryption'; +import { v4 as generateUUID } from 'uuid'; + +describe('GET /unsubscribe', () => { + let user; + let testEmail = 'test@habitica.com'; + + beforeEach(async () => { + user = await generateUser(); + }); + + it('return error when code is not provided', async () => { + await expect(user.get('/unsubscribe')).to.eventually.be.rejected.and.eql({ + code: 400, + error: 'BadRequest', + message: 'Invalid request parameters.', + }); + }); + + it('return error when user is not found', async () => { + let code = encrypt(JSON.stringify({ + _id: generateUUID(), + })); + + await expect(user.get(`/unsubscribe?code=${code}`)).to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('userNotFound'), + }); + }); + + it('unsubscribes a user from email notifications', async () => { + let code = encrypt(JSON.stringify({ + _id: user._id, + email: user.email, + })); + + await user.get(`/unsubscribe?code=${code}`); + + let unsubscribedUser = await user.get('/user'); + + expect(unsubscribedUser.preferences.emailNotifications.unsubscribeFromAll).to.be.true; + }); + + it('unsubscribes an email from notifications', async () => { + let code = encrypt(JSON.stringify({ + email: testEmail, + })); + + let unsubscribedMessage = await user.get(`/unsubscribe?code=${code}`); + + expect(unsubscribedMessage).to.equal('

Unsubscribed successfully!

You won\'t receive any other email from Habitica.'); + }); + + it('returns okay when email is already unsubscribed', async () => { + let code = encrypt(JSON.stringify({ + email: testEmail, + })); + + let unsubscribedMessage = await user.get(`/unsubscribe?code=${code}`); + + expect(unsubscribedMessage).to.equal('

Unsubscribed successfully!

You won\'t receive any other email from Habitica.'); + }); +}); diff --git a/website/src/controllers/api-v3/unsubscription.js b/website/src/controllers/api-v3/unsubscription.js new file mode 100644 index 0000000000..5578cd742f --- /dev/null +++ b/website/src/controllers/api-v3/unsubscription.js @@ -0,0 +1,57 @@ +import { model as User } from '../../models/user'; +import { model as EmailUnsubscription } from '../../models/emailUnsubscription'; +import { decrypt } from '../../libs/api-v3/encryption'; +import { + NotFound, +} from '../../libs/api-v3/errors'; + +let api = {}; + +/** + * @api {post} /unsubscribe Unsubscribe an email or user from email notifications + * @apiVersion 3.0.0 + * @apiName UnsubscribeEmail + * @apiGroup Unsubscribe + * + * @apiParam {String} code An unsubscription code + * + * @apiSuccess {String} okRes An message stating the user/email unsubscribed successfully + */ +api.unsubscribe = { + method: 'GET', + url: '/unsubscribe', + middlewares: [], + async handler (req, res) { + req.checkQuery({ + code: { + notEmpty: {errorMessage: res.t('missingUnsubscriptionCode')}, + }, + }); + + let validationErrors = req.validationErrors(); + if (validationErrors) throw validationErrors; + + let data = JSON.parse(decrypt(req.query.code)); + + if (data._id) { + let userUpdated = await User.update( + {_id: data._id}, + { $set: {'preferences.emailNotifications.unsubscribeFromAll': true}} + ); + + if (userUpdated.nModified !== 1) throw new NotFound(res.t('userNotFound')); + + res.send(`

${res.t('unsubscribedSuccessfully', null, req.language)}

res.t('unsubscribedTextUsers', null, req.language)`); + } else { + let unsubscribedEmail = await EmailUnsubscription.findOne({email: data.email}); + let okResponse = `

${res.t('unsubscribedSuccessfully', null, req.language)}

${res.t('unsubscribedTextOthers', null, req.language)}`; + if (unsubscribedEmail) return res.send(okResponse); + + await EmailUnsubscription.create({email: data.email}); + + res.send(okResponse); + } + }, +}; + +export default api; From d60ff421c94d35ee87bad61a7ee20b692f19344c Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Mon, 22 Feb 2016 09:03:21 -0600 Subject: [PATCH 2/2] Updated route to be named /emails/unsubscribe and other minor fixes --- .../GET-email-unsubscribe.test.js} | 12 ++++++------ .../api-v3/{unsubscription.js => email.js} | 11 ++++------- 2 files changed, 10 insertions(+), 13 deletions(-) rename test/api/v3/integration/{unsubscription/POST-paymentId-subscribe-cancel.test.js => emails/GET-email-unsubscribe.test.js} (78%) rename website/src/controllers/api-v3/{unsubscription.js => email.js} (76%) diff --git a/test/api/v3/integration/unsubscription/POST-paymentId-subscribe-cancel.test.js b/test/api/v3/integration/emails/GET-email-unsubscribe.test.js similarity index 78% rename from test/api/v3/integration/unsubscription/POST-paymentId-subscribe-cancel.test.js rename to test/api/v3/integration/emails/GET-email-unsubscribe.test.js index 359c0061df..bd13cbdc1e 100644 --- a/test/api/v3/integration/unsubscription/POST-paymentId-subscribe-cancel.test.js +++ b/test/api/v3/integration/emails/GET-email-unsubscribe.test.js @@ -5,7 +5,7 @@ import { import { encrypt } from '../../../../../website/src/libs/api-v3/encryption'; import { v4 as generateUUID } from 'uuid'; -describe('GET /unsubscribe', () => { +describe('GET /email/unsubscribe', () => { let user; let testEmail = 'test@habitica.com'; @@ -14,7 +14,7 @@ describe('GET /unsubscribe', () => { }); it('return error when code is not provided', async () => { - await expect(user.get('/unsubscribe')).to.eventually.be.rejected.and.eql({ + await expect(user.get('/email/unsubscribe')).to.eventually.be.rejected.and.eql({ code: 400, error: 'BadRequest', message: 'Invalid request parameters.', @@ -26,7 +26,7 @@ describe('GET /unsubscribe', () => { _id: generateUUID(), })); - await expect(user.get(`/unsubscribe?code=${code}`)).to.eventually.be.rejected.and.eql({ + await expect(user.get(`/email/unsubscribe?code=${code}`)).to.eventually.be.rejected.and.eql({ code: 404, error: 'NotFound', message: t('userNotFound'), @@ -39,7 +39,7 @@ describe('GET /unsubscribe', () => { email: user.email, })); - await user.get(`/unsubscribe?code=${code}`); + await user.get(`/email/unsubscribe?code=${code}`); let unsubscribedUser = await user.get('/user'); @@ -51,7 +51,7 @@ describe('GET /unsubscribe', () => { email: testEmail, })); - let unsubscribedMessage = await user.get(`/unsubscribe?code=${code}`); + let unsubscribedMessage = await user.get(`/email/unsubscribe?code=${code}`); expect(unsubscribedMessage).to.equal('

Unsubscribed successfully!

You won\'t receive any other email from Habitica.'); }); @@ -61,7 +61,7 @@ describe('GET /unsubscribe', () => { email: testEmail, })); - let unsubscribedMessage = await user.get(`/unsubscribe?code=${code}`); + let unsubscribedMessage = await user.get(`/email/unsubscribe?code=${code}`); expect(unsubscribedMessage).to.equal('

Unsubscribed successfully!

You won\'t receive any other email from Habitica.'); }); diff --git a/website/src/controllers/api-v3/unsubscription.js b/website/src/controllers/api-v3/email.js similarity index 76% rename from website/src/controllers/api-v3/unsubscription.js rename to website/src/controllers/api-v3/email.js index 5578cd742f..b3af91b340 100644 --- a/website/src/controllers/api-v3/unsubscription.js +++ b/website/src/controllers/api-v3/email.js @@ -19,7 +19,7 @@ let api = {}; */ api.unsubscribe = { method: 'GET', - url: '/unsubscribe', + url: '/email/unsubscribe', middlewares: [], async handler (req, res) { req.checkQuery({ @@ -41,14 +41,11 @@ api.unsubscribe = { if (userUpdated.nModified !== 1) throw new NotFound(res.t('userNotFound')); - res.send(`

${res.t('unsubscribedSuccessfully', null, req.language)}

res.t('unsubscribedTextUsers', null, req.language)`); + res.send(`

${res.t('unsubscribedSuccessfully')}

${res.t('unsubscribedTextUsers')}`); } else { let unsubscribedEmail = await EmailUnsubscription.findOne({email: data.email}); - let okResponse = `

${res.t('unsubscribedSuccessfully', null, req.language)}

${res.t('unsubscribedTextOthers', null, req.language)}`; - if (unsubscribedEmail) return res.send(okResponse); - - await EmailUnsubscription.create({email: data.email}); - + let okResponse = `

${res.t('unsubscribedSuccessfully')}

${res.t('unsubscribedTextOthers')}`; + if (!unsubscribedEmail) await EmailUnsubscription.create({email: data.email}); res.send(okResponse); } },