Apply AX naming and comment cleanup
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run

This commit is contained in:
Virgil 2026-04-04 05:27:28 +00:00
parent 7c214f0c8b
commit c7f86cf5b9
6 changed files with 108 additions and 106 deletions

View file

@ -17,16 +17,16 @@ import (
var assets embed.FS
func main() {
// Create the mining service
// miningService := NewMiningService() // powers the Wails dashboard with managers and saved settings.
miningService := NewMiningService()
// Get the sub-filesystem rooted at frontend/dist/browser
// browserFS, err := fs.Sub(assets, "frontend/dist/browser") // serves the built dashboard from frontend/dist/browser.
browserFS, err := fs.Sub(assets, "frontend/dist/browser")
if err != nil {
log.Fatal("Failed to create sub-filesystem:", err)
}
// Create a new Wails application
// app := application.New(...) // opens the dashboard window and registers the mining service.
app := application.New(application.Options{
Name: "Mining Dashboard",
Description: "Multi-miner management dashboard",
@ -41,7 +41,7 @@ func main() {
},
})
// Get saved window state
// windowState := miningService.GetWindowState() // restores the last saved 1400x900 window size when available.
windowState := miningService.GetWindowState()
width := windowState.Width
height := windowState.Height
@ -52,7 +52,7 @@ func main() {
height = 900
}
// Create the main window with saved dimensions
// app.Window.NewWithOptions(...) // uses the restored size and keeps the title bar hidden inset.
app.Window.NewWithOptions(application.WebviewWindowOptions{
Title: "Mining Dashboard",
Width: width,
@ -66,7 +66,7 @@ func main() {
URL: "/",
})
// Handle graceful shutdown
// sigChan := make(chan os.Signal, 1) // captures Ctrl+C and SIGTERM for a clean shutdown.
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
go func() {
@ -75,7 +75,7 @@ func main() {
os.Exit(0)
}()
// Run the application
// app.Run() // keeps the window open until SIGINT or SIGTERM triggers miningService.Shutdown().
if err := app.Run(); err != nil {
log.Fatal(err)
}

View file

@ -13,20 +13,20 @@ import (
// MiningService exposes mining functionality to the Wails frontend.
type MiningService struct {
manager *mining.Manager
profileMgr *mining.ProfileManager
settingsMgr *mining.SettingsManager
manager *mining.Manager
profileManager *mining.ProfileManager
settingsManager *mining.SettingsManager
}
// NewMiningService creates a new mining service with an initialized manager.
func NewMiningService() *MiningService {
manager := mining.NewManager()
profileMgr, _ := mining.NewProfileManager()
settingsMgr, _ := mining.NewSettingsManager()
profileManager, _ := mining.NewProfileManager()
settingsManager, _ := mining.NewSettingsManager()
return &MiningService{
manager: manager,
profileMgr: profileMgr,
settingsMgr: settingsMgr,
manager: manager,
profileManager: profileManager,
settingsManager: settingsManager,
}
}
@ -64,7 +64,7 @@ type Profile struct {
}
// GetSystemInfo returns system information and installed miners.
func (s *MiningService) GetSystemInfo() (*SystemInfo, error) {
func (service *MiningService) GetSystemInfo() (*SystemInfo, error) {
cpuInfo, _ := cpu.Info()
cpuName := "Unknown"
if len(cpuInfo) > 0 {
@ -78,7 +78,7 @@ func (s *MiningService) GetSystemInfo() (*SystemInfo, error) {
}
miners := []MinerInstallInfo{}
// Check installation for each miner type by creating temporary instances
// miner.CheckInstallation() // probes xmrig and tt-miner without launching a process.
for _, minerType := range []string{"xmrig", "tt-miner"} {
var miner mining.Miner
switch minerType {
@ -115,15 +115,15 @@ func (s *MiningService) GetSystemInfo() (*SystemInfo, error) {
}
// ListMiners returns all running miners.
func (s *MiningService) ListMiners() []MinerStatus {
miners := s.manager.ListMiners()
func (service *MiningService) ListMiners() []MinerStatus {
miners := service.manager.ListMiners()
result := make([]MinerStatus, len(miners))
for i, m := range miners {
stats, _ := m.GetStats()
for i, miner := range miners {
stats, _ := miner.GetStats()
result[i] = MinerStatus{
Name: m.GetName(),
Running: true, // If it's in the list, it's running
MinerType: getMinerType(m),
Name: miner.GetName(),
Running: true, // service.ListMiners() only returns miners that are already running.
MinerType: getMinerType(miner),
Stats: stats,
}
}
@ -131,8 +131,8 @@ func (s *MiningService) ListMiners() []MinerStatus {
}
// getMinerType extracts the miner type from a miner instance.
func getMinerType(m mining.Miner) string {
name := m.GetName()
func getMinerType(miner mining.Miner) string {
name := miner.GetName()
if strings.HasPrefix(name, "xmrig") {
return "xmrig"
}
@ -143,8 +143,8 @@ func getMinerType(m mining.Miner) string {
}
// StartMiner starts a miner with the given configuration.
func (s *MiningService) StartMiner(minerType string, config *mining.Config) (string, error) {
miner, err := s.manager.StartMiner(minerType, config)
func (service *MiningService) StartMiner(minerType string, config *mining.Config) (string, error) {
miner, err := service.manager.StartMiner(minerType, config)
if err != nil {
return "", err
}
@ -152,11 +152,11 @@ func (s *MiningService) StartMiner(minerType string, config *mining.Config) (str
}
// StartMinerFromProfile starts a miner using a saved profile.
func (s *MiningService) StartMinerFromProfile(profileID string) (string, error) {
if s.profileMgr == nil {
func (service *MiningService) StartMinerFromProfile(profileID string) (string, error) {
if service.profileManager == nil {
return "", fmt.Errorf("profile manager not initialized")
}
profile, ok := s.profileMgr.GetProfile(profileID)
profile, ok := service.profileManager.GetProfile(profileID)
if !ok {
return "", fmt.Errorf("profile not found: %s", profileID)
}
@ -169,7 +169,7 @@ func (s *MiningService) StartMinerFromProfile(profileID string) (string, error)
}
}
miner, err := s.manager.StartMiner(profile.MinerType, &config)
miner, err := service.manager.StartMiner(profile.MinerType, &config)
if err != nil {
return "", err
}
@ -177,13 +177,13 @@ func (s *MiningService) StartMinerFromProfile(profileID string) (string, error)
}
// StopMiner stops a running miner by name.
func (s *MiningService) StopMiner(name string) error {
return s.manager.StopMiner(name)
func (service *MiningService) StopMiner(name string) error {
return service.manager.StopMiner(name)
}
// GetMinerStats returns stats for a specific miner.
func (s *MiningService) GetMinerStats(name string) (*mining.PerformanceMetrics, error) {
miner, err := s.manager.GetMiner(name)
func (service *MiningService) GetMinerStats(name string) (*mining.PerformanceMetrics, error) {
miner, err := service.manager.GetMiner(name)
if err != nil {
return nil, err
}
@ -191,8 +191,8 @@ func (s *MiningService) GetMinerStats(name string) (*mining.PerformanceMetrics,
}
// GetMinerLogs returns log lines for a specific miner.
func (s *MiningService) GetMinerLogs(name string) ([]string, error) {
miner, err := s.manager.GetMiner(name)
func (service *MiningService) GetMinerLogs(name string) ([]string, error) {
miner, err := service.manager.GetMiner(name)
if err != nil {
return nil, err
}
@ -200,7 +200,7 @@ func (s *MiningService) GetMinerLogs(name string) ([]string, error) {
}
// InstallMiner installs a miner of the given type.
func (s *MiningService) InstallMiner(minerType string) error {
func (service *MiningService) InstallMiner(minerType string) error {
var miner mining.Miner
switch minerType {
case "xmrig":
@ -214,16 +214,16 @@ func (s *MiningService) InstallMiner(minerType string) error {
}
// UninstallMiner uninstalls a miner of the given type.
func (s *MiningService) UninstallMiner(minerType string) error {
return s.manager.UninstallMiner(minerType)
func (service *MiningService) UninstallMiner(minerType string) error {
return service.manager.UninstallMiner(minerType)
}
// GetProfiles returns all saved mining profiles.
func (s *MiningService) GetProfiles() ([]Profile, error) {
if s.profileMgr == nil {
func (service *MiningService) GetProfiles() ([]Profile, error) {
if service.profileManager == nil {
return []Profile{}, nil
}
profiles := s.profileMgr.GetAllProfiles()
profiles := service.profileManager.GetAllProfiles()
result := make([]Profile, len(profiles))
for i, p := range profiles {
@ -243,8 +243,8 @@ func (s *MiningService) GetProfiles() ([]Profile, error) {
}
// CreateProfile creates a new mining profile.
func (s *MiningService) CreateProfile(name, minerType string, config map[string]interface{}) (*Profile, error) {
if s.profileMgr == nil {
func (service *MiningService) CreateProfile(name, minerType string, config map[string]interface{}) (*Profile, error) {
if service.profileManager == nil {
return nil, fmt.Errorf("profile manager not initialized")
}
@ -260,7 +260,7 @@ func (s *MiningService) CreateProfile(name, minerType string, config map[string]
Config: mining.RawConfig(configBytes),
}
profile, err := s.profileMgr.CreateProfile(newProfile)
profile, err := service.profileManager.CreateProfile(newProfile)
if err != nil {
return nil, err
}
@ -274,22 +274,22 @@ func (s *MiningService) CreateProfile(name, minerType string, config map[string]
}
// DeleteProfile deletes a profile by ID.
func (s *MiningService) DeleteProfile(id string) error {
if s.profileMgr == nil {
func (service *MiningService) DeleteProfile(id string) error {
if service.profileManager == nil {
return nil
}
return s.profileMgr.DeleteProfile(id)
return service.profileManager.DeleteProfile(id)
}
// GetHashrateHistory returns hashrate history for a miner.
func (s *MiningService) GetHashrateHistory(name string) []mining.HashratePoint {
history, _ := s.manager.GetMinerHashrateHistory(name)
func (service *MiningService) GetHashrateHistory(name string) []mining.HashratePoint {
history, _ := service.manager.GetMinerHashrateHistory(name)
return history
}
// SendStdin sends input to a miner's stdin.
func (s *MiningService) SendStdin(name, input string) error {
miner, err := s.manager.GetMiner(name)
func (service *MiningService) SendStdin(name, input string) error {
miner, err := service.manager.GetMiner(name)
if err != nil {
return err
}
@ -297,36 +297,36 @@ func (s *MiningService) SendStdin(name, input string) error {
}
// Shutdown gracefully shuts down all miners.
func (s *MiningService) Shutdown() {
s.manager.Stop()
func (service *MiningService) Shutdown() {
service.manager.Stop()
}
// === Settings Methods ===
// GetSettings returns the current app settings
func (s *MiningService) GetSettings() (*mining.AppSettings, error) {
if s.settingsMgr == nil {
func (service *MiningService) GetSettings() (*mining.AppSettings, error) {
if service.settingsManager == nil {
return mining.DefaultSettings(), nil
}
return s.settingsMgr.Get(), nil
return service.settingsManager.Get(), nil
}
// SaveSettings saves the app settings
func (s *MiningService) SaveSettings(settings *mining.AppSettings) error {
if s.settingsMgr == nil {
func (service *MiningService) SaveSettings(settings *mining.AppSettings) error {
if service.settingsManager == nil {
return fmt.Errorf("settings manager not initialized")
}
return s.settingsMgr.Update(func(s *mining.AppSettings) {
*s = *settings
return service.settingsManager.Update(func(serviceSettings *mining.AppSettings) {
*serviceSettings = *settings
})
}
// SaveWindowState saves the window position and size
func (s *MiningService) SaveWindowState(x, y, width, height int, maximized bool) error {
if s.settingsMgr == nil {
func (service *MiningService) SaveWindowState(x, y, width, height int, maximized bool) error {
if service.settingsManager == nil {
return nil
}
return s.settingsMgr.UpdateWindowState(x, y, width, height, maximized)
return service.settingsManager.UpdateWindowState(x, y, width, height, maximized)
}
// WindowState represents window position and size for the frontend
@ -339,11 +339,11 @@ type WindowState struct {
}
// GetWindowState returns the saved window state
func (s *MiningService) GetWindowState() *WindowState {
if s.settingsMgr == nil {
func (service *MiningService) GetWindowState() *WindowState {
if service.settingsManager == nil {
return &WindowState{Width: 1400, Height: 900}
}
state := s.settingsMgr.GetWindowState()
state := service.settingsManager.GetWindowState()
return &WindowState{
X: state.X,
Y: state.Y,
@ -354,33 +354,33 @@ func (s *MiningService) GetWindowState() *WindowState {
}
// SetStartOnBoot enables/disables start on system boot
func (s *MiningService) SetStartOnBoot(enabled bool) error {
if s.settingsMgr == nil {
func (service *MiningService) SetStartOnBoot(enabled bool) error {
if service.settingsManager == nil {
return nil
}
return s.settingsMgr.SetStartOnBoot(enabled)
return service.settingsManager.SetStartOnBoot(enabled)
}
// SetAutostartMiners enables/disables automatic miner start
func (s *MiningService) SetAutostartMiners(enabled bool) error {
if s.settingsMgr == nil {
func (service *MiningService) SetAutostartMiners(enabled bool) error {
if service.settingsManager == nil {
return nil
}
return s.settingsMgr.SetAutostartMiners(enabled)
return service.settingsManager.SetAutostartMiners(enabled)
}
// SetCPUThrottle configures CPU throttling settings
func (s *MiningService) SetCPUThrottle(enabled bool, maxPercent int) error {
if s.settingsMgr == nil {
func (service *MiningService) SetCPUThrottle(enabled bool, maxPercent int) error {
if service.settingsManager == nil {
return nil
}
return s.settingsMgr.SetCPUThrottle(enabled, maxPercent)
return service.settingsManager.SetCPUThrottle(enabled, maxPercent)
}
// SetMinerDefaults updates default miner configuration
func (s *MiningService) SetMinerDefaults(defaults mining.MinerDefaults) error {
if s.settingsMgr == nil {
func (service *MiningService) SetMinerDefaults(defaults mining.MinerDefaults) error {
if service.settingsManager == nil {
return nil
}
return s.settingsMgr.SetMinerDefaults(defaults)
return service.settingsManager.SetMinerDefaults(defaults)
}

View file

@ -16,7 +16,7 @@ import (
const installationCachePointerFileName = ".installed-miners"
// validateConfigPath("/home/alice/.config/lethean-desktop/miners/config.json") // nil
// validateConfigPath("/tmp/config.json") // error because the path is outside XDG_CONFIG_HOME
// validateConfigPath("/tmp/config.json") // rejects paths outside XDG_CONFIG_HOME.
func validateConfigPath(configPath string) error {
expectedBase := filepath.Join(xdg.ConfigHome, "lethean-desktop")
@ -42,7 +42,7 @@ var doctorCmd = &cobra.Command{
if err := updateDoctorCache(); err != nil {
return fmt.Errorf("failed to run doctor check: %w", err)
}
// loadAndDisplayCache() // prints the refreshed miner summary after updateDoctorCache()
// loadAndDisplayCache() // prints the refreshed miner summary after updateDoctorCache().
_, err := loadAndDisplayCache()
return err
},
@ -55,9 +55,10 @@ func loadAndDisplayCache() (bool, error) {
}
signpostPath := filepath.Join(homeDir, installationCachePointerFileName)
// os.Stat(signpostPath) // returns os.ErrNotExist when no cache has been written yet.
if _, err := os.Stat(signpostPath); os.IsNotExist(err) {
fmt.Println("No cached data found. Run 'install' for a miner first.")
return false, nil // No cache to load
return false, nil // loadAndDisplayCache() returns false until install() writes the first cache file.
}
configPathBytes, err := os.ReadFile(signpostPath)
@ -66,7 +67,7 @@ func loadAndDisplayCache() (bool, error) {
}
configPath := strings.TrimSpace(string(configPathBytes))
// validateConfigPath("/home/alice/.config/lethean-desktop/miners/config.json") // blocks path traversal
// validateConfigPath("/home/alice/.config/lethean-desktop/miners/config.json") // blocks path traversal outside XDG_CONFIG_HOME.
if err := validateConfigPath(configPath); err != nil {
return false, fmt.Errorf("security error: %w", err)
}
@ -90,7 +91,7 @@ func loadAndDisplayCache() (bool, error) {
fmt.Println()
for _, details := range systemInfo.InstalledMinersInfo {
// details.Path = "/home/alice/.local/share/lethean-desktop/miners/xmrig" -> "XMRig"
// details.Path = "/home/alice/.local/share/lethean-desktop/miners/xmrig" // maps to a friendly miner label like "XMRig".
var minerName string
if details.Path != "" {
if strings.Contains(details.Path, "xmrig") {

View file

@ -42,7 +42,7 @@ var serveCmd = &cobra.Command{
displayAddr := fmt.Sprintf("%s:%d", displayHost, port)
listenAddr := fmt.Sprintf("%s:%d", host, port)
// manager := getServiceManager() // reuses the shared manager for `mining serve`.
// manager := getServiceManager() // `mining serve` and `mining start` share the same manager instance.
manager := getServiceManager()
service, err := mining.NewService(manager, listenAddr, displayAddr, namespace)
@ -50,7 +50,7 @@ var serveCmd = &cobra.Command{
return fmt.Errorf("failed to create new service: %w", err)
}
// service.ServiceStartup(ctx) // starts the HTTP server without blocking the shell loop.
// service.ServiceStartup(ctx) // starts the HTTP server on 127.0.0.1:9090 while the shell keeps reading stdin.
go func() {
if err := service.ServiceStartup(ctx); err != nil {
fmt.Fprintf(os.Stderr, "Failed to start service: %v\n", err)
@ -58,11 +58,11 @@ var serveCmd = &cobra.Command{
}
}()
// shutdownSignal := make(chan os.Signal, 1) // receives SIGINT and SIGTERM from Ctrl+C.
// shutdownSignal := make(chan os.Signal, 1) // captures Ctrl+C and SIGTERM for graceful shutdown.
shutdownSignal := make(chan os.Signal, 1)
signal.Notify(shutdownSignal, syscall.SIGINT, syscall.SIGTERM)
// Start the interactive shell in a goroutine.
// go func() { fmt.Print(">> ") } // keeps the interactive shell responsive while the API serves requests.
go func() {
fmt.Printf("Mining service started on http://%s:%d\n", displayHost, port)
fmt.Printf("Swagger documentation is available at http://%s:%d%s/index.html\n", displayHost, port, service.SwaggerUIPath)
@ -101,7 +101,7 @@ var serveCmd = &cobra.Command{
poolURL := commandArgs[1]
walletAddress := commandArgs[2]
// Validate pool URL format
// poolURL := "stratum+tcp://pool.example.com:3333" // required scheme for the miner configuration.
if !strings.HasPrefix(poolURL, "stratum+tcp://") &&
!strings.HasPrefix(poolURL, "stratum+ssl://") &&
!strings.HasPrefix(poolURL, "stratum://") {
@ -115,7 +115,7 @@ var serveCmd = &cobra.Command{
continue
}
// Validate wallet address length
// walletAddress := "44Affq5kSiGBoZ..." // keeps the wallet field within the 256-character limit.
if len(walletAddress) > 256 {
fmt.Fprintf(os.Stderr, "Error: Wallet address too long (max 256 chars)\n")
fmt.Print(">> ")
@ -128,7 +128,7 @@ var serveCmd = &cobra.Command{
LogOutput: true,
}
// Validate config before starting
// config.Validate() // rejects malformed pool and wallet values before the miner starts.
if err := config.Validate(); err != nil {
fmt.Fprintf(os.Stderr, "Error: Invalid configuration: %v\n", err)
fmt.Print(">> ")
@ -193,7 +193,7 @@ var serveCmd = &cobra.Command{
fmt.Print(">> ")
}
// Check for scanner errors (I/O issues)
// scanner.Err() // reports stdin failures such as a closed terminal.
if err := scanner.Err(); err != nil {
fmt.Fprintf(os.Stderr, "Error reading input: %v\n", err)
}
@ -206,7 +206,7 @@ var serveCmd = &cobra.Command{
case <-ctx.Done():
}
// Explicit cleanup of manager resources
// manager.Stop() // stops miner goroutines and closes the shared manager before exit.
manager.Stop()
fmt.Println("Mining service stopped.")

View file

@ -60,7 +60,7 @@ Available presets:
displayAddr := fmt.Sprintf("%s:%d", displayHost, port)
listenAddr := fmt.Sprintf("%s:%d", host, port)
// manager := mining.NewManagerForSimulation() // keeps simulation miners isolated from real autostart state.
// manager := mining.NewManagerForSimulation() // keeps simulated miners isolated from the real autostart state.
manager := mining.NewManagerForSimulation()
// getSimulatedConfig(0) // returns a config such as sim-cpu-medium-001.
@ -68,12 +68,12 @@ Available presets:
simulatedConfig := getSimulatedConfig(i)
simulatedMiner := mining.NewSimulatedMiner(simulatedConfig)
// simulatedMiner.Start(&mining.Config{}) // uses the simulated miner lifecycle, not a real binary.
// simulatedMiner.Start(&mining.Config{}) // starts the fake miner lifecycle without launching a real binary.
if err := simulatedMiner.Start(&mining.Config{}); err != nil {
return fmt.Errorf("failed to start simulated miner %d: %w", i, err)
}
// manager.RegisterMiner(simulatedMiner) // exposes the simulated miner through the shared manager.
// manager.RegisterMiner(simulatedMiner) // makes the simulated miner visible to `mining serve`.
if err := manager.RegisterMiner(simulatedMiner); err != nil {
return fmt.Errorf("failed to register simulated miner %d: %w", i, err)
}
@ -82,7 +82,7 @@ Available presets:
simulatedConfig.Name, simulatedConfig.Algorithm, simulatedConfig.BaseHashrate)
}
// Create and start the service
// service, err := mining.NewService(manager, listenAddr, displayAddr, namespace) // serves the simulator on http://127.0.0.1:9090.
service, err := mining.NewService(manager, listenAddr, displayAddr, namespace)
if err != nil {
return fmt.Errorf("failed to create new service: %w", err)
@ -102,7 +102,7 @@ Available presets:
fmt.Printf("\nSimulating %d miner(s). Press Ctrl+C to stop.\n", simulatedMinerCount)
fmt.Printf("Note: All data is simulated - no actual mining is occurring.\n\n")
// shutdownSignal := make(chan os.Signal, 1) // receives SIGINT and SIGTERM from Ctrl+C.
// shutdownSignal := make(chan os.Signal, 1) // captures Ctrl+C and SIGTERM for graceful shutdown.
shutdownSignal := make(chan os.Signal, 1)
signal.Notify(shutdownSignal, syscall.SIGINT, syscall.SIGTERM)
@ -113,7 +113,7 @@ Available presets:
case <-ctx.Done():
}
// Stop all simulated miners
// for _, miner := range manager.ListMiners() { manager.StopMiner(context.Background(), miner.GetName()) } // stops every simulated miner before exit.
for _, miner := range manager.ListMiners() {
manager.StopMiner(context.Background(), miner.GetName())
}
@ -137,10 +137,11 @@ func getSimulatedConfig(index int) mining.SimulatedMinerConfig {
config.Name = name
// Override with custom values if provided
// simulationHashrate = 8000 // overrides the preset with a custom 8 kH/s baseline.
if simulationHashrate > 0 {
config.BaseHashrate = simulationHashrate
}
// simulationAlgorithm = "rx/0" // swaps the preset algorithm before the miner starts.
if simulationAlgorithm != "" {
config.Algorithm = simulationAlgorithm
}
@ -167,7 +168,7 @@ func init() {
rootCmd.AddCommand(simulateCmd)
}
// Helper function to format hashrate
// formatHashrate(1250) // returns "1.25 kH/s" for display in the simulation summary.
func formatHashrate(h int) string {
if h >= 1000000000 {
return strconv.FormatFloat(float64(h)/1000000000, 'f', 2, 64) + " GH/s"

View file

@ -32,9 +32,9 @@ import (
ginSwagger "github.com/swaggo/gin-swagger"
)
// service, err := mining.NewService(manager, ":9090", "localhost:9090", "api/v1/mining")
// service, err := mining.NewService(manager, "127.0.0.1:9090", "localhost:9090", "/api/v1/mining")
// if err != nil { return err }
// service.ServiceStartup()
// service.ServiceStartup(context.Background()) // starts the REST API on 127.0.0.1:9090.
type Service struct {
Manager ManagerInterface
ProfileManager *ProfileManager
@ -80,7 +80,7 @@ func respondWithError(c *gin.Context, status int, code string, message string, d
Retryable: isRetryableError(status),
}
// Add suggestions based on error code
// respondWithError(c, http.StatusServiceUnavailable, ErrCodeServiceUnavailable, "service unavailable", "database offline") // adds a retry suggestion.
switch code {
case ErrCodeMinerNotFound:
apiError.Suggestion = "Check the miner name or install the miner first"