Normalize clan members data

This commit is contained in:
Matt Lott 2015-04-02 17:00:28 -07:00
parent df120bdea3
commit 56342ad993
9 changed files with 149 additions and 74 deletions

View file

@ -8,11 +8,7 @@ _.extend ClanSchema.properties,
type: {type: 'string', 'enum': ['public']}
ownerID: c.objectId()
ownerName: c.shortString()
members: c.array {title: 'Members'},
c.object {required: ['id', 'name', 'level']},
id: c.objectId()
name: c.shortString()
level: {type: 'integer'}
members: c.array {title: 'Members'}, c.objectId()
c.extendBasicProperties ClanSchema, 'Clan'

View file

@ -306,6 +306,8 @@ _.extend UserSchema.properties,
referrer: { type: 'string' }
chinaVersion: { type: 'boolean' }
clans: c.array {}, c.objectId()
c.extendBasicProperties UserSchema, 'user'
UserSchema.definitions =

View file

@ -23,11 +23,11 @@ block content
th Level
th
tbody
each member in clan.get('members')
each member in members
tr
td
a(href="/user/#{member.id}")= member.name
td= member.level
a(href="/user/#{member.id}")= member.get('name') || 'Anoner'
td= member.level()
if isOwner && member.id !== clan.get('ownerID')
td
button.btn.btn-sm.btn-warning.remove-member-btn(data-id="#{member.id}") Remove Member

View file

@ -2,7 +2,9 @@ app = require 'core/application'
AuthModal = require 'views/core/AuthModal'
RootView = require 'views/core/RootView'
template = require 'templates/clans/clan-details'
CocoCollection = require 'collections/CocoCollection'
Clan = require 'models/Clan'
User = require 'models/User'
# TODO: Message for clan not found
# TODO: join/leave mostly duped from clans view
@ -20,13 +22,22 @@ module.exports = class ClanDetailsView extends RootView
constructor: (options, @clanID) ->
super options
@clan = new Clan _id: @clanID
@listenTo @clan, 'sync', => @render?()
@supermodel.loadModel @clan, 'clan', cache: false
@members = new CocoCollection([], { url: "/db/clan/#{clanID}/members", model: User, comparator:'_id' })
@listenTo @members, 'sync', => @render?()
@supermodel.loadCollection(@members, 'members', {cache: false})
@listenTo me, 'sync', => @render?()
getRenderData: =>
destroy: ->
@stopListening?()
getRenderData: ->
context = super()
context.clan = @clan
context.members = @members.models
context.isOwner = @clan.get('ownerID') is me.id
context.isMember = _.find(@clan.get('members'), (m) -> m.id is me.id) ? false
context.isMember = @clanID in me.get('clans')
context
onDeleteClan: (e) ->
@ -49,9 +60,8 @@ module.exports = class ClanDetailsView extends RootView
error: (model, response, options) =>
console.error 'Error joining clan', response
success: (model, response, options) =>
@listenToOnce @clan, 'sync', =>
@render?()
@clan.fetch cache: false
me.fetch cache: false
@members.fetch cache: false
@supermodel.addRequestResource( 'join_clan', options).load()
onLeaveClan: (e) ->
@ -61,9 +71,8 @@ module.exports = class ClanDetailsView extends RootView
error: (model, response, options) =>
console.error 'Error leaving clan', response
success: (model, response, options) =>
@listenToOnce @clan, 'sync', =>
@render?()
@clan.fetch cache: false
me.fetch cache: false
@members.fetch cache: false
@supermodel.addRequestResource( 'leave_clan', options).load()
onRemoveMember: (e) ->
@ -74,9 +83,7 @@ module.exports = class ClanDetailsView extends RootView
error: (model, response, options) =>
console.error 'Error removing clan member', response
success: (model, response, options) =>
@listenToOnce @clan, 'sync', =>
@render?()
@clan.fetch cache: false
@members.fetch cache: false
@supermodel.addRequestResource( 'remove_member', options).load()
else
console.error "No member ID attached to remove button."

View file

