AX: standardize mining handler naming
This commit is contained in:
parent
aea1fc1d03
commit
757e0a9ce4
6 changed files with 279 additions and 279 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue