package api import ( "errors" "net/http" "strconv" "strings" "github.com/ez-api/ez-api/internal/model" "github.com/ez-api/ez-api/internal/service" "github.com/gin-gonic/gin" "gorm.io/gorm" ) type MasterRealtimeView struct { Requests int64 `json:"requests"` Tokens int64 `json:"tokens"` QPS int64 `json:"qps"` QPSLimit int64 `json:"qps_limit"` RateLimited bool `json:"rate_limited"` UpdatedAt *int64 `json:"updated_at,omitempty"` } func toMasterRealtimeView(stats service.MasterRealtimeSnapshot) *MasterRealtimeView { var updatedAt *int64 if stats.UpdatedAt != nil { sec := stats.UpdatedAt.Unix() updatedAt = &sec } return &MasterRealtimeView{ Requests: stats.Requests, Tokens: stats.Tokens, QPS: stats.QPS, QPSLimit: stats.QPSLimit, RateLimited: stats.RateLimited, UpdatedAt: updatedAt, } } // GetMasterRealtime godoc // @Summary Master realtime stats (admin) // @Description Return realtime counters for the specified master // @Tags admin // @Produce json // @Security AdminAuth // @Param id path int true "Master ID" // @Success 200 {object} MasterRealtimeView // @Failure 400 {object} gin.H // @Failure 404 {object} gin.H // @Failure 500 {object} gin.H // @Router /admin/masters/{id}/realtime [get] func (h *AdminHandler) GetMasterRealtime(c *gin.Context) { idRaw := strings.TrimSpace(c.Param("id")) idU64, err := strconv.ParseUint(idRaw, 10, 64) if err != nil || idU64 == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid master id"}) return } if h.statsService == nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "stats service not configured"}) return } var m model.Master if err := h.db.Select("id", "global_qps").First(&m, uint(idU64)).Error; err != nil { if errors.Is(err, gorm.ErrRecordNotFound) { c.JSON(http.StatusNotFound, gin.H{"error": "master not found"}) return } c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load master", "details": err.Error()}) return } stats, err := h.statsService.GetMasterRealtimeSnapshot(c.Request.Context(), uint(idU64)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load realtime stats", "details": err.Error()}) return } if stats.QPSLimit == 0 && m.GlobalQPS > 0 { stats.QPSLimit = int64(m.GlobalQPS) } c.JSON(http.StatusOK, toMasterRealtimeView(stats)) } // GetSelfRealtime godoc // @Summary Master realtime stats // @Description Return realtime counters for the authenticated master // @Tags master // @Produce json // @Security MasterAuth // @Success 200 {object} MasterRealtimeView // @Failure 401 {object} gin.H // @Failure 500 {object} gin.H // @Router /v1/realtime [get] func (h *MasterHandler) GetSelfRealtime(c *gin.Context) { master, exists := c.Get("master") if !exists { c.JSON(http.StatusUnauthorized, gin.H{"error": "master key not found in context"}) return } if h.statsService == nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "stats service not configured"}) return } m := master.(*model.Master) stats, err := h.statsService.GetMasterRealtimeSnapshot(c.Request.Context(), m.ID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to load realtime stats", "details": err.Error()}) return } if stats.QPSLimit == 0 && m.GlobalQPS > 0 { stats.QPSLimit = int64(m.GlobalQPS) } c.JSON(http.StatusOK, toMasterRealtimeView(stats)) }