@ -21,15 +21,21 @@ module.exports = class MainAdminView extends RootView
constructor: (options) ->
super options
@publicClans = new CocoCollection([], { url: '/db/clan', model: Clan, comparator:'_id' })
@listenTo @publicClans, 'sync', => @render?()
@supermodel.loadCollection(@publicClans, 'public_clans', {cache: false})
@myClans = new CocoCollection([], { url: '/db/user/-/clans', model: Clan, comparator:'_id' })
@listenTo @myClans, 'sync', => @render?()
@supermodel.loadCollection(@myClans, 'my_clans', {cache: false})
@listenTo me, 'sync', => @render?()
destroy: ->
@stopListening?()
getRenderData: ->
context = super()
context.publicClans = @publicClans.models
context.myClans = _.filter @publicClans.models, (c) ->
return true if c.ownerID is me.get('_id')
return true for member in c.get('members') when me.get('_id') is member.id
context.myClanIDs = _.map context.myClans, (c) -> c.id
context.myClans = @myClans.models
context.myClanIDs = me.get('clans') ? []
context
onClickCreateClan: (e) ->
@ -70,7 +76,9 @@ module.exports = class MainAdminView extends RootView
error: (model, response, options) =>
console.error 'Error leaving clan', response
success: (model, response, options) =>
window.location.reload()
me.fetch cache: false
@publicClans.fetch cache: false
@myClans.fetch cache: false
@supermodel.addRequestResource( 'leave_clan', options).load()
else
console.error "No clan ID attached to leave button."

View file

@ -2,10 +2,18 @@ mongoose = require 'mongoose'
log = require 'winston'
config = require '../../server_config'
plugins = require '../plugins/plugins'
User = require '../users/User'
jsonSchema = require '../../app/schemas/models/clan.schema'
ClanSchema = new mongoose.Schema {}, {strict: false, minimize: false, read:config.mongo.readpref}
ClanSchema.pre 'save', (next) ->
User.update {_id: @get('ownerID')}, {$addToSet: {clans: @get('_id')}}, (err) =>
if err
log.error err
return next(err)
next()
ClanSchema.statics.privateProperties = []
ClanSchema.statics.editableProperties = [
'type'

View file

@ -2,6 +2,8 @@ async = require 'async'
mongoose = require 'mongoose'
Handler = require '../commons/Handler'
Clan = require './Clan'
User = require '../users/User'
UserHandler = require '../users/user_handler'
ClanHandler = class ClanHandler extends Handler
modelClass: Clan
@ -25,9 +27,7 @@ ClanHandler = class ClanHandler extends Handler
instance = super(req)
instance.set 'ownerID', req.user._id
instance.set 'ownerName', userName
instance.set 'members', [
{id: req.user._id, name: userName, level: req.user.level()}
]
instance.set 'members', [req.user._id]
instance
delete: (req, res, clanID) ->
@ -35,36 +35,59 @@ ClanHandler = class ClanHandler extends Handler
return @sendDatabaseError res, err if err
return @sendNotFoundError res unless clan
return @sendForbiddenError res unless @hasAccessToDocument(req, clan)
memberIDs = clan.get('members')
Clan.remove {_id: clan.get('_id')}, (err) =>
return @sendDatabaseError res, err if err
@sendNoContent(res)
User.update {_id: {$in: memberIDs}}, {$pull: {clans: clan.get('_id')}}, {multi: true}, (err) =>
return @sendDatabaseError(res, err) if err
@sendNoContent(res)
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 @getMembers(req, res, args[0]) if args[1] is 'members'
return @removeMember(req, res, args[0], args[2]) if args.length is 3 and args[1] is 'remove'
super(arguments...)
joinClan: (req, res, clanID) ->
return @sendForbiddenError(res) unless req.user? and not req.user.isAnonymous()
try
clanID = mongoose.Types.ObjectId(clanID)
catch err
return @sendNotFoundError(res, err)
Clan.findById clanID, (err, clan) =>
return @sendSuccess(res, clan) if _.find clan.get('members'), (m) -> m.id.equals req.user.id
member =
id: req.user._id
name: req.user.get('name') ? 'Anoner'
level: req.user.level()
Clan.update {_id: clanID}, {$push: {members: member}}, (err) =>
return @sendDatabaseError(res, err) if err
Clan.update {_id: clanID}, {$addToSet: {members: req.user._id}}, (err) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res)
User.update {_id: req.user._id}, {$addToSet: {clans: clanID}}, (err) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res)
leaveClan: (req, res, clanID) ->
return @sendForbiddenError(res) unless req.user? and not req.user.isAnonymous()
try
clanID = mongoose.Types.ObjectId(clanID)
catch err
return @sendNotFoundError(res, err)
Clan.findById clanID, (err, clan) =>
return @sendDatabaseError(res, err) if err
return @sendForbiddenError(res) if clan.get('ownerID')?.equals req.user._id
Clan.update {_id: clanID}, {$pull: {members: {id: req.user._id}}}, (err) =>
Clan.update {_id: clanID}, {$pull: {members: req.user._id}}, (err) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res)
User.update {_id: req.user._id}, {$pull: {clans: clanID}}, (err) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res)
getMembers: (req, res, clanID) ->
return @sendForbiddenError(res) unless req.user
clanIDs = req.user.get('clans') ? []
Clan.findById clanID, (err, clans) =>
return @sendDatabaseError(res, err) if err
memberIDs = clans.get('members') ? []
User.find {_id: {$in: memberIDs}}, (err, users) =>
return @sendDatabaseError(res, err) if err
cleandocs = (UserHandler.formatEntity(req, doc) for doc in users)
@sendSuccess(res, cleandocs)
removeMember: (req, res, clanID, memberID) ->
return @sendForbiddenError(res) unless req.user? and not req.user.isAnonymous()
@ -77,8 +100,10 @@ ClanHandler = class ClanHandler extends Handler
return @sendDatabaseError(res, err) if err
return @sendForbiddenError res unless @hasAccessToDocument(req, clan)
return @sendForbiddenError(res) if clan.get('ownerID').equals memberID
Clan.update {_id: clanID}, {$pull: {members: {id: memberID}}}, (err) =>
Clan.update {_id: clanID}, {$pull: {members: memberID}}, (err) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res)
User.update {_id: memberID}, {$pull: {clans: clanID}}, (err) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res)
module.exports = new ClanHandler()

