diff --git a/app/templates/clans/clan-details.jade b/app/templates/clans/clan-details.jade index c5784ca4b..1c54c6e3d 100644 --- a/app/templates/clans/clan-details.jade +++ b/app/templates/clans/clan-details.jade @@ -30,7 +30,7 @@ block content td= member.level if isOwner && member.id !== clan.get('ownerID') td - button.btn.btn-sm.btn-warning Remove Member + button.btn.btn-sm.btn-warning.remove-member-btn(data-id="#{member.id}") Remove Member else td diff --git a/app/views/clans/ClanDetailsView.coffee b/app/views/clans/ClanDetailsView.coffee index 1f507a3d9..41632430c 100644 --- a/app/views/clans/ClanDetailsView.coffee +++ b/app/views/clans/ClanDetailsView.coffee @@ -5,7 +5,6 @@ template = require 'templates/clans/clan-details' Clan = require 'models/Clan' # TODO: join/leave mostly duped from clans view -# TODO: Refresh data instead of page module.exports = class ClanDetailsView extends RootView id: 'clan-details-view' @@ -14,6 +13,7 @@ module.exports = class ClanDetailsView extends RootView events: 'click .join-clan-btn': 'onJoinClan' 'click .leave-clan-btn': 'onLeaveClan' + 'click .remove-member-btn': 'onRemoveMember' constructor: (options, @clanID) -> super options @@ -36,7 +36,9 @@ module.exports = class ClanDetailsView extends RootView error: (model, response, options) => console.error 'Error joining clan', response success: (model, response, options) => - window.location.reload() + @listenToOnce @clan, 'sync', => + @render?() + @clan.fetch cache: false @supermodel.addRequestResource( 'join_clan', options).load() else console.error "No clan ID attached to join button." @@ -49,7 +51,24 @@ module.exports = class ClanDetailsView extends RootView error: (model, response, options) => console.error 'Error leaving clan', response success: (model, response, options) => - window.location.reload() + @listenToOnce @clan, 'sync', => + @render?() + @clan.fetch cache: false @supermodel.addRequestResource( 'leave_clan', options).load() else console.error "No clan ID attached to leave button." + + onRemoveMember: (e) -> + if memberID = $(e.target).data('id') + options = + url: "/db/clan/#{@clanID}/remove/#{memberID}" + method: 'PUT' + error: (model, response, options) => + console.error 'Error removing clan member', response + success: (model, response, options) => + @listenToOnce @clan, 'sync', => + @render?() + @clan.fetch cache: false + @supermodel.addRequestResource( 'remove_member', options).load() + else + console.error "No member ID attached to remove button." diff --git a/server/clans/clan_handler.coffee b/server/clans/clan_handler.coffee index f04946bd5..3dc0f0b3b 100644 --- a/server/clans/clan_handler.coffee +++ b/server/clans/clan_handler.coffee @@ -32,6 +32,7 @@ ClanHandler = class ClanHandler extends Handler getByRelationship: (req, res, args...) -> return @joinClan(req, res, args[0]) if args[1] is 'join' return @leaveClan(req, res, args[0]) if args[1] is 'leave' + return @removeMember(req, res, args[0], args[2]) if args.length is 3 and args[1] is 'remove' super(arguments...) joinClan: (req, res, clanID) -> @@ -55,4 +56,19 @@ ClanHandler = class ClanHandler extends Handler return @sendDatabaseError(res, err) if err @sendSuccess(res) + removeMember: (req, res, clanID, memberID) -> + return @sendForbiddenError(res) unless req.user? and not req.user.isAnonymous() + try + clanID = mongoose.Types.ObjectId(clanID) + memberID = mongoose.Types.ObjectId(memberID) + catch err + return @sendBadInputError(res, err) + Clan.findById clanID, (err, clan) => + return @sendDatabaseError(res, err) if err + return @sendForbiddenError(res) unless clan.get('ownerID')?.equals req.user._id + return @sendForbiddenError(res) if clan.get('ownerID').equals memberID + Clan.update {_id: clanID}, {$pull: {members: {id: memberID}}}, (err) => + return @sendDatabaseError(res, err) if err + @sendSuccess(res) + module.exports = new ClanHandler() diff --git a/test/server/functional/clan.spec.coffee b/test/server/functional/clan.spec.coffee index 8e7499c3a..1b77f4e58 100644 --- a/test/server/functional/clan.spec.coffee +++ b/test/server/functional/clan.spec.coffee @@ -163,3 +163,78 @@ describe 'Clans', -> expect(err).toBeNull() expect(res.statusCode).toBe(403) done() + + it 'Remove member', (done) -> + loginNewUser (user1) -> + createClan 'public', (clan1) -> + loginNewUser (user2) -> + request.put {uri: "#{clanURL}/#{clan1.id}/join" }, (err, res, body) -> + expect(err).toBeNull() + expect(res.statusCode).toBe(200) + loginUser user1, (user1) -> + request.put {uri: "#{clanURL}/#{clan1.id}/remove/#{user2.id}" }, (err, res, body) -> + expect(err).toBeNull() + expect(res.statusCode).toBe(200) + Clan.findById clan1.id, (err, clan1) -> + expect(err).toBeNull() + expect(clan1.get('members').length).toEqual(1) + expect(clan1.get('members')[0].id).toEqual(user1.get('_id')) + done() + + it 'Remove non-member 200', (done) -> + loginNewUser (user2) -> + loginNewUser (user1) -> + createClan 'public', (clan1) -> + request.put {uri: "#{clanURL}/#{clan1.id}/remove/#{user2.id}" }, (err, res, body) -> + expect(err).toBeNull() + expect(res.statusCode).toBe(200) + Clan.findById clan1.id, (err, clan1) -> + expect(err).toBeNull() + expect(clan1.get('members').length).toEqual(1) + expect(clan1.get('members')[0].id).toEqual(user1.get('_id')) + done() + + it 'Remove invalid memberID 422', (done) -> + loginNewUser (user1) -> + createClan 'public', (clan1) -> + request.put {uri: "#{clanURL}/#{clan1.id}/remove/123" }, (err, res, body) -> + expect(err).toBeNull() + expect(res.statusCode).toBe(422) + done() + + it 'Remove member, not in clan 403', (done) -> + loginNewUser (user1) -> + createClan 'public', (clan1) -> + loginNewUser (user2) -> + request.put {uri: "#{clanURL}/#{clan1.id}/join" }, (err, res, body) -> + expect(err).toBeNull() + expect(res.statusCode).toBe(200) + loginNewUser (user3) -> + request.put {uri: "#{clanURL}/#{clan1.id}/remove/#{user2.id}" }, (err, res, body) -> + expect(err).toBeNull() + expect(res.statusCode).toBe(403) + done() + + it 'Remove member, not the owner 403', (done) -> + loginNewUser (user1) -> + createClan 'public', (clan1) -> + loginNewUser (user2) -> + request.put {uri: "#{clanURL}/#{clan1.id}/join" }, (err, res, body) -> + expect(err).toBeNull() + expect(res.statusCode).toBe(200) + loginNewUser (user3) -> + request.put {uri: "#{clanURL}/#{clan1.id}/join" }, (err, res, body) -> + expect(err).toBeNull() + expect(res.statusCode).toBe(200) + request.put {uri: "#{clanURL}/#{clan1.id}/remove/#{user2.id}" }, (err, res, body) -> + expect(err).toBeNull() + expect(res.statusCode).toBe(403) + done() + + it 'Remove member from owned clan', (done) -> + loginNewUser (user1) -> + createClan 'public', (clan1) -> + request.put {uri: "#{clanURL}/#{clan1.id}/remove/#{user1.id}" }, (err, res, body) -> + expect(err).toBeNull() + expect(res.statusCode).toBe(403) + done()