mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-19 07:37:25 +01:00
challenges: better syncing of new, updated, & deleted tasks from
challenge to user. start with task.challenge.broken code
This commit is contained in:
@@ -43,7 +43,9 @@ habitrpg.controller("ChallengesCtrl", ['$scope', '$rootScope', 'User', 'Challeng
|
|||||||
$scope.discard();
|
$scope.discard();
|
||||||
Challenges.Challenge.query();
|
Challenges.Challenge.query();
|
||||||
} else {
|
} else {
|
||||||
challenge._editing = false;
|
// TODO figure out a more elegant way about this
|
||||||
|
//challenge._editing = false;
|
||||||
|
$scope.locked = true;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -61,7 +63,7 @@ habitrpg.controller("ChallengesCtrl", ['$scope', '$rootScope', 'User', 'Challeng
|
|||||||
*/
|
*/
|
||||||
$scope["delete"] = function(challenge) {
|
$scope["delete"] = function(challenge) {
|
||||||
if (confirm("Delete challenge, are you sure?") !== true) return;
|
if (confirm("Delete challenge, are you sure?") !== true) return;
|
||||||
challenge.delete();
|
challenge.$delete();
|
||||||
};
|
};
|
||||||
|
|
||||||
//------------------------------------------------------------
|
//------------------------------------------------------------
|
||||||
|
|||||||
@@ -130,7 +130,6 @@ habitrpg
|
|||||||
scope.obj = scope[attrs.obj];
|
scope.obj = scope[attrs.obj];
|
||||||
scope.main = attrs.main;
|
scope.main = attrs.main;
|
||||||
|
|
||||||
|
|
||||||
scope.lists = [
|
scope.lists = [
|
||||||
{
|
{
|
||||||
header: 'Habits',
|
header: 'Habits',
|
||||||
@@ -154,7 +153,6 @@ habitrpg
|
|||||||
tasks: scope.obj.rewards
|
tasks: scope.obj.rewards
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
scope.editable = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}]);
|
}]);
|
||||||
|
|||||||
@@ -39,19 +39,74 @@ api.create = function(req, res){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function keepAttrs(task) {
|
||||||
|
// only sync/compare important attrs
|
||||||
|
var keepAttrs = 'text notes up down priority repeat'.split(' ');
|
||||||
|
if (task.type=='reward') keepAttrs.push('value');
|
||||||
|
return _.pick(task, keepAttrs);
|
||||||
|
}
|
||||||
|
|
||||||
// UPDATE
|
// UPDATE
|
||||||
api.update = function(req, res){
|
api.update = function(req, res){
|
||||||
//FIXME sanitize
|
//FIXME sanitize
|
||||||
Challenge.findByIdAndUpdate(req.params.cid, {$set:req.body}, function(err, saved){
|
var cid = req.params.cid;
|
||||||
|
async.waterfall([
|
||||||
|
function(cb){
|
||||||
|
// We first need the original challenge data, since we're going to compare against new & decide to sync users
|
||||||
|
Challenge.findById(cid, cb);
|
||||||
|
},
|
||||||
|
function(chal, cb) {
|
||||||
|
|
||||||
|
// Update the challenge, and then just res.json success (note we're passing `cb` here).
|
||||||
|
// The syncing stuff is really heavy, and the client doesn't care - so we kick it off in the background
|
||||||
|
delete req.body._id;
|
||||||
|
Challenge.findByIdAndUpdate(cid, {$set:req.body}, cb);
|
||||||
|
|
||||||
|
// Compare whether any changes have been made to tasks. If so, we'll want to sync those changes to subscribers
|
||||||
|
function comparableData(obj) {
|
||||||
|
return (
|
||||||
|
_.chain(obj.habits.concat(obj.dailys).concat(obj.todos).concat(obj.rewards))
|
||||||
|
.sortBy('id') // we don't want to update if they're sort-order is different
|
||||||
|
.transform(function(result, task){
|
||||||
|
result.push(keepAttrs(task));
|
||||||
|
}))
|
||||||
|
.toString(); // for comparing arrays easily
|
||||||
|
}
|
||||||
|
if (comparableData(chal) !== comparableData(req.body)) {
|
||||||
|
User.find({_id: {$in: chal.members}}, function(err, users){
|
||||||
|
console.log('Challenge updated, sync to subscribers');
|
||||||
|
if (err) throw err;
|
||||||
|
_.each(users, function(user){
|
||||||
|
syncChalToUser(chal, user);
|
||||||
|
user.save();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
], function(err, saved){
|
||||||
if(err) res.json(500, {err:err});
|
if(err) res.json(500, {err:err});
|
||||||
res.json(saved);
|
res.json(saved);
|
||||||
// TODO update subscribed users' tasks, each user.__v++
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// DELETE
|
// DELETE
|
||||||
// 1. update challenge
|
api['delete'] = function(req, res){
|
||||||
// 2. update sub'd users' tasks
|
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;
|
||||||
|
_.each(users, function(user){
|
||||||
|
_.each(user.tasks, function(task){
|
||||||
|
if (task.challenge && task.challenge.id == removed._id) {
|
||||||
|
task.challenge.broken = 'CHALLENGE_DELETED';
|
||||||
|
}
|
||||||
|
})
|
||||||
|
user.save();
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Syncs all new tasks, deleted tasks, etc to the user object
|
* Syncs all new tasks, deleted tasks, etc to the user object
|
||||||
@@ -79,18 +134,23 @@ var syncChalToUser = function(chal, user) {
|
|||||||
}
|
}
|
||||||
tags = {};
|
tags = {};
|
||||||
tags[chal._id] = true;
|
tags[chal._id] = true;
|
||||||
_.each(['habits','dailys','todos','rewards'], function(type){
|
|
||||||
_.each(chal[type], function(task){
|
// Sync new tasks and updated tasks
|
||||||
_.defaults(task, {tags: tags, challenge:{}});
|
_.each(chal.tasks, function(task){
|
||||||
_.defaults(task.challenge, {id:chal._id, broken:false});
|
var type = task.type;
|
||||||
if (~(i = _.findIndex(user[type], {id:task.id}))) {
|
_.defaults(task, {tags: tags, challenge:{}});
|
||||||
_.defaults(user[type][i], task);
|
_.defaults(task.challenge, {id:chal._id, broken:false});
|
||||||
} else {
|
if (user.tasks[task.id]) {
|
||||||
user[type].push(task);
|
_.merge(user.tasks[task.id], keepAttrs(task));
|
||||||
}
|
} else {
|
||||||
})
|
user[type+'s'].push(task);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Flag deleted tasks as "broken"
|
||||||
|
_.each(user.tasks, function(task){
|
||||||
|
if (!chal.tasks[task.id]) task.challenge.broken = 'TASK_DELETED';
|
||||||
})
|
})
|
||||||
//FIXME account for deleted tasks (each users.tasks.broken = true)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
api.join = function(req, res){
|
api.join = function(req, res){
|
||||||
|
|||||||
@@ -28,6 +28,5 @@ ChallengeSchema.virtual('tasks').get(function () {
|
|||||||
return tasks;
|
return tasks;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
module.exports.schema = ChallengeSchema;
|
module.exports.schema = ChallengeSchema;
|
||||||
module.exports.model = mongoose.model("Challenge", ChallengeSchema);
|
module.exports.model = mongoose.model("Challenge", ChallengeSchema);
|
||||||
@@ -86,6 +86,8 @@ router.post('/market/buy', auth.auth, user.marketBuy);
|
|||||||
// without knowing which group they belong to. So to prevent unecessary lookups, we have them as a top-level resource
|
// 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.get)
|
||||||
router.post('/challenges', auth.auth, challenges.create)
|
router.post('/challenges', auth.auth, challenges.create)
|
||||||
|
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)
|
router.post('/challenges/:cid/join', auth.auth, challenges.join)
|
||||||
router.post('/challenges/:cid/leave', auth.auth, challenges.leave)
|
router.post('/challenges/:cid/leave', auth.auth, challenges.leave)
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@
|
|||||||
habitrpg-tasks(main=false, obj='newChallenge')
|
habitrpg-tasks(main=false, obj='newChallenge')
|
||||||
|
|
||||||
// Challenges list
|
// Challenges list
|
||||||
.accordion-group(ng-repeat='challenge in challenges')
|
.accordion-group(ng-repeat='challenge in challenges', ng-init='locked=true')
|
||||||
.accordion-heading
|
.accordion-heading
|
||||||
ul.pull-right.challenge-accordion-header-specs
|
ul.pull-right.challenge-accordion-header-specs
|
||||||
li {{challenge.members.length}} Subscribers
|
li {{challenge.members.length}} Subscribers
|
||||||
@@ -57,21 +57,21 @@
|
|||||||
.accordion-body.collapse(id='accordion-challenge-{{challenge._id}}')
|
.accordion-body.collapse(id='accordion-challenge-{{challenge._id}}')
|
||||||
.accordion-inner
|
.accordion-inner
|
||||||
// Edit button
|
// Edit button
|
||||||
span(style='position: absolute; right: 0;')
|
ul.unstyled()
|
||||||
ul.nav.nav-pills(ng-show='challenge.leader==user._id && !challenge._editing')
|
li(ng-show='challenge.leader==user._id && locked')
|
||||||
li
|
button.btn.btn-default(ng-click='locked = false') Edit
|
||||||
a(ng-click='challenge._editing = true')
|
li(ng-hide='locked')
|
||||||
i.icon-pencil
|
button.btn.btn-primary(ng-click='save(challenge)') Save
|
||||||
ul.nav.nav-pills(ng-show='challenge._editing')
|
button.btn.btn-danger(ng-click='delete(challenge)') Delete
|
||||||
li
|
button.btn.btn-default(ng-click='locked=true') Cancel
|
||||||
a(ng-click='save(challenge)')
|
|
||||||
i.icon-ok
|
div(ng-hide='locked')
|
||||||
div(ng-show='challenge._editing')
|
|
||||||
.-options
|
.-options
|
||||||
input.option-content(type='text', ng-model='challenge.name')
|
input.option-content(type='text', ng-model='challenge.name')
|
||||||
textarea.option-content(cols='3', placeholder='Description', ng-model='challenge.description')
|
textarea.option-content(cols='3', placeholder='Description', ng-model='challenge.description')
|
||||||
// <input type=number class='option-content' placeholder='Gems Prize' value={@challenge.prize} />
|
// <input type=number class='option-content' placeholder='Gems Prize' value={@challenge.prize} />
|
||||||
a.btn.btn-small.btn-danger(ng-click='delete(challenge)') Delete
|
hr
|
||||||
|
|
||||||
div(ng-if='challenge.description') {{challenge.description}}
|
div(ng-if='challenge.description') {{challenge.description}}
|
||||||
habitrpg-tasks(obj='challenge', main=false)
|
habitrpg-tasks(obj='challenge', main=false)
|
||||||
h3 Statistics
|
h3 Statistics
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ script(id='templates/habitrpg-tasks.html', type="text/ng-template")
|
|||||||
.todos-chart(ng-if='list.type == "todo"', ng-show='charts.todos')
|
.todos-chart(ng-if='list.type == "todo"', ng-show='charts.todos')
|
||||||
|
|
||||||
// Add New
|
// Add New
|
||||||
form.addtask-form.form-inline.new-task-form(name='new{{list.type}}form', ng-show='editable', ng-hide='list.showCompleted && list.type=="todo"', ng-submit='addTask(list)')
|
form.addtask-form.form-inline.new-task-form(name='new{{list.type}}form', ng-hide='locked || (list.showCompleted && list.type=="todo")', ng-submit='addTask(list)')
|
||||||
span.addtask-field
|
span.addtask-field
|
||||||
input(type='text', ng-model='list.newTask', placeholder='{{list.placeHolder}}', required)
|
input(type='text', ng-model='list.newTask', placeholder='{{list.placeHolder}}', required)
|
||||||
input.addtask-btn(type='submit', value='+', ng-disabled='new{{list.type}}form.$invalid')
|
input.addtask-btn(type='submit', value='+', ng-disabled='new{{list.type}}form.$invalid')
|
||||||
|
|||||||
@@ -61,19 +61,19 @@ li(ng-repeat='task in list.tasks', class='task {{taskClasses(task, user.filters,
|
|||||||
|
|
||||||
// Broken Challenge
|
// Broken Challenge
|
||||||
.well(ng-if='task.challenge.broken')
|
.well(ng-if='task.challenge.broken')
|
||||||
div(ng-if='task.challenge.broken==1')
|
div(ng-if='task.challenge.broken=="TASK_DELETED"')
|
||||||
p Broken Challenge Link: this task was part of a challenge, but has been removed from it. What would you like to do?
|
p Broken Challenge Link: this task was part of a challenge, but has been removed from it. What would you like to do?
|
||||||
p
|
p
|
||||||
a(ng-click="unlink(task, 'keep')") Keep It
|
a(ng-click="unlink(task, 'keep')") Keep It
|
||||||
| |
|
| |
|
||||||
a(ng-click="remove(list, $index)") Remove It
|
a(ng-click="remove(list, $index)") Remove It
|
||||||
div(ng-if='task.challenge.broken==2')
|
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 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
|
p
|
||||||
a(ng-click="unlink(task 'keep-all')") Keep Them
|
a(ng-click="unlink(task 'keep-all')") Keep Them
|
||||||
| |
|
| |
|
||||||
a(ng-click="unlink(task, 'remove-all')") Remove Them
|
a(ng-click="unlink(task, 'remove-all')") Remove Them
|
||||||
div(ng-if='task.challenge.broken==3')
|
//-div(ng-if='task.challenge.broken=="UNSUBSCRIBED"')
|
||||||
p Broken Challenge Link: this task was part of a challenge, but you have unsubscribed from the challenge. What to do with the orphan tasks?
|
p Broken Challenge Link: this task was part of a challenge, but you have unsubscribed from the challenge. What to do with the orphan tasks?
|
||||||
p
|
p
|
||||||
a(ng-click="unlink(task, 'keep-all')") Keep Them
|
a(ng-click="unlink(task, 'keep-all')") Keep Them
|
||||||
|
|||||||
Reference in New Issue
Block a user