Files
LINUX-AETHERFORGE/internal/api/handlers/seer.go
drjones 3678b199d0
Some checks failed
Test / test (push) Has been cancelled
Initial commit: AetherForge Linux (forge-mesh) v0.1.0-dev
2026-07-04 09:31:23 +00:00

174 lines
4.3 KiB
Go

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},
},
}
}