mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-19 15:48:04 +01:00
add exportChallengeCsv route (missing tests)
This commit is contained in:
@@ -20,6 +20,7 @@
|
|||||||
"cookie-parser": "^1.4.0",
|
"cookie-parser": "^1.4.0",
|
||||||
"cookie-session": "^1.2.0",
|
"cookie-session": "^1.2.0",
|
||||||
"coupon-code": "~0.3.0",
|
"coupon-code": "~0.3.0",
|
||||||
|
"csv-stringify": "^1.0.1",
|
||||||
"domain-middleware": "~0.1.0",
|
"domain-middleware": "~0.1.0",
|
||||||
"estraverse": "^4.1.1",
|
"estraverse": "^4.1.1",
|
||||||
"express": "~4.13.3",
|
"express": "~4.13.3",
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { model as Challenge } from '../../models/challenge';
|
|||||||
import { model as Group } from '../../models/group';
|
import { model as Group } from '../../models/group';
|
||||||
import {
|
import {
|
||||||
model as User,
|
model as User,
|
||||||
|
nameFields,
|
||||||
} from '../../models/user';
|
} from '../../models/user';
|
||||||
import {
|
import {
|
||||||
NotFound,
|
NotFound,
|
||||||
@@ -15,6 +16,7 @@ import * as Tasks from '../../models/task';
|
|||||||
import { txnEmail } from '../../libs/api-v3/email';
|
import { txnEmail } from '../../libs/api-v3/email';
|
||||||
import pushNotify from '../../libs/api-v3/pushNotifications';
|
import pushNotify from '../../libs/api-v3/pushNotifications';
|
||||||
import Q from 'q';
|
import Q from 'q';
|
||||||
|
import csvStringify from '../../libs/api-v3/csvStringify';
|
||||||
|
|
||||||
let api = {};
|
let api = {};
|
||||||
|
|
||||||
@@ -239,6 +241,78 @@ api.getChallenge = {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @api {get} /challenges/:challengeId/export/csv Export a challenge in CSV
|
||||||
|
* @apiVersion 3.0.0
|
||||||
|
* @apiName ExportChallengeCsv
|
||||||
|
* @apiGroup Challenge
|
||||||
|
*
|
||||||
|
* @apiParam {UUID} challengeId The challenge _id
|
||||||
|
*
|
||||||
|
* @apiSuccess {object} challenge The challenge object
|
||||||
|
*/
|
||||||
|
api.exportChallengeCsv = {
|
||||||
|
method: 'GET',
|
||||||
|
url: '/challenges/:challengeId/export/csv',
|
||||||
|
middlewares: [authWithHeaders(), cron],
|
||||||
|
async handler (req, res) {
|
||||||
|
req.checkParams('challengeId', res.t('challengeIdRequired')).notEmpty().isUUID();
|
||||||
|
|
||||||
|
let validationErrors = req.validationErrors();
|
||||||
|
if (validationErrors) throw validationErrors;
|
||||||
|
|
||||||
|
let user = res.locals.user;
|
||||||
|
let challengeId = req.params.challengeId;
|
||||||
|
|
||||||
|
let challenge = await Challenge.findById(challengeId).select('_id groupId leader').exec();
|
||||||
|
if (!challenge) throw new NotFound(res.t('challengeNotFound'));
|
||||||
|
let group = await Group.getGroup({user, groupId: challenge.groupId, fields: '_id type privacy', optionalMembership: true});
|
||||||
|
if (!group || !challenge.canView(user, group)) throw new NotFound(res.t('challengeNotFound'));
|
||||||
|
|
||||||
|
// In v2 this used the aggregation framework to run some computation on MongoDB but then iterated through all
|
||||||
|
// results on the server so the perf difference isn't that big (hopefully)
|
||||||
|
|
||||||
|
let challengeTasks = _.reduce(challenge.tasksOrder, (result, array) => {
|
||||||
|
return result.concat(array);
|
||||||
|
}, []).sort();
|
||||||
|
|
||||||
|
let [members, tasks] = await Q.all([
|
||||||
|
User.find({challenges: challengeId})
|
||||||
|
.select(nameFields)
|
||||||
|
.sortBy({_id: 1})
|
||||||
|
.lean() // so we don't involve mongoose
|
||||||
|
.exec(),
|
||||||
|
|
||||||
|
Tasks.Task.find({'task.challenge.id': challengeId, userId: {$exists: true}})
|
||||||
|
.sortBy({userId: 1, _id: 1}).select('userId type text value notes').lean().exec(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let resArray = members.map(member => [member._id, member.profile.name]);
|
||||||
|
|
||||||
|
// We assume every user in the challenge as at least some data so we can say that members[0] tasks will be at tasks [0]
|
||||||
|
let lastUserId;
|
||||||
|
let index = -1;
|
||||||
|
tasks.forEach(task => {
|
||||||
|
if (task.userId !== lastUserId) {
|
||||||
|
lastUserId = task.userId;
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
|
||||||
|
resArray[index].push(`${task.type}:${task.text}`, task.value, task.notes);
|
||||||
|
});
|
||||||
|
|
||||||
|
// The first row is going to be UUID name Task Value Notes repeated n times for the n challenge tasks
|
||||||
|
resArray.unshift(['UUID', 'name']);
|
||||||
|
_.times(challengeTasks.length, () => resArray[0].push('Task', 'Value', 'Notes'));
|
||||||
|
|
||||||
|
res.set({
|
||||||
|
'Content-Type': 'text/csv',
|
||||||
|
'Content-disposition': `attachment; filename=${challengeId}.csv`,
|
||||||
|
});
|
||||||
|
res.status(200).send(await csvStringify(resArray));
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @api {put} /challenges/:challengeId Update a challenge
|
* @api {put} /challenges/:challengeId Update a challenge
|
||||||
* @apiVersion 3.0.0
|
* @apiVersion 3.0.0
|
||||||
|
|||||||
11
website/src/libs/api-v3/csvStringify.js
Normal file
11
website/src/libs/api-v3/csvStringify.js
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
import csvStringify from 'csv-stringify';
|
||||||
|
import Q from 'q';
|
||||||
|
|
||||||
|
export default function (input) {
|
||||||
|
return Q.promise((resolve, reject) => {
|
||||||
|
csvStringify(input, (err, output) => {
|
||||||
|
if (err) return reject(err);
|
||||||
|
return resolve(output);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
@@ -48,9 +48,9 @@ schema.methods.canModify = function canModifyChallenge (user) {
|
|||||||
|
|
||||||
// Returns true if user has access to the challenge (can join)
|
// Returns true if user has access to the challenge (can join)
|
||||||
schema.methods.hasAccess = function hasAccessToChallenge (user) {
|
schema.methods.hasAccess = function hasAccessToChallenge (user) {
|
||||||
let userGroups = user.guilds.slice(0);
|
let userGroups = user.guilds.slice(0); // clone user.guilds so we don't modify the original
|
||||||
if (user.party._id) userGroups.push(user.party._id);
|
if (user.party._id) userGroups.push(user.party._id);
|
||||||
userGroups.push('habitrpg'); // tavern challenges
|
userGroups.push('habitrpg'); // tavern
|
||||||
return this.canModify(user) || userGroups.indexOf(this.groupId) !== -1;
|
return this.canModify(user) || userGroups.indexOf(this.groupId) !== -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user