Skip to content

Commit

Permalink
feat: update team role (#625)
Browse files Browse the repository at this point in the history
Co-authored-by: Cátia Antunes <c.antunes@kigroup.de>
Co-authored-by: Rui Silva <r.silva@kigroup.de>
  • Loading branch information
3 people authored Nov 24, 2022
1 parent d3ffbab commit 8005b98
Show file tree
Hide file tree
Showing 19 changed files with 339 additions and 92 deletions.
26 changes: 26 additions & 0 deletions backend/src/libs/guards/teamRoles.guard.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { InjectModel } from '@nestjs/mongoose';
import { Model } from 'mongoose';

import TeamUser, { TeamUserDocument } from '../../modules/teams/schemas/team.user.schema';

@Injectable()
export class TeamUserGuard implements CanActivate {
constructor(
private readonly reflector: Reflector,
@InjectModel(TeamUser.name) private teamUserModel: Model<TeamUserDocument>
) {}

async canActivate(context: ExecutionContext) {
const permission = this.reflector.get<string>('permission', context.getHandler());
const request = context.switchToHttp().getRequest();

const user = request.user;
const team: string = request.params.teamId;

const userFound = await this.teamUserModel.findOne({ user: user._id, teamId: team }).exec();

return user.isSAdmin || permission === userFound?.role;
}
}
18 changes: 18 additions & 0 deletions backend/src/modules/teams/applications/update.team.application.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { Inject, Injectable } from '@nestjs/common';

import TeamUserDto from '../dto/team.user.dto';
import { UpdateTeamApplicationInterface } from '../interfaces/applications/update.team.application.interface';
import { UpdateTeamServiceInterface } from '../interfaces/services/update.team.service.interface';
import { TYPES } from '../interfaces/types';

@Injectable()
export class UpdateTeamApplication implements UpdateTeamApplicationInterface {
constructor(
@Inject(TYPES.services.UpdateTeamService)
private updateTeamService: UpdateTeamServiceInterface
) {}

updateTeamUser(teamData: TeamUserDto) {
return this.updateTeamService.updateTeamUser(teamData);
}
}
54 changes: 52 additions & 2 deletions backend/src/modules/teams/controller/team.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,19 @@ import {
Put,
Query,
Req,
SetMetadata,
UseGuards,
UsePipes,
ValidationPipe
} from '@nestjs/common';
import {
ApiBadRequestResponse,
ApiBearerAuth,
ApiBody,
ApiCreatedResponse,
ApiForbiddenResponse,
ApiInternalServerErrorResponse,
ApiNotFoundResponse,
ApiOkResponse,
ApiOperation,
ApiParam,
Expand All @@ -29,20 +33,26 @@ import {
import { TeamParams } from 'libs/dto/param/team.params';
import { TeamQueryParams } from 'libs/dto/param/team.query.params';
import { TeamRoles } from 'libs/enum/team.roles';
import { INSERT_FAILED } from 'libs/exceptions/messages';
import { INSERT_FAILED, UPDATE_FAILED } from 'libs/exceptions/messages';
import JwtAuthenticationGuard from 'libs/guards/jwtAuth.guard';
import RequestWithUser from 'libs/interfaces/requestWithUser.interface';
import { BadRequestResponse } from 'libs/swagger/errors/bad-request.swagger';
import { InternalServerErrorResponse } from 'libs/swagger/errors/internal-server-error.swagger';
import { UnauthorizedResponse } from 'libs/swagger/errors/unauthorized.swagger';

import { TeamUserGuard } from '../../../libs/guards/teamRoles.guard';
import { ForbiddenResponse } from '../../../libs/swagger/errors/forbidden.swagger';
import { NotFoundResponse } from '../../../libs/swagger/errors/not-found.swagger';
import { UpdateTeamApplication } from '../applications/update.team.application';
import { CreateTeamDto } from '../dto/crate-team.dto';
import TeamDto from '../dto/team.dto';
import TeamUserDto from '../dto/team.user.dto';
import { CreateTeamApplicationInterface } from '../interfaces/applications/create.team.application.interface';
import { GetTeamApplicationInterface } from '../interfaces/applications/get.team.application.interface';
import { TYPES } from '../interfaces/types';

const TeamUser = (permission: string) => SetMetadata('permission', permission);

@ApiBearerAuth('access-token')
@ApiTags('Teams')
@UseGuards(JwtAuthenticationGuard)
Expand All @@ -52,7 +62,9 @@ export default class TeamsController {
@Inject(TYPES.applications.CreateTeamApplication)
private createTeamApp: CreateTeamApplicationInterface,
@Inject(TYPES.applications.GetTeamApplication)
private getTeamApp: GetTeamApplicationInterface
private getTeamApp: GetTeamApplicationInterface,
@Inject(TYPES.applications.UpdateTeamApplication)
private updateTeamApp: UpdateTeamApplication
) {}

@ApiOperation({ summary: 'Create a new team' })
Expand Down Expand Up @@ -170,4 +182,42 @@ export default class TeamsController {
getTeam(@Param() { teamId }: TeamParams, @Query() teamQueryParams?: TeamQueryParams) {
return this.getTeamApp.getTeam(teamId, teamQueryParams);
}

@ApiOperation({ summary: 'Update a specific team member' })
@ApiParam({ type: String, name: 'teamId', required: true })
@ApiBody({ type: TeamUserDto })
@ApiOkResponse({
type: TeamUserDto,
description: 'Team member updated successfully!'
})
@ApiBadRequestResponse({
description: 'Bad Request',
type: BadRequestResponse
})
@ApiUnauthorizedResponse({
description: 'Unauthorized',
type: UnauthorizedResponse
})
@ApiNotFoundResponse({
type: NotFoundResponse,
description: 'Not found!'
})
@ApiForbiddenResponse({
description: 'Forbidden',
type: ForbiddenResponse
})
@ApiInternalServerErrorResponse({
description: 'Internal Server Error',
type: InternalServerErrorResponse
})
@TeamUser(TeamRoles.ADMIN)
@UseGuards(TeamUserGuard)
@Put(':teamId')
async updateTeamUser(@Body() teamData: TeamUserDto) {
const teamUser = await this.updateTeamApp.updateTeamUser(teamData);

if (!teamUser) throw new BadRequestException(UPDATE_FAILED);

return teamUser;
}
}
1 change: 0 additions & 1 deletion backend/src/modules/teams/dto/team.user.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,5 @@ export default class TeamUserDto {
@ApiPropertyOptional()
@IsOptional()
@IsBoolean()
@IsMongoId()
isNewJoiner?: boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { LeanDocument } from 'mongoose';

import TeamUserDto from '../../dto/team.user.dto';
import { TeamUserDocument } from '../../schemas/team.user.schema';

export interface UpdateTeamApplicationInterface {
updateTeamUser(teamData: TeamUserDto): Promise<LeanDocument<TeamUserDocument> | null>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface GetTeamServiceInterface {

getUsersOfTeam(teamId: string): Promise<LeanDocument<TeamUserDocument>[]>;

getTeamUser(userId: string, boardId: string): Promise<LeanDocument<TeamUserDocument> | null>;
getTeamUser(userId: string, teamId: string): Promise<LeanDocument<TeamUserDocument> | null>;

getAllTeams(): Promise<LeanDocument<TeamDocument>[]>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { LeanDocument } from 'mongoose';

import TeamUserDto from '../../dto/team.user.dto';
import { TeamUserDocument } from '../../schemas/team.user.schema';

export interface UpdateTeamServiceInterface {
updateTeamUser(teamData: TeamUserDto): Promise<LeanDocument<TeamUserDocument> | null>;
}
7 changes: 4 additions & 3 deletions backend/src/modules/teams/interfaces/types.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
export const TYPES = {
services: {
CreateTeamService: 'CreateTeamService',
GetTeamService: 'GetTeamService'
GetTeamService: 'GetTeamService',
UpdateTeamService: 'UpdateTeamService'
// DeleteTeamService: 'DeleteTeamService',
// UpdateTeamService: 'UpdateTeamService',
},
applications: {
CreateTeamApplication: 'CreateTeamApplication',
GetTeamApplication: 'GetTeamApplication'
GetTeamApplication: 'GetTeamApplication',
UpdateTeamApplication: 'UpdateTeamApplication'
// DeleteBoardApplication: 'DeleteBoardApplication',
// UpdateBoardApplication: 'UpdateBoardApplication',
}
Expand Down
12 changes: 12 additions & 0 deletions backend/src/modules/teams/providers.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { CreateTeamApplication } from './applications/create.team.application';
import { GetTeamApplication } from './applications/get.team.application';
import { UpdateTeamApplication } from './applications/update.team.application';
import { TYPES } from './interfaces/types';
import CreateTeamService from './services/create.team.service';
import GetTeamService from './services/get.team.service';
import UpdateTeamService from './services/update.team.service';

export const createTeamService = {
provide: TYPES.services.CreateTeamService,
Expand All @@ -23,3 +25,13 @@ export const getTeamApplication = {
provide: TYPES.applications.GetTeamApplication,
useClass: GetTeamApplication
};

export const updateTeamService = {
provide: TYPES.services.UpdateTeamService,
useClass: UpdateTeamService
};

export const updateTeamApplication = {
provide: TYPES.applications.UpdateTeamApplication,
useClass: UpdateTeamApplication
};
23 changes: 23 additions & 0 deletions backend/src/modules/teams/services/update.team.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { Injectable } from '@nestjs/common';
import { InjectModel } from '@nestjs/mongoose';
import { LeanDocument, Model } from 'mongoose';

import TeamUserDto from '../dto/team.user.dto';
import { UpdateTeamServiceInterface } from '../interfaces/services/update.team.service.interface';
import TeamUser, { TeamUserDocument } from '../schemas/team.user.schema';

@Injectable()
export default class UpdateTeamService implements UpdateTeamServiceInterface {
constructor(@InjectModel(TeamUser.name) private teamUserModel: Model<TeamUserDocument>) {}

updateTeamUser(teamData: TeamUserDto): Promise<LeanDocument<TeamUserDocument> | null> {
return this.teamUserModel
.findOneAndUpdate(
{ user: teamData.user, team: teamData.team },
{ $set: { role: teamData.role, isNewJoiner: teamData.isNewJoiner } },
{ new: true }
)
.lean()
.exec();
}
}
15 changes: 12 additions & 3 deletions backend/src/modules/teams/teams.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,22 @@ import {
createTeamApplication,
createTeamService,
getTeamApplication,
getTeamService
getTeamService,
updateTeamApplication,
updateTeamService
} from './providers';

@Module({
imports: [mongooseTeamModule, mongooseTeamUserModule, forwardRef(() => BoardsModule)],
providers: [createTeamService, createTeamApplication, getTeamService, getTeamApplication],
providers: [
createTeamService,
createTeamApplication,
getTeamService,
getTeamApplication,
updateTeamService,
updateTeamApplication
],
controllers: [TeamsController],
exports: [getTeamApplication, getTeamService, createTeamService]
exports: [getTeamApplication, getTeamService, createTeamService, updateTeamService]
})
export default class TeamsModule {}
5 changes: 5 additions & 0 deletions frontend/src/api/teamService.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { GetServerSidePropsContext } from 'next';

import { CreateTeamDto, Team } from 'types/team/team';
import fetchData from 'utils/fetchData';
import { TeamUserUpdate } from '../types/team/team.user';

export const getAllTeams = (context?: GetServerSidePropsContext): Promise<Team[]> => {
return fetchData(`/teams`, { context, serverSide: !!context });
Expand All @@ -18,3 +19,7 @@ export const createTeamRequest = (newTeam: CreateTeamDto): Promise<Team> => {
export const getTeamRequest = (id: string, context?: GetServerSidePropsContext): Promise<Team> => {
return fetchData(`/teams/${id}`, { context, serverSide: !!context });
};

export const updateTeamUserRequest = (team: TeamUserUpdate): Promise<TeamUserUpdate> => {
return fetchData(`/teams/${team.team}`, { method: 'PUT', data: team });
};
Loading

0 comments on commit 8005b98

Please sign in to comment.