diff --git a/packages/rocketchat-api/server/v1/channels.js b/packages/rocketchat-api/server/v1/channels.js index d7bdac522d12..35af717021da 100644 --- a/packages/rocketchat-api/server/v1/channels.js +++ b/packages/rocketchat-api/server/v1/channels.js @@ -795,3 +795,43 @@ RocketChat.API.v1.addRoute('channels.unarchive', { authRequired: true }, { return RocketChat.API.v1.success(); } }); + +RocketChat.API.v1.addRoute('channels.notifications', { authRequired: true }, { + get() { + const { roomId } = this.requestParams(); + + if (!roomId) { + return RocketChat.API.v1.failure('The \'roomId\' param is required'); + } + + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, this.userId, { + fields: { + _room: 0, + _user: 0, + $loki: 0 + } + }); + + return RocketChat.API.v1.success({ + subscription + }); + }, + post() { + const saveNotifications = (notifications, roomId) => { + Object.keys(notifications).map((notificationKey) => { + Meteor.runAsUser(this.userId, () => Meteor.call('saveNotificationSettings', roomId, notificationKey, notifications[notificationKey])); + }); + }; + const { roomId, notifications } = this.bodyParams; + + if (!roomId) { + return RocketChat.API.v1.failure('The \'roomId\' param is required'); + } + + if (!notifications || Object.keys(notifications).length === 0) { + return RocketChat.API.v1.failure('The \'notifications\' param is required'); + } + + saveNotifications(notifications, roomId); + } +}); diff --git a/packages/rocketchat-lib/server/models/Subscriptions.js b/packages/rocketchat-lib/server/models/Subscriptions.js index 726a0b4299c2..9e585150bf01 100644 --- a/packages/rocketchat-lib/server/models/Subscriptions.js +++ b/packages/rocketchat-lib/server/models/Subscriptions.js @@ -30,16 +30,16 @@ class ModelSubscriptions extends RocketChat.models._Base { // FIND ONE - findOneByRoomIdAndUserId(roomId, userId) { + findOneByRoomIdAndUserId(roomId, userId, options) { if (this.useCache) { - return this.cache.findByIndex('rid,u._id', [roomId, userId]).fetch(); + return this.cache.findByIndex('rid,u._id', [roomId, userId], options).fetch(); } const query = { rid: roomId, 'u._id': userId }; - return this.findOne(query); + return this.findOne(query, options); } findOneByRoomNameAndUserId(roomName, userId) { @@ -61,7 +61,7 @@ class ModelSubscriptions extends RocketChat.models._Base { } const query = - {'u._id': userId}; + { 'u._id': userId }; return this.find(query, options); } @@ -123,7 +123,7 @@ class ModelSubscriptions extends RocketChat.models._Base { } const query = - {rid: roomId}; + { rid: roomId }; return this.find(query, options); } @@ -140,7 +140,9 @@ class ModelSubscriptions extends RocketChat.models._Base { } getLastSeen(options) { - if (options == null) { options = {}; } + if (options == null) { + options = {}; + } const query = { ls: { $exists: 1 } }; options.sort = { ls: -1 }; options.limit = 1; @@ -198,7 +200,7 @@ class ModelSubscriptions extends RocketChat.models._Base { // UPDATE archiveByRoomId(roomId) { const query = - {rid: roomId}; + { rid: roomId }; const update = { $set: { @@ -213,7 +215,7 @@ class ModelSubscriptions extends RocketChat.models._Base { unarchiveByRoomId(roomId) { const query = - {rid: roomId}; + { rid: roomId }; const update = { $set: { @@ -311,7 +313,9 @@ class ModelSubscriptions extends RocketChat.models._Base { } setFavoriteByRoomIdAndUserId(roomId, userId, favorite) { - if (favorite == null) { favorite = true; } + if (favorite == null) { + favorite = true; + } const query = { rid: roomId, 'u._id': userId @@ -328,7 +332,7 @@ class ModelSubscriptions extends RocketChat.models._Base { updateNameAndAlertByRoomId(roomId, name, fname) { const query = - {rid: roomId}; + { rid: roomId }; const update = { $set: { @@ -343,7 +347,7 @@ class ModelSubscriptions extends RocketChat.models._Base { updateNameByRoomId(roomId, name) { const query = - {rid: roomId}; + { rid: roomId }; const update = { $set: { @@ -356,7 +360,7 @@ class ModelSubscriptions extends RocketChat.models._Base { setUserUsernameByUserId(userId, username) { const query = - {'u._id': userId}; + { 'u._id': userId }; const update = { $set: { @@ -383,7 +387,9 @@ class ModelSubscriptions extends RocketChat.models._Base { } incUnreadForRoomIdExcludingUserId(roomId, userId, inc) { - if (inc == null) { inc = 1; } + if (inc == null) { + inc = 1; + } const query = { rid: roomId, 'u._id': { @@ -447,6 +453,7 @@ class ModelSubscriptions extends RocketChat.models._Base { return this.update(query, update, { multi: true }); } + setAlertForRoomIdExcludingUserId(roomId, userId) { const query = { rid: roomId, @@ -522,7 +529,7 @@ class ModelSubscriptions extends RocketChat.models._Base { updateTypeByRoomId(roomId, type) { const query = - {rid: roomId}; + { rid: roomId }; const update = { $set: { @@ -535,7 +542,7 @@ class ModelSubscriptions extends RocketChat.models._Base { addRoleById(_id, role) { const query = - {_id}; + { _id }; const update = { $addToSet: { @@ -548,7 +555,7 @@ class ModelSubscriptions extends RocketChat.models._Base { removeRoleById(_id, role) { const query = - {_id}; + { _id }; const update = { $pull: { @@ -604,14 +611,14 @@ class ModelSubscriptions extends RocketChat.models._Base { // REMOVE removeByUserId(userId) { const query = - {'u._id': userId}; + { 'u._id': userId }; return this.remove(query); } removeByRoomId(roomId) { const query = - {rid: roomId}; + { rid: roomId }; return this.remove(query); } diff --git a/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js b/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js index 365d0b3f6c52..1bf4ec2ac844 100644 --- a/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js +++ b/packages/rocketchat-push-notifications/server/methods/saveNotificationSettings.js @@ -1,49 +1,59 @@ Meteor.methods({ - saveNotificationSettings(rid, field, value) { + saveNotificationSettings(roomId, field, value) { if (!Meteor.userId()) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'saveNotificationSettings' }); } - - check(rid, String); + check(roomId, String); check(field, String); check(value, String); - if (['audioNotifications', 'desktopNotifications', 'mobilePushNotifications', 'emailNotifications', 'unreadAlert', 'disableNotifications', 'hideUnreadStatus'].indexOf(field) === -1) { + const notifications = { + 'audioNotifications': { + updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateAudioNotificationsById(subscription._id, value) + }, + 'desktopNotifications': { + updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateDesktopNotificationsById(subscription._id, value) + }, + 'mobilePushNotifications': { + updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateMobilePushNotificationsById(subscription._id, value) + }, + 'emailNotifications': { + updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateEmailNotificationsById(subscription._id, value) + }, + 'unreadAlert': { + updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateUnreadAlertById(subscription._id, value) + }, + 'disableNotifications': { + updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateDisableNotificationsById(subscription._id, value === '1') + }, + 'hideUnreadStatus': { + updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateHideUnreadStatusById(subscription._id, value === '1') + }, + 'desktopNotificationDuration': { + updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateDesktopNotificationDurationById(subscription._id, value) + }, + 'audioNotificationValue': { + updateMethod: (subscription, value) => RocketChat.models.Subscriptions.updateAudioNotificationValueById(subscription._id, value) + } + }; + const isInvalidNotification = !Object.keys(notifications).includes(field); + const basicValuesForNotifications = ['all', 'mentions', 'nothing', 'default']; + const fieldsMustHaveBasicValues = ['emailNotifications', 'audioNotifications', 'mobilePushNotifications', 'desktopNotifications']; + + if (isInvalidNotification) { throw new Meteor.Error('error-invalid-settings', 'Invalid settings field', { method: 'saveNotificationSettings' }); } - if (field !== 'hideUnreadStatus' && field !== 'disableNotifications' && ['all', 'mentions', 'nothing', 'default'].indexOf(value) === -1) { + if (fieldsMustHaveBasicValues.includes(field) && !basicValuesForNotifications.includes(value)) { throw new Meteor.Error('error-invalid-settings', 'Invalid settings value', { method: 'saveNotificationSettings' }); } - const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(rid, Meteor.userId()); + const subscription = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(roomId, Meteor.userId()); if (!subscription) { throw new Meteor.Error('error-invalid-subscription', 'Invalid subscription', { method: 'saveNotificationSettings' }); } - switch (field) { - case 'audioNotifications': - RocketChat.models.Subscriptions.updateAudioNotificationsById(subscription._id, value); - break; - case 'desktopNotifications': - RocketChat.models.Subscriptions.updateDesktopNotificationsById(subscription._id, value); - break; - case 'mobilePushNotifications': - RocketChat.models.Subscriptions.updateMobilePushNotificationsById(subscription._id, value); - break; - case 'emailNotifications': - RocketChat.models.Subscriptions.updateEmailNotificationsById(subscription._id, value); - break; - case 'unreadAlert': - RocketChat.models.Subscriptions.updateUnreadAlertById(subscription._id, value); - break; - case 'disableNotifications': - RocketChat.models.Subscriptions.updateDisableNotificationsById(subscription._id, value === '1' ? true : false); - break; - case 'hideUnreadStatus': - RocketChat.models.Subscriptions.updateHideUnreadStatusById(subscription._id, value === '1' ? true : false); - break; - } + notifications[field].updateMethod(subscription, value); return true; }, diff --git a/tests/end-to-end/api/02-channels.js b/tests/end-to-end/api/02-channels.js index 784263e7b8ce..a0196e8cfd33 100644 --- a/tests/end-to-end/api/02-channels.js +++ b/tests/end-to-end/api/02-channels.js @@ -2,8 +2,20 @@ /* globals expect */ /* eslint no-unused-vars: 0 */ -import {getCredentials, api, login, request, credentials, apiEmail, apiUsername, targetUser, log, apiPublicChannelName, channel } from '../../data/api-data.js'; -import {adminEmail, password} from '../../data/user.js'; +import { + getCredentials, + api, + login, + request, + credentials, + apiEmail, + apiUsername, + targetUser, + log, + apiPublicChannelName, + channel +} from '../../data/api-data.js'; +import { adminEmail, password } from '../../data/user.js'; import supertest from 'supertest'; function getRoomInfo(roomId) { @@ -477,6 +489,44 @@ describe('[Channels]', function() { .end(done); }); + it('GET /channels.notifications', (done) => { + request.get(api('channels.notifications')) + .set(credentials) + .query({ + roomId: channel._id + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('subscription').and.to.be.an('object'); + }) + .end(done); + }); + + it('POST /channels.notifications', (done) => { + request.post(api('channels.notifications')) + .set(credentials) + .send({ + roomId: channel._id, + notifications: { + disableNotifications: '0', + emailNotifications: 'nothing', + audioNotificationValue: 'beep', + desktopNotifications: 'nothing', + desktopNotificationDuration: '2', + audioNotifications: 'all', + mobilePushNotifications: 'mentions' + } + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + }) + .end(done); + }); + it('/channels.leave', async(done) => { const roomInfo = await getRoomInfo(channel._id);