mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-18 15:17:25 +01:00
Challenge small refactor (#9964)
* Removed challenge group duplication * Removed duplication of joined challenge * Moved helpers to challenge library * Passed group to function * Fixed group response
This commit is contained in:
@@ -1,8 +1,6 @@
|
|||||||
import { authWithHeaders, authWithSession } from '../../middlewares/auth';
|
import { authWithHeaders, authWithSession } from '../../middlewares/auth';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import cloneDeep from 'lodash/cloneDeep';
|
import cloneDeep from 'lodash/cloneDeep';
|
||||||
import omit from 'lodash/omit';
|
|
||||||
import uuid from 'uuid';
|
|
||||||
import { model as Challenge } from '../../models/challenge';
|
import { model as Challenge } from '../../models/challenge';
|
||||||
import {
|
import {
|
||||||
model as Group,
|
model as Group,
|
||||||
@@ -24,77 +22,15 @@ import {
|
|||||||
createTasks,
|
createTasks,
|
||||||
} from '../../libs/taskManager';
|
} from '../../libs/taskManager';
|
||||||
|
|
||||||
const TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'dateCompleted', 'history', 'id', 'streak', 'createdAt', 'challenge'];
|
import {
|
||||||
|
addUserJoinChallengeNotification,
|
||||||
|
getChallengeGroupResponse,
|
||||||
|
createChallenge,
|
||||||
|
cleanUpTask,
|
||||||
|
} from '../../libs/challenges';
|
||||||
|
|
||||||
let api = {};
|
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
|
* @apiDefine ChallengeLeader Challenge Leader
|
||||||
* The leader of the challenge can use this route.
|
* The leader of the challenge can use this route.
|
||||||
@@ -264,12 +200,7 @@ api.createChallenge = {
|
|||||||
_id: user._id,
|
_id: user._id,
|
||||||
profile: {name: user.profile.name},
|
profile: {name: user.profile.name},
|
||||||
};
|
};
|
||||||
response.group = { // we already have the group data
|
response.group = getChallengeGroupResponse(group);
|
||||||
_id: group._id,
|
|
||||||
name: group.name,
|
|
||||||
type: group.type,
|
|
||||||
privacy: group.privacy,
|
|
||||||
};
|
|
||||||
|
|
||||||
res.analytics.track('challenge create', {
|
res.analytics.track('challenge create', {
|
||||||
uuid: user._id,
|
uuid: user._id,
|
||||||
@@ -320,22 +251,13 @@ api.joinChallenge = {
|
|||||||
|
|
||||||
challenge.memberCount += 1;
|
challenge.memberCount += 1;
|
||||||
|
|
||||||
// Add achievement if user's first challenge
|
addUserJoinChallengeNotification(user);
|
||||||
if (!user.achievements.joinedChallenge) {
|
|
||||||
user.achievements.joinedChallenge = true;
|
|
||||||
user.addNotification('CHALLENGE_JOINED_ACHIEVEMENT');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add all challenge's tasks to user's tasks and save the challenge
|
// Add all challenge's tasks to user's tasks and save the challenge
|
||||||
let results = await Bluebird.all([challenge.syncToUser(user), challenge.save()]);
|
let results = await Bluebird.all([challenge.syncToUser(user), challenge.save()]);
|
||||||
|
|
||||||
let response = results[1].toJSON();
|
let response = results[1].toJSON();
|
||||||
response.group = { // we already have the group data
|
response.group = getChallengeGroupResponse(group);
|
||||||
_id: group._id,
|
|
||||||
name: group.name,
|
|
||||||
type: group.type,
|
|
||||||
privacy: group.privacy,
|
|
||||||
};
|
|
||||||
let chalLeader = await User.findById(response.leader).select(nameFields).exec();
|
let chalLeader = await User.findById(response.leader).select(nameFields).exec();
|
||||||
response.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null;
|
response.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null;
|
||||||
|
|
||||||
@@ -692,12 +614,7 @@ api.updateChallenge = {
|
|||||||
|
|
||||||
let savedChal = await challenge.save();
|
let savedChal = await challenge.save();
|
||||||
let response = savedChal.toJSON();
|
let response = savedChal.toJSON();
|
||||||
response.group = { // we already have the group data
|
response.group = getChallengeGroupResponse(group);
|
||||||
_id: group._id,
|
|
||||||
name: group.name,
|
|
||||||
type: group.type,
|
|
||||||
privacy: group.privacy,
|
|
||||||
};
|
|
||||||
let chalLeader = await User.findById(response.leader).select(nameFields).exec();
|
let chalLeader = await User.findById(response.leader).select(nameFields).exec();
|
||||||
response.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null;
|
response.leader = chalLeader ? chalLeader.toJSON({minimize: true}) : null;
|
||||||
res.respond(200, response);
|
res.respond(200, response);
|
||||||
@@ -798,23 +715,6 @@ 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
|
* @api {post} /api/v3/challenges/:challengeId/clone Clone a challenge
|
||||||
* @apiName CloneChallenge
|
* @apiName CloneChallenge
|
||||||
|
|||||||
110
website/server/libs/challenges/index.js
Normal file
110
website/server/libs/challenges/index.js
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
// Currently this holds helpers for challenge api, but we should break this up into submodules as it expands
|
||||||
|
import omit from 'lodash/omit';
|
||||||
|
import uuid from 'uuid';
|
||||||
|
import Bluebird from 'bluebird';
|
||||||
|
import { model as Challenge } from '../../models/challenge';
|
||||||
|
import {
|
||||||
|
model as Group,
|
||||||
|
TAVERN_ID,
|
||||||
|
} from '../../models/group';
|
||||||
|
import {
|
||||||
|
NotFound,
|
||||||
|
NotAuthorized,
|
||||||
|
} from '../errors';
|
||||||
|
|
||||||
|
const TASK_KEYS_TO_REMOVE = ['_id', 'completed', 'dateCompleted', 'history', 'id', 'streak', 'createdAt', 'challenge'];
|
||||||
|
|
||||||
|
export function addUserJoinChallengeNotification (user) {
|
||||||
|
if (user.achievements.joinedChallenge) return;
|
||||||
|
user.achievements.joinedChallenge = true;
|
||||||
|
user.addNotification('CHALLENGE_JOINED_ACHIEVEMENT');
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getChallengeGroupResponse (group) {
|
||||||
|
return {
|
||||||
|
_id: group._id,
|
||||||
|
name: group.name,
|
||||||
|
type: group.type,
|
||||||
|
privacy: group.privacy,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
|
||||||
|
addUserJoinChallengeNotification(user);
|
||||||
|
|
||||||
|
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};
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user