From 7522bd9cd6bcfe72c3f1903ff5f0c5d535b11800 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 21 Mar 2017 15:06:02 -0700 Subject: [PATCH] Sends a mail to the suspended user. --- .../Community/CommunityContainer.js | 2 +- .../Community/components/BanUserDialog.js | 3 +- .../Community/components/SuspendUserDialog.js | 2 +- .../src/graphql/mutations/index.js | 5 +-- .../src/graphql/mutations/suspendUser.graphql | 4 +-- client/coral-admin/src/translations.json | 4 +-- graph/mutators/user.js | 8 +++-- graph/resolvers/root_mutation.js | 4 +-- graph/typeDefs.graphql | 4 +-- services/users.js | 33 ++++++++++++++++++- views/email/suspension.ejs | 1 + views/email/suspension.txt.ejs | 1 + 12 files changed, 54 insertions(+), 17 deletions(-) create mode 100644 views/email/suspension.ejs create mode 100644 views/email/suspension.txt.ejs diff --git a/client/coral-admin/src/containers/Community/CommunityContainer.js b/client/coral-admin/src/containers/Community/CommunityContainer.js index a7e59a14a..4f65303ff 100644 --- a/client/coral-admin/src/containers/Community/CommunityContainer.js +++ b/client/coral-admin/src/containers/Community/CommunityContainer.js @@ -3,7 +3,7 @@ import {connect} from 'react-redux'; import {compose} from 'react-apollo'; import {modUserFlaggedQuery} from 'coral-admin/src/graphql/queries'; -import {banUser, setUserStatus, suspendUser} from '../../graphql/mutations'; +import {banUser, setUserStatus, suspendUser} from 'coral-admin/src/graphql/mutations'; import { fetchAccounts, diff --git a/client/coral-admin/src/containers/Community/components/BanUserDialog.js b/client/coral-admin/src/containers/Community/components/BanUserDialog.js index 25169ffee..affffcd54 100644 --- a/client/coral-admin/src/containers/Community/components/BanUserDialog.js +++ b/client/coral-admin/src/containers/Community/components/BanUserDialog.js @@ -5,7 +5,8 @@ import styles from './BanUserDialog.css'; import Button from 'coral-ui/components/Button'; import I18n from 'coral-framework/modules/i18n/i18n'; -import translations from '../../../translations'; +import translations from 'coral-admin/src/translations.json'; + const lang = new I18n(translations); const BanUserDialog = ({open, handleClose, handleBanUser, user}) => ( diff --git a/client/coral-admin/src/containers/Community/components/SuspendUserDialog.js b/client/coral-admin/src/containers/Community/components/SuspendUserDialog.js index 899920b46..44f38f796 100644 --- a/client/coral-admin/src/containers/Community/components/SuspendUserDialog.js +++ b/client/coral-admin/src/containers/Community/components/SuspendUserDialog.js @@ -52,7 +52,7 @@ class SuspendUserDialog extends Component { const cancel = this.props.onClose; const next = () => this.setState({stage: stage + 1}); const suspend = () => { - suspendUser({userId: user.user.id}) + suspendUser({userId: user.user.id, message: this.state.email}) .then(() => { this.props.handleClose(); }); diff --git a/client/coral-admin/src/graphql/mutations/index.js b/client/coral-admin/src/graphql/mutations/index.js index 4a574cc93..7ca1b078f 100644 --- a/client/coral-admin/src/graphql/mutations/index.js +++ b/client/coral-admin/src/graphql/mutations/index.js @@ -32,10 +32,11 @@ export const setUserStatus = graphql(SET_USER_STATUS, { export const suspendUser = graphql(SUSPEND_USER, { props: ({mutate}) => ({ - suspendUser: ({userId}) => { + suspendUser: ({userId, message}) => { return mutate({ variables: { - userId + userId, + message }, refetchQueries: ['Users'] }); diff --git a/client/coral-admin/src/graphql/mutations/suspendUser.graphql b/client/coral-admin/src/graphql/mutations/suspendUser.graphql index f34d93370..0d56a3cc0 100644 --- a/client/coral-admin/src/graphql/mutations/suspendUser.graphql +++ b/client/coral-admin/src/graphql/mutations/suspendUser.graphql @@ -1,5 +1,5 @@ -mutation suspendUser($userId: ID!) { - suspendUser(id: $userId) { +mutation suspendUser($userId: ID!, $message: String) { + suspendUser(id: $userId, message: $message) { errors { translation_key } diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index a5b50b21c..f59499e50 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -119,7 +119,7 @@ "suspenduser": { "title": "Suspend a user", "title_0": "We noticed you rejected a username", - "description_0": "Would you like to temporarily ban this user becuase of their {0}? Doing so will temporarily hide their comments until they rewrite their {0}.", + "description_0": "Would you like to temporarily ban this user because of their {0}? Doing so will temporarily hide their comments until they rewrite their {0}.", "title_1": "Notify the user of their temporary suspension", "description_1": "Suspending this user will temporarily disable their account and hide all of their comments on the site.", "no_cancel": "No, cancel", @@ -128,7 +128,7 @@ "bio": "bio", "username": "username", "email_subject": "Your account has been suspended", - "email": "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your {0}. Please e-mail moderator@newsorg.com if you have any questions or concerns.", + "email": "Another member of the community recently flagged your username for review. Because of its content your user was rejected. This means you can no longer comment, like, or flag content until you rewrite your username. Please e-mail us if you have any questions or concerns.", "write_message": "Write a message" }, "dashboard": { diff --git a/graph/mutators/user.js b/graph/mutators/user.js index 6f94ae9b1..48966c5e8 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -6,9 +6,11 @@ const setUserStatus = ({user}, {id, status}) => { .then(res => res); }; -const suspendUser = ({user}, {id}) => { - return UsersService.suspendUser(id) - .then(res => res); +const suspendUser = ({user}, {id, message}) => { + return UsersService.suspendUser(id, message) + .then(res => { + return res; + }); }; module.exports = (context) => { diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index f819cc586..a474407d2 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -47,8 +47,8 @@ const RootMutation = { setUserStatus(_, {id, status}, {mutators: {User}}) { return wrapResponse(null)(User.setUserStatus({id, status})); }, - suspendUser(_, {id}, {mutators: {User}}) { - return wrapResponse(null)(User.suspendUser({id})); + suspendUser(_, {id, message}, {mutators: {User}}) { + return wrapResponse(null)(User.suspendUser({id, message})); }, setCommentStatus(_, {id, status}, {mutators: {Comment}}) { return wrapResponse(null)(Comment.setCommentStatus({id, status})); diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index bfb88c779..8b07c2ab2 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -724,8 +724,8 @@ type RootMutation { # Sets User status. Requires the `ADMIN` role. setUserStatus(id: ID!, status: USER_STATUS!): SetUserStatusResponse - # Sets User status to BANNED and canEditName to true. Requires the `ADMIN` role. - suspendUser(id: ID!): SuspendUserResponse + # Sets User status to BANNED and canEditName to true. It sends a message to the banned User. Requires the `ADMIN` role. + suspendUser(id: ID!, message: String): SuspendUserResponse # Sets Comment status. Requires the `ADMIN` role. setCommentStatus(id: ID!, status: COMMENT_STATUS!): SetCommentStatusResponse diff --git a/services/users.js b/services/users.js index cd10c418c..2d6a94a79 100644 --- a/services/users.js +++ b/services/users.js @@ -1,8 +1,12 @@ const bcrypt = require('bcrypt'); const jwt = require('jsonwebtoken'); +const ejs = require('ejs'); const Wordlist = require('./wordlist'); + const errors = require('../errors'); + const uuid = require('uuid'); + const redis = require('./redis'); const redisClient = redis.createClient(); @@ -14,6 +18,7 @@ const RECAPTCHA_WINDOW_SECONDS = 60 * 10; // 10 minutes. const RECAPTCHA_INCORRECT_TRIGGER = 5; // after 3 incorrect attempts, recaptcha will be required. const ActionsService = require('./actions'); +const MailerService = require('./mailer'); // In the event that the TALK_SESSION_SECRET is missing but we are testing, then // set the process.env.TALK_SESSION_SECRET. @@ -451,7 +456,7 @@ module.exports = class UsersService { * @param {String} id id of a user * @param {Function} done callback after the operation is complete */ - static suspendUser(id) { + static suspendUser(id, message) { return UserModel.update({ id }, { @@ -459,6 +464,32 @@ module.exports = class UsersService { status: 'BANNED', canEditName: true } + }) + .then(() => { + return UsersService.findById(id) + .then((user) => { + if (message) { + let localProfile = user.profiles.find((profile) => profile.provider === 'local'); + + if (localProfile) { + const options = + { + app: ejs, // needed to render the templates. + template: 'email/suspension', // needed to know which template to render! + locals: { // specifies the template locals. + body: message + }, + subject: 'Email Suspension', + to: localProfile.id // This only works if the user has registered via e-mail. + // We may want a standard way to access a user's e-mail address in the future + }; + return MailerService.sendSimple(options); + } else { + return Promise.reject(errors.ErrMissingEmail); + } + } + }); + }); } diff --git a/views/email/suspension.ejs b/views/email/suspension.ejs new file mode 100644 index 000000000..f19cc8043 --- /dev/null +++ b/views/email/suspension.ejs @@ -0,0 +1 @@ +<%= message %> diff --git a/views/email/suspension.txt.ejs b/views/email/suspension.txt.ejs new file mode 100644 index 000000000..f19cc8043 --- /dev/null +++ b/views/email/suspension.txt.ejs @@ -0,0 +1 @@ +<%= message %>