Clone challenges api (#9684)

* Added clone api

* Added new clone UI

* Fixed challenge clone

* Fixed lint and added mongo toObject

* Removed clone field, fixed type, fixed challenge task query

* Auto selected group

* Accounted for group balance when creating challenge

* Added check for if user is leader of guild

* Added leader existence check

* Added fix for leader and prizecost equal to
This commit is contained in:
Keith Holliday
2018-01-30 08:23:20 -07:00
committed by GitHub
parent ccf8e0b320
commit 4fe6c8db64
5 changed files with 244 additions and 104 deletions

View File

@@ -1,5 +1,8 @@
import { authWithHeaders, authWithSession } from '../../middlewares/auth';
import _ from 'lodash';
import cloneDeep from 'lodash/cloneDeep';
import omit from 'lodash/omit';
import uuid from 'uuid';
import { model as Challenge } from '../../models/challenge';
import {
model as Group,
@@ -17,9 +20,81 @@ import {
import * as Tasks from '../../models/task';
import Bluebird from 'bluebird';
import csvStringify from '../../libs/csvStringify';
import {
createTasks,
} from '../../libs/taskManager';
const TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'dateCompleted', 'history', 'id', 'streak', 'createdAt', 'challenge'];
let api = {};
async function createChallenge (user, req, res) {
let groupId = req.body.group;
let prize = req.body.prize;
let group = await Group.getGroup({user, groupId, fields: '-chat', mustBeMember: true});
if (!group) throw new NotFound(res.t('groupNotFound'));
if (!group.isMember(user)) throw new NotAuthorized(res.t('mustBeGroupMember'));
if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) {
throw new NotAuthorized(res.t('onlyGroupLeaderChal'));
}
if (group._id === TAVERN_ID && prize < 1) {
throw new NotAuthorized(res.t('tavChalsMinPrize'));
}
if (prize > 0) {
let groupBalance = group.balance && group.leader === user._id ? group.balance : 0;
let prizeCost = prize / 4;
if (prizeCost > user.balance + groupBalance) {
throw new NotAuthorized(res.t('cantAfford'));
}
if (groupBalance >= prizeCost) {
// Group pays for all of prize
group.balance -= prizeCost;
} else if (groupBalance > 0) {
// User pays remainder of prize cost after group
let remainder = prizeCost - group.balance;
group.balance = 0;
user.balance -= remainder;
} else {
// User pays for all of prize
user.balance -= prizeCost;
}
}
group.challengeCount += 1;
if (!req.body.summary) {
req.body.summary = req.body.name;
}
req.body.leader = user._id;
req.body.official = user.contributor.admin && req.body.official ? true : false;
let challenge = new Challenge(Challenge.sanitize(req.body));
// First validate challenge so we don't save group if it's invalid (only runs sync validators)
let challengeValidationErrors = challenge.validateSync();
if (challengeValidationErrors) throw challengeValidationErrors;
// Add achievement if user's first challenge
if (!user.achievements.joinedChallenge) {
user.achievements.joinedChallenge = true;
user.addNotification('CHALLENGE_JOINED_ACHIEVEMENT');
}
let results = await Bluebird.all([challenge.save({
validateBeforeSave: false, // already validate
}), group.save()]);
let savedChal = results[0];
await savedChal.syncToUser(user); // (it also saves the user)
return {savedChal, group};
}
/**
* @apiDefine ChallengeLeader Challenge Leader
* The leader of the challenge can use this route.
@@ -179,71 +254,10 @@ api.createChallenge = {
req.checkBody('group', res.t('groupIdRequired')).notEmpty();
let validationErrors = req.validationErrors();
const validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
let groupId = req.body.group;
let prize = req.body.prize;
let group = await Group.getGroup({user, groupId, fields: '-chat', mustBeMember: true});
if (!group) throw new NotFound(res.t('groupNotFound'));
if (!group.isMember(user)) throw new NotAuthorized(res.t('mustBeGroupMember'));
if (group.leaderOnly && group.leaderOnly.challenges && group.leader !== user._id) {
throw new NotAuthorized(res.t('onlyGroupLeaderChal'));
}
if (group._id === TAVERN_ID && prize < 1) {
throw new NotAuthorized(res.t('tavChalsMinPrize'));
}
if (prize > 0) {
let groupBalance = group.balance && group.leader === user._id ? group.balance : 0;
let prizeCost = prize / 4;
if (prizeCost > user.balance + groupBalance) {
throw new NotAuthorized(res.t('cantAfford'));
}
if (groupBalance >= prizeCost) {
// Group pays for all of prize
group.balance -= prizeCost;
} else if (groupBalance > 0) {
// User pays remainder of prize cost after group
let remainder = prizeCost - group.balance;
group.balance = 0;
user.balance -= remainder;
} else {
// User pays for all of prize
user.balance -= prizeCost;
}
}
group.challengeCount += 1;
if (!req.body.summary) {
req.body.summary = req.body.name;
}
req.body.leader = user._id;
req.body.official = user.contributor.admin && req.body.official ? true : false;
let challenge = new Challenge(Challenge.sanitize(req.body));
// First validate challenge so we don't save group if it's invalid (only runs sync validators)
let challengeValidationErrors = challenge.validateSync();
if (challengeValidationErrors) throw challengeValidationErrors;
// Add achievement if user's first challenge
if (!user.achievements.joinedChallenge) {
user.achievements.joinedChallenge = true;
user.addNotification('CHALLENGE_JOINED_ACHIEVEMENT');
}
let results = await Bluebird.all([challenge.save({
validateBeforeSave: false, // already validate
}), group.save()]);
let savedChal = results[0];
await savedChal.syncToUser(user); // (it also saves the user)
const {savedChal, group} = await createChallenge(user, req, res);
let response = savedChal.toJSON();
response.leader = { // the leader is the authenticated user
@@ -784,4 +798,70 @@ api.selectChallengeWinner = {
},
};
function cleanUpTask (task) {
let cleansedTask = omit(task, TASK_KEYS_TO_REMOVE);
// Copy checklists but reset to uncomplete and assign new id
if (!cleansedTask.checklist) cleansedTask.checklist = [];
cleansedTask.checklist.forEach((item) => {
item.completed = false;
item.id = uuid();
});
if (cleansedTask.type !== 'reward') {
delete cleansedTask.value;
}
return cleansedTask;
}
/**
* @api {post} /api/v3/challenges/:challengeId/clone Clone a challenge
* @apiName CloneChallenge
* @apiGroup Challenge
*
* @apiParam (Path) {UUID} challengeId The _id for the challenge to clone
*
* @apiSuccess {Object} challenge The cloned challenge
*
* @apiUse ChallengeNotFound
*/
api.cloneChallenge = {
method: 'POST',
url: '/challenges/:challengeId/clone',
middlewares: [authWithHeaders()],
async handler (req, res) {
let user = res.locals.user;
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
let validationErrors = req.validationErrors();
if (validationErrors) throw validationErrors;
const challengeToClone = await Challenge.findOne({_id: req.params.challengeId}).exec();
if (!challengeToClone) throw new NotFound(res.t('challengeNotFound'));
const {savedChal} = await createChallenge(user, req, res);
const challengeTasks = await Tasks.Task.find({
'challenge.id': challengeToClone._id,
userId: {$exists: false},
}).exec();
const tasksToClone = challengeTasks.map(task => {
let clonedTask = cloneDeep(task.toObject());
let omittedTask = cleanUpTask(clonedTask);
return omittedTask;
});
const taskRequest = {
body: tasksToClone,
};
const clonedTasks = await createTasks(taskRequest, res, {user, challenge: savedChal});
res.respond(200, {clonedTasks, clonedChallenge: savedChal});
},
};
module.exports = api;