From 757e0a9ce4e89d966103a62539ce6acf1cf0a498 Mon Sep 17 00:00:00 2001 From: Virgil Date: Sat, 4 Apr 2026 07:03:01 +0000 Subject: [PATCH] AX: standardize mining handler naming --- pkg/mining/auth.go | 56 +++---- pkg/mining/errors.go | 6 +- pkg/mining/events.go | 14 +- pkg/mining/node_service.go | 178 +++++++++++----------- pkg/mining/ratelimiter.go | 10 +- pkg/mining/service.go | 294 ++++++++++++++++++------------------- 6 files changed, 279 insertions(+), 279 deletions(-) diff --git a/pkg/mining/auth.go b/pkg/mining/auth.go index 6169102..c9c54b4 100644 --- a/pkg/mining/auth.go +++ b/pkg/mining/auth.go @@ -38,26 +38,26 @@ func DefaultAuthConfig() AuthConfig { // authConfig := AuthConfigFromEnv() // reads MINING_API_AUTH, MINING_API_USER, MINING_API_PASS, MINING_API_REALM func AuthConfigFromEnv() AuthConfig { - config := DefaultAuthConfig() + authConfig := DefaultAuthConfig() if os.Getenv("MINING_API_AUTH") == "true" { - config.Enabled = true - config.Username = os.Getenv("MINING_API_USER") - config.Password = os.Getenv("MINING_API_PASS") + authConfig.Enabled = true + authConfig.Username = os.Getenv("MINING_API_USER") + authConfig.Password = os.Getenv("MINING_API_PASS") - if config.Username == "" || config.Password == "" { + if authConfig.Username == "" || authConfig.Password == "" { logging.Warn("API auth enabled but credentials not set", logging.Fields{ "hint": "Set MINING_API_USER and MINING_API_PASS environment variables", }) - config.Enabled = false + authConfig.Enabled = false } } if realm := os.Getenv("MINING_API_REALM"); realm != "" { - config.Realm = realm + authConfig.Realm = realm } - return config + return authConfig } // digestAuth := NewDigestAuth(authConfig); router.Use(digestAuth.Middleware()); defer digestAuth.Stop() @@ -74,7 +74,7 @@ func NewDigestAuth(config AuthConfig) *DigestAuth { config: config, stopChan: make(chan struct{}), } - // Start nonce cleanup goroutine + // go digestAuth.cleanupNonces() // clears expired nonces every 5 minutes go digestAuth.cleanupNonces() return digestAuth } @@ -88,57 +88,57 @@ func (digestAuth *DigestAuth) Stop() { // router.Use(digestAuth.Middleware()) // enforces Digest or Basic auth on all routes func (digestAuth *DigestAuth) Middleware() gin.HandlerFunc { - return func(c *gin.Context) { + return func(requestContext *gin.Context) { if !digestAuth.config.Enabled { - c.Next() + requestContext.Next() return } - authHeader := c.GetHeader("Authorization") + authHeader := requestContext.GetHeader("Authorization") if authHeader == "" { - digestAuth.sendChallenge(c) + digestAuth.sendChallenge(requestContext) return } // Try digest auth first if strings.HasPrefix(authHeader, "Digest ") { - if digestAuth.validateDigest(c, authHeader) { - c.Next() + if digestAuth.validateDigest(requestContext, authHeader) { + requestContext.Next() return } - digestAuth.sendChallenge(c) + digestAuth.sendChallenge(requestContext) return } // Fall back to basic auth if strings.HasPrefix(authHeader, "Basic ") { - if digestAuth.validateBasic(c, authHeader) { - c.Next() + if digestAuth.validateBasic(requestContext, authHeader) { + requestContext.Next() return } } - digestAuth.sendChallenge(c) + digestAuth.sendChallenge(requestContext) } } // digestAuth.sendChallenge(c) // writes WWW-Authenticate header and 401 JSON response -func (digestAuth *DigestAuth) sendChallenge(c *gin.Context) { +func (digestAuth *DigestAuth) sendChallenge(requestContext *gin.Context) { nonce := digestAuth.generateNonce() digestAuth.nonces.Store(nonce, time.Now()) challenge := `Digest realm="` + digestAuth.config.Realm + `", qop="auth", nonce="` + nonce + `", opaque="` + digestAuth.generateOpaque() + `"` - c.Header("WWW-Authenticate", challenge) - c.AbortWithStatusJSON(http.StatusUnauthorized, APIError{ + requestContext.Header("WWW-Authenticate", challenge) + requestContext.AbortWithStatusJSON(http.StatusUnauthorized, APIError{ Code: "AUTH_REQUIRED", Message: "Authentication required", Suggestion: "Provide valid credentials using Digest or Basic authentication", }) } -// valid := digestAuth.validateDigest(c, c.GetHeader("Authorization")) -func (digestAuth *DigestAuth) validateDigest(c *gin.Context, authHeader string) bool { +// valid := digestAuth.validateDigest(requestContext, requestContext.GetHeader("Authorization")) +func (digestAuth *DigestAuth) validateDigest(requestContext *gin.Context, authHeader string) bool { params := parseDigestParams(authHeader[7:]) // Skip "Digest " nonce := params["nonce"] @@ -163,7 +163,7 @@ func (digestAuth *DigestAuth) validateDigest(c *gin.Context, authHeader string) // Calculate expected response hashA1 := md5Hash(digestAuth.config.Username + ":" + digestAuth.config.Realm + ":" + digestAuth.config.Password) - hashA2 := md5Hash(c.Request.Method + ":" + params["uri"]) + hashA2 := md5Hash(requestContext.Request.Method + ":" + params["uri"]) var expectedResponse string if params["qop"] == "auth" { @@ -176,10 +176,10 @@ func (digestAuth *DigestAuth) validateDigest(c *gin.Context, authHeader string) return subtle.ConstantTimeCompare([]byte(expectedResponse), []byte(params["response"])) == 1 } -// valid := digestAuth.validateBasic(c, c.GetHeader("Authorization")) -func (digestAuth *DigestAuth) validateBasic(c *gin.Context, authHeader string) bool { +// valid := digestAuth.validateBasic(requestContext, requestContext.GetHeader("Authorization")) +func (digestAuth *DigestAuth) validateBasic(requestContext *gin.Context, authHeader string) bool { // Gin has built-in basic auth, but we do manual validation for consistency - username, password, ok := c.Request.BasicAuth() + username, password, ok := requestContext.Request.BasicAuth() if !ok { return false } diff --git a/pkg/mining/errors.go b/pkg/mining/errors.go index 7ef710c..bd18218 100644 --- a/pkg/mining/errors.go +++ b/pkg/mining/errors.go @@ -4,8 +4,8 @@ import ( "net/http" ) -// respondWithError(c, http.StatusNotFound, ErrCodeMinerNotFound, "xmrig not found", err.Error()) -// respondWithError(c, http.StatusConflict, ErrCodeMinerExists, "xmrig already running", "") +// respondWithError(requestContext, http.StatusNotFound, ErrCodeMinerNotFound, "xmrig not found", err.Error()) +// respondWithError(requestContext, http.StatusConflict, ErrCodeMinerExists, "xmrig already running", "") const ( ErrCodeMinerNotFound = "MINER_NOT_FOUND" ErrCodeMinerExists = "MINER_EXISTS" @@ -74,7 +74,7 @@ func (e *MiningError) IsRetryable() bool { return e.Retryable } -// c.JSON(e.StatusCode(), e) // 404 for not-found, 500 for internal errors +// requestContext.JSON(e.StatusCode(), e) // 404 for not-found, 500 for internal errors func (e *MiningError) StatusCode() int { if e.HTTPStatus == 0 { return http.StatusInternalServerError diff --git a/pkg/mining/events.go b/pkg/mining/events.go index d81efc2..136c328 100644 --- a/pkg/mining/events.go +++ b/pkg/mining/events.go @@ -56,12 +56,12 @@ type MinerEventData struct { // hub.ServeWs(conn) // upgrades conn, creates wsClient, registers with hub type wsClient struct { - conn *websocket.Conn - send chan []byte - hub *EventHub - miners map[string]bool // subscribed miners, "*" for all - minersMutex sync.RWMutex // protects miners map from concurrent access - closeOnce sync.Once + conn *websocket.Conn + send chan []byte + hub *EventHub + miners map[string]bool // subscribed miners, "*" for all + minersMutex sync.RWMutex // protects miners map from concurrent access + closeOnce sync.Once } // client.safeClose() // safe to call from multiple goroutines; channel closed exactly once @@ -377,7 +377,7 @@ func (client *wsClient) readPump() { } } -// if !hub.ServeWs(conn) { c.JSON(http.StatusServiceUnavailable, gin.H{"error": "connection limit reached"}) } +// if !hub.ServeWs(conn) { requestContext.JSON(http.StatusServiceUnavailable, gin.H{"error": "connection limit reached"}) } func (hub *EventHub) ServeWs(conn *websocket.Conn) bool { // Check connection limit hub.mutex.RLock() diff --git a/pkg/mining/node_service.go b/pkg/mining/node_service.go index 142e47b..d422734 100644 --- a/pkg/mining/node_service.go +++ b/pkg/mining/node_service.go @@ -10,7 +10,7 @@ import ( ) // nodeService, err := NewNodeService() -// nodeService.SetupRoutes(router.Group("/api/v1/mining")) +// router.Group("/api/v1/mining").Group("/node").GET("/info", nodeService.handleNodeInfo) type NodeService struct { nodeManager *node.NodeManager peerRegistry *node.PeerRegistry @@ -31,8 +31,8 @@ func NewNodeService() (*NodeService, error) { return nil, err } - config := node.DefaultTransportConfig() - transport := node.NewTransport(nodeManager, peerRegistry, config) + transportConfig := node.DefaultTransportConfig() + transport := node.NewTransport(nodeManager, peerRegistry, transportConfig) nodeService := &NodeService{ nodeManager: nodeManager, @@ -46,16 +46,16 @@ func NewNodeService() (*NodeService, error) { return nodeService, nil } -// service.SetupRoutes(router.Group("/api/v1/mining")) // registers /node, /peers, /remote route groups +// router.Group("/api/v1/mining") exposes /node, /peers, and /remote route groups. func (nodeService *NodeService) SetupRoutes(router *gin.RouterGroup) { - // Node identity endpoints + // router.Group("/node").GET("/info", nodeService.handleNodeInfo) exposes node identity and peer counts. nodeGroup := router.Group("/node") { nodeGroup.GET("/info", nodeService.handleNodeInfo) nodeGroup.POST("/init", nodeService.handleNodeInit) } - // Peer management endpoints + // router.Group("/peers").POST("", nodeService.handleAddPeer) registers a peer like 10.0.0.2:9090. peerGroup := router.Group("/peers") { peerGroup.GET("", nodeService.handleListPeers) @@ -66,7 +66,7 @@ func (nodeService *NodeService) SetupRoutes(router *gin.RouterGroup) { peerGroup.POST("/:id/connect", nodeService.handleConnectPeer) peerGroup.POST("/:id/disconnect", nodeService.handleDisconnectPeer) - // Allowlist management + // router.Group("/peers/auth/allowlist").POST("", nodeService.handleAddToAllowlist) accepts keys like ed25519:abc... peerGroup.GET("/auth/mode", nodeService.handleGetAuthMode) peerGroup.PUT("/auth/mode", nodeService.handleSetAuthMode) peerGroup.GET("/auth/allowlist", nodeService.handleListAllowlist) @@ -74,7 +74,7 @@ func (nodeService *NodeService) SetupRoutes(router *gin.RouterGroup) { peerGroup.DELETE("/auth/allowlist/:key", nodeService.handleRemoveFromAllowlist) } - // Remote operations endpoints + // router.Group("/remote").POST("/:peerId/start", nodeService.handleRemoteStart) starts a miner on a peer like peer-123. remoteGroup := router.Group("/remote") { remoteGroup.GET("/stats", nodeService.handleRemoteStats) @@ -110,7 +110,7 @@ type NodeInfoResponse struct { // @Produce json // @Success 200 {object} NodeInfoResponse // @Router /node/info [get] -func (nodeService *NodeService) handleNodeInfo(c *gin.Context) { +func (nodeService *NodeService) handleNodeInfo(requestContext *gin.Context) { response := NodeInfoResponse{ HasIdentity: nodeService.nodeManager.HasIdentity(), RegisteredPeers: nodeService.peerRegistry.Count(), @@ -121,7 +121,7 @@ func (nodeService *NodeService) handleNodeInfo(c *gin.Context) { response.Identity = nodeService.nodeManager.GetIdentity() } - c.JSON(http.StatusOK, response) + requestContext.JSON(http.StatusOK, response) } // POST /node/init {"name": "my-node", "role": "worker"} @@ -139,15 +139,15 @@ type NodeInitRequest struct { // @Param request body NodeInitRequest true "Node initialization parameters" // @Success 200 {object} node.NodeIdentity // @Router /node/init [post] -func (nodeService *NodeService) handleNodeInit(c *gin.Context) { +func (nodeService *NodeService) handleNodeInit(requestContext *gin.Context) { var request NodeInitRequest - if err := c.ShouldBindJSON(&request); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + if err := requestContext.ShouldBindJSON(&request); err != nil { + requestContext.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if nodeService.nodeManager.HasIdentity() { - c.JSON(http.StatusConflict, gin.H{"error": "node identity already exists"}) + requestContext.JSON(http.StatusConflict, gin.H{"error": "node identity already exists"}) return } @@ -160,16 +160,16 @@ func (nodeService *NodeService) handleNodeInit(c *gin.Context) { case "dual", "": role = node.RoleDual default: - c.JSON(http.StatusBadRequest, gin.H{"error": "invalid role"}) + requestContext.JSON(http.StatusBadRequest, gin.H{"error": "invalid role"}) return } if err := nodeService.nodeManager.GenerateIdentity(request.Name, role); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + requestContext.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, nodeService.nodeManager.GetIdentity()) + requestContext.JSON(http.StatusOK, nodeService.nodeManager.GetIdentity()) } // handleListPeers godoc @@ -179,9 +179,9 @@ func (nodeService *NodeService) handleNodeInit(c *gin.Context) { // @Produce json // @Success 200 {array} node.Peer // @Router /peers [get] -func (nodeService *NodeService) handleListPeers(c *gin.Context) { +func (nodeService *NodeService) handleListPeers(requestContext *gin.Context) { peers := nodeService.peerRegistry.ListPeers() - c.JSON(http.StatusOK, peers) + requestContext.JSON(http.StatusOK, peers) } // POST /peers {"address": "10.0.0.2:9090", "name": "worker-1"} @@ -199,10 +199,10 @@ type AddPeerRequest struct { // @Param request body AddPeerRequest true "Peer information" // @Success 201 {object} node.Peer // @Router /peers [post] -func (nodeService *NodeService) handleAddPeer(c *gin.Context) { +func (nodeService *NodeService) handleAddPeer(requestContext *gin.Context) { var request AddPeerRequest - if err := c.ShouldBindJSON(&request); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + if err := requestContext.ShouldBindJSON(&request); err != nil { + requestContext.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } @@ -215,11 +215,11 @@ func (nodeService *NodeService) handleAddPeer(c *gin.Context) { } if err := nodeService.peerRegistry.AddPeer(peer); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + requestContext.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusCreated, peer) + requestContext.JSON(http.StatusCreated, peer) } // handleGetPeer godoc @@ -230,14 +230,14 @@ func (nodeService *NodeService) handleAddPeer(c *gin.Context) { // @Param id path string true "Peer ID" // @Success 200 {object} node.Peer // @Router /peers/{id} [get] -func (nodeService *NodeService) handleGetPeer(c *gin.Context) { - peerID := c.Param("id") +func (nodeService *NodeService) handleGetPeer(requestContext *gin.Context) { + peerID := requestContext.Param("id") peer := nodeService.peerRegistry.GetPeer(peerID) if peer == nil { - c.JSON(http.StatusNotFound, gin.H{"error": "peer not found"}) + requestContext.JSON(http.StatusNotFound, gin.H{"error": "peer not found"}) return } - c.JSON(http.StatusOK, peer) + requestContext.JSON(http.StatusOK, peer) } // handleRemovePeer godoc @@ -248,13 +248,13 @@ func (nodeService *NodeService) handleGetPeer(c *gin.Context) { // @Param id path string true "Peer ID" // @Success 200 {object} map[string]string // @Router /peers/{id} [delete] -func (nodeService *NodeService) handleRemovePeer(c *gin.Context) { - peerID := c.Param("id") +func (nodeService *NodeService) handleRemovePeer(requestContext *gin.Context) { + peerID := requestContext.Param("id") if err := nodeService.peerRegistry.RemovePeer(peerID); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + requestContext.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, gin.H{"status": "peer removed"}) + requestContext.JSON(http.StatusOK, gin.H{"status": "peer removed"}) } // handlePingPeer godoc @@ -266,18 +266,18 @@ func (nodeService *NodeService) handleRemovePeer(c *gin.Context) { // @Success 200 {object} map[string]float64 // @Failure 404 {object} APIError "Peer not found" // @Router /peers/{id}/ping [post] -func (nodeService *NodeService) handlePingPeer(c *gin.Context) { - peerID := c.Param("id") +func (nodeService *NodeService) handlePingPeer(requestContext *gin.Context) { + peerID := requestContext.Param("id") rtt, err := nodeService.controller.PingPeer(peerID) if err != nil { if containsStr(err.Error(), "not found") || containsStr(err.Error(), "not connected") { - respondWithError(c, http.StatusNotFound, "PEER_NOT_FOUND", "peer not found or not connected", err.Error()) + respondWithError(requestContext, http.StatusNotFound, "PEER_NOT_FOUND", "peer not found or not connected", err.Error()) return } - respondWithError(c, http.StatusInternalServerError, ErrCodeInternalError, "ping failed", err.Error()) + respondWithError(requestContext, http.StatusInternalServerError, ErrCodeInternalError, "ping failed", err.Error()) return } - c.JSON(http.StatusOK, gin.H{"rtt_ms": rtt}) + requestContext.JSON(http.StatusOK, gin.H{"rtt_ms": rtt}) } // handleConnectPeer godoc @@ -289,17 +289,17 @@ func (nodeService *NodeService) handlePingPeer(c *gin.Context) { // @Success 200 {object} map[string]string // @Failure 404 {object} APIError "Peer not found" // @Router /peers/{id}/connect [post] -func (nodeService *NodeService) handleConnectPeer(c *gin.Context) { - peerID := c.Param("id") +func (nodeService *NodeService) handleConnectPeer(requestContext *gin.Context) { + peerID := requestContext.Param("id") if err := nodeService.controller.ConnectToPeer(peerID); err != nil { if containsStr(err.Error(), "not found") { - respondWithError(c, http.StatusNotFound, "PEER_NOT_FOUND", "peer not found", err.Error()) + respondWithError(requestContext, http.StatusNotFound, "PEER_NOT_FOUND", "peer not found", err.Error()) return } - respondWithError(c, http.StatusInternalServerError, ErrCodeConnectionFailed, "connection failed", err.Error()) + respondWithError(requestContext, http.StatusInternalServerError, ErrCodeConnectionFailed, "connection failed", err.Error()) return } - c.JSON(http.StatusOK, gin.H{"status": "connected"}) + requestContext.JSON(http.StatusOK, gin.H{"status": "connected"}) } // handleDisconnectPeer godoc @@ -310,18 +310,18 @@ func (nodeService *NodeService) handleConnectPeer(c *gin.Context) { // @Param id path string true "Peer ID" // @Success 200 {object} map[string]string // @Router /peers/{id}/disconnect [post] -func (nodeService *NodeService) handleDisconnectPeer(c *gin.Context) { - peerID := c.Param("id") +func (nodeService *NodeService) handleDisconnectPeer(requestContext *gin.Context) { + peerID := requestContext.Param("id") if err := nodeService.controller.DisconnectFromPeer(peerID); err != nil { // Make disconnect idempotent - if peer not connected, still return success if containsStr(err.Error(), "not connected") { - c.JSON(http.StatusOK, gin.H{"status": "disconnected"}) + requestContext.JSON(http.StatusOK, gin.H{"status": "disconnected"}) return } - respondWithError(c, http.StatusInternalServerError, ErrCodeInternalError, "disconnect failed", err.Error()) + respondWithError(requestContext, http.StatusInternalServerError, ErrCodeInternalError, "disconnect failed", err.Error()) return } - c.JSON(http.StatusOK, gin.H{"status": "disconnected"}) + requestContext.JSON(http.StatusOK, gin.H{"status": "disconnected"}) } // handleRemoteStats godoc @@ -331,9 +331,9 @@ func (nodeService *NodeService) handleDisconnectPeer(c *gin.Context) { // @Produce json // @Success 200 {object} map[string]node.StatsPayload // @Router /remote/stats [get] -func (nodeService *NodeService) handleRemoteStats(c *gin.Context) { +func (nodeService *NodeService) handleRemoteStats(requestContext *gin.Context) { stats := nodeService.controller.GetAllStats() - c.JSON(http.StatusOK, stats) + requestContext.JSON(http.StatusOK, stats) } // handlePeerStats godoc @@ -344,14 +344,14 @@ func (nodeService *NodeService) handleRemoteStats(c *gin.Context) { // @Param peerId path string true "Peer ID" // @Success 200 {object} node.StatsPayload // @Router /remote/{peerId}/stats [get] -func (nodeService *NodeService) handlePeerStats(c *gin.Context) { - peerID := c.Param("peerId") +func (nodeService *NodeService) handlePeerStats(requestContext *gin.Context) { + peerID := requestContext.Param("peerId") stats, err := nodeService.controller.GetRemoteStats(peerID) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + requestContext.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, stats) + requestContext.JSON(http.StatusOK, stats) } // POST /remote/{peerId}/start {"minerType": "xmrig", "profileId": "abc123"} @@ -371,19 +371,19 @@ type RemoteStartRequest struct { // @Param request body RemoteStartRequest true "Start parameters" // @Success 200 {object} map[string]string // @Router /remote/{peerId}/start [post] -func (nodeService *NodeService) handleRemoteStart(c *gin.Context) { - peerID := c.Param("peerId") +func (nodeService *NodeService) handleRemoteStart(requestContext *gin.Context) { + peerID := requestContext.Param("peerId") var request RemoteStartRequest - if err := c.ShouldBindJSON(&request); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + if err := requestContext.ShouldBindJSON(&request); err != nil { + requestContext.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := nodeService.controller.StartRemoteMiner(peerID, request.MinerType, request.ProfileID, request.Config); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + requestContext.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, gin.H{"status": "miner started"}) + requestContext.JSON(http.StatusOK, gin.H{"status": "miner started"}) } // POST /remote/{peerId}/stop {"minerName": "xmrig-main"} @@ -401,19 +401,19 @@ type RemoteStopRequest struct { // @Param request body RemoteStopRequest true "Stop parameters" // @Success 200 {object} map[string]string // @Router /remote/{peerId}/stop [post] -func (nodeService *NodeService) handleRemoteStop(c *gin.Context) { - peerID := c.Param("peerId") +func (nodeService *NodeService) handleRemoteStop(requestContext *gin.Context) { + peerID := requestContext.Param("peerId") var request RemoteStopRequest - if err := c.ShouldBindJSON(&request); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + if err := requestContext.ShouldBindJSON(&request); err != nil { + requestContext.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := nodeService.controller.StopRemoteMiner(peerID, request.MinerName); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + requestContext.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, gin.H{"status": "miner stopped"}) + requestContext.JSON(http.StatusOK, gin.H{"status": "miner stopped"}) } // handleRemoteLogs godoc @@ -426,12 +426,12 @@ func (nodeService *NodeService) handleRemoteStop(c *gin.Context) { // @Param lines query int false "Number of lines (max 10000)" default(100) // @Success 200 {array} string // @Router /remote/{peerId}/logs/{miner} [get] -func (nodeService *NodeService) handleRemoteLogs(c *gin.Context) { - peerID := c.Param("peerId") - minerName := c.Param("miner") +func (nodeService *NodeService) handleRemoteLogs(requestContext *gin.Context) { + peerID := requestContext.Param("peerId") + minerName := requestContext.Param("miner") lines := 100 const maxLines = 10000 // Prevent resource exhaustion - if linesParam := c.Query("lines"); linesParam != "" { + if linesParam := requestContext.Query("lines"); linesParam != "" { if parsed, err := strconv.Atoi(linesParam); err == nil && parsed > 0 { lines = parsed if lines > maxLines { @@ -442,10 +442,10 @@ func (nodeService *NodeService) handleRemoteLogs(c *gin.Context) { logs, err := nodeService.controller.GetRemoteLogs(peerID, minerName, lines) if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) + requestContext.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, logs) + requestContext.JSON(http.StatusOK, logs) } // GET /peers/auth/mode → {"mode": "open"} or {"mode": "allowlist"} @@ -460,13 +460,13 @@ type AuthModeResponse struct { // @Produce json // @Success 200 {object} AuthModeResponse // @Router /peers/auth/mode [get] -func (nodeService *NodeService) handleGetAuthMode(c *gin.Context) { +func (nodeService *NodeService) handleGetAuthMode(requestContext *gin.Context) { mode := nodeService.peerRegistry.GetAuthMode() modeStr := "open" if mode == node.PeerAuthAllowlist { modeStr = "allowlist" } - c.JSON(http.StatusOK, AuthModeResponse{Mode: modeStr}) + requestContext.JSON(http.StatusOK, AuthModeResponse{Mode: modeStr}) } // PUT /peers/auth/mode {"mode": "allowlist"} // or "open" @@ -484,10 +484,10 @@ type SetAuthModeRequest struct { // @Success 200 {object} AuthModeResponse // @Failure 400 {object} APIError "Invalid mode" // @Router /peers/auth/mode [put] -func (nodeService *NodeService) handleSetAuthMode(c *gin.Context) { +func (nodeService *NodeService) handleSetAuthMode(requestContext *gin.Context) { var request SetAuthModeRequest - if err := c.ShouldBindJSON(&request); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + if err := requestContext.ShouldBindJSON(&request); err != nil { + requestContext.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } @@ -498,12 +498,12 @@ func (nodeService *NodeService) handleSetAuthMode(c *gin.Context) { case "allowlist": mode = node.PeerAuthAllowlist default: - respondWithError(c, http.StatusBadRequest, "INVALID_MODE", "mode must be 'open' or 'allowlist'", "") + respondWithError(requestContext, http.StatusBadRequest, "INVALID_MODE", "mode must be 'open' or 'allowlist'", "") return } nodeService.peerRegistry.SetAuthMode(mode) - c.JSON(http.StatusOK, AuthModeResponse{Mode: request.Mode}) + requestContext.JSON(http.StatusOK, AuthModeResponse{Mode: request.Mode}) } // GET /peers/auth/allowlist → {"publicKeys": ["ed25519:abc...", "ed25519:def..."]} @@ -518,9 +518,9 @@ type AllowlistResponse struct { // @Produce json // @Success 200 {object} AllowlistResponse // @Router /peers/auth/allowlist [get] -func (nodeService *NodeService) handleListAllowlist(c *gin.Context) { +func (nodeService *NodeService) handleListAllowlist(requestContext *gin.Context) { keys := nodeService.peerRegistry.ListAllowedPublicKeys() - c.JSON(http.StatusOK, AllowlistResponse{PublicKeys: keys}) + requestContext.JSON(http.StatusOK, AllowlistResponse{PublicKeys: keys}) } // POST /peers/auth/allowlist {"publicKey": "ed25519:abc123..."} @@ -538,20 +538,20 @@ type AddAllowlistRequest struct { // @Success 201 {object} map[string]string // @Failure 400 {object} APIError "Invalid request" // @Router /peers/auth/allowlist [post] -func (nodeService *NodeService) handleAddToAllowlist(c *gin.Context) { +func (nodeService *NodeService) handleAddToAllowlist(requestContext *gin.Context) { var request AddAllowlistRequest - if err := c.ShouldBindJSON(&request); err != nil { - c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) + if err := requestContext.ShouldBindJSON(&request); err != nil { + requestContext.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if len(request.PublicKey) < 16 { - respondWithError(c, http.StatusBadRequest, "INVALID_KEY", "public key too short", "") + respondWithError(requestContext, http.StatusBadRequest, "INVALID_KEY", "public key too short", "") return } nodeService.peerRegistry.AllowPublicKey(request.PublicKey) - c.JSON(http.StatusCreated, gin.H{"status": "added"}) + requestContext.JSON(http.StatusCreated, gin.H{"status": "added"}) } // handleRemoveFromAllowlist godoc @@ -562,13 +562,13 @@ func (nodeService *NodeService) handleAddToAllowlist(c *gin.Context) { // @Param key path string true "Public key to remove (URL-encoded)" // @Success 200 {object} map[string]string // @Router /peers/auth/allowlist/{key} [delete] -func (nodeService *NodeService) handleRemoveFromAllowlist(c *gin.Context) { - key := c.Param("key") +func (nodeService *NodeService) handleRemoveFromAllowlist(requestContext *gin.Context) { + key := requestContext.Param("key") if key == "" { - respondWithError(c, http.StatusBadRequest, "MISSING_KEY", "public key required", "") + respondWithError(requestContext, http.StatusBadRequest, "MISSING_KEY", "public key required", "") return } nodeService.peerRegistry.RevokePublicKey(key) - c.JSON(http.StatusOK, gin.H{"status": "removed"}) + requestContext.JSON(http.StatusOK, gin.H{"status": "removed"}) } diff --git a/pkg/mining/ratelimiter.go b/pkg/mining/ratelimiter.go index 9186edd..c81007e 100644 --- a/pkg/mining/ratelimiter.go +++ b/pkg/mining/ratelimiter.go @@ -80,8 +80,8 @@ func (limiter *RateLimiter) Stop() { // router.Use(limiter.Middleware()) // install before route handlers func (limiter *RateLimiter) Middleware() gin.HandlerFunc { - return func(c *gin.Context) { - clientAddress := c.ClientIP() + return func(requestContext *gin.Context) { + clientAddress := requestContext.ClientIP() limiter.mutex.Lock() client, exists := limiter.clients[clientAddress] @@ -101,15 +101,15 @@ func (limiter *RateLimiter) Middleware() gin.HandlerFunc { if client.tokens < 1 { limiter.mutex.Unlock() - respondWithError(c, http.StatusTooManyRequests, "RATE_LIMITED", + respondWithError(requestContext, http.StatusTooManyRequests, "RATE_LIMITED", "too many requests", "rate limit exceeded") - c.Abort() + requestContext.Abort() return } client.tokens-- limiter.mutex.Unlock() - c.Next() + requestContext.Next() } } diff --git a/pkg/mining/service.go b/pkg/mining/service.go index 39d6c91..48bd8b0 100644 --- a/pkg/mining/service.go +++ b/pkg/mining/service.go @@ -279,60 +279,60 @@ const ( // router.Use(cacheMiddleware()) serves GET /miners/available with Cache-Control: public, max-age=300. func cacheMiddleware() gin.HandlerFunc { - return func(c *gin.Context) { + return func(requestContext *gin.Context) { // Only cache GET requests like /api/v1/mining/info. - if c.Request.Method != http.MethodGet { - c.Header("Cache-Control", CacheNoStore) - c.Next() + if requestContext.Request.Method != http.MethodGet { + requestContext.Header("Cache-Control", CacheNoStore) + requestContext.Next() return } - path := c.Request.URL.Path + path := requestContext.Request.URL.Path // Cache GET /api/v1/mining/miners/available for 5 minutes. switch { case strings.HasSuffix(path, "/available"): - c.Header("Cache-Control", CachePublic5Min) + requestContext.Header("Cache-Control", CachePublic5Min) case strings.HasSuffix(path, "/info"): // Cache GET /api/v1/mining/info for 1 minute. - c.Header("Cache-Control", CachePublic1Min) + requestContext.Header("Cache-Control", CachePublic1Min) case strings.Contains(path, "/swagger"): // Cache GET /api/v1/mining/swagger/index.html for 5 minutes. - c.Header("Cache-Control", CachePublic5Min) + requestContext.Header("Cache-Control", CachePublic5Min) default: // Keep dynamic requests like /api/v1/mining/miners/xmrig live. - c.Header("Cache-Control", CacheNoCache) + requestContext.Header("Cache-Control", CacheNoCache) } - c.Next() + requestContext.Next() } } // router.Use(requestTimeoutMiddleware(30 * time.Second)) aborts GET /history/miners/xmrig after 30 seconds. func requestTimeoutMiddleware(timeout time.Duration) gin.HandlerFunc { - return func(c *gin.Context) { + return func(requestContext *gin.Context) { // Skip timeout for WebSocket upgrades like /ws/events. - if c.GetHeader("Upgrade") == "websocket" { - c.Next() + if requestContext.GetHeader("Upgrade") == "websocket" { + requestContext.Next() return } - if strings.HasSuffix(c.Request.URL.Path, "/events") { - c.Next() + if strings.HasSuffix(requestContext.Request.URL.Path, "/events") { + requestContext.Next() return } // Create a request-scoped timeout for requests like GET /api/v1/mining/history/xmrig. - ctx, cancel := context.WithTimeout(c.Request.Context(), timeout) + ctx, cancel := context.WithTimeout(requestContext.Request.Context(), timeout) defer cancel() // Replace the incoming request context with the timed one. - c.Request = c.Request.WithContext(ctx) + requestContext.Request = requestContext.Request.WithContext(ctx) var responded int32 done := make(chan struct{}) go func() { - c.Next() + requestContext.Next() atomic.StoreInt32(&responded, 1) close(done) }() @@ -343,15 +343,15 @@ func requestTimeoutMiddleware(timeout time.Duration) gin.HandlerFunc { case <-ctx.Done(): // Timeout occurred; only respond if the handler has not already written a response. if atomic.CompareAndSwapInt32(&responded, 0, 1) { - c.Abort() - respondWithError(c, http.StatusGatewayTimeout, ErrCodeTimeout, + requestContext.Abort() + respondWithError(requestContext, http.StatusGatewayTimeout, ErrCodeTimeout, "Request timed out", "Request exceeded "+timeout.String()+" timeout") } } } } -// conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil) upgrades GET /ws/events to WebSocket. +// conn, err := wsUpgrader.Upgrade(requestContext.Writer, requestContext.Request, nil) upgrades GET /ws/events to WebSocket. var wsUpgrader = websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, @@ -415,8 +415,8 @@ func NewService(manager ManagerInterface, listenAddress string, displayAddress s if len(miners) == 0 { return nil } - // state := []map[string]interface{}{{"name": "xmrig-rx_0", "status": "running"}} - state := make([]map[string]interface{}, 0, len(miners)) + // minerStates := []map[string]interface{}{{"name": "xmrig-rx_0", "status": "running"}} + minerStates := make([]map[string]interface{}, 0, len(miners)) for _, miner := range miners { stats, _ := miner.GetStats(context.Background()) minerState := map[string]interface{}{ @@ -429,10 +429,10 @@ func NewService(manager ManagerInterface, listenAddress string, displayAddress s minerState["rejected"] = stats.Rejected minerState["uptime"] = stats.Uptime } - state = append(state, minerState) + minerStates = append(minerStates, minerState) } return map[string]interface{}{ - "miners": state, + "miners": minerStates, } }) @@ -502,10 +502,10 @@ func (service *Service) InitRouter() { // service.Router.Use(contentTypeValidationMiddleware()) // rejects POST /api/v1/mining/profiles without application/json. service.Router.Use(contentTypeValidationMiddleware()) - // c.Request.Body = http.MaxBytesReader(..., 1<<20) // caps bodies for requests like POST /api/v1/mining/profiles. - service.Router.Use(func(c *gin.Context) { - c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 1<<20) // 1MB - c.Next() + // requestContext.Request.Body = http.MaxBytesReader(..., 1<<20) // caps bodies for requests like POST /api/v1/mining/profiles. + service.Router.Use(func(requestContext *gin.Context) { + requestContext.Request.Body = http.MaxBytesReader(requestContext.Writer, requestContext.Request.Body, 1<<20) // 1MB + requestContext.Next() }) // service.Router.Use(csrfMiddleware()) // allows API clients with Authorization or X-Requested-With headers. @@ -678,7 +678,7 @@ func (service *Service) SetupRoutes() { logging.Info("MCP server enabled", logging.Fields{"endpoint": service.APIBasePath + "/mcp"}) } -// c.JSON(http.StatusOK, HealthResponse{Status: "healthy", Components: map[string]string{"db": "ok"}}) +// requestContext.JSON(http.StatusOK, HealthResponse{Status: "healthy", Components: map[string]string{"db": "ok"}}) type HealthResponse struct { Status string `json:"status"` Components map[string]string `json:"components,omitempty"` @@ -691,8 +691,8 @@ type HealthResponse struct { // @Produce json // @Success 200 {object} HealthResponse // @Router /health [get] -func (service *Service) handleHealth(c *gin.Context) { - c.JSON(http.StatusOK, HealthResponse{ +func (service *Service) handleHealth(requestContext *gin.Context) { + requestContext.JSON(http.StatusOK, HealthResponse{ Status: "healthy", }) } @@ -705,7 +705,7 @@ func (service *Service) handleHealth(c *gin.Context) { // @Success 200 {object} HealthResponse // @Success 503 {object} HealthResponse // @Router /ready [get] -func (service *Service) handleReady(c *gin.Context) { +func (service *Service) handleReady(requestContext *gin.Context) { components := make(map[string]string) allReady := true @@ -747,7 +747,7 @@ func (service *Service) handleReady(c *gin.Context) { httpStatus = http.StatusServiceUnavailable } - c.JSON(httpStatus, HealthResponse{ + requestContext.JSON(httpStatus, HealthResponse{ Status: status, Components: components, }) @@ -761,13 +761,13 @@ func (service *Service) handleReady(c *gin.Context) { // @Success 200 {object} SystemInfo // @Failure 500 {object} map[string]string "Internal server error" // @Router /info [get] -func (service *Service) handleGetInfo(c *gin.Context) { +func (service *Service) handleGetInfo(requestContext *gin.Context) { systemInfo, err := service.updateInstallationCache() if err != nil { - respondWithMiningError(c, ErrInternal("failed to get system info").WithCause(err)) + respondWithMiningError(requestContext, ErrInternal("failed to get system info").WithCause(err)) return } - c.JSON(http.StatusOK, systemInfo) + requestContext.JSON(http.StatusOK, systemInfo) } // systemInfo, err := service.updateInstallationCache() @@ -828,13 +828,13 @@ func (service *Service) updateInstallationCache() (*SystemInfo, error) { // @Produce json // @Success 200 {object} SystemInfo // @Router /doctor [post] -func (service *Service) handleDoctor(c *gin.Context) { +func (service *Service) handleDoctor(requestContext *gin.Context) { systemInfo, err := service.updateInstallationCache() if err != nil { - respondWithMiningError(c, ErrInternal("failed to update cache").WithCause(err)) + respondWithMiningError(requestContext, ErrInternal("failed to update cache").WithCause(err)) return } - c.JSON(http.StatusOK, systemInfo) + requestContext.JSON(http.StatusOK, systemInfo) } // handleUpdateCheck godoc @@ -844,7 +844,7 @@ func (service *Service) handleDoctor(c *gin.Context) { // @Produce json // @Success 200 {object} map[string]string // @Router /update [post] -func (service *Service) handleUpdateCheck(c *gin.Context) { +func (service *Service) handleUpdateCheck(requestContext *gin.Context) { updates := make(map[string]string) for _, availableMiner := range service.Manager.ListAvailableMiners() { miner, err := CreateMiner(availableMiner.Name) @@ -878,11 +878,11 @@ func (service *Service) handleUpdateCheck(c *gin.Context) { } if len(updates) == 0 { - c.JSON(http.StatusOK, gin.H{"status": "All miners are up to date."}) + requestContext.JSON(http.StatusOK, gin.H{"status": "All miners are up to date."}) return } - c.JSON(http.StatusOK, gin.H{"updates_available": updates}) + requestContext.JSON(http.StatusOK, gin.H{"updates_available": updates}) } // handleUninstallMiner godoc @@ -893,16 +893,16 @@ func (service *Service) handleUpdateCheck(c *gin.Context) { // @Param miner_type path string true "Miner Type to uninstall" // @Success 200 {object} map[string]string // @Router /miners/{miner_type}/uninstall [delete] -func (service *Service) handleUninstallMiner(c *gin.Context) { - minerType := c.Param("miner_name") - if err := service.Manager.UninstallMiner(c.Request.Context(), minerType); err != nil { - respondWithMiningError(c, ErrInternal("failed to uninstall miner").WithCause(err)) +func (service *Service) handleUninstallMiner(requestContext *gin.Context) { + minerType := requestContext.Param("miner_name") + if err := service.Manager.UninstallMiner(requestContext.Request.Context(), minerType); err != nil { + respondWithMiningError(requestContext, ErrInternal("failed to uninstall miner").WithCause(err)) return } if _, err := service.updateInstallationCache(); err != nil { logging.Warn("failed to update cache after uninstall", logging.Fields{"error": err}) } - c.JSON(http.StatusOK, gin.H{"status": minerType + " uninstalled successfully."}) + requestContext.JSON(http.StatusOK, gin.H{"status": minerType + " uninstalled successfully."}) } // handleListMiners godoc @@ -912,9 +912,9 @@ func (service *Service) handleUninstallMiner(c *gin.Context) { // @Produce json // @Success 200 {array} XMRigMiner // @Router /miners [get] -func (service *Service) handleListMiners(c *gin.Context) { +func (service *Service) handleListMiners(requestContext *gin.Context) { miners := service.Manager.ListMiners() - c.JSON(http.StatusOK, miners) + requestContext.JSON(http.StatusOK, miners) } // handleListAvailableMiners godoc @@ -924,9 +924,9 @@ func (service *Service) handleListMiners(c *gin.Context) { // @Produce json // @Success 200 {array} AvailableMiner // @Router /miners/available [get] -func (service *Service) handleListAvailableMiners(c *gin.Context) { +func (service *Service) handleListAvailableMiners(requestContext *gin.Context) { miners := service.Manager.ListAvailableMiners() - c.JSON(http.StatusOK, miners) + requestContext.JSON(http.StatusOK, miners) } // handleInstallMiner godoc @@ -937,16 +937,16 @@ func (service *Service) handleListAvailableMiners(c *gin.Context) { // @Param miner_type path string true "Miner Type to install/update" // @Success 200 {object} map[string]string // @Router /miners/{miner_type}/install [post] -func (service *Service) handleInstallMiner(c *gin.Context) { - minerType := c.Param("miner_name") +func (service *Service) handleInstallMiner(requestContext *gin.Context) { + minerType := requestContext.Param("miner_name") miner, err := CreateMiner(minerType) if err != nil { - respondWithMiningError(c, ErrUnsupportedMiner(minerType)) + respondWithMiningError(requestContext, ErrUnsupportedMiner(minerType)) return } if err := miner.Install(); err != nil { - respondWithMiningError(c, ErrInstallFailed(minerType).WithCause(err)) + respondWithMiningError(requestContext, ErrInstallFailed(minerType).WithCause(err)) return } @@ -956,11 +956,11 @@ func (service *Service) handleInstallMiner(c *gin.Context) { details, err := miner.CheckInstallation() if err != nil { - respondWithMiningError(c, ErrInternal("failed to verify installation").WithCause(err)) + respondWithMiningError(requestContext, ErrInternal("failed to verify installation").WithCause(err)) return } - c.JSON(http.StatusOK, gin.H{"status": "installed", "version": details.Version, "path": details.Path}) + requestContext.JSON(http.StatusOK, gin.H{"status": "installed", "version": details.Version, "path": details.Path}) } // handleStartMinerWithProfile godoc @@ -971,32 +971,32 @@ func (service *Service) handleInstallMiner(c *gin.Context) { // @Param id path string true "Profile ID" // @Success 200 {object} XMRigMiner // @Router /profiles/{id}/start [post] -func (service *Service) handleStartMinerWithProfile(c *gin.Context) { - profileID := c.Param("id") +func (service *Service) handleStartMinerWithProfile(requestContext *gin.Context) { + profileID := requestContext.Param("id") profile, exists := service.ProfileManager.GetProfile(profileID) if !exists { - respondWithMiningError(c, ErrProfileNotFound(profileID)) + respondWithMiningError(requestContext, ErrProfileNotFound(profileID)) return } - var config Config - if err := json.Unmarshal(profile.Config, &config); err != nil { - respondWithMiningError(c, ErrInvalidConfig("failed to parse profile config").WithCause(err)) + var minerConfig Config + if err := json.Unmarshal(profile.Config, &minerConfig); err != nil { + respondWithMiningError(requestContext, ErrInvalidConfig("failed to parse profile config").WithCause(err)) return } // Validate config from profile to prevent shell injection and other issues - if err := config.Validate(); err != nil { - respondWithMiningError(c, ErrInvalidConfig("profile config validation failed").WithCause(err)) + if err := minerConfig.Validate(); err != nil { + respondWithMiningError(requestContext, ErrInvalidConfig("profile config validation failed").WithCause(err)) return } - miner, err := service.Manager.StartMiner(c.Request.Context(), profile.MinerType, &config) + miner, err := service.Manager.StartMiner(requestContext.Request.Context(), profile.MinerType, &minerConfig) if err != nil { - respondWithMiningError(c, ErrStartFailed(profile.Name).WithCause(err)) + respondWithMiningError(requestContext, ErrStartFailed(profile.Name).WithCause(err)) return } - c.JSON(http.StatusOK, miner) + requestContext.JSON(http.StatusOK, miner) } // handleStopMiner godoc @@ -1007,13 +1007,13 @@ func (service *Service) handleStartMinerWithProfile(c *gin.Context) { // @Param miner_name path string true "Miner Name" // @Success 200 {object} map[string]string // @Router /miners/{miner_name} [delete] -func (service *Service) handleStopMiner(c *gin.Context) { - minerName := c.Param("miner_name") - if err := service.Manager.StopMiner(c.Request.Context(), minerName); err != nil { - respondWithMiningError(c, ErrStopFailed(minerName).WithCause(err)) +func (service *Service) handleStopMiner(requestContext *gin.Context) { + minerName := requestContext.Param("miner_name") + if err := service.Manager.StopMiner(requestContext.Request.Context(), minerName); err != nil { + respondWithMiningError(requestContext, ErrStopFailed(minerName).WithCause(err)) return } - c.JSON(http.StatusOK, gin.H{"status": "stopped"}) + requestContext.JSON(http.StatusOK, gin.H{"status": "stopped"}) } // handleGetMinerStats godoc @@ -1024,19 +1024,19 @@ func (service *Service) handleStopMiner(c *gin.Context) { // @Param miner_name path string true "Miner Name" // @Success 200 {object} PerformanceMetrics // @Router /miners/{miner_name}/stats [get] -func (service *Service) handleGetMinerStats(c *gin.Context) { - minerName := c.Param("miner_name") +func (service *Service) handleGetMinerStats(requestContext *gin.Context) { + minerName := requestContext.Param("miner_name") miner, err := service.Manager.GetMiner(minerName) if err != nil { - respondWithMiningError(c, ErrMinerNotFound(minerName).WithCause(err)) + respondWithMiningError(requestContext, ErrMinerNotFound(minerName).WithCause(err)) return } - stats, err := miner.GetStats(c.Request.Context()) + stats, err := miner.GetStats(requestContext.Request.Context()) if err != nil { - respondWithMiningError(c, ErrInternal("failed to get miner stats").WithCause(err)) + respondWithMiningError(requestContext, ErrInternal("failed to get miner stats").WithCause(err)) return } - c.JSON(http.StatusOK, stats) + requestContext.JSON(http.StatusOK, stats) } // handleGetMinerHashrateHistory godoc @@ -1047,14 +1047,14 @@ func (service *Service) handleGetMinerStats(c *gin.Context) { // @Param miner_name path string true "Miner Name" // @Success 200 {array} HashratePoint // @Router /miners/{miner_name}/hashrate-history [get] -func (service *Service) handleGetMinerHashrateHistory(c *gin.Context) { - minerName := c.Param("miner_name") +func (service *Service) handleGetMinerHashrateHistory(requestContext *gin.Context) { + minerName := requestContext.Param("miner_name") history, err := service.Manager.GetMinerHashrateHistory(minerName) if err != nil { - respondWithMiningError(c, ErrMinerNotFound(minerName).WithCause(err)) + respondWithMiningError(requestContext, ErrMinerNotFound(minerName).WithCause(err)) return } - c.JSON(http.StatusOK, history) + requestContext.JSON(http.StatusOK, history) } // handleGetMinerLogs godoc @@ -1065,11 +1065,11 @@ func (service *Service) handleGetMinerHashrateHistory(c *gin.Context) { // @Param miner_name path string true "Miner Name" // @Success 200 {array} string "Base64 encoded log lines" // @Router /miners/{miner_name}/logs [get] -func (service *Service) handleGetMinerLogs(c *gin.Context) { - minerName := c.Param("miner_name") +func (service *Service) handleGetMinerLogs(requestContext *gin.Context) { + minerName := requestContext.Param("miner_name") miner, err := service.Manager.GetMiner(minerName) if err != nil { - respondWithMiningError(c, ErrMinerNotFound(minerName).WithCause(err)) + respondWithMiningError(requestContext, ErrMinerNotFound(minerName).WithCause(err)) return } logs := miner.GetLogs() @@ -1078,10 +1078,10 @@ func (service *Service) handleGetMinerLogs(c *gin.Context) { for i, line := range logs { encodedLogs[i] = base64.StdEncoding.EncodeToString([]byte(line)) } - c.JSON(http.StatusOK, encodedLogs) + requestContext.JSON(http.StatusOK, encodedLogs) } -// c.ShouldBindJSON(&StdinInput{Input: "h"}) // `h` prints hash rate and `p` pauses mining. +// requestContext.ShouldBindJSON(&StdinInput{Input: "h"}) // `h` prints hash rate and `p` pauses mining. type StdinInput struct { Input string `json:"input" binding:"required"` } @@ -1098,26 +1098,26 @@ type StdinInput struct { // @Failure 400 {object} map[string]string // @Failure 404 {object} map[string]string // @Router /miners/{miner_name}/stdin [post] -func (service *Service) handleMinerStdin(c *gin.Context) { - minerName := c.Param("miner_name") +func (service *Service) handleMinerStdin(requestContext *gin.Context) { + minerName := requestContext.Param("miner_name") miner, err := service.Manager.GetMiner(minerName) if err != nil { - respondWithError(c, http.StatusNotFound, ErrCodeMinerNotFound, "miner not found", err.Error()) + respondWithError(requestContext, http.StatusNotFound, ErrCodeMinerNotFound, "miner not found", err.Error()) return } var input StdinInput - if err := c.ShouldBindJSON(&input); err != nil { - respondWithMiningError(c, ErrInvalidConfig("invalid input format").WithCause(err)) + if err := requestContext.ShouldBindJSON(&input); err != nil { + respondWithMiningError(requestContext, ErrInvalidConfig("invalid input format").WithCause(err)) return } if err := miner.WriteStdin(input.Input); err != nil { - respondWithMiningError(c, ErrInternal("failed to write to stdin").WithCause(err)) + respondWithMiningError(requestContext, ErrInternal("failed to write to stdin").WithCause(err)) return } - c.JSON(http.StatusOK, gin.H{"status": "sent", "input": input.Input}) + requestContext.JSON(http.StatusOK, gin.H{"status": "sent", "input": input.Input}) } // handleListProfiles godoc @@ -1127,9 +1127,9 @@ func (service *Service) handleMinerStdin(c *gin.Context) { // @Produce json // @Success 200 {array} MiningProfile // @Router /profiles [get] -func (service *Service) handleListProfiles(c *gin.Context) { +func (service *Service) handleListProfiles(requestContext *gin.Context) { profiles := service.ProfileManager.GetAllProfiles() - c.JSON(http.StatusOK, profiles) + requestContext.JSON(http.StatusOK, profiles) } // handleCreateProfile godoc @@ -1142,30 +1142,30 @@ func (service *Service) handleListProfiles(c *gin.Context) { // @Success 201 {object} MiningProfile // @Failure 400 {object} APIError "Invalid profile data" // @Router /profiles [post] -func (service *Service) handleCreateProfile(c *gin.Context) { +func (service *Service) handleCreateProfile(requestContext *gin.Context) { var profile MiningProfile - if err := c.ShouldBindJSON(&profile); err != nil { - respondWithError(c, http.StatusBadRequest, ErrCodeInvalidInput, "invalid profile data", err.Error()) + if err := requestContext.ShouldBindJSON(&profile); err != nil { + respondWithError(requestContext, http.StatusBadRequest, ErrCodeInvalidInput, "invalid profile data", err.Error()) return } // Validate required fields if profile.Name == "" { - respondWithError(c, http.StatusBadRequest, ErrCodeInvalidInput, "profile name is required", "") + respondWithError(requestContext, http.StatusBadRequest, ErrCodeInvalidInput, "profile name is required", "") return } if profile.MinerType == "" { - respondWithError(c, http.StatusBadRequest, ErrCodeInvalidInput, "miner type is required", "") + respondWithError(requestContext, http.StatusBadRequest, ErrCodeInvalidInput, "miner type is required", "") return } createdProfile, err := service.ProfileManager.CreateProfile(&profile) if err != nil { - respondWithError(c, http.StatusInternalServerError, ErrCodeInternalError, "failed to create profile", err.Error()) + respondWithError(requestContext, http.StatusInternalServerError, ErrCodeInternalError, "failed to create profile", err.Error()) return } - c.JSON(http.StatusCreated, createdProfile) + requestContext.JSON(http.StatusCreated, createdProfile) } // handleGetProfile godoc @@ -1176,14 +1176,14 @@ func (service *Service) handleCreateProfile(c *gin.Context) { // @Param id path string true "Profile ID" // @Success 200 {object} MiningProfile // @Router /profiles/{id} [get] -func (service *Service) handleGetProfile(c *gin.Context) { - profileID := c.Param("id") +func (service *Service) handleGetProfile(requestContext *gin.Context) { + profileID := requestContext.Param("id") profile, exists := service.ProfileManager.GetProfile(profileID) if !exists { - respondWithError(c, http.StatusNotFound, ErrCodeProfileNotFound, "profile not found", "") + respondWithError(requestContext, http.StatusNotFound, ErrCodeProfileNotFound, "profile not found", "") return } - c.JSON(http.StatusOK, profile) + requestContext.JSON(http.StatusOK, profile) } // handleUpdateProfile godoc @@ -1197,11 +1197,11 @@ func (service *Service) handleGetProfile(c *gin.Context) { // @Success 200 {object} MiningProfile // @Failure 404 {object} APIError "Profile not found" // @Router /profiles/{id} [put] -func (service *Service) handleUpdateProfile(c *gin.Context) { - profileID := c.Param("id") +func (service *Service) handleUpdateProfile(requestContext *gin.Context) { + profileID := requestContext.Param("id") var profile MiningProfile - if err := c.ShouldBindJSON(&profile); err != nil { - respondWithError(c, http.StatusBadRequest, ErrCodeInvalidInput, "invalid profile data", err.Error()) + if err := requestContext.ShouldBindJSON(&profile); err != nil { + respondWithError(requestContext, http.StatusBadRequest, ErrCodeInvalidInput, "invalid profile data", err.Error()) return } profile.ID = profileID @@ -1209,13 +1209,13 @@ func (service *Service) handleUpdateProfile(c *gin.Context) { if err := service.ProfileManager.UpdateProfile(&profile); err != nil { // Check if error is "not found" if strings.Contains(err.Error(), "not found") { - respondWithError(c, http.StatusNotFound, ErrCodeProfileNotFound, "profile not found", err.Error()) + respondWithError(requestContext, http.StatusNotFound, ErrCodeProfileNotFound, "profile not found", err.Error()) return } - respondWithError(c, http.StatusInternalServerError, ErrCodeInternalError, "failed to update profile", err.Error()) + respondWithError(requestContext, http.StatusInternalServerError, ErrCodeInternalError, "failed to update profile", err.Error()) return } - c.JSON(http.StatusOK, profile) + requestContext.JSON(http.StatusOK, profile) } // handleDeleteProfile godoc @@ -1226,18 +1226,18 @@ func (service *Service) handleUpdateProfile(c *gin.Context) { // @Param id path string true "Profile ID" // @Success 200 {object} map[string]string // @Router /profiles/{id} [delete] -func (service *Service) handleDeleteProfile(c *gin.Context) { - profileID := c.Param("id") +func (service *Service) handleDeleteProfile(requestContext *gin.Context) { + profileID := requestContext.Param("id") if err := service.ProfileManager.DeleteProfile(profileID); err != nil { // Make DELETE idempotent - if profile doesn't exist, still return success if strings.Contains(err.Error(), "not found") { - c.JSON(http.StatusOK, gin.H{"status": "profile deleted"}) + requestContext.JSON(http.StatusOK, gin.H{"status": "profile deleted"}) return } - respondWithError(c, http.StatusInternalServerError, ErrCodeInternalError, "failed to delete profile", err.Error()) + respondWithError(requestContext, http.StatusInternalServerError, ErrCodeInternalError, "failed to delete profile", err.Error()) return } - c.JSON(http.StatusOK, gin.H{"status": "profile deleted"}) + requestContext.JSON(http.StatusOK, gin.H{"status": "profile deleted"}) } // handleHistoryStatus godoc @@ -1247,15 +1247,15 @@ func (service *Service) handleDeleteProfile(c *gin.Context) { // @Produce json // @Success 200 {object} map[string]interface{} // @Router /history/status [get] -func (service *Service) handleHistoryStatus(c *gin.Context) { +func (service *Service) handleHistoryStatus(requestContext *gin.Context) { if manager, ok := service.Manager.(*Manager); ok { - c.JSON(http.StatusOK, gin.H{ + requestContext.JSON(http.StatusOK, gin.H{ "enabled": manager.IsDatabaseEnabled(), "retentionDays": manager.databaseRetention, }) return } - c.JSON(http.StatusOK, gin.H{"enabled": false, "error": "manager type not supported"}) + requestContext.JSON(http.StatusOK, gin.H{"enabled": false, "error": "manager type not supported"}) } // handleAllMinersHistoricalStats godoc @@ -1265,20 +1265,20 @@ func (service *Service) handleHistoryStatus(c *gin.Context) { // @Produce json // @Success 200 {array} database.HashrateStats // @Router /history/miners [get] -func (service *Service) handleAllMinersHistoricalStats(c *gin.Context) { +func (service *Service) handleAllMinersHistoricalStats(requestContext *gin.Context) { manager, ok := service.Manager.(*Manager) if !ok { - respondWithMiningError(c, ErrInternal("manager type not supported")) + respondWithMiningError(requestContext, ErrInternal("manager type not supported")) return } stats, err := manager.GetAllMinerHistoricalStats() if err != nil { - respondWithMiningError(c, ErrDatabaseError("get historical stats").WithCause(err)) + respondWithMiningError(requestContext, ErrDatabaseError("get historical stats").WithCause(err)) return } - c.JSON(http.StatusOK, stats) + requestContext.JSON(http.StatusOK, stats) } // handleMinerHistoricalStats godoc @@ -1289,26 +1289,26 @@ func (service *Service) handleAllMinersHistoricalStats(c *gin.Context) { // @Param miner_name path string true "Miner Name" // @Success 200 {object} database.HashrateStats // @Router /history/miners/{miner_name} [get] -func (service *Service) handleMinerHistoricalStats(c *gin.Context) { - minerName := c.Param("miner_name") +func (service *Service) handleMinerHistoricalStats(requestContext *gin.Context) { + minerName := requestContext.Param("miner_name") manager, ok := service.Manager.(*Manager) if !ok { - respondWithMiningError(c, ErrInternal("manager type not supported")) + respondWithMiningError(requestContext, ErrInternal("manager type not supported")) return } stats, err := manager.GetMinerHistoricalStats(minerName) if err != nil { - respondWithMiningError(c, ErrDatabaseError("get miner stats").WithCause(err)) + respondWithMiningError(requestContext, ErrDatabaseError("get miner stats").WithCause(err)) return } if stats == nil { - respondWithMiningError(c, ErrMinerNotFound(minerName).WithDetails("no historical data found")) + respondWithMiningError(requestContext, ErrMinerNotFound(minerName).WithDetails("no historical data found")) return } - c.JSON(http.StatusOK, stats) + requestContext.JSON(http.StatusOK, stats) } // handleMinerHistoricalHashrate godoc @@ -1321,11 +1321,11 @@ func (service *Service) handleMinerHistoricalStats(c *gin.Context) { // @Param until query string false "End time (RFC3339 format)" // @Success 200 {array} HashratePoint // @Router /history/miners/{miner_name}/hashrate [get] -func (service *Service) handleMinerHistoricalHashrate(c *gin.Context) { - minerName := c.Param("miner_name") +func (service *Service) handleMinerHistoricalHashrate(requestContext *gin.Context) { + minerName := requestContext.Param("miner_name") manager, ok := service.Manager.(*Manager) if !ok { - respondWithMiningError(c, ErrInternal("manager type not supported")) + respondWithMiningError(requestContext, ErrInternal("manager type not supported")) return } @@ -1333,12 +1333,12 @@ func (service *Service) handleMinerHistoricalHashrate(c *gin.Context) { until := time.Now() since := until.Add(-24 * time.Hour) - if sinceStr := c.Query("since"); sinceStr != "" { + if sinceStr := requestContext.Query("since"); sinceStr != "" { if t, err := time.Parse(time.RFC3339, sinceStr); err == nil { since = t } } - if untilStr := c.Query("until"); untilStr != "" { + if untilStr := requestContext.Query("until"); untilStr != "" { if t, err := time.Parse(time.RFC3339, untilStr); err == nil { until = t } @@ -1346,11 +1346,11 @@ func (service *Service) handleMinerHistoricalHashrate(c *gin.Context) { history, err := manager.GetMinerHistoricalHashrate(minerName, since, until) if err != nil { - respondWithMiningError(c, ErrDatabaseError("get hashrate history").WithCause(err)) + respondWithMiningError(requestContext, ErrDatabaseError("get hashrate history").WithCause(err)) return } - c.JSON(http.StatusOK, history) + requestContext.JSON(http.StatusOK, history) } // handleWebSocketEvents godoc @@ -1360,19 +1360,19 @@ func (service *Service) handleMinerHistoricalHashrate(c *gin.Context) { // @Tags websocket // @Success 101 {string} string "Switching Protocols" // @Router /ws/events [get] -func (service *Service) handleWebSocketEvents(c *gin.Context) { - conn, err := wsUpgrader.Upgrade(c.Writer, c.Request, nil) +func (service *Service) handleWebSocketEvents(requestContext *gin.Context) { + conn, err := wsUpgrader.Upgrade(requestContext.Writer, requestContext.Request, nil) if err != nil { logging.Error("failed to upgrade WebSocket connection", logging.Fields{"error": err}) return } - logging.Info("new WebSocket connection", logging.Fields{"remote": c.Request.RemoteAddr}) + logging.Info("new WebSocket connection", logging.Fields{"remote": requestContext.Request.RemoteAddr}) // Only record connection after successful registration to avoid metrics race if service.EventHub.ServeWs(conn) { RecordWSConnection(true) } else { - logging.Warn("WebSocket connection rejected", logging.Fields{"remote": c.Request.RemoteAddr, "reason": "limit reached"}) + logging.Warn("WebSocket connection rejected", logging.Fields{"remote": requestContext.Request.RemoteAddr, "reason": "limit reached"}) } } @@ -1383,6 +1383,6 @@ func (service *Service) handleWebSocketEvents(c *gin.Context) { // @Produce json // @Success 200 {object} map[string]interface{} // @Router /metrics [get] -func (service *Service) handleMetrics(c *gin.Context) { - c.JSON(http.StatusOK, GetMetricsSnapshot()) +func (service *Service) handleMetrics(requestContext *gin.Context) { + requestContext.JSON(http.StatusOK, GetMetricsSnapshot()) }