diff --git a/test/api/v3/integration/inbox/GET-inbox_messages.test.js b/test/api/v3/integration/inbox/GET-inbox_messages.test.js index 39e5e37c37..c0c86a8a00 100644 --- a/test/api/v3/integration/inbox/GET-inbox_messages.test.js +++ b/test/api/v3/integration/inbox/GET-inbox_messages.test.js @@ -60,4 +60,10 @@ describe('GET /inbox/messages', () => { expect(messages.length).to.equal(4); }); + + it('returns only the messages of one conversation', async () => { + const messages = await user.get(`/inbox/messages?conversation=${otherUser.id}`); + + expect(messages.length).to.equal(3); + }); }); diff --git a/test/api/v4/inbox/GET-inbox-conversations.test.js b/test/api/v4/inbox/GET-inbox-conversations.test.js new file mode 100644 index 0000000000..33131b871a --- /dev/null +++ b/test/api/v4/inbox/GET-inbox-conversations.test.js @@ -0,0 +1,44 @@ +import { + generateUser, +} from '../../../helpers/api-integration/v4'; + +describe('GET /inbox/conversations', () => { + let user; + let otherUser; + let thirdUser; + + before(async () => { + [user, otherUser, thirdUser] = await Promise.all([generateUser(), generateUser(), generateUser()]); + + await otherUser.post('/members/send-private-message', { + toUserId: user.id, + message: 'first', + }); + await user.post('/members/send-private-message', { + toUserId: otherUser.id, + message: 'second', + }); + await user.post('/members/send-private-message', { + toUserId: thirdUser.id, + message: 'third', + }); + await otherUser.post('/members/send-private-message', { + toUserId: user.id, + message: 'fourth', + }); + + // message to yourself + await user.post('/members/send-private-message', { + toUserId: user.id, + message: 'fifth', + }); + }); + + it('returns the conversations', async () => { + const result = await user.get('/inbox/conversations'); + + expect(result.length).to.be.equal(3); + expect(result[0].user).to.be.equal(user.profile.name); + expect(result[0].username).to.be.equal(user.auth.local.username); + }); +}); diff --git a/website/server/controllers/api-v3/inbox.js b/website/server/controllers/api-v3/inbox.js index be32148a12..b806931a8d 100644 --- a/website/server/controllers/api-v3/inbox.js +++ b/website/server/controllers/api-v3/inbox.js @@ -12,6 +12,7 @@ let api = {}; * @apiDescription Get inbox messages for a user * * @apiParam (Query) {Number} page Load the messages of the selected Page - 10 Messages per Page + * @apiParam (Query) {GUID} conversation Loads only the messages of a conversation * * @apiSuccess {Array} data An array of inbox messages */ @@ -22,9 +23,10 @@ api.getInboxMessages = { async handler (req, res) { const user = res.locals.user; const page = req.query.page; + const conversation = req.query.conversation; const userInbox = await inboxLib.getUserInbox(user, { - page, + page, conversation, }); res.respond(200, userInbox); diff --git a/website/server/controllers/api-v4/inbox.js b/website/server/controllers/api-v4/inbox.js index bfceab993c..1a474c3220 100644 --- a/website/server/controllers/api-v4/inbox.js +++ b/website/server/controllers/api-v4/inbox.js @@ -72,4 +72,25 @@ api.clearMessages = { }, }; +/** + * @api {get} /inbox/conversations Get the conversations for a user + * @apiName conversations + * @apiGroup Inbox + * @apiDescription Get the conversations for a user + * + * @apiSuccess {Array} data An array of inbox conversations + */ +api.conversations = { + method: 'GET', + middlewares: [authWithHeaders()], + url: '/inbox/conversations', + async handler (req, res) { + const user = res.locals.user; + + const result = await inboxLib.listConversations(user); + + res.respond(200, result); + }, +}; + module.exports = api; diff --git a/website/server/libs/inbox/index.js b/website/server/libs/inbox/index.js index c131d11fa4..dac3e2f135 100644 --- a/website/server/libs/inbox/index.js +++ b/website/server/libs/inbox/index.js @@ -1,14 +1,25 @@ -import { inboxModel as Inbox } from '../../models/message'; +import {inboxModel as Inbox} from '../../models/message'; +import { + model as User, +} from '../../models/user'; +import orderBy from 'lodash/orderBy'; +import keyBy from 'lodash/keyBy'; const PM_PER_PAGE = 10; -export async function getUserInbox (user, options = {asArray: true, page: 0}) { +export async function getUserInbox (user, options = {asArray: true, page: 0, conversation: null}) { if (typeof options.asArray === 'undefined') { options.asArray = true; } + const findObj = {ownerId: user._id}; + + if (options.conversation) { + findObj.uuid = options.conversation; + } + let query = Inbox - .find({ownerId: user._id}) + .find(findObj) .sort({timestamp: -1}); if (typeof options.page !== 'undefined') { @@ -29,12 +40,45 @@ export async function getUserInbox (user, options = {asArray: true, page: 0}) { } } +export async function listConversations (user) { + let query = Inbox + .aggregate([ + { + $match: { + ownerId: user._id, + }, + }, + { + $group: { + _id: '$uuid', + timestamp: {$max: '$timestamp'}, // sort before group doesn't work - use the max value to sort it again after + }, + }, + ]); + + const conversationsList = orderBy(await query.exec(), ['timestamp'], ['desc']).map(c => c._id); + + const users = await User.find({_id: {$in: conversationsList}}) + .select('_id profile.name auth.local.username') + .lean() + .exec(); + + const usersMap = keyBy(users, '_id'); + const conversations = conversationsList.map(userId => ({ + uuid: usersMap[userId]._id, + user: usersMap[userId].profile.name, + username: usersMap[userId].auth.local.username, + })); + + return conversations; +} + export async function getUserInboxMessage (user, messageId) { return Inbox.findOne({ownerId: user._id, _id: messageId}).exec(); } export async function deleteMessage (user, messageId) { - const message = await Inbox.findOne({_id: messageId, ownerId: user._id }).exec(); + const message = await Inbox.findOne({_id: messageId, ownerId: user._id}).exec(); if (!message) return false; await Inbox.remove({_id: message._id, ownerId: user._id}).exec();