diff --git a/public/js/controllers/challengesCtrl.js b/public/js/controllers/challengesCtrl.js
index c8989b1c35..9655cd0b6b 100644
--- a/public/js/controllers/challengesCtrl.js
+++ b/public/js/controllers/challengesCtrl.js
@@ -1,7 +1,7 @@
"use strict";
-habitrpg.controller("ChallengesCtrl", ['$scope', 'User', 'Challenges', 'Notification', '$compile', 'Groups',
- function($scope, User, Challenges, Notification, $compile, Groups) {
+habitrpg.controller("ChallengesCtrl", ['$scope', 'User', 'Challenges', 'Notification', '$compile', 'Groups', '$state',
+ function($scope, User, Challenges, Notification, $compile, Groups, $state) {
// FIXME get this from cache
Groups.Group.query(function(groups){
@@ -20,7 +20,7 @@ habitrpg.controller("ChallengesCtrl", ['$scope', 'User', 'Challenges', 'Notifica
* Create
*/
$scope.create = function() {
- $scope.newChallenge = new Challenges.Challenge({
+ $scope.obj = $scope.newChallenge = new Challenges.Challenge({
name: '',
description: '',
habits: [],
@@ -40,11 +40,11 @@ habitrpg.controller("ChallengesCtrl", ['$scope', 'User', 'Challenges', 'Notifica
$scope.save = function(challenge) {
if (!challenge.group) return alert('Please select group');
var isNew = !challenge._id;
- challenge.$save(function(){
+ challenge.$save(function(_challenge){
if (isNew) {
Notification.text('Challenge Created');
$scope.discard();
- Challenges.Challenge.query();
+ $scope.challenges.unshift(_challenge);
} else {
// TODO figure out a more elegant way about this
//challenge._editing = false;
@@ -64,9 +64,13 @@ habitrpg.controller("ChallengesCtrl", ['$scope', 'User', 'Challenges', 'Notifica
/**
* Delete
*/
- $scope["delete"] = function(challenge) {
+ $scope["delete"] = function(challenge, $index) {
if (confirm("Delete challenge, are you sure?") !== true) return;
- challenge.$delete();
+ challenge.$delete(function(){
+ $state.go('options.challenges');
+ $scope.challenges = Challenges.Challenge.query();
+ User.log({});
+ });
};
//------------------------------------------------------------
@@ -97,29 +101,43 @@ habitrpg.controller("ChallengesCtrl", ['$scope', 'User', 'Challenges', 'Notifica
/*
--------------------------
- Unsubscribe functions
+ Subscription
--------------------------
*/
- $scope.unsubscribe = function(keep) {
+ $scope.join = function(challenge){
+ challenge.$join(function(){
+ User.log({});
+ });
+
+ }
+
+ $scope.leave = function(keep) {
if (keep == 'cancel') {
$scope.selectedChal = undefined;
} else {
- $scope.selectedChal.$leave({keep:keep});
+ $scope.selectedChal.$leave({keep:keep}, function(){
+ User.log({});
+ });
}
$scope.popoverEl.popover('destroy');
}
- $scope.clickUnsubscribe = function(chal, $event) {
+
+ /**
+ * Named "clickLeave" to distinguish between "actual" leave above, since this triggers the
+ * "are you sure?" dialog.
+ */
+ $scope.clickLeave = function(chal, $event) {
$scope.selectedChal = chal;
$scope.popoverEl = $($event.target);
var html = $compile(
- 'Remove Tasks
\nKeep Tasks
\nCancel
'
+ 'Remove Tasks
\nKeep Tasks
\nCancel
'
)($scope);
$scope.popoverEl.popover('destroy').popover({
html: true,
placement: 'top',
trigger: 'manual',
- title: 'Unsubscribe From Challenge And:',
+ title: 'Leave challenge and...',
content: html
}).popover('show');
}
diff --git a/src/controllers/challenges.js b/src/controllers/challenges.js
index 6aed897abb..0e703b1f43 100644
--- a/src/controllers/challenges.js
+++ b/src/controllers/challenges.js
@@ -26,10 +26,15 @@ api.list = function(req, res) {
{group: 'habitrpg'}
]
})
- .select('name description memberCount groups')
+ .select('name description memberCount groups members')
.populate('groups', '_id name')
.exec(function(err, challenges){
if (err) return res.json(500,{err:err});
+ _.each(challenges, function(c){
+ if (~c.members.indexOf(user._id))
+ c._isMember = true;
+ c.members = [];
+ })
res.json(challenges);
});
}
@@ -43,6 +48,8 @@ api.get = function(req, res) {
if(err) return res.json(500, {err:err});
// slim down the return members' tasks to only the ones in the challenge
_.each(challenge.members, function(member){
+ if (member._id == user._id)
+ challenge._isMember = true;
_.each(['habits', 'dailys', 'todos', 'rewards'], function(type){
member[type] = _.where(member[type], function(task){
return task.challenge && task.challenge.id == challenge._id;
@@ -58,9 +65,8 @@ api.create = function(req, res){
// FIXME sanitize
var challenge = new Challenge(req.body);
challenge.save(function(err, saved){
- // Need to create challenge with refs (group, leader)? Or is this taken care of automatically?
- // @see http://mongoosejs.com/docs/populate.html
if (err) return res.json(500, {err:err});
+ Group.findByIdAndUpdate(saved.group, {$addToSet:{challenges:saved._id}}) // fixme error-check
res.json(saved);
});
}
@@ -118,19 +124,32 @@ api.update = function(req, res){
// DELETE
api['delete'] = function(req, res){
- Challenge.findOneAndRemove({_id:req.params.cid}, function(err, removed){
- if (err) return res.json(500, {err: err});
- User.find({_id:{$in: removed.members}}, function(err, users){
- if (err) throw err;
+ var removed;
+ async.waterfall([
+ function(cb){
+ Challenge.findOneAndRemove({_id:req.params.cid}, cb)
+ },
+ function(_removed, cb) {
+ removed = _removed;
+ User.find({_id:{$in: removed.members}}, cb);
+ },
+ function(users, cb) {
+ var parallel = [];
_.each(users, function(user){
_.each(user.tasks, function(task){
if (task.challenge && task.challenge.id == removed._id) {
task.challenge.broken = 'CHALLENGE_DELETED';
}
})
- user.save();
+ parallel.push(function(cb2){
+ user.save(cb2);
+ })
})
- })
+ async.parallel(parallel, cb);
+ }
+ ], function(err){
+ if (err) return res.json(500, {err: err});
+ res.send(200);
})
}
@@ -199,6 +218,7 @@ api.join = function(req, res){
}
], function(err, result){
if(err) return res.json(500,{err:err});
+ result._isMember = true;
res.json(result);
});
}
@@ -206,7 +226,7 @@ api.join = function(req, res){
function unlink(user, cid, keep, tid) {
switch (keep) {
case 'keep':
- delete user.tasks[tid].challenge;
+ user.tasks[tid].challenge = {};
break;
case 'remove':
user[user.tasks[tid].type+'s'].id(tid).remove();
@@ -214,7 +234,7 @@ function unlink(user, cid, keep, tid) {
case 'keep-all':
_.each(user.tasks, function(t){
if (t.challenge && t.challenge.id == cid) {
- delete t.challenge;
+ t.challenge = {};
}
});
break;
@@ -249,6 +269,7 @@ api.leave = function(req, res){
}
], function(err, result){
if(err) return res.json(500,{err:err});
+ result._isMember = false;
res.json(result);
});
}
@@ -264,6 +285,10 @@ api.unlink = function(req, res, next) {
if (!req.query.keep)
return res.json(400, {err: 'Provide unlink method as ?keep=keep-all (keep, keep-all, remove, remove-all)'});
unlink(user, cid, req.query.keep, tid);
+ user.markModified('habits');
+ user.markModified('dailys');
+ user.markModified('todos');
+ user.markModified('rewards');
user.save(function(err, saved){
if (err) return res.json(500,{err:err});
res.send(200);
diff --git a/src/models/challenge.js b/src/models/challenge.js
index fb77bd1bba..a3c60edc9c 100644
--- a/src/models/challenge.js
+++ b/src/models/challenge.js
@@ -3,6 +3,7 @@ var Schema = mongoose.Schema;
var helpers = require('habitrpg-shared/script/helpers');
var _ = require('lodash');
var TaskSchema = require('./task').schema;
+var Group = require('./group').model;
var ChallengeSchema = new Schema({
_id: {type: String, 'default': helpers.uuid},
@@ -20,7 +21,7 @@ var ChallengeSchema = new Schema({
//},
timestamp: {type: Date, 'default': Date.now},
members: [{type: String, ref: 'User'}],
- memberCount: [{type: Number, 'default': 0}]
+ memberCount: {type: Number, 'default': 0}
});
ChallengeSchema.virtual('tasks').get(function () {
@@ -29,10 +30,19 @@ ChallengeSchema.virtual('tasks').get(function () {
return tasks;
});
+// FIXME this isn't always triggered, since we sometimes use update() or findByIdAndUpdate()
+// @see https://github.com/LearnBoost/mongoose/issues/964
ChallengeSchema.pre('save', function(next){
this.memberCount = _.size(this.members);
- next();
+ next()
})
+ChallengeSchema.methods.toJSON = function(){
+ var doc = this.toObject();
+ doc.memberCount = _.size(doc.members); // @see pre('save') comment above
+ doc._isMember = this._isMember;
+ return doc;
+}
+
module.exports.schema = ChallengeSchema;
module.exports.model = mongoose.model("Challenge", ChallengeSchema);
\ No newline at end of file
diff --git a/src/models/group.js b/src/models/group.js
index 9393e94c9b..bf2227ce0d 100644
--- a/src/models/group.js
+++ b/src/models/group.js
@@ -58,6 +58,8 @@ function removeDuplicates(doc){
}
}
+// FIXME this isn't always triggered, since we sometimes use update() or findByIdAndUpdate()
+// @see https://github.com/LearnBoost/mongoose/issues/964
GroupSchema.pre('save', function(next){
removeDuplicates(this);
this.memberCount = _.size(this.members);
@@ -69,6 +71,11 @@ GroupSchema.methods.toJSON = function(){
var doc = this.toObject();
removeDuplicates(doc);
doc._isMember = this._isMember;
+
+ // @see pre('save') comment above
+ this.memberCount = _.size(this.members);
+ this.challengeCount = _.size(this.challenges);
+
return doc;
}
diff --git a/views/options/challenges.jade b/views/options/challenges.jade
index 3593abab3d..879d31f8f4 100644
--- a/views/options/challenges.jade
+++ b/views/options/challenges.jade
@@ -5,7 +5,7 @@ script(type='text/ng-template', id='partials/options.challenges.detail.html')
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-danger(ng-click='delete(challenge, $index)') Delete
button.btn.btn-default(ng-click='challenge._locked=true') Cancel
div(ng-hide='challenge._locked')
@@ -32,13 +32,14 @@ script(type='text/ng-template', id='partials/options.challenges.html')
| {{group.name}}
li
input(type='checkbox', ng-model='search.members')
- | Subscribed (TODO)
+ | Particiapting in(TODO)
li
input(type='checkbox', ng-model='search.members')
- | Available (TODO)
+ | Not participating in (TODO)
.span10
// Creation form
- a.btn.btn-success(ng-click='create()') Create Challenge
+ div(ng-hide='newChallenge')
+ button.btn.btn-success(ng-click='create()') Create Challenge
.create-challenge-from(ng-if='newChallenge')
form(ng-submit='save(newChallenge)')
div
@@ -54,7 +55,7 @@ script(type='text/ng-template', id='partials/options.challenges.html')
.accordion-group(ng-repeat='challenge in challenges | filter:search', ng-init='challenge._locked=true')
.accordion-heading
ul.pull-right.challenge-accordion-header-specs
- li {{challenge.members.length}} Subscribers
+ li {{challenge.memberCount}} Participants
li(ng-show='challenge.prize')
// prize
table(ng-show='challenge.prize')
@@ -64,13 +65,13 @@ script(type='text/ng-template', id='partials/options.challenges.html')
span.Pet_Currency_Gem1x
td Prize
li
- // subscribe / unsubscribe
- a.btn.btn-small.btn-danger(ng-show='indexOf(challenge.members, user._id)', ng-click='clickUnsubscribe(challenge, $event)')
+ // leave / join
+ a.btn.btn-small.btn-danger(ng-show='challenge._isMember', ng-click='clickLeave(challenge, $event)')
i.icon-ban-circle
- | Unsubscribe
- a.btn.btn-small.btn-success(ng-hide='indexOf(challenge.members, user._id)', ng-click='challenge.$join()')
+ | Leave
+ a.btn.btn-small.btn-success(ng-hide='challenge._isMember', ng-click='join(challenge)')
i.icon-ok
- | Subscribe
+ | Join
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')
diff --git a/views/shared/tasks/task.jade b/views/shared/tasks/task.jade
index c0c1c6557a..f88d9a238f 100644
--- a/views/shared/tasks/task.jade
+++ b/views/shared/tasks/task.jade
@@ -22,9 +22,9 @@ li(ng-repeat='task in obj[list.type+"s"]', class='task {{taskClasses(task, user.
//challenges
span(ng-if='task.challenge.id')
span(ng-if='task.challenge.broken')
- i.icon-bullhorn(style='background-color:red;', ng-click='task._edit=true', tooltip="Broken Challenge Link")
+ i.icon-bullhorn(style='background-color:red;', ng-click='task._editing = true', tooltip="Broken Challenge Link", tooltip-placement='right')
span(ng-if='!task.challenge.broken')
- i.icon-bullhorn(tooltip="Challenge Task")
+ i.icon-bullhorn(tooltip="Challenge")
// delete
a(ng-if='!task.challenge.id', ng-click='removeTask(obj[list.type+"s"], $index)', tooltip='Delete')
i.icon-trash
@@ -68,7 +68,7 @@ li(ng-repeat='task in obj[list.type+"s"]', class='task {{taskClasses(task, user.
p
a(ng-click='unlink(task, "keep")') Keep It
| |
- a(ng-click="remove(list, $index)") Remove It
+ a(ng-click="removeTask(obj[list.type+'s'], $index)") Remove It
div(ng-if='task.challenge.broken=="CHALLENGE_DELETED"')
p Broken Challenge Link: this task was part of a challenge, but the challenge (or group) has been deleted. What to do with the orphan tasks?
p