Add enhanced health check endpoint

- Implement comprehensive health check with database ping
- Track and report server uptime
- Return proper HTTP status codes (503 if database is down)
- Add Swagger documentation for health endpoint
This commit is contained in:
2025-11-26 20:40:03 +01:00
parent 353a6c1f4b
commit daa7154b12
3 changed files with 91 additions and 6 deletions
+2 -1
View File
@@ -73,8 +73,9 @@ func main() {
authHandlers := httpInfra.NewAuthHandlers(registerHandler, loginHandler, jwtService) authHandlers := httpInfra.NewAuthHandlers(registerHandler, loginHandler, jwtService)
habitHandlers := httpInfra.NewHabitHandlers(createHandler, getTodaysHandler, getUserHabitsHandler, getHabitByIDHandler, getHabitEntriesHandler, updateHandler, archiveHandler, markHandler, unmarkHandler) habitHandlers := httpInfra.NewHabitHandlers(createHandler, getTodaysHandler, getUserHabitsHandler, getHabitByIDHandler, getHabitEntriesHandler, updateHandler, archiveHandler, markHandler, unmarkHandler)
statsHandlers := httpInfra.NewStatsHandlers(getHabitStatsHandler) statsHandlers := httpInfra.NewStatsHandlers(getHabitStatsHandler)
healthHandlers := httpInfra.NewHealthHandlers(db.Conn())
router := httpInfra.NewRouter(cfg.CORSOrigins, habitHandlers, authHandlers, statsHandlers, jwtService) router := httpInfra.NewRouter(cfg.CORSOrigins, habitHandlers, authHandlers, statsHandlers, healthHandlers, jwtService)
addr := fmt.Sprintf("%s:%s", cfg.Host, cfg.Port) addr := fmt.Sprintf("%s:%s", cfg.Host, cfg.Port)
log.Printf("Server starting on %s", addr) log.Printf("Server starting on %s", addr)
@@ -0,0 +1,87 @@
package http
import (
"database/sql"
"net/http"
"time"
)
var startTime = time.Now()
type HealthHandlers struct {
db *sql.DB
}
func NewHealthHandlers(db *sql.DB) *HealthHandlers {
return &HealthHandlers{
db: db,
}
}
type HealthResponse struct {
Status string `json:"status"`
Database string `json:"database"`
Uptime string `json:"uptime"`
}
// Health godoc
// @Summary Health check
// @Description Get API health status including database connectivity and uptime
// @Tags system
// @Produce json
// @Success 200 {object} HealthResponse
// @Failure 503 {object} HealthResponse
// @Router /health [get]
func (h *HealthHandlers) Health(w http.ResponseWriter, r *http.Request) {
dbStatus := "ok"
overallStatus := "ok"
statusCode := http.StatusOK
if err := h.db.Ping(); err != nil {
dbStatus = "error"
overallStatus = "degraded"
statusCode = http.StatusServiceUnavailable
}
uptime := time.Since(startTime)
uptimeStr := formatDuration(uptime)
response := HealthResponse{
Status: overallStatus,
Database: dbStatus,
Uptime: uptimeStr,
}
respondJSON(w, statusCode, response)
}
func formatDuration(d time.Duration) string {
d = d.Round(time.Second)
h := d / time.Hour
d -= h * time.Hour
m := d / time.Minute
d -= m * time.Minute
s := d / time.Second
if h > 0 {
return formatTime(int(h), "h") + formatTime(int(m), "m")
}
if m > 0 {
return formatTime(int(m), "m") + formatTime(int(s), "s")
}
return formatTime(int(s), "s")
}
func formatTime(value int, unit string) string {
if value == 0 {
return ""
}
return formatInt(value) + unit
}
func formatInt(n int) string {
if n < 10 {
return "0" + string(rune('0'+n))
}
return string(rune('0'+n/10)) + string(rune('0'+n%10))
}
+2 -5
View File
@@ -15,7 +15,7 @@ import (
_ "apocapoc-api/docs" _ "apocapoc-api/docs"
) )
func NewRouter(corsOrigins string, habitHandlers *HabitHandlers, authHandlers *AuthHandlers, statsHandlers *StatsHandlers, jwtService *auth.JWTService) *chi.Mux { func NewRouter(corsOrigins string, habitHandlers *HabitHandlers, authHandlers *AuthHandlers, statsHandlers *StatsHandlers, healthHandlers *HealthHandlers, jwtService *auth.JWTService) *chi.Mux {
r := chi.NewRouter() r := chi.NewRouter()
r.Use(middleware.Logger) r.Use(middleware.Logger)
@@ -34,10 +34,7 @@ func NewRouter(corsOrigins string, habitHandlers *HabitHandlers, authHandlers *A
httpSwagger.URL("/api/v1/docs/doc.json"), httpSwagger.URL("/api/v1/docs/doc.json"),
)) ))
r.Get("/api/v1/health", func(w http.ResponseWriter, r *http.Request) { r.Get("/api/v1/health", healthHandlers.Health)
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{"status":"ok"}`))
})
r.Route("/api/v1/auth", func(r chi.Router) { r.Route("/api/v1/auth", func(r chi.Router) {
r.Use(httprate.LimitByIP(10, 1*time.Minute)) r.Use(httprate.LimitByIP(10, 1*time.Minute))