diff --git a/src/cron.js b/src/cron.js index 8f62a1e53..34fb1b8ab 100644 --- a/src/cron.js +++ b/src/cron.js @@ -204,7 +204,7 @@ function recreateJobs(tz) { if (gJobs.digestEmail) gJobs.digestEmail.stop(); gJobs.digestEmail = new CronJob({ cronTime: '00 00 00 * * 3', // every wednesday - onTick: digest.send, + onTick: digest.send.bind(null, NOOP_CALLBACK), start: true, timeZone: tz }); diff --git a/src/digest.js b/src/digest.js index 9fa0fda4e..f9aa0e341 100644 --- a/src/digest.js +++ b/src/digest.js @@ -1,25 +1,26 @@ 'use strict'; -var debug = require('debug')('box:digest'), +var assert = require('assert'), + async = require('async'), + debug = require('debug')('box:digest'), eventlog = require('./eventlog.js'), - updatechecker = require('./updatechecker.js'), mailer = require('./mailer.js'), - settings = require('./settings.js'); - -var NOOP_CALLBACK = function (error) { if (error) debug(error); }; + settings = require('./settings.js'), + updatechecker = require('./updatechecker.js'), + users = require('./users.js'); exports = module.exports = { send: send }; function send(callback) { - callback = callback || NOOP_CALLBACK; + assert.strictEqual(typeof callback, 'function'); settings.getEmailDigest(function (error, enabled) { if (error) return callback(error); if (!enabled) { - debug('Email digest is disabled'); + debug('send: email digest is disabled'); return callback(); } @@ -52,11 +53,13 @@ function send(callback) { usersRemoved: usersRemoved // unused because we don't have username to work with }; - // always send digest for backup failure notification debug('send: sending digest email', info); - mailer.sendDigest(info); - callback(); + users.getAllAdmins(function (error, admins) { + if (error) return callback(error); + + async.eachSeries(admins, (admin, done) => mailer.sendDigest(admin.email, info, done), callback); + }); }); }); } diff --git a/src/mailer.js b/src/mailer.js index ea4eff61e..261cf742f 100644 --- a/src/mailer.js +++ b/src/mailer.js @@ -37,7 +37,6 @@ var assert = require('assert'), settings = require('./settings.js'), showdown = require('showdown'), smtpTransport = require('nodemailer-smtp-transport'), - users = require('./users.js'), util = require('util'); var NOOP_CALLBACK = function (error) { if (error) debug(error); }; @@ -46,38 +45,20 @@ var MAIL_TEMPLATES_DIR = path.join(__dirname, 'mail_templates'); var gMailQueue = [ ]; -function getAdminEmails(callback) { - users.getAllAdmins(function (error, admins) { - if (error) return callback(error); - - if (admins.length === 0) return callback(new Error('No admins on this cloudron')); // box not activated yet - - var adminEmails = [ ]; - admins.forEach(function (admin) { adminEmails.push(admin.email); }); - - callback(null, adminEmails); - }); -} - // This will collect the most common details required for notification emails function getMailConfig(callback) { assert.strictEqual(typeof callback, 'function'); - getAdminEmails(function (error, adminEmails) { - if (error) return callback(error); + settings.getCloudronName(function (error, cloudronName) { + // this is not fatal + if (error) { + debug(error); + cloudronName = 'Cloudron'; + } - settings.getCloudronName(function (error, cloudronName) { - // this is not fatal - if (error) { - debug(error); - cloudronName = 'Cloudron'; - } - - callback(null, { - adminEmails: adminEmails, - cloudronName: cloudronName, - notificationFrom: `"${cloudronName}" ` - }); + callback(null, { + cloudronName: cloudronName, + notificationFrom: `"${cloudronName}" ` }); }); } @@ -340,10 +321,12 @@ function appDied(mailTo, app) { }); } -function appUpdateAvailable(app, hasSubscription, info) { +function appUpdateAvailable(mailTo, app, hasSubscription, info, callback) { + assert.strictEqual(typeof mailTo, 'string'); assert.strictEqual(typeof app, 'object'); assert.strictEqual(typeof hasSubscription, 'boolean'); assert.strictEqual(typeof info, 'object'); + assert.strictEqual(typeof callback, 'function'); getMailConfig(function (error, mailConfig) { if (error) return debug('Error getting mail details:', error); @@ -368,18 +351,22 @@ function appUpdateAvailable(app, hasSubscription, info) { var mailOptions = { from: mailConfig.notificationFrom, - to: mailConfig.adminEmails.join(', '), + to: mailTo, subject: util.format('App %s has a new update available', app.fqdn), text: render('app_update_available.ejs', templateDataText), html: render('app_update_available.ejs', templateDataHTML) }; enqueue(mailOptions); + + callback(); }); } -function sendDigest(info) { +function sendDigest(mailTo, info, callback) { + assert.strictEqual(typeof mailTo, 'string'); assert.strictEqual(typeof info, 'object'); + assert.strictEqual(typeof callback, 'function'); getMailConfig(function (error, mailConfig) { if (error) return debug('Error getting mail details:', error); @@ -399,13 +386,15 @@ function sendDigest(info) { var mailOptions = { from: mailConfig.notificationFrom, - to: mailConfig.adminEmails.join(', '), + to: mailTo, subject: util.format('[%s] Weekly activity digest', mailConfig.cloudronName), text: render('digest.ejs', templateDataText), html: render('digest.ejs', templateDataHTML) }; enqueue(mailOptions); + + callback(); }); } diff --git a/src/updatechecker.js b/src/updatechecker.js index f5469c60b..7061a630c 100644 --- a/src/updatechecker.js +++ b/src/updatechecker.js @@ -21,7 +21,8 @@ var apps = require('./apps.js'), notifications = require('./notifications.js'), paths = require('./paths.js'), safe = require('safetydance'), - settings = require('./settings.js'); + settings = require('./settings.js'), + users = require('./users.js'); var gAppUpdateInfo = { }, // id -> update info { creationDate, manifest } gBoxUpdateInfo = null; // { version, changelog, upgrade, sourceTarballUrl } @@ -107,20 +108,25 @@ function checkAppUpdates(callback) { // always send notifications if user is on the free plan if (appstore.isFreePlan(result)) { debug('Notifying user of app update for %s from %s to %s', app.id, app.manifest.version, updateInfo.manifest.version); - mailer.appUpdateAvailable(app, false /* subscription */, updateInfo); - return iteratorDone(); + users.getAllAdmins(function (error, admins) { + if (error) return callback(error); + + async.eachSeries(admins, (admin, done) => mailer.appUpdateAvailable(admin.email, app, false /* subscription */, updateInfo, done), iteratorDone); + }); + return; } // only send notifications if update pattern is 'never' settings.getAppAutoupdatePattern(function (error, result) { - if (error) { - debug(error); - } else if (result === constants.AUTOUPDATE_PATTERN_NEVER) { - debug('Notifying user of app update for %s from %s to %s', app.id, app.manifest.version, updateInfo.manifest.version); - mailer.appUpdateAvailable(app, true /* hasSubscription */, updateInfo); - } + if (error) return iteratorDone(error); + if (result !== constants.AUTOUPDATE_PATTERN_NEVER) return iteratorDone(); - iteratorDone(); + debug('Notifying user of app update for %s from %s to %s', app.id, app.manifest.version, updateInfo.manifest.version); + users.getAllAdmins(function (error, admins) { + if (error) return callback(error); + + async.eachSeries(admins, (admin, done) => mailer.appUpdateAvailable(admin.email, app, true /* subscription */, updateInfo, done), iteratorDone); + }); }); }); });