diff --git a/CHANGES b/CHANGES
index 021c31bcd..46c7466e5 100644
--- a/CHANGES
+++ b/CHANGES
@@ -2278,4 +2278,5 @@
* clone: save and restore app config
* app import: restore icon, tag, label, proxy configs etc
* sieve: fix redirects to not do SRS
+* notifications are now system level instead of per-user
diff --git a/migrations/20210528205138-notifications-drop-userId.js b/migrations/20210528205138-notifications-drop-userId.js
new file mode 100644
index 000000000..1d759799e
--- /dev/null
+++ b/migrations/20210528205138-notifications-drop-userId.js
@@ -0,0 +1,13 @@
+'use strict';
+
+exports.up = function(db, callback) {
+ db.runSql('ALTER TABLE notifications DROP COLUMN userId', function (error) {
+ if (error) return callback(error);
+
+ db.runSql('DELETE FROM notifications', callback); // just clear notifications table
+ });
+};
+
+exports.down = function(db, callback) {
+ db.runSql('ALTER TABLE notifications ADD COLUMN userId VARCHAR(128) NOT NULL', callback);
+};
diff --git a/migrations/schema.sql b/migrations/schema.sql
index 0c096355e..928141437 100644
--- a/migrations/schema.sql
+++ b/migrations/schema.sql
@@ -234,7 +234,6 @@ CREATE TABLE IF NOT EXISTS tasks(
CREATE TABLE IF NOT EXISTS notifications(
id int NOT NULL AUTO_INCREMENT,
- userId VARCHAR(128) NOT NULL,
eventId VARCHAR(128), // reference to eventlog. can be null
title VARCHAR(512) NOT NULL,
message TEXT,
diff --git a/package-lock.json b/package-lock.json
index 5cb916dd3..5c5da8c28 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -208,7 +208,7 @@
},
"amdefine": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz",
+ "resolved": false,
"integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=",
"dev": true
},
@@ -650,7 +650,7 @@
},
"code-point-at": {
"version": "1.1.0",
- "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
+ "resolved": false,
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
"dev": true
},
@@ -2154,7 +2154,7 @@
},
"is-arrayish": {
"version": "0.2.1",
- "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "resolved": false,
"integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
"dev": true
},
@@ -3351,7 +3351,7 @@
},
"number-is-nan": {
"version": "1.0.1",
- "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
+ "resolved": false,
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
"dev": true
},
@@ -3440,7 +3440,7 @@
},
"parse-json": {
"version": "2.2.0",
- "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz",
+ "resolved": false,
"integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=",
"dev": true,
"requires": {
@@ -4098,50 +4098,6 @@
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
},
- "showdown": {
- "version": "1.9.1",
- "resolved": "https://registry.npmjs.org/showdown/-/showdown-1.9.1.tgz",
- "integrity": "sha512-9cGuS382HcvExtf5AHk7Cb4pAeQQ+h0eTr33V1mu+crYWV4KvWAw6el92bDrqGEk5d46Ai/fhbEUwqJ/mTCNEA==",
- "requires": {
- "yargs": "^14.2"
- },
- "dependencies": {
- "yargs": {
- "version": "14.2.2",
- "resolved": "https://registry.npmjs.org/yargs/-/yargs-14.2.2.tgz",
- "integrity": "sha512-/4ld+4VV5RnrynMhPZJ/ZpOCGSCeghMykZ3BhdFBDa9Wy/RH6uEGNWDJog+aUlq+9OM1CFTgtYRW5Is1Po9NOA==",
- "requires": {
- "cliui": "^5.0.0",
- "decamelize": "^1.2.0",
- "find-up": "^3.0.0",
- "get-caller-file": "^2.0.1",
- "require-directory": "^2.1.1",
- "require-main-filename": "^2.0.0",
- "set-blocking": "^2.0.0",
- "string-width": "^3.0.0",
- "which-module": "^2.0.0",
- "y18n": "^4.0.0",
- "yargs-parser": "^15.0.0"
- },
- "dependencies": {
- "y18n": {
- "version": "4.0.3",
- "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
- "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ=="
- }
- }
- },
- "yargs-parser": {
- "version": "15.0.1",
- "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-15.0.1.tgz",
- "integrity": "sha512-0OAMV2mAZQrs3FkNpDQcBk1x5HXb8X4twADss4S0Iuk+2dGnLOE/fRHrsYm542GduMveyA77OF4wrNJuanRCWw==",
- "requires": {
- "camelcase": "^5.0.0",
- "decamelize": "^1.2.0"
- }
- }
- }
- },
"signal-exit": {
"version": "3.0.2",
"resolved": false,
diff --git a/package.json b/package.json
index b33103fd3..172962e54 100644
--- a/package.json
+++ b/package.json
@@ -60,7 +60,6 @@
"s3-block-read-stream": "^0.5.0",
"safetydance": "^2.0.1",
"semver": "^7.3.5",
- "showdown": "^1.9.1",
"speakeasy": "^2.0.0",
"split": "^1.0.1",
"superagent": "^6.1.0",
diff --git a/src/apphealthmonitor.js b/src/apphealthmonitor.js
index 702275163..dd937bbef 100644
--- a/src/apphealthmonitor.js
+++ b/src/apphealthmonitor.js
@@ -1,6 +1,6 @@
'use strict';
-var appdb = require('./appdb.js'),
+const appdb = require('./appdb.js'),
apps = require('./apps.js'),
assert = require('assert'),
async = require('async'),
diff --git a/src/eventlog.js b/src/eventlog.js
index b5d6d1e68..e238e23d2 100644
--- a/src/eventlog.js
+++ b/src/eventlog.js
@@ -80,6 +80,7 @@ const assert = require('assert'),
debug = require('debug')('box:eventlog'),
eventlogdb = require('./eventlogdb.js'),
notifications = require('./notifications.js'),
+ safe = require('safetydance'),
util = require('util'),
uuid = require('uuid');
@@ -93,12 +94,12 @@ function add(action, source, data, callback) {
callback = callback || NOOP_CALLBACK;
- eventlogdb.add(uuid.v4(), action, source, data, function (error, id) {
+ eventlogdb.add(uuid.v4(), action, source, data, async function (error, id) {
if (error) return callback(error);
callback(null, { id: id });
- notifications.onEvent(id, action, source, data, NOOP_CALLBACK);
+ await safe(notifications.onEvent(id, action, source, data));
});
}
@@ -110,12 +111,12 @@ function upsert(action, source, data, callback) {
callback = callback || NOOP_CALLBACK;
- eventlogdb.upsert(uuid.v4(), action, source, data, function (error, id) {
+ eventlogdb.upsert(uuid.v4(), action, source, data, async function (error, id) {
if (error) return callback(error);
callback(null, { id: id });
- notifications.onEvent(id, action, source, data, NOOP_CALLBACK);
+ await safe(notifications.onEvent(id, action, source, data));
});
}
diff --git a/src/mail_templates/app_updates_available.ejs b/src/mail_templates/app_updates_available.ejs
deleted file mode 100644
index 7e083017e..000000000
--- a/src/mail_templates/app_updates_available.ejs
+++ /dev/null
@@ -1,55 +0,0 @@
-<%if (format === 'text') { %>
-
-Dear Cloudron Admin,
-
-<% for (var i = 0; i < apps.length; i++) { -%>
-The app '<%= apps[i].app.manifest.title %>' installed at <%= apps[i].app.fqdn %> has an update available.
-
-<%= apps[i].app.manifest.title %> v<%= apps[i].updateInfo.manifest.version %> changes:
-<%= apps[i].updateInfo.manifest.changelog %>
-
-<% } -%>
-
-Update now at <%= webadminUrl %>
-
-Powered by https://cloudron.io
-
-Sent at: <%= new Date().toUTCString() %>
-
-<% } else { %>
-
-
-
-
-
- Dear <%= cloudronName %> Admin,
-
-
-
-
-<% for (var i = 0; i < apps.length; i++) { -%>
-
- The app '<%= apps[i].app.manifest.title %>' installed at <%= apps[i].app.fqdn %> has an update available.
-
-
-
<%= apps[i].app.manifest.title %> v<%= apps[i].updateInfo.manifest.version %> changes:
- <%- apps[i].changelogHTML %>
-
-
-<% } -%>
-
-
-
-
Update now
-
-
-
-
-
- Powered by
Cloudron.
- Sent at: <%= new Date().toUTCString() %>
-
-
-
-
-<% } %>
diff --git a/src/mail_templates/box_update_available.ejs b/src/mail_templates/box_update_available.ejs
deleted file mode 100644
index 9d0c7128b..000000000
--- a/src/mail_templates/box_update_available.ejs
+++ /dev/null
@@ -1,45 +0,0 @@
-<%if (format === 'text') { %>
-
-Dear <%= cloudronName %> Admin,
-
-Cloudron v<%= newBoxVersion %> is now available!
-
-Changes:
-<% for (var i = 0; i < changelog.length; i++) { %>
- * <%- changelog[i] %>
-<% } %>
-
-Powered by https://cloudron.io
-
-Sent at: <%= new Date().toUTCString() %>
-
-<% } else { %>
-
-
-
-
-
-Dear <%= cloudronName %> Admin,
-
-
-
- Cloudron v<%= newBoxVersion %> is now available!
-
-
-
Changes:
-
- <% for (var i = 0; i < changelogHTML.length; i++) { %>
- - <%- changelogHTML[i] %>
- <% } %>
-
-
-
-
-
-
-
-
-
-<% } %>
diff --git a/src/mail_templates/box_update_error.ejs b/src/mail_templates/box_update_error.ejs
deleted file mode 100644
index 8a792eb8c..000000000
--- a/src/mail_templates/box_update_error.ejs
+++ /dev/null
@@ -1,20 +0,0 @@
-<%if (format === 'text') { %>
-
-Dear Cloudron Admin,
-
-Cloudron update failed because of the following reason:
-
--------------------------------------
-
-<%- message %>
-
--------------------------------------
-
-
-Powered by https://cloudron.io
-
-Sent at: <%= new Date().toUTCString() %>
-
-<% } else { %>
-
-<% } %>
diff --git a/src/mailer.js b/src/mailer.js
index fd3931fff..f5b43a5a6 100644
--- a/src/mailer.js
+++ b/src/mailer.js
@@ -2,8 +2,6 @@
exports = module.exports = {
passwordReset,
- boxUpdateAvailable,
- appUpdatesAvailable,
sendInvite,
sendNewLoginLocation,
@@ -11,14 +9,13 @@ exports = module.exports = {
backupFailed,
certificateRenewalError,
- boxUpdateError,
sendTestMail,
_mailQueue: [] // accumulate mails in test mode
};
-var assert = require('assert'),
+const assert = require('assert'),
BoxError = require('./boxerror.js'),
debug = require('debug')('box:mailer'),
ejs = require('ejs'),
@@ -27,13 +24,12 @@ var assert = require('assert'),
path = require('path'),
safe = require('safetydance'),
settings = require('./settings.js'),
- showdown = require('showdown'),
translation = require('./translation.js'),
smtpTransport = require('nodemailer-smtp-transport');
-var NOOP_CALLBACK = function (error) { if (error) debug(error); };
+const NOOP_CALLBACK = function (error) { if (error) debug(error); };
-var MAIL_TEMPLATES_DIR = path.join(__dirname, 'mail_templates');
+const MAIL_TEMPLATES_DIR = path.join(__dirname, 'mail_templates');
// This will collect the most common details required for notification emails
function getMailConfig(callback) {
@@ -215,81 +211,6 @@ function passwordReset(user) {
});
}
-function boxUpdateAvailable(mailTo, updateInfo, callback) {
- assert.strictEqual(typeof mailTo, 'string');
- assert.strictEqual(typeof updateInfo, 'object');
- assert.strictEqual(typeof callback, 'function');
-
- getMailConfig(function (error, mailConfig) {
- if (error) return debug('Error getting mail details:', error);
-
- var converter = new showdown.Converter();
-
- var templateData = {
- webadminUrl: settings.dashboardOrigin(),
- newBoxVersion: updateInfo.version,
- changelog: updateInfo.changelog,
- changelogHTML: updateInfo.changelog.map(function (e) { return converter.makeHtml(e); }),
- cloudronName: mailConfig.cloudronName,
- cloudronAvatarUrl: settings.dashboardOrigin() + '/api/v1/cloudron/avatar'
- };
-
- var templateDataText = JSON.parse(JSON.stringify(templateData));
- templateDataText.format = 'text';
-
- var templateDataHTML = JSON.parse(JSON.stringify(templateData));
- templateDataHTML.format = 'html';
-
- var mailOptions = {
- from: mailConfig.notificationFrom,
- to: mailTo,
- subject: `[${mailConfig.cloudronName}] Cloudron update available`,
- text: render('box_update_available.ejs', templateDataText),
- html: render('box_update_available.ejs', templateDataHTML)
- };
-
- sendMail(mailOptions, callback);
- });
-}
-
-function appUpdatesAvailable(mailTo, apps, callback) {
- assert.strictEqual(typeof mailTo, 'string');
- assert.strictEqual(typeof apps, '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: settings.dashboardOrigin(),
- apps: apps,
- cloudronName: mailConfig.cloudronName,
- cloudronAvatarUrl: settings.dashboardOrigin() + '/api/v1/cloudron/avatar'
- };
-
- var templateDataText = JSON.parse(JSON.stringify(templateData));
- templateDataText.format = 'text';
-
- var templateDataHTML = JSON.parse(JSON.stringify(templateData));
- templateDataHTML.format = 'html';
-
- var mailOptions = {
- from: mailConfig.notificationFrom,
- to: mailTo,
- subject: `[${mailConfig.cloudronName}] App update available`,
- text: render('app_updates_available.ejs', templateDataText),
- html: render('app_updates_available.ejs', templateDataHTML)
- };
-
- sendMail(mailOptions, callback);
- });
-}
-
function backupFailed(mailTo, errorMessage, logUrl) {
assert.strictEqual(typeof mailTo, 'string');
@@ -326,24 +247,6 @@ function certificateRenewalError(mailTo, domain, message) {
});
}
-function boxUpdateError(mailTo, message) {
- assert.strictEqual(typeof mailTo, 'string');
- assert.strictEqual(typeof message, 'string');
-
- getMailConfig(function (error, mailConfig) {
- if (error) return debug('Error getting mail details:', error);
-
- var mailOptions = {
- from: mailConfig.notificationFrom,
- to: mailTo,
- subject: `[${mailConfig.cloudronName}] Cloudron update error`,
- text: render('box_update_error.ejs', { message: message, format: 'text' })
- };
-
- sendMail(mailOptions);
- });
-}
-
function sendTestMail(domain, email, callback) {
assert.strictEqual(typeof domain, 'string');
assert.strictEqual(typeof email, 'string');
diff --git a/src/notificationdb.js b/src/notificationdb.js
deleted file mode 100644
index dbc5ef944..000000000
--- a/src/notificationdb.js
+++ /dev/null
@@ -1,150 +0,0 @@
-'use strict';
-
-exports = module.exports = {
- get,
- getByUserIdAndTitle,
- add,
- update,
- del,
- list,
-
- // exported for testing
- _clear: clear
-};
-
-let assert = require('assert'),
- BoxError = require('./boxerror.js'),
- database = require('./database.js');
-
-const NOTIFICATION_FIELDS = [ 'id', 'userId', 'eventId', 'title', 'message', 'creationTime', 'acknowledged' ];
-
-function postProcess(result) {
- assert.strictEqual(typeof result, 'object');
- result.id = String(result.id);
-
- // convert to boolean
- result.acknowledged = !!result.acknowledged;
-}
-
-function add(notification, callback) {
- assert.strictEqual(typeof notification, 'object');
- assert.strictEqual(typeof callback, 'function');
-
- const query = 'INSERT INTO notifications (userId, eventId, title, message, acknowledged) VALUES (?, ?, ?, ?, ?)';
- const args = [ notification.userId, notification.eventId, notification.title, notification.message, notification.acknowledged ];
-
- database.query(query, args, function (error, result) {
- if (error && error.code === 'ER_NO_REFERENCED_ROW_2') return callback(new BoxError(BoxError.NOT_FOUND, 'no such eventlog entry'));
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
-
- callback(null, String(result.insertId));
- });
-}
-
-function getByUserIdAndTitle(userId, title, callback) {
- assert.strictEqual(typeof userId, 'string');
- assert.strictEqual(typeof title, 'string');
- assert.strictEqual(typeof callback, 'function');
-
- database.query('SELECT ' + NOTIFICATION_FIELDS + ' from notifications WHERE userId = ? AND title = ? ORDER BY creationTime LIMIT 1', [ userId, title ], function (error, results) {
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
- if (results.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Notification not found'));
-
- postProcess(results[0]);
-
- callback(null, results[0]);
- });
-}
-
-function update(id, data, callback) {
- assert.strictEqual(typeof id, 'string');
- assert.strictEqual(typeof data, 'object');
- assert.strictEqual(typeof callback, 'function');
-
- let args = [ ];
- let fields = [ ];
- for (let k in data) {
- fields.push(k + ' = ?');
- args.push(data[k]);
- }
- args.push(id);
-
- database.query('UPDATE notifications SET ' + fields.join(', ') + ' WHERE id = ?', args, function (error, result) {
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
- if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Notification not found'));
-
- callback(null);
- });
-}
-
-function get(id, callback) {
- assert.strictEqual(typeof id, 'string');
- assert.strictEqual(typeof callback, 'function');
-
- database.query('SELECT ' + NOTIFICATION_FIELDS + ' FROM notifications WHERE id = ?', [ id ], function (error, result) {
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
- if (result.length === 0) return callback(new BoxError(BoxError.NOT_FOUND, 'Notification not found'));
-
- postProcess(result[0]);
-
- callback(null, result[0]);
- });
-}
-
-function del(id, callback) {
- assert.strictEqual(typeof id, 'string');
- assert.strictEqual(typeof callback, 'function');
-
- database.query('DELETE FROM notifications WHERE id = ?', [ id ], function (error, result) {
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
- if (result.affectedRows !== 1) return callback(new BoxError(BoxError.NOT_FOUND, 'Notification not found'));
-
- callback(null);
- });
-}
-
-function list(filters, page, perPage, callback) {
- assert.strictEqual(typeof filters, 'object');
- assert.strictEqual(typeof page, 'number');
- assert.strictEqual(typeof perPage, 'number');
- assert.strictEqual(typeof callback, 'function');
-
- let args = [];
-
- let where = [];
- if ('userId' in filters) {
- where.push('userId=?');
- args.push(filters.userId);
- }
-
- if ('acknowledged' in filters) {
- where.push('acknowledged=?');
- args.push(filters.acknowledged);
- }
-
- let query = `SELECT ${NOTIFICATION_FIELDS} FROM notifications`;
- if (where.length) query += ' WHERE ' + where.join(' AND ');
- query += ' ORDER BY creationTime DESC LIMIT ?,?';
-
- args.push((page-1)*perPage);
- args.push(perPage);
-
- database.query(query, args, function (error, results) {
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
-
- results.forEach(postProcess);
-
- callback(null, results);
- });
-}
-
-function clear(callback) {
- assert.strictEqual(typeof callback, 'function');
-
- database.query('DELETE FROM notifications', function (error) {
- if (error) return callback(new BoxError(BoxError.DATABASE_ERROR, error));
-
- callback(null);
- });
-}
-
diff --git a/src/notifications.js b/src/notifications.js
index da2f34bb1..ae9fa4414 100644
--- a/src/notifications.js
+++ b/src/notifications.js
@@ -4,12 +4,10 @@ exports = module.exports = {
get,
update,
list,
+ del,
onEvent,
- appUpdatesAvailable,
- boxUpdateAvailable,
-
ALERT_BACKUP_CONFIG: 'backupConfig',
ALERT_DISK_SPACE: 'diskSpace',
ALERT_MAIL_STATUS: 'mailStatus',
@@ -23,115 +21,111 @@ exports = module.exports = {
_add: add
};
-const apps = require('./apps.js'),
- assert = require('assert'),
- async = require('async'),
+const assert = require('assert'),
auditSource = require('./auditsource.js'),
BoxError = require('./boxerror.js'),
changelog = require('./changelog.js'),
- constants = require('./constants.js'),
- debug = require('debug')('box:notifications'),
+ database = require('./database.js'),
eventlog = require('./eventlog.js'),
mailer = require('./mailer.js'),
- notificationdb = require('./notificationdb.js'),
settings = require('./settings.js'),
- users = require('./users.js');
+ users = require('./users.js'),
+ util = require('util');
-function add(userId, eventId, title, message, callback) {
- assert.strictEqual(typeof userId, 'string');
+const NOTIFICATION_FIELDS = [ 'id', 'eventId', 'title', 'message', 'creationTime', 'acknowledged' ];
+
+function postProcess(result) {
+ assert.strictEqual(typeof result, 'object');
+ result.id = String(result.id);
+
+ // convert to boolean
+ result.acknowledged = !!result.acknowledged;
+ return result;
+}
+
+async function add(eventId, title, message) {
assert(typeof eventId === 'string' || eventId === null);
assert.strictEqual(typeof title, 'string');
assert.strictEqual(typeof message, 'string');
- assert.strictEqual(typeof callback, 'function');
- notificationdb.add({
- userId,
- eventId,
- title,
- message,
- acknowledged: false
- }, function (error, result) {
- if (error) return callback(error);
+ const query = 'INSERT INTO notifications (eventId, title, message, acknowledged) VALUES (?, ?, ?, ?)';
+ const args = [ eventId, title, message, false ];
- callback(null, { id: result });
- });
+ const result = await database.query(query, args);
+ return String(result.insertId);
}
-function get(id, callback) {
+async function get(id) {
assert.strictEqual(typeof id, 'string');
- assert.strictEqual(typeof callback, 'function');
- notificationdb.get(id, function (error, result) {
- if (error) return callback(error);
+ const result = await database.query('SELECT ' + NOTIFICATION_FIELDS + ' FROM notifications WHERE id = ?', [ id ]);
+ if (result.length === 0) return null;
- callback(null, result);
- });
+ return postProcess(result[0]);
}
-function update(id, data, callback) {
- assert.strictEqual(typeof id, 'string');
+async function getByTitle(title) {
+ assert.strictEqual(typeof title, 'string');
+
+ const results = await database.query('SELECT ' + NOTIFICATION_FIELDS + ' from notifications WHERE title = ? ORDER BY creationTime LIMIT 1', [ title ]);
+ if (results.length === 0) return null;
+
+ return postProcess(results[0]);
+}
+
+async function update(notification, data) {
+ assert.strictEqual(typeof notification, 'object');
assert.strictEqual(typeof data, 'object');
- assert.strictEqual(typeof callback, 'function');
- notificationdb.update(id, data, function (error) {
- if (error) return callback(error);
+ let args = [ ];
+ let fields = [ ];
+ for (let k in data) {
+ fields.push(k + ' = ?');
+ args.push(data[k]);
+ }
+ args.push(notification.id);
- callback(null);
- });
+ const result = await database.query('UPDATE notifications SET ' + fields.join(', ') + ' WHERE id = ?', args);
+ if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Notification not found');
}
-function list(options, page, perPage, callback) {
- assert.strictEqual(typeof options, 'object');
+async function del(id) {
+ assert.strictEqual(typeof id, 'string');
+
+ const result = await database.query('DELETE FROM notifications WHERE id = ?', [ id ]);
+ if (result.affectedRows !== 1) throw new BoxError(BoxError.NOT_FOUND, 'Notification not found');
+}
+
+async function list(filters, page, perPage) {
+ assert.strictEqual(typeof filters, 'object');
assert.strictEqual(typeof page, 'number');
assert.strictEqual(typeof perPage, 'number');
- assert.strictEqual(typeof callback, 'function');
- notificationdb.list({ userId: options.userId, acknowledge: options.acknowledged }, page, perPage, function (error, result) {
- if (error) return callback(error);
+ let args = [];
- callback(null, result);
- });
+ let where = [];
+ if ('acknowledged' in filters) {
+ where.push('acknowledged=?');
+ args.push(filters.acknowledged);
+ }
+
+ let query = `SELECT ${NOTIFICATION_FIELDS} FROM notifications`;
+ if (where.length) query += ' WHERE ' + where.join(' AND ');
+ query += ' ORDER BY creationTime DESC LIMIT ?,?';
+
+ args.push((page-1)*perPage);
+ args.push(perPage);
+
+ const results = await database.query(query, args);
+ results.forEach(postProcess);
+ return results;
}
-// Calls iterator with (admin, callback)
-function forEachAdmin(options, iterator, callback) {
- assert(Array.isArray(options.skip));
- assert.strictEqual(typeof iterator, 'function');
- assert.strictEqual(typeof callback, 'function');
-
- users.getAdmins(function (error, result) {
- if (error && error.reason === BoxError.NOT_FOUND) return callback();
- if (error) return callback(error);
-
- // filter out users we want to skip (like the user who did the action or the user the action was performed on)
- result = result.filter(function (r) { return options.skip.indexOf(r.id) === -1; });
-
- async.each(result, iterator, callback);
- });
-}
-
-function forEachSuperadmin(options, iterator, callback) {
- assert(Array.isArray(options.skip));
- assert.strictEqual(typeof iterator, 'function');
- assert.strictEqual(typeof callback, 'function');
-
- users.getSuperadmins(function (error, result) {
- if (error && error.reason === BoxError.NOT_FOUND) return callback();
- if (error) return callback(error);
-
- // filter out users we want to skip (like the user who did the action or the user the action was performed on)
- result = result.filter(function (r) { return options.skip.indexOf(r.id) === -1; });
-
- async.each(result, iterator, callback);
- });
-}
-
-function oomEvent(eventId, app, addon, containerId, event, callback) {
+async function oomEvent(eventId, app, addon, containerId /*, event*/) {
assert.strictEqual(typeof eventId, 'string');
assert.strictEqual(typeof app, 'object');
assert.strictEqual(typeof addon, 'object');
assert.strictEqual(typeof containerId, 'string');
- assert.strictEqual(typeof callback, 'function');
assert(app || addon);
@@ -144,182 +138,129 @@ function oomEvent(eventId, app, addon, containerId, event, callback) {
message = `The service has been restarted automatically. If you see this notification often, consider increasing the [memory limit](${settings.dashboardOrigin()}/#/services)`;
}
- forEachAdmin({ skip: [] }, function (admin, done) {
- add(admin.id, eventId, title, message, done);
- }, callback);
+ await add(eventId, title, message);
}
-function appUpdated(eventId, app, callback) {
+async function appUpdated(eventId, app) {
assert.strictEqual(typeof eventId, 'string');
assert.strictEqual(typeof app, 'object');
- assert.strictEqual(typeof callback, 'function');
- if (!app.appStoreId) return callback(); // skip notification of dev apps
+ if (!app.appStoreId) return; // skip notification of dev apps
const tmp = app.manifest.description.match(/(.*)<\/upstream>/i);
const upstreamVersion = (tmp && tmp[1]) ? tmp[1] : '';
const title = upstreamVersion ? `${app.manifest.title} at ${app.fqdn} updated to ${upstreamVersion} (package version ${app.manifest.version})`
: `${app.manifest.title} at ${app.fqdn} updated to package version ${app.manifest.version}`;
- forEachAdmin({ skip: [] }, function (admin, done) {
- add(admin.id, eventId, title, `The application installed at https://${app.fqdn} was updated.\n\nChangelog:\n${app.manifest.changelog}\n`, done);
- }, callback);
+ await add(eventId, title, `The application installed at https://${app.fqdn} was updated.\n\nChangelog:\n${app.manifest.changelog}\n`);
}
-function boxUpdateAvailable(updateInfo, callback) {
- assert.strictEqual(typeof updateInfo, 'object');
- assert.strictEqual(typeof callback, 'function');
-
- settings.getAutoupdatePattern(function (error, result) {
- if (error) return callback(error);
-
- if (result !== constants.AUTOUPDATE_PATTERN_NEVER) return callback();
-
- forEachAdmin({ skip: [] }, function (admin, done) {
- mailer.boxUpdateAvailable(admin.email, updateInfo, done);
- }, callback);
- });
-}
-
-function appUpdatesAvailable(appUpdates, callback) {
- assert.strictEqual(typeof appUpdates, 'object');
- assert.strictEqual(typeof callback, 'function');
-
- settings.getAutoupdatePattern(function (error, result) {
- if (error) return callback(error);
-
- // if we are auto updating, then just consider apps that cannot be auto updated
- if (result !== constants.AUTOUPDATE_PATTERN_NEVER) appUpdates = appUpdates.filter(update => !apps.canAutoupdateApp(update.app, update.updateInfo));
-
- if (appUpdates.length === 0) return callback();
-
- forEachAdmin({ skip: [] }, function (admin, done) {
- mailer.appUpdatesAvailable(admin.email, appUpdates, done);
- }, callback);
- });
-}
-
-function boxUpdated(eventId, oldVersion, newVersion, callback) {
+async function boxUpdated(eventId, oldVersion, newVersion) {
assert.strictEqual(typeof eventId, 'string');
assert.strictEqual(typeof oldVersion, 'string');
assert.strictEqual(typeof newVersion, 'string');
- assert.strictEqual(typeof callback, 'function');
const changes = changelog.getChanges(newVersion);
const changelogMarkdown = changes.map((m) => `* ${m}\n`).join('');
- forEachAdmin({ skip: [] }, function (admin, done) {
- add(admin.id, eventId, `Cloudron updated to v${newVersion}`, `Cloudron was updated from v${oldVersion} to v${newVersion}.\n\nChangelog:\n${changelogMarkdown}\n`, done);
- }, callback);
+ await add(eventId, `Cloudron updated to v${newVersion}`, `Cloudron was updated from v${oldVersion} to v${newVersion}.\n\nChangelog:\n${changelogMarkdown}\n`);
}
-function boxUpdateError(eventId, errorMessage, callback) {
+async function boxUpdateError(eventId, errorMessage) {
assert.strictEqual(typeof eventId, 'string');
assert.strictEqual(typeof errorMessage, 'string');
- assert.strictEqual(typeof callback, 'function');
- forEachAdmin({ skip: [] }, function (admin, done) {
- mailer.boxUpdateError(admin.email, errorMessage);
- add(admin.id, eventId, 'Cloudron update failed', `Failed to update Cloudron: ${errorMessage}.`, done);
- }, callback);
+ await add(eventId, 'Cloudron update failed', `Failed to update Cloudron: ${errorMessage}.`);
}
-function certificateRenewalError(eventId, vhost, errorMessage, callback) {
+async function certificateRenewalError(eventId, vhost, errorMessage) {
assert.strictEqual(typeof eventId, 'string');
assert.strictEqual(typeof vhost, 'string');
assert.strictEqual(typeof errorMessage, 'string');
- assert.strictEqual(typeof callback, 'function');
- forEachAdmin({ skip: [] }, function (admin, callback) {
+ const getAdmins = util.callbackify(users.getAdmins);
+
+ const admins = await getAdmins();
+
+ for (const admin of admins) {
mailer.certificateRenewalError(admin.email, vhost, errorMessage);
- add(admin.id, eventId, `Certificate renewal of ${vhost} failed`, `Failed to renew certs of ${vhost}: ${errorMessage}. Renewal will be retried in 12 hours`, callback);
- }, callback);
+ }
+
+ await add(eventId, `Certificate renewal of ${vhost} failed`, `Failed to renew certs of ${vhost}: ${errorMessage}. Renewal will be retried in 12 hours`);
}
-function backupFailed(eventId, taskId, errorMessage, callback) {
+async function backupFailed(eventId, taskId, errorMessage) {
assert.strictEqual(typeof eventId, 'string');
assert.strictEqual(typeof taskId, 'string');
assert.strictEqual(typeof errorMessage, 'string');
- assert.strictEqual(typeof callback, 'function');
- forEachSuperadmin({ skip: [] }, function (admin, callback) {
- mailer.backupFailed(admin.email, errorMessage, `${settings.dashboardOrigin()}/logs.html?taskId=${taskId}`);
- add(admin.id, eventId, 'Backup failed', `Backup failed: ${errorMessage}. Logs are available [here](/logs.html?taskId=${taskId}).`, callback);
- }, callback);
+ const getSuperAdmins = util.callbackify(users.getSuperAdmins);
+
+ const superAdmins = await getSuperAdmins();
+
+ for (const superAdmin of superAdmins) {
+ mailer.backupFailed(superAdmin.email, errorMessage, `${settings.dashboardOrigin()}/logs.html?taskId=${taskId}`);
+ }
+
+ await add(eventId, 'Backup failed', `Backup failed: ${errorMessage}. Logs are available [here](/logs.html?taskId=${taskId}).`);
}
-function alert(id, title, message, callback) {
+async function alert(id, title, message) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof title, 'string');
assert.strictEqual(typeof message, 'string');
- assert.strictEqual(typeof callback, 'function');
const acknowledged = !message;
- forEachAdmin({ skip: [] }, function (admin, callback) {
- const data = {
- userId: admin.id,
+ const result = await getByTitle(title);
+ if (!result && acknowledged) return; // do not add acked alerts
+
+ if (!result) {
+ await add(null /* eventId */, title, message);
+ } else {
+ await update(result, {
eventId: null,
title,
message,
acknowledged,
creationTime: new Date()
- };
-
- notificationdb.getByUserIdAndTitle(admin.id, title, function (error, result) {
- if (error && error.reason !== BoxError.NOT_FOUND) return callback(error);
-
- if (!result && acknowledged) return callback(); // do not add acked alerts
-
- let updateFunc = !result ? notificationdb.add.bind(null, data) : notificationdb.update.bind(null, result.id, data);
-
- updateFunc(function (error) {
- if (error) return callback(error);
-
- callback(null);
- });
});
- }, function (error) {
- if (error) debug('alert: error notifying', error);
-
- callback();
- });
+ }
}
-function onEvent(id, action, source, data, callback) {
+async function onEvent(id, action, source, data) {
assert.strictEqual(typeof id, 'string');
assert.strictEqual(typeof action, 'string');
assert.strictEqual(typeof source, 'object');
assert.strictEqual(typeof data, 'object');
- assert.strictEqual(typeof callback, 'function');
// external ldap syncer does not generate notifications - FIXME username might be an issue here
- if (source.username === auditSource.EXTERNAL_LDAP_TASK.username) return callback();
+ if (source.username === auditSource.EXTERNAL_LDAP_TASK.username) return;
switch (action) {
case eventlog.ACTION_APP_OOM:
- return oomEvent(id, data.app, data.addon, data.containerId, data.event, callback);
+ return await oomEvent(id, data.app, data.addon, data.containerId, data.event);
case eventlog.ACTION_APP_UPDATE_FINISH:
- return appUpdated(id, data.app, callback);
+ return await appUpdated(id, data.app);
case eventlog.ACTION_CERTIFICATE_RENEWAL:
case eventlog.ACTION_CERTIFICATE_NEW:
- if (!data.errorMessage) return callback();
- return certificateRenewalError(id, data.domain, data.errorMessage, callback);
+ if (!data.errorMessage) return;
+ return await certificateRenewalError(id, data.domain, data.errorMessage);
case eventlog.ACTION_BACKUP_FINISH:
- if (!data.errorMessage) return callback();
- if (source.username !== auditSource.CRON.username && !data.timedOut) return callback(); // manual stop by user
+ if (!data.errorMessage) return;
+ if (source.username !== auditSource.CRON.username && !data.timedOut) return; // manual stop by user
- return backupFailed(id, data.taskId, data.errorMessage, callback); // only notify for automated backups or timedout
+ return await backupFailed(id, data.taskId, data.errorMessage); // only notify for automated backups or timedout
case eventlog.ACTION_UPDATE_FINISH:
- if (!data.errorMessage) return boxUpdated(id, data.oldVersion, data.newVersion, callback);
- if (data.timedOut) return boxUpdateError(id, data.errorMessage, callback);
- return callback();
+ if (!data.errorMessage) return await boxUpdated(id, data.oldVersion, data.newVersion);
+ if (data.timedOut) return await boxUpdateError(id, data.errorMessage);
+ return;
default:
- return callback();
+ return;
}
}
diff --git a/src/routes/notifications.js b/src/routes/notifications.js
index 4c83be849..612b7f06c 100644
--- a/src/routes/notifications.js
+++ b/src/routes/notifications.js
@@ -1,7 +1,7 @@
'use strict';
exports = module.exports = {
- verifyOwnership,
+ load,
get,
list,
update
@@ -11,29 +11,27 @@ let assert = require('assert'),
BoxError = require('../boxerror.js'),
HttpError = require('connect-lastmile').HttpError,
HttpSuccess = require('connect-lastmile').HttpSuccess,
- notifications = require('../notifications.js');
+ notifications = require('../notifications.js'),
+ safe = require('safetydance');
-function verifyOwnership(req, res, next) {
- if (!req.params.notificationId) return next(); // skip for listing
+async function load(req, res, next) {
+ assert.strictEqual(typeof req.params.notificationId, 'string');
- notifications.get(req.params.notificationId, function (error, result) {
- if (error) return next(BoxError.toHttpError(error));
+ const [error, result] = await safe(notifications.get(req.params.notificationId));
+ if (error) return next(BoxError.toHttpError(error));
+ if (!result) return next(new HttpError(404, 'Notification not found'));
- if (result.userId !== req.user.id) return next(new HttpError(403, 'User is not owner'));
-
- req.notification = result;
-
- next();
- });
+ req.resource = result;
+ next();
}
function get(req, res, next) {
- assert.strictEqual(typeof req.notification, 'object');
+ assert.strictEqual(typeof req.resource, 'object');
- next(new HttpSuccess(200, { notification: req.notification }));
+ next(new HttpSuccess(200, req.resource));
}
-function list(req, res, next) {
+async function list(req, res, next) {
const page = typeof req.query.page !== 'undefined' ? parseInt(req.query.page) : 1;
if (!page || page < 0) return next(new HttpError(400, 'page query param has to be a postive number'));
@@ -42,24 +40,20 @@ function list(req, res, next) {
if (req.query.acknowledged && !(req.query.acknowledged === 'true' || req.query.acknowledged === 'false')) return next(new HttpError(400, 'acknowledged must be a true or false'));
- const acknowledged = req.query.acknowledged ? req.query.acknowledged === 'true' : null;
+ const acknowledged = req.query.acknowledged ? req.query.acknowledged === 'true' : false;
- notifications.list({ userId: req.user.id, acknowledged }, page, perPage, function (error, result) {
- if (error) return next(BoxError.toHttpError(error));
-
- next(new HttpSuccess(200, { notifications: result }));
- });
+ const [error, result] = await safe(notifications.list({ userId: req.user.id, acknowledged }, page, perPage));
+ if (error) return next(BoxError.toHttpError(error));
+ next(new HttpSuccess(200, { notifications: result }));
}
-function update(req, res, next) {
- assert.strictEqual(typeof req.params.notificationId, 'string');
+async function update(req, res, next) {
+ assert.strictEqual(typeof req.resource, 'object');
assert.strictEqual(typeof req.body, 'object');
if (typeof req.body.acknowledged !== 'boolean') return next(new HttpError(400, 'acknowledged must be a booliean'));
- notifications.update(req.params.notificationId, { acknowledged: req.body.acknowledged }, function (error) {
- if (error) return next(BoxError.toHttpError(error));
-
- next(new HttpSuccess(204, {}));
- });
+ const [error] = await safe(notifications.update(req.resource, { acknowledged: req.body.acknowledged }));
+ if (error) return next(BoxError.toHttpError(error));
+ next(new HttpSuccess(204, {}));
}
diff --git a/src/server.js b/src/server.js
index e42e216ef..5cbafa134 100644
--- a/src/server.js
+++ b/src/server.js
@@ -132,9 +132,9 @@ function initializeExpressSync() {
router.post('/api/v1/tasks/:taskId/stop', json, token, authorizeAdmin, routes.tasks.stopTask);
// notification routes (these are server level)
- router.get ('/api/v1/notifications', token, routes.notifications.verifyOwnership, routes.notifications.list);
- router.get ('/api/v1/notifications/:notificationId', token, routes.notifications.verifyOwnership, routes.notifications.get);
- router.post('/api/v1/notifications/:notificationId', json, token, routes.notifications.verifyOwnership, routes.notifications.update);
+ router.get ('/api/v1/notifications', token, authorizeAdmin, routes.notifications.list);
+ router.get ('/api/v1/notifications/:notificationId', token, authorizeAdmin, routes.notifications.load, routes.notifications.get);
+ router.post('/api/v1/notifications/:notificationId', json, token, authorizeAdmin, routes.notifications.load, routes.notifications.update);
// backup routes
router.get ('/api/v1/backups', token, authorizeAdmin, routes.backups.list);
diff --git a/src/test/database-test.js b/src/test/database-test.js
index f0db826f3..27db578f7 100644
--- a/src/test/database-test.js
+++ b/src/test/database-test.js
@@ -20,7 +20,6 @@ const appdb = require('../appdb.js'),
hat = require('../hat.js'),
mailboxdb = require('../mailboxdb.js'),
maildb = require('../maildb.js'),
- notificationdb = require('../notificationdb.js'),
reverseProxy = require('../reverseproxy.js'),
settingsdb = require('../settingsdb.js'),
taskdb = require('../taskdb.js'),
@@ -120,190 +119,6 @@ describe('database', function () {
], done);
});
- describe('notifications', function () {
- var EVENT_0 = {
- id: 'event_0',
- action: 'action',
- source: {},
- data: {}
- };
-
- var EVENT_1 = {
- id: 'event_1',
- action: 'action',
- source: {},
- data: {}
- };
-
- var EVENT_2 = {
- id: 'event_2',
- action: 'action',
- source: {},
- data: {}
- };
-
- var NOTIFICATION_0 = {
- userId: USER_0.id,
- eventId: EVENT_0.id,
- title: 'title z', // titles are this way for ordering
- message: 'some message there',
- };
-
- var NOTIFICATION_1 = {
- userId: USER_0.id,
- eventId: EVENT_1.id,
- title: 'title y',
- message: 'some message there',
- };
-
- var NOTIFICATION_2 = {
- userId: USER_1.id,
- eventId: EVENT_2.id,
- title: 'title x',
- message: 'some message there',
- };
-
- var NOTIFICATION_3 = {
- userId: USER_0.id,
- eventId: null,
- title: 'title w',
- message: 'some message there',
- };
-
- before(function (done) {
- async.series([
- userdb.add.bind(null, USER_0.id, USER_0),
- userdb.add.bind(null, USER_1.id, USER_1),
- eventlogdb.add.bind(null, EVENT_0.id, EVENT_0.action, EVENT_0.source, EVENT_0.data),
- eventlogdb.add.bind(null, EVENT_1.id, EVENT_1.action, EVENT_1.source, EVENT_1.data),
- eventlogdb.add.bind(null, EVENT_2.id, EVENT_2.action, EVENT_2.source, EVENT_2.data),
- ], done);
- });
-
- after(function (done) {
- database._clear(done);
- });
-
- it('can add notification', function (done) {
- notificationdb.add(NOTIFICATION_0, function (error, result) {
- expect(error).to.equal(null);
- expect(result).to.be.a('string');
- NOTIFICATION_0.id = result;
- done();
- });
- });
-
- it('can add second notification', function (done) {
- notificationdb.add(NOTIFICATION_1, function (error, result) {
- expect(error).to.equal(null);
- expect(result).to.be.a('string');
- NOTIFICATION_1.id = result;
- done();
- });
- });
-
- it('can add third notification for another user', function (done) {
- notificationdb.add(NOTIFICATION_2, function (error, result) {
- expect(error).to.equal(null);
- expect(result).to.be.a('string');
- NOTIFICATION_2.id = result;
- done();
- });
- });
-
- it('can get by id', function (done) {
- notificationdb.get(NOTIFICATION_0.id, function (error, result) {
- expect(error).to.equal(null);
- expect(result.id).to.equal(NOTIFICATION_0.id);
- expect(result.title).to.equal(NOTIFICATION_0.title);
- expect(result.message).to.equal(NOTIFICATION_0.message);
- expect(result.acknowledged).to.equal(false);
- done();
- });
- });
-
- it('cannot get by non-existing id', function (done) {
- notificationdb.get('nopenothere', function (error, result) {
- expect(error).to.be.a(BoxError);
- expect(error.reason).to.equal(BoxError.NOT_FOUND);
- expect(result).to.not.be.ok();
- done();
- });
- });
-
- it('can list by user', function (done) {
- notificationdb.list({ userId: USER_0.id }, 1, 100, function (error, result) {
- expect(error).to.equal(null);
- expect(result).to.be.an('array');
- expect(result.length).to.equal(2);
- expect(result[0].id).to.equal(NOTIFICATION_0.id);
- expect(result[0].title).to.equal(NOTIFICATION_0.title);
- expect(result[0].message).to.equal(NOTIFICATION_0.message);
- expect(result[0].acknowledged).to.equal(false);
- done();
- });
- });
-
- it('cannot update non-existing notification', function (done) {
- notificationdb.update('isnotthere', { acknowledged: true }, function (error) {
- expect(error).to.be.a(BoxError);
- expect(error.reason).to.equal(BoxError.NOT_FOUND);
- done();
- });
- });
-
- it('update succeeds', function (done) {
- notificationdb.update(NOTIFICATION_1.id, { acknowledged: true }, function (error) {
- expect(error).to.equal(null);
-
- notificationdb.get(NOTIFICATION_1.id, function (error, result) {
- expect(error).to.equal(null);
- expect(result.id).to.equal(NOTIFICATION_1.id);
- expect(result.title).to.equal(NOTIFICATION_1.title);
- expect(result.message).to.equal(NOTIFICATION_1.message);
- expect(result.acknowledged).to.equal(true);
-
- done();
- });
- });
- });
-
- it('deletion succeeds', function (done) {
- notificationdb.del(NOTIFICATION_0.id, function (error) {
- expect(error).to.equal(null);
-
- notificationdb.get(NOTIFICATION_0.id, function (error, result) {
- expect(error).to.be.a(BoxError);
- expect(error.reason).to.equal(BoxError.NOT_FOUND);
- expect(result).to.not.be.ok();
-
- done();
- });
- });
- });
-
- it('deletion for non-existing notification fails', function (done) {
- notificationdb.del('doesnotexts', function (error) {
- expect(error).to.be.a(BoxError);
- expect(error.reason).to.equal(BoxError.NOT_FOUND);
-
- done();
- });
- });
-
- it('can add notification without eventId', function (done) {
- notificationdb.add(NOTIFICATION_3, function (error, result) {
- expect(error).to.equal(null);
- expect(result).to.be.a('string');
-
- // stash for further use
- NOTIFICATION_3.id = result;
-
- done();
- });
- });
- });
-
describe('domains', function () {
before(function (done) {
userdb.add(USER_0.id, USER_0, done);
diff --git a/src/test/notifications-test.js b/src/test/notifications-test.js
index d11587234..20322b305 100644
--- a/src/test/notifications-test.js
+++ b/src/test/notifications-test.js
@@ -6,51 +6,24 @@
'use strict';
-var async = require('async'),
+const async = require('async'),
BoxError = require('../boxerror.js'),
database = require('../database.js'),
- users = require('../users.js'),
- userdb = require('../userdb.js'),
- eventlogdb = require('../eventlogdb.js'),
+ expect = require('expect.js'),
notifications = require('../notifications.js'),
- expect = require('expect.js');
+ safe = require('safetydance');
-// owner
-var USER_0 = {
- username: 'username0',
- password: 'Username0pass?1234',
- email: 'user0@email.com',
- fallbackEmail: 'user0fallback@email.com',
- displayName: 'User 0',
- role: 'owner'
-};
-
-var EVENT_0 = {
+const EVENT_0 = {
id: 'event_0',
- action: '',
+ action: 'action',
source: {},
data: {}
};
-var AUDIT_SOURCE = {
- ip: '1.2.3.4'
-};
-
function setup(done) {
async.series([
database.initialize,
database._clear,
- users.createOwner.bind(null, USER_0.username, USER_0.password, USER_0.email, USER_0.displayName, AUDIT_SOURCE),
- function (callback) {
- userdb.getByUsername(USER_0.username, function (error, result) {
- if (error) return callback(error);
-
- USER_0.id = result.id;
-
- callback();
- });
- },
- eventlogdb.add.bind(null, EVENT_0.id, EVENT_0.action, EVENT_0.source, EVENT_0.data),
], done);
}
@@ -65,114 +38,59 @@ describe('Notifications', function () {
before(setup);
after(cleanup);
- var notificationId;
+ let notificationIds = [];
- it('add succeeds', function (done) {
- notifications._add(USER_0.id, EVENT_0.id, 'title', 'message text', function (error, result) {
- expect(error).to.eql(null);
- expect(result.id).to.be.ok();
-
- notificationId = result.id;
-
- done();
- });
+ it('can add notifications', async function () {
+ for (let i = 0; i < 3; i++) {
+ const [error, id] = await safe(notifications._add(EVENT_0.id, `title ${i}`, `message ${i}`));
+ expect(error).to.equal(null);
+ expect(id).to.be.a('string');
+ notificationIds.push(id);
+ }
});
- it('get succeeds', function (done) {
- notifications.get(notificationId, function (error, result) {
- expect(error).to.eql(null);
- expect(result.id).to.equal(notificationId);
- expect(result.title).to.equal('title');
- expect(result.message).to.equal('message text');
- expect(result.acknowledged).to.equal(false);
- expect(result.creationTime).to.be.a(Date);
-
- done();
- });
+ it('can get by id', async function () {
+ const [error, result] = await safe(notifications.get(notificationIds[0]));
+ expect(error).to.be(null);
+ expect(result.title).to.be('title 0');
+ expect(result.message).to.be('message 0');
+ expect(result.acknowledged).to.be(false);
});
- it('get of unknown id fails', function (done) {
- notifications.get('notfoundid', function (error, result) {
- expect(error).to.be.a(BoxError);
- expect(error.reason).to.be(BoxError.NOT_FOUND);
- expect(result).to.not.be.ok();
-
- done();
- });
+ it('cannot get non-existent id', async function () {
+ const result = await notifications.get('random');
+ expect(result).to.be(null);
});
- it('ack succeeds', function (done) {
- notifications.ack(notificationId, function (error) {
- expect(error).to.eql(null);
-
- notifications.get(notificationId, function (error, result) {
- expect(error).to.eql(null);
- expect(result.acknowledged).to.equal(true);
-
- done();
- });
- });
+ it('can list notifications', async function () {
+ const result = await notifications.list({}, 1, 10);
+ expect(result.length).to.be(3);
+ expect(result[0].title).to.be('title 0');
+ expect(result[1].title).to.be('title 1');
+ expect(result[2].title).to.be('title 2');
});
- it('ack succeeds twice', function (done) {
- notifications.ack(notificationId, function (error) {
- expect(error).to.eql(null);
+ it('can update notification', async function () {
+ await notifications.update({ id: notificationIds[0] }, { title: 'updated title 0', message: 'updated message 0', acknowledged: true });
- notifications.get(notificationId, function (error, result) {
- expect(error).to.eql(null);
- expect(result.acknowledged).to.equal(true);
-
- done();
- });
- });
+ const result = await notifications.get(notificationIds[0]);
+ expect(result.title).to.be('updated title 0');
+ expect(result.message).to.be('updated message 0');
+ expect(result.acknowledged).to.be(true);
});
- it('ack fails for nonexisting id', function (done) {
- notifications.ack('id does not exist', function (error) {
- expect(error).to.be.a(BoxError);
- expect(error.reason).to.be(BoxError.NOT_FOUND);
+ it('cannot update non-existent notification', async function () {
+ const [error] = await safe(notifications.update({ id: 'random' }, { title: 'updated title 0', message: 'updated message 0', acknowledged: true }));
+ expect(error.reason).to.be(BoxError.NOT_FOUND);
- done();
- });
});
- it('getAllPaged succeeds', function (done) {
- notifications.getAllPaged(USER_0.id, null, 1, 1, function (error, results) {
- expect(error).to.eql(null);
- expect(results).to.be.an(Array);
- expect(results.length).to.be(1);
-
- expect(results[0].id).to.be(notificationId);
- expect(results[0].title).to.equal('title');
- expect(results[0].message).to.equal('message text');
- expect(results[0].acknowledged).to.equal(true);
- expect(results[0].creationTime).to.be.a(Date);
-
- done();
- });
+ it('can delete', async function () {
+ await notifications.del(notificationIds[0]);
});
- it('getAllPaged succeeds for second page (takes 5 seconds to add)', function (done) {
- async.timesSeries(5, function (n, callback) {
- // timeout is for database TIMESTAMP resolution
- setTimeout(function () {
- notifications._add(USER_0.id, EVENT_0.id, 'title' + n, 'some message', callback);
- }, 1000);
- }, function (error) {
- expect(error).to.eql(null);
-
- notifications.getAllPaged(USER_0.id, null /* ack */, 2, 3, function (error, results) {
- expect(error).to.eql(null);
- expect(results).to.be.an(Array);
- expect(results.length).to.be(3);
-
- expect(results[0].title).to.equal('title1');
- expect(results[1].title).to.equal('title0');
- // the previous tests already add one notification with 'title'
- expect(results[2].title).to.equal('title');
-
- done();
- });
- });
+ it('cannot delete non-existent notification', async function () {
+ const [error] = await safe(notifications.del('random'));
+ expect(error.reason).to.be(BoxError.NOT_FOUND);
});
});
diff --git a/src/updatechecker.js b/src/updatechecker.js
index d87e087e7..3940036b1 100644
--- a/src/updatechecker.js
+++ b/src/updatechecker.js
@@ -8,7 +8,7 @@ exports = module.exports = {
_checkAppUpdates: checkAppUpdates
};
-var apps = require('./apps.js'),
+const apps = require('./apps.js'),
appstore = require('./appstore.js'),
assert = require('assert'),
async = require('async'),
@@ -40,8 +40,6 @@ function checkAppUpdates(options, callback) {
let state = getUpdateInfo();
let newState = { }; // create new state so that old app ids are removed
- var pendingNotifications = [];
-
apps.getAll(function (error, result) {
if (error) return callback(error);
@@ -58,14 +56,6 @@ function checkAppUpdates(options, callback) {
newState[app.id] = updateInfo;
- if (safe.query(state[app.id], 'manifest.version') === updateInfo.manifest.version) {
- debug(`checkAppUpdates: Skipping app update notification of ${app.id} since user was already notified of ${updateInfo.manifest.version}`);
- return iteratorDone();
- }
-
- debug(`checkAppUpdates: ${app.id} can be updated to ${updateInfo.manifest.id}@${updateInfo.manifest.version}`);
-
- pendingNotifications.push({ app, updateInfo });
iteratorDone();
});
}, function () {
@@ -73,7 +63,7 @@ function checkAppUpdates(options, callback) {
setUpdateInfo(newState);
- notifications.appUpdatesAvailable(pendingNotifications, callback);
+ callback();
});
});
}
@@ -115,7 +105,7 @@ function checkBoxUpdates(options, callback) {
state.box = updateInfo;
setUpdateInfo(state);
- notifications.boxUpdateAvailable(updateInfo, callback);
+ callback();
});
});
}