Add routes for managing users in teams

This commit is contained in:
Jerko Steiner 2019-03-19 17:51:16 +05:00
parent dd358154b6
commit 46e56b7ad4
9 changed files with 198 additions and 6 deletions

View File

@ -3,6 +3,7 @@ import {INewUser} from './INewUser'
import {ITeam} from './ITeam' import {ITeam} from './ITeam'
import {IUserTeam} from './IUserTeam' import {IUserTeam} from './IUserTeam'
import {IUser} from './IUser' import {IUser} from './IUser'
import {IUserInTeam} from './IUserInTeam'
export interface IAPIDef { export interface IAPIDef {
'/auth/register': { '/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': { '/my/teams': {
get: { get: {
response: IUserTeam[] response: IUserTeam[]

View File

@ -0,0 +1,7 @@
export interface IUserInTeam {
teamId: number
userId: number
displayName: string
roleId: number
roleName: string
}

View File

@ -6,4 +6,7 @@ export * from './IRole'
export * from './IRoutes' export * from './IRoutes'
export * from './ITeam' export * from './ITeam'
export * from './IUser' export * from './IUser'
export * from './IUser'
export * from './IUserInTeam'
export * from './IUserTeam'
export * from './URLFormatter' export * from './URLFormatter'

View File

@ -1,6 +1,7 @@
import {Team} from '../entities/Team' import {Team} from '../entities/Team'
import {UserTeam} from '../entities/UserTeam' import {UserTeam} from '../entities/UserTeam'
import {IUserTeamParams} from './IUserTeamParams' import {IUserTeamParams} from './IUserTeamParams'
import {IUserInTeam} from '@rondo/common'
export interface ITeamService { export interface ITeamService {
create(params: {name: string, userId: number}): Promise<Team> create(params: {name: string, userId: number}): Promise<Team>
@ -17,5 +18,7 @@ export interface ITeamService {
find(userId: number): Promise<UserTeam[]> find(userId: number): Promise<UserTeam[]>
findUsers(teamId: number): Promise<IUserInTeam[]>
// TODO add other methods // TODO add other methods
} }

View File

@ -1,5 +1,5 @@
import {test} from '../test' import {test} from '../test'
import {createTeam} from './TeamTestUtils' import {addUser, removeUser, findUsers, createTeam} from './TeamTestUtils'
describe('team', () => { describe('team', () => {
@ -8,10 +8,12 @@ describe('team', () => {
let cookie!: string let cookie!: string
let token!: string let token!: string
let mainUserId: number
beforeEach(async () => { beforeEach(async () => {
const session = await test.registerAccount() const session = await test.registerAccount()
cookie = session.cookie cookie = session.cookie
token = session.token token = session.token
mainUserId = session.userId
t.setHeaders({ cookie, 'x-csrf-token': token }) 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', () => { // describe('GET /my/teams', () => {
// }) // })

View File

@ -64,6 +64,49 @@ export class TeamRoutes extends BaseRoute<IAPIDef> {
}) })
}) })
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
})
})
} }
} }

View File

@ -49,10 +49,31 @@ export class TeamService extends BaseService implements ITeamService {
} }
async removeUser({teamId, userId, roleId}: IUserTeamParams) { async removeUser({teamId, userId, roleId}: IUserTeamParams) {
// TODO check if this is the last admin team member
await this.getRepository(UserTeam) await this.getRepository(UserTeam)
.delete({userId, teamId, roleId}) .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) { async findOne(id: number) {
return this.getRepository(Team).findOne(id) return this.getRepository(Team).findOne(id)
} }

View File

@ -11,3 +11,44 @@ export async function createTeam(t: RequestTester<IAPIDef>, name: string) {
expect(response.body.id).toBeTruthy() expect(response.body.id).toBeTruthy()
return response.body return response.body
} }
export async function addUser(t: RequestTester<IAPIDef>, 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<IAPIDef>, 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<IAPIDef>, params: {
teamId: number,
}) {
const response = await t
.get('/teams/:teamId/users', {
params: {
teamId: params.teamId,
},
})
.expect(200)
return response.body
}

View File

@ -89,12 +89,16 @@ export class TestUtils<T extends IRoutes> {
return match![1] return match![1]
} }
getLoginBody(csrfToken: string) { getLoginBody(csrfToken: string, username?: string) {
const {username, password} = this const {password} = this
return {username, password, _csrf: csrfToken} return {
username: username || this.username,
password,
_csrf: csrfToken,
}
} }
async registerAccount() { async registerAccount(username?: string) {
const {context} = this const {context} = this
const {cookie, token} = await this.getCsrf() const {cookie, token} = await this.getCsrf()
@ -104,7 +108,7 @@ export class TestUtils<T extends IRoutes> {
.send({ .send({
firstName: 'test', firstName: 'test',
lastName: 'test', lastName: 'test',
...this.getLoginBody(token), ...this.getLoginBody(token, username),
}) })
.expect(200) .expect(200)