diff --git a/migrations/20241211205706-notifications-add-context.js b/migrations/20241211205706-notifications-add-context.js new file mode 100644 index 000000000..088e7d8c2 --- /dev/null +++ b/migrations/20241211205706-notifications-add-context.js @@ -0,0 +1,9 @@ +'use strict'; + +exports.up = async function (db) { + await db.runSql('ALTER TABLE notifications ADD COLUMN context VARCHAR(128) DEFAULT ""'); +}; + +exports.down = async function (db) { + await db.runSql('ALTER TABLE notifications DROP COLUMN context'); +}; diff --git a/migrations/schema.sql b/migrations/schema.sql index 9f38bca2b..1aa5ec61e 100644 --- a/migrations/schema.sql +++ b/migrations/schema.sql @@ -272,6 +272,7 @@ CREATE TABLE IF NOT EXISTS notifications( message TEXT, acknowledged BOOLEAN DEFAULT false, creationTime TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + context VARCHAR(128) DEFAULT "", // used along with "type" to create uniqueness INDEX creationTime_index (creationTime), FOREIGN KEY(eventId) REFERENCES eventlog(id), diff --git a/src/notifications.js b/src/notifications.js index eccc944c3..59d477c3a 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -48,7 +48,7 @@ const assert = require('assert'), mailer = require('./mailer.js'), users = require('./users.js'); -const NOTIFICATION_FIELDS = [ 'id', 'eventId', 'type', 'title', 'message', 'creationTime', 'acknowledged' ]; +const NOTIFICATION_FIELDS = [ 'id', 'eventId', 'type', 'title', 'message', 'creationTime', 'acknowledged', 'context' ]; function postProcess(result) { assert.strictEqual(typeof result, 'object'); @@ -63,8 +63,8 @@ async function add(type, title, message, data) { assert.strictEqual(typeof message, 'string'); assert.strictEqual(typeof data, 'object'); - const query = 'INSERT INTO notifications (type, title, message, acknowledged, eventId) VALUES (?, ?, ?, ?, ?)'; - const args = [ type, title, message, false, data?.eventId || null ]; + const query = 'INSERT INTO notifications (type, title, message, acknowledged, eventId, context) VALUES (?, ?, ?, ?, ?, ?)'; + const args = [ type, title, message, false, data?.eventId || null, data.context || '' ]; const result = await database.query(query, args); return String(result.insertId); @@ -79,10 +79,11 @@ async function get(id) { return postProcess(result[0]); } -async function getByType(type) { +async function getByType(type, context) { assert.strictEqual(typeof type, 'string'); + assert.strictEqual(typeof context, 'string'); - const results = await database.query(`SELECT ${NOTIFICATION_FIELDS} from notifications WHERE type = ? ORDER BY creationTime LIMIT 1`, [ type ]); + const results = await database.query(`SELECT ${NOTIFICATION_FIELDS} from notifications WHERE type = ? AND context = ? ORDER BY creationTime LIMIT 1`, [ type, context || '']); if (results.length === 0) return null; return postProcess(results[0]); @@ -280,16 +281,14 @@ async function pin(type, title, message, options) { return null; } - const isUpdateType = type === exports.TYPE_BOX_UPDATE || type === exports.TYPE_MANUAL_APP_UPDATE_NEEDED; - if (options.context) type = `${type}-${options.context}`; // create a unique type for this context - - const result = await getByType(type); - if (!result) return await add(type, title, message, { eventId: null }); + const result = await getByType(type, options.context || ''); + if (!result) return await add(type, title, message, { eventId: null, context: options.context || '' }); // do not reset the ack state if user has already seen the update notification + const isUpdateType = type === exports.TYPE_BOX_UPDATE || type === exports.TYPE_MANUAL_APP_UPDATE_NEEDED; const acknowledged = (isUpdateType && result.message === message) ? result.acknowledged : false; - await update(result, { id: result.id, title, message, acknowledged, creationTime: new Date() }); + await update(result, { title, message, acknowledged, creationTime: new Date() }); return result.id; } @@ -297,8 +296,7 @@ async function unpin(type, options) { assert.strictEqual(typeof type, 'string'); // TYPE_ assert.strictEqual(typeof options, 'object'); - if (options.context) type = `${type}-${options.context}`; // create a unique type for this context - await database.query('UPDATE notifications SET acknowledged=1 WHERE type = ?', [ type ]); + await database.query('UPDATE notifications SET acknowledged=1 WHERE type = ? AND context = ?', [ type, options.context || '' ]); } async function onEvent(id, action, source, data) { diff --git a/src/test/notifications-test.js b/src/test/notifications-test.js index 3fb2a9172..b6c0d4df4 100644 --- a/src/test/notifications-test.js +++ b/src/test/notifications-test.js @@ -132,6 +132,8 @@ describe('Notifications', function () { it('can add pin', async function () { pinId = await notifications.pin(notifications.TYPE_REBOOT, 'Reboot required', 'Do it now', {}); + console.log(pinId); + const result = await notifications.get(pinId); expect(result.title).to.be('Reboot required'); expect(result.message).to.be('Do it now');