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:
+2
-1
@@ -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))
|
||||||
|
}
|
||||||
@@ -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))
|
||||||
|
|||||||
Reference in New Issue
Block a user