package app import ( "fmt" "github.com/gin-gonic/gin" "github.com/spf13/viper" "go.uber.org/zap" "gorm.io/gorm" "houston/appcontext" "houston/cmd/app/handler" "houston/internal/clients" "houston/internal/metrics" "houston/logger" "houston/model/ingester" "houston/model/log" "houston/model/team" "houston/model/user" "houston/pkg/rest" "houston/pkg/slackbot" "houston/repository/externalTeamRepo" rcaRepository "houston/repository/rca/impl" "houston/repository/rcaInput" "houston/service" "houston/service/documentService" incidentService "houston/service/incident/impl" "houston/service/incident_channel" logService "houston/service/log" "houston/service/orchestration" rcaService "houston/service/rca/impl" "houston/service/slack" "houston/service/teamService" "net/http" "strings" "time" ) type Server struct { gin *gin.Engine db *gorm.DB mjolnirClient *clients.MjolnirClient authService *service.AuthService } func NewServer(gin *gin.Engine, db *gorm.DB, mjolnirClient *clients.MjolnirClient) *Server { return &Server{ gin: gin, db: db, mjolnirClient: mjolnirClient, authService: service.NewAuthService(mjolnirClient, slackbot.NewSlackClient(nil)), } } func (s *Server) Handler(houstonGroup *gin.RouterGroup) { s.readinessHandler(houstonGroup) houstonGroup.Use(s.metricMiddleware()) s.gin.Use(s.metricMiddleware()) metrics.AdminHandler() s.incidentClientHandler(houstonGroup) s.filtersHandlerV2(houstonGroup) s.rcaHandler(houstonGroup) s.incidentChannelHandler(houstonGroup) s.gin.Use(s.createMiddleware()) s.incidentClientHandlerV2(houstonGroup) s.productsHandler(houstonGroup) s.productTeamsHandler(houstonGroup) s.reminderHandler(houstonGroup) s.teamHandler(houstonGroup) s.severityHandler(houstonGroup) s.incidentHandler(houstonGroup) s.logHandler(houstonGroup) s.usersHandler(houstonGroup) s.tagHandler(houstonGroup) s.filtersHandler(houstonGroup) s.incidentUserHandler(houstonGroup) s.requestStatusHandler(houstonGroup) s.tagValueHandler(houstonGroup) //this should always be at the end since it opens websocket to connect to slackbot s.houstonHandler() } func (s *Server) houstonHandler() { incidentServiceV2 := incidentService.NewIncidentServiceV2(s.db) incidentOrchestrator := orchestration.NewIncidentOrchestrator( appcontext.GetProductTeamsService(), appcontext.GetProductsService(), appcontext.GetTeamUserService(), appcontext.GetUserService(), appcontext.GetTeamService(), appcontext.GetSeverityService(), appcontext.GetSlackService(), incidentServiceV2, ) houstonClient := NewHoustonClient() houstonHandler := handler.NewSlackHandler(s.db, houstonClient.socketModeClient, incidentOrchestrator) houstonHandler.HoustonConnect() } func (s *Server) teamHandler(houstonGroup *gin.RouterGroup) { houstonClient := NewHoustonClient() slackClient := slackbot.NewSlackClient(houstonClient.socketModeClient) authService := service.NewAuthService(s.mjolnirClient, slackClient) teamHandler := service.NewTeamService(s.gin, s.db, slackClient, authService) //Will be deprecated because they are not using houston group s.gin.GET("/teams", teamHandler.GetTeams) 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") { houstonGroup.GET("/teams", teamHandlerV2.HandleGetAllTeams) houstonGroup.GET("/teams/:id", teamHandlerV2.HandleGetTeamDetails) } else { houstonGroup.GET("/teams", teamHandler.GetTeams) houstonGroup.GET("/teams/:id", teamHandler.GetTeams) } houstonGroup.GET("/teams-v2/:id", teamHandlerV2.HandleGetTeam) 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)) houstonGroup.DELETE("/teams/:id/members/:userId", teamHandler.RemoveTeamMember) houstonGroup.DELETE("/teams-v2/:id/members/:userId", s.authService.IfManagerOrAdmin(teamHandlerV2.HandleRemoveMember)) houstonGroup.DELETE("/teams/:id", teamHandler.RemoveTeam) } func (s *Server) severityHandler(houstonGroup *gin.RouterGroup) { houstonClient := NewHoustonClient() slackClient := slackbot.NewSlackClient(houstonClient.socketModeClient) severityHandler := service.NewSeverityService(s.gin, s.db, slackClient) //Will be deprecated because they are not using hosuton group s.gin.GET("/severities", severityHandler.GetSeverities) s.gin.GET("/severities/:id", severityHandler.GetSeverities) s.gin.POST("/severities", severityHandler.UpdateSeverities) houstonGroup.GET("/severities", severityHandler.GetSeverities) houstonGroup.GET("/severities/:id", severityHandler.GetSeverities) houstonGroup.POST("/severities", severityHandler.UpdateSeverities) houstonGroup.DELETE("/severity/:id/member/:emailID", s.authService.IfAdmin(severityHandler.RemoveMember)) } func (s *Server) incidentClientHandler(houstonGroup *gin.RouterGroup) { houstonClient := NewHoustonClient() incidentHandler := service.NewIncidentService(s.gin, s.db, houstonClient.socketModeClient, appcontext.GetTeamService()) // Add a header to the routes in houstonGroup houstonGroup.Use(func(c *gin.Context) { // Add your desired header key-value pair c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, PATCH, GET, PUT, DELETE") c.Writer.Header().Set("Access-Control-Allow-Headers", viper.GetString("allowed.custom.headers")) origin := c.Request.Header.Get("Origin") c.Writer.Header().Set("Access-Control-Allow-Origin", origin) }) //TODO- Remove these api. Provide Multi Realm support in Internal Web BFF houstonGroup.GET("/incidents/unsecured/v2/:id", incidentHandler.GetIncidents) } func (s *Server) productsHandler(houstonGroup *gin.RouterGroup) { productsHandler := handler.NewProductHandler(s.gin) houstonGroup.POST("/product", s.authService.IfAdmin(productsHandler.HandleCreateProduct)) houstonGroup.PUT("/product/:id", s.authService.IfAdmin(productsHandler.HandleUpdateProduct)) houstonGroup.DELETE("/product/:id", s.authService.IfAdmin(productsHandler.HandleDeleteProductByID)) houstonGroup.GET("/products", productsHandler.HandleGetAllProducts) houstonGroup.GET("/product/:id", productsHandler.HandleGetProductByID) } func (s *Server) productTeamsHandler(houstonGroup *gin.RouterGroup) { productTeamsHandler := handler.NewProductTeamHandler(s.gin) houstonGroup.POST("/productTeamsMapping", s.authService.IfAdmin(productTeamsHandler.HandleCreateProductTeamMapping)) houstonGroup.GET("/productTeamsMapping", productTeamsHandler.HandleGetAllProductTeamsMapping) } func (s *Server) reminderHandler(houstonGroup *gin.RouterGroup) { reminderHandler := handler.NewReminderHandler(s.gin) houstonGroup.POST("reminder/team-incidents", reminderHandler.HandleTeamIncidents) houstonGroup.POST("reminder/sla-breach", reminderHandler.HandleSlaBreachReminder) houstonGroup.POST("reminder/user-incidents", reminderHandler.HandleUserIncidents) } func (s *Server) tagValueHandler(houstonGroup *gin.RouterGroup) { tagValueHandler := handler.NewTagValueHandler(appcontext.GetTagValueService()) houstonGroup.POST("/tag-value", s.authService.IfAdmin(tagValueHandler.HandleAddTagValue)) houstonGroup.PATCH("/tag-value", s.authService.IfAdmin(tagValueHandler.HandleUpdateTagValue)) } func (s *Server) incidentClientHandlerV2(houstonGroup *gin.RouterGroup) { houstonGroup.Use(func(c *gin.Context) { // Add your desired header key-value pair c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, OPTIONS, GET, PUT") c.Writer.Header().Set("Access-Control-Allow-Headers", viper.GetString("allowed.custom.headers")) origin := c.Request.Header.Get("Origin") c.Writer.Header().Set("Access-Control-Allow-Origin", origin) }) houstonClient := NewHoustonClient() slackClient := slackbot.NewSlackClient(houstonClient.socketModeClient) authService := service.NewAuthService(s.mjolnirClient, slackClient) incidentServiceV2 := incidentService.NewIncidentServiceV2(s.db) incidentOrchestrator := orchestration.NewIncidentOrchestrator( appcontext.GetProductTeamsService(), appcontext.GetProductsService(), appcontext.GetTeamUserService(), appcontext.GetUserService(), appcontext.GetTeamService(), appcontext.GetSeverityService(), appcontext.GetSlackService(), incidentServiceV2, ) incidentHandler := handler.NewIncidentHandler(s.gin, s.db, incidentServiceV2, incidentOrchestrator) houstonGroup.POST("/create-incident-v3", incidentHandler.HandleCreateIncidentV3) s.gin.POST("/incidents", authService.IfValidHoustonUser(incidentHandler.HandleUpdateIncident)) houstonGroup.POST("/incidents", incidentHandler.HandleUpdateIncident) houstonGroup.POST("/incidents/resolve", authService.IfValidHoustonUser(incidentHandler.HandleResolveIncident)) houstonGroup.POST("/incidents/escalate", incidentHandler.HandleEscalateIncident) houstonGroup.POST("/incidents/re-investigate", incidentHandler.HandleMoveIncidentsToInvestigating) houstonGroup.POST("/link-jira-to-incident", incidentHandler.HandleJiraLinking) houstonGroup.POST("/unlink-jira-from-incident", incidentHandler.HandleJiraUnLinking) houstonGroup.GET("/get-jira-statuses", incidentHandler.HandleGetJiraStatuses) houstonGroup.GET("/user/products", incidentHandler.HandleGetProductsOfUser) houstonGroup.GET("/product/reporting-and-responder-teams", incidentHandler.HandleGetReportingAndResponderTeams) } func (s *Server) incidentHandler(houstonGroup *gin.RouterGroup) { houstonClient := NewHoustonClient() incidentHandler := service.NewIncidentService(s.gin, s.db, houstonClient.socketModeClient, appcontext.GetTeamService()) //Will be deprecated because they are not using hosuton group s.gin.GET("/incidents", incidentHandler.GetIncidents) s.gin.GET("/incidents/:id", incidentHandler.GetIncidents) s.gin.GET("/incidents/header", incidentHandler.GetIncidentHeader) houstonGroup.GET("/incidents", s.authService.IfValidHoustonUser(incidentHandler.GetIncidents)) houstonGroup.GET("/incidents/:id", s.authService.CanUserAccessIncident( s.authService.IfValidHoustonUser(incidentHandler.GetIncidents), )) houstonGroup.GET("/incidents/header", incidentHandler.GetIncidentHeader) houstonGroup.GET("/teamIncidents/:teamId", incidentHandler.GetTeamIncidents) } func (s *Server) incidentChannelHandler(houstonGroup *gin.RouterGroup) { incidentChannelHandler := handler.NewIncidentChannelHandler(s.gin, incident_channel.NewIncidentChannelService(s.db)) houstonGroup.POST("/incident-channel/archive", incidentChannelHandler.HandleArchiveIncidentChannels) } func (s *Server) incidentUserHandler(houstonGroup *gin.RouterGroup) { incidentUserHandler := handler.NewIncidentUserHandler(s.gin, appcontext.GetIncidentUserService()) houstonGroup.GET("/incident-user", s.authService.IfValidHoustonUser(incidentUserHandler.HandleGetIncidentUsers)) houstonGroup.POST("/incident-user/synchronization", s.authService.IfAdmin(incidentUserHandler.HandleSyncIncidentUsers)) } func (s *Server) requestStatusHandler(houstonGroup *gin.RouterGroup) { requestStatusHandler := handler.NewRequestStatusHandler(s.gin, appcontext.GetRequestStatusService()) houstonGroup.GET("/request-status/:request_id", requestStatusHandler.HandleGetRequestStatusByRequestId) } func (s *Server) logHandler(houstonGroup *gin.RouterGroup) { logHandler := logService.NewLogService(s.gin, s.db) houstonGroup.GET("/logs/:log_type/:id", s.authService.CanUserAccessIncidentLogs(logHandler.GetLogs)) } func (s *Server) rcaHandler(houstonGroup *gin.RouterGroup) { slackService := slack.NewSlackService() incidentServiceV2 := incidentService.NewIncidentServiceV2(s.db) rcaRepository := rcaRepository.NewRcaRepository(s.db) rcaInputRepository := rcaInput.NewRcaInputRepository(s.db) restClient := rest.NewHttpRestClient() documentService := documentService.NewActionsImpl(restClient) userRepository := user.NewUserRepository(s.db) rcaService := rcaService.NewRcaService(incidentServiceV2, slackService, documentService, rcaRepository, rcaInputRepository, userRepository, appcontext.GetDriveService(), appcontext.GetIncidentJiraService()) rcaHandler := handler.NewRcaHandler(s.gin, rcaService) houstonGroup.POST("/rca", rcaHandler.HandlePostRca) houstonGroup.GET("/conversation/:incidentId", rcaHandler.HandleGetConversationUrls) } func (s *Server) tagHandler(houstonGroup *gin.RouterGroup) { tagService := appcontext.GetTagService() tagHandler := handler.NewTagHandler(s.gin, tagService) houstonGroup.GET("/tags/resolve", tagHandler.HandleGetTagsForIncidentResolution) } func (s *Server) usersHandler(houstonGroup *gin.RouterGroup) { houstonClient := NewHoustonClient() slackClient := slackbot.NewSlackClient(houstonClient.socketModeClient) authService := service.NewAuthService(s.mjolnirClient, slackClient) usersHandler := service.NewUserService(s.gin, slackClient, s.db, houstonClient.socketModeClient, authService) //Will be deprecated because they are not using hosuton group s.gin.GET("/users/:id", usersHandler.GetUserInfo) s.gin.GET("/users", usersHandler.GetUsersInConversation) houstonGroup.GET("/users/:id", usersHandler.GetUserInfo) houstonGroup.GET("/users", usersHandler.GetUsersInConversation) houstonGroup.GET("/bots", usersHandler.GetAllHoustonUserBots) usersHandlerV2 := handler.NewUserHandler(s.gin, appcontext.GetUserService()) houstonGroup.PUT("/users", usersHandlerV2.HandleUpsertHoustonUsers) } func (s *Server) filtersHandler(houstonGroup *gin.RouterGroup) { filtersHandler := service.NewFilterService(s.gin, s.db) //Will be deprecated because they are not using hosuton group s.gin.GET("/filters", filtersHandler.GetFilters) houstonGroup.GET("/filters", filtersHandler.GetFilters) } // to be removed via internal bff func (s *Server) filtersHandlerV2(houstonGroup *gin.RouterGroup) { filtersHandler := service.NewFilterService(s.gin, s.db) houstonGroup.GET("/filters/v2", filtersHandler.GetFilters) } func (s *Server) readinessHandler(houstonGroup *gin.RouterGroup) { readinessHandler := service.NewReadinessService(s.gin) //Will be deprecated because they are not using hosuton group s.gin.GET("/ping", readinessHandler.Ping) houstonGroup.GET("/ping", readinessHandler.Ping) } func (s *Server) Start() { logger.Info("starting houston server", zap.String("port", viper.GetString("port"))) s.gin.Run(fmt.Sprintf(":%v", "8080")) } func (s *Server) metricMiddleware() gin.HandlerFunc { return func(c *gin.Context) { startTime := time.Now() c.Next() endTime := time.Now() duration := endTime.Sub(startTime) metricsPublisher := metrics.NewMetricPublisher() apiMetrics := ingester.ApiMetric{ Url: c.FullPath(), Method: c.Request.Method, ResponseCode: c.Writer.Status(), StartTime: startTime.Unix(), EndTime: endTime.Unix(), DurationInMs: duration.Milliseconds(), BytesSent: c.Writer.Size(), } metricsPublisher.PublishMetrics(ingester.MetricAttributes{ApiMetric: apiMetrics}, ingester.HttpClientRequestMetrics) } } func (s *Server) createMiddleware() gin.HandlerFunc { return func(c *gin.Context) { whitelistedDomains := getWhitelistedDomains() //auth handling isAuthEnabled := viper.GetBool("auth.enabled") if isAuthEnabled { sessionResponse, err := s.mjolnirClient.GetSessionResponse(c.Request.Header.Get("X-Session-Token")) if err != nil || sessionResponse.StatusCode == 401 { c.AbortWithStatus(http.StatusUnauthorized) return } } //cors handling c.Writer.Header().Set("Access-Control-Allow-Methods", "POST, PATCH, DELETE, OPTIONS, GET, PUT") c.Writer.Header().Set("Access-Control-Allow-Headers", viper.GetString("allowed.custom.headers")) origin := c.Request.Header.Get("Origin") if !whitelistedDomains[origin] { c.AbortWithStatus(http.StatusUnauthorized) return } c.Writer.Header().Set("Access-Control-Allow-Origin", origin) if c.Request.Method == "OPTIONS" { c.AbortWithStatus(http.StatusOK) return } } } func getWhitelistedDomains() map[string]bool { allowedList := make(map[string]bool) domains := strings.Split(viper.GetString("whitelisted.domains"), ",") for _, domain := range domains { domainLocal := domain allowedList[domainLocal] = true } return allowedList }