From 46e56b7ad4537b3a3cadb6d2a436bd0249b95abc Mon Sep 17 00:00:00 2001 From: Jerko Steiner Date: Tue, 19 Mar 2019 17:51:16 +0500 Subject: [PATCH] Add routes for managing users in teams --- packages/common/src/IAPIDef.ts | 25 +++++++++++ packages/common/src/IUserInTeam.ts | 7 +++ packages/common/src/index.ts | 3 ++ packages/server/src/team/ITeamService.ts | 3 ++ packages/server/src/team/TeamRoutes.test.ts | 47 ++++++++++++++++++++- packages/server/src/team/TeamRoutes.ts | 43 +++++++++++++++++++ packages/server/src/team/TeamService.ts | 21 +++++++++ packages/server/src/team/TeamTestUtils.ts | 41 ++++++++++++++++++ packages/server/src/test-utils/TestUtils.ts | 14 +++--- 9 files changed, 198 insertions(+), 6 deletions(-) create mode 100644 packages/common/src/IUserInTeam.ts diff --git a/packages/common/src/IAPIDef.ts b/packages/common/src/IAPIDef.ts index 8f24373..fe904f3 100644 --- a/packages/common/src/IAPIDef.ts +++ b/packages/common/src/IAPIDef.ts @@ -3,6 +3,7 @@ import {INewUser} from './INewUser' import {ITeam} from './ITeam' import {IUserTeam} from './IUserTeam' import {IUser} from './IUser' +import {IUserInTeam} from './IUserInTeam' export interface IAPIDef { '/auth/register': { @@ -70,6 +71,30 @@ export interface IAPIDef { } } + '/teams/:teamId/users': { + get: { + params: { + teamId: number + } + response: IUserInTeam[] // TODO + } + } + + '/teams/:teamId/users/:userId': { + post: { + params: { + teamId: number + userId: number + } + } + delete: { + params: { + teamId: number + userId: number + } + } + } + '/my/teams': { get: { response: IUserTeam[] diff --git a/packages/common/src/IUserInTeam.ts b/packages/common/src/IUserInTeam.ts new file mode 100644 index 0000000..6391158 --- /dev/null +++ b/packages/common/src/IUserInTeam.ts @@ -0,0 +1,7 @@ +export interface IUserInTeam { + teamId: number + userId: number + displayName: string + roleId: number + roleName: string +} diff --git a/packages/common/src/index.ts b/packages/common/src/index.ts index 2d60735..4865634 100644 --- a/packages/common/src/index.ts +++ b/packages/common/src/index.ts @@ -6,4 +6,7 @@ export * from './IRole' export * from './IRoutes' export * from './ITeam' export * from './IUser' +export * from './IUser' +export * from './IUserInTeam' +export * from './IUserTeam' export * from './URLFormatter' diff --git a/packages/server/src/team/ITeamService.ts b/packages/server/src/team/ITeamService.ts index 737991c..a6fb6c6 100644 --- a/packages/server/src/team/ITeamService.ts +++ b/packages/server/src/team/ITeamService.ts @@ -1,6 +1,7 @@ import {Team} from '../entities/Team' import {UserTeam} from '../entities/UserTeam' import {IUserTeamParams} from './IUserTeamParams' +import {IUserInTeam} from '@rondo/common' export interface ITeamService { create(params: {name: string, userId: number}): Promise @@ -17,5 +18,7 @@ export interface ITeamService { find(userId: number): Promise + findUsers(teamId: number): Promise + // TODO add other methods } diff --git a/packages/server/src/team/TeamRoutes.test.ts b/packages/server/src/team/TeamRoutes.test.ts index 42cec00..568cbd0 100644 --- a/packages/server/src/team/TeamRoutes.test.ts +++ b/packages/server/src/team/TeamRoutes.test.ts @@ -1,5 +1,5 @@ import {test} from '../test' -import {createTeam} from './TeamTestUtils' +import {addUser, removeUser, findUsers, createTeam} from './TeamTestUtils' describe('team', () => { @@ -8,10 +8,12 @@ describe('team', () => { let cookie!: string let token!: string + let mainUserId: number beforeEach(async () => { const session = await test.registerAccount() cookie = session.cookie token = session.token + mainUserId = session.userId t.setHeaders({ cookie, 'x-csrf-token': token }) }) @@ -78,6 +80,49 @@ describe('team', () => { }) }) + describe('POST /teams/:teamId/users/:userId', () => { + it('adds a user to the team', async () => { + const team = await createTeam(t, 'test') + const teamId = team.id + const {userId} = await test.registerAccount('test2@user.com') + await addUser(t, {userId, teamId}) + }) + }) + + describe('DELETE /teams/:teamId/users/:userId', () => { + it('removes the user from the team', async () => { + const team = await createTeam(t, 'test') + const teamId = team.id + const {userId} = await test.registerAccount('test2@user.com') + await addUser(t, {userId, teamId}) + await removeUser(t, {userId, teamId}) + }) + }) + + describe('GET /teams/:teamId/users', () => { + it('fetches team members user info', async () => { + const team = await createTeam(t, 'test') + const teamId = team.id + const {userId} = await test.registerAccount('test2@user.com') + await addUser(t, {userId, teamId}) + const users = await findUsers(t, {teamId}) + expect(users.length).toBe(2) + expect(users).toEqual([{ + teamId, + userId: mainUserId, + displayName: jasmine.any(String), + roleId: 1, + roleName: 'ADMIN', + }, { + teamId, + userId, + displayName: jasmine.any(String), + roleId: 1, + roleName: 'ADMIN', + }]) + }) + }) + // describe('GET /my/teams', () => { // }) diff --git a/packages/server/src/team/TeamRoutes.ts b/packages/server/src/team/TeamRoutes.ts index 8fe385e..2b8c662 100644 --- a/packages/server/src/team/TeamRoutes.ts +++ b/packages/server/src/team/TeamRoutes.ts @@ -64,6 +64,49 @@ export class TeamRoutes extends BaseRoute { }) }) + t.get('/teams/:teamId/users', async req => { + const teamId = Number(req.params.teamId) + + await this.permissions.belongsToTeam({ + teamId, + userId: req.user!.id, + }) + + return this.teamService.findUsers(teamId) + }) + + t.post('/teams/:teamId/users/:userId', async req => { + const teamId = Number(req.params.teamId) + const userId = Number(req.params.userId) + + await this.permissions.belongsToTeam({ + teamId, + userId: req.user!.id, + }) + + await this.teamService.addUser({ + userId, + teamId, + roleId: 1, // TODO customize roles + }) + }) + + t.delete('/teams/:teamId/users/:userId', async req => { + const teamId = Number(req.params.teamId) + const userId = Number(req.params.userId) + + await this.permissions.belongsToTeam({ + teamId, + userId: req.user!.id, + }) + + await this.teamService.removeUser({ + teamId, + userId, + roleId: 1, // TODO customzie roles + }) + }) + } } diff --git a/packages/server/src/team/TeamService.ts b/packages/server/src/team/TeamService.ts index 7839642..6e3beea 100644 --- a/packages/server/src/team/TeamService.ts +++ b/packages/server/src/team/TeamService.ts @@ -49,10 +49,31 @@ export class TeamService extends BaseService implements ITeamService { } async removeUser({teamId, userId, roleId}: IUserTeamParams) { + // TODO check if this is the last admin team member await this.getRepository(UserTeam) .delete({userId, teamId, roleId}) } + async findUsers(teamId: number) { + const userTeams = await this.getRepository(UserTeam) + .createQueryBuilder('ut') + .select('ut') + .innerJoinAndSelect('ut.user', 'user') + .innerJoinAndSelect('ut.role', 'role') + .where('ut.teamId = :teamId', { + teamId, + }) + .getMany() + + return userTeams.map(ut => ({ + teamId, + userId: ut.userId, + displayName: `${ut.user.firstName} ${ut.user.lastName}`, + roleId: ut.role!.id, + roleName: ut.role!.name, + })) + } + async findOne(id: number) { return this.getRepository(Team).findOne(id) } diff --git a/packages/server/src/team/TeamTestUtils.ts b/packages/server/src/team/TeamTestUtils.ts index 3352bec..9d38fc4 100644 --- a/packages/server/src/team/TeamTestUtils.ts +++ b/packages/server/src/team/TeamTestUtils.ts @@ -11,3 +11,44 @@ export async function createTeam(t: RequestTester, name: string) { expect(response.body.id).toBeTruthy() return response.body } + +export async function addUser(t: RequestTester, params: { + teamId: number, + userId: number, +}) { + await t + .post('/teams/:teamId/users/:userId', { + params: { + teamId: params.teamId, + userId: params.userId, + }, + }) + .expect(200) +} + +export async function removeUser(t: RequestTester, params: { + teamId: number, + userId: number, +}) { + await t + .delete('/teams/:teamId/users/:userId', { + params: { + teamId: params.teamId, + userId: params.userId, + }, + }) + .expect(200) +} + +export async function findUsers(t: RequestTester, params: { + teamId: number, +}) { + const response = await t + .get('/teams/:teamId/users', { + params: { + teamId: params.teamId, + }, + }) + .expect(200) + return response.body +} diff --git a/packages/server/src/test-utils/TestUtils.ts b/packages/server/src/test-utils/TestUtils.ts index 43763db..59bdb40 100644 --- a/packages/server/src/test-utils/TestUtils.ts +++ b/packages/server/src/test-utils/TestUtils.ts @@ -89,12 +89,16 @@ export class TestUtils { return match![1] } - getLoginBody(csrfToken: string) { - const {username, password} = this - return {username, password, _csrf: csrfToken} + getLoginBody(csrfToken: string, username?: string) { + const {password} = this + return { + username: username || this.username, + password, + _csrf: csrfToken, + } } - async registerAccount() { + async registerAccount(username?: string) { const {context} = this const {cookie, token} = await this.getCsrf() @@ -104,7 +108,7 @@ export class TestUtils { .send({ firstName: 'test', lastName: 'test', - ...this.getLoginBody(token), + ...this.getLoginBody(token, username), }) .expect(200)