package api import ( "net/http" "strconv" "time" "github.com/ez-api/ez-api/internal/model" "github.com/gin-gonic/gin" ) type APIKeyStatsSummaryResponse struct { TotalRequests int64 `json:"total_requests"` SuccessRequests int64 `json:"success_requests"` FailureRequests int64 `json:"failure_requests"` SuccessRate float64 `json:"success_rate"` FailureRate float64 `json:"failure_rate"` } // GetAPIKeyStatsSummary godoc // @Summary APIKey stats summary (admin) // @Description Aggregate APIKey success/failure stats across all provider groups // @Tags admin // @Produce json // @Security AdminAuth // @Param since query int false "Start time (unix seconds)" // @Param until query int false "End time (unix seconds)" // @Success 200 {object} ResponseEnvelope{data=APIKeyStatsSummaryResponse} // @Failure 500 {object} ResponseEnvelope{data=gin.H} // @Router /admin/apikey-stats/summary [get] func (h *AdminHandler) GetAPIKeyStatsSummary(c *gin.Context) { if h == nil || h.db == nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "database not configured"}) return } // Parse optional time range parameters var sinceTime, untilTime *time.Time if sinceStr := c.Query("since"); sinceStr != "" { if ts, err := strconv.ParseInt(sinceStr, 10, 64); err == nil { t := time.Unix(ts, 0).UTC() sinceTime = &t } } if untilStr := c.Query("until"); untilStr != "" { if ts, err := strconv.ParseInt(untilStr, 10, 64); err == nil { t := time.Unix(ts, 0).UTC() untilTime = &t } } // If time range specified, query from LogRecord table if sinceTime != nil || untilTime != nil { h.getAPIKeyStatsFromLogs(c, sinceTime, untilTime) return } // Default: use aggregated stats from APIKey table (all-time) var totals struct { TotalRequests int64 `json:"total_requests"` SuccessRequests int64 `json:"success_requests"` FailureRequests int64 `json:"failure_requests"` } if err := h.db.Model(&model.APIKey{}). Select("COALESCE(SUM(total_requests),0) as total_requests, COALESCE(SUM(success_requests),0) as success_requests"). Scan(&totals).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to aggregate api key stats", "details": err.Error()}) return } total := totals.TotalRequests success := totals.SuccessRequests failure := total - success if failure < 0 { failure = 0 } var successRate float64 var failureRate float64 if total > 0 { successRate = float64(success) / float64(total) failureRate = float64(failure) / float64(total) } c.JSON(http.StatusOK, APIKeyStatsSummaryResponse{ TotalRequests: total, SuccessRequests: success, FailureRequests: failure, SuccessRate: successRate, FailureRate: failureRate, }) } // getAPIKeyStatsFromLogs calculates stats from log records for a specific time range func (h *AdminHandler) getAPIKeyStatsFromLogs(c *gin.Context, sinceTime, untilTime *time.Time) { q := h.logBaseQuery() if sinceTime != nil { q = q.Where("created_at >= ?", *sinceTime) } if untilTime != nil { q = q.Where("created_at <= ?", *untilTime) } // Only count requests that went through a provider (provider_id > 0) q = q.Where("provider_id > 0") var result struct { TotalRequests int64 SuccessRequests int64 } if err := q.Select(` COUNT(*) as total_requests, SUM(CASE WHEN status_code >= 200 AND status_code < 400 THEN 1 ELSE 0 END) as success_requests `).Scan(&result).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to aggregate api key stats from logs", "details": err.Error()}) return } total := result.TotalRequests success := result.SuccessRequests failure := total - success if failure < 0 { failure = 0 } var successRate float64 var failureRate float64 if total > 0 { successRate = float64(success) / float64(total) failureRate = float64(failure) / float64(total) } c.JSON(http.StatusOK, APIKeyStatsSummaryResponse{ TotalRequests: total, SuccessRequests: success, FailureRequests: failure, SuccessRate: successRate, FailureRate: failureRate, }) }