diff --git a/test/api/v3/integration/chat/POST-chat.flag.test.js b/test/api/v3/integration/chat/POST-chat.flag.test.js new file mode 100644 index 0000000000..5f4954d28a --- /dev/null +++ b/test/api/v3/integration/chat/POST-chat.flag.test.js @@ -0,0 +1,126 @@ +import { + generateUser, + requester, + translate as t, +} from '../../../../helpers/api-integration.helper'; +import _ from 'lodash'; + +describe('POST /chat/:chatId/flag', () => { + let user; + let api; + let group; + let testMessage = 'Test Message'; + + before(() => { + let groupName = 'Test Guild'; + let groupType = 'guild'; + let groupPrivacy = 'public'; + + return generateUser({balance: 1}).then((generatedUser) => { + user = generatedUser; + api = requester(user); + }) + .then(() => { + return api.post('/groups', { + name: groupName, + type: groupType, + privacy: groupPrivacy, + }); + }) + .then((generatedGroup) => { + group = generatedGroup; + }); + }); + + it('Returns an error when chat message is not found', () => { + return expect(api.post(`/groups/${group._id}/chat/incorrectMessage/flag`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('messageGroupChatNotFound'), + }); + }); + + it('Returns an error when user tries to flag their own message', () => { + return api.post(`/groups/${group._id}/chat`, { message: testMessage}) + .then((result) => { + return expect(api.post(`/groups/${group._id}/chat/${result.message.id}/flag`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('messageGroupChatFlagOwnMessage'), + }); + }); + }); + + it('Flags a chat', () => { + let api2; + let message; + + return generateUser().then((generatedUser) => { + api2 = requester(generatedUser); + return api2.post(`/groups/${group._id}/chat`, { message: testMessage}); + }) + .then((result) => { + message = result.message; + return api.post(`/groups/${group._id}/chat/${message.id}/flag`); + }) + .then((result) => { + expect(result.flags[user._id]).to.equal(true); + expect(result.flagCount).to.equal(1); + return api.get(`/groups/${group._id}`); + }) + .then((updatedGroup) => { + let messageToCheck = _.find(updatedGroup.chat, {id: message.id}); + expect(messageToCheck.flags[user._id]).to.equal(true); + }); + }); + + it('Flags a chat with a higher flag acount when an admin flags the message', () => { + let api2; + let secondUser; + let message; + + return generateUser({'contributor.admin': true}).then((generatedUser) => { + secondUser = generatedUser; + api2 = requester(generatedUser); + return api.post(`/groups/${group._id}/chat`, { message: testMessage}); + }) + .then((result) => { + message = result.message; + return api2.post(`/groups/${group._id}/chat/${message.id}/flag`); + }) + .then((result) => { + expect(result.flags[secondUser._id]).to.equal(true); + expect(result.flagCount).to.equal(5); + return api.get(`/groups/${group._id}`); + }) + .then((updatedGroup) => { + let messageToCheck = _.find(updatedGroup.chat, {id: message.id}); + expect(messageToCheck.flags[secondUser._id]).to.equal(true); + expect(messageToCheck.flagCount).to.equal(5); + }); + }); + + it('Returns an error when user tries to flag a message that is already flagged', () => { + let api2; + let message; + + return generateUser().then((generatedUser) => { + api2 = requester(generatedUser); + return api2.post(`/groups/${group._id}/chat`, { message: testMessage}); + }) + .then((result) => { + message = result.message; + return api.post(`/groups/${group._id}/chat/${message.id}/flag`); + }) + .then(() => { + return expect(api.post(`/groups/${group._id}/chat/${message.id}/flag`)) + .to.eventually.be.rejected.and.eql({ + code: 404, + error: 'NotFound', + message: t('messageGroupChatFlagAlreadyReported'), + }); + }); + }); +}); diff --git a/test/api/v3/integration/chat/POST-chat.like.test.js b/test/api/v3/integration/chat/POST-chat.like.test.js index 1096aa11e3..66403221bd 100644 --- a/test/api/v3/integration/chat/POST-chat.like.test.js +++ b/test/api/v3/integration/chat/POST-chat.like.test.js @@ -3,6 +3,7 @@ import { requester, translate as t, } from '../../../../helpers/api-integration.helper'; +import _ from 'lodash'; describe('POST /chat/:chatId/like', () => { let user; @@ -54,15 +55,49 @@ describe('POST /chat/:chatId/like', () => { it('Likes a chat', () => { let api2; + let message; + return generateUser().then((generatedUser) => { api2 = requester(generatedUser); return api2.post(`/groups/${group._id}/chat`, { message: testMessage}); }) .then((result) => { - return api.post(`/groups/${group._id}/chat/${result.message.id}/like`); + message = result.message; + return api.post(`/groups/${group._id}/chat/${message.id}/like`); }) .then((result) => { expect(result.likes[user._id]).to.equal(true); + return api.get(`/groups/${group._id}`); + }) + .then((updatedGroup) => { + let messageToCheck = _.find(updatedGroup.chat, {id: message.id}); + expect(messageToCheck.likes[user._id]).to.equal(true); + }); + }); + + it('Unlikes a chat', () => { + let api2; + let message; + + return generateUser().then((generatedUser) => { + api2 = requester(generatedUser); + return api2.post(`/groups/${group._id}/chat`, { message: testMessage}); + }) + .then((result) => { + message = result.message; + return api.post(`/groups/${group._id}/chat/${message.id}/like`); + }) + .then((result) => { + expect(result.likes[user._id]).to.equal(true); + return api.post(`/groups/${group._id}/chat/${message.id}/like`); + }) + .then((result) => { + expect(result.likes[user._id]).to.equal(undefined); + return api.get(`/groups/${group._id}`); + }) + .then((updatedGroup) => { + let messageToCheck = _.find(updatedGroup.chat, {id: message.id}); + expect(messageToCheck.likes[user._id]).to.equal(undefined); }); }); }); diff --git a/website/src/controllers/api-v3/chat.js b/website/src/controllers/api-v3/chat.js index d8b79b3979..7fc6f7deda 100644 --- a/website/src/controllers/api-v3/chat.js +++ b/website/src/controllers/api-v3/chat.js @@ -1,10 +1,13 @@ import { authWithHeaders } from '../../middlewares/api-v3/auth'; import cron from '../../middlewares/api-v3/cron'; import { model as Group } from '../../models/group'; +import { model as User } from '../../models/user'; import { NotFound, } from '../../libs/api-v3/errors'; import _ from 'lodash'; +import { sendTxn } from '../../libs/api-v3/email'; +import nconf from 'nconf'; let api = {}; @@ -137,10 +140,140 @@ api.likeChat = { message.likes[user._id] = true; } - return group.save(); + return Group.update( + {_id: group._id, 'chat.id': message.id}, + {$set: {'chat.$.likes': message.likes}} + ); }) - .then((group) => { - if (!group) throw new NotFound(res.t('groupNotFound')); + .then((groupSaved) => { + if (!groupSaved) throw new NotFound(res.t('groupNotFound')); + res.respond(200, message); + }) + .catch(next); + }, +}; + +/** + * @api {post} /groups/:groupId/chat/:chatId/like Like a group chat message + * @apiVersion 3.0.0 + * @apiName LikeChat + * @apiGroup Chat + * + * @apiParam {groupId} groupId The group _id + * @apiParam {chatId} chatId The chat message _id + * + * @apiSuccess {Array} chat An array of chat messages + */ +api.flagChat = { + method: 'Post', + url: '/groups/:groupId/chat/:chatId/flag', + middlewares: [authWithHeaders(), cron], + handler (req, res, next) { + let user = res.locals.user; + let groupId = req.params.groupId; + let message; + let group; + let author; + + req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); + req.checkParams('chatId', res.t('chatIdRequired')).notEmpty(); + + let validationErrors = req.validationErrors(); + if (validationErrors) return next(validationErrors); + + Group.getGroup(user, groupId) + .then((groupFound) => { + if (!groupFound) throw new NotFound(res.t('groupNotFound')); + group = groupFound; + message = _.find(group.chat, {id: req.params.chatId}); + + if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); + + if (message.uuid === user._id) throw new NotFound(res.t('messageGroupChatFlagOwnMessage')); + + return User.findOne({_id: message.uuid}, {auth: 1}); + }) + .then((foundAuthor) => { + author = foundAuthor; + + // Log user ids that have flagged the message + if (!message.flags) message.flags = {}; + if (message.flags[user._id] && !user.contributor.admin) throw new NotFound(res.t('messageGroupChatFlagAlreadyReported')); + message.flags[user._id] = true; + + // Log total number of flags (publicly viewable) + if (!message.flagCount) message.flagCount = 0; + if (user.contributor.admin) { + // Arbitraty amount, higher than 2 + message.flagCount = 5; + } else { + message.flagCount++; + } + + // return group.save(); + return Group.update( + {_id: group._id, 'chat.id': message.id}, + {$set: { + 'chat.$.flags': message.flags, + 'chat.$.flagCount': message.flagCount, + }}); + }) + .then((group2) => { + if (!group2) throw new NotFound(res.t('groupNotFound')); + + let addressesToSendTo = nconf.get('FLAG_REPORT_EMAIL'); + addressesToSendTo = typeof addressesToSendTo === 'string' ? JSON.parse(addressesToSendTo) : addressesToSendTo; + + if (Array.isArray(addressesToSendTo)) { + addressesToSendTo = addressesToSendTo.map((email) => { + return {email, canSend: true}; + }); + } else { + addressesToSendTo = {email: addressesToSendTo}; + } + + let reporterEmailContent; + if (user.auth.local) { + reporterEmailContent = user.auth.local.email; + } else if (user.auth.facebook && user.auth.facebook.emails && user.auth.facebook.emails[0]) { + reporterEmailContent = user.auth.facebook.emails[0].value; + } + + let authorEmailContent; + if (author.auth.local) { + authorEmailContent = author.auth.local.email; + } else if (author.auth.facebook && author.auth.facebook.emails && author.auth.facebook.emails[0]) { + authorEmailContent = author.auth.facebook.emails[0].value; + } + + let groupUrl; + if (group._id === 'habitrpg') { + groupUrl = '/#/options/groups/tavern'; + } else if (group.type === 'guild') { + groupUrl = `/#/options/groups/guilds/{$group._id}`; + } else { + groupUrl = 'party'; + } + + sendTxn(addressesToSendTo, 'flag-report-to-mods', [ + {name: 'MESSAGE_TIME', content: (new Date(message.timestamp)).toString()}, + {name: 'MESSAGE_TEXT', content: message.text}, + + {name: 'REPORTER_USERNAME', content: user.profile.name}, + {name: 'REPORTER_UUID', content: user._id}, + {name: 'REPORTER_EMAIL', content: reporterEmailContent}, + {name: 'REPORTER_MODAL_URL', content: `/static/front/#?memberId={$user._id}`}, + + {name: 'AUTHOR_USERNAME', content: message.user}, + {name: 'AUTHOR_UUID', content: message.uuid}, + {name: 'AUTHOR_EMAIL', content: authorEmailContent}, + {name: 'AUTHOR_MODAL_URL', content: `/static/front/#?memberId={$message.uuid}`}, + + {name: 'GROUP_NAME', content: group.name}, + {name: 'GROUP_TYPE', content: group.type}, + {name: 'GROUP_ID', content: group._id}, + {name: 'GROUP_URL', content: groupUrl}, + ]); res.respond(200, message); }) .catch(next);