diff --git a/migrations/20131029_add_invites_to_groups.js b/migrations/20131029_add_invites_to_groups.js index 63947c1e23..30575a5c99 100644 --- a/migrations/20131029_add_invites_to_groups.js +++ b/migrations/20131029_add_invites_to_groups.js @@ -17,15 +17,9 @@ db.users.find().forEach(function(user){ }); _.each(groups, function(usersInvited, groupId){ - var group = db.groups.findOne({_id: groupId}); - - if(group){ - group.invites = usersInvited; - - try { - db.groups.update({_id: groupId}, group); - } catch(e) { - print(e); - } - }; + try { + db.groups.update({_id: groupId}, {$set: {'group.invites': usersInvited} }); + } catch(e) { + print(e); + } }); diff --git a/public/js/app.js b/public/js/app.js index b6a5e14a97..516379a444 100644 --- a/public/js/app.js +++ b/public/js/app.js @@ -110,16 +110,23 @@ window.habitrpg = angular.module('habitrpg', .state('options.challenges', { url: "/challenges", controller: 'ChallengesCtrl', - templateUrl: "partials/options.challenges.html", - resolve: { - groups: ['$http', 'API_URL', function($http, API_URL){ - // TODO come up with more unified ngResource-style approach - return $http.get(API_URL + '/api/v1/groups?minimal=true'); - }], - challenges: ['Challenges', function(Challenges){ - return Challenges.Challenge.query(); - }] - } + templateUrl: "partials/options.challenges.html" + }) + .state('options.challenges.detail', { + url: '/:cid', + templateUrl: 'partials/options.challenges.detail.html', +// resolve: { +// challenge: ['$scope', 'Challenges', '$stateParams', '$q', +// function($scope, Challenges, $stateParams, $q){ +// // FIXME does ui-router not work with ng-resource by default? +// var challenge = $q.defer(); +// Challenges.Challenge.get({cid:$stateParams.cid}, function(_challenge){ +// challenge._locked = true; +// challenge.resolve(_challenge); +// }); +// return challenge.promise; +// }] +// } }) // Options > Settings diff --git a/public/js/controllers/challengesCtrl.js b/public/js/controllers/challengesCtrl.js index 264bc1c5f9..d6608ce581 100644 --- a/public/js/controllers/challengesCtrl.js +++ b/public/js/controllers/challengesCtrl.js @@ -1,11 +1,16 @@ "use strict"; -habitrpg.controller("ChallengesCtrl", ['$scope', 'User', 'Challenges', 'Notification', '$compile', 'groups', 'challenges', - function($scope, User, Challenges, Notification, $compile, groups, challenges) { +habitrpg.controller("ChallengesCtrl", ['$scope', 'User', 'Challenges', 'Notification', '$compile', 'Groups', + function($scope, User, Challenges, Notification, $compile, Groups) { - // groups & challenges are loaded as `resolve` via ui-router (see app.js) - $scope.groups = groups; - $scope.challenges = challenges; + // FIXME get this from cache + Groups.Group.query(function(groups){ + groups.tavern[0].name = 'Tavern'; + $scope.groups = groups.party.concat(groups.guilds).concat(groups.tavern); + }); + // FIXME $scope.challenges needs to be resolved first (see app.js) + $scope.challenges = Challenges.Challenge.query(); + // we should fix this, that's pretty brittle //------------------------------------------------------------ // Challenge diff --git a/src/controllers/challenges.js b/src/controllers/challenges.js index 4e167ce39c..6aed897abb 100644 --- a/src/controllers/challenges.js +++ b/src/controllers/challenges.js @@ -17,26 +17,39 @@ var api = module.exports; ------------------------------------------------------------------------ */ +api.list = function(req, res) { + var user = res.locals.user; + Challenge.find({ + $or:[ + {leader: user._id}, + {members:{$in:[user._id]}}, + {group: 'habitrpg'} + ] + }) + .select('name description memberCount groups') + .populate('groups', '_id name') + .exec(function(err, challenges){ + if (err) return res.json(500,{err:err}); + res.json(challenges); + }); +} + // GET api.get = function(req, res) { var user = res.locals.user; - Challenge.find({$or:[{leader: user._id}, {members:{$in:[user._id]}}]}) + Challenge.findById(req.params.cid) .populate('members', 'profile.name habits dailys rewards todos') - .exec(function(err, challenges){ + .exec(function(err, challenge){ if(err) return res.json(500, {err:err}); - // slim down the return members' tasks to only the ones in the challenge - _.each(challenges, function(challenge){ - _.each(challenge.members, function(member){ - _.each(['habits', 'dailys', 'todos', 'rewards'], function(type){ - member[type] = _.where(member[type], function(task){ - return task.challenge && task.challenge.id == challenge._id; - }) + _.each(challenge.members, function(member){ + _.each(['habits', 'dailys', 'todos', 'rewards'], function(type){ + member[type] = _.where(member[type], function(task){ + return task.challenge && task.challenge.id == challenge._id; }) }) }); - - res.json(challenges); + res.json(challenge); }) } diff --git a/src/controllers/groups.js b/src/controllers/groups.js index 2b50791e43..3bf3473b94 100644 --- a/src/controllers/groups.js +++ b/src/controllers/groups.js @@ -36,7 +36,7 @@ api.getMember = function(req, res) { * Fetch groups list. This no longer returns party or tavern, as those can be requested indivdually * as /groups/party or /groups/tavern */ -api.getGroups = function(req, res) { +api.list = function(req, res) { var user = res.locals.user; var groupFields = 'name description memberCount'; var sort = '-memberCount'; @@ -45,7 +45,11 @@ api.getGroups = function(req, res) { // unecessary given our ui-router setup party: function(cb){ - return cb(null, [{}]); + Group.findOne({type: 'party', members: {'$in': [user._id]}}) + .select(groupFields).exec(function(err, party){ + if (err) return cb(err); + cb(null, [party]); // return as an array for consistent ngResource use + }); }, guilds: function(cb) { @@ -70,7 +74,10 @@ api.getGroups = function(req, res) { // unecessary given our ui-router setup tavern: function(cb) { - return cb(null, [{}]); + Group.findById('habitrpg').select(groupFields).exec(function(err, tavern){ + if (err) return cb(err); + cb(null, [tavern]); // return as an array for consistent ngResource use + }); } }, function(err, results){ @@ -83,7 +90,7 @@ api.getGroups = function(req, res) { * Get group * TODO: implement requesting fields ?fields=chat,members */ -api.getGroup = function(req, res) { +api.get = function(req, res) { var user = res.locals.user; var gid = req.params.gid; @@ -111,7 +118,7 @@ api.getGroup = function(req, res) { }; -api.createGroup = function(req, res, next) { +api.create = function(req, res, next) { var group = new Group(req.body); var user = res.locals.user; @@ -136,7 +143,7 @@ api.createGroup = function(req, res, next) { } } -api.updateGroup = function(req, res, next) { +api.update = function(req, res, next) { var group = res.locals.group; var user = res.locals.user; diff --git a/src/models/challenge.js b/src/models/challenge.js index 4971dcc992..fb77bd1bba 100644 --- a/src/models/challenge.js +++ b/src/models/challenge.js @@ -19,9 +19,8 @@ var ChallengeSchema = new Schema({ //id: group._id //}, timestamp: {type: Date, 'default': Date.now}, - members: [{type: String, ref: 'User'}] -}, { - minimize: 'false' + members: [{type: String, ref: 'User'}], + memberCount: [{type: Number, 'default': 0}] }); ChallengeSchema.virtual('tasks').get(function () { @@ -30,5 +29,10 @@ ChallengeSchema.virtual('tasks').get(function () { return tasks; }); +ChallengeSchema.pre('save', function(next){ + this.memberCount = _.size(this.members); + next(); +}) + module.exports.schema = ChallengeSchema; module.exports.model = mongoose.model("Challenge", ChallengeSchema); \ No newline at end of file diff --git a/src/routes/api.js b/src/routes/api.js index 0018f9d8cb..8983da231a 100644 --- a/src/routes/api.js +++ b/src/routes/api.js @@ -58,10 +58,10 @@ router['delete']('/user', auth.auth, user['delete']); router['delete']('/user/tags/:tid', auth.auth, user.deleteTag); /* Groups*/ -router.get('/groups', auth.auth, groups.getGroups); -router.post('/groups', auth.auth, groups.createGroup); -router.get('/groups/:gid', auth.auth, groups.getGroup); -router.post('/groups/:gid', auth.auth, groups.attachGroup, groups.updateGroup); +router.get('/groups', auth.auth, groups.list); +router.post('/groups', auth.auth, groups.create); +router.get('/groups/:gid', auth.auth, groups.get); +router.post('/groups/:gid', auth.auth, groups.attachGroup, groups.update); //DELETE /groups/:gid router.post('/groups/:gid/join', auth.auth, groups.attachGroup, groups.join); @@ -84,8 +84,9 @@ router.post('/market/buy', auth.auth, user.marketBuy); // Note: while challenges belong to groups, and would therefore make sense as a nested resource // (eg /groups/:gid/challenges/:cid), they will also be referenced by users from the "challenges" tab // without knowing which group they belong to. So to prevent unecessary lookups, we have them as a top-level resource -router.get('/challenges', auth.auth, challenges.get) +router.get('/challenges', auth.auth, challenges.list) router.post('/challenges', auth.auth, challenges.create) +router.get('/challenges/:cid', auth.auth, challenges.get) router.post('/challenges/:cid', auth.auth, challenges.update) router['delete']('/challenges/:cid', auth.auth, challenges['delete']) router.post('/challenges/:cid/join', auth.auth, challenges.join) diff --git a/views/options/challenges.jade b/views/options/challenges.jade index 2abca59c4d..5c56c6c6b5 100644 --- a/views/options/challenges.jade +++ b/views/options/challenges.jade @@ -1,3 +1,27 @@ +script(type='text/ng-template', id='partials/options.challenges.detail.html') + // Edit button + ul.unstyled() + li(ng-show='challenge.leader==user._id && challenge._locked') + button.btn.btn-default(ng-click='challenge._locked = false') Edit + li(ng-hide='challenge._locked') + button.btn.btn-primary(ng-click='save(challenge)') Save + button.btn.btn-danger(ng-click='delete(challenge)') Delete + button.btn.btn-default(ng-click='challenge._locked=true') Cancel + + div(ng-hide='challenge._locked') + .-options + input.option-content(type='text', ng-model='challenge.name') + textarea.option-content(cols='3', placeholder='Description', ng-model='challenge.description') + // + hr + + div(ng-if='challenge.description') {{challenge.description}} + habitrpg-tasks(obj='challenge', main=false) + h3 Statistics + div(ng-repeat='member in challenge.members', ng-init='member._locked = true') + h4 {{member.profile.name}} + habitrpg-tasks(main=false, obj='member') + script(type='text/ng-template', id='partials/options.challenges.html') .row-fluid .span2.well @@ -47,28 +71,7 @@ script(type='text/ng-template', id='partials/options.challenges.html') a.btn.btn-small.btn-success(ng-hide='indexOf(challenge.members, user._id)', ng-click='challenge.$join()') i.icon-ok | Subscribe - a.accordion-toggle(data-toggle='collapse', data-target='#accordion-challenge-{{challenge._id}}') {{challenge.name}} (by {{challenge.leader.name}}) - .accordion-body.collapse(id='accordion-challenge-{{challenge._id}}') - .accordion-inner - // Edit button - ul.unstyled() - li(ng-show='challenge.leader==user._id && challenge._locked') - button.btn.btn-default(ng-click='challenge._locked = false') Edit - li(ng-hide='challenge._locked') - button.btn.btn-primary(ng-click='save(challenge)') Save - button.btn.btn-danger(ng-click='delete(challenge)') Delete - button.btn.btn-default(ng-click='challenge._locked=true') Cancel - - div(ng-hide='challenge._locked') - .-options - input.option-content(type='text', ng-model='challenge.name') - textarea.option-content(cols='3', placeholder='Description', ng-model='challenge.description') - // - hr - - div(ng-if='challenge.description') {{challenge.description}} - habitrpg-tasks(obj='challenge', main=false) - h3 Statistics - div(ng-repeat='member in challenge.members', ng-init='member._locked = true') - h4 {{member.profile.name}} - habitrpg-tasks(main=false, obj='member') \ No newline at end of file + a.accordion-toggle(ui-sref='options.challenges.detail({cid:challenge._id})') {{challenge.name}} (by {{challenge.leader.name}}) + .accordion-body(ng-class='{collapse: !$stateParams.cid == challenge._id}') + .accordion-inner(ng-if='$stateParams.cid == challenge._id') + div(ui-view) \ No newline at end of file