package handlers import ( "encoding/base64" "encoding/json" "fmt" "net/http" "strings" "time" "forge-mesh/internal/auth" "forge-mesh/internal/config" "forge-mesh/internal/fleet" ) // SeerHandler streams court/LOTL events via SSE. type SeerHandler struct { Store *fleet.Store Username string Password string } func (h *SeerHandler) Stream(w http.ResponseWriter, r *http.Request) { if !h.authenticated(r) { http.Error(w, "unauthorized", http.StatusUnauthorized) return } flusher, ok := w.(http.Flusher) if !ok { http.Error(w, "streaming unsupported", http.StatusInternalServerError) return } w.Header().Set("Content-Type", "text/event-stream") w.Header().Set("Cache-Control", "no-cache") w.Header().Set("Connection", "keep-alive") events, _ := h.Store.ListSeerEvents(20) for _, ev := range events { payload, _ := json.Marshal(ev) fmt.Fprintf(w, "data: %s\n\n", payload) } flusher.Flush() ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for { select { case <-r.Context().Done(): return case <-ticker.C: events, err := h.Store.ListSeerEvents(5) if err != nil { continue } for _, ev := range events { payload, _ := json.Marshal(ev) fmt.Fprintf(w, "data: %s\n\n", payload) flusher.Flush() } } } } func (h *SeerHandler) authenticated(r *http.Request) bool { if user, pass, ok := r.BasicAuth(); ok { return user == h.Username && pass == h.Password } if authHeader := r.URL.Query().Get("authorization"); authHeader != "" { raw := strings.TrimPrefix(authHeader, "Basic ") decoded, err := base64.StdEncoding.DecodeString(raw) if err != nil { return false } parts := strings.SplitN(string(decoded), ":", 2) if len(parts) != 2 { return false } return parts[0] == h.Username && parts[1] == h.Password } return false } // WSTicketHandler issues one-time WebSocket tickets. type WSTicketHandler struct { Tickets *auth.TicketStore } func (h *WSTicketHandler) Issue(w http.ResponseWriter, r *http.Request) { ticket, err := h.Tickets.Issue() if err != nil || ticket == "" { http.Error(w, "ticket issue failed", http.StatusInternalServerError) return } auth.JSON(w, http.StatusOK, map[string]string{"ticket": ticket}) } // OperatorHandler returns operator clearance info. type OperatorHandler struct { Clearance int } func (h *OperatorHandler) Me(w http.ResponseWriter, r *http.Request) { auth.JSON(w, http.StatusOK, map[string]any{ "clearance_level": h.Clearance, "label": fleet.ClearanceLabel(h.Clearance), }) } // PolicyHandler serves wallet and mining profile endpoints. type PolicyHandler struct { Cfg *config.Config Store *fleet.Store } func (h *PolicyHandler) GetWallet(w http.ResponseWriter, r *http.Request) { auth.JSON(w, http.StatusOK, h.Cfg.WalletPolicy) } func (h *PolicyHandler) PutWallet(w http.ResponseWriter, r *http.Request) { var wp config.WalletPolicy if err := json.NewDecoder(r.Body).Decode(&wp); err != nil { http.Error(w, "bad request", http.StatusBadRequest) return } if wp.DefaultWallet != "" { h.Cfg.WalletPolicy.DefaultWallet = wp.DefaultWallet } if wp.Currency != "" { h.Cfg.WalletPolicy.Currency = wp.Currency } auth.JSON(w, http.StatusOK, h.Cfg.WalletPolicy) } func (h *PolicyHandler) GetMiningProfile(w http.ResponseWriter, r *http.Request) { auth.JSON(w, http.StatusOK, defaultMiningProfile(h.Cfg)) } func (h *PolicyHandler) PutMiningProfile(w http.ResponseWriter, r *http.Request) { var profile miningProfileRequest if err := json.NewDecoder(r.Body).Decode(&profile); err != nil { http.Error(w, "bad request", http.StatusBadRequest) return } if profile.WalletAddress != "" { h.Cfg.WalletPolicy.DefaultWallet = profile.WalletAddress } auth.JSON(w, http.StatusOK, profile) } type miningProfileRequest struct { WalletAddress string `json:"wallet_address"` Tiers []tierEntry `json:"tiers"` } type tierEntry struct { Tier int `json:"tier"` Name string `json:"name"` Enabled bool `json:"enabled"` } func defaultMiningProfile(cfg *config.Config) miningProfileRequest { return miningProfileRequest{ WalletAddress: cfg.WalletPolicy.DefaultWallet, Tiers: []tierEntry{ {Tier: 1, Name: "OCI podman", Enabled: true}, {Tier: 2, Name: "Bundled xmrig", Enabled: true}, {Tier: 3, Name: "GPU lolMiner", Enabled: true}, {Tier: 4, Name: "Stratum-direct fallback", Enabled: false}, }, } }