diff --git a/packages/rocketchat-api/server/v1/channels.js b/packages/rocketchat-api/server/v1/channels.js index d1aa55331c2e..66abb8fac450 100644 --- a/packages/rocketchat-api/server/v1/channels.js +++ b/packages/rocketchat-api/server/v1/channels.js @@ -140,6 +140,60 @@ RocketChat.API.v1.addRoute('channels.close', { authRequired: true }, { } }); +RocketChat.API.v1.addRoute('channels.counters', { authRequired: true }, { + get() { + const access = RocketChat.authz.hasPermission(this.userId, 'view-room-administration'); + const ruserId = this.requestParams().userId; + let user = this.userId; + let unreads = null; + let userMentions = null; + let unreadsFrom = null; + let joined = false; + let msgs = null; + let latest = null; + let members = null; + let lm = null; + + if (ruserId) { + if (!access) { + return RocketChat.API.v1.unauthorized(); + } + user = ruserId; + } + const room = findChannelByIdOrName({ + params: this.requestParams(), + returnUsernames: true + }); + const channel = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user); + lm = channel._room.lm ? channel._room.lm : channel._room._updatedAt; + + if (typeof channel !== 'undefined' && channel.open) { + if (channel.ls) { + unreads = RocketChat.models.Messages.countVisibleByRoomIdBetweenTimestampsInclusive(channel.rid, channel.ls, lm); + unreadsFrom = channel.ls; + } + userMentions = channel.userMentions; + joined = true; + } + + if (access || joined) { + msgs = room.msgs; + latest = lm; + members = room.usernames.length; + } + + return RocketChat.API.v1.success({ + joined, + members, + unreads, + unreadsFrom, + msgs, + latest, + userMentions + }); + } +}); + // Channel -> create function createChannelValidator(params) { diff --git a/packages/rocketchat-api/server/v1/groups.js b/packages/rocketchat-api/server/v1/groups.js index 562d7bb4da65..a3b5ed7d0dd9 100644 --- a/packages/rocketchat-api/server/v1/groups.js +++ b/packages/rocketchat-api/server/v1/groups.js @@ -107,6 +107,75 @@ RocketChat.API.v1.addRoute('groups.close', { authRequired: true }, { } }); +RocketChat.API.v1.addRoute('groups.counters', { authRequired: true }, { + get() { + const access = RocketChat.authz.hasPermission(this.userId, 'view-room-administration'); + const params = this.requestParams(); + let user = this.userId; + let room; + let unreads = null; + let userMentions = null; + let unreadsFrom = null; + let joined = false; + let msgs = null; + let latest = null; + let members = null; + let lm = null; + + if ((!params.roomId || !params.roomId.trim()) && (!params.roomName || !params.roomName.trim())) { + throw new Meteor.Error('error-room-param-not-provided', 'The parameter "roomId" or "roomName" is required'); + } + + if (params.roomId) { + room = RocketChat.models.Rooms.findOneById(params.roomId); + } else if (params.roomName) { + room = RocketChat.models.Rooms.findOneByName(params.roomName); + } + + if (!room || room.t !== 'p') { + throw new Meteor.Error('error-room-not-found', 'The required "roomId" or "roomName" param provided does not match any group'); + } + + if (room.archived) { + throw new Meteor.Error('error-room-archived', `The private group, ${ room.name }, is archived`); + } + + if (params.userId) { + if (!access) { + return RocketChat.API.v1.unauthorized(); + } + user = params.userId; + } + const group = RocketChat.models.Subscriptions.findOneByRoomIdAndUserId(room._id, user); + lm = group._room.lm ? group._room.lm : group._room._updatedAt; + + if (typeof group !== 'undefined' && group.open) { + if (group.ls) { + unreads = RocketChat.models.Messages.countVisibleByRoomIdBetweenTimestampsInclusive(group.rid, group.ls, lm); + unreadsFrom = group.ls; + } + userMentions = group.userMentions; + joined = true; + } + + if (access || joined) { + msgs = room.msgs; + latest = lm; + members = room.usernames.length; + } + + return RocketChat.API.v1.success({ + joined, + members, + unreads, + unreadsFrom, + msgs, + latest, + userMentions + }); + } +}); + //Create Private Group RocketChat.API.v1.addRoute('groups.create', { authRequired: true }, { post() { diff --git a/packages/rocketchat-api/server/v1/im.js b/packages/rocketchat-api/server/v1/im.js index 6d5905016705..c2ad7d8da8e0 100644 --- a/packages/rocketchat-api/server/v1/im.js +++ b/packages/rocketchat-api/server/v1/im.js @@ -49,6 +49,58 @@ RocketChat.API.v1.addRoute(['dm.close', 'im.close'], { authRequired: true }, { } }); +RocketChat.API.v1.addRoute(['dm.counters', 'im.counters'], { authRequired: true }, { + get() { + const access = RocketChat.authz.hasPermission(this.userId, 'view-room-administration'); + const ruserId = this.requestParams().userId; + let user = this.userId; + let unreads = null; + let userMentions = null; + let unreadsFrom = null; + let joined = false; + let msgs = null; + let latest = null; + let members = null; + let lm = null; + + if (ruserId) { + if (!access) { + return RocketChat.API.v1.unauthorized(); + } + user = ruserId; + } + const rs = findDirectMessageRoom(this.requestParams(), {'_id': user}); + const room = rs.room; + const dm = rs.subscription; + lm = room.lm ? room.lm : room._updatedAt; + + if (typeof dm !== 'undefined' && dm.open) { + if (dm.ls && room.msgs) { + unreads = dm.unread; + unreadsFrom = dm.ls; + } + userMentions = dm.userMentions; + joined = true; + } + + if (access || joined) { + msgs = room.msgs; + latest = lm; + members = room.usernames.length; + } + + return RocketChat.API.v1.success({ + joined, + members, + unreads, + unreadsFrom, + msgs, + latest, + userMentions + }); + } +}); + RocketChat.API.v1.addRoute(['dm.files', 'im.files'], { authRequired: true }, { get() { const findResult = findDirectMessageRoom(this.requestParams(), this.user); diff --git a/packages/rocketchat-lib/server/models/Messages.js b/packages/rocketchat-lib/server/models/Messages.js index 4fe8b4386462..80792259960a 100644 --- a/packages/rocketchat-lib/server/models/Messages.js +++ b/packages/rocketchat-lib/server/models/Messages.js @@ -20,6 +20,21 @@ RocketChat.models.Messages = new class extends RocketChat.models._Base { this.tryEnsureIndex({ 'slackBotId': 1, 'slackTs': 1 }, { sparse: 1 }); } + countVisibleByRoomIdBetweenTimestampsInclusive(roomId, afterTimestamp, beforeTimestamp, options) { + const query = { + _hidden: { + $ne: true + }, + rid: roomId, + ts: { + $gte: afterTimestamp, + $lte: beforeTimestamp + } + }; + + return this.find(query, options).count(); + } + // FIND findByMention(username, options) { const query = {'mentions.username': username}; diff --git a/tests/end-to-end/api/02-channels.js b/tests/end-to-end/api/02-channels.js index e8b035332ef2..5e3b7cd6b722 100644 --- a/tests/end-to-end/api/02-channels.js +++ b/tests/end-to-end/api/02-channels.js @@ -410,7 +410,26 @@ describe('[Channels]', function() { }) .end(done); }); - + it('/channels.counters', (done) => { + request.get(api('channels.counters')) + .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('joined', true); + expect(res.body).to.have.property('members'); + expect(res.body).to.have.property('unreads'); + expect(res.body).to.have.property('unreadsFrom'); + expect(res.body).to.have.property('msgs'); + expect(res.body).to.have.property('latest'); + expect(res.body).to.have.property('userMentions'); + }) + .end(done); + }); it('/channels.members', (done) => { request.get(api('channels.members')) .set(credentials) diff --git a/tests/end-to-end/api/03-groups.js b/tests/end-to-end/api/03-groups.js index d1c75b7f59bd..52f56af74909 100644 --- a/tests/end-to-end/api/03-groups.js +++ b/tests/end-to-end/api/03-groups.js @@ -19,7 +19,7 @@ function getRoomInfo(roomId) { }); } -describe('groups', function() { +describe('[Groups]', function() { this.retries(0); before(done => getCredentials(done)); @@ -343,6 +343,27 @@ describe('groups', function() { .end(done); }); + it('/groups.counters', (done) => { + request.get(api('groups.counters')) + .set(credentials) + .query({ + roomId: group._id + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('joined', true); + expect(res.body).to.have.property('members'); + expect(res.body).to.have.property('unreads'); + expect(res.body).to.have.property('unreadsFrom'); + expect(res.body).to.have.property('msgs'); + expect(res.body).to.have.property('latest'); + expect(res.body).to.have.property('userMentions'); + }) + .end(done); + }); + it('/groups.rename', async(done) => { const roomInfo = await getRoomInfo(group._id); diff --git a/tests/end-to-end/api/04-direct-message.js b/tests/end-to-end/api/04-direct-message.js index 5bf8a8154b48..84ee72527554 100644 --- a/tests/end-to-end/api/04-direct-message.js +++ b/tests/end-to-end/api/04-direct-message.js @@ -101,6 +101,27 @@ describe('[Direct Messages]', function() { .end(done); }); + it('/im.counters', (done) => { + request.get(api('im.counters')) + .set(credentials) + .query({ + roomId: directMessage._id + }) + .expect('Content-Type', 'application/json') + .expect(200) + .expect((res) => { + expect(res.body).to.have.property('success', true); + expect(res.body).to.have.property('joined', true); + expect(res.body).to.have.property('members'); + expect(res.body).to.have.property('unreads'); + expect(res.body).to.have.property('unreadsFrom'); + expect(res.body).to.have.property('msgs'); + expect(res.body).to.have.property('latest'); + expect(res.body).to.have.property('userMentions'); + }) + .end(done); + }); + it('/im.close', (done) => { request.post(api('im.close')) .set(credentials)