Reverted group flag code (#9784)

* Reverted group flag code

* Reverted all flagging code

* Added hyphens back
This commit is contained in:
Keith Holliday
2018-01-11 12:04:07 -06:00
committed by GitHub
parent e96d0659cb
commit e0bf6d2e55
15 changed files with 229 additions and 505 deletions

View File

@@ -5,181 +5,144 @@ import {
import { find } from 'lodash'; import { find } from 'lodash';
describe('POST /chat/:chatId/flag', () => { describe('POST /chat/:chatId/flag', () => {
context('Flags a Group Chat', () => { let user, admin, anotherUser, group;
let user, admin, anotherUser, group; const TEST_MESSAGE = 'Test Message';
const TEST_MESSAGE = 'Test Message';
beforeEach(async () => { beforeEach(async () => {
user = await generateUser({balance: 1}); user = await generateUser({balance: 1});
admin = await generateUser({balance: 1, 'contributor.admin': true}); admin = await generateUser({balance: 1, 'contributor.admin': true});
anotherUser = await generateUser(); anotherUser = await generateUser();
group = await user.post('/groups', { group = await user.post('/groups', {
name: 'Test Guild', name: 'Test Guild',
type: 'guild', type: 'guild',
privacy: 'public', privacy: 'public',
});
});
it('Returns an error when chat message is not found', async () => {
await expect(user.post(`/groups/${group._id}/chat/incorrectMessage/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatNotFound'),
});
});
it('Allows players to flag their own message', async () => {
let message = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
await expect(user.post(`/groups/${group._id}/chat/${message.message.id}/flag`)).to.eventually.be.ok;
});
it('Flags a chat', async () => {
let { message } = await anotherUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
expect(flagResult.flags[user._id]).to.equal(true);
expect(flagResult.flagCount).to.equal(1);
let groupWithFlags = await admin.get(`/groups/${group._id}`);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck.flags[user._id]).to.equal(true);
});
it('Flags a chat when the author\'s account was deleted', async () => {
let deletedUser = await generateUser();
let { message } = await deletedUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
await deletedUser.del('/user', {
password: 'password',
});
let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
expect(flagResult.flags[user._id]).to.equal(true);
expect(flagResult.flagCount).to.equal(1);
let groupWithFlags = await admin.get(`/groups/${group._id}`);
let messageToCheck = find(groupWithFlags.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', async () => {
let { message } = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
let flagResult = await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
expect(flagResult.flags[admin._id]).to.equal(true);
expect(flagResult.flagCount).to.equal(5);
let groupWithFlags = await admin.get(`/groups/${group._id}`);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck.flags[admin._id]).to.equal(true);
expect(messageToCheck.flagCount).to.equal(5);
});
it('allows admin to flag a message in a private group', async () => {
let privateGroup = await user.post('/groups', {
name: 'Test party',
type: 'party',
privacy: 'private',
});
await user.post(`/groups/${privateGroup._id}/invite`, {
uuids: [anotherUser._id],
});
await anotherUser.post(`/groups/${privateGroup._id}/join`);
let { message } = await user.post(`/groups/${privateGroup._id}/chat`, {message: TEST_MESSAGE});
let flagResult = await admin.post(`/groups/${privateGroup._id}/chat/${message.id}/flag`);
expect(flagResult.flags[admin._id]).to.equal(true);
expect(flagResult.flagCount).to.equal(5);
let groupWithFlags = await anotherUser.get(`/groups/${privateGroup._id}`);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck).to.not.exist;
});
it('does not allow non member to flag message in private group', async () => {
let privateGroup = await user.post('/groups', {
name: 'Test party',
type: 'party',
privacy: 'private',
});
let { message } = await user.post(`/groups/${privateGroup._id}/chat`, {message: TEST_MESSAGE});
await expect(anotherUser.post(`/groups/${privateGroup._id}/chat/${message.id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('groupNotFound'),
});
});
it('Returns an error when user tries to flag a message that is already flagged', async () => {
let { message } = await anotherUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
await expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatFlagAlreadyReported'),
});
});
it('shows a hidden message to the original poster', async () => {
let { message } = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
let groupWithFlags = await user.get(`/groups/${group._id}`);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck).to.exist;
let auGroupWithFlags = await anotherUser.get(`/groups/${group._id}`);
let auMessageToCheck = find(auGroupWithFlags.chat, {id: message.id});
expect(auMessageToCheck).to.not.exist;
}); });
}); });
context('Flags an inbox chat', () => { it('Returns an error when chat message is not found', async () => {
let user; await expect(user.post(`/groups/${group._id}/chat/incorrectMessage/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatNotFound'),
});
});
beforeEach(async () => { it('Allows players to flag their own message', async () => {
user = await generateUser(); let message = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
await expect(user.post(`/groups/${group._id}/chat/${message.message.id}/flag`)).to.eventually.be.ok;
});
it('Flags a chat', async () => {
let { message } = await anotherUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
expect(flagResult.flags[user._id]).to.equal(true);
expect(flagResult.flagCount).to.equal(1);
let groupWithFlags = await admin.get(`/groups/${group._id}`);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck.flags[user._id]).to.equal(true);
});
it('Flags a chat when the author\'s account was deleted', async () => {
let deletedUser = await generateUser();
let { message } = await deletedUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
await deletedUser.del('/user', {
password: 'password',
}); });
it('Flags a chat', async () => { let flagResult = await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
const messageToSend = 'messageToSend'; expect(flagResult.flags[user._id]).to.equal(true);
const receiver = await generateUser(); expect(flagResult.flagCount).to.equal(1);
await user.post('/members/send-private-message', { let groupWithFlags = await admin.get(`/groups/${group._id}`);
message: messageToSend,
toUserId: receiver._id,
});
const updatedReceiver = await receiver.get('/user'); let messageToCheck = find(groupWithFlags.chat, {id: message.id});
const sendersMessageInReceiversInbox = find(updatedReceiver.inbox.messages, (message) => { expect(messageToCheck.flags[user._id]).to.equal(true);
return message.uuid === user._id && message.text === messageToSend; });
});
const flagResult = await receiver.post(`/chat/${sendersMessageInReceiversInbox.id}/flag`); it('Flags a chat with a higher flag acount when an admin flags the message', async () => {
expect(flagResult.flags[receiver._id]).to.equal(true); let { message } = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
expect(flagResult.flagCount).to.equal(1);
const updatedReceiverAfterFlag = await receiver.get('/user'); let flagResult = await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
const receiversInboxAfterFlag = _.find(updatedReceiverAfterFlag.inbox.messages, (message) => { expect(flagResult.flags[admin._id]).to.equal(true);
return message.uuid === user._id && message.text === messageToSend; expect(flagResult.flagCount).to.equal(5);
});
expect(receiversInboxAfterFlag.flags[receiver._id]).to.equal(true); let groupWithFlags = await admin.get(`/groups/${group._id}`);
expect(receiversInboxAfterFlag.flagCount).to.equal(1);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck.flags[admin._id]).to.equal(true);
expect(messageToCheck.flagCount).to.equal(5);
});
it('allows admin to flag a message in a private group', async () => {
let privateGroup = await user.post('/groups', {
name: 'Test party',
type: 'party',
privacy: 'private',
}); });
await user.post(`/groups/${privateGroup._id}/invite`, {
uuids: [anotherUser._id],
});
await anotherUser.post(`/groups/${privateGroup._id}/join`);
let { message } = await user.post(`/groups/${privateGroup._id}/chat`, {message: TEST_MESSAGE});
let flagResult = await admin.post(`/groups/${privateGroup._id}/chat/${message.id}/flag`);
expect(flagResult.flags[admin._id]).to.equal(true);
expect(flagResult.flagCount).to.equal(5);
let groupWithFlags = await anotherUser.get(`/groups/${privateGroup._id}`);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck).to.not.exist;
});
it('does not allow non member to flag message in private group', async () => {
let privateGroup = await user.post('/groups', {
name: 'Test party',
type: 'party',
privacy: 'private',
});
let { message } = await user.post(`/groups/${privateGroup._id}/chat`, {message: TEST_MESSAGE});
await expect(anotherUser.post(`/groups/${privateGroup._id}/chat/${message.id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('groupNotFound'),
});
});
it('Returns an error when user tries to flag a message that is already flagged', async () => {
let { message } = await anotherUser.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
await user.post(`/groups/${group._id}/chat/${message.id}/flag`);
await expect(user.post(`/groups/${group._id}/chat/${message.id}/flag`))
.to.eventually.be.rejected.and.eql({
code: 404,
error: 'NotFound',
message: t('messageGroupChatFlagAlreadyReported'),
});
});
it('shows a hidden message to the original poster', async () => {
let { message } = await user.post(`/groups/${group._id}/chat`, {message: TEST_MESSAGE});
await admin.post(`/groups/${group._id}/chat/${message.id}/flag`);
let groupWithFlags = await user.get(`/groups/${group._id}`);
let messageToCheck = find(groupWithFlags.chat, {id: message.id});
expect(messageToCheck).to.exist;
let auGroupWithFlags = await anotherUser.get(`/groups/${group._id}`);
let auMessageToCheck = find(auGroupWithFlags.chat, {id: message.id});
expect(auMessageToCheck).to.not.exist;
}); });
}); });

View File

@@ -9,7 +9,6 @@ div
h2 {{$t('tipTitle', {tipNumber: currentTipNumber})}} h2 {{$t('tipTitle', {tipNumber: currentTipNumber})}}
p {{currentTip}} p {{currentTip}}
#app(:class='{"casting-spell": castingSpell}') #app(:class='{"casting-spell": castingSpell}')
report-flag-modal
amazon-payments-modal amazon-payments-modal
snackbars snackbars
router-view(v-if="!isUserLoggedIn || isStaticPage") router-view(v-if="!isUserLoggedIn || isStaticPage")
@@ -119,7 +118,6 @@ import SelectMembersModal from 'client/components/selectMembersModal.vue';
import notifications from 'client/mixins/notifications'; import notifications from 'client/mixins/notifications';
import { setup as setupPayments } from 'client/libs/payments'; import { setup as setupPayments } from 'client/libs/payments';
import amazonPaymentsModal from 'client/components/payments/amazonModal'; import amazonPaymentsModal from 'client/components/payments/amazonModal';
import reportFlagModal from 'client/components/chat/reportFlagModal';
export default { export default {
mixins: [notifications], mixins: [notifications],
@@ -133,7 +131,6 @@ export default {
BuyModal, BuyModal,
SelectMembersModal, SelectMembersModal,
amazonPaymentsModal, amazonPaymentsModal,
reportFlagModal,
}, },
data () { data () {
return { return {

View File

@@ -3,7 +3,6 @@ div
.mentioned-icon(v-if='isUserMentioned') .mentioned-icon(v-if='isUserMentioned')
.message-hidden(v-if='msg.flagCount === 1 && user.contributor.admin') Message flagged once, not hidden .message-hidden(v-if='msg.flagCount === 1 && user.contributor.admin') Message flagged once, not hidden
.message-hidden(v-if='msg.flagCount > 1 && user.contributor.admin') Message hidden .message-hidden(v-if='msg.flagCount > 1 && user.contributor.admin') Message hidden
.flag-message(v-if='msg.flags && msg.flags[user._id]') {{$t('youFlaggedThisMessage')}}
.card-body .card-body
h3.leader( h3.leader(
:class='userLevelStyle(msg)', :class='userLevelStyle(msg)',
@@ -50,7 +49,7 @@ div
top: -.5em; top: -.5em;
} }
.message-hidden, .flag-message { .message-hidden {
margin-left: 1.5em; margin-left: 1.5em;
margin-top: 1em; margin-top: 1em;
color: red; color: red;

View File

@@ -3,6 +3,7 @@
.row .row
.col-12 .col-12
copy-as-todo-modal(:group-name='groupName', :group-id='groupId') copy-as-todo-modal(:group-name='groupName', :group-id='groupId')
report-flag-modal
div(v-for="(msg, index) in messages", v-if='chat && canViewFlag(msg)') div(v-for="(msg, index) in messages", v-if='chat && canViewFlag(msg)')
// @TODO: is there a different way to do these conditionals? This creates an infinite loop // @TODO: is there a different way to do these conditionals? This creates an infinite loop
//.hr(v-if='displayDivider(msg)') //.hr(v-if='displayDivider(msg)')
@@ -82,12 +83,14 @@ import findIndex from 'lodash/findIndex';
import Avatar from '../avatar'; import Avatar from '../avatar';
import copyAsTodoModal from './copyAsTodoModal'; import copyAsTodoModal from './copyAsTodoModal';
import reportFlagModal from './reportFlagModal';
import chatCard from './chatCard'; import chatCard from './chatCard';
export default { export default {
props: ['chat', 'groupId', 'groupName', 'inbox'], props: ['chat', 'groupId', 'groupName', 'inbox'],
components: { components: {
copyAsTodoModal, copyAsTodoModal,
reportFlagModal,
chatCard, chatCard,
Avatar, Avatar,
}, },

View File

@@ -1,5 +1,5 @@
<template lang="pug"> <template lang="pug">
b-modal#report-flag(:title='$t("abuseFlagModalHeading")', size='lg', :hide-footer='true', v-if='user') b-modal#report-flag(:title='$t("abuseFlagModalHeading")', size='lg', :hide-footer='true')
.modal-header .modal-header
h4(v-html="$t('abuseFlagModalHeading', reportData)") h4(v-html="$t('abuseFlagModalHeading', reportData)")
.modal-body .modal-body
@@ -49,7 +49,7 @@ export default {
}, },
created () { created () {
this.$root.$on('habitica::report-chat', data => { this.$root.$on('habitica::report-chat', data => {
if (!data.message) return; if (!data.message || !data.groupId) return;
this.abuseObject = data.message; this.abuseObject = data.message;
this.groupId = data.groupId; this.groupId = data.groupId;
this.$root.$emit('bv::show::modal', 'report-flag'); this.$root.$emit('bv::show::modal', 'report-flag');
@@ -64,17 +64,10 @@ export default {
}, },
async reportAbuse () { async reportAbuse () {
this.notify('Thank you for reporting this violation. The moderators have been notified.'); this.notify('Thank you for reporting this violation. The moderators have been notified.');
await this.$store.dispatch('chat:flag', {
if (!this.groupId) { groupId: this.groupId,
await this.$store.dispatch('chat:flagInbox', { chatId: this.abuseObject.id,
chatId: this.abuseObject.id, });
});
} else {
await this.$store.dispatch('chat:flag', {
groupId: this.groupId,
chatId: this.abuseObject.id,
});
}
this.close(); this.close();
}, },

View File

@@ -239,7 +239,6 @@ export default {
user: message.user, user: message.user,
uuid: message.uuid, uuid: message.uuid,
id: message.id, id: message.id,
flags: message.flags,
}; };
if (message.sent) { if (message.sent) {

View File

@@ -74,12 +74,6 @@ export async function flag (store, payload) {
return response.data.data; return response.data.data;
} }
export async function flagInbox (store, payload) {
let url = `/api/v3/chat/${payload.chatId}/flag`;
let response = await axios.post(url);
return response.data.data;
}
export async function clearFlagCount (store, payload) { export async function clearFlagCount (store, payload) {
let url = `/api/v3/groups/${payload.groupId}/chat/${payload.chatId}/clearflags`; let url = `/api/v3/groups/${payload.groupId}/chat/${payload.chatId}/clearflags`;
let response = await axios.post(url); let response = await axios.post(url);

View File

@@ -66,6 +66,5 @@
"messageNotificationNotFound": "Notification not found.", "messageNotificationNotFound": "Notification not found.",
"notificationsRequired": "Notification ids are required.", "notificationsRequired": "Notification ids are required.",
"beginningOfConversation": "This is the beginning of your conversation with <%= userName %>. Remember to be kind, respectful, and follow the Community Guidelines!", "beginningOfConversation": "This is the beginning of your conversation with <%= userName %>. Remember to be kind, respectful, and follow the Community Guidelines!"
"youFlaggedThisMessage": "You have reported this message to moderators. They will contact you about the issue soon."
} }

View File

@@ -11,8 +11,6 @@ import { removeFromArray } from '../../libs/collectionManipulators';
import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/email'; import { getUserInfo, getGroupUrl, sendTxn } from '../../libs/email';
import slack from '../../libs/slack'; import slack from '../../libs/slack';
import pusher from '../../libs/pusher'; import pusher from '../../libs/pusher';
import { getAuthorEmailFromMessage } from '../../libs/chat';
import { chatReporterFactory } from '../../libs/chatReporting/chatReporterFactory';
import nconf from 'nconf'; import nconf from 'nconf';
import Bluebird from 'bluebird'; import Bluebird from 'bluebird';
import bannedWords from '../../libs/bannedWords'; import bannedWords from '../../libs/bannedWords';
@@ -20,6 +18,7 @@ import guildsAllowingBannedWords from '../../libs/guildsAllowingBannedWords';
import { getMatchesByWordArray } from '../../libs/stringUtils'; import { getMatchesByWordArray } from '../../libs/stringUtils';
import bannedSlurs from '../../libs/bannedSlurs'; 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) => { const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => {
return { email, canSend: true }; return { email, canSend: true };
}); });
@@ -41,6 +40,22 @@ const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email)
let api = {}; 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) { function textContainsBannedSlur (message) {
let bannedSlursMatched = getMatchesByWordArray(message, bannedSlurs); let bannedSlursMatched = getMatchesByWordArray(message, bannedSlurs);
return bannedSlursMatched.length > 0; return bannedSlursMatched.length > 0;
@@ -186,11 +201,11 @@ api.postChat = {
let [savedGroup] = await Bluebird.all(toSave); let [savedGroup] = await Bluebird.all(toSave);
// real-time chat is only enabled for private groups (for now only for parties) // realtime chat is only enabled for private groups (for now only for parties)
if (savedGroup.privacy === 'private' && savedGroup.type === 'party') { if (savedGroup.privacy === 'private' && savedGroup.type === 'party') {
// req.body.pusherSocketId is sent from official clients to identify the sender user's real time socket // req.body.pusherSocketId is sent from official clients to identify the sender user's real time socket
// see https://pusher.com/docs/server_api_guide/server_excluding_recipients // see https://pusher.com/docs/server_api_guide/server_excluding_recipients
pusher.trigger(`presence-group-${savedGroup._id}`, 'new-chat', newChatMessage, req.body.pusherSocketId); pusher.trigger(`presencegroup${savedGroup._id}`, 'newchat', newChatMessage, req.body.pusherSocketId);
} }
if (chatUpdated) { if (chatUpdated) {
@@ -283,51 +298,84 @@ api.likeChat = {
* @apiError (404) {NotFound} AlreadyFlagged Chat messages cannot be flagged more than once by a user * @apiError (404) {NotFound} AlreadyFlagged Chat messages cannot be flagged more than once by a user
* @apiError (404) {NotFound} messageGroupChatFlagAlreadyReported The message has already been flagged * @apiError (404) {NotFound} messageGroupChatFlagAlreadyReported The message has already been flagged
*/ */
api.flagGroupChat = { api.flagChat = {
method: 'POST', method: 'POST',
url: '/groups/:groupId/chat/:chatId/flag', url: '/groups/:groupId/chat/:chatId/flag',
middlewares: [authWithHeaders()], middlewares: [authWithHeaders()],
async handler (req, res) { async handler (req, res) {
const chatReporter = chatReporterFactory('Group', req, res); let user = res.locals.user;
const message = await chatReporter.flag(); let groupId = req.params.groupId;
res.respond(200, message); 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,
});
// @TODO:
/**
* @api {post} /api/v3/groups/:groupId/chat/:chatId/flag Flag a group chat message
* @apiDescription A message will be hidden from chat if two or more users flag a message. It will be hidden immediately if a moderator flags the message. An email is sent to the moderators about every flagged message.
* @apiName FlagChat
* @apiGroup Chat
*
* @apiParam (Path) {UUID} groupId The group id ('party' for the user party and 'habitrpg' for tavern are accepted)
* @apiParam (Path) {UUID} chatId The chat message id
*
* @apiSuccess {Object} data The flagged chat message
* @apiSuccess {UUID} data.id The id of the message
* @apiSuccess {String} data.text The text of the message
* @apiSuccess {Number} data.timestamp The timestamp of the message in milliseconds
* @apiSuccess {Object} data.likes The likes of the message
* @apiSuccess {Object} data.flags The flags of the message
* @apiSuccess {Number} data.flagCount The number of flags the message has
* @apiSuccess {UUID} data.uuid The user id of the author of the message
* @apiSuccess {String} data.user The username of the author of the message
*
* @apiUse GroupNotFound
* @apiUse MessageNotFound
* @apiUse GroupIdRequired
* @apiUse ChatIdRequired
* @apiError (404) {NotFound} AlreadyFlagged Chat messages cannot be flagged more than once by a user
* @apiError (404) {NotFound} messageGroupChatFlagAlreadyReported The message has already been flagged
*/
api.flagChat = {
method: 'POST',
url: '/chat/:chatId/flag',
middlewares: [authWithHeaders()],
async handler (req, res) {
const chatReporter = chatReporterFactory('Inbox', req, res);
const message = await chatReporter.flag();
res.respond(200, message); res.respond(200, message);
}, },
}; };

View File

@@ -1,18 +0,0 @@
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';
}
}

View File

@@ -1,36 +0,0 @@
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');
}
}

View File

@@ -1,10 +0,0 @@
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);
}
}

View File

@@ -1,97 +0,0 @@
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;
}
}

View File

@@ -1,74 +0,0 @@
import find from 'lodash/find';
import nconf from 'nconf';
import ChatReporter from './chatReporter';
import {
NotFound,
} from '../errors';
import { sendTxn } from '../email';
import slack from '../slack';
const FLAG_REPORT_EMAILS = nconf.get('FLAG_REPORT_EMAIL').split(',').map((email) => {
return { email, canSend: true };
});
export default class InboxChatReporter extends ChatReporter {
constructor (req, res) {
super(req, res);
this.user = res.locals.user;
}
async validate () {
this.req.checkParams('chatId', this.res.t('chatIdRequired')).notEmpty();
const validationErrors = this.req.validationErrors();
if (validationErrors) throw validationErrors;
const message = find(this.user.inbox.messages, {id: this.req.params.chatId});
if (!message) throw new NotFound(this.res.t('messageChatNotFound'));
return {message};
}
async notify (group, message) {
await super.notify(group, message);
sendTxn(FLAG_REPORT_EMAILS, 'flag-report-to-mods', this.emailVariables);
slack.sendInboxFlagNotification({
authorEmail: this.authorEmail,
flagger: this.user,
message,
});
}
async flagInboxMessage (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[`inbox.messages.$.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['inbox.messages.$.flagCount'] = message.flagCount;
this.user.markModified('inbox.messages');
await this.user.save();
}
async flag () {
let {message} = await this.validate();
await this.flagInboxMessage(message);
await this.notify(null, message);
return message;
}
}

View File

@@ -64,42 +64,6 @@ function sendFlagNotification ({
}); });
} }
function sendInboxFlagNotification ({
authorEmail,
flagger,
message,
}) {
if (!SLACK_FLAGGING_URL) {
return;
}
let titleLink;
let authorName;
let title = `Flag in ${flagger.profile.name}'s Inbox'`;
let text = `${flagger.profile.name} (${flagger.id}) flagged a message (language: ${flagger.preferences.language})`;
if (!message.user && message.uuid === 'system') {
authorName = 'System Message';
} else {
authorName = `${message.user} - ${authorEmail} - ${message.uuid}`;
}
flagSlack.send({
text,
attachments: [{
fallback: 'Flag Message',
color: 'danger',
author_name: authorName,
title,
title_link: titleLink,
text: message.text,
footer: `<${SLACK_FLAGGING_FOOTER_LINK}?chatId=${message.id}|Flag this message>`,
mrkdwn_in: [
'text',
],
}],
});
}
function sendSubscriptionNotification ({ function sendSubscriptionNotification ({
buyer, buyer,
recipient, recipient,
@@ -173,5 +137,5 @@ function sendSlurNotification ({
} }
module.exports = { module.exports = {
sendFlagNotification, sendSubscriptionNotification, sendSlurNotification, sendInboxFlagNotification, sendFlagNotification, sendSubscriptionNotification, sendSlurNotification,
}; };