mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 06:37:23 +01:00
commit d30dff2311087ff2fe5f3e2a913c594abeee6b0e Author: Sabe Jones <sabe@habitica.com> Date: Tue Feb 27 16:01:11 2024 -0600 fix(challenge): move isOfficial to mount process commit ae52dca3cd0b4fd490f07b1979049803ce2f1e2f Merge: 2b20ff1e462c6e82a58aAuthor: Sabe Jones <sabe@habitica.com> Date: Tue Feb 27 15:20:40 2024 -0600 Merge branch 'release' into phillip/challenges_official commit 2b20ff1e46b1447eac3f9dbdf29566154c9fa656 Author: Sabe Jones <sabe@habitica.com> Date: Wed Feb 14 15:31:22 2024 -0600 fix(tests): correct lint and TypeError commit 5dae5c716f11db4c652e423eab43805ddfac3455 Merge: 29d9edc7aa1a3c2f64e4Author: Sabe Jones <sabe@habitica.com> Date: Wed Feb 14 15:01:18 2024 -0600 Merge branch 'release' into phillip/challenges_official commit 29d9edc7aa7445d24f5be24ca923719a4ab5f70d Author: Sabe Jones <sabe@habitica.com> Date: Wed Feb 14 14:41:16 2024 -0600 fix(challenges): don't momentarily show Report on official commit f994d12775107cba7ec816ee522cfeb0c69ef567 Author: Phillip Thelen <phillip@habitica.com> Date: Tue Feb 13 10:08:08 2024 +0100 hide report button for official challenges commit ac06dcaca701b91913d5fc5307626b1616a9e0e8 Author: Phillip Thelen <phillip@habitica.com> Date: Tue Feb 13 10:04:49 2024 +0100 prevent official challenges from being flagged commit a07ce1e6de66a2c833c6f392cf396a7743ca0f97 Author: Phillip Thelen <phillip@habitica.com> Date: Mon Feb 5 19:12:17 2024 +0100 test shouldn’t be exclusive commit 4c2436a1a0fa905530b7e1cd66f75a2b07be5810 Author: Phillip Thelen <phillip@habitica.com> Date: Mon Feb 5 19:11:20 2024 +0100 remove log commit 292f3a578d51fd08c572afc574cc73d08356f46a Author: Phillip Thelen <phillip@habitica.com> Date: Mon Feb 5 19:10:13 2024 +0100 Automatically set official field on challenges if habitica_official cateogory is set
163 lines
4.8 KiB
JavaScript
163 lines
4.8 KiB
JavaScript
// Currently this holds helpers for challenge api,
|
|
// but we should break this up into submodules as it expands
|
|
import omit from 'lodash/omit';
|
|
import { v4 as uuid } from 'uuid';
|
|
import { model as Challenge } from '../../models/challenge';
|
|
import {
|
|
model as Group,
|
|
TAVERN_ID,
|
|
} from '../../models/group';
|
|
import {
|
|
BadRequest,
|
|
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) {
|
|
const groupId = req.body.group;
|
|
const { prize } = req.body;
|
|
|
|
const 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.type === 'guild' && group._id !== TAVERN_ID && !group.hasActiveGroupPlan()) {
|
|
throw new BadRequest(res.t('featureRetired'));
|
|
}
|
|
|
|
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'));
|
|
}
|
|
|
|
group.challengeCount += 1;
|
|
|
|
if (!req.body.summary) {
|
|
req.body.summary = req.body.name;
|
|
}
|
|
req.body.leader = user._id;
|
|
req.body.official = !!(user.hasPermission('challengeAdmin') && req.body.official);
|
|
const categories = req.body.categories || [];
|
|
categories.forEach(category => {
|
|
if (category.slug === 'habitica_official' && !user.hasPermission('challengeAdmin')) {
|
|
throw new NotAuthorized(res.t('noPrivAccess'));
|
|
} else if (category.slug === 'habitica_official' && user.hasPermission('challengeAdmin')) {
|
|
req.body.official = true;
|
|
}
|
|
});
|
|
const challenge = new Challenge(Challenge.sanitize(req.body));
|
|
|
|
// First validate challenge so we don't save group if it's invalid (only runs sync validators)
|
|
const challengeValidationErrors = challenge.validateSync();
|
|
if (challengeValidationErrors) throw challengeValidationErrors;
|
|
|
|
if (prize > 0) {
|
|
const groupBalance = group.balance && group.leader === user._id ? group.balance : 0;
|
|
const 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;
|
|
|
|
await user.updateBalance(0, 'create_bank_challenge', challenge._id, challenge.name);
|
|
} else if (groupBalance > 0) {
|
|
// User pays remainder of prize cost after group
|
|
const remainder = prizeCost - group.balance;
|
|
group.balance = 0;
|
|
await user.updateBalance(-remainder, 'create_challenge', challenge._id, challenge.name);
|
|
} else {
|
|
// User pays for all of prize
|
|
await user.updateBalance(-prizeCost, 'create_challenge', challenge._id, challenge.name);
|
|
}
|
|
}
|
|
|
|
if (challenge.flagCount > 0) {
|
|
challenge.flagCount = 0;
|
|
challenge.flags = {};
|
|
}
|
|
|
|
const results = await Promise.all([challenge.save({
|
|
validateBeforeSave: false, // already validated
|
|
}), group.save(), user.save()]);
|
|
const savedChal = results[0];
|
|
|
|
return { savedChal, group };
|
|
}
|
|
|
|
export function cleanUpTask (task) {
|
|
const 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;
|
|
}
|
|
|
|
// Create an aggregation query for querying challenges.
|
|
// Ensures that official challenges are listed first.
|
|
export function createChallengeQuery (query) {
|
|
return Challenge.aggregate()
|
|
.match(query)
|
|
.addFields({
|
|
isOfficial: {
|
|
$cond: {
|
|
if: { $isArray: '$categories' },
|
|
then: {
|
|
$gt: [
|
|
{
|
|
$size: {
|
|
$filter: {
|
|
input: '$categories',
|
|
as: 'cat',
|
|
cond: {
|
|
$eq: ['$$cat.slug', 'habitica_official'],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
0,
|
|
],
|
|
},
|
|
else: false,
|
|
},
|
|
},
|
|
})
|
|
.sort('-isOfficial -createdAt');
|
|
}
|