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" 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) s.gin.Use(s.metricMiddleware()) s.incidentClientHandler(houstonGroup) s.filtersHandlerV2(houstonGroup) s.rcaHandler(houstonGroup) s.gin.Use(s.createMiddleware()) s.incidentClientHandlerV2(houstonGroup) s.productsHandler(houstonGroup) s.teamHandler(houstonGroup) s.severityHandler(houstonGroup) s.incidentHandler(houstonGroup) s.logHandler(houstonGroup) s.usersHandler(houstonGroup) s.tagHandler(houstonGroup) s.filtersHandler(houstonGroup) metrics.AdminHandler() //this should always be at the end since it opens websocket to connect to slackbot s.houstonHandler() } func (s *Server) houstonHandler() { houstonClient := NewHoustonClient() houstonHandler := handler.NewSlackHandler(s.db, houstonClient.socketModeClient) 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) 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 { houstonGroup.GET("/teams", teamHandler.GetTeams) houstonGroup.GET("/teams/:id", teamHandler.GetTeams) } houstonGroup.POST("/teams/add", teamHandler.AddTeam) houstonGroup.POST("/teams", teamHandler.UpdateTeam) houstonGroup.PATCH("/teams/:id/manager/:userId", teamHandler.MakeManager) houstonGroup.DELETE("/teams/:id/members/:userId", teamHandler.RemoveTeamMember) 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) // 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) }) //Will be deprecated because they are not using hosuton group s.gin.POST("/create-incident", incidentHandler.CreateIncident) //TODO- Remove these api. Provide Multi Realm support in Internal Web BFF houstonGroup.GET("/incidents/unsecured/v2/:id", incidentHandler.GetIncidents) houstonGroup.POST("/create-incident", incidentHandler.CreateIncident) } 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) 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) incidentHandler := handler.NewIncidentHandler(s.gin, s.db, authService, incidentServiceV2) houstonGroup.POST("/create-incident-v2", incidentHandler.HandleCreateIncident) if viper.GetBool("UPDATE_INCIDENT_V2_ENABLED") { s.gin.POST("/incidents", incidentHandler.HandleUpdateIncident) houstonGroup.POST("/incidents", incidentHandler.HandleUpdateIncident) } else { incidentHandlerV1 := service.NewIncidentService(s.gin, s.db, houstonClient.socketModeClient) s.gin.POST("/incidents", incidentHandlerV1.UpdateIncident) houstonGroup.POST("/incidents", incidentHandlerV1.UpdateIncident) } houstonGroup.POST("/incidents/resolve", incidentHandler.HandleResolveIncident) houstonGroup.POST("/link-jira-to-incident", incidentHandler.HandleJiraLinking) houstonGroup.POST("/unlink-jira-from-incident", incidentHandler.HandleJiraUnLinking) houstonGroup.GET("/get-jira-statuses", incidentHandler.HandleGetJiraStatuses) } func (s *Server) incidentHandler(houstonGroup *gin.RouterGroup) { houstonClient := NewHoustonClient() incidentHandler := service.NewIncidentService(s.gin, s.db, houstonClient.socketModeClient) //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", incidentHandler.GetIncidents) houstonGroup.GET("/incidents/:id", incidentHandler.GetIncidents) houstonGroup.GET("/incidents/header", incidentHandler.GetIncidentHeader) houstonGroup.GET("/teamIncidents/:teamId", incidentHandler.GetTeamIncidents) } func (s *Server) logHandler(houstonGroup *gin.RouterGroup) { logHandler := service.NewLogService(s.gin, s.db) houstonGroup.GET("/logs/:log_type/:id", 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()) 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("/users/update", usersHandler.UpdateHoustonUsers) houstonGroup.GET("/bots", usersHandler.GetAllHoustonUserBots) } 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.Request.URL.Path, 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.ApiMetrics) } } 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 }