v3 adapt v2: port challenges closing and fix create

This commit is contained in:
Matteo Pagliazzi
2016-04-08 18:45:50 +02:00
parent 70fd869934
commit 83fa30fd0a
2 changed files with 47 additions and 97 deletions
+45 -96
View File
@@ -233,9 +233,23 @@ api.create = async function(req, res, next){
let challengeValidationErrors = challenge.validateSync();
if (challengeValidationErrors) throw challengeValidationErrors;
req.body.habits = req.body.habits || [];
req.body.todos = req.body.todos || [];
req.body.dailys = req.body.dailys || [];
req.body.rewards = req.body.rewards || [];
var chalTasks = req.body.habits.concat(req.body.rewards)
.concat(req.body.dailys).concat(req.body.todos);
chalTasks = tasks.map(function(task) {
var newTask = new Tasks[task.type](Tasks.Task.sanitizeCreate(task));
newTask.challenge.id = chal._id;
return newTask.save();
});
let results = await Q.all([challenge.save({
validateBeforeSave: false, // already validate
}), group.save()]);
validateBeforeSave: false, // already validated
}), group.save()].concat(chalTasks));
let savedChal = results[0];
await savedChal.syncToUser(user); // (it also saves the user)
@@ -292,113 +306,48 @@ api.update = function(req, res, next){
})
}
/**
* Called by either delete() or selectWinner(). Will delete the challenge and set the "broken" property on all users' subscribed tasks
* @param {cid} the challenge id
* @param {broken} the object representing the broken status of the challenge. Eg:
* {broken: 'CHALLENGE_DELETED', id: CHALLENGE_ID}
* {broken: 'CHALLENGE_CLOSED', id: CHALLENGE_ID, winner: USER_NAME}
*/
function closeChal(cid, broken, cb) {
var removed;
async.waterfall([
function(cb2){
Challenge.findOneAndRemove({_id:cid}, cb2)
},
function(_removed, cb2) {
removed = _removed;
var pull = {'$pull':{}}; pull['$pull'][_removed._id] = 1;
Group.findByIdAndUpdate(_removed.group, {new: true}, pull);
User.find({_id:{$in: removed.members}}, cb2);
},
function(users, cb2) {
var parallel = [];
_.each(users, function(user){
var tag = _.find(user.tags, {id:cid});
if (tag) tag.challenge = undefined;
_.each(user.tasks, function(task){
if (task.challenge && task.challenge.id == removed._id) {
_.merge(task.challenge, broken);
}
})
parallel.push(function(cb3){
user.save(cb3);
})
})
async.parallel(parallel, cb2);
removed = null;
}
], cb);
}
import { _closeChal } from '../api-v3/challenges';
/**
* Delete & close
*/
api.delete = function(req, res, next){
var user = res.locals.user;
var cid = req.params.cid;
api.delete = async function(req, res, next){
try {
var user = res.locals.user;
var cid = req.params.cid;
async.waterfall([
function(cb){
Challenge.findById(cid, cb);
},
function(chal, cb){
if (!chal) return cb('Challenge ' + cid + ' not found');
if (chal.leader != user._id && !user.contributor.admin) return cb(shared.i18n.t('noPermissionDeleteChallenge', req.language));
if (chal.group != 'habitrpg') user.balance += chal.prize/4; // Refund gems to user if a non-tavern challenge
user.save(cb);
},
function(save, num, cb){
closeChal(req.params.cid, {broken: 'CHALLENGE_DELETED'}, cb);
}
], function(err){
if (err) return next(err);
let challenge = await Challenge.findOne({_id: req.params.cid}).exec();
if (!challenge) return next('Challenge ' + cid + ' not found');
if (!challenge.canModify(user)) return next(shared.i18n.t('noPermissionCloseChallenge'));
// Close channel in background, some ops are run in the background without `await`ing
await _closeChal(challenge, {broken: 'CHALLENGE_DELETED'});
res.sendStatus(200);
user = cid = null;
});
} catch (err) {
next(err);
}
}
/**
* Select Winner & Close
*/
api.selectWinner = function(req, res, next) {
if (!req.query.uid) return res.status(401).json({err: 'Must select a winner'});
var user = res.locals.user;
var cid = req.params.cid;
var chal;
async.waterfall([
function(cb){
Challenge.findById(cid, cb);
},
function(_chal, cb){
chal = _chal;
if (!chal) return cb('Challenge ' + cid + ' not found');
if (chal.leader != user._id && !user.contributor.admin) return cb(shared.i18n.t('noPermissionCloseChallenge', req.language));
User.findById(req.query.uid, cb)
},
function(winner, cb){
if (!winner) return cb('Winner ' + req.query.uid + ' not found.');
_.defaults(winner.achievements, {challenges: []});
winner.achievements.challenges.push(chal.name);
winner.balance += chal.prize/4;
winner.save(cb);
},
function(saved, num, cb) {
if(saved.preferences.emailNotifications.wonChallenge !== false){
utils.txnEmail(saved, 'won-challenge', [
{name: 'CHALLENGE_NAME', content: chal.name}
]);
}
api.selectWinner = async function(req, res, next) {
try {
if (!req.query.uid) return res.status(401).json({err: 'Must select a winner'});
pushNotify.sendNotify(saved, shared.i18n.t('wonChallenge'), chal.name);
let challenge = await Challenge.findOne({_id: req.params.cid}).exec();
if (!challenge) return next('Challenge ' + cid + ' not found');
if (!challenge.canModify(user)) return next(shared.i18n.t('noPermissionCloseChallenge'));
closeChal(cid, {broken: 'CHALLENGE_CLOSED', winner: saved.profile.name}, cb);
}
], function(err){
if (err) return next(err);
res.sendStatus(200);
user = cid = chal = null;
})
let winner = await User.findOne({_id: req.params.uid}).exec();
if (!winner || winner.challenges.indexOf(challenge._id) === -1) return next('Winner ' + req.query.uid + ' not found.');
// Close channel in background, some ops are run in the background without `await`ing
await _closeChal(challenge, {broken: 'CHALLENGE_CLOSED', winner});
res.respond(200, {});
} catch (err) {
next(err);
}
}
api.join = function(req, res, next){
+2 -1
View File
@@ -445,7 +445,8 @@ api.updateChallenge = {
// TODO everything here should be moved to a worker
// actually even for a worker it's probably just too big and will kill mongo
async function _closeChal (challenge, broken = {}) {
// Exported because it's used in v2 controller
export async function _closeChal (challenge, broken = {}) {
let winner = broken.winner;
let brokenReason = broken.broken;