challenges: lots of misc. bug fixes

This commit is contained in:
Tyler Renelle
2013-10-29 20:37:22 -07:00
parent 6e71a76e22
commit d116ac7b81
6 changed files with 100 additions and 39 deletions

View File

@@ -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(
'<a ng-controller="ChallengesCtrl" ng-click="unsubscribe(\'remove-all\')">Remove Tasks</a><br/>\n<a ng-click="unsubscribe(\'keep-all\')">Keep Tasks</a><br/>\n<a ng-click="unsubscribe(\'cancel\')">Cancel</a><br/>'
'<a ng-controller="ChallengesCtrl" ng-click="leave(\'remove-all\')">Remove Tasks</a><br/>\n<a ng-click="leave(\'keep-all\')">Keep Tasks</a><br/>\n<a ng-click="leave(\'cancel\')">Cancel</a><br/>'
)($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');
}

View File

@@ -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);

View File

@@ -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);

View File

@@ -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;
}

View File

@@ -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')

View File

@@ -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
| &nbsp;|&nbsp;
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