From 12232dbe16c97d5fb4b8bb4aaa8a65da6d81d6eb Mon Sep 17 00:00:00 2001 From: Ivan Socolsky Date: Wed, 29 Apr 2015 00:34:18 -0300 Subject: [PATCH] email service --- lib/emailservice.js | 111 ++++++++++++++++++++++++++++ lib/server.js | 44 ++++++++--- lib/storage.js | 24 ++++-- lib/templates/new_tx_proposal.plain | 2 + 4 files changed, 164 insertions(+), 17 deletions(-) create mode 100644 lib/emailservice.js create mode 100644 lib/templates/new_tx_proposal.plain diff --git a/lib/emailservice.js b/lib/emailservice.js new file mode 100644 index 0000000..15e54fb --- /dev/null +++ b/lib/emailservice.js @@ -0,0 +1,111 @@ +'use strict'; + +var _ = require('lodash'); +var $ = require('preconditions').singleton(); +var async = require('async'); +var log = require('npmlog'); +log.debug = log.verbose; +var fs = require('fs'); + +var Model = require('./model'); + +var EMAIL_TYPES = { + 'NewTxProposal': { + filename: 'new_tx_proposal', + notifyCreator: false, + notifyDoer: false, + }, + 'NewOutgoingTx': { + filename: 'new_outgoing_tx', + notifyCreator: true, + notifyDoer: true, + }, + 'NewIncomingTx': { + filename: 'new_incoming_tx', + notifyCreator: true, + notifyDoer: true, + }, + 'TxProposalFinallyRejected': { + filename: 'txp_finally_rejected', + notifyCreator: true, + notifyDoer: false, + }, +}; + + +function EmailService(opts) { + this.storage = opts.storage; + this.lock = opts.lock; +}; + +EmailService.prototype._readTemplate = function(filename, cb) { + fs.readFile(__dirname + '/templates/' + filename + '.plain', 'utf8', function(err, template) { + var lines = template.split('\n'); + return cb(null, { + subject: _.template(lines[0]), + body: _.template(_.rest(lines).join('\n')), + }); + }); +}; + +EmailService.prototype._applyTemplate = function(template, data, cb) { + var result = _.mapValues(template, function(t) { + // TODO: If this throws exception, log and abort email generation + return t(data); + }); + return cb(null, result); +}; + +EmailService.prototype._generateFromNotification = function(notification, cb) { + var self = this; + + var emailType = EMAIL_TYPES[notification.type]; + if (!emailType) return cb(); + + self.storage.fetchPreferences(notification.walletId, null, function(err, preferences) { + if (_.isEmpty(preferences)) return cb(); + + var addressesByCopayer = _.reduce(preferences, function(memo, p) { + if (p.email) { + memo[p.copayerId] = p.email; + } + return memo; + }, {}); + + if (_.isEmpty(addressesByCopayer)) return cb(); + + self._readTemplate(emailType.filename, function(err, template) { + if (err) return cb(err); + + self._applyTemplate(template, notification.data, function(err, content) { + if (err) return cb(err); + + _.each(addressesByCopayer, function(emailAddress, copayerId) { + var email = Model.Email.create({ + walletId: notification.walletId, + copayerId: copayerId, + to: emailAddress, + subject: content.subject, + body: content.body, + }); + self.storage.storeEmail(email, function(err) { + return cb(err); + }); + }); + }); + }); + }); + + return cb(); +}; + +EmailService.prototype._send = function(cb) { + var self = this; + + this.lock.runLocked('emails', cb, function() { + //self._fetchUnsentEmails(); + + }); +}; + +module.exports = EmailService; diff --git a/lib/server.js b/lib/server.js index 92b46f2..d58263f 100644 --- a/lib/server.js +++ b/lib/server.js @@ -18,13 +18,19 @@ var Lock = require('./lock'); var Storage = require('./storage'); var MessageBroker = require('./messagebroker'); var BlockchainExplorer = require('./blockchainexplorer'); +var EmailService = require('./emailservice'); var Model = require('./model'); var Wallet = Model.Wallet; var initialized = false; -var lock, storage, blockchainExplorer, blockchainExplorerOpts; + +var lock; +var storage; +var blockchainExplorer; +var blockchainExplorerOpts; var messageBroker; +var emailService; /** @@ -41,6 +47,7 @@ function WalletService() { this.blockchainExplorerOpts = blockchainExplorerOpts; this.messageBroker = messageBroker; this.notifyTicker = 0; + this.emailService = emailService; }; /** @@ -58,20 +65,28 @@ WalletService.initialize = function(opts, cb) { blockchainExplorer = opts.blockchainExplorer; blockchainExplorerOpts = opts.blockchainExplorerOpts; - if (initialized) - return cb(); + if (initialized) return cb(); function initStorage(cb) { if (opts.storage) { storage = opts.storage; return cb(); + } else { + var newStorage = new Storage(); + newStorage.connect(opts.storageOpts, function(err) { + if (err) return cb(err); + storage = newStorage; + return cb(); + }); } - var newStorage = new Storage(); - newStorage.connect(opts.storageOpts, function(err) { - if (err) return cb(err); - storage = newStorage; - return cb(); + }; + + function initEmailService(cb) { + emailService = new EmailService({ + lock: lock, + storage: storage, }); + return cb(); }; function initMessageBroker(cb) { @@ -91,11 +106,15 @@ WalletService.initialize = function(opts, cb) { function(next) { initMessageBroker(next); }, + function(next) { + initEmailService(next); + }, ], function(err) { if (err) { log.error('Could not initialize', err); throw err; } + ], function() { initialized = true; return cb(); }); @@ -331,9 +350,12 @@ WalletService.prototype._notify = function(type, data, opts, cb) { creatorId: opts.isGlobal ? null : copayerId, walletId: walletId, }); - this.storage.storeNotification(walletId, n, function() { - self.messageBroker.send(n); - if (cb) return cb(); + + this.storage.storeNotification(walletId, notification, function() { + self.messageBroker.send(notification); + self.emailService._generateFromNotification(notification, function() { + if (cb) return cb(); + }); }); }; diff --git a/lib/storage.js b/lib/storage.js index 8b29a3c..b340f3e 100644 --- a/lib/storage.js +++ b/lib/storage.js @@ -356,13 +356,25 @@ Storage.prototype.fetchAddress = function(address, cb) { }; Storage.prototype.fetchPreferences = function(walletId, copayerId, cb) { - this.db.collection(collections.PREFERENCES).findOne({ + this.db.collection(collections.PREFERENCES).find({ walletId: walletId, - copayerId: copayerId, - }, function(err, result) { + }).toArray(function(err, result) { if (err) return cb(err); + + if (copayerId) { + result = _.find(result, { + copayerId: copayerId + }); + } if (!result) return cb(); - return cb(null, Model.Preferences.fromObj(result)); + + var preferences = _.map([].concat(result), function(r) { + return Model.Preferences.fromObj(r); + }); + if (copayerId) { + preferences = preferences[0]; + } + return cb(null, preferences); }); }; @@ -379,7 +391,7 @@ Storage.prototype.storePreferences = function(preferences, cb) { Storage.prototype.storeEmail = function(email, cb) { this.db.collection(collections.EMAIL_QUEUE).update({ id: email.id, - }, txp, { + }, email, { w: 1, upsert: true, }, cb); @@ -388,7 +400,7 @@ Storage.prototype.storeEmail = function(email, cb) { Storage.prototype.fetchUnsentEmails = function(cb) { this.db.collection(collections.EMAIL_QUEUE).find({ status: 'pending', - }, function(err, result) { + }).toArray(function(err, result) { if (err) return cb(err); if (!result) return cb(); return cb(null, Model.Email.fromObj(result)); diff --git a/lib/templates/new_tx_proposal.plain b/lib/templates/new_tx_proposal.plain new file mode 100644 index 0000000..2c39404 --- /dev/null +++ b/lib/templates/new_tx_proposal.plain @@ -0,0 +1,2 @@ +New transaction proposal! +A new transaction proposal has been created by another copayer.