|
|
@ -7,6 +7,7 @@ var Mustache = require('mustache'); |
|
|
|
var log = require('npmlog'); |
|
|
|
log.debug = log.verbose; |
|
|
|
var fs = require('fs'); |
|
|
|
var path = require('path'); |
|
|
|
var nodemailer = require('nodemailer'); |
|
|
|
|
|
|
|
var WalletUtils = require('bitcore-wallet-utils'); |
|
|
@ -49,10 +50,33 @@ function EmailService() {}; |
|
|
|
EmailService.prototype.start = function(opts, cb) { |
|
|
|
opts = opts || {}; |
|
|
|
|
|
|
|
function _readDirectories(basePath, cb) { |
|
|
|
fs.readdir(basePath, function(err, files) { |
|
|
|
if (err) return cb(err); |
|
|
|
async.filter(files, function(file, next) { |
|
|
|
fs.stat(path.join(basePath, file), function(err, stats) { |
|
|
|
return next(!err && stats.isDirectory()); |
|
|
|
}); |
|
|
|
}, function(dirs) { |
|
|
|
return cb(null, dirs); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
var self = this; |
|
|
|
|
|
|
|
self.defaultLanguage = opts.defaultLanguage || 'en'; |
|
|
|
self.defaultUnit = opts.defaultUnit || 'btc'; |
|
|
|
self.templatePath = path.normalize((opts.templatePath || (__dirname + '/templates')) + '/'); |
|
|
|
|
|
|
|
async.parallel([ |
|
|
|
|
|
|
|
function(done) { |
|
|
|
_readDirectories(self.templatePath, function(err, res) { |
|
|
|
self.availableLanguages = res; |
|
|
|
done(err); |
|
|
|
}); |
|
|
|
}, |
|
|
|
function(done) { |
|
|
|
if (opts.storage) { |
|
|
|
self.storage = opts.storage; |
|
|
@ -85,19 +109,33 @@ EmailService.prototype.start = function(opts, cb) { |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
EmailService.prototype._compileTemplate = function(template) { |
|
|
|
var lines = template.split('\n'); |
|
|
|
return { |
|
|
|
subject: lines[0], |
|
|
|
body: _.rest(lines).join('\n'), |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
// TODO: cache for X minutes
|
|
|
|
EmailService.prototype._readTemplate = function(filename, cb) { |
|
|
|
fs.readFile(__dirname + '/templates/' + filename + '.plain', 'utf8', function(err, template) { |
|
|
|
EmailService.prototype._readTemplateFile = function(language, filename, cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
var fullFilename = path.join(self.templatePath, language, filename); |
|
|
|
fs.readFile(fullFilename, 'utf8', function(err, template) { |
|
|
|
if (err) { |
|
|
|
log.error('Could not read template file ' + filename, err); |
|
|
|
return cb(err); |
|
|
|
return cb(new Error('Could not read template file ' + fullFilename, err)); |
|
|
|
} |
|
|
|
var lines = template.split('\n'); |
|
|
|
return cb(null, { |
|
|
|
subject: lines[0], |
|
|
|
body: _.rest(lines).join('\n'), |
|
|
|
}); |
|
|
|
return cb(null, template); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
// TODO: cache for X minutes
|
|
|
|
EmailService.prototype._loadTemplate = function(emailType, recipient, extension, cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
self._readTemplateFile(recipient.language, emailType.filename + extension, function(err, template) { |
|
|
|
if (err) return cb(err); |
|
|
|
return cb(null, self._compileTemplate(template)); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
@ -126,9 +164,18 @@ EmailService.prototype._getRecipientsList = function(notification, emailType, cb |
|
|
|
|
|
|
|
usedEmails[p.email] = true; |
|
|
|
if (notification.creatorId == p.copayerId && !emailType.notifyDoer) return; |
|
|
|
if (!_.contains(self.availableLanguages, p.language)) { |
|
|
|
if (p.language) { |
|
|
|
log.warn('Language for email "' + p.language + '" not available.'); |
|
|
|
} |
|
|
|
p.language = self.defaultLanguage; |
|
|
|
} |
|
|
|
|
|
|
|
return { |
|
|
|
copayerId: p.copayerId, |
|
|
|
emailAddress: p.email |
|
|
|
emailAddress: p.email, |
|
|
|
language: p.language, |
|
|
|
unit: p.unit || self.defaultUnit, |
|
|
|
}; |
|
|
|
})); |
|
|
|
|
|
|
@ -136,13 +183,13 @@ EmailService.prototype._getRecipientsList = function(notification, emailType, cb |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
EmailService.prototype._getDataForTemplate = function(notification, cb) { |
|
|
|
EmailService.prototype._getDataForTemplate = function(notification, recipient, cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
var data = _.cloneDeep(notification.data); |
|
|
|
data.subjectPrefix = _.trim(self.subjectPrefix) + ' '; |
|
|
|
if (data.amount) { |
|
|
|
data.amount = WalletUtils.formatAmount(+data.amount, 'bit') + ' bits'; |
|
|
|
data.amount = WalletUtils.formatAmount(+data.amount, recipient.unit) + ' ' + recipient.unit; |
|
|
|
} |
|
|
|
self.storage.fetchWallet(notification.walletId, function(err, wallet) { |
|
|
|
if (err) return cb(err); |
|
|
@ -177,8 +224,11 @@ EmailService.prototype._send = function(email, cb) { |
|
|
|
from: email.from, |
|
|
|
to: email.to, |
|
|
|
subject: email.subject, |
|
|
|
text: email.body, |
|
|
|
text: email.bodyPlain, |
|
|
|
}; |
|
|
|
if (email.bodyHtml) { |
|
|
|
mailOptions.html = email.bodyHtml; |
|
|
|
} |
|
|
|
self.mailer.sendMail(mailOptions, function(err, result) { |
|
|
|
if (err) { |
|
|
|
log.error('An error occurred when trying to send email to ' + email.to, err); |
|
|
@ -189,6 +239,40 @@ EmailService.prototype._send = function(email, cb) { |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
EmailService.prototype._readAndApplyTemplates = function(notification, emailType, recipientsList, cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
|
async.map(recipientsList, function(recipient, next) { |
|
|
|
async.waterfall([ |
|
|
|
|
|
|
|
function(next) { |
|
|
|
self._getDataForTemplate(notification, recipient, next); |
|
|
|
}, |
|
|
|
function(data, next) { |
|
|
|
async.map(['plain', 'html'], function(type, next) { |
|
|
|
self._loadTemplate(emailType, recipient, '.' + type, function(err, template) { |
|
|
|
if (err && type == 'html') return next(); |
|
|
|
if (err) return next(err); |
|
|
|
self._applyTemplate(template, data, function(err, res) { |
|
|
|
return next(err, [type, res]); |
|
|
|
}); |
|
|
|
}); |
|
|
|
}, function(err, res) { |
|
|
|
return next(err, _.zipObject(res)); |
|
|
|
}); |
|
|
|
}, |
|
|
|
function(result, next) { |
|
|
|
next(null, result); |
|
|
|
}, |
|
|
|
], function(err, res) { |
|
|
|
next(err, [recipient.language, res]); |
|
|
|
}); |
|
|
|
}, function(err, res) { |
|
|
|
return cb(err, _.zipObject(res)); |
|
|
|
}); |
|
|
|
}; |
|
|
|
|
|
|
|
EmailService.prototype.sendEmail = function(notification, cb) { |
|
|
|
var self = this; |
|
|
|
|
|
|
@ -211,30 +295,19 @@ EmailService.prototype.sendEmail = function(notification, cb) { |
|
|
|
async.waterfall([ |
|
|
|
|
|
|
|
function(next) { |
|
|
|
async.parallel([ |
|
|
|
|
|
|
|
function(next) { |
|
|
|
self._readTemplate(emailType.filename, next); |
|
|
|
}, |
|
|
|
function(next) { |
|
|
|
self._getDataForTemplate(notification, next); |
|
|
|
}, |
|
|
|
], function(err, res) { |
|
|
|
next(err, res[0], res[1]); |
|
|
|
}); |
|
|
|
}, |
|
|
|
function(template, data, next) { |
|
|
|
self._applyTemplate(template, data, next); |
|
|
|
self._readAndApplyTemplates(notification, emailType, recipientsList, next); |
|
|
|
}, |
|
|
|
function(content, next) { |
|
|
|
function(contents, next) { |
|
|
|
async.map(recipientsList, function(recipient, next) { |
|
|
|
var content = contents[recipient.language]; |
|
|
|
var email = Model.Email.create({ |
|
|
|
walletId: notification.walletId, |
|
|
|
copayerId: recipient.copayerId, |
|
|
|
from: self.from, |
|
|
|
to: recipient.emailAddress, |
|
|
|
subject: content.subject, |
|
|
|
body: content.body, |
|
|
|
subject: content.plain.subject, |
|
|
|
bodyPlain: content.plain.body, |
|
|
|
bodyHtml: content.html ? content.html.body : null, |
|
|
|
notificationId: notification.id, |
|
|
|
}); |
|
|
|
self.storage.storeEmail(email, function(err) { |
|
|
|