package api import ( "encoding/json" "log" "net/http" "strconv" "git.rouggy.com/rouggy/ShackMaster/internal/config" "github.com/gorilla/websocket" ) type Server struct { deviceManager *DeviceManager hub *Hub config *config.Config upgrader websocket.Upgrader } func NewServer(dm *DeviceManager, hub *Hub, cfg *config.Config) *Server { return &Server{ deviceManager: dm, hub: hub, config: cfg, upgrader: websocket.Upgrader{ ReadBufferSize: 1024, WriteBufferSize: 1024, CheckOrigin: func(r *http.Request) bool { return true // Allow all origins for now }, }, } } func (s *Server) SetupRoutes() *http.ServeMux { mux := http.NewServeMux() // WebSocket endpoint mux.HandleFunc("/ws", s.handleWebSocket) // REST API endpoints mux.HandleFunc("/api/status", s.handleGetStatus) mux.HandleFunc("/api/config", s.handleGetConfig) // WebSwitch endpoints mux.HandleFunc("/api/webswitch/relay/on", s.handleWebSwitchRelayOn) mux.HandleFunc("/api/webswitch/relay/off", s.handleWebSwitchRelayOff) mux.HandleFunc("/api/webswitch/all/on", s.handleWebSwitchAllOn) mux.HandleFunc("/api/webswitch/all/off", s.handleWebSwitchAllOff) // Rotator endpoints mux.HandleFunc("/api/rotator/move", s.handleRotatorMove) mux.HandleFunc("/api/rotator/cw", s.handleRotatorCW) mux.HandleFunc("/api/rotator/ccw", s.handleRotatorCCW) mux.HandleFunc("/api/rotator/stop", s.handleRotatorStop) // Tuner endpoints mux.HandleFunc("/api/tuner/operate", s.handleTunerOperate) mux.HandleFunc("/api/tuner/tune", s.handleTunerAutoTune) mux.HandleFunc("/api/tuner/antenna", s.handleTunerAntenna) // Antenna Genius endpoints mux.HandleFunc("/api/antenna/set", s.handleAntennaSet) // Power Genius endpoints mux.HandleFunc("/api/power/fanmode", s.handlePowerFanMode) // Static files (will be frontend) mux.Handle("/", http.FileServer(http.Dir("./web/dist"))) return mux } func (s *Server) handleWebSocket(w http.ResponseWriter, r *http.Request) { conn, err := s.upgrader.Upgrade(w, r, nil) if err != nil { log.Printf("WebSocket upgrade error: %v", err) return } ServeWs(s.hub, conn) } func (s *Server) handleGetStatus(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } status := s.deviceManager.GetStatus() s.sendJSON(w, status) } func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodGet { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } // Only send public config info (not API keys) configInfo := map[string]interface{}{ "callsign": s.config.Location.Callsign, "location": map[string]float64{ "latitude": s.config.Location.Latitude, "longitude": s.config.Location.Longitude, }, } s.sendJSON(w, configInfo) } // WebSwitch handlers func (s *Server) handleWebSwitchRelayOn(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } relay, err := strconv.Atoi(r.URL.Query().Get("relay")) if err != nil || relay < 1 || relay > 5 { http.Error(w, "Invalid relay number", http.StatusBadRequest) return } if err := s.deviceManager.WebSwitch().TurnOn(relay); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleWebSwitchRelayOff(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } relay, err := strconv.Atoi(r.URL.Query().Get("relay")) if err != nil || relay < 1 || relay > 5 { http.Error(w, "Invalid relay number", http.StatusBadRequest) return } if err := s.deviceManager.WebSwitch().TurnOff(relay); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleWebSwitchAllOn(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := s.deviceManager.WebSwitch().AllOn(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleWebSwitchAllOff(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := s.deviceManager.WebSwitch().AllOff(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } // Rotator handlers func (s *Server) handleRotatorMove(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Rotator int `json:"rotator"` Azimuth int `json:"azimuth"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := s.deviceManager.RotatorGenius().MoveToAzimuth(req.Rotator, req.Azimuth); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleRotatorCW(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } rotator, err := strconv.Atoi(r.URL.Query().Get("rotator")) if err != nil || rotator < 1 || rotator > 2 { http.Error(w, "Invalid rotator number", http.StatusBadRequest) return } if err := s.deviceManager.RotatorGenius().RotateCW(rotator); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleRotatorCCW(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } rotator, err := strconv.Atoi(r.URL.Query().Get("rotator")) if err != nil || rotator < 1 || rotator > 2 { http.Error(w, "Invalid rotator number", http.StatusBadRequest) return } if err := s.deviceManager.RotatorGenius().RotateCCW(rotator); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleRotatorStop(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := s.deviceManager.RotatorGenius().Stop(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } // Tuner handlers func (s *Server) handleTunerOperate(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Operate bool `json:"operate"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := s.deviceManager.TunerGenius().SetOperate(req.Operate); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleTunerAutoTune(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } if err := s.deviceManager.TunerGenius().AutoTune(); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) handleTunerAntenna(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Antenna int `json:"antenna"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := s.deviceManager.TunerGenius().ActivateAntenna(req.Antenna); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } // Antenna Genius handlers func (s *Server) handleAntennaSet(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Radio int `json:"radio"` Antenna int `json:"antenna"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := s.deviceManager.AntennaGenius().SetRadioAntenna(req.Radio, req.Antenna); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } // Power Genius handlers func (s *Server) handlePowerFanMode(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "Method not allowed", http.StatusMethodNotAllowed) return } var req struct { Mode string `json:"mode"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, "Invalid request body", http.StatusBadRequest) return } if err := s.deviceManager.PowerGenius().SetFanMode(req.Mode); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) return } s.sendJSON(w, map[string]string{"status": "ok"}) } func (s *Server) sendJSON(w http.ResponseWriter, data interface{}) { w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(data) }