mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: closes #8316, add more data to export profile
This commit is contained in:
@@ -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),
|
||||||
|
user.auth.getSessions(targetUid, req.sessionID),
|
||||||
|
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 }));
|
||||||
|
|
||||||
const csv = await json2csv(objects[0], {});
|
let chatData = [];
|
||||||
res.set('Content-Type', 'text/csv').set('Content-Disposition', 'attachment; filename="' + targetUid + '_profile.csv"').send(csv);
|
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',
|
||||||
|
|||||||
Reference in New Issue
Block a user