Skip to content

Commit

Permalink
feat: #8460, export groups members as csv
Browse files Browse the repository at this point in the history
  • Loading branch information
barisusakli committed Jul 10, 2020
1 parent 15aabfd commit 00d39fb
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 9 deletions.
2 changes: 2 additions & 0 deletions public/language/en-GB/admin/manage/groups.json
Expand Up @@ -8,6 +8,8 @@
"hidden": "Hidden",
"private": "Private",
"edit": "Edit",
"delete": "Delete",
"download-csv": "Download CSV",
"search-placeholder": "Search",
"create": "Create Group",
"description-placeholder": "A short description about your group",
Expand Down
20 changes: 20 additions & 0 deletions public/openapi/read.yaml
Expand Up @@ -2276,6 +2276,26 @@ paths:
schema:
type: string
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:
get:
tags:
Expand Down
2 changes: 1 addition & 1 deletion public/src/admin/manage/groups.js
Expand Up @@ -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 action = el.attr('data-action');
var groupName = el.parents('tr[data-groupname]').attr('data-groupname');
Expand Down
29 changes: 29 additions & 0 deletions src/controllers/admin/groups.js
@@ -1,11 +1,14 @@
'use strict';

const nconf = require('nconf');
const validator = require('validator');

const db = require('../../database');
const user = require('../../user');
const groups = require('../../groups');
const meta = require('../../meta');
const pagination = require('../../pagination');
const events = require('../../events');

const groupsController = module.exports;

Expand Down Expand Up @@ -60,3 +63,29 @@ async function getGroupNames() {
const groupNames = await db.getSortedSetRange('groups:createtime', 0, -1);
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);
};
1 change: 1 addition & 0 deletions src/routes/admin.js
Expand Up @@ -69,6 +69,7 @@ module.exports = function (app, 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/groups/:groupname/csv', middleware.authenticate, helpers.tryRoute(controllers.admin.groups.getCSV));
router.get('/api/admin/analytics', middleware.authenticate, helpers.tryRoute(controllers.admin.dashboard.getAnalytics));

const multipart = require('connect-multiparty');
Expand Down
18 changes: 10 additions & 8 deletions src/views/admin/manage/groups.tpl
Expand Up @@ -19,7 +19,7 @@
<!-- BEGIN groups -->
<tr data-groupname="{groups.displayName}">
<td>
{groups.displayName}
<a href="{config.relative_path}/admin/manage/groups/{groups.nameEncoded}">{groups.displayName}</a>
</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>
Expand All @@ -42,13 +42,15 @@
{groups.memberCount}
</td>
<td>
<div class="btn-group ">
<a href="{config.relative_path}/admin/manage/groups/{groups.nameEncoded}" class="btn btn-default btn-xs">
<i class="fa fa-edit"></i> [[admin/manage/groups:edit]]
</a>
<!-- IF !groups.system -->
<button class="btn btn-danger btn-xs" data-action="delete"><i class="fa fa-times"></i></button>
<!-- ENDIF !groups.system -->
<div class="btn-group">
<button class="btn btn-default btn-xs dropdown-toggle" data-toggle="dropdown" type="button"><i class="fa fa-fw fa-ellipsis-h"></i></button>
<ul class="dropdown-menu dropdown-menu-right">
<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 -->
<li data-action="delete"><a href="#"><i class="fa fa-fw fa-times"></i> [[admin/manage/groups:delete]]</a></li>
<!-- ENDIF !groups.system -->
</ul>
</div>
</td>
</tr>
Expand Down
37 changes: 37 additions & 0 deletions test/controllers-admin.js
Expand Up @@ -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) {
request(nconf.get('url') + '/api/admin/advanced/hooks', { jar: jar, json: true }, function (err, res, body) {
assert.ifError(err);
Expand Down

0 comments on commit 00d39fb

Please sign in to comment.