mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-27 09:06:15 +01:00
feat: #8460, export groups members as csv
This commit is contained in:
@@ -8,6 +8,8 @@
|
|||||||
"hidden": "Hidden",
|
"hidden": "Hidden",
|
||||||
"private": "Private",
|
"private": "Private",
|
||||||
"edit": "Edit",
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"download-csv": "Download CSV",
|
||||||
"search-placeholder": "Search",
|
"search-placeholder": "Search",
|
||||||
"create": "Create Group",
|
"create": "Create Group",
|
||||||
"description-placeholder": "A short description about your group",
|
"description-placeholder": "A short description about your group",
|
||||||
|
|||||||
@@ -2276,6 +2276,26 @@ paths:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
format: binary
|
format: binary
|
||||||
|
/api/admin/groups/{groupname}/csv:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- admin
|
||||||
|
summary: Get members of a group (.csv)
|
||||||
|
parameters:
|
||||||
|
- in: header
|
||||||
|
name: referer
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
example: /admin/manage/groups
|
||||||
|
responses:
|
||||||
|
"200":
|
||||||
|
description: "A CSV file containing all users in the group"
|
||||||
|
content:
|
||||||
|
text/csv:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
format: binary
|
||||||
/api/admin/analytics:
|
/api/admin/analytics:
|
||||||
get:
|
get:
|
||||||
tags:
|
tags:
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ define('admin/manage/groups', ['translator', 'benchpress'], function (translator
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
$('.groups-list').on('click', 'button[data-action]', function () {
|
$('.groups-list').on('click', '[data-action]', function () {
|
||||||
var el = $(this);
|
var el = $(this);
|
||||||
var action = el.attr('data-action');
|
var action = el.attr('data-action');
|
||||||
var groupName = el.parents('tr[data-groupname]').attr('data-groupname');
|
var groupName = el.parents('tr[data-groupname]').attr('data-groupname');
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const nconf = require('nconf');
|
||||||
const validator = require('validator');
|
const validator = require('validator');
|
||||||
|
|
||||||
const db = require('../../database');
|
const db = require('../../database');
|
||||||
|
const user = require('../../user');
|
||||||
const groups = require('../../groups');
|
const groups = require('../../groups');
|
||||||
const meta = require('../../meta');
|
const meta = require('../../meta');
|
||||||
const pagination = require('../../pagination');
|
const pagination = require('../../pagination');
|
||||||
|
const events = require('../../events');
|
||||||
|
|
||||||
const groupsController = module.exports;
|
const groupsController = module.exports;
|
||||||
|
|
||||||
@@ -60,3 +63,29 @@ async function getGroupNames() {
|
|||||||
const groupNames = await db.getSortedSetRange('groups:createtime', 0, -1);
|
const groupNames = await db.getSortedSetRange('groups:createtime', 0, -1);
|
||||||
return groupNames.filter(name => name !== 'registered-users' && !groups.isPrivilegeGroup(name));
|
return groupNames.filter(name => name !== 'registered-users' && !groups.isPrivilegeGroup(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
groupsController.getCSV = async function (req, res) {
|
||||||
|
const referer = req.headers.referer;
|
||||||
|
|
||||||
|
if (!referer || !referer.replace(nconf.get('url'), '').startsWith('/admin/manage/groups')) {
|
||||||
|
return res.status(403).send('[[error:invalid-origin]]');
|
||||||
|
}
|
||||||
|
await events.log({
|
||||||
|
type: 'getGroupCSV',
|
||||||
|
uid: req.uid,
|
||||||
|
ip: req.ip,
|
||||||
|
});
|
||||||
|
const groupName = req.params.groupname;
|
||||||
|
const members = (await groups.getMembersOfGroups([groupName]))[0];
|
||||||
|
const fields = ['email', 'username', 'uid'];
|
||||||
|
const userData = await user.getUsersFields(members, fields);
|
||||||
|
let csvContent = fields.join(',') + '\n';
|
||||||
|
csvContent += userData.reduce((memo, user) => {
|
||||||
|
memo += user.email + ',' + user.username + ',' + user.uid + '\n';
|
||||||
|
return memo;
|
||||||
|
}, '');
|
||||||
|
|
||||||
|
res.attachment(validator.escape(groupName) + '_members.csv');
|
||||||
|
res.setHeader('Content-Type', 'text/csv');
|
||||||
|
res.end(csvContent);
|
||||||
|
};
|
||||||
|
|||||||
@@ -69,6 +69,7 @@ module.exports = function (app, middleware, controllers) {
|
|||||||
|
|
||||||
function apiRoutes(router, middleware, controllers) {
|
function apiRoutes(router, middleware, controllers) {
|
||||||
router.get('/api/admin/users/csv', middleware.authenticate, helpers.tryRoute(controllers.admin.users.getCSV));
|
router.get('/api/admin/users/csv', middleware.authenticate, helpers.tryRoute(controllers.admin.users.getCSV));
|
||||||
|
router.get('/api/admin/groups/:groupname/csv', middleware.authenticate, helpers.tryRoute(controllers.admin.groups.getCSV));
|
||||||
router.get('/api/admin/analytics', middleware.authenticate, helpers.tryRoute(controllers.admin.dashboard.getAnalytics));
|
router.get('/api/admin/analytics', middleware.authenticate, helpers.tryRoute(controllers.admin.dashboard.getAnalytics));
|
||||||
|
|
||||||
const multipart = require('connect-multiparty');
|
const multipart = require('connect-multiparty');
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
<!-- BEGIN groups -->
|
<!-- BEGIN groups -->
|
||||||
<tr data-groupname="{groups.displayName}">
|
<tr data-groupname="{groups.displayName}">
|
||||||
<td>
|
<td>
|
||||||
{groups.displayName}
|
<a href="{config.relative_path}/admin/manage/groups/{groups.nameEncoded}">{groups.displayName}</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<span class="label label-default" style="color:{groups.textColor}; background-color: {groups.labelColor};"><!-- IF groups.icon --><i class="fa {groups.icon}"></i> <!-- ENDIF groups.icon -->{groups.userTitle}</span>
|
<span class="label label-default" style="color:{groups.textColor}; background-color: {groups.labelColor};"><!-- IF groups.icon --><i class="fa {groups.icon}"></i> <!-- ENDIF groups.icon -->{groups.userTitle}</span>
|
||||||
@@ -43,12 +43,14 @@
|
|||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<div class="btn-group">
|
<div class="btn-group">
|
||||||
<a href="{config.relative_path}/admin/manage/groups/{groups.nameEncoded}" class="btn btn-default btn-xs">
|
<button class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" type="button"><i class="fa fa-fw fa-ellipsis-h"></i></button>
|
||||||
<i class="fa fa-edit"></i> [[admin/manage/groups:edit]]
|
<ul class="dropdown-menu dropdown-menu-right">
|
||||||
</a>
|
<li><a href="{config.relative_path}/admin/manage/groups/{groups.nameEncoded}"><i class="fa fa-fw fa-edit"></i> [[admin/manage/groups:edit]]</a></li>
|
||||||
|
<li><a href="{config.relative_path}/api/admin/groups/{groups.nameEncoded}/csv"><i class="fa fa-fw fa-file-text"></i> [[admin/manage/groups:download-csv]]</a></li>
|
||||||
<!-- IF !groups.system -->
|
<!-- IF !groups.system -->
|
||||||
<button class="btn btn-danger btn-xs" data-action="delete"><i class="fa fa-times"></i></button>
|
<li data-action="delete"><a href="#"><i class="fa fa-fw fa-times"></i> [[admin/manage/groups:delete]]</a></li>
|
||||||
<!-- ENDIF !groups.system -->
|
<!-- ENDIF !groups.system -->
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
|||||||
@@ -364,6 +364,43 @@ describe('Admin Controllers', function () {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should return 403 if no referer', function (done) {
|
||||||
|
request(nconf.get('url') + '/api/admin/groups/administrators/csv', { jar: jar }, function (err, res, body) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.equal(res.statusCode, 403);
|
||||||
|
assert.equal(body, '[[error:invalid-origin]]');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 403 if referer is not /api/admin/groups/administrators/csv', function (done) {
|
||||||
|
request(nconf.get('url') + '/api/admin/groups/administrators/csv', {
|
||||||
|
jar: jar,
|
||||||
|
headers: {
|
||||||
|
referer: '/topic/1/test',
|
||||||
|
},
|
||||||
|
}, function (err, res, body) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.equal(res.statusCode, 403);
|
||||||
|
assert.equal(body, '[[error:invalid-origin]]');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should load /api/admin/groups/administrators/csv', function (done) {
|
||||||
|
request(nconf.get('url') + '/api/admin/groups/administrators/csv', {
|
||||||
|
jar: jar,
|
||||||
|
headers: {
|
||||||
|
referer: nconf.get('url') + '/admin/manage/groups',
|
||||||
|
},
|
||||||
|
}, function (err, res, body) {
|
||||||
|
assert.ifError(err);
|
||||||
|
assert.equal(res.statusCode, 200);
|
||||||
|
assert(body);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('should load /admin/advanced/hooks', function (done) {
|
it('should load /admin/advanced/hooks', function (done) {
|
||||||
request(nconf.get('url') + '/api/admin/advanced/hooks', { jar: jar, json: true }, function (err, res, body) {
|
request(nconf.get('url') + '/api/admin/advanced/hooks', { jar: jar, json: true }, function (err, res, body) {
|
||||||
assert.ifError(err);
|
assert.ifError(err);
|
||||||
|
|||||||
Reference in New Issue
Block a user