From 037280601a91d5b13f9614a16847f587135df9ff Mon Sep 17 00:00:00 2001 From: Keith Holliday Date: Sun, 11 Feb 2018 08:35:24 -0700 Subject: [PATCH] Refactored group chat flagging (#9907) --- website/server/controllers/api-v3/chat.js | 94 +----------------- website/server/libs/chat.js | 18 ++++ .../server/libs/chatReporting/chatReporter.js | 36 +++++++ .../libs/chatReporting/chatReporterFactory.js | 11 +++ .../libs/chatReporting/groupChatReporter.js | 97 +++++++++++++++++++ 5 files changed, 166 insertions(+), 90 deletions(-) create mode 100644 website/server/libs/chat.js create mode 100644 website/server/libs/chatReporting/chatReporter.js create mode 100644 website/server/libs/chatReporting/chatReporterFactory.js create mode 100644 website/server/libs/chatReporting/groupChatReporter.js diff --git a/website/server/controllers/api-v3/chat.js b/website/server/controllers/api-v3/chat.js index bfb93cc46b..fba5277d49 100644 --- a/website/server/controllers/api-v3/chat.js +++ b/website/server/controllers/api-v3/chat.js @@ -11,6 +11,8 @@ import { removeFromArray } from '../../libs/collectionManipulators'; import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/email'; import slack from '../../libs/slack'; import pusher from '../../libs/pusher'; +import { getAuthorEmailFromMessage } from '../../libs/chat'; +import { chatReporterFactory } from '../../libs/chatReporting/chatReporterFactory'; import nconf from 'nconf'; import Bluebird from 'bluebird'; import bannedWords from '../../libs/bannedWords'; @@ -18,7 +20,6 @@ import guildsAllowingBannedWords from '../../libs/guildsAllowingBannedWords'; import { getMatchesByWordArray } from '../../libs/stringUtils'; import bannedSlurs from '../../libs/bannedSlurs'; -const COMMUNITY_MANAGER_EMAIL = nconf.get('EMAILS:COMMUNITY_MANAGER_EMAIL'); const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => { return { email, canSend: true }; }); @@ -40,22 +41,6 @@ const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) let api = {}; -async function getAuthorEmailFromMessage (message) { - let authorId = message.uuid; - - if (authorId === 'system') { - return 'system'; - } - - let author = await User.findOne({_id: authorId}, {auth: 1}).exec(); - - if (author) { - return getUserInfo(author, ['email']).email; - } else { - return 'Author Account Deleted'; - } -} - function textContainsBannedSlur (message) { let bannedSlursMatched = getMatchesByWordArray(message, bannedSlurs); return bannedSlursMatched.length > 0; @@ -303,79 +288,8 @@ api.flagChat = { url: '/groups/:groupId/chat/:chatId/flag', middlewares: [authWithHeaders()], async handler (req, res) { - let user = res.locals.user; - let groupId = req.params.groupId; - req.checkParams('groupId', res.t('groupIdRequired')).notEmpty(); - req.checkParams('chatId', res.t('chatIdRequired')).notEmpty(); - - let validationErrors = req.validationErrors(); - if (validationErrors) throw validationErrors; - - let group = await Group.getGroup({ - user, - groupId, - optionalMembership: user.contributor.admin, - }); - if (!group) throw new NotFound(res.t('groupNotFound')); - let message = _.find(group.chat, {id: req.params.chatId}); - - if (!message) throw new NotFound(res.t('messageGroupChatNotFound')); - if (message.uuid === 'system') throw new BadRequest(res.t('messageCannotFlagSystemMessages', {communityManagerEmail: COMMUNITY_MANAGER_EMAIL})); - let update = {$set: {}}; - - // Log user ids that have flagged the message - if (!message.flags) message.flags = {}; - // TODO fix error type - if (message.flags[user._id] && !user.contributor.admin) throw new NotFound(res.t('messageGroupChatFlagAlreadyReported')); - message.flags[user._id] = true; - update.$set[`chat.$.flags.${user._id}`] = true; - - // Log total number of flags (publicly viewable) - if (!message.flagCount) message.flagCount = 0; - if (user.contributor.admin) { - // Arbitrary amount, higher than 2 - message.flagCount = 5; - } else { - message.flagCount++; - } - update.$set['chat.$.flagCount'] = message.flagCount; - - await Group.update( - {_id: group._id, 'chat.id': message.id}, - update - ).exec(); - - let reporterEmailContent = getUserInfo(user, ['email']).email; - let authorEmail = await getAuthorEmailFromMessage(message); - let groupUrl = getGroupUrl(group); - - sendTxn(FLAG_REPORT_EMAILS, '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: authorEmail}, - {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}, - ]); - - slack.sendFlagNotification({ - authorEmail, - flagger: user, - group, - message, - }); - + const chatReporter = chatReporterFactory('Group', req, res); + const message = await chatReporter.flag(); res.respond(200, message); }, }; diff --git a/website/server/libs/chat.js b/website/server/libs/chat.js new file mode 100644 index 0000000000..3554bc90ee --- /dev/null +++ b/website/server/libs/chat.js @@ -0,0 +1,18 @@ +import { model as User } from '../models/user'; +import { getUserInfo } from './email'; + +export async function getAuthorEmailFromMessage (message) { + let authorId = message.uuid; + + if (authorId === 'system') { + return 'system'; + } + + let author = await User.findOne({_id: authorId}, {auth: 1}).exec(); + + if (author) { + return getUserInfo(author, ['email']).email; + } else { + return 'Author Account Deleted'; + } +} diff --git a/website/server/libs/chatReporting/chatReporter.js b/website/server/libs/chatReporting/chatReporter.js new file mode 100644 index 0000000000..148dbd4696 --- /dev/null +++ b/website/server/libs/chatReporting/chatReporter.js @@ -0,0 +1,36 @@ +import { +} from '../errors'; +import { getUserInfo } from '../email'; +import { getAuthorEmailFromMessage } from '../chat'; + +export default class ChatReporter { + constructor (req, res) { + this.req = req; + this.res = res; + } + + async validate () {} + + async notify (group, message) { + const reporterEmailContent = getUserInfo(this.user, ['email']).email; + this.authorEmail = await getAuthorEmailFromMessage(message); + this.emailVariables = [ + {name: 'MESSAGE_TIME', content: (new Date(message.timestamp)).toString()}, + {name: 'MESSAGE_TEXT', content: message.text}, + + {name: 'REPORTER_USERNAME', content: this.user.profile.name}, + {name: 'REPORTER_UUID', content: this.user._id}, + {name: 'REPORTER_EMAIL', content: reporterEmailContent}, + {name: 'REPORTER_MODAL_URL', content: `/static/front/#?memberId=${this.user._id}`}, + + {name: 'AUTHOR_USERNAME', content: message.user}, + {name: 'AUTHOR_UUID', content: message.uuid}, + {name: 'AUTHOR_EMAIL', content: this.authorEmail}, + {name: 'AUTHOR_MODAL_URL', content: `/static/front/#?memberId=${message.uuid}`}, + ]; + } + + async flag () { + throw new Error('Flag must be implemented'); + } +} diff --git a/website/server/libs/chatReporting/chatReporterFactory.js b/website/server/libs/chatReporting/chatReporterFactory.js new file mode 100644 index 0000000000..cd97da6793 --- /dev/null +++ b/website/server/libs/chatReporting/chatReporterFactory.js @@ -0,0 +1,11 @@ +import GroupChatReporter from './groupChatReporter'; +// import InboxChatReporter from './inboxChatReporter'; + +export function chatReporterFactory (type, req, res) { + if (type === 'Group') { + return new GroupChatReporter(req, res); + } + // else if (type === 'Inbox') { + // return new InboxChatReporter(req, res); + // } +} diff --git a/website/server/libs/chatReporting/groupChatReporter.js b/website/server/libs/chatReporting/groupChatReporter.js new file mode 100644 index 0000000000..58dee49e63 --- /dev/null +++ b/website/server/libs/chatReporting/groupChatReporter.js @@ -0,0 +1,97 @@ +import find from 'lodash/find'; +import nconf from 'nconf'; + +import ChatReporter from './chatReporter'; +import { + BadRequest, + NotFound, +} from '../errors'; +import { getGroupUrl, sendTxn } from '../email'; +import slack from '../slack'; +import { model as Group } from '../../models/group'; + +const COMMUNITY_MANAGER_EMAIL = nconf.get('EMAILS:COMMUNITY_MANAGER_EMAIL'); +const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => { + return { email, canSend: true }; +}); + +export default class GroupChatReporter extends ChatReporter { + constructor (req, res) { + super(req, res); + + this.user = res.locals.user; + this.groupId = req.params.groupId; + } + + async validate () { + this.req.checkParams('groupId', this.res.t('groupIdRequired')).notEmpty(); + this.req.checkParams('chatId', this.res.t('chatIdRequired')).notEmpty(); + + let validationErrors = this.req.validationErrors(); + if (validationErrors) throw validationErrors; + + let group = await Group.getGroup({ + user: this.user, + groupId: this.groupId, + optionalMembership: this.user.contributor.admin, + }); + if (!group) throw new NotFound(this.res.t('groupNotFound')); + + let message = find(group.chat, {id: this.req.params.chatId}); + if (!message) throw new NotFound(this.res.t('messageGroupChatNotFound')); + if (message.uuid === 'system') throw new BadRequest(this.res.t('messageCannotFlagSystemMessages', {communityManagerEmail: COMMUNITY_MANAGER_EMAIL})); + + return {message, group}; + } + + async notify (group, message) { + await super.notify(group, message); + + const groupUrl = getGroupUrl(group); + sendTxn(FLAG_REPORT_EMAILS, 'flag-report-to-mods', this.emailVariables.concat([ + {name: 'GROUP_NAME', content: group.name}, + {name: 'GROUP_TYPE', content: group.type}, + {name: 'GROUP_ID', content: group._id}, + {name: 'GROUP_URL', content: groupUrl}, + ])); + + slack.sendFlagNotification({ + authorEmail: this.authorEmail, + flagger: this.user, + group, + message, + }); + } + + async flagGroupMessage (group, message) { + let update = {$set: {}}; + // Log user ids that have flagged the message + if (!message.flags) message.flags = {}; + // TODO fix error type + if (message.flags[this.user._id] && !this.user.contributor.admin) throw new NotFound(this.res.t('messageGroupChatFlagAlreadyReported')); + message.flags[this.user._id] = true; + update.$set[`chat.$.flags.${this.user._id}`] = true; + + // Log total number of flags (publicly viewable) + if (!message.flagCount) message.flagCount = 0; + if (this.user.contributor.admin) { + // Arbitrary amount, higher than 2 + message.flagCount = 5; + } else { + message.flagCount++; + } + update.$set['chat.$.flagCount'] = message.flagCount; + + await Group.update( + {_id: group._id, 'chat.id': message.id}, + update + ).exec(); + } + + async flag () { + let {message, group} = await this.validate(); + await this.flagGroupMessage(group, message); + await this.notify(group, message); + return message; + } +}