mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
fix: #8969, export csv to file
This commit is contained in:
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user