mirror of
https://github.com/laoxong/nofx.git
synced 2026-06-04 09:58:22 +08:00
fix: sanitize API error messages to prevent sensitive info exposure
This commit is contained in:
+27
-28
@@ -60,7 +60,7 @@ func (s *Server) handleBacktestStart(c *gin.Context) {
|
|||||||
|
|
||||||
var req backtestStartRequest
|
var req backtestStartRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -78,16 +78,16 @@ func (s *Server) handleBacktestStart(c *gin.Context) {
|
|||||||
if cfg.StrategyID != "" {
|
if cfg.StrategyID != "" {
|
||||||
strategy, err := s.store.Strategy().Get(cfg.UserID, cfg.StrategyID)
|
strategy, err := s.store.Strategy().Get(cfg.UserID, cfg.StrategyID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to load strategy: %v", err)})
|
SafeBadRequest(c, "Failed to load strategy")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strategy == nil {
|
if strategy == nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("strategy not found: %s", cfg.StrategyID)})
|
SafeBadRequest(c, "Strategy not found")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var strategyConfig store.StrategyConfig
|
var strategyConfig store.StrategyConfig
|
||||||
if err := json.Unmarshal([]byte(strategy.Config), &strategyConfig); err != nil {
|
if err := json.Unmarshal([]byte(strategy.Config), &strategyConfig); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to parse strategy config: %v", err)})
|
SafeBadRequest(c, "Failed to parse strategy config")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg.SetLoadedStrategy(&strategyConfig)
|
cfg.SetLoadedStrategy(&strategyConfig)
|
||||||
@@ -102,7 +102,7 @@ func (s *Server) handleBacktestStart(c *gin.Context) {
|
|||||||
if len(cfg.Symbols) == 0 {
|
if len(cfg.Symbols) == 0 {
|
||||||
symbols, err := s.resolveStrategyCoins(&strategyConfig)
|
symbols, err := s.resolveStrategyCoins(&strategyConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("failed to resolve coins from strategy: %v", err)})
|
SafeBadRequest(c, "Failed to resolve coins from strategy")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
cfg.Symbols = symbols
|
cfg.Symbols = symbols
|
||||||
@@ -111,7 +111,7 @@ func (s *Server) handleBacktestStart(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.hydrateBacktestAIConfig(&cfg); err != nil {
|
if err := s.hydrateBacktestAIConfig(&cfg); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Failed to configure AI model")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,7 +120,7 @@ func (s *Server) handleBacktestStart(c *gin.Context) {
|
|||||||
|
|
||||||
runner, err := s.backtestManager.Start(context.Background(), cfg)
|
runner, err := s.backtestManager.Start(context.Background(), cfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeError(c, http.StatusBadRequest, "Failed to start backtest", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,11 +149,11 @@ func (s *Server) handleBacktestControl(c *gin.Context, fn func(string) error) {
|
|||||||
|
|
||||||
var req runIDRequest
|
var req runIDRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if req.RunID == "" {
|
if req.RunID == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "run_id is required"})
|
SafeBadRequest(c, "run_id is required")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,7 +162,7 @@ func (s *Server) handleBacktestControl(c *gin.Context, fn func(string) error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := fn(req.RunID); err != nil {
|
if err := fn(req.RunID); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeError(c, http.StatusBadRequest, "Failed to execute backtest operation", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,11 +181,11 @@ func (s *Server) handleBacktestLabel(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
var req labelRequest
|
var req labelRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(req.RunID) == "" {
|
if strings.TrimSpace(req.RunID) == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "run_id is required"})
|
SafeBadRequest(c, "run_id is required")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userID := normalizeUserID(c.GetString("user_id"))
|
userID := normalizeUserID(c.GetString("user_id"))
|
||||||
@@ -194,7 +194,7 @@ func (s *Server) handleBacktestLabel(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
meta, err := s.backtestManager.UpdateLabel(req.RunID, req.Label)
|
meta, err := s.backtestManager.UpdateLabel(req.RunID, req.Label)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
SafeInternalError(c, "Update backtest label", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, meta)
|
c.JSON(http.StatusOK, meta)
|
||||||
@@ -207,11 +207,11 @@ func (s *Server) handleBacktestDelete(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
var req runIDRequest
|
var req runIDRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if strings.TrimSpace(req.RunID) == "" {
|
if strings.TrimSpace(req.RunID) == "" {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "run_id is required"})
|
SafeBadRequest(c, "run_id is required")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userID := normalizeUserID(c.GetString("user_id"))
|
userID := normalizeUserID(c.GetString("user_id"))
|
||||||
@@ -219,7 +219,7 @@ func (s *Server) handleBacktestDelete(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := s.backtestManager.Delete(req.RunID); err != nil {
|
if err := s.backtestManager.Delete(req.RunID); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
SafeInternalError(c, "Delete backtest run", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{"message": "deleted"})
|
c.JSON(http.StatusOK, gin.H{"message": "deleted"})
|
||||||
@@ -277,7 +277,7 @@ func (s *Server) handleBacktestRuns(c *gin.Context) {
|
|||||||
|
|
||||||
metas, err := s.backtestManager.ListRuns()
|
metas, err := s.backtestManager.ListRuns()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
SafeInternalError(c, "List backtest runs", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
stateFilter := strings.ToLower(strings.TrimSpace(c.Query("state")))
|
stateFilter := strings.ToLower(strings.TrimSpace(c.Query("state")))
|
||||||
@@ -349,7 +349,7 @@ func (s *Server) handleBacktestEquity(c *gin.Context) {
|
|||||||
|
|
||||||
points, err := s.backtestManager.LoadEquity(runID, timeframe, limit)
|
points, err := s.backtestManager.LoadEquity(runID, timeframe, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeError(c, http.StatusBadRequest, "Failed to load equity data", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, points)
|
c.JSON(http.StatusOK, points)
|
||||||
@@ -375,7 +375,7 @@ func (s *Server) handleBacktestTrades(c *gin.Context) {
|
|||||||
|
|
||||||
events, err := s.backtestManager.LoadTrades(runID, limit)
|
events, err := s.backtestManager.LoadTrades(runID, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeError(c, http.StatusBadRequest, "Failed to load trades", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, events)
|
c.JSON(http.StatusOK, events)
|
||||||
@@ -404,7 +404,7 @@ func (s *Server) handleBacktestMetrics(c *gin.Context) {
|
|||||||
c.JSON(http.StatusAccepted, gin.H{"error": "metrics not ready yet"})
|
c.JSON(http.StatusAccepted, gin.H{"error": "metrics not ready yet"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeError(c, http.StatusBadRequest, "Failed to load metrics", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, metrics)
|
c.JSON(http.StatusOK, metrics)
|
||||||
@@ -427,7 +427,7 @@ func (s *Server) handleBacktestTrace(c *gin.Context) {
|
|||||||
cycle := queryInt(c, "cycle", 0)
|
cycle := queryInt(c, "cycle", 0)
|
||||||
record, err := s.backtestManager.GetTrace(runID, cycle)
|
record, err := s.backtestManager.GetTrace(runID, cycle)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
SafeNotFound(c, "Trace record")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, record)
|
c.JSON(http.StatusOK, record)
|
||||||
@@ -461,7 +461,7 @@ func (s *Server) handleBacktestDecisions(c *gin.Context) {
|
|||||||
|
|
||||||
records, err := backtest.LoadDecisionRecords(runID, limit, offset)
|
records, err := backtest.LoadDecisionRecords(runID, limit, offset)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
SafeInternalError(c, "Load decision records", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
c.JSON(http.StatusOK, records)
|
c.JSON(http.StatusOK, records)
|
||||||
@@ -483,7 +483,7 @@ func (s *Server) handleBacktestExport(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
path, err := s.backtestManager.ExportRun(runID)
|
path, err := s.backtestManager.ExportRun(runID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeError(c, http.StatusBadRequest, "Failed to export backtest", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer os.Remove(path)
|
defer os.Remove(path)
|
||||||
@@ -536,8 +536,7 @@ func (s *Server) handleBacktestKlines(c *gin.Context) {
|
|||||||
|
|
||||||
klines, err := market.GetKlinesRange(symbol, timeframe, startTime, endTime)
|
klines, err := market.GetKlinesRange(symbol, timeframe, startTime, endTime)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("Failed to fetch klines for %s: %v", symbol, err)
|
SafeInternalError(c, "Fetch klines", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to fetch klines: %v", err)})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -620,11 +619,11 @@ func writeBacktestAccessError(c *gin.Context, err error) bool {
|
|||||||
}
|
}
|
||||||
switch {
|
switch {
|
||||||
case errors.Is(err, errBacktestForbidden):
|
case errors.Is(err, errBacktestForbidden):
|
||||||
c.JSON(http.StatusForbidden, gin.H{"error": "No permission to access this backtest task"})
|
SafeForbidden(c, "No permission to access this backtest task")
|
||||||
case errors.Is(err, os.ErrNotExist), errors.Is(err, sql.ErrNoRows):
|
case errors.Is(err, os.ErrNotExist), errors.Is(err, sql.ErrNoRows):
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "Backtest task does not exist"})
|
SafeNotFound(c, "Backtest task")
|
||||||
default:
|
default:
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
SafeInternalError(c, "Access backtest", err)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-7
@@ -131,7 +131,7 @@ func (h *DebateHandler) HandleCreateDebate(c *gin.Context) {
|
|||||||
|
|
||||||
var req CreateDebateRequest
|
var req CreateDebateRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -292,7 +292,7 @@ func (h *DebateHandler) HandleStartDebate(c *gin.Context) {
|
|||||||
|
|
||||||
// Start debate asynchronously
|
// Start debate asynchronously
|
||||||
if err := h.engine.StartDebate(debateID); err != nil {
|
if err := h.engine.StartDebate(debateID); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
SafeInternalError(c, "Start debate", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -316,7 +316,7 @@ func (h *DebateHandler) HandleCancelDebate(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := h.engine.CancelDebate(debateID); err != nil {
|
if err := h.engine.CancelDebate(debateID); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
SafeInternalError(c, "Cancel debate", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -495,20 +495,20 @@ func (h *DebateHandler) HandleExecuteDebate(c *gin.Context) {
|
|||||||
// Parse request
|
// Parse request
|
||||||
var req ExecuteDebateRequest
|
var req ExecuteDebateRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get trader executor
|
// Get trader executor
|
||||||
executor, err := h.traderManager.GetTraderExecutor(req.TraderID)
|
executor, err := h.traderManager.GetTraderExecutor(req.TraderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": fmt.Sprintf("trader not available: %v", err)})
|
SafeError(c, http.StatusBadRequest, "Trader not available", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute consensus
|
// Execute consensus
|
||||||
if err := h.engine.ExecuteConsensus(debateID, executor); err != nil {
|
if err := h.engine.ExecuteConsensus(debateID, executor); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
SafeInternalError(c, "Execute consensus", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -635,7 +635,9 @@ func (h *DebateHandler) broadcastConsensus(sessionID string, decision *store.Deb
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *DebateHandler) broadcastError(sessionID string, err error) {
|
func (h *DebateHandler) broadcastError(sessionID string, err error) {
|
||||||
|
// Sanitize error message before broadcasting to client
|
||||||
|
safeMsg := SanitizeError(err, "An error occurred during debate")
|
||||||
h.broadcast(sessionID, "error", map[string]interface{}{
|
h.broadcast(sessionID, "error", map[string]interface{}{
|
||||||
"error": err.Error(),
|
"error": safeMsg,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/gin-gonic/gin"
|
||||||
|
"nofx/logger"
|
||||||
|
)
|
||||||
|
|
||||||
|
// SafeError returns a safe error message without exposing internal details
|
||||||
|
// It logs the actual error for debugging but returns a generic message to the client
|
||||||
|
func SafeError(c *gin.Context, statusCode int, publicMsg string, internalErr error) {
|
||||||
|
// Log the actual error internally
|
||||||
|
if internalErr != nil {
|
||||||
|
logger.Errorf("[API Error] %s: %v", publicMsg, internalErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
c.JSON(statusCode, gin.H{"error": publicMsg})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeInternalError logs internal error and returns a generic message
|
||||||
|
func SafeInternalError(c *gin.Context, operation string, err error) {
|
||||||
|
logger.Errorf("[Internal Error] %s: %v", operation, err)
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": operation + " failed"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeBadRequest returns a safe bad request error
|
||||||
|
// For validation errors, we can be more specific since they're about user input
|
||||||
|
func SafeBadRequest(c *gin.Context, msg string) {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": msg})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeNotFound returns a generic not found error
|
||||||
|
func SafeNotFound(c *gin.Context, resource string) {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": resource + " not found"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeUnauthorized returns unauthorized error
|
||||||
|
func SafeUnauthorized(c *gin.Context) {
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Unauthorized"})
|
||||||
|
}
|
||||||
|
|
||||||
|
// SafeForbidden returns forbidden error
|
||||||
|
func SafeForbidden(c *gin.Context, msg string) {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{"error": msg})
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSensitiveError checks if an error message contains sensitive information
|
||||||
|
func IsSensitiveError(err error) bool {
|
||||||
|
if err == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
errMsg := strings.ToLower(err.Error())
|
||||||
|
|
||||||
|
sensitivePatterns := []string{
|
||||||
|
// Database
|
||||||
|
"postgres", "mysql", "sqlite", "database", "sql",
|
||||||
|
"connection", "connect", "failed to connect",
|
||||||
|
// Network
|
||||||
|
"dial", "tcp", "udp", "socket", "timeout",
|
||||||
|
// Server info
|
||||||
|
"127.0.0.1", "localhost", "0.0.0.0",
|
||||||
|
// File system
|
||||||
|
"no such file", "permission denied", "open /",
|
||||||
|
// Credentials
|
||||||
|
"password", "user=", "host=", "port=",
|
||||||
|
// Internal
|
||||||
|
"panic", "runtime error", "stack trace",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, pattern := range sensitivePatterns {
|
||||||
|
if strings.Contains(errMsg, pattern) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for IP addresses (simple pattern)
|
||||||
|
if strings.Contains(errMsg, ":") && (strings.Contains(errMsg, ".") || strings.Contains(errMsg, "::")) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// SanitizeError returns the error message if safe, otherwise returns a generic message
|
||||||
|
func SanitizeError(err error, fallbackMsg string) string {
|
||||||
|
if err == nil {
|
||||||
|
return fallbackMsg
|
||||||
|
}
|
||||||
|
if IsSensitiveError(err) {
|
||||||
|
return fallbackMsg
|
||||||
|
}
|
||||||
|
return err.Error()
|
||||||
|
}
|
||||||
+73
-115
@@ -486,7 +486,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
|||||||
userID := c.GetString("user_id")
|
userID := c.GetString("user_id")
|
||||||
var req CreateTraderRequest
|
var req CreateTraderRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -682,7 +682,7 @@ func (s *Server) handleCreateTrader(c *gin.Context) {
|
|||||||
err = s.store.Trader().Create(traderRecord)
|
err = s.store.Trader().Create(traderRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("❌ Failed to create trader: %v", err)
|
logger.Infof("❌ Failed to create trader: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to create trader: %v", err)})
|
SafeInternalError(c, "Failed to create trader", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
logger.Infof("🔧 DEBUG: CreateTrader succeeded")
|
logger.Infof("🔧 DEBUG: CreateTrader succeeded")
|
||||||
@@ -732,7 +732,7 @@ func (s *Server) handleUpdateTrader(c *gin.Context) {
|
|||||||
|
|
||||||
var req UpdateTraderRequest
|
var req UpdateTraderRequest
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -823,7 +823,7 @@ func (s *Server) handleUpdateTrader(c *gin.Context) {
|
|||||||
traderRecord.ID, traderRecord.Name, traderRecord.AIModelID, traderRecord.StrategyID, req.StrategyID)
|
traderRecord.ID, traderRecord.Name, traderRecord.AIModelID, traderRecord.StrategyID, req.StrategyID)
|
||||||
err = s.store.Trader().Update(traderRecord)
|
err = s.store.Trader().Update(traderRecord)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to update trader: %v", err)})
|
SafeInternalError(c, "Failed to update trader", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -854,7 +854,7 @@ func (s *Server) handleDeleteTrader(c *gin.Context) {
|
|||||||
// Delete from database
|
// Delete from database
|
||||||
err := s.store.Trader().Delete(userID, traderID)
|
err := s.store.Trader().Delete(userID, traderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to delete trader: %v", err)})
|
SafeInternalError(c, "Failed to delete trader", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1012,14 +1012,14 @@ func (s *Server) handleUpdateTraderPrompt(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update database
|
// Update database
|
||||||
err := s.store.Trader().UpdateCustomPrompt(userID, traderID, req.CustomPrompt, req.OverrideBasePrompt)
|
err := s.store.Trader().UpdateCustomPrompt(userID, traderID, req.CustomPrompt, req.OverrideBasePrompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to update custom prompt: %v", err)})
|
SafeInternalError(c, "Failed to update custom prompt", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1044,14 +1044,14 @@ func (s *Server) handleToggleCompetition(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update database
|
// Update database
|
||||||
err := s.store.Trader().UpdateShowInCompetition(userID, traderID, req.ShowInCompetition)
|
err := s.store.Trader().UpdateShowInCompetition(userID, traderID, req.ShowInCompetition)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to update competition visibility: %v", err)})
|
SafeInternalError(c, "Update competition visibility", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1150,7 +1150,7 @@ func (s *Server) handleSyncBalance(c *gin.Context) {
|
|||||||
|
|
||||||
if createErr != nil {
|
if createErr != nil {
|
||||||
logger.Infof("⚠️ Failed to create temporary trader: %v", createErr)
|
logger.Infof("⚠️ Failed to create temporary trader: %v", createErr)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to connect to exchange: %v", createErr)})
|
SafeInternalError(c, "Failed to connect to exchange", createErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1158,7 +1158,7 @@ func (s *Server) handleSyncBalance(c *gin.Context) {
|
|||||||
balanceInfo, balanceErr := tempTrader.GetBalance()
|
balanceInfo, balanceErr := tempTrader.GetBalance()
|
||||||
if balanceErr != nil {
|
if balanceErr != nil {
|
||||||
logger.Infof("⚠️ Failed to query exchange balance: %v", balanceErr)
|
logger.Infof("⚠️ Failed to query exchange balance: %v", balanceErr)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to query balance: %v", balanceErr)})
|
SafeInternalError(c, "Failed to query balance", balanceErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1302,7 +1302,7 @@ func (s *Server) handleClosePosition(c *gin.Context) {
|
|||||||
|
|
||||||
if createErr != nil {
|
if createErr != nil {
|
||||||
logger.Infof("⚠️ Failed to create temporary trader: %v", createErr)
|
logger.Infof("⚠️ Failed to create temporary trader: %v", createErr)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to connect to exchange: %v", createErr)})
|
SafeInternalError(c, "Failed to connect to exchange", createErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1344,7 +1344,7 @@ func (s *Server) handleClosePosition(c *gin.Context) {
|
|||||||
|
|
||||||
if closeErr != nil {
|
if closeErr != nil {
|
||||||
logger.Infof("❌ Close position failed: symbol=%s, side=%s, error=%v", req.Symbol, req.Side, closeErr)
|
logger.Infof("❌ Close position failed: symbol=%s, side=%s, error=%v", req.Symbol, req.Side, closeErr)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to close position: %v", closeErr)})
|
SafeInternalError(c, "Failed to close position", closeErr)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1582,7 +1582,7 @@ func (s *Server) handleGetModelConfigs(c *gin.Context) {
|
|||||||
models, err := s.store.AIModel().List(userID)
|
models, err := s.store.AIModel().List(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("❌ Failed to get AI model configs: %v", err)
|
logger.Infof("❌ Failed to get AI model configs: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to get AI model configs: %v", err)})
|
SafeInternalError(c, "Failed to get AI model configs", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1684,7 +1684,7 @@ func (s *Server) handleUpdateModelConfigs(c *gin.Context) {
|
|||||||
for modelID, modelData := range req.Models {
|
for modelID, modelData := range req.Models {
|
||||||
err := s.store.AIModel().Update(userID, modelID, modelData.Enabled, modelData.APIKey, modelData.CustomAPIURL, modelData.CustomModelName)
|
err := s.store.AIModel().Update(userID, modelID, modelData.Enabled, modelData.APIKey, modelData.CustomAPIURL, modelData.CustomModelName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to update model %s: %v", modelID, err)})
|
SafeInternalError(c, fmt.Sprintf("Update model %s", modelID), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1706,8 +1706,7 @@ func (s *Server) handleGetExchangeConfigs(c *gin.Context) {
|
|||||||
logger.Infof("🔍 Querying exchange configs for user %s", userID)
|
logger.Infof("🔍 Querying exchange configs for user %s", userID)
|
||||||
exchanges, err := s.store.Exchange().List(userID)
|
exchanges, err := s.store.Exchange().List(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("❌ Failed to get exchange configs: %v", err)
|
SafeInternalError(c, "Failed to get exchange configs", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to get exchange configs: %v", err)})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1805,7 +1804,7 @@ func (s *Server) handleUpdateExchangeConfigs(c *gin.Context) {
|
|||||||
for exchangeID, exchangeData := range req.Exchanges {
|
for exchangeID, exchangeData := range req.Exchanges {
|
||||||
err := s.store.Exchange().Update(userID, exchangeID, exchangeData.Enabled, exchangeData.APIKey, exchangeData.SecretKey, exchangeData.Passphrase, exchangeData.Testnet, exchangeData.HyperliquidWalletAddr, exchangeData.AsterUser, exchangeData.AsterSigner, exchangeData.AsterPrivateKey, exchangeData.LighterWalletAddr, exchangeData.LighterPrivateKey, exchangeData.LighterAPIKeyPrivateKey, exchangeData.LighterAPIKeyIndex)
|
err := s.store.Exchange().Update(userID, exchangeID, exchangeData.Enabled, exchangeData.APIKey, exchangeData.SecretKey, exchangeData.Passphrase, exchangeData.Testnet, exchangeData.HyperliquidWalletAddr, exchangeData.AsterUser, exchangeData.AsterSigner, exchangeData.AsterPrivateKey, exchangeData.LighterWalletAddr, exchangeData.LighterPrivateKey, exchangeData.LighterAPIKeyPrivateKey, exchangeData.LighterAPIKeyIndex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to update exchange %s: %v", exchangeID, err)})
|
SafeInternalError(c, fmt.Sprintf("Update exchange %s", exchangeID), err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1910,7 +1909,7 @@ func (s *Server) handleCreateExchange(c *gin.Context) {
|
|||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("❌ Failed to create exchange account: %v", err)
|
logger.Infof("❌ Failed to create exchange account: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to create exchange account: %v", err)})
|
SafeInternalError(c, "Failed to create exchange account", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1953,7 +1952,7 @@ func (s *Server) handleDeleteExchange(c *gin.Context) {
|
|||||||
err = s.store.Exchange().Delete(userID, exchangeID)
|
err = s.store.Exchange().Delete(userID, exchangeID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("❌ Failed to delete exchange account: %v", err)
|
logger.Infof("❌ Failed to delete exchange account: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to delete exchange account: %v", err)})
|
SafeInternalError(c, "Failed to delete exchange account", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1966,7 +1965,7 @@ func (s *Server) handleTraderList(c *gin.Context) {
|
|||||||
userID := c.GetString("user_id")
|
userID := c.GetString("user_id")
|
||||||
traders, err := s.store.Trader().List(userID)
|
traders, err := s.store.Trader().List(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("Failed to get trader list: %v", err)})
|
SafeInternalError(c, "Failed to get trader list", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2019,7 +2018,7 @@ func (s *Server) handleGetTraderConfig(c *gin.Context) {
|
|||||||
|
|
||||||
fullCfg, err := s.store.Trader().GetFullConfig(userID, traderID)
|
fullCfg, err := s.store.Trader().GetFullConfig(userID, traderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": fmt.Sprintf("Failed to get trader config: %v", err)})
|
SafeNotFound(c, "Trader config")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
traderConfig := fullCfg.Trader
|
traderConfig := fullCfg.Trader
|
||||||
@@ -2062,13 +2061,13 @@ func (s *Server) handleGetTraderConfig(c *gin.Context) {
|
|||||||
func (s *Server) handleStatus(c *gin.Context) {
|
func (s *Server) handleStatus(c *gin.Context) {
|
||||||
_, traderID, err := s.getTraderFromQuery(c)
|
_, traderID, err := s.getTraderFromQuery(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid trader ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trader, err := s.traderManager.GetTrader(traderID)
|
trader, err := s.traderManager.GetTrader(traderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
SafeNotFound(c, "Trader")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2080,23 +2079,20 @@ func (s *Server) handleStatus(c *gin.Context) {
|
|||||||
func (s *Server) handleAccount(c *gin.Context) {
|
func (s *Server) handleAccount(c *gin.Context) {
|
||||||
_, traderID, err := s.getTraderFromQuery(c)
|
_, traderID, err := s.getTraderFromQuery(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid trader ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trader, err := s.traderManager.GetTrader(traderID)
|
trader, err := s.traderManager.GetTrader(traderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
SafeNotFound(c, "Trader")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.Infof("📊 Received account info request [%s]", trader.GetName())
|
logger.Infof("📊 Received account info request [%s]", trader.GetName())
|
||||||
account, err := trader.GetAccountInfo()
|
account, err := trader.GetAccountInfo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Infof("❌ Failed to get account info [%s]: %v", trader.GetName(), err)
|
SafeInternalError(c, "Get account info", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"error": fmt.Sprintf("Failed to get account info: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2113,21 +2109,19 @@ func (s *Server) handleAccount(c *gin.Context) {
|
|||||||
func (s *Server) handlePositions(c *gin.Context) {
|
func (s *Server) handlePositions(c *gin.Context) {
|
||||||
_, traderID, err := s.getTraderFromQuery(c)
|
_, traderID, err := s.getTraderFromQuery(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid trader ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trader, err := s.traderManager.GetTrader(traderID)
|
trader, err := s.traderManager.GetTrader(traderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
SafeNotFound(c, "Trader")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
positions, err := trader.GetPositions()
|
positions, err := trader.GetPositions()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get positions", err)
|
||||||
"error": fmt.Sprintf("Failed to get position list: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2138,13 +2132,13 @@ func (s *Server) handlePositions(c *gin.Context) {
|
|||||||
func (s *Server) handlePositionHistory(c *gin.Context) {
|
func (s *Server) handlePositionHistory(c *gin.Context) {
|
||||||
_, traderID, err := s.getTraderFromQuery(c)
|
_, traderID, err := s.getTraderFromQuery(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid trader ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trader, err := s.traderManager.GetTrader(traderID)
|
trader, err := s.traderManager.GetTrader(traderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
SafeNotFound(c, "Trader")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2165,9 +2159,7 @@ func (s *Server) handlePositionHistory(c *gin.Context) {
|
|||||||
// Get closed positions
|
// Get closed positions
|
||||||
positions, err := store.Position().GetClosedPositions(trader.GetID(), limit)
|
positions, err := store.Position().GetClosedPositions(trader.GetID(), limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get position history", err)
|
||||||
"error": fmt.Sprintf("Failed to get position history: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2192,13 +2184,13 @@ func (s *Server) handlePositionHistory(c *gin.Context) {
|
|||||||
func (s *Server) handleTrades(c *gin.Context) {
|
func (s *Server) handleTrades(c *gin.Context) {
|
||||||
_, traderID, err := s.getTraderFromQuery(c)
|
_, traderID, err := s.getTraderFromQuery(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid trader ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trader, err := s.traderManager.GetTrader(traderID)
|
trader, err := s.traderManager.GetTrader(traderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
SafeNotFound(c, "Trader")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2224,9 +2216,7 @@ func (s *Server) handleTrades(c *gin.Context) {
|
|||||||
|
|
||||||
allTrades, err := store.Position().GetRecentTrades(trader.GetID(), limit)
|
allTrades, err := store.Position().GetRecentTrades(trader.GetID(), limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get trades", err)
|
||||||
"error": fmt.Sprintf("Failed to get trades: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2249,13 +2239,13 @@ func (s *Server) handleTrades(c *gin.Context) {
|
|||||||
func (s *Server) handleOrders(c *gin.Context) {
|
func (s *Server) handleOrders(c *gin.Context) {
|
||||||
_, traderID, err := s.getTraderFromQuery(c)
|
_, traderID, err := s.getTraderFromQuery(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid trader ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trader, err := s.traderManager.GetTrader(traderID)
|
trader, err := s.traderManager.GetTrader(traderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
SafeNotFound(c, "Trader")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2283,9 +2273,7 @@ func (s *Server) handleOrders(c *gin.Context) {
|
|||||||
// Get all orders for this trader
|
// Get all orders for this trader
|
||||||
allOrders, err := store.Order().GetTraderOrders(trader.GetID(), limit)
|
allOrders, err := store.Order().GetTraderOrders(trader.GetID(), limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get orders", err)
|
||||||
"error": fmt.Sprintf("Failed to get orders: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2317,13 +2305,13 @@ func (s *Server) handleOrderFills(c *gin.Context) {
|
|||||||
|
|
||||||
_, traderID, err := s.getTraderFromQuery(c)
|
_, traderID, err := s.getTraderFromQuery(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid trader ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trader, err := s.traderManager.GetTrader(traderID)
|
trader, err := s.traderManager.GetTrader(traderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
SafeNotFound(c, "Trader")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2336,9 +2324,7 @@ func (s *Server) handleOrderFills(c *gin.Context) {
|
|||||||
// Get fills for this order
|
// Get fills for this order
|
||||||
fills, err := store.Order().GetOrderFills(orderID)
|
fills, err := store.Order().GetOrderFills(orderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get order fills", err)
|
||||||
"error": fmt.Sprintf("Failed to get order fills: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2376,30 +2362,21 @@ func (s *Server) handleKlines(c *gin.Context) {
|
|||||||
// US Stocks via Alpaca
|
// US Stocks via Alpaca
|
||||||
klines, err = s.getKlinesFromAlpaca(symbol, interval, limit)
|
klines, err = s.getKlinesFromAlpaca(symbol, interval, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("❌ Alpaca API failed for %s: %v", symbol, err)
|
SafeInternalError(c, "Get klines from Alpaca", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"error": fmt.Sprintf("Failed to get klines from Alpaca: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "forex", "metals":
|
case "forex", "metals":
|
||||||
// Forex and Metals via Twelve Data
|
// Forex and Metals via Twelve Data
|
||||||
klines, err = s.getKlinesFromTwelveData(symbol, interval, limit)
|
klines, err = s.getKlinesFromTwelveData(symbol, interval, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("❌ TwelveData API failed for %s: %v", symbol, err)
|
SafeInternalError(c, "Get klines from TwelveData", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"error": fmt.Sprintf("Failed to get klines from TwelveData: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "hyperliquid", "hyperliquid-xyz", "xyz":
|
case "hyperliquid", "hyperliquid-xyz", "xyz":
|
||||||
// Hyperliquid native API - supports both crypto perps and stock perps (xyz dex)
|
// Hyperliquid native API - supports both crypto perps and stock perps (xyz dex)
|
||||||
klines, err = s.getKlinesFromHyperliquid(symbol, interval, limit)
|
klines, err = s.getKlinesFromHyperliquid(symbol, interval, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("❌ Hyperliquid API failed for %s: %v", symbol, err)
|
SafeInternalError(c, "Get klines from Hyperliquid", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"error": fmt.Sprintf("Failed to get klines from Hyperliquid: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
@@ -2407,10 +2384,7 @@ func (s *Server) handleKlines(c *gin.Context) {
|
|||||||
symbol = market.Normalize(symbol)
|
symbol = market.Normalize(symbol)
|
||||||
klines, err = s.getKlinesFromCoinank(symbol, interval, exchange, limit)
|
klines, err = s.getKlinesFromCoinank(symbol, interval, exchange, limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Errorf("❌ CoinAnk API failed for %s on %s: %v", symbol, exchange, err)
|
SafeInternalError(c, "Get klines from CoinAnk", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
|
||||||
"error": fmt.Sprintf("Failed to get klines from CoinAnk: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -2728,22 +2702,20 @@ func (s *Server) handleSymbols(c *gin.Context) {
|
|||||||
func (s *Server) handleDecisions(c *gin.Context) {
|
func (s *Server) handleDecisions(c *gin.Context) {
|
||||||
_, traderID, err := s.getTraderFromQuery(c)
|
_, traderID, err := s.getTraderFromQuery(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid trader ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trader, err := s.traderManager.GetTrader(traderID)
|
trader, err := s.traderManager.GetTrader(traderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
SafeNotFound(c, "Trader")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all historical decision records (unlimited)
|
// Get all historical decision records (unlimited)
|
||||||
records, err := trader.GetStore().Decision().GetLatestRecords(trader.GetID(), 10000)
|
records, err := trader.GetStore().Decision().GetLatestRecords(trader.GetID(), 10000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get decision log", err)
|
||||||
"error": fmt.Sprintf("Failed to get decision log: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2754,13 +2726,13 @@ func (s *Server) handleDecisions(c *gin.Context) {
|
|||||||
func (s *Server) handleLatestDecisions(c *gin.Context) {
|
func (s *Server) handleLatestDecisions(c *gin.Context) {
|
||||||
_, traderID, err := s.getTraderFromQuery(c)
|
_, traderID, err := s.getTraderFromQuery(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid trader ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trader, err := s.traderManager.GetTrader(traderID)
|
trader, err := s.traderManager.GetTrader(traderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
SafeNotFound(c, "Trader")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2777,9 +2749,7 @@ func (s *Server) handleLatestDecisions(c *gin.Context) {
|
|||||||
|
|
||||||
records, err := trader.GetStore().Decision().GetLatestRecords(trader.GetID(), limit)
|
records, err := trader.GetStore().Decision().GetLatestRecords(trader.GetID(), limit)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get decision log", err)
|
||||||
"error": fmt.Sprintf("Failed to get decision log: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2796,21 +2766,19 @@ func (s *Server) handleLatestDecisions(c *gin.Context) {
|
|||||||
func (s *Server) handleStatistics(c *gin.Context) {
|
func (s *Server) handleStatistics(c *gin.Context) {
|
||||||
_, traderID, err := s.getTraderFromQuery(c)
|
_, traderID, err := s.getTraderFromQuery(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid trader ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
trader, err := s.traderManager.GetTrader(traderID)
|
trader, err := s.traderManager.GetTrader(traderID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": err.Error()})
|
SafeNotFound(c, "Trader")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
stats, err := trader.GetStore().Decision().GetStatistics(trader.GetID())
|
stats, err := trader.GetStore().Decision().GetStatistics(trader.GetID())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get statistics", err)
|
||||||
"error": fmt.Sprintf("Failed to get statistics: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2829,9 +2797,7 @@ func (s *Server) handleCompetition(c *gin.Context) {
|
|||||||
|
|
||||||
competition, err := s.traderManager.GetCompetitionData()
|
competition, err := s.traderManager.GetCompetitionData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get competition data", err)
|
||||||
"error": fmt.Sprintf("Failed to get competition data: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2843,7 +2809,7 @@ func (s *Server) handleCompetition(c *gin.Context) {
|
|||||||
func (s *Server) handleEquityHistory(c *gin.Context) {
|
func (s *Server) handleEquityHistory(c *gin.Context) {
|
||||||
_, traderID, err := s.getTraderFromQuery(c)
|
_, traderID, err := s.getTraderFromQuery(c)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid trader ID")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2851,9 +2817,7 @@ func (s *Server) handleEquityHistory(c *gin.Context) {
|
|||||||
// Every 3 minutes per cycle: 10000 records = about 20 days of data
|
// Every 3 minutes per cycle: 10000 records = about 20 days of data
|
||||||
snapshots, err := s.store.Equity().GetLatest(traderID, 10000)
|
snapshots, err := s.store.Equity().GetLatest(traderID, 10000)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get historical data", err)
|
||||||
"error": fmt.Sprintf("Failed to get historical data: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2931,7 +2895,8 @@ func (s *Server) authMiddleware() gin.HandlerFunc {
|
|||||||
// Validate JWT token
|
// Validate JWT token
|
||||||
claims, err := auth.ValidateJWT(tokenString)
|
claims, err := auth.ValidateJWT(tokenString)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid token: " + err.Error()})
|
logger.Errorf("[Auth] Invalid token: %v", err)
|
||||||
|
c.JSON(http.StatusUnauthorized, gin.H{"error": "Invalid or expired token"})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -2999,7 +2964,7 @@ func (s *Server) handleRegister(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3036,7 +3001,7 @@ func (s *Server) handleRegister(c *gin.Context) {
|
|||||||
|
|
||||||
err = s.store.User().Create(user)
|
err = s.store.User().Create(user)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create user: " + err.Error()})
|
SafeInternalError(c, "Failed to create user", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3059,14 +3024,14 @@ func (s *Server) handleCompleteRegistration(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user information
|
// Get user information
|
||||||
user, err := s.store.User().GetByID(req.UserID)
|
user, err := s.store.User().GetByID(req.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "User does not exist"})
|
SafeNotFound(c, "User")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3112,7 +3077,7 @@ func (s *Server) handleLogin(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3156,14 +3121,14 @@ func (s *Server) handleVerifyOTP(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get user information
|
// Get user information
|
||||||
user, err := s.store.User().GetByID(req.UserID)
|
user, err := s.store.User().GetByID(req.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusNotFound, gin.H{"error": "User does not exist"})
|
SafeNotFound(c, "User")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3197,7 +3162,7 @@ func (s *Server) handleResetPassword(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3326,9 +3291,7 @@ func (s *Server) handlePublicTraderList(c *gin.Context) {
|
|||||||
// Get trader information from all users
|
// Get trader information from all users
|
||||||
competition, err := s.traderManager.GetCompetitionData()
|
competition, err := s.traderManager.GetCompetitionData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get trader list", err)
|
||||||
"error": fmt.Sprintf("Failed to get trader list: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3371,9 +3334,7 @@ func (s *Server) handlePublicTraderList(c *gin.Context) {
|
|||||||
func (s *Server) handlePublicCompetition(c *gin.Context) {
|
func (s *Server) handlePublicCompetition(c *gin.Context) {
|
||||||
competition, err := s.traderManager.GetCompetitionData()
|
competition, err := s.traderManager.GetCompetitionData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get competition data", err)
|
||||||
"error": fmt.Sprintf("Failed to get competition data: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3384,9 +3345,7 @@ func (s *Server) handlePublicCompetition(c *gin.Context) {
|
|||||||
func (s *Server) handleTopTraders(c *gin.Context) {
|
func (s *Server) handleTopTraders(c *gin.Context) {
|
||||||
topTraders, err := s.traderManager.GetTopTradersData()
|
topTraders, err := s.traderManager.GetTopTradersData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get top traders data", err)
|
||||||
"error": fmt.Sprintf("Failed to get top 10 trader data: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3409,9 +3368,7 @@ func (s *Server) handleEquityHistoryBatch(c *gin.Context) {
|
|||||||
// If no trader_ids specified, return historical data for top 5
|
// If no trader_ids specified, return historical data for top 5
|
||||||
topTraders, err := s.traderManager.GetTopTradersData()
|
topTraders, err := s.traderManager.GetTopTradersData()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
SafeInternalError(c, "Get top traders", err)
|
||||||
"error": fmt.Sprintf("Failed to get top 5 traders: %v", err),
|
|
||||||
})
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3506,7 +3463,8 @@ func (s *Server) getEquityHistoryForTraders(traderIDs []string, hours int) map[s
|
|||||||
snapshots, err = s.store.Equity().GetLatest(traderID, 500)
|
snapshots, err = s.store.Equity().GetLatest(traderID, 500)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
errors[traderID] = fmt.Sprintf("Failed to get historical data: %v", err)
|
logger.Errorf("[API] Failed to get equity history for %s: %v", traderID, err)
|
||||||
|
errors[traderID] = "Failed to get historical data"
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+17
-15
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"nofx/decision"
|
"nofx/decision"
|
||||||
|
"nofx/logger"
|
||||||
"nofx/market"
|
"nofx/market"
|
||||||
"nofx/mcp"
|
"nofx/mcp"
|
||||||
"nofx/store"
|
"nofx/store"
|
||||||
@@ -33,7 +34,7 @@ func validateStrategyConfig(config *store.StrategyConfig) []string {
|
|||||||
func (s *Server) handlePublicStrategies(c *gin.Context) {
|
func (s *Server) handlePublicStrategies(c *gin.Context) {
|
||||||
strategies, err := s.store.Strategy().ListPublic()
|
strategies, err := s.store.Strategy().ListPublic()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get public strategies: " + err.Error()})
|
SafeInternalError(c, "Failed to get public strategies", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ func (s *Server) handleGetStrategies(c *gin.Context) {
|
|||||||
|
|
||||||
strategies, err := s.store.Strategy().List(userID)
|
strategies, err := s.store.Strategy().List(userID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get strategy list: " + err.Error()})
|
SafeInternalError(c, "Failed to get strategy list", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,14 +152,14 @@ func (s *Server) handleCreateStrategy(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request parameters: " + err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize configuration
|
// Serialize configuration
|
||||||
configJSON, err := json.Marshal(req.Config)
|
configJSON, err := json.Marshal(req.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to serialize configuration"})
|
SafeInternalError(c, "Serialize configuration", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -173,7 +174,7 @@ func (s *Server) handleCreateStrategy(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.store.Strategy().Create(strategy); err != nil {
|
if err := s.store.Strategy().Create(strategy); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create strategy: " + err.Error()})
|
SafeInternalError(c, "Failed to create strategy", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -221,14 +222,14 @@ func (s *Server) handleUpdateStrategy(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request parameters: " + err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Serialize configuration
|
// Serialize configuration
|
||||||
configJSON, err := json.Marshal(req.Config)
|
configJSON, err := json.Marshal(req.Config)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to serialize configuration"})
|
SafeInternalError(c, "Serialize configuration", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +244,7 @@ func (s *Server) handleUpdateStrategy(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.store.Strategy().Update(strategy); err != nil {
|
if err := s.store.Strategy().Update(strategy); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update strategy: " + err.Error()})
|
SafeInternalError(c, "Failed to update strategy", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -269,7 +270,7 @@ func (s *Server) handleDeleteStrategy(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.store.Strategy().Delete(userID, strategyID); err != nil {
|
if err := s.store.Strategy().Delete(userID, strategyID); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete strategy: " + err.Error()})
|
SafeInternalError(c, "Failed to delete strategy", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,7 +288,7 @@ func (s *Server) handleActivateStrategy(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := s.store.Strategy().SetActive(userID, strategyID); err != nil {
|
if err := s.store.Strategy().SetActive(userID, strategyID); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to activate strategy: " + err.Error()})
|
SafeInternalError(c, "Failed to activate strategy", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,13 +310,13 @@ func (s *Server) handleDuplicateStrategy(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request parameters: " + err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
newID := uuid.New().String()
|
newID := uuid.New().String()
|
||||||
if err := s.store.Strategy().Duplicate(userID, sourceID, newID, req.Name); err != nil {
|
if err := s.store.Strategy().Duplicate(userID, sourceID, newID, req.Name); err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to duplicate strategy: " + err.Error()})
|
SafeInternalError(c, "Failed to duplicate strategy", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -383,7 +384,7 @@ func (s *Server) handlePreviewPrompt(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request parameters: " + err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +434,7 @@ func (s *Server) handleStrategyTestRun(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if err := c.ShouldBindJSON(&req); err != nil {
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid request parameters: " + err.Error()})
|
SafeBadRequest(c, "Invalid request parameters")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -447,8 +448,9 @@ func (s *Server) handleStrategyTestRun(c *gin.Context) {
|
|||||||
// Get candidate coins
|
// Get candidate coins
|
||||||
candidates, err := engine.GetCandidateCoins()
|
candidates, err := engine.GetCandidateCoins()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
logger.Errorf("[API Error] Failed to get candidate coins: %v", err)
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{
|
c.JSON(http.StatusInternalServerError, gin.H{
|
||||||
"error": "Failed to get candidate coins: " + err.Error(),
|
"error": "Failed to get candidate coins",
|
||||||
"ai_response": "",
|
"ai_response": "",
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
|
|||||||
Reference in New Issue
Block a user