View file

@ -10,6 +10,7 @@ async = require 'async'
log = require 'winston'
moment = require 'moment'
AnalyticsLogEvent = require '../analytics/AnalyticsLogEvent'
Clan = require '../clans/Clan'
LevelSession = require '../levels/sessions/LevelSession'
LevelSessionHandler = require '../levels/sessions/level_session_handler'
SubscriptionHandler = require '../payments/subscription_handler'
@ -262,6 +263,7 @@ UserHandler = class UserHandler extends Handler
return @getLevelSessionsForEmployer(req, res, args[0]) if args[1] is 'level.sessions' and args[2] is 'employer'
return @getLevelSessions(req, res, args[0]) if args[1] is 'level.sessions'
return @getCandidates(req, res) if args[1] is 'candidates'
return @getClans(req, res) if args[1] is 'clans'
return @getEmployers(req, res) if args[1] is 'employers'
return @getSimulatorLeaderboard(req, res, args[0]) if args[1] is 'simulatorLeaderboard'
return @getMySimulatorLeaderboardRank(req, res, args[0]) if args[1] is 'simulator_leaderboard_rank'
@ -539,6 +541,13 @@ UserHandler = class UserHandler extends Handler
candidates = (@formatCandidate(authorized, candidate) for candidate in candidates)
@sendSuccess(res, candidates)
getClans: (req, res) ->
return @sendForbiddenError(res) unless req.user
clanIDs = req.user.get('clans') ? []
Clan.find {_id: {$in: clanIDs}}, (err, documents) =>
return @sendDatabaseError(res, err) if err
@sendSuccess(res, documents)
formatCandidate: (authorized, document) ->
fields = if authorized then ['name', 'jobProfile', 'jobProfileApproved', 'photoURL', '_id'] else ['_id','jobProfile', 'jobProfileApproved']
obj = _.pick document.toObject(), fields

View file

