package handlers import ( "fmt" "net" "net/http" "time" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/Wikid82/charon/backend/internal/logger" "github.com/Wikid82/charon/backend/internal/models" "github.com/Wikid82/charon/backend/internal/services" "github.com/Wikid82/charon/backend/internal/util" ) // RemoteServerHandler handles HTTP requests for remote server management. type RemoteServerHandler struct { service *services.RemoteServerService notificationService *services.NotificationService } // NewRemoteServerHandler creates a new remote server handler. func NewRemoteServerHandler(service *services.RemoteServerService, ns *services.NotificationService) *RemoteServerHandler { return &RemoteServerHandler{ service: service, notificationService: ns, } } // RegisterRoutes registers remote server routes. func (h *RemoteServerHandler) RegisterRoutes(router *gin.RouterGroup) { router.GET("/remote-servers", h.List) router.POST("/remote-servers", h.Create) router.GET("/remote-servers/:uuid", h.Get) router.PUT("/remote-servers/:uuid", h.Update) router.DELETE("/remote-servers/:uuid", h.Delete) router.POST("/remote-servers/test", h.TestConnectionCustom) router.POST("/remote-servers/:uuid/test", h.TestConnection) } // List retrieves all remote servers. func (h *RemoteServerHandler) List(c *gin.Context) { enabledOnly := c.Query("enabled") == "true" servers, err := h.service.List(enabledOnly) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, servers) } // Create creates a new remote server. func (h *RemoteServerHandler) Create(c *gin.Context) { var server models.RemoteServer if err := c.ShouldBindJSON(&server); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } server.UUID = uuid.NewString() if err := h.service.Create(&server); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Send Notification if h.notificationService != nil { h.notificationService.SendExternal(c.Request.Context(), "remote_server", "Remote Server Added", fmt.Sprintf("Remote Server %s (%s:%d) added", util.SanitizeForLog(server.Name), util.SanitizeForLog(server.Host), server.Port), map[string]interface{}{ "Name": util.SanitizeForLog(server.Name), "Host": util.SanitizeForLog(server.Host), "Port": server.Port, "Action": "created", }, ) } c.JSON(http.StatusCreated, server) } // Get retrieves a remote server by UUID. func (h *RemoteServerHandler) Get(c *gin.Context) { uuidStr := c.Param("uuid") server, err := h.service.GetByUUID(uuidStr) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "server not found"}) return } c.JSON(http.StatusOK, server) } // Update updates an existing remote server. func (h *RemoteServerHandler) Update(c *gin.Context) { uuidStr := c.Param("uuid") server, err := h.service.GetByUUID(uuidStr) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "server not found"}) return } if err := c.ShouldBindJSON(server); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.service.Update(server); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, server) } // Delete removes a remote server. func (h *RemoteServerHandler) Delete(c *gin.Context) { uuidStr := c.Param("uuid") server, err := h.service.GetByUUID(uuidStr) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "server not found"}) return } if err := h.service.Delete(server.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } // Send Notification if h.notificationService != nil { h.notificationService.SendExternal(c.Request.Context(), "remote_server", "Remote Server Deleted", fmt.Sprintf("Remote Server %s deleted", util.SanitizeForLog(server.Name)), map[string]interface{}{ "Name": util.SanitizeForLog(server.Name), "Action": "deleted", }, ) } c.JSON(http.StatusNoContent, nil) } // TestConnection tests the TCP connection to a remote server. func (h *RemoteServerHandler) TestConnection(c *gin.Context) { uuidStr := c.Param("uuid") server, err := h.service.GetByUUID(uuidStr) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "server not found"}) return } // Test TCP connection with 5 second timeout address := net.JoinHostPort(server.Host, fmt.Sprintf("%d", server.Port)) conn, err := net.DialTimeout("tcp", address, 5*time.Second) result := gin.H{ "server_uuid": server.UUID, "address": address, "timestamp": time.Now().UTC(), } if err != nil { result["reachable"] = false result["error"] = err.Error() // Update server reachability status server.Reachable = false now := time.Now().UTC() server.LastChecked = &now _ = h.service.Update(server) c.JSON(http.StatusOK, result) return } defer func() { if err := conn.Close(); err != nil { logger.Log().WithError(err).Warn("failed to close tcp connection") } }() // Connection successful result["reachable"] = true result["latency_ms"] = time.Since(time.Now()).Milliseconds() // Update server reachability status server.Reachable = true now := time.Now().UTC() server.LastChecked = &now _ = h.service.Update(server) c.JSON(http.StatusOK, result) } // TestConnectionCustom tests connectivity to a host/port provided in the body func (h *RemoteServerHandler) TestConnectionCustom(c *gin.Context) { var req struct { Host string `json:"host" binding:"required"` Port int `json:"port" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Test TCP connection with 5 second timeout address := net.JoinHostPort(req.Host, fmt.Sprintf("%d", req.Port)) start := time.Now() conn, err := net.DialTimeout("tcp", address, 5*time.Second) result := gin.H{ "address": address, "timestamp": time.Now().UTC(), } if err != nil { result["reachable"] = false result["error"] = err.Error() c.JSON(http.StatusOK, result) return } defer func() { if err := conn.Close(); err != nil { logger.Log().WithError(err).Warn("failed to close tcp connection") } }() // Connection successful result["reachable"] = true result["latency_ms"] = time.Since(start).Milliseconds() c.JSON(http.StatusOK, result) }