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