From b2430eaf60437ee498a153c934af0cf3928989d4 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 1 Feb 2017 15:55:33 -0500 Subject: [PATCH] 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(); + }); + }); + }); + });