@ -10,7 +10,7 @@ describe 'Clans', ->
clanCount = 0
createClanName = (name) -> name + clanCount++
createClan = (type, done) ->
createClan = (user, type, done) ->
name = createClanName 'myclan'
requestBody =
type: type
@ -23,7 +23,13 @@ describe 'Clans', ->
Clan.findById body._id, (err, clan) ->
expect(clan.get('type')).toEqual(type)
expect(clan.get('name')).toEqual(name)
done(clan)
expect(clan.get('members')?.length).toEqual(1)
expect(clan.get('members')?[0]).toEqual(user._id)
User.findById user.id, (err, user) ->
expect(err).toBeNull()
expect(user.get('clans')?.length).toBeGreaterThan(0)
expect(_.find user.get('clans'), (clanID) -> clan._id.equals clanID).toBeDefined()
done(clan)
it 'Clear database users and clans', (done) ->
clearModels [User, Clan], (err) ->
@ -32,7 +38,7 @@ describe 'Clans', ->
it 'Create clan', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan) ->
createClan user1, 'public', (clan) ->
done()
it 'Anonymous create clan 401', (done) ->
@ -65,8 +71,8 @@ describe 'Clans', ->
it 'Get clans', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan 'public', (clan2) ->
createClan user1, 'public', (clan1) ->
createClan user1, 'public', (clan2) ->
request.get {uri: clanURL }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
@ -75,8 +81,8 @@ describe 'Clans', ->
it 'Get clans anonymous', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan 'public', (clan2) ->
createClan user1, 'public', (clan1) ->
createClan user1, 'public', (clan2) ->
logoutUser ->
request.get {uri: clanURL }, (err, res, body) ->
expect(err).toBeNull()
@ -86,19 +92,24 @@ describe 'Clans', ->
it 'Join clan', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, 'public', (clan1) ->
loginNewUser (user2) ->
request.put {uri: "#{clanURL}/#{clan1.id}/join" }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
Clan.findById clan1.id, (err, clan1) ->
expect(err).toBeNull()
expect(_.find clan1.get('members'), (m) -> m.id.equals user2.id).toBeDefined()
done()
expect(clan1.get('members')?.length).toEqual(2)
expect(_.find clan1.get('members'), (memberID) -> user2._id.equals memberID).toBeDefined()
User.findById user2.id, (err, user2) ->
expect(err).toBeNull()
expect(user2.get('clans')?.length).toBeGreaterThan(0)
expect(_.find user2.get('clans'), (clanID) -> clan1._id.equals clanID).toBeDefined()
done()
it 'Join invalid clan 404', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, 'public', (clan1) ->
loginNewUser (user2) ->
request.put {uri: "#{clanURL}/1234/join" }, (err, res, body) ->
expect(err).toBeNull()
@ -107,7 +118,7 @@ describe 'Clans', ->
it 'Join clan anonymous 401', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, 'public', (clan1) ->
logoutUser ->
request.put {uri: "#{clanURL}/#{clan1.id}/join" }, (err, res, body) ->
expect(err).toBeNull()
@ -116,14 +127,14 @@ describe 'Clans', ->
it 'Join clan twice 200', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, 'public', (clan1) ->
loginNewUser (user2) ->
request.put {uri: "#{clanURL}/#{clan1.id}/join" }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
Clan.findById clan1.id, (err, clan1) ->
expect(err).toBeNull()
expect(_.find clan1.get('members'), (m) -> m.id.equals user2.id).toBeDefined()
expect(_.find clan1.get('members'), (memberID) -> memberID.equals user2.id).toBeDefined()
request.put {uri: "#{clanURL}/#{clan1.id}/join" }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
@ -131,7 +142,7 @@ describe 'Clans', ->
it 'Leave clan', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, 'public', (clan1) ->
loginNewUser (user2) ->
request.put {uri: "#{clanURL}/#{clan1.id}/join" }, (err, res, body) ->
expect(err).toBeNull()
@ -141,24 +152,27 @@ describe 'Clans', ->
expect(res.statusCode).toBe(200)
Clan.findById clan1.id, (err, clan1) ->
expect(err).toBeNull()
expect(_.find clan1.get('members'), (m) -> m.id.equals user2.id).toBeUndefined()
done()
expect(_.find clan1.get('members'), (memberID) -> memberID.equals user2.id).toBeUndefined()
User.findById user2.id, (err, user2) ->
expect(err).toBeNull()
expect(user2.get('clans').length).toEqual(0)
done()
it 'Leave clan not member 200', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, 'public', (clan1) ->
loginNewUser (user2) ->
request.put {uri: "#{clanURL}/#{clan1.id}/leave" }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(200)
Clan.findById clan1.id, (err, clan1) ->
expect(err).toBeNull()
expect(_.find clan1.get('members'), (m) -> m.id.equals user2.id).toBeUndefined()
expect(_.find clan1.get('members'), (memberID) -> memberID.equals user2.id).toBeUndefined()
done()
it 'Leave owned clan 403', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, 'public', (clan1) ->
request.put {uri: "#{clanURL}/#{clan1.id}/leave" }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
@ -166,7 +180,7 @@ describe 'Clans', ->
it 'Remove member', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, 'public', (clan1) ->
loginNewUser (user2) ->
request.put {uri: "#{clanURL}/#{clan1.id}/join" }, (err, res, body) ->
expect(err).toBeNull()
@ -178,25 +192,28 @@ describe 'Clans', ->
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()
expect(clan1.get('members')[0]).toEqual(user1.get('_id'))
User.findById user2.id, (err, user2) ->
expect(err).toBeNull()
expect(user2.get('clans').length).toEqual(0)
done()
it 'Remove non-member 200', (done) ->
loginNewUser (user2) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, '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'))
expect(clan1.get('members')[0]).toEqual(user1.get('_id'))
done()
it 'Remove invalid memberID 404', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, 'public', (clan1) ->
request.put {uri: "#{clanURL}/#{clan1.id}/remove/123" }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(404)
@ -204,7 +221,7 @@ describe 'Clans', ->
it 'Remove member, not in clan 403', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, 'public', (clan1) ->
loginNewUser (user2) ->
request.put {uri: "#{clanURL}/#{clan1.id}/join" }, (err, res, body) ->
expect(err).toBeNull()
@ -217,7 +234,7 @@ describe 'Clans', ->
it 'Remove member, not the owner 403', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, 'public', (clan1) ->
loginNewUser (user2) ->
request.put {uri: "#{clanURL}/#{clan1.id}/join" }, (err, res, body) ->
expect(err).toBeNull()
@ -231,9 +248,9 @@ describe 'Clans', ->
expect(res.statusCode).toBe(403)
done()
it 'Remove member from owned clan', (done) ->
it 'Remove member from owned clan 403', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan1) ->
createClan user1, 'public', (clan1) ->
request.put {uri: "#{clanURL}/#{clan1.id}/remove/#{user1.id}" }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(403)
@ -241,15 +258,18 @@ describe 'Clans', ->
it 'Delete clan', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan) ->
createClan user1, 'public', (clan) ->
request.del {uri: "#{clanURL}/#{clan.id}" }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(204)
done()
User.findById user1.id, (err, user1) ->
expect(err).toBeNull()
expect(user1.get('clans').length).toEqual(0)
done()
it 'Delete clan anonymous 401', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan) ->
createClan user1, 'public', (clan) ->
logoutUser ->
request.del {uri: "#{clanURL}/#{clan.id}" }, (err, res, body) ->
expect(err).toBeNull()
@ -258,7 +278,7 @@ describe 'Clans', ->
it 'Delete clan not owner 403', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan) ->
createClan user1, 'public', (clan) ->
loginNewUser (user2) ->
request.del {uri: "#{clanURL}/#{clan.id}" }, (err, res, body) ->
expect(err).toBeNull()
@ -267,7 +287,7 @@ describe 'Clans', ->
it 'Delete clan no longer exists 404', (done) ->
loginNewUser (user1) ->
createClan 'public', (clan) ->
createClan user1, 'public', (clan) ->
request.del {uri: "#{clanURL}/#{clan.id}" }, (err, res, body) ->
expect(err).toBeNull()
expect(res.statusCode).toBe(204)