package api import ( "context" "encoding/json" "fmt" "net/http" "strconv" "time" "yaum/internal/monitor" "yaum/internal/model" "yaum/internal/store" ) type ServiceResponse struct { model.Service LastHeartbeat *model.Heartbeat `json:"last_heartbeat,omitempty"` } type Handler struct { store store.Store timeout int } func NewHandler(s store.Store, timeoutSec int) *Handler { return &Handler{store: s, timeout: timeoutSec} } func (h *Handler) ListServices(w http.ResponseWriter, r *http.Request) { services, err := h.store.ListServices() if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } resp := make([]ServiceResponse, len(services)) for i, svc := range services { resp[i] = ServiceResponse{ Service: svc.Service, LastHeartbeat: svc.LastHeartbeat, } } writeJSON(w, http.StatusOK, resp) } func (h *Handler) CreateService(w http.ResponseWriter, r *http.Request) { var svc model.Service if err := json.NewDecoder(r.Body).Decode(&svc); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "json invalido: " + err.Error()}) return } if svc.Name == "" || svc.URL == "" { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "name e url sao obrigatorios"}) return } created, err := h.store.AddService(svc) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusCreated, created) } func (h *Handler) ToggleService(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "id invalido"}) return } svc, err := h.store.ToggleService(id) if err != nil { writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, svc) } func (h *Handler) TestService(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "id invalido"}) return } svc, err := h.store.GetService(id) if err != nil || svc == nil { writeJSON(w, http.StatusNotFound, map[string]string{"error": "servico nao encontrado"}) return } ctx, cancel := context.WithTimeout(r.Context(), time.Duration(h.timeout)*time.Second) defer cancel() hb := monitor.CheckHTTP(ctx, svc.URL, svc.KeywordToFind) hb.ServiceID = svc.ID if saverr := h.store.AddHeartbeat(hb); saverr != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": saverr.Error()}) return } writeJSON(w, http.StatusOK, hb) } func (h *Handler) UpdateService(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "id invalido"}) return } var svc model.Service if err := json.NewDecoder(r.Body).Decode(&svc); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "json invalido: " + err.Error()}) return } updated, err := h.store.UpdateService(id, svc) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, updated) } func (h *Handler) DeleteService(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "id invalido"}) return } if err := h.store.DeleteService(id); err != nil { writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()}) return } w.WriteHeader(http.StatusNoContent) } func (h *Handler) GetHistory(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "id invalido"}) return } page, _ := strconv.Atoi(r.URL.Query().Get("page")) if page < 1 { page = 1 } perPage, _ := strconv.Atoi(r.URL.Query().Get("per_page")) if perPage < 1 || perPage > 100 { perPage = 50 } offset := (page - 1) * perPage history, err := h.store.GetHistory(id, perPage, offset) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } total, err := h.store.CountHeartbeats(id) if err != nil { total = len(history) } totalPages := (total + perPage - 1) / perPage if totalPages < 1 { totalPages = 1 } writeJSON(w, http.StatusOK, map[string]any{ "data": history, "page": page, "per_page": perPage, "total": total, "total_pages": totalPages, }) } func (h *Handler) GetActiveMaintenance(w http.ResponseWriter, r *http.Request) { m, err := h.store.GetActiveMaintenance(time.Now()) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } if m == nil { writeJSON(w, http.StatusOK, map[string]any{"maintenance": nil}) return } writeJSON(w, http.StatusOK, map[string]any{"maintenance": m}) } func (h *Handler) ListMaintenances(w http.ResponseWriter, r *http.Request) { maintenances, err := h.store.ListMaintenances() if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, maintenances) } func (h *Handler) CreateMaintenance(w http.ResponseWriter, r *http.Request) { var m model.Maintenance if err := json.NewDecoder(r.Body).Decode(&m); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "json invalido: " + err.Error()}) return } if m.Title == "" { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "titulo obrigatorio"}) return } created, err := h.store.AddMaintenance(m) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusCreated, created) } func (h *Handler) UpdateMaintenance(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "id invalido"}) return } var m model.Maintenance if err := json.NewDecoder(r.Body).Decode(&m); err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "json invalido: " + err.Error()}) return } updated, err := h.store.UpdateMaintenance(id, m) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, updated) } func (h *Handler) DeleteMaintenance(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "id invalido"}) return } if err := h.store.DeleteMaintenance(id); err != nil { writeJSON(w, http.StatusNotFound, map[string]string{"error": err.Error()}) return } w.WriteHeader(http.StatusNoContent) } func (h *Handler) BadgeSVG(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { w.Header().Set("Content-Type", "image/svg+xml") w.WriteHeader(http.StatusBadRequest) w.Write([]byte(badgeSVG("ERRO", "#888"))) return } svc, err := h.store.GetService(id) if err != nil || svc == nil { w.Header().Set("Content-Type", "image/svg+xml") w.WriteHeader(http.StatusNotFound) w.Write([]byte(badgeSVG("N/A", "#888"))) return } stats, _ := h.store.GetServiceStats(id) lastHb, _ := h.store.GetLastHeartbeat(id) var label, color string if lastHb == nil { label = "AGUARDANDO" color = "#888" } else if lastHb.IsUp { if stats != nil && stats.Uptime30d > 0 { label = "UP " + fmt.Sprintf("%.1f", stats.Uptime30d) + "%" } else { label = "UP" } color = "#22f06a" } else { label = "DOWN" color = "#ff4060" } w.Header().Set("Content-Type", "image/svg+xml") w.Header().Set("Cache-Control", "no-cache, no-store, must-revalidate") w.Write([]byte(badgeSVG(label, color))) } func badgeSVG(label string, color string) string { textWidth := len(label) * 8 if textWidth < 40 { textWidth = 40 } totalWidth := 20 + textWidth + 8 return fmt.Sprintf( ` %s `, totalWidth, totalWidth, totalWidth-textWidth-8, textWidth+8, color, 10+(textWidth/2), label, ) } func (h *Handler) GetStats(w http.ResponseWriter, r *http.Request) { id, err := strconv.Atoi(r.PathValue("id")) if err != nil { writeJSON(w, http.StatusBadRequest, map[string]string{"error": "id invalido"}) return } stats, err := h.store.GetServiceStats(id) if err != nil { writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) return } writeJSON(w, http.StatusOK, stats) } func writeJSON(w http.ResponseWriter, status int, data any) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(data) }