diff --git a/cmd/app/handler/team_handler.go b/cmd/app/handler/team_handler.go index 2a5bc24..bb06a98 100644 --- a/cmd/app/handler/team_handler.go +++ b/cmd/app/handler/team_handler.go @@ -12,6 +12,7 @@ import ( common "houston/service/response/common" "houston/service/teamService" "net/http" + "regexp" "strconv" ) @@ -158,6 +159,22 @@ func (handler *TeamHandler) HandleMakeManager(c *gin.Context) { c.JSON(http.StatusOK, common.SuccessResponse("Team manager updated successfully", http.StatusOK)) } +func (handler *TeamHandler) HandleAddMembers(c *gin.Context, request team.UpdateTeamRequest) { + err := validateAddMemberRequest(request) + if err != nil { + handler.handleErrorResponse(c, err) + return + } + + err = handler.service.AddTeamMember(request, c.GetHeader(util.UserEmailHeader)) + if err != nil { + handler.handleErrorResponse(c, err) + return + } + + c.JSON(http.StatusOK, common.SuccessResponse("Members added successfully", http.StatusOK)) +} + func validateAddTeamRequest(request team.AddTeamRequest) error { teamName := request.Name minLength := viper.GetInt("TEAM_NAME_MIN_LENGTH") @@ -193,12 +210,9 @@ func validateUpdateTeamRequest(request team.UpdateTeamRequest) error { } if request.Members != nil { - if request.Members.SeverityId == 0 { - return customErrors.NewInvalidInputError("Severity id not provided for members to add") - } - - if len(request.Members.UserEmailIds) == 0 { - return customErrors.NewInvalidInputError("No user email ids provided for members to add") + err := validateMembers(request.Members) + if err != nil { + return err } } @@ -241,3 +255,46 @@ func validateGetTeamParams(c *gin.Context) (uint, error) { return uint(teamId), nil } + +func validateAddMemberRequest(request team.UpdateTeamRequest) error { + if request.Id == 0 { + return customErrors.NewInvalidInputError("Team id not provided") + } + + err := validateMembers(request.Members) + if err != nil { + return err + } + + return nil +} + +func validateMembers(members *team.Members) error { + if members == nil { + return customErrors.NewInvalidInputError("Members not provided") + } + + if members.SeverityId == 0 { + return customErrors.NewInvalidInputError("Severity was not selected") + } + + if len(members.UserEmailIds) == 0 { + return customErrors.NewInvalidInputError("No email id string entered in input field- Please enter a valid email id.") + } + + if err := validateEachMemberEmail(members.UserEmailIds); err != nil { + return err + } + + return nil +} + +func validateEachMemberEmail(emailIds []string) error { + emailRegex := regexp.MustCompile(util.EMAIL_REGEX) + for _, email := range emailIds { + if !emailRegex.MatchString(email) { + return customErrors.NewInvalidInputError(fmt.Sprintf("Invalid email: %s", email)) + } + } + return nil +} diff --git a/cmd/app/server.go b/cmd/app/server.go index 66fef50..091185d 100644 --- a/cmd/app/server.go +++ b/cmd/app/server.go @@ -108,6 +108,7 @@ func (s *Server) teamHandler(houstonGroup *gin.RouterGroup) { houstonGroup.POST("/teams/add", teamHandler.AddTeam) houstonGroup.POST("/teams-v2/add", s.authService.IfAdmin(teamHandlerV2.HandleAddTeam)) houstonGroup.POST("/teams-v2", s.authService.IfMemberOrAdmin(teamHandlerV2.HandleUpdateTeam)) + houstonGroup.POST("/teams/members", s.authService.IfMemberOrAdmin(teamHandlerV2.HandleAddMembers)) houstonGroup.POST("/teams", teamHandler.UpdateTeam) houstonGroup.PATCH("/teams/:id/manager/:userId", teamHandler.MakeManager) houstonGroup.PATCH("/teams-v2/:id/manager/:userId", s.authService.IfManagerOrAdmin(teamHandlerV2.HandleMakeManager)) diff --git a/common/util/constant.go b/common/util/constant.go index b0897a2..d13aef3 100644 --- a/common/util/constant.go +++ b/common/util/constant.go @@ -121,3 +121,7 @@ const RedColorCode = "#FF0000" const ( DUPLICATE_KEY_VALUE_MESSAGE = "duplicate key value" ) + +const ( + EMAIL_REGEX = `^[a-zA-Z]+\.[a-zA-Z]+@navi\.com$` +) diff --git a/repository/teamUser/team_user_repository_impl.go b/repository/teamUser/team_user_repository_impl.go index d6cc8d2..6f62a58 100644 --- a/repository/teamUser/team_user_repository_impl.go +++ b/repository/teamUser/team_user_repository_impl.go @@ -72,6 +72,20 @@ func (repo *teamUserRepositoryImpl) GetTeamUserByTeamIdAndUserEmailId(teamId uin return &teamUser, nil } +func (repo *teamUserRepositoryImpl) GetTeamUsersByTeamIdAndUserEmailIds(teamId uint, userEmailIds []string) ([]teamUser.TeamUserEntity, error) { + var teamUsers []teamUser.TeamUserEntity + if err := repo.gormClient. + Preload("User"). + Joins("JOIN houston_user ON houston_user.id = team_user.user_id"). + Where("houston_user.email IN (?) AND team_user.team_id = ?", userEmailIds, teamId). + Find(&teamUsers). + Error; err != nil { + return nil, err + } + + return teamUsers, nil +} + func (repo *teamUserRepositoryImpl) GetTeamUsersWithMinimumSeverityIdLessThanOrEqualToGivenSeverity(teamId, severityId uint) ([]teamUser.TeamUserEntity, error) { var teamUsers []teamUser.TeamUserEntity diff --git a/repository/teamUser/team_user_repository_interface.go b/repository/teamUser/team_user_repository_interface.go index ffae5da..c77cc40 100644 --- a/repository/teamUser/team_user_repository_interface.go +++ b/repository/teamUser/team_user_repository_interface.go @@ -12,6 +12,7 @@ type TeamUserRepository interface { GetTeamsByUserId(userId uint) ([]teamUser.TeamUserEntity, error) RemoveTeamUserByTeamIdAndUserId(teamId, userId uint) error GetTeamUserByTeamIdAndUserEmailId(teamId uint, userEmailId string) (*teamUser.TeamUserEntity, error) + GetTeamUsersByTeamIdAndUserEmailIds(teamId uint, userEmailIds []string) ([]teamUser.TeamUserEntity, error) GetTeamUsersWithMinimumSeverityIdLessThanOrEqualToGivenSeverity(teamId, severityId uint) ([]teamUser.TeamUserEntity, error) } diff --git a/service/teamService/team_service_v2.go b/service/teamService/team_service_v2.go index 2da39e8..1e89391 100644 --- a/service/teamService/team_service_v2.go +++ b/service/teamService/team_service_v2.go @@ -423,8 +423,17 @@ func (teamService *TeamServiceV2) UpdateTeam(request teamRequest.UpdateTeamReque return err } + err = teamService.commitTeamDetails(teamEntity, userEmail) + if err != nil { + return err + } + + return nil +} + +func (teamService *TeamServiceV2) commitTeamDetails(teamEntity *team.TeamEntity, userEmail string) error { teamEntity.UpdatedBy = userEmail - err = teamService.teamRepository.UpdateTeam(teamEntity) + err := teamService.teamRepository.UpdateTeam(teamEntity) if err != nil { logger.Error(fmt.Sprintf("%s could not update team with id: %d : %+v", logTag, teamEntity.ID, err)) return customErrors.NewDataAccessError(fmt.Sprintf("could not update team with id: %d", teamEntity.ID)) @@ -433,6 +442,29 @@ func (teamService *TeamServiceV2) UpdateTeam(request teamRequest.UpdateTeamReque return nil } +func (teamService *TeamServiceV2) AddTeamMember(request teamRequest.UpdateTeamRequest, userEmail string) error { + teamEntity, err := teamService.GetTeamById(request.Id) + if err != nil { + return err + } + + _, err = teamService.doUsersWithEmailsExistInTeam(request.Id, request.Members.UserEmailIds) + if err != nil { + return err + } + + if err := teamService.addMembers(teamEntity, request.Members); err != nil { + return err + } + + err = teamService.commitTeamDetails(teamEntity, userEmail) + if err != nil { + return err + } + + return nil +} + func (teamService *TeamServiceV2) RemoveTeamMember(teamId, userId uint, requesterEmail string) error { teamEntity, err := teamService.GetTeamById(teamId) if err != nil { @@ -715,6 +747,24 @@ func (teamService *TeamServiceV2) removeUserFromTeamEntity(teamEntity team.TeamE return customErrors.NewNotFoundError("User not found in team") } +func (teamService *TeamServiceV2) doUsersWithEmailsExistInTeam(teamId uint, emails []string) (bool, error) { + teamUsers, err := teamService.teamUserService.GetTeamUsersByTeamIdAndUserEmailIds(teamId, emails) + if err != nil { + logger.Error(fmt.Sprintf("%s error in fetching team users for team with id: %d : %+v", logTag, teamId, err)) + return false, customErrors.NewDataAccessError("Could not fetch team users for team") + } + + if teamUsers != nil && len(teamUsers) > 0 { + var existingEmails []string + for _, teamUser := range teamUsers { + existingEmails = append(existingEmails, teamUser.User.Email) + } + return true, customErrors.NewInvalidInputError("User(s) already exist in the team: " + strings.Join(existingEmails, ", ")) + } + + return false, nil +} + func findUserInTeam(emailId string, teamUsers []*teamUserModel.TeamUserDTO) *teamUserModel.TeamUserDTO { for _, teamUserValue := range teamUsers { if teamUserValue.User.Email == emailId { diff --git a/service/teamService/team_service_v2_interface.go b/service/teamService/team_service_v2_interface.go index 6decab7..1ab98a2 100644 --- a/service/teamService/team_service_v2_interface.go +++ b/service/teamService/team_service_v2_interface.go @@ -13,6 +13,7 @@ type ITeamServiceV2 interface { GetExternalTeam(teamId uint, provider string) (*externalTeam.ExternalTeamDTO, error) AddTeam(request teamRequest.AddTeamRequest, userEmail string) (*service.AddTeamResponse, error) UpdateTeam(request teamRequest.UpdateTeamRequest, userEmail string) error + AddTeamMember(request teamRequest.UpdateTeamRequest, userEmail string) error RemoveTeamMember(teamId, userId uint, requesterEmail string) error MakeManager(teamId, memberToMakeManagerId uint, requesterEmail string) error GetTeam(teamId uint) (*service.TeamResponseV2, error) diff --git a/service/teamService/team_service_v2_test.go b/service/teamService/team_service_v2_test.go index be9e90c..6ba3174 100644 --- a/service/teamService/team_service_v2_test.go +++ b/service/teamService/team_service_v2_test.go @@ -619,6 +619,56 @@ func (suite *TeamServiceV2Suite) Test_UpdateTeam_AddMembers_Success() { suite.Nil(err, "error should be nil") } +func (suite *TeamServiceV2Suite) Test_AddTeamMember_TeamNotFound() { + suite.teamRepository.FindTeamByIdMock.Return(nil, errors.New("error")) + + err := suite.teamService.AddTeamMember(teamRequest.UpdateTeamRequest{Id: 1, Members: &teamRequest.Members{ + SeverityId: 2, + UserEmailIds: []string{"test1@navi.com", "test2@navi.com"}, + }}, "") + suite.NotNil(err, "error should not be nil") +} + +func (suite *TeamServiceV2Suite) Test_AddTeamMember_TeamUserError() { + suite.teamRepository.FindTeamByIdMock.Return(GetMockTeamEntity(), nil) + suite.teamUserService.GetTeamUsersByTeamIdAndUserEmailIdsMock.Return(nil, errors.New("error")) + + err := suite.teamService.AddTeamMember(teamRequest.UpdateTeamRequest{Id: 1, Members: &teamRequest.Members{ + SeverityId: 2, + UserEmailIds: []string{"test1@navi.com", "test2@navi.com"}, + }}, "") + suite.NotNil(err, "error should not be nil") +} + +func (suite *TeamServiceV2Suite) Test_AddTeamMember_DuplicateUserCase() { + suite.teamRepository.FindTeamByIdMock.Return(GetMockTeamEntity(), nil) + suite.teamUserService.GetTeamUsersByTeamIdAndUserEmailIdsMock.Return(GetMockTeamUsers(), nil) + + err := suite.teamService.AddTeamMember(teamRequest.UpdateTeamRequest{Id: 1, Members: &teamRequest.Members{ + SeverityId: 2, + UserEmailIds: []string{"test1@navi.com", "test2@navi.com"}, + }}, "") + suite.NotNil(err, "error should not be nil") +} + +func (suite *TeamServiceV2Suite) Test_AddTeamMember_SuccessCase() { + suite.teamRepository.FindTeamByIdMock.Return(GetMockTeamEntity(), nil) + suite.teamUserService.GetTeamUsersByTeamIdAndUserEmailIdsMock.Return(nil, nil) + suite.teamSeverityService.GetSeveritiesMapForATeamMock.Return(GetMockTeamSeverityMap(), nil) + suite.teamUserService.GetTeamUsersByTeamIdMock.Return(GetMockTeamUsers(), nil) + suite.userService.GetHoustonUserByEmailIdMock.Return(GetMockUserDTO(), nil) + suite.teamUserService.AddTeamUserMock.Return(GetMockTeamUserDTO(), nil) + suite.teamUserSeverityService.AddTeamUserSeverityMock.Return(nil) + suite.teamUserSeverityService.UpdateTeamSeverityForTeamUserMock.Return(nil) + suite.teamRepository.UpdateTeamMock.Return(nil) + + err := suite.teamService.AddTeamMember(teamRequest.UpdateTeamRequest{Id: 1, Members: &teamRequest.Members{ + SeverityId: 2, + UserEmailIds: []string{"test1@navi.com", "test2@navi.com"}, + }}, "") + suite.Nil(err, "error should be nil") +} + func (suite *TeamServiceV2Suite) Test_RemoveTeamMember_TeamNotFound() { suite.teamRepository.FindTeamByIdMock.Return(nil, errors.New("error")) diff --git a/service/teamUser/impl/team_user_service.go b/service/teamUser/impl/team_user_service.go index 7c89424..157e69b 100644 --- a/service/teamUser/impl/team_user_service.go +++ b/service/teamUser/impl/team_user_service.go @@ -83,6 +83,16 @@ func (service *TeamUserService) GetTeamUserByTeamIdAndUserEmailId(teamId uint, u return teamUser.ToDTO(), nil } +func (service *TeamUserService) GetTeamUsersByTeamIdAndUserEmailIds(teamId uint, userEmailIds []string) ([]*teamUserModel.TeamUserDTO, error) { + teamUsers, err := service.teamUserRepository.GetTeamUsersByTeamIdAndUserEmailIds(teamId, userEmailIds) + if err != nil { + logger.Error("error in fetching team users", zap.Error(err)) + return nil, err + } + + return dtoConverter.TeamUserEntitiesToDTOs(teamUsers), nil +} + func (service *TeamUserService) GetTeamUsersWithMinimumSeverityIdLessThanOrEqualToGivenSeverity(teamId, severityId uint) ([]*teamUserModel.TeamUserDTO, error) { teamUsers, err := service.teamUserRepository.GetTeamUsersWithMinimumSeverityIdLessThanOrEqualToGivenSeverity(teamId, severityId) if err != nil { diff --git a/service/teamUser/impl/team_user_service_test.go b/service/teamUser/impl/team_user_service_test.go index b64274d..ba57b83 100644 --- a/service/teamUser/impl/team_user_service_test.go +++ b/service/teamUser/impl/team_user_service_test.go @@ -112,6 +112,22 @@ func (suite *TeamUserServiceSuite) Test_GetTeamUserByTeamIdAndUserEmailId_Succes suite.NotNil(teamUser, "teamUser should not be nil") } +func (suite *TeamUserServiceSuite) Test_GetTeamUsersByTeamIdAndUserEmailIds_RepoFailureCase() { + suite.teamUserRepository.GetTeamUsersByTeamIdAndUserEmailIdsMock.Return(nil, errors.New("error")) + + teamUsers, err := suite.teamUserService.GetTeamUsersByTeamIdAndUserEmailIds(1, []string{"email"}) + suite.Nil(teamUsers, "teamUsers should be nil") + suite.EqualError(err, "error", "error should be error") +} + +func (suite *TeamUserServiceSuite) Test_GetTeamUsersByTeamIdAndUserEmailIds_SuccessCase() { + suite.teamUserRepository.GetTeamUsersByTeamIdAndUserEmailIdsMock.Return(GetMockTeamUserEntities(), nil) + + teamUsers, err := suite.teamUserService.GetTeamUsersByTeamIdAndUserEmailIds(1, []string{"email"}) + suite.Nil(err, "error should be nil") + suite.NotNil(teamUsers, "teamUsers should not be nil") +} + func (suite *TeamUserServiceSuite) Test_GetTeamUsersWithMinimumSeverityIdLessThanOrEqualToGivenSeverity_RepoFailureCase() { suite.teamUserRepository.GetTeamUsersWithMinimumSeverityIdLessThanOrEqualToGivenSeverityMock.Return(nil, errors.New("error")) diff --git a/service/teamUser/team_user_service_interface.go b/service/teamUser/team_user_service_interface.go index 4cdf3f1..ac96aae 100644 --- a/service/teamUser/team_user_service_interface.go +++ b/service/teamUser/team_user_service_interface.go @@ -9,5 +9,6 @@ type ITeamUserService interface { GetTeamsByUserId(userId uint) ([]*teamUserModel.TeamUserDTO, error) RemoveTeamUser(teamId, userId uint) error GetTeamUserByTeamIdAndUserEmailId(teamId uint, userEmailId string) (*teamUserModel.TeamUserDTO, error) + GetTeamUsersByTeamIdAndUserEmailIds(teamId uint, userEmailIds []string) ([]*teamUserModel.TeamUserDTO, error) GetTeamUsersWithMinimumSeverityIdLessThanOrEqualToGivenSeverity(teamId, severityId uint) ([]*teamUserModel.TeamUserDTO, error) }