From 6cc0bfd1d79074df790272b8091b1f0226d14283 Mon Sep 17 00:00:00 2001 From: zwlucas Date: Fri, 29 May 2026 15:57:37 -0300 Subject: feat: upload project Signed-off-by: zwlucas --- backend/internal/api/handler.go | 243 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 243 insertions(+) create mode 100644 backend/internal/api/handler.go (limited to 'backend/internal/api/handler.go') diff --git a/backend/internal/api/handler.go b/backend/internal/api/handler.go new file mode 100644 index 0000000..f304351 --- /dev/null +++ b/backend/internal/api/handler.go @@ -0,0 +1,243 @@ +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 + } + history, err := h.store.GetHistory(id, 50) + if err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + writeJSON(w, http.StatusOK, history) +} + +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) 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) +} -- cgit v1.2.3