Admin endpoints

This commit is contained in:
Jori Lallo
2017-12-26 15:02:26 +02:00
parent a74e90fc09
commit 26d0d815a2
14 changed files with 366 additions and 38 deletions

View File

@@ -0,0 +1,88 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`#team.addAdmin should promote a new admin 1`] = `
Object {
"avatarUrl": "http://example.com/avatar.png",
"email": "user1@example.com",
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
"isAdmin": true,
"name": "User 1",
"ok": true,
"status": 200,
"username": "user1",
}
`;
exports[`#team.addAdmin should require admin 1`] = `
Object {
"error": "only_available_for_admins",
"message": "Only available for admins",
"ok": false,
"status": 403,
}
`;
exports[`#team.removeAdmin should demote an admin 1`] = `
Object {
"avatarUrl": null,
"ok": true,
"status": 200,
}
`;
exports[`#team.removeAdmin should require admin 1`] = `
Object {
"error": "only_available_for_admins",
"message": "Only available for admins",
"ok": false,
"status": 403,
}
`;
exports[`#team.removeAdmin shouldn't demote admins if only one available 1`] = `
Object {
"error": "at_least_one_admin_is_required",
"message": "At least one admin is required",
"ok": false,
"status": 400,
}
`;
exports[`#team.users should require admin 1`] = `
Object {
"error": "only_available_for_admins",
"message": "Only available for admins",
"ok": false,
"status": 403,
}
`;
exports[`#team.users should return teams paginated user list 1`] = `
Object {
"data": Array [
Object {
"avatarUrl": "http://example.com/avatar.png",
"email": "admin@example.com",
"id": "fa952cff-fa64-4d42-a6ea-6955c9689046",
"isAdmin": true,
"name": "Admin User",
"username": "admin",
},
Object {
"avatarUrl": "http://example.com/avatar.png",
"email": "user1@example.com",
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
"isAdmin": false,
"name": "User 1",
"username": "user1",
},
],
"ok": true,
"pagination": Object {
"limit": 15,
"nextPath": "/api/team.users?limit=15&offset=15",
"offset": 0,
},
"status": 200,
}
`;

View File

