diff --git a/client/coral-admin/src/containers/Community/components/SuspendUserDialog.js b/client/coral-admin/src/containers/Community/components/SuspendUserDialog.js
index 827352c42..0841333be 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.handleClose;
const next = () => this.setState({stage: stage + 1});
const suspend = () => {
- suspendUser({userId: user.user.id, message: this.state.email})
+ suspendUser({id: user.user.id, message: this.state.email, mustChangeUsername: true})
.then(() => {
this.props.handleClose();
});
@@ -79,7 +79,7 @@ class SuspendUserDialog extends Component {
open={open}
onClose={handleClose}
onCancel={handleClose}
- title={lang.t('suspenduser.title')}>
+ title={lang.t('suspenduser.suspend_user')}>
{lang.t(stages[stage].title, lang.t('suspenduser.username'))}
diff --git a/client/coral-admin/src/graphql/mutations/index.js b/client/coral-admin/src/graphql/mutations/index.js
index 16c391907..aad348c5c 100644
--- a/client/coral-admin/src/graphql/mutations/index.js
+++ b/client/coral-admin/src/graphql/mutations/index.js
@@ -32,11 +32,10 @@ export const setUserStatus = graphql(SET_USER_STATUS, {
export const suspendUser = graphql(SUSPEND_USER, {
props: ({mutate}) => ({
- suspendUser: ({userId, message}) => {
+ suspendUser: (input) => {
return mutate({
variables: {
- userId,
- message
+ input,
},
refetchQueries: ['Users']
});
diff --git a/client/coral-admin/src/graphql/mutations/suspendUser.graphql b/client/coral-admin/src/graphql/mutations/suspendUser.graphql
index 0d56a3cc0..f6ab2426f 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!, $message: String) {
- suspendUser(id: $userId, message: $message) {
+mutation suspendUser($input: SuspendUserInput!) {
+ suspendUser(input: $input) {
errors {
translation_key
}
diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js
index 79fba460c..827991cb5 100644
--- a/client/coral-framework/actions/auth.js
+++ b/client/coral-framework/actions/auth.js
@@ -187,7 +187,7 @@ export const fetchSignUpFacebook = () => (dispatch) => {
);
};
-export const facebookCallback = (err, data) => (dispatch) => {
+export const facebookCallback = (err, data) => (dispatch, getState) => {
if (err) {
dispatch(signInFacebookFailure(err));
return;
@@ -196,8 +196,10 @@ export const facebookCallback = (err, data) => (dispatch) => {
dispatch(handleAuthToken(data.token));
dispatch(signInFacebookSuccess(data.user));
dispatch(hideSignInDialog());
- dispatch(showCreateUsernameDialog());
- dispatch(hideSignInDialog());
+ const {user: {canEditName, suspension}} = getState().auth.toJS();
+ if (canEditName && !suspension.mustChangeUsername) {
+ dispatch(showCreateUsernameDialog());
+ }
} catch (err) {
dispatch(signInFacebookFailure(err));
return;
diff --git a/client/coral-sign-in/containers/ChangeUsernameContainer.js b/client/coral-sign-in/containers/ChangeUsernameContainer.js
index 12a9a75b8..cebd9605b 100644
--- a/client/coral-sign-in/containers/ChangeUsernameContainer.js
+++ b/client/coral-sign-in/containers/ChangeUsernameContainer.js
@@ -104,7 +104,7 @@ class ChangeUsernameContainer extends Component {
return (
{
return UsersService.setStatus(id, status);
};
-const suspendUser = ({user}, {id, message}) => {
- return UsersService.suspendUser(id, message);
+const suspendUser = ({user}, {id, message, mustChangeUsername, until}) => {
+ return UsersService.suspendUser(id, message, mustChangeUsername, until);
};
const ignoreUser = ({user}, userToIgnore) => {
diff --git a/graph/resolvers/comment.js b/graph/resolvers/comment.js
index 8d0473900..8074abe67 100644
--- a/graph/resolvers/comment.js
+++ b/graph/resolvers/comment.js
@@ -25,9 +25,9 @@ const Comment = {
// TODO: remove
if (user && excludeIgnored) {
- return Comments.countByParentIDPersonalized({id, excludeIgnored});
+ return Comments.countByParentIDPersonalized({id, excludeIgnored});
}
- return Comments.countByParentID.load(id);
+ return Comments.countByParentID.load(id);
},
actions({id}, _, {user, loaders: {Actions}}) {
diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js
index 2abc88f0d..0b954f2a1 100644
--- a/graph/resolvers/root_mutation.js
+++ b/graph/resolvers/root_mutation.js
@@ -20,8 +20,8 @@ const RootMutation = {
setUserStatus(_, {id, status}, {mutators: {User}}) {
return wrapResponse(null)(User.setUserStatus({id, status}));
},
- suspendUser(_, {id, message}, {mutators: {User}}) {
- return wrapResponse(null)(User.suspendUser({id, message}));
+ suspendUser(_, {input: {id, message, mustChangeUsername, until}}, {mutators: {User}}) {
+ return wrapResponse(null)(User.suspendUser({id, message, mustChangeUsername, until}));
},
ignoreUser(_, {id}, {mutators: {User}}) {
return wrapResponse(null)(User.ignoreUser({id}));
diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql
index f51258eb1..824197f3b 100644
--- a/graph/typeDefs.graphql
+++ b/graph/typeDefs.graphql
@@ -669,6 +669,23 @@ input CreateDontAgreeInput {
message: String
}
+# Input for suspendUser mutation.
+input SuspendUserInput {
+
+ # id of target user.
+ id: ID!
+
+ # message to be sent to the user.
+ # TODO: should this be required?
+ message: String
+
+ # If set, the user is requested to change its username.
+ mustChangeUsername: Boolean
+
+ # If set, the suspension lasts at least until specified date.
+ until: Date
+}
+
# DeleteActionResponse is the response returned with possibly some errors
# relating to the delete action attempt.
type DeleteActionResponse implements Response {
@@ -741,7 +758,7 @@ type EditCommentResponse implements Response {
comment: Comment
# An array of errors relating to the mutation that occured.
- errors: [UserError]
+ errors: [UserError]
}
# All mutations for the application are defined on this object.
@@ -765,8 +782,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. It sends a message to the banned User. Requires the `ADMIN` role.
- suspendUser(id: ID!, message: String): SuspendUserResponse
+ # Suspends a user. Requires the `ADMIN` role.
+ suspendUser(input: SuspendUserInput!): SuspendUserResponse
# Sets Comment status. Requires the `ADMIN` role.
setCommentStatus(id: ID!, status: COMMENT_STATUS!): SetCommentStatusResponse
diff --git a/models/user.js b/models/user.js
index 56f750d34..47c521fba 100644
--- a/models/user.js
+++ b/models/user.js
@@ -111,6 +111,18 @@ const UserSchema = new mongoose.Schema({
default: false
},
+ // User's suspension details.
+ suspensionDetails: {
+ mustChangeUsername: {
+ type: Boolean,
+ default: false,
+ },
+ until: {
+ type: Date,
+ default: null,
+ },
+ },
+
// User's settings
settings: {
bio: {
@@ -139,6 +151,7 @@ const UserSchema = new mongoose.Schema({
},
toJSON: {
+ virtuals: true,
transform: function (doc, ret) {
delete ret.password;
delete ret._id;
@@ -147,6 +160,10 @@ const UserSchema = new mongoose.Schema({
}
});
+UserSchema.virtual('suspended').get(function() {
+ return this.suspensionDetails.mustChangeUsername || this.suspensionDetails.until > new Date();
+});
+
// Add the indixies on the user profile data.
UserSchema.index({
'profiles.id': 1,
diff --git a/services/users.js b/services/users.js
index 9d15e129f..e4d2982bd 100644
--- a/services/users.js
+++ b/services/users.js
@@ -451,40 +451,48 @@ module.exports = class UsersService {
/**
* Suspend a user. It changes the status to BANNED and canEditName to True.
- * @param {String} id id of a user
- * @param {Function} done callback after the operation is complete
+ * @param {String} id id of a user
+ * @param {String} message message to be send to the user
+ * @param {Boolean} mustChangeUsername if set the suspension lasts at least until user changed its username.
+ * @param {Date} until if set the suspension lasts at least until date.
*/
- static suspendUser(id, message) {
- return UserModel.findOneAndUpdate({
- id
- }, {
+ static suspendUser(id, message, mustChangeUsername, until) {
+ const changes = {
$set: {
- status: 'BANNED',
- canEditName: true
+ suspensionDetails: {},
}
- })
- .then((user) => {
- if (message) {
- let localProfile = user.profiles.find((profile) => profile.provider === 'local');
+ };
+ if (mustChangeUsername) {
+ changes.$set.status = 'BANNED';
+ changes.$set.canEditName = true;
+ changes.$set.suspensionDetails.mustChangeUsername = true;
+ }
+ if (until) {
+ changes.$set.suspensionDetails.until = until;
+ }
+ return UserModel.findOneAndUpdate({id}, changes)
+ .then((user) => {
+ if (message) {
+ let localProfile = user.profiles.find((profile) => profile.provider === 'local');
- if (localProfile) {
- const options =
- {
- template: '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
- };
+ if (localProfile) {
+ const options =
+ {
+ template: '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);
+ return MailerService.sendSimple(options);
+ } else {
+ return Promise.reject(errors.ErrMissingEmail);
+ }
}
- }
- });
+ });
}
/**
@@ -813,13 +821,15 @@ module.exports = class UsersService {
username: username,
lowercaseUsername: username.toLowerCase(),
canEditName: false,
- status: 'PENDING'
+ status: 'PENDING',
+ 'suspensionDetails.mustChangeUsername': false,
}
})
.then((result) => {
if (result.nModified <= 0) {
return Promise.reject(errors.ErrPermissionUpdateUsername);
}
+ console.log(result);
return result;
})