diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..04456b4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,34 @@ +# Repository Guidelines + +## AX Standard + +This repository is being upgraded toward the Core Agent Experience standard defined in `/home/claude/Code/core/plans/rfc/core/RFC-CORE-008-AGENT-EXPERIENCE.md`. + +Apply these rules by default: + +- Prefer explicit names over short names. Use `manager`, `service`, `config`, and `request` instead of repo-local abbreviations like `mgr`, `svc`, and `cfg`. +- Write comments as concrete usage examples with realistic values. +- Keep paths and filenames descriptive so intent is obvious before opening a file. +- Preserve one-way dependency flow: foundational packages in `pkg/` should not depend on CLI wrappers in `cmd/`. +- Make repeated shapes predictable. Reuse existing config structs, route patterns, and test helpers instead of inventing parallel forms. + +## Go Project Shape + +- `cmd/mining/` contains Cobra CLI entrypoints. +- `pkg/mining/` contains miner orchestration, REST API, profiles, auth, and service container logic. +- `pkg/node/` contains peer, protocol, transport, and worker logic. +- `pkg/database/` contains persistence and hashrate storage. +- `cmd/desktop/mining-desktop/` contains the Wails desktop app wrapper. + +## Working Rules + +- Keep changes behavioral-safe unless the task explicitly asks for feature work. +- Prefer focused AX upgrades in high-traffic files such as `pkg/mining/service.go`, `pkg/mining/manager.go`, and `cmd/mining/cmd/*.go`. +- Run `gofmt` on edited Go files. +- Validate targeted changes first with package-level tests before broader runs. + +## Verification + +- `go test ./pkg/mining/...` +- `go test ./cmd/mining/...` +- `make test-go` diff --git a/pkg/logging/logger.go b/pkg/logging/logger.go index 15977a6..1d1e854 100644 --- a/pkg/logging/logger.go +++ b/pkg/logging/logger.go @@ -106,7 +106,7 @@ func (logger *Logger) GetLevel() Level { type Fields map[string]interface{} // logger.log(LevelInfo, "started", Fields{"miner": "xmrig"}) -func (logger *Logger) log(level Level, msg string, fields Fields) { +func (logger *Logger) log(level Level, message string, fields Fields) { logger.mutex.Lock() defer logger.mutex.Unlock() @@ -129,7 +129,7 @@ func (logger *Logger) log(level Level, msg string, fields Fields) { } builder.WriteString(" ") - builder.WriteString(msg) + builder.WriteString(message) // Add fields if present if len(fields) > 0 { @@ -147,23 +147,23 @@ func (logger *Logger) log(level Level, msg string, fields Fields) { } // logger.Debug("hashrate collected", logging.Fields{"rate": 1234}) -func (logger *Logger) Debug(msg string, fields ...Fields) { - logger.log(LevelDebug, msg, mergeFields(fields)) +func (logger *Logger) Debug(message string, fields ...Fields) { + logger.log(LevelDebug, message, mergeFields(fields)) } // logger.Info("miner started", logging.Fields{"miner": "xmrig"}) -func (logger *Logger) Info(msg string, fields ...Fields) { - logger.log(LevelInfo, msg, mergeFields(fields)) +func (logger *Logger) Info(message string, fields ...Fields) { + logger.log(LevelInfo, message, mergeFields(fields)) } // logger.Warn("hashrate drop", logging.Fields{"current": 500, "min": 1000}) -func (logger *Logger) Warn(msg string, fields ...Fields) { - logger.log(LevelWarn, msg, mergeFields(fields)) +func (logger *Logger) Warn(message string, fields ...Fields) { + logger.log(LevelWarn, message, mergeFields(fields)) } // logger.Error("miner crashed", logging.Fields{"code": -1, "miner": "xmrig"}) -func (logger *Logger) Error(msg string, fields ...Fields) { - logger.log(LevelError, msg, mergeFields(fields)) +func (logger *Logger) Error(message string, fields ...Fields) { + logger.log(LevelError, message, mergeFields(fields)) } // logger.Debugf("collected %d hashrate points for %s", len(points), minerName) @@ -232,23 +232,23 @@ func SetGlobalLevel(level Level) { // Global convenience functions that use the global logger // logging.Debug("hashrate collected", logging.Fields{"rate": 1234, "miner": "xmrig"}) -func Debug(msg string, fields ...Fields) { - GetGlobal().Debug(msg, fields...) +func Debug(message string, fields ...Fields) { + GetGlobal().Debug(message, fields...) } // logging.Info("miner started", logging.Fields{"miner": "xmrig", "pool": "pool.lthn.io"}) -func Info(msg string, fields ...Fields) { - GetGlobal().Info(msg, fields...) +func Info(message string, fields ...Fields) { + GetGlobal().Info(message, fields...) } // logging.Warn("hashrate dropped below threshold", logging.Fields{"current": 500, "min": 1000}) -func Warn(msg string, fields ...Fields) { - GetGlobal().Warn(msg, fields...) +func Warn(message string, fields ...Fields) { + GetGlobal().Warn(message, fields...) } // logging.Error("miner process exited unexpectedly", logging.Fields{"code": -1}) -func Error(msg string, fields ...Fields) { - GetGlobal().Error(msg, fields...) +func Error(message string, fields ...Fields) { + GetGlobal().Error(message, fields...) } // logging.Debugf("collected %d hashrate points for %s", len(points), minerName) diff --git a/pkg/mining/circuit_breaker.go b/pkg/mining/circuit_breaker.go index 321e627..8da101f 100644 --- a/pkg/mining/circuit_breaker.go +++ b/pkg/mining/circuit_breaker.go @@ -85,7 +85,7 @@ func (circuitBreaker *CircuitBreaker) State() CircuitState { } // result, err := circuitBreaker.Execute(func() (interface{}, error) { return fetchStats(ctx) }) -func (circuitBreaker *CircuitBreaker) Execute(fn func() (interface{}, error)) (interface{}, error) { +func (circuitBreaker *CircuitBreaker) Execute(operation func() (interface{}, error)) (interface{}, error) { if !circuitBreaker.allowRequest() { circuitBreaker.mutex.RLock() if circuitBreaker.cachedResult != nil && time.Since(circuitBreaker.lastCacheTime) < circuitBreaker.cacheDuration { @@ -101,7 +101,7 @@ func (circuitBreaker *CircuitBreaker) Execute(fn func() (interface{}, error)) (i return nil, ErrCircuitOpen } - result, err := fn() + result, err := operation() if err != nil { circuitBreaker.recordFailure() diff --git a/pkg/mining/miner.go b/pkg/mining/miner.go index 93e5061..c536fb5 100644 --- a/pkg/mining/miner.go +++ b/pkg/mining/miner.go @@ -251,20 +251,20 @@ func (b *BaseMiner) InstallFromURL(url string) error { defer os.Remove(tmpfile.Name()) defer tmpfile.Close() - resp, err := getHTTPClient().Get(url) + response, err := getHTTPClient().Get(url) if err != nil { return err } - defer resp.Body.Close() + defer response.Body.Close() - if resp.StatusCode != http.StatusOK { - _, _ = io.Copy(io.Discard, resp.Body) // Drain body to allow connection reuse (error ignored intentionally) - return ErrInstallFailed(b.ExecutableName).WithDetails("unexpected status code " + strconv.Itoa(resp.StatusCode)) + if response.StatusCode != http.StatusOK { + _, _ = io.Copy(io.Discard, response.Body) // Drain body to allow connection reuse (error ignored intentionally) + return ErrInstallFailed(b.ExecutableName).WithDetails("unexpected status code " + strconv.Itoa(response.StatusCode)) } - if _, err := io.Copy(tmpfile, resp.Body); err != nil { + if _, err := io.Copy(tmpfile, response.Body); err != nil { // Drain remaining body to allow connection reuse (error ignored intentionally) - _, _ = io.Copy(io.Discard, resp.Body) + _, _ = io.Copy(io.Discard, response.Body) return err } @@ -397,12 +397,12 @@ func (b *BaseMiner) CheckInstallation() (*InstallationDetails, error) { b.Path = filepath.Dir(binaryPath) cmd := exec.Command(binaryPath, "--version") - var out bytes.Buffer - cmd.Stdout = &out + var output bytes.Buffer + cmd.Stdout = &output if err := cmd.Run(); err != nil { b.Version = "Unknown (could not run executable)" } else { - fields := strings.Fields(out.String()) + fields := strings.Fields(output.String()) if len(fields) >= 2 { b.Version = fields[1] } else { diff --git a/pkg/mining/miner_factory_test.go b/pkg/mining/miner_factory_test.go index 7756359..079785e 100644 --- a/pkg/mining/miner_factory_test.go +++ b/pkg/mining/miner_factory_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -func TestMinerFactory_Create(t *testing.T) { +func TestMinerFactory_Create_Good(t *testing.T) { factory := NewMinerFactory() tests := []struct { @@ -40,7 +40,7 @@ func TestMinerFactory_Create(t *testing.T) { } } -func TestMinerFactory_IsSupported(t *testing.T) { +func TestMinerFactory_IsSupported_Good(t *testing.T) { factory := NewMinerFactory() tests := []struct { @@ -63,7 +63,7 @@ func TestMinerFactory_IsSupported(t *testing.T) { } } -func TestMinerFactory_ListTypes(t *testing.T) { +func TestMinerFactory_ListTypes_Good(t *testing.T) { factory := NewMinerFactory() types := factory.ListTypes() @@ -73,8 +73,8 @@ func TestMinerFactory_ListTypes(t *testing.T) { // Check that expected types are present typeMap := make(map[string]bool) - for _, typ := range types { - typeMap[typ] = true + for _, minerType := range types { + typeMap[minerType] = true } expectedTypes := []string{"xmrig", "tt-miner"} @@ -85,7 +85,7 @@ func TestMinerFactory_ListTypes(t *testing.T) { } } -func TestMinerFactory_Register(t *testing.T) { +func TestMinerFactory_Register_Good(t *testing.T) { factory := NewMinerFactory() // Register a custom miner type @@ -108,7 +108,7 @@ func TestMinerFactory_Register(t *testing.T) { } } -func TestMinerFactory_RegisterAlias(t *testing.T) { +func TestMinerFactory_RegisterAlias_Good(t *testing.T) { factory := NewMinerFactory() // Register an alias for xmrig @@ -127,7 +127,7 @@ func TestMinerFactory_RegisterAlias(t *testing.T) { } } -func TestGlobalFactory_CreateMiner(t *testing.T) { +func TestGlobalFactory_CreateMiner_Good(t *testing.T) { // Test global convenience functions miner, err := CreateMiner("xmrig") if err != nil { @@ -138,7 +138,7 @@ func TestGlobalFactory_CreateMiner(t *testing.T) { } } -func TestGlobalFactory_IsMinerSupported(t *testing.T) { +func TestGlobalFactory_IsMinerSupported_Good(t *testing.T) { if !IsMinerSupported("xmrig") { t.Error("xmrig should be supported") } @@ -147,7 +147,7 @@ func TestGlobalFactory_IsMinerSupported(t *testing.T) { } } -func TestGlobalFactory_ListMinerTypes(t *testing.T) { +func TestGlobalFactory_ListMinerTypes_Good(t *testing.T) { types := ListMinerTypes() if len(types) < 2 { t.Errorf("ListMinerTypes() returned %d types, expected at least 2", len(types)) diff --git a/pkg/mining/mining.go b/pkg/mining/mining.go index b0ab6bb..b529490 100644 --- a/pkg/mining/mining.go +++ b/pkg/mining/mining.go @@ -82,7 +82,7 @@ type Config struct { Keepalive bool `json:"keepalive,omitempty"` Nicehash bool `json:"nicehash,omitempty"` RigID string `json:"rigId,omitempty"` - TLSSingerprint string `json:"tlsFingerprint,omitempty"` + TLSFingerprint string `json:"tlsFingerprint,omitempty"` Retries int `json:"retries,omitempty"` RetryPause int `json:"retryPause,omitempty"` UserAgent string `json:"userAgent,omitempty"` diff --git a/pkg/mining/node_service.go b/pkg/mining/node_service.go index bc8ac43..142e47b 100644 --- a/pkg/mining/node_service.go +++ b/pkg/mining/node_service.go @@ -9,8 +9,8 @@ import ( "github.com/gin-gonic/gin" ) -// ns, err := NewNodeService() -// ns.SetupRoutes(router.Group("/api/v1/mining")) +// nodeService, err := NewNodeService() +// nodeService.SetupRoutes(router.Group("/api/v1/mining")) type NodeService struct { nodeManager *node.NodeManager peerRegistry *node.PeerRegistry @@ -19,7 +19,7 @@ type NodeService struct { worker *node.Worker } -// ns, err := NewNodeService() // initialises node manager, peer registry, transport, controller, worker +// nodeService, err := NewNodeService() // initialises node manager, peer registry, transport, controller, worker func NewNodeService() (*NodeService, error) { nodeManager, err := node.NewNodeManager() if err != nil { @@ -46,53 +46,53 @@ func NewNodeService() (*NodeService, error) { return nodeService, nil } -// ns.SetupRoutes(router.Group("/api/v1/mining")) // registers /node, /peers, /remote route groups -func (ns *NodeService) SetupRoutes(router *gin.RouterGroup) { +// service.SetupRoutes(router.Group("/api/v1/mining")) // registers /node, /peers, /remote route groups +func (nodeService *NodeService) SetupRoutes(router *gin.RouterGroup) { // Node identity endpoints nodeGroup := router.Group("/node") { - nodeGroup.GET("/info", ns.handleNodeInfo) - nodeGroup.POST("/init", ns.handleNodeInit) + nodeGroup.GET("/info", nodeService.handleNodeInfo) + nodeGroup.POST("/init", nodeService.handleNodeInit) } // Peer management endpoints peerGroup := router.Group("/peers") { - peerGroup.GET("", ns.handleListPeers) - peerGroup.POST("", ns.handleAddPeer) - peerGroup.GET("/:id", ns.handleGetPeer) - peerGroup.DELETE("/:id", ns.handleRemovePeer) - peerGroup.POST("/:id/ping", ns.handlePingPeer) - peerGroup.POST("/:id/connect", ns.handleConnectPeer) - peerGroup.POST("/:id/disconnect", ns.handleDisconnectPeer) + peerGroup.GET("", nodeService.handleListPeers) + peerGroup.POST("", nodeService.handleAddPeer) + peerGroup.GET("/:id", nodeService.handleGetPeer) + peerGroup.DELETE("/:id", nodeService.handleRemovePeer) + peerGroup.POST("/:id/ping", nodeService.handlePingPeer) + peerGroup.POST("/:id/connect", nodeService.handleConnectPeer) + peerGroup.POST("/:id/disconnect", nodeService.handleDisconnectPeer) // Allowlist management - peerGroup.GET("/auth/mode", ns.handleGetAuthMode) - peerGroup.PUT("/auth/mode", ns.handleSetAuthMode) - peerGroup.GET("/auth/allowlist", ns.handleListAllowlist) - peerGroup.POST("/auth/allowlist", ns.handleAddToAllowlist) - peerGroup.DELETE("/auth/allowlist/:key", ns.handleRemoveFromAllowlist) + peerGroup.GET("/auth/mode", nodeService.handleGetAuthMode) + peerGroup.PUT("/auth/mode", nodeService.handleSetAuthMode) + peerGroup.GET("/auth/allowlist", nodeService.handleListAllowlist) + peerGroup.POST("/auth/allowlist", nodeService.handleAddToAllowlist) + peerGroup.DELETE("/auth/allowlist/:key", nodeService.handleRemoveFromAllowlist) } // Remote operations endpoints remoteGroup := router.Group("/remote") { - remoteGroup.GET("/stats", ns.handleRemoteStats) - remoteGroup.GET("/:peerId/stats", ns.handlePeerStats) - remoteGroup.POST("/:peerId/start", ns.handleRemoteStart) - remoteGroup.POST("/:peerId/stop", ns.handleRemoteStop) - remoteGroup.GET("/:peerId/logs/:miner", ns.handleRemoteLogs) + remoteGroup.GET("/stats", nodeService.handleRemoteStats) + remoteGroup.GET("/:peerId/stats", nodeService.handlePeerStats) + remoteGroup.POST("/:peerId/start", nodeService.handleRemoteStart) + remoteGroup.POST("/:peerId/stop", nodeService.handleRemoteStop) + remoteGroup.GET("/:peerId/logs/:miner", nodeService.handleRemoteLogs) } } -// if err := ns.StartTransport(); err != nil { log.Fatal(err) } // starts WebSocket listener on configured port -func (ns *NodeService) StartTransport() error { - return ns.transport.Start() +// if err := nodeService.StartTransport(); err != nil { log.Fatal(err) } // starts WebSocket listener on configured port +func (nodeService *NodeService) StartTransport() error { + return nodeService.transport.Start() } -// defer ns.StopTransport() // gracefully shuts down WebSocket listener and closes peer connections -func (ns *NodeService) StopTransport() error { - return ns.transport.Stop() +// defer nodeService.StopTransport() // gracefully shuts down WebSocket listener and closes peer connections +func (nodeService *NodeService) StopTransport() error { + return nodeService.transport.Stop() } // response := NodeInfoResponse{HasIdentity: true, Identity: identity, RegisteredPeers: 3, ConnectedPeers: 1} @@ -110,15 +110,15 @@ type NodeInfoResponse struct { // @Produce json // @Success 200 {object} NodeInfoResponse // @Router /node/info [get] -func (ns *NodeService) handleNodeInfo(c *gin.Context) { +func (nodeService *NodeService) handleNodeInfo(c *gin.Context) { response := NodeInfoResponse{ - HasIdentity: ns.nodeManager.HasIdentity(), - RegisteredPeers: ns.peerRegistry.Count(), - ConnectedPeers: len(ns.peerRegistry.GetConnectedPeers()), + HasIdentity: nodeService.nodeManager.HasIdentity(), + RegisteredPeers: nodeService.peerRegistry.Count(), + ConnectedPeers: len(nodeService.peerRegistry.GetConnectedPeers()), } - if ns.nodeManager.HasIdentity() { - response.Identity = ns.nodeManager.GetIdentity() + if nodeService.nodeManager.HasIdentity() { + response.Identity = nodeService.nodeManager.GetIdentity() } c.JSON(http.StatusOK, response) @@ -139,20 +139,20 @@ type NodeInitRequest struct { // @Param request body NodeInitRequest true "Node initialization parameters" // @Success 200 {object} node.NodeIdentity // @Router /node/init [post] -func (ns *NodeService) handleNodeInit(c *gin.Context) { - var req NodeInitRequest - if err := c.ShouldBindJSON(&req); err != nil { +func (nodeService *NodeService) handleNodeInit(c *gin.Context) { + var request NodeInitRequest + if err := c.ShouldBindJSON(&request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - if ns.nodeManager.HasIdentity() { + if nodeService.nodeManager.HasIdentity() { c.JSON(http.StatusConflict, gin.H{"error": "node identity already exists"}) return } role := node.RoleDual - switch req.Role { + switch request.Role { case "controller": role = node.RoleController case "worker": @@ -164,12 +164,12 @@ func (ns *NodeService) handleNodeInit(c *gin.Context) { return } - if err := ns.nodeManager.GenerateIdentity(req.Name, role); err != nil { + if err := nodeService.nodeManager.GenerateIdentity(request.Name, role); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } - c.JSON(http.StatusOK, ns.nodeManager.GetIdentity()) + c.JSON(http.StatusOK, nodeService.nodeManager.GetIdentity()) } // handleListPeers godoc @@ -179,8 +179,8 @@ func (ns *NodeService) handleNodeInit(c *gin.Context) { // @Produce json // @Success 200 {array} node.Peer // @Router /peers [get] -func (ns *NodeService) handleListPeers(c *gin.Context) { - peers := ns.peerRegistry.ListPeers() +func (nodeService *NodeService) handleListPeers(c *gin.Context) { + peers := nodeService.peerRegistry.ListPeers() c.JSON(http.StatusOK, peers) } @@ -199,22 +199,22 @@ type AddPeerRequest struct { // @Param request body AddPeerRequest true "Peer information" // @Success 201 {object} node.Peer // @Router /peers [post] -func (ns *NodeService) handleAddPeer(c *gin.Context) { - var req AddPeerRequest - if err := c.ShouldBindJSON(&req); err != nil { +func (nodeService *NodeService) handleAddPeer(c *gin.Context) { + var request AddPeerRequest + if err := c.ShouldBindJSON(&request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } peer := &node.Peer{ - ID: "pending-" + req.Address, // Will be updated on handshake - Name: req.Name, - Address: req.Address, + ID: "pending-" + request.Address, // Will be updated on handshake + Name: request.Name, + Address: request.Address, Role: node.RoleDual, Score: 50, } - if err := ns.peerRegistry.AddPeer(peer); err != nil { + if err := nodeService.peerRegistry.AddPeer(peer); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } @@ -230,9 +230,9 @@ func (ns *NodeService) handleAddPeer(c *gin.Context) { // @Param id path string true "Peer ID" // @Success 200 {object} node.Peer // @Router /peers/{id} [get] -func (ns *NodeService) handleGetPeer(c *gin.Context) { +func (nodeService *NodeService) handleGetPeer(c *gin.Context) { peerID := c.Param("id") - peer := ns.peerRegistry.GetPeer(peerID) + peer := nodeService.peerRegistry.GetPeer(peerID) if peer == nil { c.JSON(http.StatusNotFound, gin.H{"error": "peer not found"}) return @@ -248,9 +248,9 @@ func (ns *NodeService) handleGetPeer(c *gin.Context) { // @Param id path string true "Peer ID" // @Success 200 {object} map[string]string // @Router /peers/{id} [delete] -func (ns *NodeService) handleRemovePeer(c *gin.Context) { +func (nodeService *NodeService) handleRemovePeer(c *gin.Context) { peerID := c.Param("id") - if err := ns.peerRegistry.RemovePeer(peerID); err != nil { + if err := nodeService.peerRegistry.RemovePeer(peerID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } @@ -266,9 +266,9 @@ func (ns *NodeService) handleRemovePeer(c *gin.Context) { // @Success 200 {object} map[string]float64 // @Failure 404 {object} APIError "Peer not found" // @Router /peers/{id}/ping [post] -func (ns *NodeService) handlePingPeer(c *gin.Context) { +func (nodeService *NodeService) handlePingPeer(c *gin.Context) { peerID := c.Param("id") - rtt, err := ns.controller.PingPeer(peerID) + 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()) @@ -289,9 +289,9 @@ func (ns *NodeService) handlePingPeer(c *gin.Context) { // @Success 200 {object} map[string]string // @Failure 404 {object} APIError "Peer not found" // @Router /peers/{id}/connect [post] -func (ns *NodeService) handleConnectPeer(c *gin.Context) { +func (nodeService *NodeService) handleConnectPeer(c *gin.Context) { peerID := c.Param("id") - if err := ns.controller.ConnectToPeer(peerID); err != nil { + 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()) return @@ -310,9 +310,9 @@ func (ns *NodeService) handleConnectPeer(c *gin.Context) { // @Param id path string true "Peer ID" // @Success 200 {object} map[string]string // @Router /peers/{id}/disconnect [post] -func (ns *NodeService) handleDisconnectPeer(c *gin.Context) { +func (nodeService *NodeService) handleDisconnectPeer(c *gin.Context) { peerID := c.Param("id") - if err := ns.controller.DisconnectFromPeer(peerID); err != nil { + 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"}) @@ -331,8 +331,8 @@ func (ns *NodeService) handleDisconnectPeer(c *gin.Context) { // @Produce json // @Success 200 {object} map[string]node.StatsPayload // @Router /remote/stats [get] -func (ns *NodeService) handleRemoteStats(c *gin.Context) { - stats := ns.controller.GetAllStats() +func (nodeService *NodeService) handleRemoteStats(c *gin.Context) { + stats := nodeService.controller.GetAllStats() c.JSON(http.StatusOK, stats) } @@ -344,9 +344,9 @@ func (ns *NodeService) handleRemoteStats(c *gin.Context) { // @Param peerId path string true "Peer ID" // @Success 200 {object} node.StatsPayload // @Router /remote/{peerId}/stats [get] -func (ns *NodeService) handlePeerStats(c *gin.Context) { +func (nodeService *NodeService) handlePeerStats(c *gin.Context) { peerID := c.Param("peerId") - stats, err := ns.controller.GetRemoteStats(peerID) + stats, err := nodeService.controller.GetRemoteStats(peerID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -371,15 +371,15 @@ type RemoteStartRequest struct { // @Param request body RemoteStartRequest true "Start parameters" // @Success 200 {object} map[string]string // @Router /remote/{peerId}/start [post] -func (ns *NodeService) handleRemoteStart(c *gin.Context) { +func (nodeService *NodeService) handleRemoteStart(c *gin.Context) { peerID := c.Param("peerId") - var req RemoteStartRequest - if err := c.ShouldBindJSON(&req); err != nil { + var request RemoteStartRequest + if err := c.ShouldBindJSON(&request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - if err := ns.controller.StartRemoteMiner(peerID, req.MinerType, req.ProfileID, req.Config); err != nil { + if err := nodeService.controller.StartRemoteMiner(peerID, request.MinerType, request.ProfileID, request.Config); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } @@ -401,15 +401,15 @@ type RemoteStopRequest struct { // @Param request body RemoteStopRequest true "Stop parameters" // @Success 200 {object} map[string]string // @Router /remote/{peerId}/stop [post] -func (ns *NodeService) handleRemoteStop(c *gin.Context) { +func (nodeService *NodeService) handleRemoteStop(c *gin.Context) { peerID := c.Param("peerId") - var req RemoteStopRequest - if err := c.ShouldBindJSON(&req); err != nil { + var request RemoteStopRequest + if err := c.ShouldBindJSON(&request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - if err := ns.controller.StopRemoteMiner(peerID, req.MinerName); err != nil { + if err := nodeService.controller.StopRemoteMiner(peerID, request.MinerName); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } @@ -426,7 +426,7 @@ func (ns *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 (ns *NodeService) handleRemoteLogs(c *gin.Context) { +func (nodeService *NodeService) handleRemoteLogs(c *gin.Context) { peerID := c.Param("peerId") minerName := c.Param("miner") lines := 100 @@ -440,7 +440,7 @@ func (ns *NodeService) handleRemoteLogs(c *gin.Context) { } } - logs, err := ns.controller.GetRemoteLogs(peerID, minerName, lines) + logs, err := nodeService.controller.GetRemoteLogs(peerID, minerName, lines) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return @@ -460,8 +460,8 @@ type AuthModeResponse struct { // @Produce json // @Success 200 {object} AuthModeResponse // @Router /peers/auth/mode [get] -func (ns *NodeService) handleGetAuthMode(c *gin.Context) { - mode := ns.peerRegistry.GetAuthMode() +func (nodeService *NodeService) handleGetAuthMode(c *gin.Context) { + mode := nodeService.peerRegistry.GetAuthMode() modeStr := "open" if mode == node.PeerAuthAllowlist { modeStr = "allowlist" @@ -484,15 +484,15 @@ type SetAuthModeRequest struct { // @Success 200 {object} AuthModeResponse // @Failure 400 {object} APIError "Invalid mode" // @Router /peers/auth/mode [put] -func (ns *NodeService) handleSetAuthMode(c *gin.Context) { - var req SetAuthModeRequest - if err := c.ShouldBindJSON(&req); err != nil { +func (nodeService *NodeService) handleSetAuthMode(c *gin.Context) { + var request SetAuthModeRequest + if err := c.ShouldBindJSON(&request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } var mode node.PeerAuthMode - switch req.Mode { + switch request.Mode { case "open": mode = node.PeerAuthOpen case "allowlist": @@ -502,8 +502,8 @@ func (ns *NodeService) handleSetAuthMode(c *gin.Context) { return } - ns.peerRegistry.SetAuthMode(mode) - c.JSON(http.StatusOK, AuthModeResponse{Mode: req.Mode}) + nodeService.peerRegistry.SetAuthMode(mode) + c.JSON(http.StatusOK, AuthModeResponse{Mode: request.Mode}) } // GET /peers/auth/allowlist → {"publicKeys": ["ed25519:abc...", "ed25519:def..."]} @@ -518,8 +518,8 @@ type AllowlistResponse struct { // @Produce json // @Success 200 {object} AllowlistResponse // @Router /peers/auth/allowlist [get] -func (ns *NodeService) handleListAllowlist(c *gin.Context) { - keys := ns.peerRegistry.ListAllowedPublicKeys() +func (nodeService *NodeService) handleListAllowlist(c *gin.Context) { + keys := nodeService.peerRegistry.ListAllowedPublicKeys() c.JSON(http.StatusOK, AllowlistResponse{PublicKeys: keys}) } @@ -538,19 +538,19 @@ type AddAllowlistRequest struct { // @Success 201 {object} map[string]string // @Failure 400 {object} APIError "Invalid request" // @Router /peers/auth/allowlist [post] -func (ns *NodeService) handleAddToAllowlist(c *gin.Context) { - var req AddAllowlistRequest - if err := c.ShouldBindJSON(&req); err != nil { +func (nodeService *NodeService) handleAddToAllowlist(c *gin.Context) { + var request AddAllowlistRequest + if err := c.ShouldBindJSON(&request); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } - if len(req.PublicKey) < 16 { + if len(request.PublicKey) < 16 { respondWithError(c, http.StatusBadRequest, "INVALID_KEY", "public key too short", "") return } - ns.peerRegistry.AllowPublicKey(req.PublicKey) + nodeService.peerRegistry.AllowPublicKey(request.PublicKey) c.JSON(http.StatusCreated, gin.H{"status": "added"}) } @@ -562,13 +562,13 @@ func (ns *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 (ns *NodeService) handleRemoveFromAllowlist(c *gin.Context) { +func (nodeService *NodeService) handleRemoveFromAllowlist(c *gin.Context) { key := c.Param("key") if key == "" { respondWithError(c, http.StatusBadRequest, "MISSING_KEY", "public key required", "") return } - ns.peerRegistry.RevokePublicKey(key) + nodeService.peerRegistry.RevokePublicKey(key) c.JSON(http.StatusOK, gin.H{"status": "removed"}) } diff --git a/pkg/mining/repository.go b/pkg/mining/repository.go index 8e1b3fc..28bc0a2 100644 --- a/pkg/mining/repository.go +++ b/pkg/mining/repository.go @@ -17,7 +17,7 @@ type Repository[T any] interface { Save(data T) error // repo.Update(func(d *T) error { d.Field = value; return nil }) - Update(fn func(*T) error) error + Update(modifier func(*T) error) error } // repo := NewFileRepository[MinersConfig](path, WithDefaults(defaultMinersConfig)) @@ -32,9 +32,9 @@ type FileRepository[T any] struct { type FileRepositoryOption[T any] func(*FileRepository[T]) // repo := NewFileRepository[MinersConfig](path, WithDefaults(defaultMinersConfig)) -func WithDefaults[T any](fn func() T) FileRepositoryOption[T] { +func WithDefaults[T any](defaultsProvider func() T) FileRepositoryOption[T] { return func(repo *FileRepository[T]) { - repo.defaults = fn + repo.defaults = defaultsProvider } } @@ -102,7 +102,7 @@ func (repository *FileRepository[T]) saveUnlocked(data T) error { // configuration.Miners = append(configuration.Miners, entry) // return nil // }) -func (repository *FileRepository[T]) Update(fn func(*T) error) error { +func (repository *FileRepository[T]) Update(modifier func(*T) error) error { repository.mutex.Lock() defer repository.mutex.Unlock() @@ -124,7 +124,7 @@ func (repository *FileRepository[T]) Update(fn func(*T) error) error { } // Apply modification - if err := fn(&data); err != nil { + if err := modifier(&data); err != nil { return err } diff --git a/pkg/mining/repository_test.go b/pkg/mining/repository_test.go index f285166..4a82c74 100644 --- a/pkg/mining/repository_test.go +++ b/pkg/mining/repository_test.go @@ -12,7 +12,7 @@ type testData struct { Value int `json:"value"` } -func TestFileRepository_Load(t *testing.T) { +func TestFileRepository_Load_Good(t *testing.T) { t.Run("NonExistentFile", func(t *testing.T) { tmpDir := t.TempDir() path := filepath.Join(tmpDir, "nonexistent.json") @@ -78,7 +78,7 @@ func TestFileRepository_Load(t *testing.T) { }) } -func TestFileRepository_Save(t *testing.T) { +func TestFileRepository_Save_Good(t *testing.T) { t.Run("NewFile", func(t *testing.T) { tmpDir := t.TempDir() path := filepath.Join(tmpDir, "subdir", "new.json") @@ -130,7 +130,7 @@ func TestFileRepository_Save(t *testing.T) { }) } -func TestFileRepository_Update(t *testing.T) { +func TestFileRepository_Update_Good(t *testing.T) { t.Run("UpdateExisting", func(t *testing.T) { tmpDir := t.TempDir() path := filepath.Join(tmpDir, "update.json") @@ -215,7 +215,7 @@ func TestFileRepository_Update(t *testing.T) { }) } -func TestFileRepository_Delete(t *testing.T) { +func TestFileRepository_Delete_Good(t *testing.T) { tmpDir := t.TempDir() path := filepath.Join(tmpDir, "delete.json") repo := NewFileRepository[testData](path) @@ -244,7 +244,7 @@ func TestFileRepository_Delete(t *testing.T) { } } -func TestFileRepository_Path(t *testing.T) { +func TestFileRepository_Path_Good(t *testing.T) { path := "/some/path/config.json" repo := NewFileRepository[testData](path) @@ -253,7 +253,7 @@ func TestFileRepository_Path(t *testing.T) { } } -func TestFileRepository_UpdateWithLoadError(t *testing.T) { +func TestFileRepository_UpdateWithLoadError_Bad(t *testing.T) { tmpDir := t.TempDir() path := filepath.Join(tmpDir, "corrupt.json") repo := NewFileRepository[testData](path) @@ -273,7 +273,7 @@ func TestFileRepository_UpdateWithLoadError(t *testing.T) { } } -func TestFileRepository_SaveToReadOnlyDirectory(t *testing.T) { +func TestFileRepository_SaveToReadOnlyDirectory_Bad(t *testing.T) { if os.Getuid() == 0 { t.Skip("Test skipped when running as root") } @@ -295,7 +295,7 @@ func TestFileRepository_SaveToReadOnlyDirectory(t *testing.T) { } } -func TestFileRepository_DeleteNonExistent(t *testing.T) { +func TestFileRepository_DeleteNonExistent_Good(t *testing.T) { tmpDir := t.TempDir() path := filepath.Join(tmpDir, "nonexistent.json") repo := NewFileRepository[testData](path) @@ -306,7 +306,7 @@ func TestFileRepository_DeleteNonExistent(t *testing.T) { } } -func TestFileRepository_ExistsOnInvalidPath(t *testing.T) { +func TestFileRepository_ExistsOnInvalidPath_Bad(t *testing.T) { // Use a path that definitely doesn't exist repo := NewFileRepository[testData]("/nonexistent/path/to/file.json") @@ -315,7 +315,7 @@ func TestFileRepository_ExistsOnInvalidPath(t *testing.T) { } } -func TestFileRepository_ConcurrentUpdates(t *testing.T) { +func TestFileRepository_ConcurrentUpdates_Ugly(t *testing.T) { tmpDir := t.TempDir() path := filepath.Join(tmpDir, "concurrent.json") repo := NewFileRepository[testData](path, WithDefaults(func() testData { @@ -354,8 +354,9 @@ func TestFileRepository_ConcurrentUpdates(t *testing.T) { } } -// Test with slice data -func TestFileRepository_SliceData(t *testing.T) { +// repo := NewFileRepository[[]item](path, WithDefaults(func() []item { return []item{} })) +// repo.Save(items); repo.Update(func(data *[]item) error { *data = append(*data, item{...}); return nil }) +func TestFileRepository_SliceData_Good(t *testing.T) { type item struct { ID string `json:"id"` Name string `json:"name"` diff --git a/pkg/mining/service.go b/pkg/mining/service.go index e64e8c0..9d374d1 100644 --- a/pkg/mining/service.go +++ b/pkg/mining/service.go @@ -73,7 +73,7 @@ func sanitizeErrorDetails(details string) string { // respondWithError(c, http.StatusNotFound, ErrCodeMinerNotFound, "xmrig not found", err.Error()) func respondWithError(c *gin.Context, status int, code string, message string, details string) { - apiErr := APIError{ + apiError := APIError{ Code: code, Message: message, Details: sanitizeErrorDetails(details), @@ -83,21 +83,21 @@ func respondWithError(c *gin.Context, status int, code string, message string, d // Add suggestions based on error code switch code { case ErrCodeMinerNotFound: - apiErr.Suggestion = "Check the miner name or install the miner first" + apiError.Suggestion = "Check the miner name or install the miner first" case ErrCodeProfileNotFound: - apiErr.Suggestion = "Create a new profile or check the profile ID" + apiError.Suggestion = "Create a new profile or check the profile ID" case ErrCodeInstallFailed: - apiErr.Suggestion = "Check your internet connection and try again" + apiError.Suggestion = "Check your internet connection and try again" case ErrCodeStartFailed: - apiErr.Suggestion = "Check the miner configuration and logs" + apiError.Suggestion = "Check the miner configuration and logs" case ErrCodeInvalidInput: - apiErr.Suggestion = "Verify the request body matches the expected format" + apiError.Suggestion = "Verify the request body matches the expected format" case ErrCodeServiceUnavailable: - apiErr.Suggestion = "The service is temporarily unavailable, try again later" - apiErr.Retryable = true + apiError.Suggestion = "The service is temporarily unavailable, try again later" + apiError.Retryable = true } - c.JSON(status, apiErr) + c.JSON(status, apiError) } // respondWithMiningError(c, ErrMinerNotFound("xmrig")) @@ -114,7 +114,7 @@ func respondWithMiningError(c *gin.Context, err *MiningError) { details += err.Details } - apiErr := APIError{ + apiError := APIError{ Code: err.Code, Message: err.Message, Details: sanitizeErrorDetails(details), @@ -122,7 +122,7 @@ func respondWithMiningError(c *gin.Context, err *MiningError) { Retryable: err.Retryable, } - c.JSON(err.StatusCode(), apiErr) + c.JSON(err.StatusCode(), apiError) } // isRetryableError(http.StatusServiceUnavailable) // => true @@ -220,8 +220,8 @@ func logWithRequestID(c *gin.Context, level string, message string, fields loggi if fields == nil { fields = logging.Fields{} } - if reqID := getRequestID(c); reqID != "" { - fields["request_id"] = reqID + if requestID := getRequestID(c); requestID != "" { + fields["request_id"] = requestID } switch level { case "error": diff --git a/pkg/mining/settings_manager.go b/pkg/mining/settings_manager.go index 6e974f4..6c49805 100644 --- a/pkg/mining/settings_manager.go +++ b/pkg/mining/settings_manager.go @@ -165,11 +165,11 @@ func (settingsManager *SettingsManager) Get() *AppSettings { } // settingsManager.Update(func(settings *AppSettings) { settings.Theme = "dark" }) -func (settingsManager *SettingsManager) Update(fn func(*AppSettings)) error { +func (settingsManager *SettingsManager) Update(modifier func(*AppSettings)) error { settingsManager.mutex.Lock() defer settingsManager.mutex.Unlock() - fn(settingsManager.settings) + modifier(settingsManager.settings) data, err := json.MarshalIndent(settingsManager.settings, "", " ") if err != nil { diff --git a/pkg/mining/stats_collector.go b/pkg/mining/stats_collector.go index 702add3..441ed64 100644 --- a/pkg/mining/stats_collector.go +++ b/pkg/mining/stats_collector.go @@ -29,23 +29,23 @@ func FetchJSONStats[T any](ctx context.Context, config HTTPStatsConfig, target * requestURL := "http://" + config.Host + ":" + strconv.Itoa(config.Port) + config.Endpoint - req, err := http.NewRequestWithContext(ctx, "GET", requestURL, nil) + httpRequest, err := http.NewRequestWithContext(ctx, "GET", requestURL, nil) if err != nil { return ErrInternal("failed to create request").WithCause(err) } - resp, err := getHTTPClient().Do(req) + response, err := getHTTPClient().Do(httpRequest) if err != nil { return ErrInternal("HTTP request failed").WithCause(err) } - defer resp.Body.Close() + defer response.Body.Close() - if resp.StatusCode != http.StatusOK { - io.Copy(io.Discard, resp.Body) // Drain body to allow connection reuse - return ErrInternal("unexpected status code: " + strconv.Itoa(resp.StatusCode)) + if response.StatusCode != http.StatusOK { + io.Copy(io.Discard, response.Body) // Drain body to allow connection reuse + return ErrInternal("unexpected status code: " + strconv.Itoa(response.StatusCode)) } - body, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(response.Body) if err != nil { return ErrInternal("failed to read response body").WithCause(err) } diff --git a/pkg/mining/ttminer.go b/pkg/mining/ttminer.go index 0a85b03..b48e5ef 100644 --- a/pkg/mining/ttminer.go +++ b/pkg/mining/ttminer.go @@ -148,14 +148,14 @@ func (m *TTMiner) CheckInstallation() (*InstallationDetails, error) { // Run version command before acquiring lock (I/O operation) cmd := exec.Command(binaryPath, "--version") - var out bytes.Buffer - cmd.Stdout = &out + var commandOutput bytes.Buffer + cmd.Stdout = &commandOutput var version string if err := cmd.Run(); err != nil { version = "Unknown (could not run executable)" } else { // Parse version from output - output := strings.TrimSpace(out.String()) + output := strings.TrimSpace(commandOutput.String()) fields := strings.Fields(output) if len(fields) >= 2 { version = fields[1] diff --git a/pkg/mining/ttminer_stats.go b/pkg/mining/ttminer_stats.go index 04f9b48..c358a67 100644 --- a/pkg/mining/ttminer_stats.go +++ b/pkg/mining/ttminer_stats.go @@ -24,12 +24,12 @@ func (m *TTMiner) GetStats(ctx context.Context) (*PerformanceMetrics, error) { m.mutex.RUnlock() // Create request with context and timeout - reqCtx, cancel := context.WithTimeout(ctx, statsTimeout) + requestContext, cancel := context.WithTimeout(ctx, statsTimeout) defer cancel() // Use the common HTTP stats fetcher var summary TTMinerSummary - if err := FetchJSONStats(reqCtx, config, &summary); err != nil { + if err := FetchJSONStats(requestContext, config, &summary); err != nil { return nil, err } diff --git a/pkg/mining/version.go b/pkg/mining/version.go index 48eb408..f6221cc 100644 --- a/pkg/mining/version.go +++ b/pkg/mining/version.go @@ -66,18 +66,18 @@ func FetchLatestGitHubVersion(owner, repo string) (string, error) { func fetchGitHubVersionDirect(owner, repo string) (string, error) { url := "https://api.github.com/repos/" + owner + "/" + repo + "/releases/latest" - resp, err := getHTTPClient().Get(url) + response, err := getHTTPClient().Get(url) if err != nil { return "", ErrInternal("failed to fetch version").WithCause(err) } - defer resp.Body.Close() + defer response.Body.Close() - if resp.StatusCode != http.StatusOK { - io.Copy(io.Discard, resp.Body) // Drain body to allow connection reuse - return "", ErrInternal("failed to get latest release: unexpected status code " + strconv.Itoa(resp.StatusCode)) + if response.StatusCode != http.StatusOK { + io.Copy(io.Discard, response.Body) // Drain body to allow connection reuse + return "", ErrInternal("failed to get latest release: unexpected status code " + strconv.Itoa(response.StatusCode)) } - body, err := io.ReadAll(resp.Body) + body, err := io.ReadAll(response.Body) if err != nil { return "", ErrInternal("failed to read release response").WithCause(err) } diff --git a/pkg/mining/xmrig.go b/pkg/mining/xmrig.go index 5b31108..76dfe8b 100644 --- a/pkg/mining/xmrig.go +++ b/pkg/mining/xmrig.go @@ -159,13 +159,13 @@ func (m *XMRigMiner) CheckInstallation() (*InstallationDetails, error) { // Run version command before acquiring lock (I/O operation) cmd := exec.Command(binaryPath, "--version") - var out bytes.Buffer - cmd.Stdout = &out + var output bytes.Buffer + cmd.Stdout = &output var version string if err := cmd.Run(); err != nil { version = "Unknown (could not run executable)" } else { - fields := strings.Fields(out.String()) + fields := strings.Fields(output.String()) if len(fields) >= 2 { version = fields[1] } else { diff --git a/pkg/mining/xmrig_stats.go b/pkg/mining/xmrig_stats.go index 6a607cb..a8beaaf 100644 --- a/pkg/mining/xmrig_stats.go +++ b/pkg/mining/xmrig_stats.go @@ -5,7 +5,7 @@ import ( "time" ) -// reqCtx, cancel := context.WithTimeout(ctx, statsTimeout) +// requestContext, cancel := context.WithTimeout(ctx, statsTimeout) const statsTimeout = 5 * time.Second // metrics, err := miner.GetStats(ctx) @@ -29,12 +29,12 @@ func (m *XMRigMiner) GetStats(ctx context.Context) (*PerformanceMetrics, error) m.mutex.RUnlock() // Create request with context and timeout - reqCtx, cancel := context.WithTimeout(ctx, statsTimeout) + requestContext, cancel := context.WithTimeout(ctx, statsTimeout) defer cancel() // Use the common HTTP stats fetcher var summary XMRigSummary - if err := FetchJSONStats(reqCtx, config, &summary); err != nil { + if err := FetchJSONStats(requestContext, config, &summary); err != nil { return nil, err }