feat: closes #8316, add more data to export profile

v1.18.x
Barış Soner Uşaklı 5 years ago
parent 1d3fa3bc4e
commit f0323b6cfa

@ -65,7 +65,7 @@
"ipaddr.js": "^1.9.1", "ipaddr.js": "^1.9.1",
"jquery": "3.5.1", "jquery": "3.5.1",
"jsesc": "3.0.1", "jsesc": "3.0.1",
"json-2-csv": "^3.6.2", "json2csv": "5.0.1",
"jsonwebtoken": "^8.5.1", "jsonwebtoken": "^8.5.1",
"less": "^3.11.1", "less": "^3.11.1",
"lodash": "^4.17.15", "lodash": "^4.17.15",

@ -191,7 +191,7 @@
"consent.right_to_data_portability": "You have the Right to Data Portability", "consent.right_to_data_portability": "You have the Right to Data Portability",
"consent.right_to_data_portability_description": "You may request from us a machine-readable export of any collected data about you and your account. You can do so by clicking the appropriate button below.", "consent.right_to_data_portability_description": "You may request from us a machine-readable export of any collected data about you and your account. You can do so by clicking the appropriate button below.",
"consent.export_profile": "Export Profile (.csv)", "consent.export_profile": "Export Profile (.json)",
"consent.export_uploads": "Export Uploaded Content (.zip)", "consent.export_uploads": "Export Uploaded Content (.zip)",
"consent.export_posts": "Export Posts (.csv)" "consent.export_posts": "Export Posts (.csv)"
} }

@ -1,7 +1,6 @@
'use strict'; 'use strict';
const json2csv = require('json-2-csv').json2csv; const json2csvAsync = require('json2csv').parseAsync;
const util = require('util');
const meta = require('../../meta'); const meta = require('../../meta');
const analytics = require('../../analytics'); const analytics = require('../../analytics');
@ -17,12 +16,10 @@ errorsController.get = async function (req, res) {
res.render('admin/advanced/errors', data); res.render('admin/advanced/errors', data);
}; };
const json2csvAsync = util.promisify(function (data, callback) {
json2csv(data, (err, output) => callback(err, output));
});
errorsController.export = async function (req, res) { errorsController.export = async function (req, res) {
const data = await meta.errors.get(false); const data = await meta.errors.get(false);
const csv = await json2csvAsync(data); const fields = data.length ? Object.keys(data[0]) : [];
const opts = { fields };
const csv = await json2csvAsync(data, opts);
res.set('Content-Type', 'text/csv').set('Content-Disposition', 'attachment; filename="404.csv"').send(csv); res.set('Content-Type', 'text/csv').set('Content-Disposition', 'attachment; filename="404.csv"').send(csv);
}; };

@ -1,11 +1,11 @@
'use strict'; 'use strict';
const _ = require('lodash');
const path = require('path'); const path = require('path');
const fs = require('fs'); const fs = require('fs');
const winston = require('winston'); const winston = require('winston');
const converter = require('json-2-csv'); const json2csvAsync = require('json2csv').parseAsync;
const archiver = require('archiver'); const archiver = require('archiver');
const util = require('util');
const db = require('../database'); const db = require('../database');
const user = require('../user'); const user = require('../user');
@ -85,10 +85,6 @@ userController.getUserDataByUID = async function (callerUid, uid) {
return userData; return userData;
}; };
const json2csv = util.promisify(function (payload, options, callback) {
converter.json2csv(payload, callback, options);
});
userController.exportPosts = async function (req, res) { userController.exportPosts = async function (req, res) {
var payload = []; var payload = [];
await batch.processSortedSet('uid:' + res.locals.uid + ':posts', async function (pids) { await batch.processSortedSet('uid:' + res.locals.uid + ':posts', async function (pids) {
@ -103,10 +99,9 @@ userController.exportPosts = async function (req, res) {
batch: 500, batch: 500,
}); });
const csv = await json2csv(payload, { const fields = payload.length ? Object.keys(payload[0]) : [];
checkSchemaDifferences: false, const opts = { fields };
emptyFieldValue: '', const csv = await json2csvAsync(payload, opts);
});
res.set('Content-Type', 'text/csv').set('Content-Disposition', 'attachment; filename="' + res.locals.uid + '_posts.csv"').send(csv); res.set('Content-Type', 'text/csv').set('Content-Disposition', 'attachment; filename="' + res.locals.uid + '_posts.csv"').send(csv);
}; };
@ -192,15 +187,67 @@ userController.exportUploads = function (req, res, next) {
}; };
userController.exportProfile = async function (req, res) { userController.exportProfile = async function (req, res) {
const targetUid = res.locals.uid; const targetUid = parseInt(res.locals.uid, 10);
const objects = await db.getObjects(['user:' + targetUid, 'user:' + targetUid + ':settings']); const [userData, userSettings, ips, sessions, usernames, emails, bookmarks, watchedTopics, upvoted, downvoted, following] = await Promise.all([
Object.assign(objects[0], objects[1]); db.getObject('user:' + targetUid),
delete objects[0].password; db.getObject('user:' + targetUid + ':settings'),
user.getIPs(targetUid, 9),
const csv = await json2csv(objects[0], {}); user.auth.getSessions(targetUid, req.sessionID),
res.set('Content-Type', 'text/csv').set('Content-Disposition', 'attachment; filename="' + targetUid + '_profile.csv"').send(csv); user.getHistory('user:' + targetUid + ':usernames'),
user.getHistory('user:' + targetUid + ':emails'),
getSetData('uid:' + targetUid + ':bookmarks', 'post:'),
getSetData('uid:' + targetUid + ':followed_tids', 'topic:'),
getSetData('uid:' + targetUid + ':upvote', 'post:'),
getSetData('uid:' + targetUid + ':downvote', 'post:'),
getSetData('following:' + targetUid, 'user:'),
]);
delete userData.password;
const followingData = following.map(u => ({ username: u.username, uid: u.uid }));
let chatData = [];
await batch.processSortedSet('uid:' + targetUid + ':chat:rooms', async (roomIds) => {
var result = await Promise.all(roomIds.map(roomId => getRoomMessages(targetUid, roomId)));
chatData = chatData.concat(_.flatten(result));
}, { batch: 100 });
res.set('Content-Type', 'application/json')
.set('Content-Disposition', 'attachment; filename="' + targetUid + '_profile.json"')
.send({
user: userData,
settings: userSettings,
ips: ips,
sessions: sessions,
usernames: usernames,
emails: emails,
messages: chatData,
bookmarks: bookmarks,
watchedTopics: watchedTopics,
upvoted: upvoted,
downvoted: downvoted,
following: followingData,
});
}; };
async function getRoomMessages(uid, roomId) {
let data = [];
await batch.processSortedSet('uid:' + uid + ':chat:room:' + roomId + ':mids', async (mids) => {
const messageData = await db.getObjects(mids.map(mid => 'message:' + mid));
data = data.concat(messageData.filter(m => m && m.fromuid === uid && !m.system)
.map(m => ({ content: m.content, timestamp: m.timestamp }))
);
}, { batch: 500 });
return data;
}
async function getSetData(set, keyPrefix) {
let data = [];
await batch.processSortedSet(set, async (ids) => {
data = data.concat(await db.getObjects(ids.map(mid => keyPrefix + mid)));
}, { batch: 500 });
return data;
}
require('../promisify')(userController, [ require('../promisify')(userController, [
'getCurrentUser', 'getUserByUID', 'getUserByUsername', 'getUserByEmail', 'getCurrentUser', 'getUserByUID', 'getUserByUsername', 'getUserByEmail',
'exportPosts', 'exportUploads', 'exportProfile', 'exportPosts', 'exportUploads', 'exportProfile',

Loading…
Cancel
Save