// ABOUTME: HTTP handlers for the metrics receiver service. // ABOUTME: Provides endpoints for receiving and querying metrics. package receiver import ( "encoding/json" "log/slog" "net/http" ) // Handler handles HTTP requests for the metrics receiver type Handler struct { store *Store logger *slog.Logger } // NewHandler creates a new HTTP handler with the given store func NewHandler(store *Store, logger *slog.Logger) *Handler { return &Handler{store: store, logger: logger} } // RegisterRoutes registers all HTTP routes on the given mux func (h *Handler) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("POST /api/v1/metrics", h.handleReceiveMetrics) mux.HandleFunc("GET /api/v1/metrics/run/{runID}", h.handleGetByRunID) mux.HandleFunc("GET /api/v1/metrics/repo/{org}/{repo}", h.handleGetByRepository) mux.HandleFunc("GET /health", h.handleHealth) } func (h *Handler) handleReceiveMetrics(w http.ResponseWriter, r *http.Request) { var payload MetricsPayload if err := json.NewDecoder(r.Body).Decode(&payload); err != nil { h.logger.Error("failed to decode payload", slog.String("error", err.Error())) http.Error(w, "invalid JSON payload", http.StatusBadRequest) return } if payload.Execution.RunID == "" { http.Error(w, "run_id is required", http.StatusBadRequest) return } id, err := h.store.SaveMetric(&payload) if err != nil { h.logger.Error("failed to save metric", slog.String("error", err.Error())) http.Error(w, "failed to save metric", http.StatusInternalServerError) return } h.logger.Info("metric saved", slog.Uint64("id", uint64(id)), slog.String("run_id", payload.Execution.RunID), slog.String("repository", payload.Execution.Repository), ) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) _ = json.NewEncoder(w).Encode(map[string]any{"id": id, "status": "created"}) } func (h *Handler) handleGetByRunID(w http.ResponseWriter, r *http.Request) { runID := r.PathValue("runID") if runID == "" { http.Error(w, "run_id is required", http.StatusBadRequest) return } metrics, err := h.store.GetMetricsByRunID(runID) if err != nil { h.logger.Error("failed to get metrics", slog.String("error", err.Error())) http.Error(w, "failed to get metrics", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(metrics) } func (h *Handler) handleGetByRepository(w http.ResponseWriter, r *http.Request) { org := r.PathValue("org") repo := r.PathValue("repo") if org == "" || repo == "" { http.Error(w, "org and repo are required", http.StatusBadRequest) return } metrics, err := h.store.GetMetricsByRepository(org, repo) if err != nil { h.logger.Error("failed to get metrics", slog.String("error", err.Error())) http.Error(w, "failed to get metrics", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(metrics) } func (h *Handler) handleHealth(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]string{"status": "ok"}) }