fix: #8969, export csv to file

v1.18.x
Barış Soner Uşaklı 4 years ago
parent 007a3258a0
commit 6e6a7a8f8a

@ -102,5 +102,7 @@
"alerts.prompt-email": "Emails: ",
"alerts.email-sent-to": "An invitation email has been sent to %1",
"alerts.x-users-found": "%1 user(s) found, (%2 seconds)"
"alerts.x-users-found": "%1 user(s) found, (%2 seconds)",
"export-users-started": "Exporting users as csv, this might take a while. You will receive a notification when it is complete.",
"export-users-completed": "Users exported as csv, click here to download."
}

@ -50,6 +50,7 @@
"profile-exported": "<strong>%1</strong> profile exported, click to download",
"posts-exported": "<strong>%1</strong> posts exported, click to download",
"uploads-exported": "<strong>%1</strong> uploads exported, click to download",
"users-csv-exported": "Users csv exported, click to download",
"email-confirmed": "Email Confirmed",
"email-confirmed-message": "Thank you for validating your email. Your account is now fully activated.",

@ -13,6 +13,34 @@ define('admin/manage/users', [
ajaxify.go(window.location.pathname + '?' + qs);
});
$('.export-csv').on('click', function () {
socket.once('event:export-users-csv', function () {
app.removeAlert('export-users-start');
app.alert({
alert_id: 'export-users',
type: 'success',
title: '[[global:alert.success]]',
message: '[[admin/manage/users:export-users-completed]]',
clickfn: function () {
window.location.href = config.relative_path + '/api/admin/users/csv';
},
timeout: 0,
});
});
socket.emit('admin.user.exportUsersCSV', {}, function (err) {
if (err) {
return app.alertError(err);
}
app.alert({
alert_id: 'export-users-start',
message: '[[admin/manage/users:export-users-started]]',
timeout: (ajaxify.data.userCount / 5000) * 500,
});
});
return false;
});
function getSelectedUids() {
var uids = [];

@ -1,6 +1,5 @@
'use strict';
const nconf = require('nconf');
const validator = require('validator');
const user = require('../../user');
@ -242,7 +241,7 @@ async function render(req, res, data) {
filterBy.forEach(function (filter) {
data['filterBy_' + validator.escape(String(filter))] = true;
});
data.userCount = await db.getObjectField('global', 'userCount');
if (data.adminInviteOnly) {
data.showInviteButton = await privileges.users.isAdministrator(req.uid);
} else {
@ -252,19 +251,27 @@ async function render(req, res, data) {
res.render('admin/manage/users', data);
}
usersController.getCSV = async function (req, res) {
var referer = req.headers.referer;
if (!referer || !referer.replace(nconf.get('url'), '').startsWith('/admin/manage/users')) {
return res.status(403).send('[[error:invalid-origin]]');
}
events.log({
usersController.getCSV = async function (req, res, next) {
await events.log({
type: 'getUsersCSV',
uid: req.uid,
ip: req.ip,
});
const data = await user.getUsersCSV();
res.attachment('users.csv');
res.setHeader('Content-Type', 'text/csv');
res.end(data);
const path = require('path');
const { baseDir } = require('../../constants').paths;
res.sendFile('users.csv', {
root: path.join(baseDir, 'build/export'),
headers: {
'Content-Type': 'text/csv',
'Content-Disposition': 'attachment; filename=users.csv',
},
}, function (err) {
if (err) {
if (err.code === 'ENOENT') {
res.locals.isAPI = false;
return next();
}
return next(err);
}
});
};

@ -1,6 +1,7 @@
'use strict';
const async = require('async');
const winston = require('winston');
const db = require('../../database');
const api = require('../../api');
@ -157,3 +158,27 @@ User.loadGroups = async function (socket, uids) {
});
return { users: userData };
};
User.exportUsersCSV = async function (socket) {
await events.log({
type: 'exportUsersCSV',
uid: socket.uid,
ip: socket.ip,
});
setTimeout(async function () {
try {
await user.exportUsersCSV();
socket.emit('event:export-users-csv');
const notifications = require('../../notifications');
const n = await notifications.create({
bodyShort: '[[notifications:users-csv-exported]]',
path: '/api/admin/users/csv',
nid: 'users:csv:export',
from: socket.uid,
});
await notifications.push(n, [socket.uid]);
} catch (err) {
winston.error(err);
}
}, 0);
};

@ -1,9 +1,12 @@
'use strict';
const fs = require('fs');
const path = require('path');
const winston = require('winston');
const validator = require('validator');
const { baseDir } = require('../constants').paths;
const db = require('../database');
const plugins = require('../plugins');
const batch = require('../batch');
@ -36,11 +39,35 @@ module.exports = function (User) {
await batch.processSortedSet('users:joindate', async (uids) => {
const usersData = await User.getUsersFields(uids, data.fields);
csvContent += usersData.reduce((memo, user) => {
memo += user.email + ',' + user.username + ',' + user.uid + '\n';
memo += data.fields.map(field => user[field]).join(',') + '\n';
return memo;
}, '');
}, {});
return csvContent;
};
User.exportUsersCSV = async function () {
winston.verbose('[user/exportUsersCSV] Exporting User CSV data');
const data = await plugins.hooks.fire('filter:user.csvFields', { fields: ['email', 'username', 'uid'] });
const fd = await fs.promises.open(
path.join(baseDir, 'build/export', 'users.csv'),
'w'
);
fs.promises.appendFile(fd, data.fields.join(',') + '\n');
await batch.processSortedSet('users:joindate', async (uids) => {
const usersData = await User.getUsersFields(uids, data.fields.slice());
let line = '';
usersData.forEach(function (user) {
line += data.fields.map(field => user[field]).join(',') + '\n';
});
await fs.promises.appendFile(fd, line);
}, {
batch: 5000,
interval: 250,
});
await fd.close();
};
};

@ -6,7 +6,7 @@
<!-- IF showInviteButton -->
<button component="user/invite" class="btn btn-success"><i class="fa fa-users"></i> [[admin/manage/users:invite]]</button>
<!-- ENDIF showInviteButton -->
<a target="_blank" href="{config.relative_path}/api/admin/users/csv" class="btn btn-primary">[[admin/manage/users:download-csv]]</a>
<a target="_blank" href="#" class="btn btn-primary export-csv">[[admin/manage/users:download-csv]]</a>
<div class="btn-group">
<button class="btn btn-default dropdown-toggle" data-toggle="dropdown" type="button">[[admin/manage/users:edit]] <span class="caret"></span></button>
<ul class="dropdown-menu">

Loading…
Cancel
Save