diff --git a/src/mail_templates/app_update_available.ejs b/src/mail_templates/app_updates_available.ejs similarity index 62% rename from src/mail_templates/app_update_available.ejs rename to src/mail_templates/app_updates_available.ejs index 24ebc6afe..cec8c2a69 100644 --- a/src/mail_templates/app_update_available.ejs +++ b/src/mail_templates/app_updates_available.ejs @@ -2,10 +2,13 @@ Dear Cloudron Admin, -A new version <%= updateInfo.manifest.version %> of the app '<%= app.manifest.title %>' installed at <%= app.fqdn %> is available! +<% for (var i = 0; i < apps.length; i++) { -%> +A new version <%= apps[i].updateInfo.manifest.version %> of the app '<%= apps[i].app.manifest.title %>' installed at <%= apps[i].app.fqdn %> is available! Changes: -<%= updateInfo.manifest.changelog %> +<%= apps[i].updateInfo.manifest.changelog %> + +<% }} -%> <% if (!hasSubscription) { -%> *Keep your Cloudron automatically up-to-date and secure by upgrading to a paid plan at* <%= webadminUrl %>/#/settings @@ -24,14 +27,16 @@ Sent at: <%= new Date().toUTCString() %>

Dear <%= cloudronName %> Admin,

+<% for (var i = 0; i < apps.length; i++) { -%>

- A new version <%= updateInfo.manifest.version %> of the app '<%= app.manifest.title %>' installed at <%= app.fqdn %> is available! + A new version <%= apps[i].updateInfo.manifest.version %> of the app '<%= apps[i].app.manifest.title %>' installed at <%= apps[i].app.fqdn %> is available!

Changelog:
- <%- changelogHTML %> + <%- apps[i].changelogHTML %>
+<% } -%> <% if (!hasSubscription) { %>

Keep your Cloudron automatically up-to-date and secure by upgrading to a paid plan.

diff --git a/src/mailer.js b/src/mailer.js index 697c642c1..21651a142 100644 --- a/src/mailer.js +++ b/src/mailer.js @@ -5,7 +5,7 @@ exports = module.exports = { userRemoved: userRemoved, adminChanged: adminChanged, passwordReset: passwordReset, - appUpdateAvailable: appUpdateAvailable, + appUpdatesAvailable: appUpdatesAvailable, sendDigest: sendDigest, sendInvite: sendInvite, @@ -295,24 +295,24 @@ function appDied(mailTo, app) { }); } -function appUpdateAvailable(mailTo, app, hasSubscription, info, callback) { +function appUpdatesAvailable(mailTo, apps, hasSubscription, callback) { assert.strictEqual(typeof mailTo, 'string'); - assert.strictEqual(typeof app, 'object'); + assert.strictEqual(typeof apps, '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); var converter = new showdown.Converter(); + apps.forEach(function (app) { + app.changelogHTML = converter.makeHtml(app.updateInfo.manifest.changelog); + }); var templateData = { webadminUrl: config.adminOrigin(), hasSubscription: hasSubscription, - app: app, - updateInfo: info, - changelogHTML: converter.makeHtml(info.manifest.changelog), + apps: apps, cloudronName: mailConfig.cloudronName, cloudronAvatarUrl: config.adminOrigin() + '/api/v1/cloudron/avatar' }; @@ -326,9 +326,9 @@ function appUpdateAvailable(mailTo, app, hasSubscription, info, callback) { var mailOptions = { from: mailConfig.notificationFrom, 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) + subject: `New app updates available for ${mailConfig.cloudronName}`, + text: render('app_updates_available.ejs', templateDataText), + html: render('app_updates_available.ejs', templateDataHTML) }; sendMail(mailOptions, callback); diff --git a/src/updatechecker.js b/src/updatechecker.js index 0289382bf..89f802be6 100644 --- a/src/updatechecker.js +++ b/src/updatechecker.js @@ -75,6 +75,8 @@ function checkAppUpdates(callback) { if (error) return callback(error); const autoupdatesEnabled = (result !== constants.AUTOUPDATE_PATTERN_NEVER); + var notificationPending = []; + apps.getAll(function (error, result) { if (error) return callback(error); @@ -107,17 +109,28 @@ function checkAppUpdates(callback) { if (autoupdatesEnabled && !updateIsBlocked) return iteratorDone(); debug('Notifying 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 iteratorDone(error); - - async.eachSeries(admins, (admin, done) => mailer.appUpdateAvailable(admin.email, app, true /* subscription */, updateInfo, done), iteratorDone); + notificationPending.push({ + app: app, + updateInfo: updateInfo }); + + iteratorDone(); }); }); }, function () { newState.box = loadState().box; // preserve the latest box state information saveState(newState); - callback(); + + if (notificationPending.length === 0) return callback(); + + users.getAllAdmins(function (error, admins) { + if (error) { + console.error(error); + return callback(); + } + + async.eachSeries(admins, (admin, done) => mailer.appUpdatesAvailable(admin.email, notificationPending, true /* subscription */, done), callback); + }); }); }); }