All checks were successful
ci / build (push) Successful in 2m33s
Add a new receiver application under cmd/receiver that accepts metrics via HTTP POST and stores them in SQLite using GORM. The receiver expects GitHub Actions style execution context (org, repo, workflow, job, run_id). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
101 lines
3.1 KiB
Go
101 lines
3.1 KiB
Go
// 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"})
|
|
}
|