mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 06:37:23 +01:00
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:
@@ -0,0 +1,43 @@
|
|||||||
|
import {
|
||||||
|
generateUser,
|
||||||
|
generateGroup,
|
||||||
|
} from '../../../../helpers/api-v3-integration.helper';
|
||||||
|
|
||||||
|
describe('POST /challenges/:challengeId/clone', () => {
|
||||||
|
it('clones a challenge', async () => {
|
||||||
|
const user = await generateUser({balance: 10});
|
||||||
|
const group = await generateGroup(user);
|
||||||
|
|
||||||
|
const name = 'Test Challenge';
|
||||||
|
const shortName = 'TC Label';
|
||||||
|
const description = 'Test Description';
|
||||||
|
const prize = 1;
|
||||||
|
|
||||||
|
const challenge = await user.post('/challenges', {
|
||||||
|
group: group._id,
|
||||||
|
name,
|
||||||
|
shortName,
|
||||||
|
description,
|
||||||
|
prize,
|
||||||
|
});
|
||||||
|
const challengeTask = await user.post(`/tasks/challenge/${challenge._id}`, {
|
||||||
|
text: 'test habit',
|
||||||
|
type: 'habit',
|
||||||
|
up: false,
|
||||||
|
down: true,
|
||||||
|
notes: 1976,
|
||||||
|
});
|
||||||
|
|
||||||
|
const cloneChallengeResponse = await user.post(`/challenges/${challenge._id}/clone`, {
|
||||||
|
group: group._id,
|
||||||
|
name: `${name} cloned`,
|
||||||
|
shortName,
|
||||||
|
description,
|
||||||
|
prize,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(cloneChallengeResponse.clonedTasks[0].text).to.eql(challengeTask.text);
|
||||||
|
expect(cloneChallengeResponse.clonedTasks[0]._id).to.not.eql(challengeTask._id);
|
||||||
|
expect(cloneChallengeResponse.clonedTasks[0].challenge.id).to.eql(cloneChallengeResponse.clonedChallenge._id);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template lang="pug">
|
<template lang="pug">
|
||||||
.row
|
.row
|
||||||
challenge-modal(:cloning='cloning' v-on:updatedChallenge='updatedChallenge')
|
challenge-modal(v-on:updatedChallenge='updatedChallenge')
|
||||||
leave-challenge-modal(:challengeId='challenge._id')
|
leave-challenge-modal(:challengeId='challenge._id')
|
||||||
close-challenge-modal(:members='members', :challengeId='challenge._id')
|
close-challenge-modal(:members='members', :challengeId='challenge._id')
|
||||||
challenge-member-progress-modal(:memberId='progressMemberId', :challengeId='challenge._id')
|
challenge-member-progress-modal(:memberId='progressMemberId', :challengeId='challenge._id')
|
||||||
@@ -220,7 +220,6 @@ export default {
|
|||||||
memberIcon,
|
memberIcon,
|
||||||
calendarIcon,
|
calendarIcon,
|
||||||
}),
|
}),
|
||||||
cloning: false,
|
|
||||||
challenge: {},
|
challenge: {},
|
||||||
members: [],
|
members: [],
|
||||||
tasksByType: {
|
tasksByType: {
|
||||||
@@ -261,10 +260,6 @@ export default {
|
|||||||
async beforeRouteUpdate (to, from, next) {
|
async beforeRouteUpdate (to, from, next) {
|
||||||
this.searchId = to.params.challengeId;
|
this.searchId = to.params.challengeId;
|
||||||
await this.loadChallenge();
|
await this.loadChallenge();
|
||||||
|
|
||||||
if (this.$store.state.challengeOptions.cloning) {
|
|
||||||
this.cloneTasks(this.$store.state.challengeOptions.tasksToClone);
|
|
||||||
}
|
|
||||||
next();
|
next();
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@@ -284,28 +279,6 @@ export default {
|
|||||||
|
|
||||||
return cleansedTask;
|
return cleansedTask;
|
||||||
},
|
},
|
||||||
cloneTasks (tasksToClone) {
|
|
||||||
let clonedTasks = [];
|
|
||||||
|
|
||||||
for (let key in tasksToClone) {
|
|
||||||
let tasksSection = tasksToClone[key];
|
|
||||||
tasksSection.forEach(task => {
|
|
||||||
let clonedTask = cloneDeep(task);
|
|
||||||
clonedTask = this.cleanUpTask(clonedTask);
|
|
||||||
clonedTask = taskDefaults(clonedTask);
|
|
||||||
this.tasksByType[task.type].push(clonedTask);
|
|
||||||
clonedTasks.push(clonedTask);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
this.$store.dispatch('tasks:createChallengeTasks', {
|
|
||||||
challengeId: this.searchId,
|
|
||||||
tasks: clonedTasks,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.$store.state.challengeOptions.cloning = false;
|
|
||||||
this.$store.state.challengeOptions.tasksToClone = [];
|
|
||||||
},
|
|
||||||
async loadChallenge () {
|
async loadChallenge () {
|
||||||
this.challenge = await this.$store.dispatch('challenges:getChallenge', {challengeId: this.searchId});
|
this.challenge = await this.$store.dispatch('challenges:getChallenge', {challengeId: this.searchId});
|
||||||
this.members = await this.$store.dispatch('members:getChallengeMembers', {challengeId: this.searchId});
|
this.members = await this.$store.dispatch('members:getChallengeMembers', {challengeId: this.searchId});
|
||||||
@@ -377,7 +350,6 @@ export default {
|
|||||||
},
|
},
|
||||||
edit () {
|
edit () {
|
||||||
// @TODO: set working challenge
|
// @TODO: set working challenge
|
||||||
this.cloning = false;
|
|
||||||
this.$store.state.challengeOptions.workingChallenge = Object.assign({}, this.$store.state.challengeOptions.workingChallenge, this.challenge);
|
this.$store.state.challengeOptions.workingChallenge = Object.assign({}, this.$store.state.challengeOptions.workingChallenge, this.challenge);
|
||||||
this.$root.$emit('bv::show::modal', 'challenge-modal');
|
this.$root.$emit('bv::show::modal', 'challenge-modal');
|
||||||
},
|
},
|
||||||
@@ -396,10 +368,9 @@ export default {
|
|||||||
window.location = `/api/v3/challenges/${this.searchId}/export/csv`;
|
window.location = `/api/v3/challenges/${this.searchId}/export/csv`;
|
||||||
},
|
},
|
||||||
cloneChallenge () {
|
cloneChallenge () {
|
||||||
this.cloning = true;
|
this.$root.$emit('habitica:clone-challenge', {
|
||||||
this.$store.state.challengeOptions.tasksToClone = this.tasksByType;
|
challenge: this.challenge,
|
||||||
this.$store.state.challengeOptions.workingChallenge = Object.assign({}, this.$store.state.challengeOptions.workingChallenge, this.challenge);
|
});
|
||||||
this.$root.$emit('bv::show::modal', 'challenge-modal');
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ import { TAVERN_ID, MIN_SHORTNAME_SIZE_FOR_CHALLENGES, MAX_SUMMARY_SIZE_FOR_CHAL
|
|||||||
import { mapState } from 'client/libs/store';
|
import { mapState } from 'client/libs/store';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
props: ['groupId', 'cloning'],
|
props: ['groupId'],
|
||||||
directives: {
|
directives: {
|
||||||
markdown: markdownDirective,
|
markdown: markdownDirective,
|
||||||
},
|
},
|
||||||
@@ -224,6 +224,8 @@ export default {
|
|||||||
shortName: '',
|
shortName: '',
|
||||||
todos: [],
|
todos: [],
|
||||||
},
|
},
|
||||||
|
cloning: false,
|
||||||
|
cloningChallengeId: '',
|
||||||
showCategorySelect: false,
|
showCategorySelect: false,
|
||||||
categoryOptions,
|
categoryOptions,
|
||||||
categoriesHashByKey,
|
categoriesHashByKey,
|
||||||
@@ -231,7 +233,18 @@ export default {
|
|||||||
groups: [],
|
groups: [],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
async mounted () {},
|
mounted () {
|
||||||
|
this.$root.$on('habitica:clone-challenge', (data) => {
|
||||||
|
if (!data.challenge) return;
|
||||||
|
this.cloning = true;
|
||||||
|
this.cloningChallengeId = data.challenge._id;
|
||||||
|
this.$store.state.challengeOptions.workingChallenge = Object.assign({}, this.$store.state.challengeOptions.workingChallenge, data.challenge);
|
||||||
|
this.$root.$emit('bv::show::modal', 'challenge-modal');
|
||||||
|
});
|
||||||
|
},
|
||||||
|
destroyed () {
|
||||||
|
this.$root.$off('habitica:clone-challenge');
|
||||||
|
},
|
||||||
watch: {
|
watch: {
|
||||||
user () {
|
user () {
|
||||||
if (!this.challenge) this.workingChallenge.leader = this.user._id;
|
if (!this.challenge) this.workingChallenge.leader = this.user._id;
|
||||||
@@ -252,7 +265,6 @@ export default {
|
|||||||
if (this.creating) {
|
if (this.creating) {
|
||||||
return this.$t('createChallenge');
|
return this.$t('createChallenge');
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.$t('editingChallenge');
|
return this.$t('editingChallenge');
|
||||||
},
|
},
|
||||||
charactersRemaining () {
|
charactersRemaining () {
|
||||||
@@ -322,6 +334,8 @@ export default {
|
|||||||
if (!this.challenge) return;
|
if (!this.challenge) return;
|
||||||
|
|
||||||
this.workingChallenge = Object.assign({}, this.workingChallenge, this.challenge);
|
this.workingChallenge = Object.assign({}, this.workingChallenge, this.challenge);
|
||||||
|
// @TODO: Should we use a separate field? I think the API expects `group` but it is confusing
|
||||||
|
this.workingChallenge.group = this.workingChallenge.group._id;
|
||||||
this.workingChallenge.categories = [];
|
this.workingChallenge.categories = [];
|
||||||
|
|
||||||
if (this.challenge.categories) {
|
if (this.challenge.categories) {
|
||||||
@@ -388,15 +402,40 @@ export default {
|
|||||||
let challengeDetails = clone(this.workingChallenge);
|
let challengeDetails = clone(this.workingChallenge);
|
||||||
challengeDetails.categories = serverCategories;
|
challengeDetails.categories = serverCategories;
|
||||||
|
|
||||||
let challenge = await this.$store.dispatch('challenges:createChallenge', {challenge: challengeDetails});
|
let challenge;
|
||||||
// @TODO: When to remove from guild instead?
|
if (this.cloning) {
|
||||||
this.user.balance -= this.workingChallenge.prize / 4;
|
challenge = await this.$store.dispatch('challenges:cloneChallenge', {
|
||||||
|
challenge: challengeDetails,
|
||||||
|
cloningChallengeId: this.cloningChallengeId,
|
||||||
|
});
|
||||||
|
this.cloningChallengeId = '';
|
||||||
|
} else {
|
||||||
|
challenge = await this.$store.dispatch('challenges:createChallenge', {challenge: challengeDetails});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update Group Prize
|
||||||
|
let challengeGroup = this.groups.find(group => {
|
||||||
|
return group._id === this.workingChallenge.group;
|
||||||
|
});
|
||||||
|
|
||||||
|
// @TODO: Share with server
|
||||||
|
const prizeCost = this.workingChallenge.prize / 4;
|
||||||
|
const challengeGroupLeader = challengeGroup.leader && challengeGroup.leader._id ? challengeGroup.leader._id : challengeGroup.leader;
|
||||||
|
const userIsLeader = challengeGroupLeader === this.user._id;
|
||||||
|
if (challengeGroup && userIsLeader && challengeGroup.balance > 0 && challengeGroup.balance >= prizeCost) {
|
||||||
|
// Group pays for all of prize
|
||||||
|
} else if (challengeGroup && userIsLeader && challengeGroup.balance > 0) {
|
||||||
|
// User pays remainder of prize cost after group
|
||||||
|
let remainder = prizeCost - challengeGroup.balance;
|
||||||
|
this.user.balance -= remainder;
|
||||||
|
} else {
|
||||||
|
// User pays for all of prize
|
||||||
|
this.user.balance -= prizeCost;
|
||||||
|
}
|
||||||
|
|
||||||
this.$emit('createChallenge', challenge);
|
this.$emit('createChallenge', challenge);
|
||||||
this.resetWorkingChallenge();
|
this.resetWorkingChallenge();
|
||||||
|
|
||||||
if (this.cloning) this.$store.state.challengeOptions.cloning = true;
|
|
||||||
|
|
||||||
this.$root.$emit('bv::hide::modal', 'challenge-modal');
|
this.$root.$emit('bv::hide::modal', 'challenge-modal');
|
||||||
this.$router.push(`/challenges/${challenge._id}`);
|
this.$router.push(`/challenges/${challenge._id}`);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -10,6 +10,13 @@ export async function createChallenge (store, payload) {
|
|||||||
return newChallenge;
|
return newChallenge;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export async function cloneChallenge (store, payload) {
|
||||||
|
const response = await axios.post(`/api/v3/challenges/${payload.cloningChallengeId}/clone`, payload.challenge);
|
||||||
|
const newChallenge = response.data.data.clonedChallenge;
|
||||||
|
store.state.user.data.challenges.push(newChallenge._id);
|
||||||
|
return newChallenge;
|
||||||
|
}
|
||||||
|
|
||||||
export async function joinChallenge (store, payload) {
|
export async function joinChallenge (store, payload) {
|
||||||
let response = await axios.post(`/api/v3/challenges/${payload.challengeId}/join`);
|
let response = await axios.post(`/api/v3/challenges/${payload.challengeId}/join`);
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
import { authWithHeaders, authWithSession } from '../../middlewares/auth';
|
import { authWithHeaders, authWithSession } from '../../middlewares/auth';
|
||||||
import _ from 'lodash';
|
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 Challenge } from '../../models/challenge';
|
||||||
import {
|
import {
|
||||||
model as Group,
|
model as Group,
|
||||||
@@ -17,9 +20,81 @@ import {
|
|||||||
import * as Tasks from '../../models/task';
|
import * as Tasks from '../../models/task';
|
||||||
import Bluebird from 'bluebird';
|
import Bluebird from 'bluebird';
|
||||||
import csvStringify from '../../libs/csvStringify';
|
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 = {};
|
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.
|
||||||
@@ -179,71 +254,10 @@ api.createChallenge = {
|
|||||||
|
|
||||||
req.checkBody('group', res.t('groupIdRequired')).notEmpty();
|
req.checkBody('group', res.t('groupIdRequired')).notEmpty();
|
||||||
|
|
||||||
let validationErrors = req.validationErrors();
|
const validationErrors = req.validationErrors();
|
||||||
if (validationErrors) throw validationErrors;
|
if (validationErrors) throw validationErrors;
|
||||||
|
|
||||||
let groupId = req.body.group;
|
const {savedChal, group} = await createChallenge(user, req, res);
|
||||||
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)
|
|
||||||
|
|
||||||
let response = savedChal.toJSON();
|
let response = savedChal.toJSON();
|
||||||
response.leader = { // the leader is the authenticated user
|
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;
|
module.exports = api;
|
||||||
|
|||||||
Reference in New Issue
Block a user