package controllers import ( "crypto/rand" "encoding/hex" "encoding/json" "fmt" "time" "github.com/gofiber/fiber/v2" "github.com/sujit-baniya/fiber-boilerplate/app" "github.com/sujit-baniya/fiber-boilerplate/pkg/models" ) // AgentHeartbeat recibe métricas del agente instalado en un servidor. // Ruta pública: POST /agent/heartbeat — autenticada por token en JSON body. func AgentHeartbeat(c *fiber.Ctx) error { type HeartbeatRequest struct { Token string `json:"token"` Metricas string `json:"metricas"` // JSON string ya serializado por el agente } var req HeartbeatRequest if err := c.BodyParser(&req); err != nil { return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"ok": false, "error": "body inválido"}) } if req.Token == "" { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"ok": false, "error": "token requerido"}) } db := app.Http.Database.DB var servidor models.Servidor if err := db.Where("agent_token = ?", req.Token).First(&servidor).Error; err != nil { return c.Status(fiber.StatusUnauthorized).JSON(fiber.Map{"ok": false, "error": "token inválido"}) } now := time.Now() updates := map[string]interface{}{ "agent_last_seen": &now, "ultimo_ping": now.Format(time.RFC3339), } if req.Metricas != "" { updates["metricas_json"] = req.Metricas // Actualizar campos planos desde las métricas del agente (la verdad viene del agente) type agentRAM struct { TotalGB float64 `json:"total_gb"` } type agentCPU struct { Nucleos int `json:"nucleos"` } type agentDisco struct { TotalGB float64 `json:"total_gb"` } type agentPayload struct { OS string `json:"os"` RAM agentRAM `json:"ram"` CPU agentCPU `json:"cpu"` Disco agentDisco `json:"disco"` } var m agentPayload if jsonErr := json.Unmarshal([]byte(req.Metricas), &m); jsonErr == nil { if m.OS != "" { updates["so"] = m.OS } if m.RAM.TotalGB > 0 { updates["ram"] = fmt.Sprintf("%.0f GB", m.RAM.TotalGB) } if m.CPU.Nucleos > 0 { updates["nucleos"] = fmt.Sprintf("%d", m.CPU.Nucleos) } if m.Disco.TotalGB > 0 { updates["disco"] = fmt.Sprintf("%.0f GB", m.Disco.TotalGB) } } } if err := db.Model(&servidor).Updates(updates).Error; err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"ok": false, "error": "no se pudo guardar"}) } return c.JSON(fiber.Map{"ok": true, "servidor_id": servidor.ID}) } // GenerateAgentToken genera o regenera el token de agente para un servidor. // Ruta protegida: POST /app/servidor/:id/agent-token func GenerateAgentToken(c *fiber.Ctx) error { id := c.Params("id") db := app.Http.Database.DB var servidor models.Servidor if err := db.First(&servidor, id).Error; err != nil { return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "servidor no encontrado"}) } // Generar token aleatorio de 32 bytes (64 hex chars) tokenBytes := make([]byte, 32) if _, err := rand.Read(tokenBytes); err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "no se pudo generar token"}) } token := hex.EncodeToString(tokenBytes) if err := db.Model(&servidor).Update("agent_token", token).Error; err != nil { return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "no se pudo guardar token"}) } return c.JSON(fiber.Map{ "ok": true, "agent_token": token, "servidor_id": servidor.ID, }) }