- Add .golangci.yml with v2 config (errcheck, govet, staticcheck, misspell, etc.) - Fix 32 errcheck issues across config, discord, ntfy, server packages - Fix misspelling: cancelled → canceled - Fix staticcheck: use append(slice...) instead of loop - Fix staticcheck: remove empty error branch - Use t.Setenv instead of os.Setenv/Unsetenv in tests - Update CI workflow: add lint job, release tagging, ntfy notifications
94 lines
2.1 KiB
Go
94 lines
2.1 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"log/slog"
|
|
"net/http"
|
|
"time"
|
|
|
|
"git.daviestechlabs.io/daviestechlabs/ntfy-discord/internal/bridge"
|
|
"github.com/prometheus/client_golang/prometheus/promhttp"
|
|
)
|
|
|
|
// Server provides health and metrics endpoints
|
|
type Server struct {
|
|
httpServer *http.Server
|
|
bridge *bridge.Bridge
|
|
}
|
|
|
|
// New creates a new HTTP server
|
|
func New(port string, b *bridge.Bridge) *Server {
|
|
mux := http.NewServeMux()
|
|
|
|
s := &Server{
|
|
bridge: b,
|
|
httpServer: &http.Server{
|
|
Addr: ":" + port,
|
|
Handler: mux,
|
|
ReadTimeout: 5 * time.Second,
|
|
WriteTimeout: 10 * time.Second,
|
|
},
|
|
}
|
|
|
|
mux.HandleFunc("/health", s.handleHealth)
|
|
mux.HandleFunc("/ready", s.handleReady)
|
|
mux.Handle("/metrics", promhttp.Handler())
|
|
|
|
return s
|
|
}
|
|
|
|
// Start begins serving HTTP requests
|
|
func (s *Server) Start() {
|
|
slog.Info("starting HTTP server", "addr", s.httpServer.Addr)
|
|
if err := s.httpServer.ListenAndServe(); err != http.ErrServerClosed {
|
|
slog.Error("HTTP server error", "error", err)
|
|
}
|
|
}
|
|
|
|
// Shutdown gracefully stops the server
|
|
func (s *Server) Shutdown(ctx context.Context) {
|
|
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
|
|
defer cancel()
|
|
|
|
if err := s.httpServer.Shutdown(ctx); err != nil {
|
|
slog.Error("HTTP server shutdown error", "error", err)
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleHealth(w http.ResponseWriter, r *http.Request) {
|
|
status := struct {
|
|
Status string `json:"status"`
|
|
Healthy bool `json:"healthy"`
|
|
}{
|
|
Status: "ok",
|
|
Healthy: s.bridge.IsHealthy(),
|
|
}
|
|
|
|
if !status.Healthy {
|
|
status.Status = "unhealthy"
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(status)
|
|
}
|
|
|
|
func (s *Server) handleReady(w http.ResponseWriter, r *http.Request) {
|
|
status := struct {
|
|
Status string `json:"status"`
|
|
Ready bool `json:"ready"`
|
|
}{
|
|
Status: "ok",
|
|
Ready: s.bridge.IsReady(),
|
|
}
|
|
|
|
if !status.Ready {
|
|
status.Status = "not ready"
|
|
w.WriteHeader(http.StatusServiceUnavailable)
|
|
}
|
|
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_ = json.NewEncoder(w).Encode(status)
|
|
}
|