From 6c01a37f6ab2c5ac20ef5c1caad66e934180dbc9 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 11:48:16 -0500 Subject: [PATCH 01/28] Removing user bio. --- client/coral-embed-stream/src/Comment.js | 9 +-- client/coral-plugin-author-name/AuthorName.js | 27 +++++---- .../coral-settings/components/NotLoggedIn.js | 1 - .../containers/SettingsContainer.js | 55 ++++++++++--------- 4 files changed, 46 insertions(+), 46 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 529420941..a2a4c4c6e 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -92,14 +92,7 @@ class Comment extends React.Component { style={{marginLeft: depth * 30}}>
+ author={comment.user}/>
diff --git a/client/coral-plugin-author-name/AuthorName.js b/client/coral-plugin-author-name/AuthorName.js index fcf464403..aa48f35d8 100644 --- a/client/coral-plugin-author-name/AuthorName.js +++ b/client/coral-plugin-author-name/AuthorName.js @@ -1,6 +1,7 @@ import React, {Component} from 'react'; -import {Tooltip} from 'coral-ui'; -import FlagBio from '../coral-plugin-flags/FlagBio'; + +// import {Tooltip} from 'coral-ui'; +// import FlagBio from '../coral-plugin-flags/FlagBio'; const packagename = 'coral-plugin-author-name'; export default class AuthorName extends Component { @@ -29,7 +30,8 @@ export default class AuthorName extends Component { render () { const {author} = this.props; - const {showTooltip} = this.state; + + // const {showTooltip} = this.state; return (
{author && author.name} - { showTooltip && author.settings.bio && -
- {author.settings.bio} -
-
- -
-
+ { + + // Hiding bio until moderation is addressed + // showTooltip && author.settings.bio && + //
+ // {author.settings.bio} + //
+ //
+ // + //
+ //
}
); diff --git a/client/coral-settings/components/NotLoggedIn.js b/client/coral-settings/components/NotLoggedIn.js index 8266a745e..095d43a8f 100644 --- a/client/coral-settings/components/NotLoggedIn.js +++ b/client/coral-settings/components/NotLoggedIn.js @@ -15,7 +15,6 @@ export default ({showSignInDialog}) => ( From the Settings Page you can
  • See your comment history
  • -
  • Write a bio about yourself to display to the community
diff --git a/client/coral-settings/containers/SettingsContainer.js b/client/coral-settings/containers/SettingsContainer.js index f02badd80..4398ed5c6 100644 --- a/client/coral-settings/containers/SettingsContainer.js +++ b/client/coral-settings/containers/SettingsContainer.js @@ -4,12 +4,10 @@ import React, {Component} from 'react'; import I18n from 'coral-framework/modules/i18n/i18n'; import {myCommentHistory} from 'coral-framework/graphql/queries'; -import {saveBio} from 'coral-framework/actions/user'; -import BioContainer from './BioContainer'; import {link} from 'coral-framework/PymConnection'; import NotLoggedIn from '../components/NotLoggedIn'; -import {TabBar, Tab, TabContent, Spinner} from 'coral-ui'; +import {Spinner} from 'coral-ui'; import SettingsHeader from '../components/SettingsHeader'; import CommentHistory from 'coral-plugin-history/CommentHistory'; @@ -33,8 +31,7 @@ class SettingsContainer extends Component { } render() { - const {loggedIn, userData, asset, showSignInDialog, data, user} = this.props; - const {activeTab} = this.state; + const {loggedIn, asset, showSignInDialog, data} = this.props; const {me} = this.props.data; if (!loggedIn || !me) { @@ -48,25 +45,30 @@ class SettingsContainer extends Component { return (
- - {lang.t('allComments')} ({user.myComments.length}) - {lang.t('profileSettings')} - - - { - me.comments.length ? - - : -

{lang.t('userNoComment')}

- } -
- - - + { + + // Hiding bio until moderation can get figured out + /* + {lang.t('allComments')} ({user.myComments.length}) + {lang.t('profileSettings')} + + */ + me.comments.length ? + + : +

{lang.t('userNoComment')}

+ + // Hiding user bio pending effective moderation system. + /*
+ + + */ + } +
); } @@ -78,8 +80,9 @@ const mapStateToProps = state => ({ auth: state.auth.toJS() }); -const mapDispatchToProps = dispatch => ({ - saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData)) +const mapDispatchToProps = () => ({ + + // saveBio: (user_id, formData) => dispatch(saveBio(user_id, formData)) }); export default compose( From 37975574ea0f214b1097134dc2962055b8f00671 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 12:26:01 -0500 Subject: [PATCH 02/28] Switching rejected usernames from suspended to banned. --- .../src/containers/ModerationQueue/ModerationContainer.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index eef855df6..aa754b4ac 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -118,7 +118,7 @@ const mapDispatchToProps = dispatch => { userStatusUpdate: (status, userId, commentId) => dispatch(userStatusUpdate(status, userId, commentId)).then(() => { dispatch(fetchModerationQueueComments()); }), - suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('suspended', userId)) + suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('BANNED', userId)) .then(() => dispatch(sendNotificationEmail(userId, subject, text))) .then(() => dispatch(fetchModerationQueueComments())) , From b2430eaf60437ee498a153c934af0cf3928989d4 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 15:55:33 -0500 Subject: [PATCH 03/28] Adding displayname editing and appropriate toggle. --- .../src/components/SuspendUserModal.js | 2 +- graph/resolvers/root_mutation.js | 3 - graph/typeDefs.graphql | 18 +---- models/user.js | 6 ++ routes/api/account/index.js | 13 +-- routes/api/users/index.js | 11 ++- services/users.js | 31 ++++++++ test/routes/api/user/index.js | 79 +++++++++++++++++++ test/services/users.js | 52 ++++++++++++ 9 files changed, 185 insertions(+), 30 deletions(-) diff --git a/client/coral-admin/src/components/SuspendUserModal.js b/client/coral-admin/src/components/SuspendUserModal.js index 5ad8c1616..5afb8ffee 100644 --- a/client/coral-admin/src/components/SuspendUserModal.js +++ b/client/coral-admin/src/components/SuspendUserModal.js @@ -36,7 +36,7 @@ class SuspendUserModal extends Component { } componentDidMount() { - const about = this.props.actionType === 'flag_bio' ? lang.t('suspenduser.bio') : lang.t('suspenduser.username'); + const about = lang.t('suspenduser.username'); this.setState({email: lang.t('suspenduser.email', about)}); } diff --git a/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js index ddc8e4223..ef46b839a 100644 --- a/graph/resolvers/root_mutation.js +++ b/graph/resolvers/root_mutation.js @@ -8,9 +8,6 @@ const RootMutation = { deleteAction(_, {id}, {mutators: {Action}}) { return Action.delete({id}); }, - updateUserSettings(_, {settings}, {mutators: {User}}) { - return User.updateSettings(settings); - } }; module.exports = RootMutation; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 744a53252..1c1470f58 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -10,11 +10,6 @@ enum SORT_ORDER { # Date represented as an ISO8601 string. scalar Date -type UserSettings { - # bio of the user. - bio: String -} - input CommentsQuery { # current status of a comment. statuses: [COMMENT_STATUS] @@ -22,7 +17,7 @@ input CommentsQuery { # asset that a comment is on. asset_id: ID - # the parent of the comment that we want to retrive. + # the parent of the comment that we want to retrieve. parent_id: ID # comments returned will only be ones which have at least one action of this @@ -61,8 +56,8 @@ type User { # the current roles of the user. roles: [USER_ROLES] - # settings for a user. - settings: UserSettings + # determines whether the user can edit their username + canEditName: Boolean # returns all comments based on a query. comments(query: CommentsQuery): [Comment] @@ -205,11 +200,6 @@ input CreateActionInput { item_id: ID! } -input UpdateUserSettingsInput { - # user bio - bio: String! -} - type RootMutation { # creates a comment on the asset. createComment(asset_id: ID!, parent_id: ID, body: String!): Comment @@ -220,8 +210,6 @@ type RootMutation { # delete an action based on the action id. deleteAction(id: ID!): Boolean - # updates a user's settings, it will return if the query was successful. - updateUserSettings(settings: UpdateUserSettingsInput!): Boolean } schema { diff --git a/models/user.js b/models/user.js index 8b18ee983..7de99d211 100644 --- a/models/user.js +++ b/models/user.js @@ -96,6 +96,12 @@ const UserSchema = new mongoose.Schema({ default: 'ACTIVE' }, + // Determines whether the user can edit their username. + canEditName: { + type: Boolean, + default: false + }, + // User's settings settings: { bio: { diff --git a/routes/api/account/index.js b/routes/api/account/index.js index 57647a89d..4dd6add7c 100644 --- a/routes/api/account/index.js +++ b/routes/api/account/index.js @@ -115,20 +115,13 @@ router.put('/password/reset', (req, res, next) => { }); }); -router.put('/settings', authorization.needed(), (req, res, next) => { - - const { - bio - } = req.body; - +router.put('/displayname', authorization.needed(), (req, res, next) => { UsersService - .updateSettings(req.user.id, {bio}) + .editUsername(req.user.id, req.body.displayName) .then(() => { res.status(204).end(); }) - .catch((err) => { - next(err); - }); + .catch(next); }); module.exports = router; diff --git a/routes/api/users/index.js b/routes/api/users/index.js index da47007ee..8b22adb36 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -57,7 +57,16 @@ router.post('/:user_id/status', authorization.needed('ADMIN'), (req, res, next) .catch(next); }); -router.post('/:user_id/email', authorization.needed('admin'), (req, res, next) => { +router.post('/:user_id/username-enable', authorization.needed('ADMIN'), (req, res, next) => { + UsersService + .toggleUsernameEdit(req.params.user_id, true) + .then(() => { + res.status(204).end(); + }) + .catch(next); +}); + +router.post('/:user_id/email', authorization.needed('ADMIN'), (req, res, next) => { UsersService.findById(req.params.user_id) .then(user => { let localProfile = user.profiles.find((profile) => profile.provider === 'local'); diff --git a/services/users.js b/services/users.js index 635e39e47..42278f1c4 100644 --- a/services/users.js +++ b/services/users.js @@ -613,4 +613,35 @@ module.exports = class UsersService { return UserModel.find({status: 'PENDING'}); } + /** + * Gives the user the ability to edit their username. + * @param {String} id the id of the user to be toggled. + * @param {Boolean} canEditName sets whether the user can edit their name. + * @return {Promise} + */ + static toggleUsernameEdit(id, canEditName) { + return UserModel.update({id}, { + $set: {canEditName} + }); + } + + /** + * Gives the user the ability to edit their username. + * @param {String} id the id of the user to be enabled. + * @param {String} displayName The new displayname for the user. + * @return {Promise} + */ + static editUsername(id, displayName) { + return UserModel.findOne({id}) + .then((user) => { + return user.canEditName ? + UserModel.update({id}, { + $set: { + displayName: displayName.toLowerCase(), + canEditName: false + } + }) + : Promise.reject(new Error('Display name editing disabled for this account.')); + }); + } }; diff --git a/test/routes/api/user/index.js b/test/routes/api/user/index.js index d1771726d..2fdf937b5 100644 --- a/test/routes/api/user/index.js +++ b/test/routes/api/user/index.js @@ -83,3 +83,82 @@ describe('/api/v1/users/:user_id/actions', () => { }); }); }); + +describe('/api/v1/users/:user_id/username-enable', () => { + let mockUser; + + beforeEach(() => SettingsService.init(settings).then(() => { + return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana'); + }) + .then((user) => { + mockUser = user; + })); + + describe('#post', () => { + it('it should enable a user to edit their username', () => { + return chai.request(app) + .post(`/api/v1/users/${mockUser.id}/username-enable`) + .set(passport.inject({id: '456', roles: ['ADMIN']})) + .then((res) => { + expect(res).to.have.status(204); + }); + }); + }); +}); + +describe('/api/v1/account/displayname', () => { + let mockUser; + + beforeEach(() => SettingsService.init(settings).then(() => { + return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana'); + }) + .then((user) => { + mockUser = user; + })); + + describe('#post', () => { + it('it should enable a user to edit their username if canEditName is enabled', () => { + return chai.request(app) + .post(`/api/v1/users/${mockUser.id}/username-enable`) + .set(passport.inject({id: '456', roles: ['ADMIN']})) + .then(() => chai.request(app) + .put('/api/v1/account/displayname') + .set(passport.inject({id: mockUser.id, roles: []})) + .send({displayName: 'MojoJojo'})) + .then((res) => { + expect(res).to.have.status(204); + }); + }); + + it('it should return an error if the wrong user tries to edit a username', (done) => { + chai.request(app) + .post(`/api/v1/users/${mockUser.id}/username-enable`) + .set(passport.inject({id: '456', roles: ['ADMIN']})) + .then(() => chai.request(app) + .put('/api/v1/account/displayname') + .set(passport.inject({id: 'wrongid', roles: []})) + .send({displayName: 'MojoJojo'})) + .then(() => { + done(new Error('Exected Error')); + }) + .catch((err) => { + expect(err).to.be.truthy; + done(); + }); + }); + + it('it should return an error when the user tries to edit their username if canEditName is disabled', (done) => { + chai.request(app) + .put('/api/v1/account/displayname') + .set(passport.inject({id: mockUser.id, roles: []})) + .send({username: 'MojoJojo'}) + .then(() => { + done(new Error('Exected Error')); + }) + .catch((err) => { + expect(err).to.be.truthy; + done(); + }); + }); + }); +}); diff --git a/test/services/users.js b/test/services/users.js index e2e5655e4..ef027a317 100644 --- a/test/services/users.js +++ b/test/services/users.js @@ -208,4 +208,56 @@ describe('services.UsersService', () => { }); }); }); + + describe('#toggleUsernameEdit', () => { + it('should toggle the canEditName field', () => { + return UsersService + .toggleUsernameEdit(mockUsers[0].id, true) + .then(() => UsersService.findById(mockUsers[0].id)) + .then((user) => { + expect(user).to.have.property('canEditName', true); + }); + }); + }); + + describe('#editUsername', () => { + it('should let the user edit their username if the proper toggle is set', () => { + return UsersService + .toggleUsernameEdit(mockUsers[0].id, true) + .then(() => UsersService.editUsername(mockUsers[0].id, 'Jojo')) + .then(() => UsersService.findById(mockUsers[0].id)) + .then((user) => { + expect(user).to.have.property('displayName', 'jojo'); + expect(user).to.have.property('canEditName', false); + }); + }); + + it('should return an error if canEditName is false', (done) => { + UsersService + .editUsername(mockUsers[0].id, 'Jojo') + .then(() => UsersService.findById(mockUsers[0].id)) + .then(() => { + done(new Error('Error expected')); + }) + .catch((err) => { + expect(err).to.be.truthy; + done(); + }); + }); + + it('should return an error if the username is already taken', (done) => { + UsersService + .toggleUsernameEdit(mockUsers[0].id, true) + .then(() => UsersService.editUsername(mockUsers[0].id, 'Marvel')) + .then(() => UsersService.findById(mockUsers[0].id)) + .then(() => { + done(new Error('Error expected')); + }) + .catch((err) => { + expect(err).to.be.truthy; + done(); + }); + }); + }); + }); From 5dca1611bcbe182b09064fda3e9eb2992c61c631 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 15:58:07 -0500 Subject: [PATCH 04/28] Moving account endpoint tests to account. --- test/routes/api/account/index.js | 71 ++++++++++++++++++++++++++++++++ test/routes/api/user/index.js | 57 ------------------------- 2 files changed, 71 insertions(+), 57 deletions(-) create mode 100644 test/routes/api/account/index.js diff --git a/test/routes/api/account/index.js b/test/routes/api/account/index.js new file mode 100644 index 000000000..5b9acfa9d --- /dev/null +++ b/test/routes/api/account/index.js @@ -0,0 +1,71 @@ +const passport = require('../../../passport'); + +const app = require('../../../../app'); +const chai = require('chai'); +const expect = chai.expect; + +const SettingsService = require('../../../../services/settings'); +const settings = {id: '1', moderation: 'PRE', wordlist: {banned: ['bad words'], suspect: ['suspect words']}}; + +// Setup chai. +chai.should(); +chai.use(require('chai-http')); + +const UsersService = require('../../../../services/users'); + +describe('/api/v1/account/displayname', () => { + let mockUser; + + beforeEach(() => SettingsService.init(settings).then(() => { + return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana'); + }) + .then((user) => { + mockUser = user; + })); + + describe('#put', () => { + it('it should enable a user to edit their username if canEditName is enabled', () => { + return chai.request(app) + .post(`/api/v1/users/${mockUser.id}/username-enable`) + .set(passport.inject({id: '456', roles: ['ADMIN']})) + .then(() => chai.request(app) + .put('/api/v1/account/displayname') + .set(passport.inject({id: mockUser.id, roles: []})) + .send({displayName: 'MojoJojo'})) + .then((res) => { + expect(res).to.have.status(204); + }); + }); + + it('it should return an error if the wrong user tries to edit a username', (done) => { + chai.request(app) + .post(`/api/v1/users/${mockUser.id}/username-enable`) + .set(passport.inject({id: '456', roles: ['ADMIN']})) + .then(() => chai.request(app) + .put('/api/v1/account/displayname') + .set(passport.inject({id: 'wrongid', roles: []})) + .send({displayName: 'MojoJojo'})) + .then(() => { + done(new Error('Exected Error')); + }) + .catch((err) => { + expect(err).to.be.truthy; + done(); + }); + }); + + it('it should return an error when the user tries to edit their username if canEditName is disabled', (done) => { + chai.request(app) + .put('/api/v1/account/displayname') + .set(passport.inject({id: mockUser.id, roles: []})) + .send({username: 'MojoJojo'}) + .then(() => { + done(new Error('Exected Error')); + }) + .catch((err) => { + expect(err).to.be.truthy; + done(); + }); + }); + }); +}); diff --git a/test/routes/api/user/index.js b/test/routes/api/user/index.js index 2fdf937b5..4de799df6 100644 --- a/test/routes/api/user/index.js +++ b/test/routes/api/user/index.js @@ -105,60 +105,3 @@ describe('/api/v1/users/:user_id/username-enable', () => { }); }); }); - -describe('/api/v1/account/displayname', () => { - let mockUser; - - beforeEach(() => SettingsService.init(settings).then(() => { - return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana'); - }) - .then((user) => { - mockUser = user; - })); - - describe('#post', () => { - it('it should enable a user to edit their username if canEditName is enabled', () => { - return chai.request(app) - .post(`/api/v1/users/${mockUser.id}/username-enable`) - .set(passport.inject({id: '456', roles: ['ADMIN']})) - .then(() => chai.request(app) - .put('/api/v1/account/displayname') - .set(passport.inject({id: mockUser.id, roles: []})) - .send({displayName: 'MojoJojo'})) - .then((res) => { - expect(res).to.have.status(204); - }); - }); - - it('it should return an error if the wrong user tries to edit a username', (done) => { - chai.request(app) - .post(`/api/v1/users/${mockUser.id}/username-enable`) - .set(passport.inject({id: '456', roles: ['ADMIN']})) - .then(() => chai.request(app) - .put('/api/v1/account/displayname') - .set(passport.inject({id: 'wrongid', roles: []})) - .send({displayName: 'MojoJojo'})) - .then(() => { - done(new Error('Exected Error')); - }) - .catch((err) => { - expect(err).to.be.truthy; - done(); - }); - }); - - it('it should return an error when the user tries to edit their username if canEditName is disabled', (done) => { - chai.request(app) - .put('/api/v1/account/displayname') - .set(passport.inject({id: mockUser.id, roles: []})) - .send({username: 'MojoJojo'}) - .then(() => { - done(new Error('Exected Error')); - }) - .catch((err) => { - expect(err).to.be.truthy; - done(); - }); - }); - }); -}); From 7e907e18f268bb910396001e8bf68321803781d6 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 16:19:42 -0500 Subject: [PATCH 05/28] Enabling canEditName when suspending user. --- client/coral-admin/src/actions/users.js | 8 ++++++ .../src/components/ModerationList.js | 4 +-- .../src/components/{UserAction.js => User.js} | 25 ++----------------- client/coral-admin/src/constants/users.js | 1 + .../ModerationQueue/ModerationContainer.js | 3 ++- .../graphql/fragments/commentView.graphql | 3 --- 6 files changed, 15 insertions(+), 29 deletions(-) rename client/coral-admin/src/components/{UserAction.js => User.js} (75%) diff --git a/client/coral-admin/src/actions/users.js b/client/coral-admin/src/actions/users.js index 44f0325d9..954d89820 100644 --- a/client/coral-admin/src/actions/users.js +++ b/client/coral-admin/src/actions/users.js @@ -21,3 +21,11 @@ export const sendNotificationEmail = (userId, subject, body) => { .catch(error => dispatch({type: userTypes.USER_EMAIL_FAILURE, error})); }; }; + +// let a user edit their username +export const enableUsernameEdit = (userId) => { + return (dispatch) => { + return coralApi(`/users/${userId}/username-enable`, {method: 'POST'}) + .catch(error => dispatch({type: userTypes.USERNAME_ENABLE_FAILURE, error})); + }; +}; diff --git a/client/coral-admin/src/components/ModerationList.js b/client/coral-admin/src/components/ModerationList.js index c1df40d31..c2ecab9a2 100644 --- a/client/coral-admin/src/components/ModerationList.js +++ b/client/coral-admin/src/components/ModerationList.js @@ -3,7 +3,7 @@ import styles from './ModerationList.css'; import key from 'keymaster'; import Hammer from 'hammerjs'; import Comment from './Comment'; -import UserAction from './UserAction'; +import User from './User'; import SuspendUserModal from './SuspendUserModal'; // Each action has different meaning and configuration @@ -177,7 +177,7 @@ export default class ModerationList extends React.Component { // If the item is an action... const user = users[item.item_id]; - modItem = user && { +const User = props => { const {action, user} = props; let userStatus = user.status; const links = user.settings.bio ? linkify.getMatches(user.settings.bio) : []; @@ -48,32 +47,12 @@ const UserAction = props => { {lang.t('comment.banned_user')} : null} - { - user.settings.bio && -
-
-
{lang.t('user.user_bio')}:
- - - - - -
-
- }
{`${action.count} ${action.action_type === 'flag_bio' ? lang.t('user.bio_flags') : lang.t('user.username_flags')}`}
; }; -export default UserAction; - -const linkStyles = { - backgroundColor: 'rgb(255, 219, 135)', - padding: '1px 2px' -}; +export default User; const lang = new I18n(translations); diff --git a/client/coral-admin/src/constants/users.js b/client/coral-admin/src/constants/users.js index 3cd5e5b4b..9cd7e2093 100644 --- a/client/coral-admin/src/constants/users.js +++ b/client/coral-admin/src/constants/users.js @@ -2,4 +2,5 @@ export const UPDATE_STATUS_REQUEST = 'UPDATE_STATUS_REQUEST'; export const UPDATE_STATUS_SUCCESS = 'UPDATE_STATUS_SUCCESS'; export const UPDATE_STATUS_FAILURE = 'UPDATE_STATUS_FAILURE'; export const USER_EMAIL_FAILURE = 'USER_EMAIL_FAILURE'; +export const USERNAME_ENABLE_FAILURE = 'USERNAME_ENABLE_FAILURE'; export const USERS_MODERATION_QUEUE_FETCH_SUCCESS = 'USERS_MODERATION_QUEUE_FETCH_SUCCESS'; diff --git a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js index aa754b4ac..4f56a6c69 100644 --- a/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js +++ b/client/coral-admin/src/containers/ModerationQueue/ModerationContainer.js @@ -11,7 +11,7 @@ import { fetchFlaggedQueue, fetchModerationQueueComments, } from 'actions/comments'; -import {userStatusUpdate, sendNotificationEmail} from 'actions/users'; +import {userStatusUpdate, sendNotificationEmail, enableUsernameEdit} from 'actions/users'; import {fetchSettings} from 'actions/settings'; import ModerationQueue from './ModerationQueue'; @@ -119,6 +119,7 @@ const mapDispatchToProps = dispatch => { dispatch(fetchModerationQueueComments()); }), suspendUser: (userId, subject, text) => dispatch(userStatusUpdate('BANNED', userId)) + .then(() => dispatch(enableUsernameEdit(userId))) .then(() => dispatch(sendNotificationEmail(userId, subject, text))) .then(() => dispatch(fetchModerationQueueComments())) , diff --git a/client/coral-framework/graphql/fragments/commentView.graphql b/client/coral-framework/graphql/fragments/commentView.graphql index fb8051155..29f9b3bfe 100644 --- a/client/coral-framework/graphql/fragments/commentView.graphql +++ b/client/coral-framework/graphql/fragments/commentView.graphql @@ -6,9 +6,6 @@ fragment commentView on Comment { user { id name: displayName - settings { - bio - } } actions { type: action_type From e3da45daeb59523db9f3a17c4270f2696e0007b8 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 16:38:01 -0500 Subject: [PATCH 06/28] Displaying appropriate suspension message. --- client/coral-embed-stream/src/Embed.js | 8 +++++++- client/coral-framework/components/SuspendedAccount.js | 8 ++++++-- client/coral-framework/translations.json | 6 ++++-- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 2a7057cca..b57bc799f 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -96,6 +96,8 @@ class Embed extends Component { const openStream = closedAt === null; + const banned = user && user.status === 'BANNED'; + const expandForLogin = showSignInDialog ? { minHeight: document.body.scrollHeight + 200 } : {}; @@ -118,7 +120,11 @@ class Embed extends Component { content={asset.settings.infoBoxContent} enable={asset.settings.infoBoxEnable} /> - }> + + }> { user ? ( +export default ({canEditName}) => (
- {lang.t('suspendedAccountMsg')} + { + canEditName ? + lang.t('editNameMsg') + : lang.t('bannedAccountMsg') + }
); diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index 5bf9b40a9..a61213aab 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -3,7 +3,8 @@ "successUpdateSettings": "The changes you have made have been applied to the comment stream on this article", "successBioUpdate": "Your Bio has been updated", "contentNotAvailable": "This content is not available", - "suspendedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information", + "bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information", + "editNameMsg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.", "error": { "email": "Not a valid E-Mail", "password": "Password must be at least 8 characters", @@ -25,7 +26,8 @@ "successUpdateSettings": "La configuración de este articulo fue actualizada", "successBioUpdate": "Tu bio fue actualizada", "contentNotAvailable": "El contenido no se encuentra disponible", - "suspendedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information", + "bannedAccountMsg": "Tu cuenta se encuentra suspendida. Esto significa que no puedes dar Like, Marcar o escribir commentarios. Por favor, contacta moderator@fakeurl for more information", + "editNameMsg": "", "error": { "email": "No es un email válido", "password": "La contraseña debe tener por lo menos 8 caracteres", From d703cc21681cf7d0d7e55e4baf44d8520e3a738d Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 19:53:37 -0500 Subject: [PATCH 07/28] Prompting user to update name when banned. --- client/coral-embed-stream/src/Embed.js | 3 + client/coral-framework/actions/user.js | 16 +--- .../components/RestrictedContent.css | 10 +++ .../components/SuspendedAccount.js | 82 +++++++++++++++++-- client/coral-framework/constants/user.js | 6 +- client/coral-framework/translations.json | 9 +- graph/mutators/index.js | 2 - graph/mutators/user.js | 31 ------- graph/typeDefs.graphql | 2 +- models/user.js | 2 +- routes/api/account/index.js | 10 ++- routes/api/users/index.js | 2 +- services/users.js | 7 +- test/services/users.js | 16 ++-- 14 files changed, 123 insertions(+), 75 deletions(-) delete mode 100644 graph/mutators/user.js diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index b57bc799f..ceec3f1a9 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -11,6 +11,7 @@ const {fetchAssetSuccess} = assetActions; import {queryStream} from 'coral-framework/graphql/queries'; import {postComment, postAction, deleteAction} from 'coral-framework/graphql/mutations'; +import {editName} from 'coral-framework/actions/user'; import {Notification, notificationActions, authActions, assetActions, pym} from 'coral-framework'; import Stream from './Stream'; @@ -123,6 +124,7 @@ class Embed extends Component { }> { @@ -200,6 +202,7 @@ const mapDispatchToProps = dispatch => ({ loadAsset: (asset) => dispatch(fetchAssetSuccess(asset)), addNotification: (type, text) => dispatch(addNotification(type, text)), clearNotification: () => dispatch(clearNotification()), + editName: (displayName) => dispatch(editName(displayName)), showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), logout: () => dispatch(logout()), dispatch: d => dispatch(d) diff --git a/client/coral-framework/actions/user.js b/client/coral-framework/actions/user.js index 4620262d3..42d2e0398 100644 --- a/client/coral-framework/actions/user.js +++ b/client/coral-framework/actions/user.js @@ -1,4 +1,3 @@ -import * as actions from '../constants/user'; import {addNotification} from '../actions/notification'; import coralApi from '../helpers/response'; @@ -6,16 +5,9 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from './../translations'; const lang = new I18n(translations); -const saveBioRequest = () => ({type: actions.SAVE_BIO_REQUEST}); -const saveBioSuccess = settings => ({type: actions.SAVE_BIO_SUCCESS, settings}); -const saveBioFailure = error => ({type: actions.SAVE_BIO_FAILURE, error}); - -export const saveBio = (user_id, formData) => dispatch => { - dispatch(saveBioRequest()); - coralApi('/account/settings', {method: 'PUT', body: formData}) +export const editName = (displayName) => (dispatch) => { + return coralApi('/account/displayname', {method: 'PUT', body: {displayName}}) .then(() => { - dispatch(addNotification('success', lang.t('successBioUpdate'))); - dispatch(saveBioSuccess(formData)); - }) - .catch(error => dispatch(saveBioFailure(error))); + dispatch(addNotification('success', lang.t('successNameUpdate'))); + }); }; diff --git a/client/coral-framework/components/RestrictedContent.css b/client/coral-framework/components/RestrictedContent.css index 47ced7b0b..ca5a6517c 100644 --- a/client/coral-framework/components/RestrictedContent.css +++ b/client/coral-framework/components/RestrictedContent.css @@ -2,3 +2,13 @@ background: #D8D8D8; padding: 25px; } + +.editNameInput { + margin-top: 10px; + margin-bottom: 10px; +} + +.alert { + margin-top: 10px; + color: #B71C1C; +} diff --git a/client/coral-framework/components/SuspendedAccount.js b/client/coral-framework/components/SuspendedAccount.js index e6c53d038..0c6347fca 100644 --- a/client/coral-framework/components/SuspendedAccount.js +++ b/client/coral-framework/components/SuspendedAccount.js @@ -1,15 +1,79 @@ -import React from 'react'; +import React, {Component, PropTypes} from 'react'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from 'coral-framework/translations.json'; const lang = new I18n(translations); import styles from './RestrictedContent.css'; +import {Button} from 'coral-ui'; +import validate from '../helpers/validate'; -export default ({canEditName}) => ( -
- { +class SuspendedAccount extends Component { + + static propTypes = { + canEditName: PropTypes.bool, + editName: PropTypes.func.isRequired + } + + state = { + displayName: '', + alert: '' + } + + onSubmitClick = (e) => { + const {editName} = this.props; + const {displayName} = this.state; + e.preventDefault(); + if (validate.displayName(displayName)) { + editName(displayName) + .then(() => window.reload()) + .catch((error) => { + this.setState({alert: lang.t(`error.${error.message}`)}); + }); + } else { + this.setState({alert: lang.t('editName.error')}); + } + + } + + render () { + const {canEditName} = this.props; + const {displayName, alert} = this.state; + + return
+ { + canEditName ? + lang.t('editName.msg') + : lang.t('bannedAccountMsg') + } + { canEditName ? - lang.t('editNameMsg') - : lang.t('bannedAccountMsg') - } -
-); +
+
+ {alert} +
+ + this.setState({displayName: e.target.value})} + rows={3}/>
+ +
: null + } +
; + } +} + +export default SuspendedAccount; diff --git a/client/coral-framework/constants/user.js b/client/coral-framework/constants/user.js index 9c6f508fe..a8e4585db 100644 --- a/client/coral-framework/constants/user.js +++ b/client/coral-framework/constants/user.js @@ -1,6 +1,6 @@ -export const SAVE_BIO_REQUEST = 'SAVE_BIO_REQUEST'; -export const SAVE_BIO_SUCCESS = 'SAVE_BIO_SUCCESS'; -export const SAVE_BIO_FAILURE = 'SAVE_BIO_FAILURE'; +export const EDIT_NAME_REQUEST = 'EDIT_NAME_REQUEST'; +export const EDIT_NAME_SUCCESS = 'EDIT_NAME_SUCCESS'; +export const EDIT_NAME_FAILURE = 'EDIT_NAME_FAILURE'; export const COMMENTS_BY_USER_REQUEST = 'COMMENTS_BY_USER_REQUEST'; export const COMMENTS_BY_USER_SUCCESS = 'COMMENTS_BY_USER_SUCCESS'; export const COMMENTS_BY_USER_FAILURE = 'COMMENTS_BY_USER_FAILURE'; diff --git a/client/coral-framework/translations.json b/client/coral-framework/translations.json index a61213aab..80e865f8e 100644 --- a/client/coral-framework/translations.json +++ b/client/coral-framework/translations.json @@ -1,10 +1,15 @@ { "en": { "successUpdateSettings": "The changes you have made have been applied to the comment stream on this article", - "successBioUpdate": "Your Bio has been updated", + "successNameUpdate": "Your display name has been updated", "contentNotAvailable": "This content is not available", "bannedAccountMsg": "Your account is currently suspended. This means that you cannot Like, Flag, or write comments. Please contact moderator@fakeurl.com for more information", - "editNameMsg": "Your account is currently suspended because your username has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.", + "editName": { + "msg": "Your account is currently suspended because your display name has been deemed inappropriate. To restore your account, please enter a new username. You may contact moderator@fakeurl.com for more information.", + "label": "New Display Name", + "button": "Submit", + "error": "Display names can contain letters, numbers and _ only" + }, "error": { "email": "Not a valid E-Mail", "password": "Password must be at least 8 characters", diff --git a/graph/mutators/index.js b/graph/mutators/index.js index b799cf83d..58d0ed62c 100644 --- a/graph/mutators/index.js +++ b/graph/mutators/index.js @@ -2,7 +2,6 @@ const _ = require('lodash'); const Comment = require('./comment'); const Action = require('./action'); -const User = require('./user'); module.exports = (context) => { @@ -10,7 +9,6 @@ module.exports = (context) => { return _.merge(...[ Comment, Action, - User, ].map((mutators) => { // Each set of mutators is a function which takes the context. diff --git a/graph/mutators/user.js b/graph/mutators/user.js deleted file mode 100644 index 96049bbab..000000000 --- a/graph/mutators/user.js +++ /dev/null @@ -1,31 +0,0 @@ -const UsersService = require('../../services/users'); - -/** - * Updates a users settings. - * @param {Object} user the user performing the request - * @param {String} bio the new user bio - * @return {Promise} - */ -const updateUserSettings = ({user}, {bio}) => { - return UsersService.updateSettings(user.id, {bio}); -}; - -module.exports = (context) => { - - // TODO: refactor to something that'll return an error in the event an attempt - // is made to mutate state while not logged in. There's got to be a better way - // to do this. - if (context.user && context.user.can('mutation:updateUserSettings')) { - return { - User: { - updateSettings: (settings) => updateUserSettings(context, settings) - } - }; - } - - return { - User: { - updateSettings: () => {} - } - }; -}; diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 1c1470f58..b1f9b82f7 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -145,7 +145,7 @@ type Asset { # The scraped title of the asset. title: String - # The URL that the asset is locaed on. + # The URL that the asset is located on. url: String # The top level comments that are attached to the asset. diff --git a/models/user.js b/models/user.js index 7de99d211..e5db70ea6 100644 --- a/models/user.js +++ b/models/user.js @@ -146,7 +146,7 @@ const USER_GRAPH_OPERATIONS = [ 'mutation:createComment', 'mutation:createAction', 'mutation:deleteAction', - 'mutation:updateUserSettings' + 'mutation:editName' ]; /** diff --git a/routes/api/account/index.js b/routes/api/account/index.js index 4dd6add7c..b08bdfc2e 100644 --- a/routes/api/account/index.js +++ b/routes/api/account/index.js @@ -117,11 +117,17 @@ router.put('/password/reset', (req, res, next) => { router.put('/displayname', authorization.needed(), (req, res, next) => { UsersService - .editUsername(req.user.id, req.body.displayName) + .editName(req.user.id, req.body.displayName) .then(() => { res.status(204).end(); }) - .catch(next); + .catch(error => { + if (error.code === 11000) { + next(errors.ErrDisplayTaken); + } else { + next(error); + } + }); }); module.exports = router; diff --git a/routes/api/users/index.js b/routes/api/users/index.js index 8b22adb36..5d1783cfe 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -59,7 +59,7 @@ router.post('/:user_id/status', authorization.needed('ADMIN'), (req, res, next) router.post('/:user_id/username-enable', authorization.needed('ADMIN'), (req, res, next) => { UsersService - .toggleUsernameEdit(req.params.user_id, true) + .toggleNameEdit(req.params.user_id, true) .then(() => { res.status(204).end(); }) diff --git a/services/users.js b/services/users.js index 42278f1c4..ec0967c3b 100644 --- a/services/users.js +++ b/services/users.js @@ -619,7 +619,7 @@ module.exports = class UsersService { * @param {Boolean} canEditName sets whether the user can edit their name. * @return {Promise} */ - static toggleUsernameEdit(id, canEditName) { + static toggleNameEdit(id, canEditName) { return UserModel.update({id}, { $set: {canEditName} }); @@ -631,14 +631,15 @@ module.exports = class UsersService { * @param {String} displayName The new displayname for the user. * @return {Promise} */ - static editUsername(id, displayName) { + static editName(id, displayName) { return UserModel.findOne({id}) .then((user) => { return user.canEditName ? UserModel.update({id}, { $set: { displayName: displayName.toLowerCase(), - canEditName: false + canEditName: false, + status: 'PENDING' } }) : Promise.reject(new Error('Display name editing disabled for this account.')); diff --git a/test/services/users.js b/test/services/users.js index ef027a317..5774d45ae 100644 --- a/test/services/users.js +++ b/test/services/users.js @@ -209,10 +209,10 @@ describe('services.UsersService', () => { }); }); - describe('#toggleUsernameEdit', () => { + describe('#toggleNameEdit', () => { it('should toggle the canEditName field', () => { return UsersService - .toggleUsernameEdit(mockUsers[0].id, true) + .toggleNameEdit(mockUsers[0].id, true) .then(() => UsersService.findById(mockUsers[0].id)) .then((user) => { expect(user).to.have.property('canEditName', true); @@ -220,11 +220,11 @@ describe('services.UsersService', () => { }); }); - describe('#editUsername', () => { + describe('#editName', () => { it('should let the user edit their username if the proper toggle is set', () => { return UsersService - .toggleUsernameEdit(mockUsers[0].id, true) - .then(() => UsersService.editUsername(mockUsers[0].id, 'Jojo')) + .toggleNameEdit(mockUsers[0].id, true) + .then(() => UsersService.editName(mockUsers[0].id, 'Jojo')) .then(() => UsersService.findById(mockUsers[0].id)) .then((user) => { expect(user).to.have.property('displayName', 'jojo'); @@ -234,7 +234,7 @@ describe('services.UsersService', () => { it('should return an error if canEditName is false', (done) => { UsersService - .editUsername(mockUsers[0].id, 'Jojo') + .editName(mockUsers[0].id, 'Jojo') .then(() => UsersService.findById(mockUsers[0].id)) .then(() => { done(new Error('Error expected')); @@ -247,8 +247,8 @@ describe('services.UsersService', () => { it('should return an error if the username is already taken', (done) => { UsersService - .toggleUsernameEdit(mockUsers[0].id, true) - .then(() => UsersService.editUsername(mockUsers[0].id, 'Marvel')) + .toggleNameEdit(mockUsers[0].id, true) + .then(() => UsersService.editName(mockUsers[0].id, 'Marvel')) .then(() => UsersService.findById(mockUsers[0].id)) .then(() => { done(new Error('Error expected')); From 97c3ebc3e039116933359562a86f83422430b0f2 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 20:55:45 -0500 Subject: [PATCH 08/28] Whitelisting usernames once they have been approved. --- .../src/components/ModerationList.js | 2 +- models/user.js | 2 +- routes/api/users/index.js | 2 +- services/users.js | 11 ++++++-- test/routes/api/user/index.js | 25 +++++++------------ 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/client/coral-admin/src/components/ModerationList.js b/client/coral-admin/src/components/ModerationList.js index c2ecab9a2..47e91e551 100644 --- a/client/coral-admin/src/components/ModerationList.js +++ b/client/coral-admin/src/components/ModerationList.js @@ -138,7 +138,7 @@ export default class ModerationList extends React.Component { if (menuOption === 'REJECTED') { this.setState({suspendUserModal: action}); } else if (menuOption === 'ACCEPTED') { - this.props.userStatusUpdate('ACTIVE', action.item_id); + this.props.userStatusUpdate('APPROVED', action.item_id); } } } diff --git a/models/user.js b/models/user.js index d483b1893..06dc3c7d1 100644 --- a/models/user.js +++ b/models/user.js @@ -12,7 +12,7 @@ const USER_STATUS = [ 'ACTIVE', 'BANNED', 'PENDING', - 'SUSPENDED', + 'APPROVED' // Indicates that the users' displayname has been approved ]; // ProfileSchema is the mongoose schema defined as the representation of a diff --git a/routes/api/users/index.js b/routes/api/users/index.js index f136fd776..0b7cd28aa 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -171,7 +171,7 @@ router.post('/:user_id/actions', authorization.needed(), (req, res, next) => { // Set the user status to "pending" for review by moderators if (action_type.slice(0, 4) === 'FLAG') { - return UsersService.setStatus(req.user.id, 'PENDING') + return UsersService.setStatus(req.params.user_id, 'PENDING') .then(() => action); } else { return action; diff --git a/services/users.js b/services/users.js index b9a64b288..157286ccf 100644 --- a/services/users.js +++ b/services/users.js @@ -349,8 +349,15 @@ module.exports = class UsersService { // User status is not supported! Error out here. return Promise.reject(new Error(`status ${status} is not supported`)); } - - return UserModel.update({id}, {$set: {status}}); + + return UserModel.findOne({id}) + .then((user) => { + if (user.status === 'APPROVED' && status === 'PENDING') { + return Promise.resolve(); + } else { + return UserModel.update({id}, {$set: {status}}); + } + }); } /** diff --git a/test/routes/api/user/index.js b/test/routes/api/user/index.js index 4de799df6..f6e202243 100644 --- a/test/routes/api/user/index.js +++ b/test/routes/api/user/index.js @@ -52,33 +52,26 @@ describe('/api/v1/users/:user_id/email/confirm', () => { describe('/api/v1/users/:user_id/actions', () => { - const users = [{ - displayName: 'Ana', - email: 'ana@gmail.com', - password: '123456789' - }, { - displayName: 'Maria', - email: 'maria@gmail.com', - password: '123456789' - }]; + let mockUser; - beforeEach(() => { - return SettingsService.init(settings).then(() => { - return UsersService.createLocalUsers(users); - }); - }); + beforeEach(() => SettingsService.init(settings).then(() => { + return UsersService.createLocalUser('ana@gmail.com', '123321123', 'Ana'); + }) + .then((user) => { + mockUser = user; + })); describe('#post', () => { it('it should update actions', () => { return chai.request(app) - .post('/api/v1/users/abc/actions') + .post(`/api/v1/users/${mockUser.id}/actions`) .set(passport.inject({id: '456', roles: ['ADMIN']})) .send({'action_type': 'FLAG', metadata: {reason: 'Bio is too awesome.'}}) .then((res) => { expect(res).to.have.status(201); expect(res).to.have.body; expect(res.body).to.have.property('action_type', 'FLAG'); - expect(res.body).to.have.property('item_id', 'abc'); + expect(res.body).to.have.property('item_id', mockUser.id); }); }); }); From a0cf467f1b7a9ffc277aaf1526b6e66d2a21a765 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Thu, 2 Feb 2017 14:59:45 -0700 Subject: [PATCH 09/28] remove confirm and replace with verify --- client/coral-embed-stream/src/Embed.js | 2 +- client/coral-framework/actions/auth.js | 20 +- client/coral-framework/constants/auth.js | 7 +- client/coral-framework/reducers/auth.js | 28 +-- client/coral-sign-in/components/SignDialog.js | 7 +- .../coral-sign-in/components/SignInContent.js | 96 +++++----- .../coral-sign-in/components/SignUpContent.js | 172 ++++++++++-------- .../containers/SignInContainer.js | 20 +- client/coral-sign-in/translations.js | 10 +- routes/api/account/index.js | 2 +- routes/api/users/index.js | 2 +- views/admin/confirm-email.ejs | 14 +- 12 files changed, 200 insertions(+), 180 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 550fc5fc1..fa0d0f3ab 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -130,7 +130,7 @@ class Embed extends Component { :

{asset.settings.closedMessage}

} - {!loggedIn && } + {!loggedIn && } ({type: actions.CLEAN_STATE}); const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST}); const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin}); const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error}); -const emailConfirmError = () => ({type: actions.EMAIL_CONFIRM_ERROR}); export const fetchSignIn = (formData) => (dispatch) => { dispatch(signInRequest()); @@ -36,7 +35,6 @@ export const fetchSignIn = (formData) => (dispatch) => { // the user might not have a valid email. prompt the user user re-request the confirmation email dispatch(signInFailure(lang.t('error.emailNotVerified', error.metadata))); - dispatch(emailConfirmError()); } else { // invalid credentials @@ -150,20 +148,20 @@ export const checkLogin = () => dispatch => { }); }; -const confirmEmailRequest = () => ({type: actions.CONFIRM_EMAIL_REQUEST}); -const confirmEmailSuccess = () => ({type: actions.CONFIRM_EMAIL_SUCCESS}); -const confirmEmailFailure = () => ({type: actions.CONFIRM_EMAIL_FAILURE}); +const verifyEmailRequest = () => ({type: actions.VERIFY_EMAIL_REQUEST}); +const verifyEmailSuccess = () => ({type: actions.VERIFY_EMAIL_SUCCESS}); +const verifyEmailFailure = () => ({type: actions.VERIFY_EMAIL_FAILURE}); export const requestConfirmEmail = email => dispatch => { - dispatch(confirmEmailRequest()); - return coralApi('/users/resend-confirm', {method: 'POST', body: {email}}) + dispatch(verifyEmailRequest()); + return coralApi('/users/resend-verify', {method: 'POST', body: {email}}) .then(() => { - dispatch(confirmEmailSuccess()); + dispatch(verifyEmailSuccess()); }) .catch(err => { - console.log('failed to send email confirmation', err); + console.log('failed to send email verification', err); - // email might have already been confirmed - dispatch(confirmEmailFailure()); + // email might have already been verifyed + dispatch(verifyEmailFailure()); }); }; diff --git a/client/coral-framework/constants/auth.js b/client/coral-framework/constants/auth.js index 1dae348df..096fadd66 100644 --- a/client/coral-framework/constants/auth.js +++ b/client/coral-framework/constants/auth.js @@ -33,7 +33,6 @@ export const CHECK_LOGIN_FAILURE = 'CHECK_LOGIN_FAILURE'; export const CHECK_CSRF_TOKEN = 'CHECK_CSRF_TOKEN'; -export const EMAIL_CONFIRM_ERROR = 'EMAIL_CONFIRM_ERROR'; -export const CONFIRM_EMAIL_REQUEST = 'CONFIRM_EMAIL_REQUEST'; -export const CONFIRM_EMAIL_SUCCESS = 'CONFIRM_EMAIL_SUCCESS'; -export const CONFIRM_EMAIL_FAILURE = 'CONFIRM_EMAIL_FAILURE'; +export const VERIFY_EMAIL_REQUEST = 'VERIFY_EMAIL_REQUEST'; +export const VERIFY_EMAIL_SUCCESS = 'VERIFY_EMAIL_SUCCESS'; +export const VERIFY_EMAIL_FAILURE = 'VERIFY_EMAIL_FAILURE'; diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index 6cb4e97cd..836549b18 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -11,9 +11,9 @@ const initialState = Map({ error: '', passwordRequestSuccess: null, passwordRequestFailure: null, - emailConfirmationFailure: false, - emailConfirmationLoading: false, - emailConfirmationSuccess: false, + emailVerificationFailure: false, + emailVerificationLoading: false, + emailVerificationSuccess: false, successSignUp: false }); @@ -36,9 +36,9 @@ export default function auth (state = initialState, action) { error: '', passwordRequestFailure: null, passwordRequestSuccess: null, - emailConfirmationFailure: false, - emailConfirmationSuccess: false, - emailConfirmationLoading: false, + emailVerificationFailure: false, + emailVerificationSuccess: false, + emailVerificationLoading: false, successSignUp: false })); case actions.CHANGE_VIEW : @@ -107,16 +107,16 @@ export default function auth (state = initialState, action) { return state .set('passwordRequestFailure', 'There was an error sending your password reset email. Please try again soon!') .set('passwordRequestSuccess', null); - case actions.EMAIL_CONFIRM_ERROR: + case actions.VERIFY_EMAIL_FAILURE: return state - .set('emailConfirmationFailure', true) - .set('emailConfirmationLoading', false); - case actions.CONFIRM_EMAIL_REQUEST: - return state.set('emailConfirmationLoading', true); - case actions.CONFIRM_EMAIL_SUCCESS: + .set('emailVerificationFailure', true) + .set('emailVerificationLoading', false); + case actions.VERIFY_EMAIL_REQUEST: + return state.set('emailVerificationLoading', true); + case actions.VERIFY_EMAIL_SUCCESS: return state - .set('emailConfirmationSuccess', true) - .set('emailConfirmationLoading', false); + .set('emailVerificationSuccess', true) + .set('emailVerificationLoading', false); default : return state; } diff --git a/client/coral-sign-in/components/SignDialog.js b/client/coral-sign-in/components/SignDialog.js index 0645f110f..6243472b4 100644 --- a/client/coral-sign-in/components/SignDialog.js +++ b/client/coral-sign-in/components/SignDialog.js @@ -17,12 +17,7 @@ const SignDialog = ({open, view, handleClose, offset, ...props}) => ( }}> × {view === 'SIGNIN' && } - { - view === 'SIGNUP' && - } + {view === 'SIGNUP' && } {view === 'FORGOT' && } ); diff --git a/client/coral-sign-in/components/SignInContent.js b/client/coral-sign-in/components/SignInContent.js index e27185b52..8112c345f 100644 --- a/client/coral-sign-in/components/SignInContent.js +++ b/client/coral-sign-in/components/SignInContent.js @@ -10,9 +10,9 @@ const SignInContent = ({ handleChange, handleChangeEmail, emailToBeResent, - handleResendConfirmation, - emailConfirmationLoading, - emailConfirmationSuccess, + handleResendVerification, + emailVerificationLoading, + emailVerificationSuccess, formData, ...props }) => { @@ -21,24 +21,14 @@ const SignInContent = ({

- {props.auth.emailConfirmationFailure ? lang.t('signIn.emailConfirmCTA') : lang.t('signIn.signIn')} -

-
-
- -
-
-

- {lang.t('signIn.or')} + {props.auth.emailVerificationFailure ? lang.t('signIn.emailVerifyCTA') : lang.t('signIn.signIn')}

{ props.auth.error && {props.auth.error} } { - props.auth.emailConfirmationFailure - ?
-

{lang.t('signIn.requestNewConfirmEmail')}

+ props.auth.emailVerificationFailure + ? +

{lang.t('signIn.requestNewVerifyEmail')}

- {emailConfirmationLoading && } - {emailConfirmationSuccess && } + {emailVerificationLoading && } + {emailVerificationSuccess && } - :
- - -
- { - !props.auth.isLoading ? - - : - - } + :
+
+
- +
+

+ {lang.t('signIn.or')} +

+
+
+ + +
+ { + !props.auth.isLoading ? + + : + + } +
+ +
}
props.changeView('FORGOT')}>{lang.t('signIn.forgotYourPass')} @@ -90,9 +92,9 @@ const SignInContent = ({ }; SignInContent.propTypes = { - emailConfirmationLoading: PropTypes.bool.isRequired, - emailConfirmationSuccess: PropTypes.bool.isRequired, - handleResendConfirmation: PropTypes.func.isRequired, + emailVerificationLoading: PropTypes.bool.isRequired, + emailVerificationSuccess: PropTypes.bool.isRequired, + handleResendVerification: PropTypes.func.isRequired, handleChangeEmail: PropTypes.func.isRequired, emailToBeResent: PropTypes.string.isRequired }; diff --git a/client/coral-sign-in/components/SignUpContent.js b/client/coral-sign-in/components/SignUpContent.js index 246d9d884..5efadc8fc 100644 --- a/client/coral-sign-in/components/SignUpContent.js +++ b/client/coral-sign-in/components/SignUpContent.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {PropTypes} from 'react'; import Alert from './Alert'; import {Button, FormField, Spinner, Success} from 'coral-ui'; import styles from './styles.css'; @@ -6,83 +6,101 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const SignUpContent = ({handleChange, formData, ...props}) => ( -
-
-

- {lang.t('signIn.signUp')} -

-
-
- -
-
-

- {lang.t('signIn.or')} -

-
- { props.auth.error && {props.auth.error} } -
- - - - { props.errors.password && Password must be at least 8 characters. } - -
- { !props.auth.isLoading && !props.auth.successSignUp && ( - - )} - { props.auth.isLoading && } - { !props.auth.isLoading && props.auth.successSignUp && } +const SignUpContent = ({handleChange, formData, emailVerificationEnabled, ...props}) => { + const beforeSignup = !props.auth.isLoading && !props.auth.successSignUp; + const successfulSignup = !props.auth.isLoading && props.auth.successSignUp; + + return ( +
+
+

+ {lang.t('signIn.signUp')} +

+
+
+ +
+
+

+ {lang.t('signIn.or')} +

+
+ { props.auth.error && {props.auth.error} } + { beforeSignup && + + + + + { props.errors.password && Password must be at least 8 characters. } + +
+ + { props.auth.isLoading && } +
+ + } + { + successfulSignup && +
+ + { + emailVerificationEnabled && +

{lang.t('signIn.verifyEmail')}

+ } +
+ } +
+ + {lang.t('signIn.alreadyHaveAnAccount')} + props.changeView('SIGNIN')}> + {lang.t('signIn.signIn')} + +
- -
- - {lang.t('signIn.alreadyHaveAnAccount')} - props.changeView('SIGNIN')}> - {lang.t('signIn.signIn')} - -
-
-); + ); +}; + +SignUpContent.propTypes = { + emailVerificationEnabled: PropTypes.bool.isRequired +}; export default SignUpContent; diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js index 2435cdcfd..59e17c397 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react'; +import React, {Component, PropTypes} from 'react'; import {connect} from 'react-redux'; import SignDialog from '../components/SignDialog'; import Button from 'coral-ui/components/Button'; @@ -41,12 +41,16 @@ class SignInContainer extends Component { this.state = this.initialState; this.handleChange = this.handleChange.bind(this); this.handleChangeEmail = this.handleChangeEmail.bind(this); - this.handleResendConfirmation = this.handleResendConfirmation.bind(this); + this.handleResendVerification = this.handleResendVerification.bind(this); this.handleSignUp = this.handleSignUp.bind(this); this.handleSignIn = this.handleSignIn.bind(this); this.addError = this.addError.bind(this); } + static propTypes = { + requireEmailConfirmation: PropTypes.bool.isRequired + } + componentWillMount () { this.props.checkLogin(); } @@ -79,7 +83,7 @@ class SignInContainer extends Component { this.setState({emailToBeResent: value}); } - handleResendConfirmation(e) { + handleResendVerification(e) { e.preventDefault(); this.props.requestConfirmEmail(this.state.emailToBeResent) .then(() => { @@ -145,8 +149,9 @@ class SignInContainer extends Component { } render() { - const {auth, showSignInDialog, noButton, offset} = this.props; - const {emailConfirmationLoading, emailConfirmationSuccess} = auth; + const {auth, showSignInDialog, noButton, offset, requireEmailConfirmation} = this.props; + const {emailVerificationLoading, emailVerificationSuccess} = auth; + return (
{!noButton && -
-
-

- {lang.t('signIn.or')} -

-
+ { props.auth.error && {props.auth.error} } { beforeSignup && -
- - - - { props.errors.password && Password must be at least 8 characters. } - -
- - { props.auth.isLoading && }
- +
+

+ {lang.t('signIn.or')} +

+
+
+ + + + { props.errors.password && Password must be at least 8 characters. } + +
+ + { props.auth.isLoading && } +
+ +
} { successfulSignup && From d92c2d9744d5cef86f21704bbc3999ecf24e7f0a Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 3 Feb 2017 10:18:29 -0800 Subject: [PATCH 15/28] Updating user queue routes. --- client/coral-admin/src/actions/comments.js | 4 ++-- client/coral-admin/src/components/User.js | 7 ------- routes/api/queue/index.js | 2 +- test/routes/api/queue/index.js | 2 +- 4 files changed, 4 insertions(+), 11 deletions(-) diff --git a/client/coral-admin/src/actions/comments.js b/client/coral-admin/src/actions/comments.js index 3c1437de7..ac7af12fd 100644 --- a/client/coral-admin/src/actions/comments.js +++ b/client/coral-admin/src/actions/comments.js @@ -15,7 +15,7 @@ export const fetchModerationQueueComments = () => { return Promise.all([ coralApi('/queue/comments/premod'), - coralApi('/queue/users/pending'), + coralApi('/queue/users/flagged'), coralApi('/queue/comments/rejected'), coralApi('/queue/comments/flagged') ]) @@ -46,7 +46,7 @@ export const fetchPendingUsersQueue = () => { return dispatch => { dispatch({type: commentTypes.COMMENTS_MODERATION_QUEUE_FETCH_REQUEST}); - return coralApi('/queue/users/pending') + return coralApi('/queue/users/flagged') .then(addUsersCommentsActions.bind(this, dispatch)); }; }; diff --git a/client/coral-admin/src/components/User.js b/client/coral-admin/src/components/User.js index c8a056699..54de0375e 100644 --- a/client/coral-admin/src/components/User.js +++ b/client/coral-admin/src/components/User.js @@ -1,6 +1,4 @@ import React from 'react'; -import Linkify from 'react-linkify'; - import styles from './ModerationList.css'; import I18n from 'coral-framework/modules/i18n/i18n'; @@ -9,13 +7,10 @@ import translations from '../translations.json'; import {Icon} from 'react-mdl'; import ActionButton from './ActionButton'; -const linkify = new Linkify(); - // Render a single comment for the list const User = props => { const {action, user} = props; let userStatus = user.status; - const links = user.settings.bio ? linkify.getMatches(user.settings.bio) : []; // Do not display unless the user status is 'pending' or 'banned'. // This means that they have already been reviewed and approved. @@ -26,8 +21,6 @@ const User = props => { {user.displayName}
- {links ? - Contains Link : null}
{props.modActions.map( (action, i) => diff --git a/routes/api/queue/index.js b/routes/api/queue/index.js index 2373b808f..b3cbb1a52 100644 --- a/routes/api/queue/index.js +++ b/routes/api/queue/index.js @@ -79,7 +79,7 @@ router.get('/comments/flagged', authorization.needed('ADMIN'), (req, res, next) }); // Returns back all the users that are in the moderation queue. -router.get('/users/pending', (req, res, next) => { +router.get('/users/flagged', (req, res, next) => { UsersService.moderationQueue() .then((users) => { return Promise.all([ diff --git a/test/routes/api/queue/index.js b/test/routes/api/queue/index.js index ab60a9f20..9f89a961e 100644 --- a/test/routes/api/queue/index.js +++ b/test/routes/api/queue/index.js @@ -110,7 +110,7 @@ describe('/api/v1/queue', () => { it('should return all pending users and actions', function(done){ chai.request(app) - .get('/api/v1/queue/users/pending') + .get('/api/v1/queue/users/flagged') .set(passport.inject({roles: ['ADMIN']})) .end(function(err, res){ expect(err).to.be.null; From 2dacb723418453210c470e1a6eab49bdeb5694af Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 3 Feb 2017 10:27:17 -0800 Subject: [PATCH 16/28] Updating reload code. --- client/coral-framework/components/SuspendedAccount.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-framework/components/SuspendedAccount.js b/client/coral-framework/components/SuspendedAccount.js index 0c6347fca..4c221aa81 100644 --- a/client/coral-framework/components/SuspendedAccount.js +++ b/client/coral-framework/components/SuspendedAccount.js @@ -24,7 +24,7 @@ class SuspendedAccount extends Component { e.preventDefault(); if (validate.displayName(displayName)) { editName(displayName) - .then(() => window.reload()) + .then(() => location.reload()) .catch((error) => { this.setState({alert: lang.t(`error.${error.message}`)}); }); From c784de6e95c195e084d3fa9dd9338416e2782cda Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 3 Feb 2017 11:15:10 -0800 Subject: [PATCH 17/28] Updating e2e tests. --- test/e2e/pages/adminPage.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/test/e2e/pages/adminPage.js b/test/e2e/pages/adminPage.js index 3cc00d5fc..e359715ac 100644 --- a/test/e2e/pages/adminPage.js +++ b/test/e2e/pages/adminPage.js @@ -10,8 +10,7 @@ const embedStreamCommands = { return this .waitForElementVisible('@moderationList') .waitForElementVisible('@approveButton') - .click('@approveButton') - .waitForElementNotPresent('@approveButton'); + .click('@approveButton'); } }; From 3441ec36c61ce5ee522fbdfd1df62f37ff8c4569 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 3 Feb 2017 12:50:01 -0700 Subject: [PATCH 18/28] re-direct to sign in when email verification is off. add proptypes --- client/coral-framework/actions/auth.js | 4 - .../coral-sign-in/components/SignUpContent.js | 230 +++++++++++------- 2 files changed, 137 insertions(+), 97 deletions(-) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index d40997720..279fc604f 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -85,10 +85,6 @@ export const fetchSignUp = (formData, redirectUri) => (dispatch) => { coralApi('/users', {method: 'POST', body: formData, headers: {'X-Pym-Url': redirectUri}}) .then(({user}) => { dispatch(signUpSuccess(user)); - - // setTimeout(() =>{ - // dispatch(changeView('SIGNIN')); - // }, 3000); }) .catch(error => { dispatch(signUpFailure(lang.t(`error.${error.message}`))); diff --git a/client/coral-sign-in/components/SignUpContent.js b/client/coral-sign-in/components/SignUpContent.js index 04bd2fd3e..3607b246f 100644 --- a/client/coral-sign-in/components/SignUpContent.js +++ b/client/coral-sign-in/components/SignUpContent.js @@ -6,104 +6,148 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations'; const lang = new I18n(translations); -const SignUpContent = ({handleChange, formData, emailVerificationEnabled, ...props}) => { - const beforeSignup = !props.auth.isLoading && !props.auth.successSignUp; - const successfulSignup = !props.auth.isLoading && props.auth.successSignUp; +class SignUpContent extends React.Component { - return ( -
-
-

- {lang.t('signIn.signUp')} -

-
+ constructor (props) { + super(props); + this.successfulSignup = false; + } - { props.auth.error && {props.auth.error} } - { beforeSignup && -
-
- -
-
-

- {lang.t('signIn.or')} -

-
-
- - - - { props.errors.password && Password must be at least 8 characters. } - -
- - { props.auth.isLoading && }
- +
+

+ {lang.t('signIn.or')} +

+
+
+ + + + { errors.password && Password must be at least 8 characters. } + +
+ + { auth.isLoading && } +
+ +
+ } + { + successfulSignup && +
+ + { + emailVerificationEnabled && +

{lang.t('signIn.verifyEmail')}

{lang.t('signIn.verifyEmail2')}

+ } +
+ } +
+ + {lang.t('signIn.alreadyHaveAnAccount')} + changeView('SIGNIN')}> + {lang.t('signIn.signIn')} + +
- } - { - successfulSignup && -
- - { - emailVerificationEnabled && -

{lang.t('signIn.verifyEmail')}

{lang.t('signIn.verifyEmail2')}

- } -
- } -
- - {lang.t('signIn.alreadyHaveAnAccount')} - props.changeView('SIGNIN')}> - {lang.t('signIn.signIn')} - -
-
- ); -}; - -SignUpContent.propTypes = { - emailVerificationEnabled: PropTypes.bool.isRequired -}; + ); + } +} export default SignUpContent; From e91ff306e9a082e26f3a801449ad274f4234b188 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 3 Feb 2017 14:21:22 -0700 Subject: [PATCH 19/28] whoops, missed one --- client/coral-admin/src/translations.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/translations.json b/client/coral-admin/src/translations.json index a4256848e..6dffeae42 100644 --- a/client/coral-admin/src/translations.json +++ b/client/coral-admin/src/translations.json @@ -50,7 +50,7 @@ "configure": { "enable-pre-moderation": "Enable pre-moderation", "enable-pre-moderation-text": "Moderators must approve any comment before it is published.", - "require-email-verification": "Require Email Confirmation", + "require-email-verification": "Require Email Verification", "require-email-verification-text": "New Users must verify their email before commenting", "include-comment-stream": "Include Comment Stream Description for Readers.", "include-comment-stream-desc": "Write a message to be added to the top of your comment stream. Pose a topic, include community guidelines, etc.", From 19015927801af8f87af55d147e77d08ff0112f32 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 6 Feb 2017 11:07:35 -0800 Subject: [PATCH 20/28] Switching displayname edit after facebook auth to account endpoint. --- client/coral-framework/actions/auth.js | 6 +++--- routes/api/users/index.js | 10 ++-------- services/users.js | 21 +-------------------- test/services/users.js | 19 ------------------- 4 files changed, 6 insertions(+), 50 deletions(-) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index edd04de12..3c23bd5bc 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -19,11 +19,11 @@ export const updateDisplayName = displayName => ({type: actions.UPDATE_DISPLAYNA export const createDisplayName = (userId, formData) => dispatch => { dispatch(createDisplayNameRequest()); - coralApi(`/users/${userId}/displayname`, {method: 'POST', body: formData}) - .then((user) => { + coralApi('account/displayname', {method: 'PUT', body: formData}) + .then(() => { dispatch(createDisplayNameSuccess()); dispatch(hideCreateDisplayNameDialog()); - dispatch(updateDisplayName(user)); + dispatch(updateDisplayName(formData)); }) .catch(error => { dispatch(createDisplayNameFailure(lang.t(`error.${error.message}`))); diff --git a/routes/api/users/index.js b/routes/api/users/index.js index 93c1062d2..6f40e5fb5 100644 --- a/routes/api/users/index.js +++ b/routes/api/users/index.js @@ -65,17 +65,11 @@ router.post('/:user_id/username-enable', authorization.needed('ADMIN'), (req, re .toggleNameEdit(req.params.user_id, true) .then(() => { res.status(204).end(); - -router.post('/:user_id/displayname', authorization.needed(), (req, res, next) => { - UsersService.setDisplayName(req.params.user_id, req.body.displayName) - .then((user) => { - res.status(201).json(user); - }) - .catch(next); + }); }); router.post('/:user_id/email', authorization.needed('ADMIN'), (req, res, next) => { - + UsersService.findById(req.params.user_id) .then(user => { let localProfile = user.profiles.find((profile) => profile.provider === 'local'); diff --git a/services/users.js b/services/users.js index 2b7d5f718..b5c3a8885 100644 --- a/services/users.js +++ b/services/users.js @@ -349,7 +349,7 @@ module.exports = class UsersService { // User status is not supported! Error out here. return Promise.reject(new Error(`status ${status} is not supported`)); } - + return UserModel.findOne({id}) .then((user) => { if (user.status === 'APPROVED' && status === 'PENDING') { @@ -360,25 +360,6 @@ module.exports = class UsersService { }); } - /** - * Set the display name of a user. - * @param {String} id id of a user - * @param {String} displayName display name to set - * @param {Function} done callback after the operation is complete - */ - static setDisplayName(id, displayName) { - - return UsersService.isValidDisplayName(displayName) - .then(() => { // displayName is valid - return UserModel.update( - {id}, - {$set: {'displayName': displayName}}) - .then(() => { - return UserModel.findOne({'id': id}); - }); - }); - } - /** * Finds a user with the id. * @param {String} id user id (uuid) diff --git a/test/services/users.js b/test/services/users.js index 581b6f06e..5774d45ae 100644 --- a/test/services/users.js +++ b/test/services/users.js @@ -178,25 +178,6 @@ describe('services.UsersService', () => { }); }); - describe('#setDisplayName', () => { - it('should set the display name to a new unique one', () => { - return UsersService - .setDisplayName(mockUsers[0].id, 'maria') - .then(() => UsersService.findById(mockUsers[0].id)) - .then((user) => { - expect(user).to.have.property('displayName', 'maria'); - }); - }); - - it('should return an error when the displayName is not unique', () => { - return UsersService - .setDisplayName(mockUsers[0].id, 'marvel') - .catch((error) => { - expect(error).to.not.be.null; - }); - }); - }); - describe('#ban', () => { it('should set the status to banned', () => { return UsersService From 3a145e99272da015216e1bbd503cd5889fbbc8ef Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 6 Feb 2017 14:22:37 -0700 Subject: [PATCH 21/28] add a pause to see if anything changes --- test/e2e/tests/EmbedStreamTests.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/test/e2e/tests/EmbedStreamTests.js b/test/e2e/tests/EmbedStreamTests.js index 1b1fb40ab..5591d6b8d 100644 --- a/test/e2e/tests/EmbedStreamTests.js +++ b/test/e2e/tests/EmbedStreamTests.js @@ -4,8 +4,10 @@ const mocks = require('../mocks'); const mockComment = 'I read the comments'; const mockReply = 'This is a test reply'; const mockUser = { - email: `${new Date().getTime()}@test.com`, - name: 'testuser', + email: `${Date.now()}@test.com`, + name: `testuser${Math.random() + .toString() + .slice(-5)}`, pw: 'testtest' }; @@ -39,6 +41,7 @@ module.exports = { .setValue('#password', mockUser.pw) .setValue('#confirmPassword', mockUser.pw) .click('#coralSignUpButton') + .pause(5000) .waitForElementVisible('#coralLogInButton', 10000) .click('#coralLogInButton') .waitForElementVisible('.coral-plugin-commentbox-button', 4000) From 93ee5bfc4aeb7f2d37326a66454371cdf779f955 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 6 Feb 2017 14:42:40 -0700 Subject: [PATCH 22/28] just throwing stuff at the wall to see what sticks --- test/e2e/pages/embedStreamPage.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/e2e/pages/embedStreamPage.js b/test/e2e/pages/embedStreamPage.js index 372b94b66..22fd2e04f 100644 --- a/test/e2e/pages/embedStreamPage.js +++ b/test/e2e/pages/embedStreamPage.js @@ -35,8 +35,10 @@ const embedStreamCommands = { .waitForElementVisible('@signInDialogPassword') .setValue('@signInDialogEmail', user.email) .setValue('@signInDialogPassword', user.pass) + .pause(2500) .waitForElementVisible('@logInButton') .click('@logInButton') + .pause(2500) .waitForElementVisible('@logoutButton', 5000); }, logout() { From fe6d6f611db972497cd5b94c12e49a91377e6f84 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 6 Feb 2017 14:59:23 -0700 Subject: [PATCH 23/28] pause suddenly not a function? --- test/e2e/pages/embedStreamPage.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/test/e2e/pages/embedStreamPage.js b/test/e2e/pages/embedStreamPage.js index 22fd2e04f..372b94b66 100644 --- a/test/e2e/pages/embedStreamPage.js +++ b/test/e2e/pages/embedStreamPage.js @@ -35,10 +35,8 @@ const embedStreamCommands = { .waitForElementVisible('@signInDialogPassword') .setValue('@signInDialogEmail', user.email) .setValue('@signInDialogPassword', user.pass) - .pause(2500) .waitForElementVisible('@logInButton') .click('@logInButton') - .pause(2500) .waitForElementVisible('@logoutButton', 5000); }, logout() { From b22895b908eb3a1996f5498b60d8dbe673a72728 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 6 Feb 2017 14:38:14 -0800 Subject: [PATCH 24/28] Switching displayname update to account endpoint. --- client/coral-framework/actions/auth.js | 4 ++-- .../coral-framework/graphql/queries/index.js | 3 +++ client/coral-framework/reducers/auth.js | 7 +++--- routes/api/auth/index.js | 22 ++++++++++++------- services/users.js | 2 +- 5 files changed, 24 insertions(+), 14 deletions(-) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 3c23bd5bc..9c6d81f81 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -15,11 +15,11 @@ export const hideCreateDisplayNameDialog = () => ({type: actions.HIDE_CREATEDISP const createDisplayNameSuccess = () => ({type: actions.CREATEDISPLAYNAME_SUCCESS}); const createDisplayNameFailure = error => ({type: actions.CREATEDISPLAYNAME_FAILURE, error}); -export const updateDisplayName = displayName => ({type: actions.UPDATE_DISPLAYNAME, displayName}); +export const updateDisplayName = ({displayName}) => ({type: actions.UPDATE_DISPLAYNAME, displayName}); export const createDisplayName = (userId, formData) => dispatch => { dispatch(createDisplayNameRequest()); - coralApi('account/displayname', {method: 'PUT', body: formData}) + coralApi('/account/displayname', {method: 'PUT', body: formData}) .then(() => { dispatch(createDisplayNameSuccess()); dispatch(hideCreateDisplayNameDialog()); diff --git a/client/coral-framework/graphql/queries/index.js b/client/coral-framework/graphql/queries/index.js index 1a9e8d36e..7ce065baa 100644 --- a/client/coral-framework/graphql/queries/index.js +++ b/client/coral-framework/graphql/queries/index.js @@ -11,6 +11,9 @@ function getQueryVariable(variable) { return decodeURIComponent(pair[1]); } } + + // If no query is included, return a default string for development + return 'http://dev.default.stream'; } export const queryStream = graphql(STREAM_QUERY, { diff --git a/client/coral-framework/reducers/auth.js b/client/coral-framework/reducers/auth.js index 2da589507..a8606c0cc 100644 --- a/client/coral-framework/reducers/auth.js +++ b/client/coral-framework/reducers/auth.js @@ -1,4 +1,4 @@ -import {Map} from 'immutable'; +import {Map, fromJS} from 'immutable'; import * as actions from '../constants/auth'; const initialState = Map({ @@ -21,7 +21,7 @@ const initialState = Map({ const purge = user => { const {settings, profiles, ...userData} = user; // eslint-disable-line - return userData; + return fromJS(userData); }; export default function auth (state = initialState, action) { @@ -131,8 +131,9 @@ export default function auth (state = initialState, action) { .set('passwordRequestFailure', 'There was an error sending your password reset email. Please try again soon!') .set('passwordRequestSuccess', null); case actions.UPDATE_DISPLAYNAME: + console.log('Action', action); return state - .set('user', purge(action.displayName)); + .setIn(['user', 'displayName'], action.displayName); case actions.EMAIL_CONFIRM_ERROR: return state .set('emailConfirmationFailure', true) diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index 050aa1135..e3acf2c77 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -2,6 +2,7 @@ const express = require('express'); const passport = require('../../../services/passport'); const authorization = require('../../../middleware/authorization'); const errors = require('../../../errors'); +const UsersService = require('../../../services/users'); const router = express.Router(); @@ -69,15 +70,20 @@ const HandleAuthPopupCallback = (req, res, next) => (err, user) => { return res.render('auth-callback', {err: JSON.stringify(errors.ErrNotAuthorized), data: null}); } - // Perform the login of the user! - req.logIn(user, (err) => { - if (err) { - return res.render('auth-callback', {err: JSON.stringify(err), data: null}); - } + // Authorize the user to edit their displayName. + UsersService.toggleNameEdit(user.id, true) + .then(() => { + + // Perform the login of the user! + req.logIn(user, (err) => { + if (err) { + return res.render('auth-callback', {err: JSON.stringify(err), data: null}); + } - // We logged in the user! Let's send back the user data. - res.render('auth-callback', {err: null, data: JSON.stringify(user)}); - }); + // We logged in the user! Let's send back the user data. + res.render('auth-callback', {err: null, data: JSON.stringify(user)}); + }); + }); }; /** diff --git a/services/users.js b/services/users.js index b5c3a8885..1f36d969d 100644 --- a/services/users.js +++ b/services/users.js @@ -645,7 +645,7 @@ module.exports = class UsersService { } /** - * Gives the user the ability to edit their username. + * Updates the user's displayName. * @param {String} id the id of the user to be enabled. * @param {String} displayName The new displayname for the user. * @return {Promise} From 8c8a8eb3b8ee953c0635c5d9813e27560205149e Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 6 Feb 2017 16:23:00 -0700 Subject: [PATCH 25/28] define more propTypes. add a second button so tests work --- .../coral-sign-in/components/SignInContent.js | 29 +++++++++++++------ .../coral-sign-in/components/SignUpContent.js | 2 +- test/e2e/pages/embedStreamPage.js | 5 ++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/client/coral-sign-in/components/SignInContent.js b/client/coral-sign-in/components/SignInContent.js index 8112c345f..c7a5dc80d 100644 --- a/client/coral-sign-in/components/SignInContent.js +++ b/client/coral-sign-in/components/SignInContent.js @@ -14,19 +14,22 @@ const SignInContent = ({ emailVerificationLoading, emailVerificationSuccess, formData, - ...props + changeView, + handleSignIn, + auth, + fetchSignInFacebook }) => { return (

- {props.auth.emailVerificationFailure ? lang.t('signIn.emailVerifyCTA') : lang.t('signIn.signIn')} + {auth.emailVerificationFailure ? lang.t('signIn.emailVerifyCTA') : lang.t('signIn.signIn')}

- { props.auth.error && {props.auth.error} } + { auth.error && {auth.error} } { - props.auth.emailVerificationFailure + auth.emailVerificationFailure ?

{lang.t('signIn.requestNewVerifyEmail')}

:
-
@@ -50,7 +53,7 @@ const SignInContent = ({ {lang.t('signIn.or')}
- +
{ - !props.auth.isLoading ? + !auth.isLoading ? @@ -79,10 +82,10 @@ const SignInContent = ({
}
- props.changeView('FORGOT')}>{lang.t('signIn.forgotYourPass')} + changeView('FORGOT')}>{lang.t('signIn.forgotYourPass')} {lang.t('signIn.needAnAccount')} - props.changeView('SIGNUP')} id='coralRegister'> + changeView('SIGNUP')} id='coralRegister'> {lang.t('signIn.register')} @@ -92,6 +95,14 @@ const SignInContent = ({ }; SignInContent.propTypes = { + auth: PropTypes.shape({ + isLoading: PropTypes.bool.isRequired, + error: PropTypes.object, + emailVerificationFailure: PropTypes.bool + }).isRequired, + fetchSignInFacebook: PropTypes.func.isRequired, + handleSignIn: PropTypes.func.isRequired, + changeView: PropTypes.func.isRequired, emailVerificationLoading: PropTypes.bool.isRequired, emailVerificationSuccess: PropTypes.bool.isRequired, handleResendVerification: PropTypes.func.isRequired, diff --git a/client/coral-sign-in/components/SignUpContent.js b/client/coral-sign-in/components/SignUpContent.js index 69c0e816a..03c083070 100644 --- a/client/coral-sign-in/components/SignUpContent.js +++ b/client/coral-sign-in/components/SignUpContent.js @@ -140,7 +140,7 @@ class SignUpContent extends React.Component {
{lang.t('signIn.alreadyHaveAnAccount')} - changeView('SIGNIN')}> + changeView('SIGNIN')}> {lang.t('signIn.signIn')} diff --git a/test/e2e/pages/embedStreamPage.js b/test/e2e/pages/embedStreamPage.js index 372b94b66..1dd467b10 100644 --- a/test/e2e/pages/embedStreamPage.js +++ b/test/e2e/pages/embedStreamPage.js @@ -22,6 +22,8 @@ const embedStreamCommands = { .setValue('@signUpDialogDisplayName', user.displayName) .waitForElementVisible('@signUpButton') .click('@signUpButton') + .waitForElementVisible('@signInViewTrigger') + .click('@signInViewTrigger') .waitForElementVisible('@logInButton') .click('@logInButton') .waitForElementVisible('@logoutButton', 5000); @@ -102,6 +104,9 @@ module.exports = { signUpButton: { selector: '#coralSignUpButton' }, + signInViewTrigger: { + selector: '#coralSignInViewTrigger' + }, logoutButton: { selector: '.commentStream #logout' }, From 80ea100fc531d778a36dc20b89352374a19efcd1 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 6 Feb 2017 15:49:48 -0800 Subject: [PATCH 26/28] Making username update services atomic. --- services/users.js | 43 +++++++++++++++++++++++-------------------- 1 file changed, 23 insertions(+), 20 deletions(-) diff --git a/services/users.js b/services/users.js index 1f36d969d..bab7b0d20 100644 --- a/services/users.js +++ b/services/users.js @@ -350,14 +350,16 @@ module.exports = class UsersService { return Promise.reject(new Error(`status ${status} is not supported`)); } - return UserModel.findOne({id}) - .then((user) => { - if (user.status === 'APPROVED' && status === 'PENDING') { - return Promise.resolve(); - } else { - return UserModel.update({id}, {$set: {status}}); - } - }); + return UserModel.update({ + id, + status: { + $ne: 'APPROVED' + } + }, { + $set: { + status + } + }); } /** @@ -651,17 +653,18 @@ module.exports = class UsersService { * @return {Promise} */ static editName(id, displayName) { - return UserModel.findOne({id}) - .then((user) => { - return user.canEditName ? - UserModel.update({id}, { - $set: { - displayName: displayName.toLowerCase(), - canEditName: false, - status: 'PENDING' - } - }) - : Promise.reject(new Error('Display name editing disabled for this account.')); - }); + return UserModel.update({ + id, + canEditName: true + }, { + $set: { + displayName: displayName.toLowerCase(), + canEditName: false, + status: 'PENDING' + } + }).then((result) => { + return result.nModified > 0 ? result : + Promise.reject(new Error('You do not have permission to update your username.')); + }); } }; From ecb72b036ddf59aad035d1fdd8e0a24742b557e6 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 6 Feb 2017 16:49:31 -0800 Subject: [PATCH 27/28] Fixing mobile viewport width. --- views/article.ejs | 1 + 1 file changed, 1 insertion(+) diff --git a/views/article.ejs b/views/article.ejs index 72fc69ce3..1315acac6 100644 --- a/views/article.ejs +++ b/views/article.ejs @@ -8,6 +8,7 @@ +