From 7c1282711ba4e63771caf171115b97e518f65fbd Mon Sep 17 00:00:00 2001 From: Vijay Joshi Date: Fri, 23 Feb 2024 16:05:20 +0530 Subject: [PATCH] INFRA-2873 : Severity wise team member list - Team Management Module - Add team (#372) * INFRA-2873 : Boilerplate setup for team management revamp: * INFRA-2873 : Complete till add team flow * INFRA-2873 : Added unit tests and migration scripts * INFRA-2873 : Code review comments * INFRA-2873 : Add getter for team severity * INFRA-2873 : Second round of review --- Makefile | 8 + appcontext/app.go | 126 ++++++++--- cmd/app/handler/team_handler.go | 56 +++++ cmd/app/server.go | 18 +- common/util/constant.go | 4 + ...016_add_team_user_severities_tables.up.sql | 21 ++ model/severity/entity.go | 10 + model/severity/model.go | 9 + model/team/entity.go | 16 ++ model/team/model.go | 15 ++ model/teamSeverity/entity.go | 32 +++ model/teamSeverity/model.go | 15 ++ model/teamUser/entity.go | 30 +++ model/teamUser/model.go | 14 ++ model/teamUserSeverity/entity.go | 30 +++ model/teamUserSeverity/model.go | 15 ++ model/user/model.go | 24 +++ model/user/user.go | 24 +++ model/user/user_repository_interface.go | 2 + .../team_severity_repository_impl.go | 30 +++ .../team_severity_repository_interface.go | 17 ++ .../teamUser/team_user_repository_impl.go | 58 +++++ .../team_user_repository_interface.go | 20 ++ .../team_user_severity_repository_impl.go | 47 ++++ ...team_user_severity_repository_interface.go | 19 ++ service/dtoConverter/team_dto_converter.go | 40 ++++ service/incident/impl/incident_service_v2.go | 24 ++- service/request/team/add_team.go | 11 + service/severity/impl/severity_service.go | 28 +++ .../severity/impl/severity_service_test.go | 52 +++++ .../severity/severity_service_interface.go | 7 + service/teamService/team_service_v2.go | 101 ++++++++- .../teamService/team_service_v2_interface.go | 2 + service/teamService/team_service_v2_test.go | 201 +++++++++++++++++- .../impl/team_severity_service.go | 64 ++++++ .../impl/team_severity_service_test.go | 92 ++++++++ .../team_severity_service_interface.go | 8 + service/teamUser/impl/team_user_service.go | 54 +++++ .../teamUser/impl/team_user_service_test.go | 89 ++++++++ .../teamUser/team_user_service_interface.go | 9 + .../impl/team_user_severity_service.go | 25 +++ .../impl/team_user_severity_service_test.go | 39 ++++ .../team_user_severity_service_interface.go | 5 + service/user/impl/user_service.go | 48 +++++ service/user/impl/user_service_test.go | 84 ++++++++ service/user/user_service_interface.go | 8 + 46 files changed, 1593 insertions(+), 58 deletions(-) create mode 100644 db/migration/000016_add_team_user_severities_tables.up.sql create mode 100644 model/severity/model.go create mode 100644 model/team/model.go create mode 100644 model/teamSeverity/entity.go create mode 100644 model/teamSeverity/model.go create mode 100644 model/teamUser/entity.go create mode 100644 model/teamUser/model.go create mode 100644 model/teamUserSeverity/entity.go create mode 100644 model/teamUserSeverity/model.go create mode 100644 repository/teamSeverity/team_severity_repository_impl.go create mode 100644 repository/teamSeverity/team_severity_repository_interface.go create mode 100644 repository/teamUser/team_user_repository_impl.go create mode 100644 repository/teamUser/team_user_repository_interface.go create mode 100644 repository/teamUserSeverity/team_user_severity_repository_impl.go create mode 100644 repository/teamUserSeverity/team_user_severity_repository_interface.go create mode 100644 service/dtoConverter/team_dto_converter.go create mode 100644 service/request/team/add_team.go create mode 100644 service/severity/impl/severity_service.go create mode 100644 service/severity/impl/severity_service_test.go create mode 100644 service/severity/severity_service_interface.go create mode 100644 service/teamSeverity/impl/team_severity_service.go create mode 100644 service/teamSeverity/impl/team_severity_service_test.go create mode 100644 service/teamSeverity/team_severity_service_interface.go create mode 100644 service/teamUser/impl/team_user_service.go create mode 100644 service/teamUser/impl/team_user_service_test.go create mode 100644 service/teamUser/team_user_service_interface.go create mode 100644 service/teamUserSeverity/impl/team_user_severity_service.go create mode 100644 service/teamUserSeverity/impl/team_user_severity_service_test.go create mode 100644 service/teamUserSeverity/team_user_severity_service_interface.go create mode 100644 service/user/impl/user_service.go create mode 100644 service/user/impl/user_service_test.go create mode 100644 service/user/user_service_interface.go diff --git a/Makefile b/Makefile index eec4076..d20ac36 100644 --- a/Makefile +++ b/Makefile @@ -71,3 +71,11 @@ generatemocks: cd $(CURDIR)/service/alertService && minimock -i IAlertService -s _mock.go -o $(CURDIR)/mocks cd $(CURDIR)/service/tag && minimock -i ITagService -s _mock.go -o $(CURDIR)/mocks cd $(CURDIR)/service/conference && minimock -i ICalendarService -s _mock.go -o $(CURDIR)/mocks + cd $(CURDIR)/service/severity && minimock -i ISeverityService -s _mock.go -o $(CURDIR)/mocks + cd $(CURDIR)/service/user && minimock -i IUserService -s _mock.go -o $(CURDIR)/mocks + cd $(CURDIR)/service/teamSeverity && minimock -i ITeamSeverityService -s _mock.go -o $(CURDIR)/mocks + cd $(CURDIR)/service/teamUser && minimock -i ITeamUserService -s _mock.go -o $(CURDIR)/mocks + cd $(CURDIR)/service/teamUserSeverity && minimock -i ITeamUserSeverityService -s _mock.go -o $(CURDIR)/mocks + cd $(CURDIR)/repository/teamSeverity && minimock -i TeamSeverityRepository -s _mock.go -o $(CURDIR)/mocks + cd $(CURDIR)/repository/teamUser && minimock -i TeamUserRepository -s _mock.go -o $(CURDIR)/mocks + cd $(CURDIR)/repository/teamUserSeverity && minimock -i TeamUserSeverityRepository -s _mock.go -o $(CURDIR)/mocks diff --git a/appcontext/app.go b/appcontext/app.go index 6c5acea..ef7e23b 100644 --- a/appcontext/app.go +++ b/appcontext/app.go @@ -18,6 +18,9 @@ import ( "houston/pkg/rest" rcaRepository "houston/repository/rca/impl" "houston/repository/rcaInput" + teamSeverityRepo "houston/repository/teamSeverity" + teamUserRepo "houston/repository/teamUser" + teamUserSeverityRepo "houston/repository/teamUserSeverity" "houston/service/conference" "houston/service/documentService" "houston/service/google" @@ -25,8 +28,18 @@ import ( "houston/service/products" "houston/service/productsTeams" rcaService "houston/service/rca/impl" + severityService "houston/service/severity" + severityServiceImpl "houston/service/severity/impl" "houston/service/slack" tagService "houston/service/tag" + "houston/service/teamSeverity" + teamSeverityServiceImpl "houston/service/teamSeverity/impl" + "houston/service/teamUser" + teamUserServiceImpl "houston/service/teamUser/impl" + "houston/service/teamUserSeverity" + teamUserSeverityServiceImpl "houston/service/teamUserSeverity/impl" + userService "houston/service/user" + userServiceImpl "houston/service/user/impl" ) type applicationContext struct { @@ -35,22 +48,27 @@ type applicationContext struct { // todo: all the repo objects to be cleaned uo from here. appContext will only have services type houstonServices struct { - logRepo *log.Repository - teamRepo *team.Repository - severityRepo *severity.Repository - incidentRepo *incident.Repository - slackService *slack.SlackService - incidentService *incidentService.IncidentServiceV2 - tagRepo *tag.Repository - rcaRepository *rcaRepository.Repository - rcaInputRepository *rcaInput.Repository - userRepository *user.Repository - rcaService *rcaService.RcaService - driveService google.IDriveService - calendarService *conference.CalendarService - tagService *tagService.TagService - productsService products.ProductService - productTeamsService productsTeams.ProductTeamsService + logRepo *log.Repository + teamRepo *team.Repository + severityRepo *severity.Repository + incidentRepo *incident.Repository + slackService *slack.SlackService + incidentService *incidentService.IncidentServiceV2 + tagRepo *tag.Repository + rcaRepository *rcaRepository.Repository + rcaInputRepository *rcaInput.Repository + userRepository *user.Repository + rcaService *rcaService.RcaService + driveService google.IDriveService + calendarService *conference.CalendarService + tagService *tagService.TagService + productsService products.ProductService + productTeamsService productsTeams.ProductTeamsService + severityService severityService.ISeverityService + teamSeverityService teamSeverity.ITeamSeverityService + userService userService.IUserService + teamUserService teamUser.ITeamUserService + teamUserSeverityService teamUserSeverity.ITeamUserSeverityService } var appContext *applicationContext @@ -68,22 +86,27 @@ func InitializeServices() { teamRepo := initTeamRepo(logRepo) slaService := initSlackService() services = &houstonServices{ - logRepo: logRepo, - teamRepo: teamRepo, - severityRepo: severityRepo, - incidentRepo: initIncidentRepo(severityRepo, logRepo, teamRepo, slaService.SocketModeClientWrapper.GetClient()), - slackService: slaService, - incidentService: initIncidentService(), - tagRepo: initTagRepo(), - rcaRepository: initRCARepo(), - rcaInputRepository: initRCAInputRepo(), - userRepository: initUserRepo(), - rcaService: initRCAService(), - driveService: initDriveService(), - calendarService: initCalendarService(), - tagService: initTagService(), - productsService: initProductsService(), - productTeamsService: initProductTeamsService(), + logRepo: logRepo, + teamRepo: teamRepo, + severityRepo: severityRepo, + incidentRepo: initIncidentRepo(severityRepo, logRepo, teamRepo, slaService.SocketModeClientWrapper.GetClient()), + slackService: slaService, + incidentService: initIncidentService(), + tagRepo: initTagRepo(), + rcaRepository: initRCARepo(), + rcaInputRepository: initRCAInputRepo(), + userRepository: initUserRepo(), + rcaService: initRCAService(), + driveService: initDriveService(), + calendarService: initCalendarService(), + tagService: initTagService(), + productsService: initProductsService(), + productTeamsService: initProductTeamsService(), + severityService: initSeverityService(), + teamSeverityService: initTeamSeverityService(), + userService: initUserService(), + teamUserService: initTeamUserService(), + teamUserSeverityService: initTeamUserSeverityService(), } } @@ -239,9 +262,44 @@ func initUserRepo() *user.Repository { return user.NewUserRepository(GetDB()) } -func GetUserRepo() *user.Repository { - return services.userRepository +func initSeverityService() severityService.ISeverityService { + return severityServiceImpl.NewSeverityService(severity.NewSeverityRepository(GetDB())) } + +func initTeamSeverityService() teamSeverity.ITeamSeverityService { + return teamSeverityServiceImpl.NewTeamSeverityService( + teamSeverityRepo.NewTeamSeverityRepository(GetDB()), initSeverityService(), + ) +} + +func GetTeamSeverityService() teamSeverity.ITeamSeverityService { + return services.teamSeverityService +} + +func initUserService() userService.IUserService { + return userServiceImpl.NewUserService(user.NewUserRepository(GetDB())) +} + +func GetUserService() userService.IUserService { + return services.userService +} + +func initTeamUserService() teamUser.ITeamUserService { + return teamUserServiceImpl.NewTeamUserService(teamUserRepo.NewTeamUserRepository(GetDB())) +} + +func GetTeamUserService() teamUser.ITeamUserService { + return services.teamUserService +} + +func initTeamUserSeverityService() teamUserSeverity.ITeamUserSeverityService { + return teamUserSeverityServiceImpl.NewTeamUserSeverityService(teamUserSeverityRepo.NewTeamUserSeverityRepository(GetDB())) +} + +func GetTeamUserSeverityService() teamUserSeverity.ITeamUserSeverityService { + return services.teamUserSeverityService +} + func initDriveService() google.IDriveService { driveAction, err := googleDrive.NewGoogleDriveActions() if err != nil { diff --git a/cmd/app/handler/team_handler.go b/cmd/app/handler/team_handler.go index b68128b..07882fe 100644 --- a/cmd/app/handler/team_handler.go +++ b/cmd/app/handler/team_handler.go @@ -4,9 +4,11 @@ import ( "errors" "fmt" "github.com/gin-gonic/gin" + "github.com/spf13/viper" "houston/common/util" "houston/logger" "houston/model/customErrors" + "houston/service/request/team" common "houston/service/response/common" "houston/service/teamService" "net/http" @@ -55,10 +57,64 @@ func (handler *TeamHandler) handleErrorResponse(c *gin.Context, err error) { c.JSON(http.StatusInternalServerError, common.ErrorResponse(err, http.StatusInternalServerError, nil)) return } + var notFoundError *customErrors.NotFoundError if errors.As(err, ¬FoundError) { c.JSON(http.StatusNotFound, common.ErrorResponse(err, http.StatusNotFound, nil)) return } + + var invalidInputError *customErrors.InvalidInputError + if errors.As(err, &invalidInputError) { + c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil)) + return + } + c.JSON(http.StatusInternalServerError, common.ErrorResponse(err, http.StatusInternalServerError, nil)) } + +func (handler *TeamHandler) HandleAddTeam(c *gin.Context) { + var addTeamRequest team.AddTeamRequest + + if err := c.ShouldBindJSON(&addTeamRequest); err != nil { + c.JSON(http.StatusBadRequest, common.ErrorResponse(err, http.StatusBadRequest, nil)) + return + } + + if err := validateAddTeamRequest(addTeamRequest); err != nil { + handler.handleErrorResponse(c, err) + return + } + + addTeamResponse, err := handler.service.AddTeam(addTeamRequest, c.GetHeader(util.UserEmailHeader)) + if err != nil { + handler.handleErrorResponse(c, err) + return + } + + c.JSON(http.StatusCreated, common.SuccessResponse(addTeamResponse, http.StatusCreated)) +} + +func validateAddTeamRequest(request team.AddTeamRequest) error { + teamName := request.Name + minLength := viper.GetInt("TEAM_NAME_MIN_LENGTH") + maxLength := viper.GetInt("TEAM_NAME_MAX_LENGTH") + + if len(teamName) < minLength || len(teamName) > maxLength { + return customErrors.NewInvalidInputError(fmt.Sprintf("Team name must be between %v - %v", minLength, maxLength)) + } + + if request.ManagerDetails == nil { + return customErrors.NewInvalidInputError("Manager details not provided") + } + + if request.ManagerDetails.Email == nil || util.IsBlank(*request.ManagerDetails.Email) { + return customErrors.NewInvalidInputError("Manager email not provided") + } + + if request.ManagerDetails.SeverityId == nil { + return customErrors.NewInvalidInputError("Manager severity id not provided") + } + + return nil +} diff --git a/cmd/app/server.go b/cmd/app/server.go index 4609c5e..7a62557 100644 --- a/cmd/app/server.go +++ b/cmd/app/server.go @@ -87,14 +87,17 @@ func (s *Server) teamHandler(houstonGroup *gin.RouterGroup) { s.gin.GET("/teams/:id", teamHandler.GetTeams) s.gin.POST("/teams", teamHandler.UpdateTeam) + logRepository := log.NewLogRepository(s.db) + teamRepository := team.NewTeamRepository(s.db, logRepository) + userRepository := user.NewUserRepository(s.db) + slackService := slack.NewSlackService() + externalTeamRepository := externalTeamRepo.NewExternalTeamRepository(s.db) + teamServiceV2 := teamService.NewTeamServiceV2( + teamRepository, userRepository, slackService, externalTeamRepository, + appcontext.GetTeamSeverityService(), appcontext.GetUserService(), appcontext.GetTeamUserService(), appcontext.GetTeamUserSeverityService()) + teamHandlerV2 := handler.NewTeamHandler(s.gin, teamServiceV2) + if viper.GetBool("get.teams.v2.enabled") { - logRepository := log.NewLogRepository(s.db) - teamRepository := team.NewTeamRepository(s.db, logRepository) - userRepository := user.NewUserRepository(s.db) - slackService := slack.NewSlackService() - externalTeamRepository := externalTeamRepo.NewExternalTeamRepository(s.db) - teamServiceV2 := teamService.NewTeamServiceV2(teamRepository, userRepository, slackService, externalTeamRepository) - teamHandlerV2 := handler.NewTeamHandler(s.gin, teamServiceV2) houstonGroup.GET("/teams", teamHandlerV2.HandleGetAllTeams) houstonGroup.GET("/teams/:id", teamHandlerV2.HandleGetTeamDetails) } else { @@ -102,6 +105,7 @@ func (s *Server) teamHandler(houstonGroup *gin.RouterGroup) { houstonGroup.GET("/teams/:id", teamHandler.GetTeams) } houstonGroup.POST("/teams/add", teamHandler.AddTeam) + houstonGroup.POST("/teams-v2/add", s.authService.IfAdmin(teamHandlerV2.HandleAddTeam)) houstonGroup.POST("/teams", teamHandler.UpdateTeam) houstonGroup.PATCH("/teams/:id/manager/:userId", teamHandler.MakeManager) houstonGroup.DELETE("/teams/:id/members/:userId", teamHandler.RemoveTeamMember) diff --git a/common/util/constant.go b/common/util/constant.go index e7efde8..a2e8940 100644 --- a/common/util/constant.go +++ b/common/util/constant.go @@ -116,3 +116,7 @@ const ( ) const RedColorCode = "#FF0000" + +const ( + DUPLICATE_KEY_VALUE_MESSAGE = "duplicate key value" +) diff --git a/db/migration/000016_add_team_user_severities_tables.up.sql b/db/migration/000016_add_team_user_severities_tables.up.sql new file mode 100644 index 0000000..413b705 --- /dev/null +++ b/db/migration/000016_add_team_user_severities_tables.up.sql @@ -0,0 +1,21 @@ +CREATE TABLE IF NOT EXISTS team_user ( + id SERIAL PRIMARY KEY, + team_id INTEGER NOT NULL REFERENCES team(id), + user_id INTEGER NOT NULL REFERENCES houston_user(id), + CONSTRAINT team_user_unique_constraint UNIQUE (team_id, user_id) +); + +CREATE TABLE IF NOT EXISTS team_severity ( + id SERIAL PRIMARY KEY, + team_id INTEGER NOT NULL REFERENCES team(id), + severity_id INTEGER NOT NULL REFERENCES severity(id), + sla INTEGER, + CONSTRAINT team_severity_unique_constraint UNIQUE (team_id, severity_id) +); + +CREATE TABLE IF NOT EXISTS team_user_severity ( + id SERIAL PRIMARY KEY, + team_user INTEGER NOT NULL REFERENCES team_user(id), + team_severity INTEGER NOT NULL REFERENCES team_severity(id), + CONSTRAINT team_user_severity_unique_constraint UNIQUE (team_user, team_severity) +); \ No newline at end of file diff --git a/model/severity/entity.go b/model/severity/entity.go index 37d14e6..efb0b6f 100644 --- a/model/severity/entity.go +++ b/model/severity/entity.go @@ -16,3 +16,13 @@ type SeverityEntity struct { func (SeverityEntity) TableName() string { return "severity" } + +func (entity SeverityEntity) ToDTO() SeverityDTO { + return SeverityDTO{ + ID: entity.ID, + Name: entity.Name, + Description: entity.Description, + Sla: entity.Sla, + SlackUserIds: entity.SlackUserIds, + } +} diff --git a/model/severity/model.go b/model/severity/model.go new file mode 100644 index 0000000..b2f10ec --- /dev/null +++ b/model/severity/model.go @@ -0,0 +1,9 @@ +package severity + +type SeverityDTO struct { + ID uint `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Sla int `json:"sla"` + SlackUserIds []string `json:"slack_user_ids"` +} diff --git a/model/team/entity.go b/model/team/entity.go index 7f0d8c7..c48b7dd 100644 --- a/model/team/entity.go +++ b/model/team/entity.go @@ -23,6 +23,22 @@ func (TeamEntity) TableName() string { return "team" } +func (entity TeamEntity) ToDTO() TeamDTO { + return TeamDTO{ + ID: entity.ID, + Name: entity.Name, + SlackUserIds: entity.SlackUserIds, + ConfluenceLink: entity.ConfluenceLink, + OncallHandle: entity.OncallHandle, + PseOncallHandle: entity.PseOncallHandle, + Active: entity.Active, + WebhookSlackChannel: entity.WebhookSlackChannel, + ManagerHandle: entity.ManagerHandle, + CreatedBy: entity.CreatedBy, + UpdatedBy: entity.UpdatedBy, + } +} + type TeamTagEntity struct { gorm.Model TeamId int `gorm:"column:team_id"` diff --git a/model/team/model.go b/model/team/model.go new file mode 100644 index 0000000..e5ebcba --- /dev/null +++ b/model/team/model.go @@ -0,0 +1,15 @@ +package team + +type TeamDTO struct { + ID uint `json:"id"` + Name string `json:"name"` + SlackUserIds []string `json:"slack_user_ids"` + ConfluenceLink string `json:"confluence_link"` + OncallHandle string `json:"oncall_handle"` + PseOncallHandle string `json:"pse_oncall_handle"` + Active bool `json:"active"` + WebhookSlackChannel string `json:"webhook_slack_channel"` + ManagerHandle string `json:"manager_handle"` + CreatedBy string `json:"created_by"` + UpdatedBy string `json:"updated_by"` +} diff --git a/model/teamSeverity/entity.go b/model/teamSeverity/entity.go new file mode 100644 index 0000000..05172ed --- /dev/null +++ b/model/teamSeverity/entity.go @@ -0,0 +1,32 @@ +package teamSeverity + +import ( + "houston/model/severity" + "houston/model/team" +) + +type TeamSeverityEntity struct { + ID uint `gorm:"primaryKey"` + TeamID uint `gorm:"column:team_id;not null"` + SeverityID uint `gorm:"column:severity_id;not null"` + Sla int `gorm:"column:sla"` + + // Add foreign key constraints + Team team.TeamEntity `gorm:"foreignKey:TeamID"` + Severity severity.SeverityEntity `gorm:"foreignKey:SeverityID"` +} + +func (TeamSeverityEntity) TableName() string { + return "team_severity" +} + +func (entity TeamSeverityEntity) ToDTO() TeamSeverityDTO { + return TeamSeverityDTO{ + ID: entity.ID, + TeamID: entity.TeamID, + SeverityID: entity.SeverityID, + Sla: entity.Sla, + TeamDTO: entity.Team.ToDTO(), + SeverityDTO: entity.Severity.ToDTO(), + } +} diff --git a/model/teamSeverity/model.go b/model/teamSeverity/model.go new file mode 100644 index 0000000..ae6bc72 --- /dev/null +++ b/model/teamSeverity/model.go @@ -0,0 +1,15 @@ +package teamSeverity + +import ( + "houston/model/severity" + "houston/model/team" +) + +type TeamSeverityDTO struct { + ID uint `json:"id"` + TeamID uint `json:"team_id"` + SeverityID uint `json:"severity_id"` + Sla int `json:"sla"` + TeamDTO team.TeamDTO + SeverityDTO severity.SeverityDTO +} diff --git a/model/teamUser/entity.go b/model/teamUser/entity.go new file mode 100644 index 0000000..c05fa04 --- /dev/null +++ b/model/teamUser/entity.go @@ -0,0 +1,30 @@ +package teamUser + +import ( + "houston/model/team" + "houston/model/user" +) + +type TeamUserEntity struct { + ID uint `gorm:"primaryKey"` + TeamID uint `gorm:"column:team_id;not null"` + UserID uint `gorm:"column:user_id;not null"` + + // Add foreign key constraints + Team team.TeamEntity `gorm:"foreignKey:TeamID"` + User user.UserEntity `gorm:"foreignKey:UserID"` +} + +func (TeamUserEntity) TableName() string { + return "team_user" +} + +func (entity TeamUserEntity) ToDTO() *TeamUserDTO { + return &TeamUserDTO{ + ID: entity.ID, + TeamID: entity.TeamID, + UserID: entity.UserID, + Team: entity.Team.ToDTO(), + User: *entity.User.ToDTO(), + } +} diff --git a/model/teamUser/model.go b/model/teamUser/model.go new file mode 100644 index 0000000..ff9429b --- /dev/null +++ b/model/teamUser/model.go @@ -0,0 +1,14 @@ +package teamUser + +import ( + "houston/model/team" + "houston/model/user" +) + +type TeamUserDTO struct { + ID uint `json:"id"` + TeamID uint `json:"team_id"` + UserID uint `json:"user_id"` + Team team.TeamDTO + User user.UserDTO +} diff --git a/model/teamUserSeverity/entity.go b/model/teamUserSeverity/entity.go new file mode 100644 index 0000000..091e356 --- /dev/null +++ b/model/teamUserSeverity/entity.go @@ -0,0 +1,30 @@ +package teamUserSeverity + +import ( + "houston/model/teamSeverity" + "houston/model/teamUser" +) + +type TeamUserSeverityEntity struct { + ID uint `gorm:"primaryKey"` + TeamUser uint `gorm:"column:team_user;not null"` + TeamSeverity uint `gorm:"column:team_severity;not null"` + + // Add foreign key constraints + TeamUserEntity teamUser.TeamUserEntity `gorm:"foreignKey:TeamUser"` + TeamSeverityEntity teamSeverity.TeamSeverityEntity `gorm:"foreignKey:TeamSeverity"` +} + +func (TeamUserSeverityEntity) TableName() string { + return "team_user_severity" +} + +func (entity TeamUserSeverityEntity) ToDTO() TeamUserSeverityDTO { + return TeamUserSeverityDTO{ + ID: entity.ID, + TeamUser: entity.TeamUser, + TeamSeverity: entity.TeamSeverity, + TeamUserDTO: *entity.TeamUserEntity.ToDTO(), + TeamSeverityDTO: entity.TeamSeverityEntity.ToDTO(), + } +} diff --git a/model/teamUserSeverity/model.go b/model/teamUserSeverity/model.go new file mode 100644 index 0000000..54a8089 --- /dev/null +++ b/model/teamUserSeverity/model.go @@ -0,0 +1,15 @@ +package teamUserSeverity + +import ( + "houston/model/teamSeverity" + "houston/model/teamUser" +) + +type TeamUserSeverityDTO struct { + ID uint `json:"id"` + TeamUser uint `json:"team_user"` + TeamSeverity uint `json:"team_severity"` + + TeamUserDTO teamUser.TeamUserDTO + TeamSeverityDTO teamSeverity.TeamSeverityDTO +} diff --git a/model/user/model.go b/model/user/model.go index 66fa4c8..2de9068 100644 --- a/model/user/model.go +++ b/model/user/model.go @@ -16,3 +16,27 @@ type UserEntity struct { func (UserEntity) TableName() string { return "houston_user" } + +func (entity UserEntity) ToDTO() *UserDTO { + return &UserDTO{ + ID: entity.ID, + Name: entity.Name, + SlackUserId: entity.SlackUserId, + Active: entity.Active, + IsBot: entity.IsBot, + Email: entity.Email, + RealName: entity.RealName, + Image: entity.Image, + } +} + +type UserDTO struct { + ID uint `json:"id"` + Name string `json:"name"` + SlackUserId string `json:"slack_user_id"` + Active bool `json:"active"` + IsBot bool `json:"is_bot"` + Email string `json:"email"` + RealName string `json:"real_name"` + Image string `json:"image"` +} diff --git a/model/user/user.go b/model/user/user.go index df2b5af..44265ab 100644 --- a/model/user/user.go +++ b/model/user/user.go @@ -93,3 +93,27 @@ func (r *Repository) GetAllActiveHoustonUserBots() ([]UserEntity, error) { } return users, nil } + +func (r *Repository) GetHoustonUserByEmailId(emailId string) (*UserEntity, error) { + var user UserEntity + result := r.gormClient.Where("email = ?", emailId).First(&user) + if result.Error != nil { + if errors.Is(gorm.ErrRecordNotFound, result.Error) { + return nil, nil + } + return nil, result.Error + } + return &user, nil +} + +func (r *Repository) GetHoustonUserById(id uint) (*UserEntity, error) { + var user UserEntity + result := r.gormClient.Where("id = ?", id).First(&user) + if result.Error != nil { + if errors.Is(gorm.ErrRecordNotFound, result.Error) { + return nil, nil + } + return nil, result.Error + } + return &user, nil +} diff --git a/model/user/user_repository_interface.go b/model/user/user_repository_interface.go index ff30d72..5cc5395 100644 --- a/model/user/user_repository_interface.go +++ b/model/user/user_repository_interface.go @@ -9,4 +9,6 @@ type IUserRepository interface { InsertHoustonUser(user *UserEntity) error GetHoustonUsersBySlackId(slackUserId []string) (*[]UserEntity, error) GetAllActiveHoustonUserBots() ([]UserEntity, error) + GetHoustonUserByEmailId(emailId string) (*UserEntity, error) + GetHoustonUserById(id uint) (*UserEntity, error) } diff --git a/repository/teamSeverity/team_severity_repository_impl.go b/repository/teamSeverity/team_severity_repository_impl.go new file mode 100644 index 0000000..352b004 --- /dev/null +++ b/repository/teamSeverity/team_severity_repository_impl.go @@ -0,0 +1,30 @@ +package teamSeverity + +import ( + "gorm.io/gorm" + "houston/model/teamSeverity" +) + +type teamSeverityRepositoryImpl struct { + gormClient *gorm.DB +} + +func (repo *teamSeverityRepositoryImpl) AddSeveritiesForTeam(teamSeverities []teamSeverity.TeamSeverityEntity) ([]teamSeverity.TeamSeverityEntity, error) { + result := repo.gormClient.Create(&teamSeverities) + if result.Error != nil { + return nil, result.Error + } + + return teamSeverities, nil +} + +func (repo *teamSeverityRepositoryImpl) GetTeamSeveritiesByTeamId(teamId uint) ([]teamSeverity.TeamSeverityEntity, error) { + var teamSeverities []teamSeverity.TeamSeverityEntity + + result := repo.gormClient.Where("team_id = ?", teamId).Preload("Team").Preload("Severity").Find(&teamSeverities) + if result.Error != nil { + return nil, result.Error + } + + return teamSeverities, nil +} diff --git a/repository/teamSeverity/team_severity_repository_interface.go b/repository/teamSeverity/team_severity_repository_interface.go new file mode 100644 index 0000000..1f125c8 --- /dev/null +++ b/repository/teamSeverity/team_severity_repository_interface.go @@ -0,0 +1,17 @@ +package teamSeverity + +import ( + "gorm.io/gorm" + "houston/model/teamSeverity" +) + +type TeamSeverityRepository interface { + AddSeveritiesForTeam(teamSeverities []teamSeverity.TeamSeverityEntity) ([]teamSeverity.TeamSeverityEntity, error) + GetTeamSeveritiesByTeamId(teamId uint) ([]teamSeverity.TeamSeverityEntity, error) +} + +func NewTeamSeverityRepository(gormClient *gorm.DB) TeamSeverityRepository { + return &teamSeverityRepositoryImpl{ + gormClient: gormClient, + } +} diff --git a/repository/teamUser/team_user_repository_impl.go b/repository/teamUser/team_user_repository_impl.go new file mode 100644 index 0000000..9c5c067 --- /dev/null +++ b/repository/teamUser/team_user_repository_impl.go @@ -0,0 +1,58 @@ +package teamUser + +import ( + "gorm.io/gorm" + "houston/model/teamUser" +) + +type teamUserRepositoryImpl struct { + gormClient *gorm.DB +} + +func (repo *teamUserRepositoryImpl) AddTeamUser(teamUser teamUser.TeamUserEntity) (*teamUser.TeamUserEntity, error) { + result := repo.gormClient.Create(&teamUser) + if result.Error != nil { + return nil, result.Error + } + + return &teamUser, nil +} + +func (repo *teamUserRepositoryImpl) GetTeamUsersByTeamId(teamId uint) ([]teamUser.TeamUserEntity, error) { + var teamUsers []teamUser.TeamUserEntity + result := repo.gormClient.Where("team_id = ?", teamId).Preload("Team").Preload("User").Find(&teamUsers) + if result.Error != nil { + return nil, result.Error + } + + return teamUsers, nil +} + +func (repo *teamUserRepositoryImpl) GetTeamUserByTeamIdAndUserId(teamId, userId uint) (teamUser.TeamUserEntity, error) { + var teamUser teamUser.TeamUserEntity + result := repo.gormClient.Where("team_id = ? AND user_id = ?", teamId, userId).Preload("Team").Preload("User").First(&teamUser) + if result.Error != nil { + return teamUser, result.Error + } + + return teamUser, nil +} + +func (repo *teamUserRepositoryImpl) GetTeamsByUserId(userId uint) ([]teamUser.TeamUserEntity, error) { + var teamUsers []teamUser.TeamUserEntity + result := repo.gormClient.Where("user_id = ?", userId).Preload("Team").Find(&teamUsers) + if result.Error != nil { + return nil, result.Error + } + + return teamUsers, nil +} + +func (repo *teamUserRepositoryImpl) RemoveTeamUserByTeamIdAndUserId(teamId, userId uint) error { + result := repo.gormClient.Where("team_id = ? AND user_id = ?", teamId, userId).Delete(&teamUser.TeamUserEntity{}) + if result.Error != nil { + return result.Error + } + + return nil +} diff --git a/repository/teamUser/team_user_repository_interface.go b/repository/teamUser/team_user_repository_interface.go new file mode 100644 index 0000000..7ffb8ed --- /dev/null +++ b/repository/teamUser/team_user_repository_interface.go @@ -0,0 +1,20 @@ +package teamUser + +import ( + "gorm.io/gorm" + "houston/model/teamUser" +) + +type TeamUserRepository interface { + AddTeamUser(teamUser teamUser.TeamUserEntity) (*teamUser.TeamUserEntity, error) + GetTeamUsersByTeamId(teamId uint) ([]teamUser.TeamUserEntity, error) + GetTeamUserByTeamIdAndUserId(teamId, userId uint) (teamUser.TeamUserEntity, error) + GetTeamsByUserId(userId uint) ([]teamUser.TeamUserEntity, error) + RemoveTeamUserByTeamIdAndUserId(teamId, userId uint) error +} + +func NewTeamUserRepository(gormClient *gorm.DB) TeamUserRepository { + return &teamUserRepositoryImpl{ + gormClient: gormClient, + } +} diff --git a/repository/teamUserSeverity/team_user_severity_repository_impl.go b/repository/teamUserSeverity/team_user_severity_repository_impl.go new file mode 100644 index 0000000..ebcc479 --- /dev/null +++ b/repository/teamUserSeverity/team_user_severity_repository_impl.go @@ -0,0 +1,47 @@ +package teamUserSeverity + +import ( + "gorm.io/gorm" + "houston/model/teamUserSeverity" +) + +type teamUserSeverityRepositoryImpl struct { + gormClient *gorm.DB +} + +func (repo *teamUserSeverityRepositoryImpl) AddTeamUserSeverity(teamUserSeverity teamUserSeverity.TeamUserSeverityEntity) error { + result := repo.gormClient.Create(&teamUserSeverity) + if result.Error != nil { + return result.Error + } + + return nil +} + +func (repo *teamUserSeverityRepositoryImpl) GetTeamUserSeveritiesByTeamUserId(teamUserId uint) ([]teamUserSeverity.TeamUserSeverityEntity, error) { + var teamUserSeverities []teamUserSeverity.TeamUserSeverityEntity + result := repo.gormClient.Where("team_user = ?", teamUserId).Preload("TeamUserEntity").Preload("TeamSeverityEntity").Find(&teamUserSeverities) + if result.Error != nil { + return nil, result.Error + } + + return teamUserSeverities, nil +} + +func (repo *teamUserSeverityRepositoryImpl) UpdateTeamUserSeverity(teamUserSeverity teamUserSeverity.TeamUserSeverityEntity) error { + result := repo.gormClient.Save(&teamUserSeverity) + if result.Error != nil { + return result.Error + } + + return nil +} + +func (repo *teamUserSeverityRepositoryImpl) RemoveTeamUserSeverityByTeamIdAndUserId(teamId, userId uint) error { + result := repo.gormClient.Where("team_id = ? AND user_id = ?", teamId, userId).Delete(&teamUserSeverity.TeamUserSeverityEntity{}) + if result.Error != nil { + return result.Error + } + + return nil +} diff --git a/repository/teamUserSeverity/team_user_severity_repository_interface.go b/repository/teamUserSeverity/team_user_severity_repository_interface.go new file mode 100644 index 0000000..33bf618 --- /dev/null +++ b/repository/teamUserSeverity/team_user_severity_repository_interface.go @@ -0,0 +1,19 @@ +package teamUserSeverity + +import ( + "gorm.io/gorm" + "houston/model/teamUserSeverity" +) + +type TeamUserSeverityRepository interface { + AddTeamUserSeverity(teamUserSeverity teamUserSeverity.TeamUserSeverityEntity) error + GetTeamUserSeveritiesByTeamUserId(teamUserId uint) ([]teamUserSeverity.TeamUserSeverityEntity, error) + UpdateTeamUserSeverity(teamUserSeverity teamUserSeverity.TeamUserSeverityEntity) error + RemoveTeamUserSeverityByTeamIdAndUserId(teamId, userId uint) error +} + +func NewTeamUserSeverityRepository(gormClient *gorm.DB) TeamUserSeverityRepository { + return &teamUserSeverityRepositoryImpl{ + gormClient: gormClient, + } +} diff --git a/service/dtoConverter/team_dto_converter.go b/service/dtoConverter/team_dto_converter.go new file mode 100644 index 0000000..c4b04ca --- /dev/null +++ b/service/dtoConverter/team_dto_converter.go @@ -0,0 +1,40 @@ +package dtoConverter + +import ( + "houston/model/severity" + "houston/model/teamSeverity" + "houston/model/teamUser" + "houston/model/teamUserSeverity" +) + +func TeamSeverityEntitiesToDTOs(entities []teamSeverity.TeamSeverityEntity) []teamSeverity.TeamSeverityDTO { + var dtos []teamSeverity.TeamSeverityDTO + for _, entity := range entities { + dtos = append(dtos, entity.ToDTO()) + } + return dtos +} + +func TeamUserEntitiesToDTOs(entities []teamUser.TeamUserEntity) []*teamUser.TeamUserDTO { + var dtos []*teamUser.TeamUserDTO + for _, entity := range entities { + dtos = append(dtos, entity.ToDTO()) + } + return dtos +} + +func TeamUserSeverityEntitiesToDTOs(entities []teamUserSeverity.TeamUserSeverityEntity) []teamUserSeverity.TeamUserSeverityDTO { + var dtos []teamUserSeverity.TeamUserSeverityDTO + for _, entity := range entities { + dtos = append(dtos, entity.ToDTO()) + } + return dtos +} + +func SeverityEntitiesToDTOs(entities []severity.SeverityEntity) []severity.SeverityDTO { + var dtos []severity.SeverityDTO + for _, entity := range entities { + dtos = append(dtos, entity.ToDTO()) + } + return dtos +} diff --git a/service/incident/impl/incident_service_v2.go b/service/incident/impl/incident_service_v2.go index 8de7a3c..66f4dfc 100644 --- a/service/incident/impl/incident_service_v2.go +++ b/service/incident/impl/incident_service_v2.go @@ -29,6 +29,9 @@ import ( "houston/repository/externalTeamRepo" rcaRepository "houston/repository/rca/impl" "houston/repository/rcaInput" + teamSeverityRepo "houston/repository/teamSeverity" + "houston/repository/teamUser" + teamUserSeverityRepo "houston/repository/teamUserSeverity" "houston/service/alertService" conferenceService "houston/service/conference" "houston/service/documentService" @@ -43,9 +46,14 @@ import ( incidentRequest "houston/service/request/incident" service "houston/service/response" common "houston/service/response/common" + severityServiceImpl "houston/service/severity/impl" "houston/service/slack" "houston/service/tag" "houston/service/teamService" + teamSeverityServiceImpl "houston/service/teamSeverity/impl" + teamUserServiceImpl "houston/service/teamUser/impl" + teamUserSeverityServiceImpl "houston/service/teamUserSeverity/impl" + userServiceImpl "houston/service/user/impl" "math" "net/http" "regexp" @@ -88,7 +96,21 @@ func NewIncidentServiceV2(db *gorm.DB) *IncidentServiceV2 { krakatoaService := krakatoa.NewKrakatoaService() calendarActions := conference.GetCalendarActions() calendarService := conferenceService.NewCalendarService(calendarActions) - teamServiceV2 := teamService.NewTeamServiceV2(teamRepository, userRepository, slackService, externalTeamRepo.NewExternalTeamRepository(db)) + teamServiceV2 := teamService.NewTeamServiceV2( + teamRepository, + userRepository, + slackService, + externalTeamRepo.NewExternalTeamRepository(db), + teamSeverityServiceImpl.NewTeamSeverityService( + teamSeverityRepo.NewTeamSeverityRepository(db), + severityServiceImpl.NewSeverityService(severity.NewSeverityRepository(db)), + ), + userServiceImpl.NewUserService(user.NewUserRepository(db)), + teamUserServiceImpl.NewTeamUserService(teamUser.NewTeamUserRepository(db)), + teamUserSeverityServiceImpl.NewTeamUserSeverityService( + teamUserSeverityRepo.NewTeamUserSeverityRepository(db), + ), + ) tagService := tag.NewTagService(db) incidentService := &IncidentServiceV2{ db: db, diff --git a/service/request/team/add_team.go b/service/request/team/add_team.go new file mode 100644 index 0000000..93ff4ca --- /dev/null +++ b/service/request/team/add_team.go @@ -0,0 +1,11 @@ +package team + +type AddTeamRequest struct { + Name string `json:"name"` + ManagerDetails *ManagerDetails `json:"manager_details"` +} + +type ManagerDetails struct { + Email *string `json:"email"` + SeverityId *uint `json:"severity_id"` +} diff --git a/service/severity/impl/severity_service.go b/service/severity/impl/severity_service.go new file mode 100644 index 0000000..bf2e019 --- /dev/null +++ b/service/severity/impl/severity_service.go @@ -0,0 +1,28 @@ +package impl + +import ( + "go.uber.org/zap" + "houston/logger" + "houston/model/severity" + "houston/service/dtoConverter" +) + +type SeverityService struct { + severityRepository severity.ISeverityRepository +} + +func NewSeverityService(severityRepository severity.ISeverityRepository) *SeverityService { + return &SeverityService{ + severityRepository: severityRepository, + } +} + +func (service *SeverityService) GetAllActiveSeverities() ([]severity.SeverityDTO, error) { + severityEntities, err := service.severityRepository.GetAllActiveSeverity() + if err != nil { + logger.Error("failed to get all active severities", zap.Error(err)) + return nil, err + } + + return dtoConverter.SeverityEntitiesToDTOs(*severityEntities), err +} diff --git a/service/severity/impl/severity_service_test.go b/service/severity/impl/severity_service_test.go new file mode 100644 index 0000000..2121530 --- /dev/null +++ b/service/severity/impl/severity_service_test.go @@ -0,0 +1,52 @@ +package impl + +import ( + "errors" + "github.com/stretchr/testify/suite" + "houston/logger" + "houston/mocks" + "houston/model/severity" + "testing" +) + +type SeverityServiceSuite struct { + suite.Suite + severityRepository *mocks.ISeverityRepositoryMock + severityService *SeverityService +} + +func (suite *SeverityServiceSuite) Test_GetAllActiveSeverities_RepoFailureCase() { + suite.severityRepository.GetAllActiveSeverityMock.Return(nil, errors.New("error")) + + severities, err := suite.severityService.GetAllActiveSeverities() + suite.Nil(severities, "severities should be nil") + suite.EqualError(err, "error", "error should be error") +} + +func (suite *SeverityServiceSuite) Test_GetAllActiveSeverities_SuccessCase() { + mockSeverityEntities := GetMockSeverityEntities() + suite.severityRepository.GetAllActiveSeverityMock.Return(mockSeverityEntities, nil) + + severities, err := suite.severityService.GetAllActiveSeverities() + suite.Nil(err, "error should be nil") + suite.NotNil(severities, "severities should not be nil") + suite.Len(severities, len(*mockSeverityEntities), "length of severities should be equal") +} + +func GetMockSeverityEntities() *[]severity.SeverityEntity { + return &[]severity.SeverityEntity{ + {Name: "Low", Description: "Low severity"}, + {Name: "Medium", Description: "Medium severity"}, + {Name: "High", Description: "High severity"}, + } +} + +func (suite *SeverityServiceSuite) SetupSuite() { + logger.InitLogger() + suite.severityRepository = mocks.NewISeverityRepositoryMock(suite.T()) + suite.severityService = NewSeverityService(suite.severityRepository) +} + +func TestSeverityService(t *testing.T) { + suite.Run(t, new(SeverityServiceSuite)) +} diff --git a/service/severity/severity_service_interface.go b/service/severity/severity_service_interface.go new file mode 100644 index 0000000..3e421b1 --- /dev/null +++ b/service/severity/severity_service_interface.go @@ -0,0 +1,7 @@ +package severity + +import "houston/model/severity" + +type ISeverityService interface { + GetAllActiveSeverities() ([]severity.SeverityDTO, error) +} diff --git a/service/teamService/team_service_v2.go b/service/teamService/team_service_v2.go index f52d417..416802e 100644 --- a/service/teamService/team_service_v2.go +++ b/service/teamService/team_service_v2.go @@ -3,6 +3,7 @@ package teamService import ( "errors" "fmt" + "github.com/jackc/pgx/v5/pgconn" "go.uber.org/zap" "gorm.io/gorm" "houston/common/util" @@ -12,25 +13,41 @@ import ( "houston/model/team" "houston/model/user" "houston/repository/externalTeamRepo" + teamRequest "houston/service/request/team" service "houston/service/response" "houston/service/slack" + "houston/service/teamSeverity" + "houston/service/teamUser" + "houston/service/teamUserSeverity" + userService "houston/service/user" "sort" + "strings" ) type TeamServiceV2 struct { - teamRepository team.ITeamRepository - userRepository user.IUserRepository - slackService slack.ISlackService - externalTeamRepository externalTeamRepo.IExternalTeamRepository + teamRepository team.ITeamRepository + userRepository user.IUserRepository + slackService slack.ISlackService + externalTeamRepository externalTeamRepo.IExternalTeamRepository + teamSeverityService teamSeverity.ITeamSeverityService + userService userService.IUserService + teamUserService teamUser.ITeamUserService + teamUserSeverityService teamUserSeverity.ITeamUserSeverityService } func NewTeamServiceV2(teamRepository team.ITeamRepository, userRepository user.IUserRepository, - slackService slack.ISlackService, externalTeamRepository externalTeamRepo.IExternalTeamRepository) *TeamServiceV2 { + slackService slack.ISlackService, externalTeamRepository externalTeamRepo.IExternalTeamRepository, + teamSeverityService teamSeverity.ITeamSeverityService, userService userService.IUserService, + teamUserService teamUser.ITeamUserService, teamUserSeverityService teamUserSeverity.ITeamUserSeverityService) *TeamServiceV2 { return &TeamServiceV2{ - teamRepository: teamRepository, - userRepository: userRepository, - slackService: slackService, - externalTeamRepository: externalTeamRepository, + teamRepository: teamRepository, + userRepository: userRepository, + slackService: slackService, + externalTeamRepository: externalTeamRepository, + teamSeverityService: teamSeverityService, + userService: userService, + teamUserService: teamUserService, + teamUserSeverityService: teamUserSeverityService, } } @@ -193,3 +210,69 @@ func (teamService *TeamServiceV2) GetExternalTeam(teamId uint, provider string) } return &externalTeamDTO, nil } + +func (teamService *TeamServiceV2) AddTeam(request teamRequest.AddTeamRequest, userEmail string) (*service.AddTeamResponse, error) { + managerEmail := *request.ManagerDetails.Email + managerSeverityId := *request.ManagerDetails.SeverityId + + userInfo, err := teamService.userService.GetHoustonUserByEmailId(managerEmail) + if err != nil { + logger.Error(fmt.Sprintf("%s error in manager's user info for email: %s", logTag, managerEmail), zap.Error(err)) + return nil, customErrors.NewInvalidInputError("Manager email is invalid") + } + if userInfo == nil { + logger.Error(fmt.Sprintf("%s manager not found for email: %s", logTag, managerEmail)) + return nil, customErrors.NewNotFoundError("Manager with given email not found") + } + + //todo: remove slack user ids column in long term plan + managerHandle := userInfo.SlackUserId + slackUserList := []string{managerHandle} + + teamEntity := &team.TeamEntity{ + Name: request.Name, + ManagerHandle: managerHandle, + SlackUserIds: slackUserList, + CreatedBy: userEmail, + UpdatedBy: userEmail, + Active: true, + } + + _, err = teamService.teamRepository.CreateTeam(teamEntity) + if err != nil { + logger.Error(fmt.Sprintf("%s error in creating team with name: %s", logTag, request.Name), zap.Error(err)) + if pgErr, ok := err.(*pgconn.PgError); ok { + if strings.Contains(pgErr.Message, util.DUPLICATE_KEY_VALUE_MESSAGE) { + return nil, customErrors.NewInvalidInputError("Team name already exists") + } + } + return nil, customErrors.NewDataAccessError("error in creating team") + } + + teamSeverities, err := teamService.teamSeverityService.AddAllSeveritiesForATeam(teamEntity.ID) + if err != nil { + logger.Error(fmt.Sprintf("%s error in adding severities for team with id: %d", logTag, teamEntity.ID), zap.Error(err)) + return nil, customErrors.NewDataAccessError("error in adding severities for team") + } + + teamUserEntity, err := teamService.teamUserService.AddTeamUser(teamEntity.ID, userInfo.ID) + if err != nil { + logger.Error(fmt.Sprintf("%s error in adding manager to team with id: %d", logTag, teamEntity.ID), zap.Error(err)) + return nil, customErrors.NewDataAccessError("error in adding manager to team") + } + + for _, teamSeverity := range teamSeverities { + if teamSeverity.SeverityID == managerSeverityId { + err := teamService.teamUserSeverityService.AddTeamUserSeverity(teamUserEntity.ID, teamSeverity.ID) + if err != nil { + logger.Error(fmt.Sprintf("%s error in adding manager to severity with id: %d", logTag, teamSeverity.ID), zap.Error(err)) + return nil, customErrors.NewDataAccessError("error in adding manager to team severity") + } + } + } + + return &service.AddTeamResponse{ + ID: teamEntity.ID, + Message: "Team created successfully!", + }, nil +} diff --git a/service/teamService/team_service_v2_interface.go b/service/teamService/team_service_v2_interface.go index 0d54f5a..5a54a21 100644 --- a/service/teamService/team_service_v2_interface.go +++ b/service/teamService/team_service_v2_interface.go @@ -2,6 +2,7 @@ package teamService import ( "houston/model/externalTeam" + teamRequest "houston/service/request/team" service "houston/service/response" ) @@ -9,4 +10,5 @@ type ITeamServiceV2 interface { GetTeamDetails(teamId uint) (*service.TeamResponse, error) GetAllTeams() ([]service.TeamResponse, error) GetExternalTeam(teamId uint, provider string) (*externalTeam.ExternalTeamDTO, error) + AddTeam(request teamRequest.AddTeamRequest, userEmail string) (*service.AddTeamResponse, error) } diff --git a/service/teamService/team_service_v2_test.go b/service/teamService/team_service_v2_test.go index 71f127f..944369b 100644 --- a/service/teamService/team_service_v2_test.go +++ b/service/teamService/team_service_v2_test.go @@ -3,27 +3,36 @@ package teamService import ( "errors" "github.com/gojuno/minimock/v3" + "github.com/jackc/pgx/v5/pgconn" "github.com/slack-go/slack" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/suite" "gorm.io/gorm" + "houston/common/util" "houston/logger" "houston/mocks" "houston/model/externalTeam" "houston/model/team" + "houston/model/teamSeverity" + "houston/model/teamUser" "houston/model/user" + teamRequest "houston/service/request/team" service "houston/service/response" "testing" ) type TeamServiceV2Suite struct { suite.Suite - controller *minimock.Controller - teamRepository *mocks.ITeamRepositoryMock - userRepository *mocks.IUserRepositoryMock - slackService *mocks.ISlackServiceMock - externalTeamRepository *mocks.IExternalTeamRepositoryMock - teamService *TeamServiceV2 + controller *minimock.Controller + teamRepository *mocks.ITeamRepositoryMock + userRepository *mocks.IUserRepositoryMock + slackService *mocks.ISlackServiceMock + externalTeamRepository *mocks.IExternalTeamRepositoryMock + teamSeverityService *mocks.ITeamSeverityServiceMock + userService *mocks.IUserServiceMock + teamUserService *mocks.ITeamUserServiceMock + teamUserSeverityService *mocks.ITeamUserSeverityServiceMock + teamService *TeamServiceV2 } func (suite *TeamServiceV2Suite) SetupSuite() { @@ -34,7 +43,14 @@ func (suite *TeamServiceV2Suite) SetupSuite() { suite.userRepository = mocks.NewIUserRepositoryMock(suite.controller) suite.slackService = mocks.NewISlackServiceMock(suite.controller) suite.externalTeamRepository = mocks.NewIExternalTeamRepositoryMock(suite.controller) - suite.teamService = NewTeamServiceV2(suite.teamRepository, suite.userRepository, suite.slackService, suite.externalTeamRepository) + suite.teamSeverityService = mocks.NewITeamSeverityServiceMock(suite.controller) + suite.userService = mocks.NewIUserServiceMock(suite.controller) + suite.teamUserService = mocks.NewITeamUserServiceMock(suite.controller) + suite.teamUserSeverityService = mocks.NewITeamUserSeverityServiceMock(suite.controller) + suite.teamService = NewTeamServiceV2( + suite.teamRepository, suite.userRepository, suite.slackService, suite.externalTeamRepository, + suite.teamSeverityService, suite.userService, suite.teamUserService, suite.teamUserSeverityService, + ) } func TestTeamServiceV2(t *testing.T) { @@ -230,3 +246,174 @@ func (suite *TeamServiceV2Suite) TestGetExternalTeam_Success() { assert.Equal(suite.T(), uint(1), externalTeamDTO.TeamID) assert.Equal(suite.T(), true, externalTeamDTO.IsActive) } + +func (suite *TeamServiceV2Suite) Test_AddTeam_UserFetchingFailureCase() { + suite.userService.GetHoustonUserByEmailIdMock.Return(nil, errors.New("error")) + teamName, managerEmail, managerSeverityId := "test team", "test@navi.com", uint(3) + + teamResponse, err := suite.teamService.AddTeam(teamRequest.AddTeamRequest{ + Name: teamName, + ManagerDetails: &teamRequest.ManagerDetails{ + Email: &managerEmail, + SeverityId: &managerSeverityId, + }, + }, "admin@navi.com") + + assert.Nil(suite.T(), teamResponse) + assert.EqualError(suite.T(), err, "Manager email is invalid") +} + +func (suite *TeamServiceV2Suite) Test_AddTeam_UserNotFoundCase() { + suite.userService.GetHoustonUserByEmailIdMock.Return(nil, nil) + teamName, managerEmail, managerSeverityId := "test team", "test@navi.com", uint(3) + + teamResponse, err := suite.teamService.AddTeam(teamRequest.AddTeamRequest{ + Name: teamName, + ManagerDetails: &teamRequest.ManagerDetails{ + Email: &managerEmail, + SeverityId: &managerSeverityId, + }, + }, "admin@navi.com") + + assert.Nil(suite.T(), teamResponse) + assert.EqualError(suite.T(), err, "Manager with given email not found") +} + +func (suite *TeamServiceV2Suite) Test_AddTeam_TeamNameAlreadyExistsCase() { + suite.userService.GetHoustonUserByEmailIdMock.Return(GetMockUserDTO(), nil) + suite.teamRepository.CreateTeamMock.Return(nil, &pgconn.PgError{Message: util.DUPLICATE_KEY_VALUE_MESSAGE}) + teamName, managerEmail, managerSeverityId := "test team", "test@navi.com", uint(3) + + teamResponse, err := suite.teamService.AddTeam(teamRequest.AddTeamRequest{ + Name: teamName, + ManagerDetails: &teamRequest.ManagerDetails{ + Email: &managerEmail, + SeverityId: &managerSeverityId, + }, + }, "admin@navi.com") + assert.Nil(suite.T(), teamResponse) + assert.EqualError(suite.T(), err, "Team name already exists") +} + +func (suite *TeamServiceV2Suite) Test_AddTeam_TeamCreationFailureCase() { + suite.userService.GetHoustonUserByEmailIdMock.Return(GetMockUserDTO(), nil) + suite.teamRepository.CreateTeamMock.Return(nil, errors.New("error")) + teamName, managerEmail, managerSeverityId := "test team", "test@navi.com", uint(3) + + teamResponse, err := suite.teamService.AddTeam(teamRequest.AddTeamRequest{ + Name: teamName, + ManagerDetails: &teamRequest.ManagerDetails{ + Email: &managerEmail, + SeverityId: &managerSeverityId, + }, + }, "admin@navi.com") + assert.Nil(suite.T(), teamResponse) + assert.EqualError(suite.T(), err, "error in creating team") +} + +func (suite *TeamServiceV2Suite) Test_AddTeam_TeamSeverityErrorCase() { + suite.userService.GetHoustonUserByEmailIdMock.Return(GetMockUserDTO(), nil) + suite.teamRepository.CreateTeamMock.Return(GetMockTeamEntity(), nil) + suite.teamSeverityService.AddAllSeveritiesForATeamMock.Return(nil, errors.New("error")) + teamName, managerEmail, managerSeverityId := "test team", "test@navi.com", uint(3) + + teamResponse, err := suite.teamService.AddTeam(teamRequest.AddTeamRequest{ + Name: teamName, + ManagerDetails: &teamRequest.ManagerDetails{ + Email: &managerEmail, + SeverityId: &managerSeverityId, + }, + }, "admin@navi.com") + assert.Nil(suite.T(), teamResponse) + assert.EqualError(suite.T(), err, "error in adding severities for team") +} + +func (suite *TeamServiceV2Suite) Test_AddTeam_TeamUserFailureCase() { + suite.userService.GetHoustonUserByEmailIdMock.Return(GetMockUserDTO(), nil) + suite.teamRepository.CreateTeamMock.Return(GetMockTeamEntity(), nil) + suite.teamSeverityService.AddAllSeveritiesForATeamMock.Return(GetMockTeamSeverityDTOs(), nil) + suite.teamUserService.AddTeamUserMock.Return(nil, errors.New("error")) + teamName, managerEmail, managerSeverityId := "test team", "test@navi.com", uint(3) + + teamResponse, err := suite.teamService.AddTeam(teamRequest.AddTeamRequest{ + Name: teamName, + ManagerDetails: &teamRequest.ManagerDetails{ + Email: &managerEmail, + SeverityId: &managerSeverityId, + }, + }, "admin@navi.com") + + assert.Nil(suite.T(), teamResponse) + assert.EqualError(suite.T(), err, "error in adding manager to team") +} + +func (suite *TeamServiceV2Suite) Test_AddTeam_TeamUserSeverityFailureCase() { + suite.userService.GetHoustonUserByEmailIdMock.Return(GetMockUserDTO(), nil) + suite.teamRepository.CreateTeamMock.Return(GetMockTeamEntity(), nil) + suite.teamSeverityService.AddAllSeveritiesForATeamMock.Return(GetMockTeamSeverityDTOs(), nil) + suite.teamUserService.AddTeamUserMock.Return(GetMockTeamUserDTO(), nil) + suite.teamUserSeverityService.AddTeamUserSeverityMock.Return(errors.New("error")) + teamName, managerEmail, managerSeverityId := "test team", "test@navi.com", uint(3) + + teamResponse, err := suite.teamService.AddTeam(teamRequest.AddTeamRequest{ + Name: teamName, + ManagerDetails: &teamRequest.ManagerDetails{ + Email: &managerEmail, + SeverityId: &managerSeverityId, + }, + }, "admin@navi.com") + + assert.Nil(suite.T(), teamResponse) + assert.EqualError(suite.T(), err, "error in adding manager to team severity") +} + +func (suite *TeamServiceV2Suite) Test_AddTeam_SuccessCase() { + suite.userService.GetHoustonUserByEmailIdMock.Return(GetMockUserDTO(), nil) + suite.teamRepository.CreateTeamMock.Return(GetMockTeamEntity(), nil) + suite.teamSeverityService.AddAllSeveritiesForATeamMock.Return(GetMockTeamSeverityDTOs(), nil) + suite.teamUserService.AddTeamUserMock.Return(GetMockTeamUserDTO(), nil) + suite.teamUserSeverityService.AddTeamUserSeverityMock.Return(nil) + teamName, managerEmail, managerSeverityId := "test team", "test@navi.com", uint(3) + + teamResponse, err := suite.teamService.AddTeam(teamRequest.AddTeamRequest{ + Name: teamName, + ManagerDetails: &teamRequest.ManagerDetails{ + Email: &managerEmail, + SeverityId: &managerSeverityId, + }, + }, "admin@navi.com") + + assert.Nil(suite.T(), err) + assert.NotNil(suite.T(), teamResponse) +} + +func GetMockUserDTO() *user.UserDTO { + return &user.UserDTO{ + Email: "test@navi.com", + SlackUserId: "test", + Name: "Test User", + } +} + +func GetMockTeamEntity() *team.TeamEntity { + return &team.TeamEntity{ + Name: "test team", + Active: true, + } +} + +func GetMockTeamSeverityDTOs() []teamSeverity.TeamSeverityDTO { + return []teamSeverity.TeamSeverityDTO{ + {ID: 1, TeamID: 1, SeverityID: 1}, + {ID: 2, TeamID: 1, SeverityID: 2}, + {ID: 3, TeamID: 1, SeverityID: 3}, + } +} + +func GetMockTeamUserDTO() *teamUser.TeamUserDTO { + return &teamUser.TeamUserDTO{ + ID: 1, + TeamID: 1, + UserID: 1, + } +} diff --git a/service/teamSeverity/impl/team_severity_service.go b/service/teamSeverity/impl/team_severity_service.go new file mode 100644 index 0000000..d8fcb7d --- /dev/null +++ b/service/teamSeverity/impl/team_severity_service.go @@ -0,0 +1,64 @@ +package impl + +import ( + "go.uber.org/zap" + "houston/logger" + teamSeverityModel "houston/model/teamSeverity" + "houston/repository/teamSeverity" + "houston/service/dtoConverter" + "houston/service/severity" +) + +type TeamSeverityService struct { + teamSeverityRepository teamSeverity.TeamSeverityRepository + severityService severity.ISeverityService +} + +func NewTeamSeverityService( + teamSeverityRepository teamSeverity.TeamSeverityRepository, + severityService severity.ISeverityService, +) *TeamSeverityService { + return &TeamSeverityService{ + teamSeverityRepository: teamSeverityRepository, + severityService: severityService, + } +} + +func (service *TeamSeverityService) AddAllSeveritiesForATeam(teamID uint) ( + []teamSeverityModel.TeamSeverityDTO, error, +) { + severities, err := service.severityService.GetAllActiveSeverities() + if err != nil { + logger.Error("error in fetching all active severities", zap.Error(err)) + return nil, err + } + + var teamSeverities []teamSeverityModel.TeamSeverityEntity + for _, severity := range severities { + teamSeverities = append(teamSeverities, teamSeverityModel.TeamSeverityEntity{ + TeamID: teamID, + SeverityID: severity.ID, + Sla: severity.Sla, + }) + } + + teamSeverityEntities, err := service.teamSeverityRepository.AddSeveritiesForTeam(teamSeverities) + if err != nil { + logger.Error("error in adding severities for team", zap.Error(err)) + return nil, err + } + + return dtoConverter.TeamSeverityEntitiesToDTOs(teamSeverityEntities), nil +} + +func (service *TeamSeverityService) GetSeveritiesForATeam(teamID uint) ( + []teamSeverityModel.TeamSeverityDTO, error, +) { + teamSeverities, err := service.teamSeverityRepository.GetTeamSeveritiesByTeamId(teamID) + if err != nil { + logger.Error("error in fetching severities for team", zap.Error(err)) + return nil, err + } + + return dtoConverter.TeamSeverityEntitiesToDTOs(teamSeverities), nil +} diff --git a/service/teamSeverity/impl/team_severity_service_test.go b/service/teamSeverity/impl/team_severity_service_test.go new file mode 100644 index 0000000..528befb --- /dev/null +++ b/service/teamSeverity/impl/team_severity_service_test.go @@ -0,0 +1,92 @@ +package impl + +import ( + "errors" + "github.com/stretchr/testify/suite" + "houston/logger" + "houston/mocks" + "houston/model/severity" + "houston/model/teamSeverity" + "testing" +) + +type TeamSeverityServiceSuite struct { + suite.Suite + teamSeverityRepository *mocks.TeamSeverityRepositoryMock + severityService *mocks.ISeverityServiceMock + teamSeverityService *TeamSeverityService +} + +func (suite *TeamSeverityServiceSuite) Test_AddAllSeveritiesForATeam_SeverityServiceFailure() { + suite.severityService.GetAllActiveSeveritiesMock.Return(nil, errors.New("DB error")) + + severities, err := suite.teamSeverityService.AddAllSeveritiesForATeam(1) + suite.Nil(severities, "severities should be nil") + suite.EqualError(err, "DB error", "error should be DB error") +} + +func (suite *TeamSeverityServiceSuite) Test_AddAllSeveritiesForATeam_TeamSeverityRepoError() { + suite.severityService.GetAllActiveSeveritiesMock.Return(GetMockSeverityDTOs(), nil) + suite.teamSeverityRepository.AddSeveritiesForTeamMock.Return(nil, errors.New("DB error")) + + severities, err := suite.teamSeverityService.AddAllSeveritiesForATeam(1) + suite.Nil(severities, "severities should be nil") + suite.EqualError(err, "DB error", "error should be DB error") +} + +func (suite *TeamSeverityServiceSuite) Test_AddAllSeveritiesForATeam_Success() { + mockSeverityDTOs := GetMockSeverityDTOs() + mockTeamSeverityEntities := GetMockTeamSeverityEntities() + suite.severityService.GetAllActiveSeveritiesMock.Return(mockSeverityDTOs, nil) + suite.teamSeverityRepository.AddSeveritiesForTeamMock.Return(mockTeamSeverityEntities, nil) + + severities, err := suite.teamSeverityService.AddAllSeveritiesForATeam(1) + suite.NotNil(severities, "severities should not be nil") + suite.Nil(err, "error should be nil") + suite.Equal(len(mockSeverityDTOs), len(severities), "length of severities should be equal") +} + +func (suite *TeamSeverityServiceSuite) Test_GetSeveritiesForATeam_TeamSeverityRepoError() { + suite.teamSeverityRepository.GetTeamSeveritiesByTeamIdMock.Return(nil, errors.New("DB error")) + + severities, err := suite.teamSeverityService.GetSeveritiesForATeam(1) + suite.Nil(severities, "severities should be nil") + suite.EqualError(err, "DB error", "error should be DB error") +} + +func (suite *TeamSeverityServiceSuite) Test_GetSeveritiesForATeam_Success() { + mockTeamSeverityEntities := GetMockTeamSeverityEntities() + suite.teamSeverityRepository.GetTeamSeveritiesByTeamIdMock.Return(mockTeamSeverityEntities, nil) + + severities, err := suite.teamSeverityService.GetSeveritiesForATeam(1) + suite.NotNil(severities, "severities should not be nil") + suite.Nil(err, "error should be nil") + suite.Equal(len(mockTeamSeverityEntities), len(severities), "length of severities should be equal") +} + +func GetMockSeverityDTOs() []severity.SeverityDTO { + return []severity.SeverityDTO{ + {ID: 1, Name: "Low", Description: "Low severity"}, + {ID: 2, Name: "Medium", Description: "Medium severity"}, + {ID: 3, Name: "High", Description: "High severity"}, + } +} + +func GetMockTeamSeverityEntities() []teamSeverity.TeamSeverityEntity { + return []teamSeverity.TeamSeverityEntity{ + {TeamID: 1, SeverityID: 1}, + {TeamID: 1, SeverityID: 2}, + {TeamID: 1, SeverityID: 3}, + } +} + +func (suite *TeamSeverityServiceSuite) SetupSuite() { + logger.InitLogger() + suite.teamSeverityRepository = mocks.NewTeamSeverityRepositoryMock(suite.T()) + suite.severityService = mocks.NewISeverityServiceMock(suite.T()) + suite.teamSeverityService = NewTeamSeverityService(suite.teamSeverityRepository, suite.severityService) +} + +func TestTeamSeverityService(t *testing.T) { + suite.Run(t, new(TeamSeverityServiceSuite)) +} diff --git a/service/teamSeverity/team_severity_service_interface.go b/service/teamSeverity/team_severity_service_interface.go new file mode 100644 index 0000000..ea32379 --- /dev/null +++ b/service/teamSeverity/team_severity_service_interface.go @@ -0,0 +1,8 @@ +package teamSeverity + +import teamSeverityModel "houston/model/teamSeverity" + +type ITeamSeverityService interface { + AddAllSeveritiesForATeam(teamID uint) ([]teamSeverityModel.TeamSeverityDTO, error) + GetSeveritiesForATeam(teamID uint) ([]teamSeverityModel.TeamSeverityDTO, error) +} diff --git a/service/teamUser/impl/team_user_service.go b/service/teamUser/impl/team_user_service.go new file mode 100644 index 0000000..0b4294d --- /dev/null +++ b/service/teamUser/impl/team_user_service.go @@ -0,0 +1,54 @@ +package impl + +import ( + "go.uber.org/zap" + "houston/logger" + teamUserModel "houston/model/teamUser" + "houston/repository/teamUser" + "houston/service/dtoConverter" +) + +type TeamUserService struct { + teamUserRepository teamUser.TeamUserRepository +} + +func NewTeamUserService(teamUserRepository teamUser.TeamUserRepository) *TeamUserService { + return &TeamUserService{ + teamUserRepository: teamUserRepository, + } +} + +func (service *TeamUserService) AddTeamUser(teamId, userId uint) (*teamUserModel.TeamUserDTO, error) { + teamUserEntity := teamUserModel.TeamUserEntity{ + TeamID: teamId, + UserID: userId, + } + + teamUser, err := service.teamUserRepository.AddTeamUser(teamUserEntity) + if err != nil { + logger.Error("error in fetching team users", zap.Error(err)) + return nil, err + } + + return teamUser.ToDTO(), nil +} + +func (service *TeamUserService) GetTeamUsersByTeamId(teamId uint) ([]*teamUserModel.TeamUserDTO, error) { + teamUsers, err := service.teamUserRepository.GetTeamUsersByTeamId(teamId) + if err != nil { + logger.Error("error in fetching team users", zap.Error(err)) + return nil, err + } + + return dtoConverter.TeamUserEntitiesToDTOs(teamUsers), nil +} + +func (service *TeamUserService) GetTeamsByUserId(userId uint) ([]*teamUserModel.TeamUserDTO, error) { + teamUsers, err := service.teamUserRepository.GetTeamsByUserId(userId) + if err != nil { + logger.Error("error in fetching user teams", zap.Error(err)) + return nil, err + } + + return dtoConverter.TeamUserEntitiesToDTOs(teamUsers), nil +} diff --git a/service/teamUser/impl/team_user_service_test.go b/service/teamUser/impl/team_user_service_test.go new file mode 100644 index 0000000..cf8f901 --- /dev/null +++ b/service/teamUser/impl/team_user_service_test.go @@ -0,0 +1,89 @@ +package impl + +import ( + "errors" + "github.com/stretchr/testify/suite" + "houston/logger" + "houston/mocks" + teamUserModel "houston/model/teamUser" + "testing" +) + +type TeamUserServiceSuite struct { + suite.Suite + teamUserRepository *mocks.TeamUserRepositoryMock + teamUserService *TeamUserService +} + +func (suite *TeamUserServiceSuite) Test_AddTeamUser_RepoFailureCase() { + suite.teamUserRepository.AddTeamUserMock.Return(nil, errors.New("error")) + + teamUser, err := suite.teamUserService.AddTeamUser(1, 1) + suite.Nil(teamUser, "teamUser should be nil") + suite.EqualError(err, "error", "error should be error") +} + +func (suite *TeamUserServiceSuite) Test_AddTeamUser_SuccessCase() { + mockTeamUserEntity := GetMockTeamUserEntity(1, 1) + suite.teamUserRepository.AddTeamUserMock.Return(&mockTeamUserEntity, nil) + + teamUser, err := suite.teamUserService.AddTeamUser(1, 1) + suite.Nil(err, "error should be nil") + suite.NotNil(teamUser, "teamUser should not be nil") +} + +func (suite *TeamUserServiceSuite) Test_GetTeamUsersByTeamId_RepoFailureCase() { + suite.teamUserRepository.GetTeamUsersByTeamIdMock.Return(nil, errors.New("error")) + + teamUsers, err := suite.teamUserService.GetTeamUsersByTeamId(1) + suite.Nil(teamUsers, "teamUsers should be nil") + suite.EqualError(err, "error", "error should be error") +} + +func (suite *TeamUserServiceSuite) Test_GetTeamUsersByTeamId_SuccessCase() { + suite.teamUserRepository.GetTeamUsersByTeamIdMock.Return(GetMockTeamUserEntities(), nil) + + teamUsers, err := suite.teamUserService.GetTeamUsersByTeamId(1) + suite.Nil(err, "error should be nil") + suite.NotNil(teamUsers, "teamUsers should not be nil") +} + +func (suite *TeamUserServiceSuite) Test_GetTeamsByUserId_RepoFailureCase() { + suite.teamUserRepository.GetTeamsByUserIdMock.Return(nil, errors.New("error")) + + teamUsers, err := suite.teamUserService.GetTeamsByUserId(1) + suite.Nil(teamUsers, "teamUsers should be nil") + suite.EqualError(err, "error", "error should be error") +} + +func (suite *TeamUserServiceSuite) Test_GetTeamsByUserId_SuccessCase() { + suite.teamUserRepository.GetTeamsByUserIdMock.Return(GetMockTeamUserEntities(), nil) + + teamUsers, err := suite.teamUserService.GetTeamsByUserId(1) + suite.Nil(err, "error should be nil") + suite.NotNil(teamUsers, "teamUsers should not be nil") +} + +func (suite *TeamUserServiceSuite) SetupSuite() { + logger.InitLogger() + suite.teamUserRepository = mocks.NewTeamUserRepositoryMock(suite.T()) + suite.teamUserService = NewTeamUserService(suite.teamUserRepository) +} + +func TestTeamUserService(t *testing.T) { + suite.Run(t, new(TeamUserServiceSuite)) +} + +func GetMockTeamUserEntity(teamId, userId uint) teamUserModel.TeamUserEntity { + return teamUserModel.TeamUserEntity{ + TeamID: teamId, + UserID: userId, + } +} + +func GetMockTeamUserEntities() []teamUserModel.TeamUserEntity { + return []teamUserModel.TeamUserEntity{ + GetMockTeamUserEntity(1, 1), + GetMockTeamUserEntity(1, 2), + } +} diff --git a/service/teamUser/team_user_service_interface.go b/service/teamUser/team_user_service_interface.go new file mode 100644 index 0000000..6810c6a --- /dev/null +++ b/service/teamUser/team_user_service_interface.go @@ -0,0 +1,9 @@ +package teamUser + +import teamUserModel "houston/model/teamUser" + +type ITeamUserService interface { + AddTeamUser(teamId, userId uint) (*teamUserModel.TeamUserDTO, error) + GetTeamUsersByTeamId(teamId uint) ([]*teamUserModel.TeamUserDTO, error) + GetTeamsByUserId(userId uint) ([]*teamUserModel.TeamUserDTO, error) +} diff --git a/service/teamUserSeverity/impl/team_user_severity_service.go b/service/teamUserSeverity/impl/team_user_severity_service.go new file mode 100644 index 0000000..c3f3975 --- /dev/null +++ b/service/teamUserSeverity/impl/team_user_severity_service.go @@ -0,0 +1,25 @@ +package impl + +import ( + teamUserSeverityModel "houston/model/teamUserSeverity" + "houston/repository/teamUserSeverity" +) + +type TeamUserSeverityService struct { + teamUserSeverityRepository teamUserSeverity.TeamUserSeverityRepository +} + +func NewTeamUserSeverityService(teamUserSeverityRepository teamUserSeverity.TeamUserSeverityRepository) *TeamUserSeverityService { + return &TeamUserSeverityService{ + teamUserSeverityRepository: teamUserSeverityRepository, + } +} + +func (service *TeamUserSeverityService) AddTeamUserSeverity(teamUserId, teamSeverityId uint) error { + teamUserSeverityEntity := teamUserSeverityModel.TeamUserSeverityEntity{ + TeamUser: teamUserId, + TeamSeverity: teamSeverityId, + } + + return service.teamUserSeverityRepository.AddTeamUserSeverity(teamUserSeverityEntity) +} diff --git a/service/teamUserSeverity/impl/team_user_severity_service_test.go b/service/teamUserSeverity/impl/team_user_severity_service_test.go new file mode 100644 index 0000000..fc2731b --- /dev/null +++ b/service/teamUserSeverity/impl/team_user_severity_service_test.go @@ -0,0 +1,39 @@ +package impl + +import ( + "errors" + "github.com/stretchr/testify/suite" + "houston/logger" + "houston/mocks" + "testing" +) + +type TeamUserSeverityServiceSuite struct { + suite.Suite + teamUserSeverityRepository *mocks.TeamUserSeverityRepositoryMock + teamUserSeverityService *TeamUserSeverityService +} + +func (suite *TeamUserSeverityServiceSuite) Test_AddTeamUserSeverity_RepoFailureCase() { + suite.teamUserSeverityRepository.AddTeamUserSeverityMock.Return(errors.New("error")) + + err := suite.teamUserSeverityService.AddTeamUserSeverity(1, 1) + suite.EqualError(err, "error", "error should be error") +} + +func (suite *TeamUserSeverityServiceSuite) Test_AddTeamUserSeverity_SuccessCase() { + suite.teamUserSeverityRepository.AddTeamUserSeverityMock.Return(nil) + + err := suite.teamUserSeverityService.AddTeamUserSeverity(1, 1) + suite.Nil(err, "error should be nil") +} + +func (suite *TeamUserSeverityServiceSuite) SetupSuite() { + logger.InitLogger() + suite.teamUserSeverityRepository = mocks.NewTeamUserSeverityRepositoryMock(suite.T()) + suite.teamUserSeverityService = NewTeamUserSeverityService(suite.teamUserSeverityRepository) +} + +func TestTeamUserSeverityService(t *testing.T) { + suite.Run(t, new(TeamUserSeverityServiceSuite)) +} diff --git a/service/teamUserSeverity/team_user_severity_service_interface.go b/service/teamUserSeverity/team_user_severity_service_interface.go new file mode 100644 index 0000000..2825053 --- /dev/null +++ b/service/teamUserSeverity/team_user_severity_service_interface.go @@ -0,0 +1,5 @@ +package teamUserSeverity + +type ITeamUserSeverityService interface { + AddTeamUserSeverity(teamUserId, teamSeverityId uint) error +} diff --git a/service/user/impl/user_service.go b/service/user/impl/user_service.go new file mode 100644 index 0000000..b1e8fe9 --- /dev/null +++ b/service/user/impl/user_service.go @@ -0,0 +1,48 @@ +package impl + +import ( + "fmt" + "go.uber.org/zap" + "houston/logger" + "houston/model/user" +) + +type UserService struct { + userRepository user.IUserRepository +} + +func NewUserService(userRepository user.IUserRepository) *UserService { + return &UserService{ + userRepository: userRepository, + } +} + +func (service *UserService) GetHoustonUserByEmailId(emailId string) (*user.UserDTO, error) { + userEntity, err := service.userRepository.GetHoustonUserByEmailId(emailId) + if err != nil { + logger.Error("error in fetching user by email id", zap.Error(err)) + return nil, err + } + + if userEntity == nil { + logger.Error(fmt.Sprintf("user with email id %s not found", emailId)) + return nil, nil + } + + return userEntity.ToDTO(), nil +} + +func (service *UserService) GetHoustonUserById(id uint) (*user.UserDTO, error) { + userEntity, err := service.userRepository.GetHoustonUserById(id) + if err != nil { + logger.Error("error in fetching user by id", zap.Error(err)) + return nil, err + } + + if userEntity == nil { + logger.Error(fmt.Sprintf("user with id %d not found", id)) + return nil, nil + } + + return userEntity.ToDTO(), nil +} diff --git a/service/user/impl/user_service_test.go b/service/user/impl/user_service_test.go new file mode 100644 index 0000000..29ae2e4 --- /dev/null +++ b/service/user/impl/user_service_test.go @@ -0,0 +1,84 @@ +package impl + +import ( + "errors" + "github.com/stretchr/testify/suite" + "houston/logger" + "houston/mocks" + "houston/model/user" + "testing" +) + +type UserServiceSuite struct { + suite.Suite + userRepository *mocks.IUserRepositoryMock + userService *UserService +} + +func (suite *UserServiceSuite) Test_GetHoustonUserByEmailId_RepoFailureCase() { + suite.userRepository.GetHoustonUserByEmailIdMock.Return(nil, errors.New("error")) + + user, err := suite.userService.GetHoustonUserByEmailId("emailId") + suite.NotNil(err, "error should not be nil") + suite.Nil(user, "user should be nil") +} + +func (suite *UserServiceSuite) Test_GetHoustonUserByEmailId_UserNotFoundCase() { + suite.userRepository.GetHoustonUserByEmailIdMock.Return(nil, nil) + + user, err := suite.userService.GetHoustonUserByEmailId("emailId") + suite.Nil(err, "error should be nil") + suite.Nil(user, "user should be nil") +} + +func (suite *UserServiceSuite) Test_GetHoustonUserByEmailId_SuccessCase() { + mockUserEntity := GetMockUserEntity() + suite.userRepository.GetHoustonUserByEmailIdMock.Return(&mockUserEntity, nil) + + user, err := suite.userService.GetHoustonUserByEmailId("emailId") + suite.Nil(err, "error should be nil") + suite.NotNil(user, "user should not be nil") +} + +func (suite *UserServiceSuite) Test_GetHoustonUserById_RepoFailureCase() { + suite.userRepository.GetHoustonUserByIdMock.Return(nil, errors.New("error")) + + user, err := suite.userService.GetHoustonUserById(1) + suite.NotNil(err, "error should not be nil") + suite.Nil(user, "user should be nil") +} + +func (suite *UserServiceSuite) Test_GetHoustonUserById_UserNotFoundCase() { + suite.userRepository.GetHoustonUserByIdMock.Return(nil, nil) + + user, err := suite.userService.GetHoustonUserById(1) + suite.Nil(err, "error should be nil") + suite.Nil(user, "user should be nil") +} + +func (suite *UserServiceSuite) Test_GetHoustonUserById_SuccessCase() { + mockUserEntity := GetMockUserEntity() + suite.userRepository.GetHoustonUserByIdMock.Return(&mockUserEntity, nil) + + user, err := suite.userService.GetHoustonUserById(1) + suite.Nil(err, "error should be nil") + suite.NotNil(user, "user should not be nil") +} + +func (suite *UserServiceSuite) SetupSuite() { + logger.InitLogger() + suite.userRepository = mocks.NewIUserRepositoryMock(suite.T()) + suite.userService = NewUserService(suite.userRepository) +} + +func TestUserService(t *testing.T) { + suite.Run(t, new(UserServiceSuite)) +} + +func GetMockUserEntity() user.UserEntity { + return user.UserEntity{ + Email: "emailId", + SlackUserId: "slackUserId", + Name: "name", + } +} diff --git a/service/user/user_service_interface.go b/service/user/user_service_interface.go new file mode 100644 index 0000000..45b5b0e --- /dev/null +++ b/service/user/user_service_interface.go @@ -0,0 +1,8 @@ +package user + +import "houston/model/user" + +type IUserService interface { + GetHoustonUserByEmailId(emailId string) (*user.UserDTO, error) + GetHoustonUserById(id uint) (*user.UserDTO, error) +}