diff --git a/common/dist/scripts/habitrpg-shared.js b/common/dist/scripts/habitrpg-shared.js
index 5e76967282..9b1d44793f 100644
--- a/common/dist/scripts/habitrpg-shared.js
+++ b/common/dist/scripts/habitrpg-shared.js
@@ -7451,7 +7451,6 @@ process.browser = true;
process.env = {};
process.argv = [];
process.version = ''; // empty string to avoid regexp issues
-process.versions = {};
function noop() {}
diff --git a/common/locales/en/settings.json b/common/locales/en/settings.json
index d182b5940b..db4bf0953e 100644
--- a/common/locales/en/settings.json
+++ b/common/locales/en/settings.json
@@ -98,6 +98,9 @@
"invitedQuest": "Invited to Quest",
"kickedGroup": "Kicked from group",
"remindersToLogin": "Reminders to check in to HabitRPG",
+ "unsubscribedSuccessfully": "Unsubscribed successfully!",
+ "unsubscribedTextUsers": "You have successfully unsubscribed from all HabitRPG emails. You can enable only the emails you want to receive from the settings (requires login).",
+ "unsubscribedTextOthers": "You won't receive any other email from HabitRPG.",
"unsubscribeAllEmails": "Check to Unsubscribe from Emails",
"unsubscribeAllEmailsText": "By checking this box, I certify that I understand that by unsubscribing from all emails, HabitRPG will never be able to notify me via email about important changes to the site or my account.",
"correctlyUnsubscribedEmailType": "Correctly unsubscribed from \"<%= emailType %>\" emails.",
diff --git a/website/src/controllers/auth.js b/website/src/controllers/auth.js
index 25681d9474..a21d4ebc76 100644
--- a/website/src/controllers/auth.js
+++ b/website/src/controllers/auth.js
@@ -7,6 +7,7 @@ var utils = require('../utils');
var nconf = require('nconf');
var request = require('request');
var User = require('../models/user').model;
+var EmailUnsubscription = require('../models/emailUnsubscription').model;
var ga = require('./../utils').ga;
var i18n = require('./../i18n');
@@ -109,9 +110,14 @@ api.registerUser = function(req, res, next) {
newUser.preferences = newUser.preferences || {};
newUser.preferences.language = req.language; // User language detected from browser, not saved
var user = new User(newUser);
- utils.txnEmail(user, 'welcome');
ga.event('register', 'Local').send();
- user.save(cb);
+ user.save(function(err, savedUser){
+ // Clean previous email preferences
+ EmailUnsubscription.remove({email: savedUser.auth.local.email}, function(){
+ utils.txnEmail(savedUser, 'welcome');
+ });
+ cb.apply(cb, arguments);
+ });
}
}]
}, function(err, data) {
@@ -178,9 +184,16 @@ api.loginSocial = function(req, res, next) {
};
user.auth[network] = prof;
user = new User(user);
- user.save(cb);
+ user.save(function(err, savedUser){
+ // Clean previous email preferences
+ if(savedUser.auth.facebook.emails && savedUser.auth.facebook.emails[0] && savedUser.auth.facebook.emails[0].value){
+ EmailUnsubscription.remove({email: savedUser.auth.facebook.emails[0].value}, function(){
+ utils.txnEmail(savedUser, 'welcome');
+ });
+ }
+ cb.apply(cb, arguments);
+ });
- utils.txnEmail(user, 'welcome');
ga.event('register', network).send();
}]
}, function(err, results){
diff --git a/website/src/controllers/groups.js b/website/src/controllers/groups.js
index 4e2a93d79f..f93bf9c47f 100644
--- a/website/src/controllers/groups.js
+++ b/website/src/controllers/groups.js
@@ -12,6 +12,7 @@ var shared = require('../../../common');
var User = require('./../models/user').model;
var Group = require('./../models/group').model;
var Challenge = require('./../models/challenge').model;
+var EmailUnsubscription = require('./../models/emailUnsubscription').model;
var isProd = nconf.get('NODE_ENV') === 'production';
var api = module.exports;
@@ -623,10 +624,15 @@ var inviteByEmails = function(invites, group, req, res, next){
}
// TODO implement "users can only be invited once"
- invite.canSend = true; // Requested by utils.txnEmail
- utils.txnEmail(invite, ('invite-friend' + (group.type == 'guild' ? '-guild' : '')), variables);
+ // Check for the email address not to be unsubscribed
+ EmailUnsubscription.findOne({email: invite.email}, function(err, unsubscribed){
+ if(err) return cb(err);
+ if(unsubscribed) return cb();
- cb();
+ utils.txnEmail(invite, ('invite-friend' + (group.type == 'guild' ? '-guild' : '')), variables);
+
+ cb();
+ });
});
}else{
cb();
diff --git a/website/src/controllers/unsubscription.js b/website/src/controllers/unsubscription.js
new file mode 100644
index 0000000000..d0ef89f920
--- /dev/null
+++ b/website/src/controllers/unsubscription.js
@@ -0,0 +1,36 @@
+var User = require('../models/user').model;
+var EmailUnsubscription = require('../models/emailUnsubscription').model;
+var utils = require('../utils');
+var i18n = require('../../../common').i18n;
+
+var api = module.exports = {};
+
+api.unsubscribe = function(req, res, next){
+ if(!req.query.code) return res.json(500, {err: 'Missing unsubscription code.'});
+
+ var data = JSON.parse(utils.decrypt(req.query.code));
+
+ if(data._id){
+ User.update({_id: data._id}, {
+ $set: {'preferences.emailNotifications.unsubscribeFromAll': true}
+ }, {multi: false}, function(err, nAffected){
+ if(err) return next(err);
+ if(nAffected !== 1) return res.json(404, {err: 'User not found'});
+
+ res.send('
' + i18n.t('unsubscribedSuccessfully', null, req.language) + '
' + i18n.t('unsubscribedTextUsers', null, req.language));
+ });
+ }else{
+ EmailUnsubscription.findOne({email: data.email}, function(err, doc){
+ if(err) return next(err);
+ var okRes = '' + i18n.t('unsubscribedSuccessfully', null, req.language) + '
' + i18n.t('unsubscribedTextOthers', null, req.language);
+
+ if(doc) return res.send(okRes);
+
+ EmailUnsubscription.create({email: data.email}, function(err, doc){
+ if(err) return next(err);
+
+ res.send(okRes);
+ })
+ });
+ }
+};
\ No newline at end of file
diff --git a/website/src/models/emailUnsubscription.js b/website/src/models/emailUnsubscription.js
new file mode 100644
index 0000000000..144417f3fc
--- /dev/null
+++ b/website/src/models/emailUnsubscription.js
@@ -0,0 +1,14 @@
+var mongoose = require("mongoose");
+var shared = require('../../../common');
+
+// A collection used to store mailing list unsubscription for non registered email addresses
+var EmailUnsubscriptionSchema = new mongoose.Schema({
+ _id: {
+ type: String,
+ 'default': shared.uuid
+ },
+ email: String
+});
+
+module.exports.schema = EmailUnsubscriptionSchema;
+module.exports.model = mongoose.model('EmailUnsubscription', EmailUnsubscriptionSchema);
\ No newline at end of file
diff --git a/website/src/routes/unsubscription.js b/website/src/routes/unsubscription.js
new file mode 100644
index 0000000000..efd06f9197
--- /dev/null
+++ b/website/src/routes/unsubscription.js
@@ -0,0 +1,8 @@
+var express = require('express');
+var router = new express.Router();
+var i18n = require('../i18n');
+var unsubscription = require('../controllers/unsubscription');
+
+router.get('/unsubscribe', i18n.getUserLanguage, unsubscription.unsubscribe);
+
+module.exports = router;
\ No newline at end of file
diff --git a/website/src/server.js b/website/src/server.js
index 1c3484d776..1447823a4d 100644
--- a/website/src/server.js
+++ b/website/src/server.js
@@ -127,6 +127,7 @@ if (cores!==0 && cluster.isMaster && (isDev || isProd)) {
app.use(require('./routes/payments').middleware);
app.use(require('./routes/auth').middleware);
app.use(require('./routes/coupon').middleware);
+ app.use(require('./routes/unsubscription').middleware);
var v2 = express();
app.use('/api/v2', v2);
app.use('/api/v1', require('./routes/apiv1').middleware);
diff --git a/website/src/utils.js b/website/src/utils.js
index 6349ca5d3e..c2e0085087 100644
--- a/website/src/utils.js
+++ b/website/src/utils.js
@@ -44,6 +44,10 @@ function getUserInfo(user, fields) {
}
}
+ if(fields.indexOf('_id') != -1){
+ info._id = user._id;
+ }
+
if(fields.indexOf('canSend') != -1){
info.canSend = user.preferences.emailNotifications.unsubscribeFromAll !== true;
}
@@ -62,37 +66,59 @@ module.exports.txnEmail = function(mailingInfoArray, emailType, variables, perso
// It's important to pass at least a user with its `preferences` as we need to check if he unsubscribed
mailingInfoArray = mailingInfoArray.map(function(mailingInfo){
- return mailingInfo._id ? getUserInfo(mailingInfo, ['email', 'name', 'canSend']) : mailingInfo;
+ return mailingInfo._id ? getUserInfo(mailingInfo, ['_id', 'email', 'name', 'canSend']) : mailingInfo;
}).filter(function(mailingInfo){
// Always send reset-password emails
- return (mailingInfo.email && (mailingInfo.canSend || emailType === 'reset-password'));
+ // Don't check canSend for non registered users as already checked before
+ return (mailingInfo.email && ((!mailingInfo._id || mailingInfo.canSend) || emailType === 'reset-password'));
});
// Personal variables are personal to each email recipient, if they are missing
- // we manually create a structure for them with RECIPIENT_NAME
- // otherwise we just add RECIPIENT_NAME to the existing personal variables
+ // we manually create a structure for them with RECIPIENT_NAME and RECIPIENT_UNSUB_URL
+ // otherwise we just add RECIPIENT_NAME and RECIPIENT_UNSUB_URL to the existing personal variables
if(!personalVariables || personalVariables.length === 0){
personalVariables = mailingInfoArray.map(function(mailingInfo){
return {
rcpt: mailingInfo.email,
- vars: [{
- name: 'RECIPIENT_NAME',
- content: mailingInfo.name
- }]
+ vars: [
+ {
+ name: 'RECIPIENT_NAME',
+ content: mailingInfo.name
+ },
+ {
+ name: 'RECIPIENT_UNSUB_URL',
+ content: baseUrl + '/unsubscribe?code=' + module.exports.encrypt(JSON.stringify({
+ _id: mailingInfo._id,
+ email: mailingInfo.email
+ }))
+ }
+ ]
}
});
}else{
var temporaryPersonalVariables = {};
mailingInfoArray.forEach(function(mailingInfo){
- temporaryPersonalVariables[mailingInfo.email] = mailingInfo.name;
+ temporaryPersonalVariables[mailingInfo.email] = {
+ name: mailingInfo.name,
+ _id: mailingInfo._id
+ }
});
personalVariables.forEach(function(singlePersonalVariables){
- singlePersonalVariables.vars.push({
- name: 'RECIPIENT_NAME',
- content: temporaryPersonalVariables[singlePersonalVariables.rcpt]
- });
+ singlePersonalVariables.vars.push(
+ {
+ name: 'RECIPIENT_NAME',
+ content: temporaryPersonalVariables[singlePersonalVariables.rcpt].name
+ },
+ {
+ name: 'RECIPIENT_UNSUB_URL',
+ content: baseUrl + '/unsubscribe?code=' + module.exports.encrypt(JSON.stringify({
+ _id: temporaryPersonalVariables[singlePersonalVariables.rcpt]._id,
+ email: singlePersonalVariables.rcpt
+ }))
+ }
+ )
});
}