feat(server): /api/info aggregator + frontend version footer (#40)
Some checks failed
CI/CD Pipeline / Build Docker Cache (push) Successful in 9s
CI/CD Pipeline / CI Pipeline (push) Failing after 4m48s
CI/CD Pipeline / Trigger Docker Push (push) Has been skipped

Co-authored-by: Gabriel Radureau <arcodange@gmail.com>
Co-committed-by: Gabriel Radureau <arcodange@gmail.com>
This commit was merged in pull request #40.
This commit is contained in:
2026-05-05 08:29:26 +02:00
committed by arcodange
parent 4a3f1bb138
commit a2beadc458
16 changed files with 587 additions and 1 deletions

View File

@@ -171,6 +171,9 @@ func (s *Server) setupRoutes() {
// Kubernetes-style health endpoint at root level
s.router.Get("/api/healthz", s.handleHealthz)
// Info endpoint - composite aggregator
s.router.Get("/api/info", s.handleInfo)
// API routes
s.router.Route("/api/v1", func(r chi.Router) {
r.Use(s.getAllMiddlewares()...)
@@ -436,6 +439,16 @@ type HealthzResponse struct {
Timestamp time.Time `json:"timestamp"`
}
// InfoResponse represents the JSON response for /api/info
type InfoResponse struct {
Version string `json:"version"`
CommitShort string `json:"commit_short"`
BuildDate string `json:"build_date"`
UptimeSeconds int64 `json:"uptime_seconds"`
CacheEnabled bool `json:"cache_enabled"`
HealthzStatus string `json:"healthz_status"`
}
// handleHealthz godoc
//
// @Summary Kubernetes-style health check
@@ -456,6 +469,66 @@ func (s *Server) handleHealthz(w http.ResponseWriter, r *http.Request) {
json.NewEncoder(w).Encode(resp)
}
// handleInfo godoc
//
// @Summary Get composite info
// @Description Returns aggregated version, build, uptime, cache, and health info
// @Tags System/Info
// @Produce json
// @Success 200 {object} InfoResponse
// @Router /info [get]
func (s *Server) handleInfo(w http.ResponseWriter, r *http.Request) {
log.Trace().Msg("Info endpoint requested")
// Build commit_short from version.Commit (first 8 chars if available)
commitShort := version.Commit
if len(commitShort) > 8 {
commitShort = commitShort[:8]
}
// Build response
resp := InfoResponse{
Version: version.Version,
CommitShort: commitShort,
BuildDate: version.Date,
UptimeSeconds: int64(time.Since(s.startedAt).Seconds()),
CacheEnabled: s.cacheService != nil,
HealthzStatus: "healthy",
}
// Cache key
cacheKey := "info:json"
// Check cache if enabled
if s.cacheService != nil {
if cached, ok := s.cacheService.Get(cacheKey); ok {
log.Trace().Str("cache_key", cacheKey).Msg("Cache hit for info")
w.Header().Set("Content-Type", "application/json")
w.Header().Set("X-Cache", "HIT")
w.Write([]byte(cached.(string)))
return
}
}
// Marshal response
data, err := json.Marshal(resp)
if err != nil {
http.Error(w, `{"error":"server_error"}`, http.StatusInternalServerError)
return
}
// Cache the response
if s.cacheService != nil {
s.cacheService.Set(cacheKey, string(data),
time.Duration(s.config.GetCacheDefaultTTLSeconds())*time.Second)
w.Header().Set("X-Cache", "MISS")
log.Trace().Str("cache_key", cacheKey).Msg("Cached info response")
}
w.Header().Set("Content-Type", "application/json")
w.Write(data)
}
// handleGreetQuery godoc
//
// @Summary Get greeting with cache