mirror of
https://github.com/HabitRPG/habitica.git
synced 2025-12-17 14:47:53 +01:00
* Updated API Docs for api-v3 * Updated API Docs for top-level * Updates relating to @crookedneighbor comments * Updated type and field of 'to' param.
247 lines
6.7 KiB
JavaScript
247 lines
6.7 KiB
JavaScript
import { authWithSession } from '../../middlewares/api-v3/auth';
|
|
import { model as User } from '../../models/user';
|
|
import * as Tasks from '../../models/task';
|
|
import {
|
|
NotFound,
|
|
} from '../../libs/api-v3/errors';
|
|
import _ from 'lodash';
|
|
import csvStringify from '../../libs/api-v3/csvStringify';
|
|
import moment from 'moment';
|
|
import js2xml from 'js2xmlparser';
|
|
import Pageres from 'pageres';
|
|
import nconf from 'nconf';
|
|
import got from 'got';
|
|
import Bluebird from 'bluebird';
|
|
import locals from '../../middlewares/api-v3/locals';
|
|
import {
|
|
S3,
|
|
} from '../../libs/api-v3/aws';
|
|
|
|
const S3_BUCKET = nconf.get('S3:bucket');
|
|
|
|
const BASE_URL = nconf.get('BASE_URL');
|
|
|
|
let api = {};
|
|
|
|
/**
|
|
* @api {get} /export/history.csv Export user tasks history in CSV format
|
|
* @apiDescription History is only available for habits and dailies so todos and rewards won't be included. NOTE: Part of the private API that may change at any time.
|
|
* @apiVersion 3.0.0
|
|
* @apiName ExportUserHistory
|
|
* @apiGroup DataExport
|
|
*
|
|
* @apiSuccess {String} A csv file
|
|
*/
|
|
api.exportUserHistory = {
|
|
method: 'GET',
|
|
url: '/export/history.csv',
|
|
middlewares: [authWithSession],
|
|
async handler (req, res) {
|
|
let user = res.locals.user;
|
|
|
|
let tasks = await Tasks.Task.find({
|
|
userId: user._id,
|
|
type: {$in: ['habit', 'daily']},
|
|
}).exec();
|
|
|
|
let output = [
|
|
['Task Name', 'Task ID', 'Task Type', 'Date', 'Value'],
|
|
];
|
|
|
|
tasks.forEach(task => {
|
|
task.history.forEach(history => {
|
|
output.push([
|
|
task.text,
|
|
task._id,
|
|
task.type,
|
|
moment(history.date).format('YYYY-MM-DD HH:mm:ss'),
|
|
history.value,
|
|
]);
|
|
});
|
|
});
|
|
|
|
res.set({
|
|
'Content-Type': 'text/csv',
|
|
'Content-disposition': 'attachment; filename=habitica-tasks-history.csv',
|
|
});
|
|
|
|
let csvRes = await csvStringify(output);
|
|
res.status(200).send(csvRes);
|
|
},
|
|
};
|
|
|
|
// Convert user to json and attach tasks divided by type
|
|
// at user.tasks[`${taskType}s`] (user.tasks.{dailys/habits/...})
|
|
async function _getUserDataForExport (user) {
|
|
let userData = user.toJSON();
|
|
userData.tasks = {};
|
|
|
|
let tasks = await Tasks.Task.find({
|
|
userId: user._id,
|
|
}).exec();
|
|
|
|
tasks = _.chain(tasks)
|
|
.map(task => task.toJSON())
|
|
.groupBy(task => task.type)
|
|
.each((tasksPerType, taskType) => {
|
|
userData.tasks[`${taskType}s`] = tasksPerType;
|
|
})
|
|
.value();
|
|
|
|
return userData;
|
|
}
|
|
|
|
/**
|
|
* @api {get} /export/userdata.json Export user data in JSON format
|
|
* @apiVersion 3.0.0
|
|
* @apiName ExportUserDataJson
|
|
* @apiGroup DataExport
|
|
* @apiDescription NOTE: Part of the private API that may change at any time.
|
|
*
|
|
* @apiSuccess {String} A json file
|
|
*/
|
|
api.exportUserDataJson = {
|
|
method: 'GET',
|
|
url: '/export/userdata.json',
|
|
middlewares: [authWithSession],
|
|
async handler (req, res) {
|
|
let userData = await _getUserDataForExport(res.locals.user);
|
|
|
|
res.set({
|
|
'Content-Type': 'application/json',
|
|
'Content-disposition': 'attachment; filename=habitica-user-data.json',
|
|
});
|
|
let jsonRes = JSON.stringify(userData);
|
|
|
|
res.status(200).send(jsonRes);
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {get} /export/userdata.xml Export user data in XML format
|
|
* @apiVersion 3.0.0
|
|
* @apiName ExportUserDataXml
|
|
* @apiGroup DataExport
|
|
* @apiDescription NOTE: Part of the private API that may change at any time.
|
|
*
|
|
* @apiSuccess {String} A xml file
|
|
*/
|
|
api.exportUserDataXml = {
|
|
method: 'GET',
|
|
url: '/export/userdata.xml',
|
|
middlewares: [authWithSession],
|
|
async handler (req, res) {
|
|
let userData = await _getUserDataForExport(res.locals.user);
|
|
|
|
res.set({
|
|
'Content-Type': 'text/xml',
|
|
'Content-disposition': 'attachment; filename=habitica-user-data.xml',
|
|
});
|
|
res.status(200).send(js2xml('user', userData));
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {get} /export/avatar-:uuid.html Render a user avatar as an HTML page
|
|
* @apiVersion 3.0.0
|
|
* @apiName ExportUserAvatarHtml
|
|
* @apiGroup DataExport
|
|
* @apiDescription NOTE: Part of the private API that may change at any time.
|
|
*
|
|
* @apiSuccess {String} An html page
|
|
*/
|
|
api.exportUserAvatarHtml = {
|
|
method: 'GET',
|
|
url: '/export/avatar-:memberId.html',
|
|
middlewares: [locals],
|
|
async handler (req, res) {
|
|
req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
|
|
|
|
let validationErrors = req.validationErrors();
|
|
if (validationErrors) throw validationErrors;
|
|
|
|
let memberId = req.params.memberId;
|
|
let member = await User
|
|
.findById(memberId)
|
|
.select('stats profile items achievements preferences backer contributor')
|
|
.exec();
|
|
|
|
if (!member) throw new NotFound(res.t('userWithIDNotFound', {userId: memberId}));
|
|
res.render('avatar-static', {
|
|
title: member.profile.name,
|
|
env: _.defaults({user: member}, res.locals.habitrpg),
|
|
});
|
|
},
|
|
};
|
|
|
|
/**
|
|
* @api {get} /export/avatar-:uuid.png Render a user avatar as a PNG file
|
|
* @apiVersion 3.0.0
|
|
* @apiName ExportUserAvatarPng
|
|
* @apiGroup DataExport
|
|
* @apiDescription NOTE: Part of the private API that may change at any time.
|
|
*
|
|
* @apiSuccess {String} A png file
|
|
*/
|
|
api.exportUserAvatarPng = {
|
|
method: 'GET',
|
|
url: '/export/avatar-:memberId.png',
|
|
async handler (req, res) {
|
|
req.checkParams('memberId', res.t('memberIdRequired')).notEmpty().isUUID();
|
|
|
|
let validationErrors = req.validationErrors();
|
|
if (validationErrors) throw validationErrors;
|
|
|
|
let memberId = req.params.memberId;
|
|
|
|
let filename = `avatars/${memberId}.png`;
|
|
let s3url = `https://${S3_BUCKET}.s3.amazonaws.com/${filename}`;
|
|
|
|
let response;
|
|
try {
|
|
response = await got.head(s3url);
|
|
} catch (gotError) {
|
|
// If the file does not exist AWS S3 can return a 403 error
|
|
if (gotError.code !== 'ENOTFOUND' && gotError.statusCode !== 404 && gotError.statusCode !== 403) {
|
|
throw gotError;
|
|
}
|
|
}
|
|
|
|
// cache images for 30 minutes on aws, else upload a new one
|
|
if (response && response.statusCode === 200 && moment().diff(response.headers['last-modified'], 'minutes') < 30) {
|
|
return res.redirect(s3url);
|
|
}
|
|
|
|
let [stream] = await new Pageres()
|
|
.src(`${BASE_URL}/export/avatar-${memberId}.html`, ['140x147'], {
|
|
crop: true,
|
|
filename: filename.replace('.png', ''),
|
|
})
|
|
.run();
|
|
|
|
let s3upload = S3.upload({
|
|
Bucket: S3_BUCKET,
|
|
Key: filename,
|
|
ACL: 'public-read',
|
|
StorageClass: 'REDUCED_REDUNDANCY',
|
|
ContentType: 'image/png',
|
|
Expires: moment().add({minutes: 5}).toDate(),
|
|
Body: stream,
|
|
});
|
|
|
|
let s3res = await new Bluebird((resolve, reject) => {
|
|
s3upload.send((err, s3uploadRes) => {
|
|
if (err) {
|
|
reject(err);
|
|
} else {
|
|
resolve(s3uploadRes);
|
|
}
|
|
});
|
|
});
|
|
|
|
res.redirect(s3res.Location);
|
|
},
|
|
};
|
|
|
|
module.exports = api;
|