package admin import ( "net/http" "strconv" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/gin-gonic/gin" ) // DebugLogHandler provides admin endpoints to control gateway debug logging. type DebugLogHandler struct { debugLogger *service.GatewayDebugLogger } func NewDebugLogHandler(debugLogger *service.GatewayDebugLogger) *DebugLogHandler { return &DebugLogHandler{debugLogger: debugLogger} } // GetStatus returns whether debug logging is enabled. func (h *DebugLogHandler) GetStatus(c *gin.Context) { c.JSON(http.StatusOK, gin.H{ "enabled": h.debugLogger.IsEnabled(), }) } // Enable turns on debug logging. func (h *DebugLogHandler) Enable(c *gin.Context) { h.debugLogger.Enable() c.JSON(http.StatusOK, gin.H{ "enabled": true, "message": "gateway debug logging enabled", }) } // Disable turns off debug logging. func (h *DebugLogHandler) Disable(c *gin.Context) { h.debugLogger.Disable() c.JSON(http.StatusOK, gin.H{ "enabled": false, "message": "gateway debug logging disabled", }) } // ListLogs returns recent debug logs with pagination. func (h *DebugLogHandler) ListLogs(c *gin.Context) { db := h.debugLogger.DB() if db == nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "database not available"}) return } limit := 50 if v := c.Query("limit"); v != "" { if n, err := strconv.Atoi(v); err == nil && n > 0 && n <= 200 { limit = n } } accountID := c.Query("account_id") eventType := c.Query("event_type") query := `SELECT id, upstream_request_id, account_id, account_email, account_platform, event_type, method, full_url, request_headers, request_body, request_size, response_status, response_headers, response_body_preview, response_size, model_requested, model_upstream, is_stream, duration_ms, tls_profile, error_message, created_at FROM gateway_debug_logs WHERE 1=1` args := []interface{}{} argIdx := 1 if accountID != "" { query += " AND account_id = $" + strconv.Itoa(argIdx) args = append(args, accountID) argIdx++ } if eventType != "" { query += " AND event_type = $" + strconv.Itoa(argIdx) args = append(args, eventType) argIdx++ } query += " ORDER BY created_at DESC LIMIT $" + strconv.Itoa(argIdx) args = append(args, limit) rows, err := db.QueryContext(c.Request.Context(), query, args...) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } defer rows.Close() type logRow struct { ID int64 `json:"id"` UpstreamRequestID *string `json:"upstream_request_id"` AccountID int64 `json:"account_id"` AccountEmail *string `json:"account_email"` AccountPlatform *string `json:"account_platform"` EventType string `json:"event_type"` Method *string `json:"method"` FullURL *string `json:"full_url"` RequestHeaders *string `json:"request_headers"` RequestBody *string `json:"request_body"` RequestSize *int `json:"request_size"` ResponseStatus *int `json:"response_status"` ResponseHeaders *string `json:"response_headers"` ResponseBodyPreview *string `json:"response_body_preview"` ResponseSize *int `json:"response_size"` ModelRequested *string `json:"model_requested"` ModelUpstream *string `json:"model_upstream"` IsStream bool `json:"is_stream"` DurationMs *int `json:"duration_ms"` TLSProfile *string `json:"tls_profile"` ErrorMessage *string `json:"error_message"` CreatedAt string `json:"created_at"` } var results []logRow for rows.Next() { var r logRow if err := rows.Scan( &r.ID, &r.UpstreamRequestID, &r.AccountID, &r.AccountEmail, &r.AccountPlatform, &r.EventType, &r.Method, &r.FullURL, &r.RequestHeaders, &r.RequestBody, &r.RequestSize, &r.ResponseStatus, &r.ResponseHeaders, &r.ResponseBodyPreview, &r.ResponseSize, &r.ModelRequested, &r.ModelUpstream, &r.IsStream, &r.DurationMs, &r.TLSProfile, &r.ErrorMessage, &r.CreatedAt, ); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } results = append(results, r) } c.JSON(http.StatusOK, gin.H{ "items": results, "count": len(results), }) }