@@ -13,7 +13,6 @@ exports[`#user.info should return known user 1`] = `
Object {
"data": Object {
"avatarUrl": "http://example.com/avatar.png",
"email": "user1@example.com",
"id": "46fde1d4-0050-428f-9f0b-0bf77f4bdf61",
"name": "User 1",
"username": "user1",

View File

@@ -13,7 +13,7 @@ router.post('auth.info', auth(), async ctx => {
ctx.body = {
data: {
user: await presentUser(ctx, user),
user: await presentUser(ctx, user, { includeDetails: true }),
team: await presentTeam(ctx, team),
},
};
@@ -51,9 +51,14 @@ router.post('auth.slack', async ctx => {
name: data.user.name,
email: data.user.email,
teamId: team.id,
isAdmin: !teamExisted,
slackData: data.user,
slackAccessToken: data.access_token,
});
// Set initial avatar
await user.updateAvatar();
await user.save();
}
if (!teamExisted) {
@@ -68,10 +73,6 @@ router.post('auth.slack', async ctx => {
expires: new Date('2100'),
});
// Update user's avatar
await user.updateAvatar();
await user.save();
ctx.body = {
data: {
user: await presentUser(ctx, user),

View File

@@ -59,6 +59,8 @@ router.post('hooks.slack', async ctx => {
});
if (!user) throw httpErrors.BadRequest('Invalid user');
if (!user.isAdmin)
throw httpErrors.BadRequest('Only admins can add integrations');
const documents = await Document.searchForUser(user, text, {
limit: 5,

70
server/api/team.js Normal file
View File

@@ -0,0 +1,70 @@
// @flow
import Router from 'koa-router';
import httpErrors from 'http-errors';
import User from '../models/User';
import Team from '../models/Team';
import auth from './middlewares/authentication';
import pagination from './middlewares/pagination';
import { presentUser } from '../presenters';
const router = new Router();
router.use(auth({ adminOnly: true }));
router.post('team.users', pagination(), async ctx => {
const user = ctx.state.user;
const users = await User.findAll({
where: {
teamId: user.teamId,
},
order: [['createdAt', 'DESC']],
offset: ctx.state.pagination.offset,
limit: ctx.state.pagination.limit,
});
ctx.body = {
pagination: ctx.state.pagination,
data: users.map(user => presentUser(ctx, user, { includeDetails: true })),
};
});
router.post('team.addAdmin', async ctx => {
const { user } = ctx.body;
const admin = ctx.state.user;
ctx.assertPresent(user, 'id is required');
const team = await Team.findById(admin.teamId);
const promotedUser = await User.findOne({
where: { id: user, teamId: admin.teamId },
});
if (!promotedUser) throw httpErrors.NotFound();
await team.addAdmin(promotedUser);
ctx.body = presentUser(ctx, promotedUser, { includeDetails: true });
});
router.post('team.removeAdmin', async ctx => {
const { user } = ctx.body;
const admin = ctx.state.user;
ctx.assertPresent(user, 'id is required');
const team = await Team.findById(admin.teamId);
const demotedUser = await User.findOne({
where: { id: user, teamId: admin.teamId },
});
if (!demotedUser) throw httpErrors.NotFound();
try {
await team.removeAdmin(demotedUser);
ctx.body = presentUser(ctx, user, { includeDetails: true });
} catch (e) {
throw httpErrors.BadRequest(e.message);
}
});
export default router;

108
server/api/team.test.js Normal file
View File

@@ -0,0 +1,108 @@
/* eslint-disable flowtype/require-valid-file-annotation */
import TestServer from 'fetch-test-server';
import app from '..';
import { flushdb, seed } from '../test/support';
const server = new TestServer(app.callback());
beforeEach(flushdb);
afterAll(server.close);
describe('#team.users', async () => {
it('should return teams paginated user list', async () => {
const { admin } = await seed();
const res = await server.post('/api/team.users', {
body: { token: admin.getJwtToken() },
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body).toMatchSnapshot();
});
it('should require admin', async () => {
const { user } = await seed();
const res = await server.post('/api/team.users', {
body: { token: user.getJwtToken() },
});
const body = await res.json();
expect(res.status).toEqual(403);
expect(body).toMatchSnapshot();
});
});
describe('#team.addAdmin', async () => {
it('should promote a new admin', async () => {
const { admin, user } = await seed();
const res = await server.post('/api/team.addAdmin', {
body: {
token: admin.getJwtToken(),
user: user.id,
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body).toMatchSnapshot();
});
it('should require admin', async () => {
const { user } = await seed();
const res = await server.post('/api/team.addAdmin', {
body: { token: user.getJwtToken() },
});
const body = await res.json();
expect(res.status).toEqual(403);
expect(body).toMatchSnapshot();
});
});
describe('#team.removeAdmin', async () => {
it('should demote an admin', async () => {
const { admin, user } = await seed();
await user.update({ isAdmin: true }); // Make another admin
const res = await server.post('/api/team.removeAdmin', {
body: {
token: admin.getJwtToken(),
user: user.id,
},
});
const body = await res.json();
expect(res.status).toEqual(200);
expect(body).toMatchSnapshot();
});
it("shouldn't demote admins if only one available ", async () => {
const { admin } = await seed();
const res = await server.post('/api/team.removeAdmin', {
body: {
token: admin.getJwtToken(),
user: admin.id,
},
});
const body = await res.json();
expect(res.status).toEqual(400);
expect(body).toMatchSnapshot();
});
it('should require admin', async () => {
const { user } = await seed();
const res = await server.post('/api/team.addAdmin', {
body: { token: user.getJwtToken() },
});
const body = await res.json();
expect(res.status).toEqual(403);
expect(body).toMatchSnapshot();
});
});