package routes import ( "net/http" "sync" "time" "github.com/Wei-Shaw/sub2api/internal/prommetrics" "github.com/gin-gonic/gin" "github.com/prometheus/client_golang/prometheus/promhttp" ) // HealthChecker defines the interface for health check dependencies type HealthChecker interface { CheckDatabase() bool CheckRedis() bool } var ( healthChecker HealthChecker healthCheckerOnce sync.Once ) // SetHealthChecker sets the health checker instance (called during app initialization) func SetHealthChecker(checker HealthChecker) { healthCheckerOnce.Do(func() { healthChecker = checker }) } // RegisterCommonRoutes registers common routes (health check, metrics, etc.) func RegisterCommonRoutes(r *gin.Engine) { // Health check - enhanced with dependency checks r.GET("/health", healthHandler) // Readiness check - for Kubernetes readiness probe r.GET("/ready", readinessHandler) // Liveness check - for Kubernetes liveness probe r.GET("/live", livenessHandler) // Prometheus metrics endpoint - use the shared registry from prommetrics package r.GET("/metrics", gin.WrapH(promhttp.HandlerFor(prommetrics.Registry, promhttp.HandlerOpts{}))) // Claude Code telemetry logs (ignore, return 200 directly) r.POST("/api/event_logging/batch", func(c *gin.Context) { c.Status(http.StatusOK) }) // Setup status endpoint (always returns needs_setup: false in normal mode) // This is used by the frontend to detect when the service has restarted after setup r.GET("/setup/status", func(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "code": 0, "data": gin.H{ "needs_setup": false, "step": "completed", }, }) }) } // healthHandler returns the health status of the service and its dependencies func healthHandler(c *gin.Context) { status := "ok" statusCode := http.StatusOK components := make(map[string]interface{}) allHealthy := true // Check database dbStatus := "unknown" dbHealthy := false if healthChecker != nil { dbHealthy = healthChecker.CheckDatabase() if dbHealthy { dbStatus = "healthy" } else { dbStatus = "unhealthy" allHealthy = false } } components["database"] = gin.H{ "status": dbStatus, "healthy": dbHealthy, } // Check Redis redisStatus := "unknown" redisHealthy := false if healthChecker != nil { redisHealthy = healthChecker.CheckRedis() if redisHealthy { redisStatus = "healthy" } else { redisStatus = "unhealthy" allHealthy = false } } components["redis"] = gin.H{ "status": redisStatus, "healthy": redisHealthy, } // Overall status if !allHealthy { status = "degraded" statusCode = http.StatusServiceUnavailable } response := gin.H{ "status": status, "timestamp": time.Now().UTC().Format(time.RFC3339), "components": components, } c.JSON(statusCode, response) } // readinessHandler checks if the service is ready to accept traffic func readinessHandler(c *gin.Context) { // For readiness, we require all critical dependencies to be healthy if healthChecker == nil { c.JSON(http.StatusOK, gin.H{ "status": "ready", }) return } dbHealthy := healthChecker.CheckDatabase() redisHealthy := healthChecker.CheckRedis() if dbHealthy && redisHealthy { c.JSON(http.StatusOK, gin.H{ "status": "ready", "timestamp": time.Now().UTC().Format(time.RFC3339), }) return } components := make(map[string]bool) components["database"] = dbHealthy components["redis"] = redisHealthy c.JSON(http.StatusServiceUnavailable, gin.H{ "status": "not_ready", "timestamp": time.Now().UTC().Format(time.RFC3339), "components": components, }) } // livenessHandler checks if the service is alive (for Kubernetes liveness probe) func livenessHandler(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "status": "alive", "timestamp": time.Now().UTC().Format(time.RFC3339), }) } // Prometheus metric update functions - delegate to prommetrics package // RecordHTTPRequest records an HTTP request for Prometheus metrics func RecordHTTPRequest(method, path string, statusCode int, duration time.Duration) { prommetrics.RecordHTTPRequest(method, path, statusCode, duration) } // SetDBConnections sets database connection metrics func SetDBConnections(active, idle int) { prommetrics.SetDBConnections(active, idle) } // SetRedisConnections sets Redis connection metrics func SetRedisConnections(total, idle int) { prommetrics.SetRedisConnections(total, idle) } // SetActiveAccounts sets the active accounts count func SetActiveAccounts(count int) { prommetrics.SetActiveAccounts(count) } // SetRequestQueueDepth sets the request queue depth func SetRequestQueueDepth(depth int) { prommetrics.SetRequestQueueDepth(depth) } // SetOpsHealthScore sets the overall health score func SetOpsHealthScore(score int) { prommetrics.SetOpsHealthScore(score) } // SetErrorRate sets the error rate func SetErrorRate(rate float64) { prommetrics.SetErrorRate(rate) } // SetSuccessRate sets the success rate func SetSuccessRate(rate float64) { prommetrics.SetSuccessRate(rate) } // SetQPS sets queries per second func SetQPS(value float64) { prommetrics.SetQPS(value) } // SetTPS sets tokens per second func SetTPS(value float64) { prommetrics.SetTPS(value) }