mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 15:17:25 +01:00
Merge pull request #6232 from HabitRPG/api-v3-utils
[API v3] Port Utils
This commit is contained in:
@@ -12,10 +12,10 @@
|
|||||||
"babel": "^5.5.4",
|
"babel": "^5.5.4",
|
||||||
"babelify": "^7.2.0",
|
"babelify": "^7.2.0",
|
||||||
"body-parser": "^1.14.1",
|
"body-parser": "^1.14.1",
|
||||||
"compression": "^1.6.0",
|
|
||||||
"bower": "~1.3.12",
|
"bower": "~1.3.12",
|
||||||
"browserify": "~12.0.1",
|
"browserify": "~12.0.1",
|
||||||
"coffee-script": "1.6.x",
|
"coffee-script": "1.6.x",
|
||||||
|
"compression": "^1.6.0",
|
||||||
"connect-ratelimit": "0.0.7",
|
"connect-ratelimit": "0.0.7",
|
||||||
"cookie-parser": "^1.4.0",
|
"cookie-parser": "^1.4.0",
|
||||||
"cookie-session": "^1.2.0",
|
"cookie-session": "^1.2.0",
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
"nconf": "~0.8.2",
|
"nconf": "~0.8.2",
|
||||||
"newrelic": "~1.23.0",
|
"newrelic": "~1.23.0",
|
||||||
"nib": "~1.0.1",
|
"nib": "~1.0.1",
|
||||||
"nodemailer": "~0.5.2",
|
"nodemailer": "^1.9.0",
|
||||||
"pageres": "^1.0.1",
|
"pageres": "^1.0.1",
|
||||||
"passport": "~0.2.1",
|
"passport": "~0.2.1",
|
||||||
"passport-facebook": "2.0.0",
|
"passport-facebook": "2.0.0",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import mongoose from 'mongoose';
|
|||||||
import autoinc from 'mongoose-id-autoinc';
|
import autoinc from 'mongoose-id-autoinc';
|
||||||
import logger from '../website/src/libs/api-v3/logger';
|
import logger from '../website/src/libs/api-v3/logger';
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import utils from '../website/src/libs/utils';
|
|
||||||
import repl from 'repl';
|
import repl from 'repl';
|
||||||
import gulp from 'gulp';
|
import gulp from 'gulp';
|
||||||
|
|
||||||
@@ -19,8 +18,6 @@ let improveRepl = (context) => {
|
|||||||
process.stdout.write('\u001B[2J\u001B[0;0f');
|
process.stdout.write('\u001B[2J\u001B[0;0f');
|
||||||
}});
|
}});
|
||||||
|
|
||||||
utils.setupConfig();
|
|
||||||
|
|
||||||
context.Challenge = require('../website/src/models/challenge').model;
|
context.Challenge = require('../website/src/models/challenge').model;
|
||||||
context.Group = require('../website/src/models/group').model;
|
context.Group = require('../website/src/models/group').model;
|
||||||
context.User = require('../website/src/models/user').model;
|
context.User = require('../website/src/models/user').model;
|
||||||
|
|||||||
217
test/api/v3/unit/libs/email.test.js
Normal file
217
test/api/v3/unit/libs/email.test.js
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
import request from 'request';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
import nodemailer from 'nodemailer';
|
||||||
|
import Q from 'q';
|
||||||
|
import logger from '../../../../../website/src/libs/api-v3/logger';
|
||||||
|
|
||||||
|
function getUser () {
|
||||||
|
return {
|
||||||
|
_id: 'random _id',
|
||||||
|
auth: {
|
||||||
|
local: {
|
||||||
|
username: 'username',
|
||||||
|
email: 'email@email',
|
||||||
|
},
|
||||||
|
facebook: {
|
||||||
|
emails: [{
|
||||||
|
value: 'email@facebook'
|
||||||
|
}],
|
||||||
|
displayName: 'fb display name',
|
||||||
|
}
|
||||||
|
},
|
||||||
|
profile: {
|
||||||
|
name: 'profile name',
|
||||||
|
},
|
||||||
|
preferences: {
|
||||||
|
emailNotifications: {
|
||||||
|
unsubscribeFromAll: false
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('emails', () => {
|
||||||
|
let pathToEmailLib = '../../../../../website/src/libs/api-v3/email';
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
delete require.cache[require.resolve(pathToEmailLib)];
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sendEmail', () => {
|
||||||
|
it('can send an email using the default transport', () => {
|
||||||
|
let sendMailSpy = sandbox.stub().returns(Q.defer().promise);
|
||||||
|
|
||||||
|
sandbox.stub(nodemailer, 'createTransport').returns({
|
||||||
|
sendMail: sendMailSpy,
|
||||||
|
});
|
||||||
|
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
attachEmail.send();
|
||||||
|
expect(sendMailSpy).to.be.calledOnce;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('logs errors', (done) => {
|
||||||
|
let deferred = Q.defer();
|
||||||
|
let sendMailSpy = sandbox.stub().returns(deferred.promise);
|
||||||
|
|
||||||
|
sandbox.stub(nodemailer, 'createTransport').returns({
|
||||||
|
sendMail: sendMailSpy,
|
||||||
|
});
|
||||||
|
sandbox.stub(logger, 'error');
|
||||||
|
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
attachEmail.send();
|
||||||
|
expect(sendMailSpy).to.be.calledOnce;
|
||||||
|
deferred.reject();
|
||||||
|
deferred.promise.catch((err) => {
|
||||||
|
expect(logger.error).to.be.calledOnce;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getUserInfo', () => {
|
||||||
|
it('returns an empty object if no field request', () => {
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let getUserInfo = attachEmail.getUserInfo;
|
||||||
|
expect(getUserInfo({}, [])).to.be.empty;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct user data', () => {
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let getUserInfo = attachEmail.getUserInfo;
|
||||||
|
let user = getUser();
|
||||||
|
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||||
|
|
||||||
|
expect(data).to.have.property('name', user.profile.name);
|
||||||
|
expect(data).to.have.property('email', user.auth.local.email);
|
||||||
|
expect(data).to.have.property('_id', user._id);
|
||||||
|
expect(data).to.have.property('canSend', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns correct user data [facebook users]', () => {
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let getUserInfo = attachEmail.getUserInfo;
|
||||||
|
let user = getUser();
|
||||||
|
delete user.profile['name'];
|
||||||
|
delete user.auth['local'];
|
||||||
|
|
||||||
|
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||||
|
|
||||||
|
expect(data).to.have.property('name', user.auth.facebook.displayName);
|
||||||
|
expect(data).to.have.property('email', user.auth.facebook.emails[0].value);
|
||||||
|
expect(data).to.have.property('_id', user._id);
|
||||||
|
expect(data).to.have.property('canSend', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('has fallbacks for missing data', () => {
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let getUserInfo = attachEmail.getUserInfo;
|
||||||
|
let user = getUser();
|
||||||
|
delete user.profile['name'];
|
||||||
|
delete user.auth.local['email']
|
||||||
|
delete user.auth['facebook'];
|
||||||
|
|
||||||
|
let data = getUserInfo(user, ['name', 'email', '_id', 'canSend']);
|
||||||
|
|
||||||
|
expect(data).to.have.property('name', user.auth.local.username);
|
||||||
|
expect(data).not.to.have.property('email');
|
||||||
|
expect(data).to.have.property('_id', user._id);
|
||||||
|
expect(data).to.have.property('canSend', true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('sendTxnEmail', () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
sandbox.stub(request, 'post');
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
sandbox.restore();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can send a txn email to one recipient', () => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let sendTxnEmail = attachEmail.sendTxn;
|
||||||
|
let emailType = 'an email type';
|
||||||
|
let mailingInfo = {
|
||||||
|
name: 'my name',
|
||||||
|
email: 'my@email',
|
||||||
|
};
|
||||||
|
|
||||||
|
sendTxnEmail(mailingInfo, emailType);
|
||||||
|
expect(request.post).to.be.calledWith(sinon.match({
|
||||||
|
json: {
|
||||||
|
data: {
|
||||||
|
emailType: sinon.match.same(emailType),
|
||||||
|
to: sinon.match((value) => {
|
||||||
|
return Array.isArray(value) && value[0].name === mailingInfo.name;
|
||||||
|
}, 'matches mailing info array'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not send email if address is missing', () => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let sendTxnEmail = attachEmail.sendTxn;
|
||||||
|
let emailType = 'an email type';
|
||||||
|
let mailingInfo = {
|
||||||
|
name: 'my name',
|
||||||
|
//email: 'my@email',
|
||||||
|
};
|
||||||
|
|
||||||
|
sendTxnEmail(mailingInfo, emailType);
|
||||||
|
expect(request.post).not.to.be.called;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('uses getUserInfo in case of user data', () => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let sendTxnEmail = attachEmail.sendTxn;
|
||||||
|
let emailType = 'an email type';
|
||||||
|
let mailingInfo = getUser();
|
||||||
|
|
||||||
|
sendTxnEmail(mailingInfo, emailType);
|
||||||
|
expect(request.post).to.be.calledWith(sinon.match({
|
||||||
|
json: {
|
||||||
|
data: {
|
||||||
|
emailType: sinon.match.same(emailType),
|
||||||
|
to: sinon.match(val => val[0]._id === mailingInfo._id),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
it('sends email with some default variables', () => {
|
||||||
|
sandbox.stub(nconf, 'get').withArgs('IS_PROD').returns(true);
|
||||||
|
let attachEmail = require(pathToEmailLib);
|
||||||
|
let sendTxnEmail = attachEmail.sendTxn;
|
||||||
|
let emailType = 'an email type';
|
||||||
|
let mailingInfo = {
|
||||||
|
name: 'my name',
|
||||||
|
email: 'my@email',
|
||||||
|
};
|
||||||
|
let variables = [1,2,3];
|
||||||
|
|
||||||
|
sendTxnEmail(mailingInfo, emailType, variables);
|
||||||
|
expect(request.post).to.be.calledWith(sinon.match({
|
||||||
|
json: {
|
||||||
|
data: {
|
||||||
|
variables: sinon.match((value) => {
|
||||||
|
return value[0].name === 'BASE_URL';
|
||||||
|
}, 'matches variables'),
|
||||||
|
personalVariables: sinon.match((value) => {
|
||||||
|
return (value[0].rcpt === mailingInfo.email
|
||||||
|
&& value[0].vars[0].name === 'RECIPIENT_NAME'
|
||||||
|
&& value[0].vars[1].name === 'RECIPIENT_UNSUB_URL'
|
||||||
|
);
|
||||||
|
}, 'matches personal variables'),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
15
test/api/v3/unit/libs/encryption.test.js
Normal file
15
test/api/v3/unit/libs/encryption.test.js
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import {
|
||||||
|
encrypt,
|
||||||
|
decrypt,
|
||||||
|
} from '../../../../../website/src/libs/api-v3/encryption';
|
||||||
|
|
||||||
|
describe('encryption', () => {
|
||||||
|
it('can encrypt and decrypt', () => {
|
||||||
|
let data = 'some secret text';
|
||||||
|
let encrypted = encrypt(data);
|
||||||
|
let decrypted = decrypt(encrypted);
|
||||||
|
|
||||||
|
expect(encrypted).not.to.equal(data);
|
||||||
|
expect(data).to.equal(decrypted);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -8,7 +8,7 @@ var Group = require('../../../website/src/models/group').model;
|
|||||||
var groupsController = require('../../../website/src/controllers/api-v2/groups');
|
var groupsController = require('../../../website/src/controllers/api-v2/groups');
|
||||||
|
|
||||||
describe('Groups Controller', function() {
|
describe('Groups Controller', function() {
|
||||||
var utils = require('../../../website/src/libs/utils');
|
var utils = require('../../../website/src/libs/api-v2/utils');
|
||||||
|
|
||||||
describe('#invite', function() {
|
describe('#invite', function() {
|
||||||
var res, req, user, group;
|
var res, req, user, group;
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ var validator = require('validator');
|
|||||||
var passport = require('passport');
|
var passport = require('passport');
|
||||||
var shared = require('../../../../common');
|
var shared = require('../../../../common');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var utils = require('../../libs/utils');
|
var utils = require('../../libs/api-v2/utils');
|
||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var request = require('request');
|
var request = require('request');
|
||||||
var FirebaseTokenGenerator = require('firebase-token-generator');
|
var FirebaseTokenGenerator = require('firebase-token-generator');
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ var Group = require('./../../models/group').model;
|
|||||||
var Challenge = require('./../../models/challenge').model;
|
var Challenge = require('./../../models/challenge').model;
|
||||||
var logging = require('./../../libs/api-v2/logging');
|
var logging = require('./../../libs/api-v2/logging');
|
||||||
var csv = require('express-csv');
|
var csv = require('express-csv');
|
||||||
var utils = require('../../libs/utils');
|
var utils = require('../../libs/api-v2/utils');
|
||||||
var api = module.exports;
|
var api = module.exports;
|
||||||
var pushNotify = require('./../pushNotifications');
|
var pushNotify = require('./../pushNotifications');
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ var _ = require('lodash');
|
|||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
var Q = require('q');
|
var Q = require('q');
|
||||||
var utils = require('./../../libs/utils');
|
var utils = require('./../../libs/api-v2/utils');
|
||||||
var shared = require('../../../../common');
|
var shared = require('../../../../common');
|
||||||
var User = require('./../../models/user').model;
|
var User = require('./../../models/user').model;
|
||||||
var Group = require('./../../models/group').model;
|
var Group = require('./../../models/group').model;
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ var api = module.exports;
|
|||||||
var async = require('async');
|
var async = require('async');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var shared = require('../../../../common');
|
var shared = require('../../../../common');
|
||||||
var utils = require('../../libs/utils');
|
var utils = require('../../libs/api-v2/utils');
|
||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var pushNotify = require('./../pushNotifications');
|
var pushNotify = require('./../pushNotifications');
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
var User = require('../../models/user').model;
|
var User = require('../../models/user').model;
|
||||||
var EmailUnsubscription = require('../../models/emailUnsubscription').model;
|
var EmailUnsubscription = require('../../models/emailUnsubscription').model;
|
||||||
var utils = require('../../libs/utils');
|
var utils = require('../../libs/api-v2/utils');
|
||||||
var i18n = require('../../../../common').i18n;
|
var i18n = require('../../../../common').i18n;
|
||||||
|
|
||||||
var api = module.exports = {};
|
var api = module.exports = {};
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ var nconf = require('nconf');
|
|||||||
var async = require('async');
|
var async = require('async');
|
||||||
var shared = require('../../../../common');
|
var shared = require('../../../../common');
|
||||||
var User = require('./../../models/user').model;
|
var User = require('./../../models/user').model;
|
||||||
var utils = require('./../../libs/utils');
|
var utils = require('./../../libs/api-v2/utils');
|
||||||
var analytics = utils.analytics;
|
var analytics = utils.analytics;
|
||||||
var Group = require('./../../models/group').model;
|
var Group = require('./../../models/group').model;
|
||||||
var Challenge = require('./../../models/challenge').model;
|
var Challenge = require('./../../models/challenge').model;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var shared = require('../../../../common');
|
var shared = require('../../../../common');
|
||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var utils = require('./../../libs/utils');
|
var utils = require('./../../libs/api-v2/utils');
|
||||||
var moment = require('moment');
|
var moment = require('moment');
|
||||||
var isProduction = nconf.get("NODE_ENV") === "production";
|
var isProduction = nconf.get("NODE_ENV") === "production";
|
||||||
var stripe = require('./stripe');
|
var stripe = require('./stripe');
|
||||||
|
|||||||
@@ -9,13 +9,13 @@ var logger = require('./libs/api-v3/logger');
|
|||||||
// Initialize configuration
|
// Initialize configuration
|
||||||
var setupNconf = require('./libs/api-v3/setupNconf');
|
var setupNconf = require('./libs/api-v3/setupNconf');
|
||||||
setupNconf();
|
setupNconf();
|
||||||
var utils = require('./libs/utils');
|
|
||||||
utils.setupConfig();
|
|
||||||
|
|
||||||
var IS_PROD = nconf.get('IS_PROD');
|
var IS_PROD = nconf.get('IS_PROD');
|
||||||
var IS_DEV = nconf.get('IS_DEV');
|
var IS_DEV = nconf.get('IS_DEV');
|
||||||
var cores = Number(nconf.get('WEB_CONCURRENCY')) || 0;
|
var cores = Number(nconf.get('WEB_CONCURRENCY')) || 0;
|
||||||
|
|
||||||
|
if (IS_DEV) Error.stackTraceLimit = Infinity;
|
||||||
|
|
||||||
// Setup the cluster module
|
// Setup the cluster module
|
||||||
if (cores !== 0 && cluster.isMaster && (IS_DEV || IS_PROD)) {
|
if (cores !== 0 && cluster.isMaster && (IS_DEV || IS_PROD)) {
|
||||||
// Fork workers. If config.json has CORES=x, use that - otherwise, use all cpus-1 (production)
|
// Fork workers. If config.json has CORES=x, use that - otherwise, use all cpus-1 (production)
|
||||||
|
|||||||
158
website/src/libs/api-v3/email.js
Normal file
158
website/src/libs/api-v3/email.js
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
import { createTransport } from 'nodemailer';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
import logger from './logger';
|
||||||
|
import { encrypt } from './encryption';
|
||||||
|
import request from 'request';
|
||||||
|
|
||||||
|
const IS_PROD = nconf.get('IS_PROD');
|
||||||
|
const EMAIL_SERVER = {
|
||||||
|
url: nconf.get('EMAIL_SERVER:url'),
|
||||||
|
auth: {
|
||||||
|
user: nconf.get('EMAIL_SERVER:authUser'),
|
||||||
|
password: nconf.get('EMAIL_SERVER:authPassword'),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const BASE_URL = nconf.get('BASE_URL');
|
||||||
|
|
||||||
|
let smtpTransporter = createTransport({
|
||||||
|
service: nconf.get('SMTP_SERVICE'),
|
||||||
|
auth: {
|
||||||
|
user: nconf.get('SMTP_USER'),
|
||||||
|
pass: nconf.get('SMTP_PASS'),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send email directly from the server using the smtpTransporter,
|
||||||
|
// used only to send password reset emails because users unsubscribed on Mandrill wouldn't get them
|
||||||
|
export function send (mailData) {
|
||||||
|
return smtpTransporter
|
||||||
|
.sendMail(mailData)
|
||||||
|
.catch((error) => logger.error(error));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getUserInfo (user, fields = []) {
|
||||||
|
let info = {};
|
||||||
|
|
||||||
|
if (fields.indexOf('name') !== -1) {
|
||||||
|
info.name = user.profile && user.profile.name;
|
||||||
|
|
||||||
|
if (!info.name) {
|
||||||
|
if (user.auth.local && user.auth.local.username) {
|
||||||
|
info.name = user.auth.local.username;
|
||||||
|
} else if (user.auth.facebook) {
|
||||||
|
info.name = user.auth.facebook.displayName || user.auth.facebook.username;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields.indexOf('email') !== -1) {
|
||||||
|
if (user.auth.local && user.auth.local.email) {
|
||||||
|
info.email = user.auth.local.email;
|
||||||
|
} else if (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0] && user.auth.facebook.emails[0].value) {
|
||||||
|
info.email = user.auth.facebook.emails[0].value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields.indexOf('_id') !== -1) {
|
||||||
|
info._id = user._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fields.indexOf('canSend') !== -1) {
|
||||||
|
if (user.preferences && user.preferences.emailNotifications) {
|
||||||
|
info.canSend = user.preferences.emailNotifications.unsubscribeFromAll !== true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send a transactional email using Mandrill through the external email server
|
||||||
|
export function sendTxn (mailingInfoArray, emailType, variables, personalVariables) {
|
||||||
|
mailingInfoArray = Array.isArray(mailingInfoArray) ? mailingInfoArray : [mailingInfoArray];
|
||||||
|
|
||||||
|
variables = [
|
||||||
|
{name: 'BASE_URL', content: BASE_URL},
|
||||||
|
].concat(variables || []);
|
||||||
|
|
||||||
|
// It's important to pass at least a user with its `preferences` as we need to check if he unsubscribed
|
||||||
|
mailingInfoArray = mailingInfoArray.map((mailingInfo) => {
|
||||||
|
return mailingInfo._id ? getUserInfo(mailingInfo, ['_id', 'email', 'name', 'canSend']) : mailingInfo;
|
||||||
|
}).filter((mailingInfo) => {
|
||||||
|
// Always send reset-password emails
|
||||||
|
// 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 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((mailingInfo) => {
|
||||||
|
return {
|
||||||
|
rcpt: mailingInfo.email,
|
||||||
|
vars: [
|
||||||
|
{
|
||||||
|
name: 'RECIPIENT_NAME',
|
||||||
|
content: mailingInfo.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'RECIPIENT_UNSUB_URL',
|
||||||
|
content: `/unsubscribe?code=${encrypt(JSON.stringify({
|
||||||
|
_id: mailingInfo._id,
|
||||||
|
email: mailingInfo.email,
|
||||||
|
}))}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
let temporaryPersonalVariables = {};
|
||||||
|
|
||||||
|
mailingInfoArray.forEach((mailingInfo) => {
|
||||||
|
temporaryPersonalVariables[mailingInfo.email] = {
|
||||||
|
name: mailingInfo.name,
|
||||||
|
_id: mailingInfo._id,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
personalVariables.forEach((singlePersonalVariables) => {
|
||||||
|
singlePersonalVariables.vars.push(
|
||||||
|
{
|
||||||
|
name: 'RECIPIENT_NAME',
|
||||||
|
content: temporaryPersonalVariables[singlePersonalVariables.rcpt].name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'RECIPIENT_UNSUB_URL',
|
||||||
|
content: `/unsubscribe?code=${encrypt(JSON.stringify({
|
||||||
|
_id: temporaryPersonalVariables[singlePersonalVariables.rcpt]._id,
|
||||||
|
email: singlePersonalVariables.rcpt,
|
||||||
|
}))}`,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (IS_PROD && mailingInfoArray.length > 0) {
|
||||||
|
request.post({
|
||||||
|
url: `${EMAIL_SERVER.url}/job`,
|
||||||
|
auth: {
|
||||||
|
user: EMAIL_SERVER.auth.user,
|
||||||
|
pass: EMAIL_SERVER.auth.password,
|
||||||
|
},
|
||||||
|
json: {
|
||||||
|
type: 'email',
|
||||||
|
data: {
|
||||||
|
emailType,
|
||||||
|
to: mailingInfoArray,
|
||||||
|
variables,
|
||||||
|
personalVariables,
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
priority: 'high',
|
||||||
|
attempts: 5,
|
||||||
|
backoff: {delay: 10 * 60 * 1000, type: 'fixed'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, (err) => logger.error(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
25
website/src/libs/api-v3/encryption.js
Normal file
25
website/src/libs/api-v3/encryption.js
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import {
|
||||||
|
createCipher,
|
||||||
|
createDecipher,
|
||||||
|
} from 'crypto';
|
||||||
|
import nconf from 'nconf';
|
||||||
|
|
||||||
|
// TODO check this is secure
|
||||||
|
const algorithm = 'aes-256-ctr';
|
||||||
|
const SESSION_SECRET = nconf.get('SESSION_SECRET');
|
||||||
|
|
||||||
|
export function encrypt (text) {
|
||||||
|
let cipher = createCipher(algorithm, SESSION_SECRET);
|
||||||
|
let crypted = cipher.update(text, 'utf8', 'hex');
|
||||||
|
|
||||||
|
crypted += cipher.final('hex');
|
||||||
|
return crypted;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function decrypt (text) {
|
||||||
|
let decipher = createDecipher(algorithm, SESSION_SECRET);
|
||||||
|
let dec = decipher.update(text, 'hex', 'utf8');
|
||||||
|
|
||||||
|
dec += decipher.final('utf8');
|
||||||
|
return dec;
|
||||||
|
}
|
||||||
@@ -12,7 +12,9 @@ if (IS_PROD) {
|
|||||||
// log errors to console too
|
// log errors to console too
|
||||||
} else {
|
} else {
|
||||||
logger
|
logger
|
||||||
.add(winston.transports.Console);
|
.add(winston.transports.Console, {
|
||||||
|
colorize: true,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default logger;
|
export default logger;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
var nconf = require('nconf');
|
var nconf = require('nconf');
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var utils = require('../libs/utils');
|
var utils = require('../libs/api-v2/utils');
|
||||||
var shared = require('../../../common');
|
var shared = require('../../../common');
|
||||||
var i18n = require('../libs/api-v2/i18n');
|
var i18n = require('../libs/api-v2/i18n');
|
||||||
var buildManifest = require('../libs/api-v2/buildManifest');
|
var buildManifest = require('../libs/api-v2/buildManifest');
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
import nconf from 'nconf';
|
import nconf from 'nconf';
|
||||||
import logger from './libs/api-v3/logger';
|
import logger from './libs/api-v3/logger';
|
||||||
import utils from './libs/utils';
|
|
||||||
import express from 'express';
|
import express from 'express';
|
||||||
import http from 'http';
|
import http from 'http';
|
||||||
// import path from 'path';
|
// import path from 'path';
|
||||||
@@ -15,7 +14,6 @@ import mongoose from 'mongoose';
|
|||||||
import Q from 'q';
|
import Q from 'q';
|
||||||
import domainMiddleware from './middlewares/api-v3/domain';
|
import domainMiddleware from './middlewares/api-v3/domain';
|
||||||
import attachMiddlewares from './middlewares/api-v3/index';
|
import attachMiddlewares from './middlewares/api-v3/index';
|
||||||
utils.setupConfig();
|
|
||||||
|
|
||||||
// Setup translations
|
// Setup translations
|
||||||
// let i18n = require('./libs/api-v2/i18n');
|
// let i18n = require('./libs/api-v2/i18n');
|
||||||
|
|||||||
Reference in New Issue
Block a user