package httpapi import ( "errors" "net/http" "time" "github.com/gin-gonic/gin" "github.com/szabadpalya/api/internal/store" ) // health is the readiness probe. It deliberately fails when Postgres is // unreachable: an app that is running but cannot reach its database is not // serving anything useful, and that is exactly the state a plain process check // would miss. // // The body carries no error detail on purpose — this endpoint is // unauthenticated and must not leak connection strings or stack traces. func (s *Server) health(c *gin.Context) { started := time.Now() c.Header("Cache-Control", "no-store") if err := s.store.Ping(c.Request.Context()); err != nil { c.JSON(http.StatusServiceUnavailable, gin.H{ "status": "degraded", "database": "unreachable", }) return } c.JSON(http.StatusOK, gin.H{ "status": "ok", "database": "ok", "latencyMs": time.Since(started).Milliseconds(), }) } func (s *Server) listCampaigns(c *gin.Context) { campaigns, err := s.store.ListCampaigns(c.Request.Context()) if err != nil { s.fail(c, err) return } c.JSON(http.StatusOK, campaigns) } func (s *Server) getCampaign(c *gin.Context) { campaign, err := s.store.GetCampaignBySlug(c.Request.Context(), c.Param("slug")) if errors.Is(err, store.ErrNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "not found"}) return } if err != nil { s.fail(c, err) return } c.JSON(http.StatusOK, campaign) } func (s *Server) stats(c *gin.Context) { st, err := s.store.PlatformStats(c.Request.Context()) if err != nil { s.fail(c, err) return } c.JSON(http.StatusOK, st) } // fail logs the real error and tells the client nothing about it. func (s *Server) fail(c *gin.Context, err error) { c.Error(err) c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"}) }