summaryrefslogtreecommitdiff
path: root/backend-go/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'backend-go/cmd')
-rw-r--r--backend-go/cmd/eve_haul_go/main.go306
1 files changed, 306 insertions, 0 deletions
diff --git a/backend-go/cmd/eve_haul_go/main.go b/backend-go/cmd/eve_haul_go/main.go
new file mode 100644
index 0000000..12f9b00
--- /dev/null
+++ b/backend-go/cmd/eve_haul_go/main.go
@@ -0,0 +1,306 @@
+package main
+
+import (
+"context"
+"encoding/json"
+"fmt"
+"log"
+"net/http"
+"os"
+"os/signal"
+"syscall"
+"strings"
+"time"
+
+"github.com/joho/godotenv"
+
+"eve_haul_go/internal/api"
+"eve_haul_go/internal/core"
+"eve_haul_go/internal/esi"
+)
+
+type EventStreamData struct {
+Route []core.RouteStep `json:"route"`
+RouteInfo *core.RouteInfo `json:"route_info"`
+Log []string `json:"log"`
+}
+
+func main() {
+if err := godotenv.Overload("../.env"); err != nil {
+fmt.Printf("Error loading .env file: %v\n", err)
+}
+
+log.SetFlags(log.LstdFlags | log.Lmicroseconds)
+
+os.Chdir("../backend/src") // Match Python's working directory so data/cache works
+
+// Check if setup is needed
+dataReady, authReady := isSetupReady()
+
+var manager *core.Manager
+var managerReady bool = false
+
+if dataReady && authReady {
+log.Println("Prerequisites found. Initializing Loader...")
+dl := core.NewDataLoader("../data")
+
+items, err := dl.LoadItems()
+if err != nil {
+log.Fatalf("Error loading items: %v", err)
+}
+log.Printf("Loaded %d items", len(items))
+
+stations, solarSystems, err := dl.LoadMapData()
+if err != nil {
+log.Fatalf("Error loading map data: %v", err)
+}
+log.Printf("Loaded %d stations", len(stations))
+
+jumps, err := dl.LoadStarGateConnections()
+if err != nil {
+log.Fatalf("Error loading stargate jumps: %v", err)
+}
+
+ship := &core.Ship{
+ MaxWarpSpeed: 4.55,
+ MaxSubwarpSpeed: 166.9,
+ PlayerCost: 15000000.0 / 3600.0,
+ RiskCost: 60000000.0,
+ GankersAreas: map[string]float64{"Ahbazon": 0.5},
+ TaxRate: 8.0,
+ ProfitThreshold: 100000.0,
+ MaxCapital: 20000000,
+ SnipeThreshold: 20000000.0,
+ Cargo: 4000,
+}
+
+graph := core.NewGraph(stations, solarSystems, ship)
+graph.Build(jumps)
+log.Printf("Graph built with %d nodes", len(graph.Adj))
+
+if os.Getenv("character_id") == "" {
+ log.Fatalf("character_id missing in .env but auth present")
+}
+
+client := esi.NewEsiClient("./cache/auth.json")
+
+manager = core.NewManager(client, graph)
+
+market, err := core.NewMarket(items, client)
+if err == nil {
+manager.Market = market
+} else {
+log.Printf("Warning: Failed to init market: %v", err)
+}
+
+go func() {
+for {
+log.Println("Starting manager update")
+if err := manager.Update(context.Background()); err != nil {
+log.Printf("An error occurred during manager update: %v", err)
+}
+log.Println("Manager update completed")
+time.Sleep(2 * time.Second)
+}
+}()
+managerReady = true
+} else {
+log.Println("Missing data or auth. Starting in setup mode...")
+}
+
+mux := httpNewServeOptionsMux(manager, managerReady)
+
+server := &http.Server{
+Addr: ":8000",
+Handler: mux,
+}
+
+go func() {
+ log.Println("Listening on :8000")
+ if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
+ log.Fatalf("Server failed: %v", err)
+ }
+}()
+
+quit := make(chan os.Signal, 1)
+signal.Notify(quit, os.Interrupt, syscall.SIGTERM)
+<-quit
+log.Println("Shutting down server gracefully...")
+
+ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
+defer cancel()
+if err := server.Shutdown(ctx); err != nil {
+ log.Printf("Server forced to shutdown: %v", err)
+ os.Exit(0)
+}
+log.Println("Server exited")
+}
+
+func isSetupReady() (bool, bool) {
+dataReady := true
+authReady := true
+
+if _, err := os.Stat("../data/mapDenormalize.csv"); err != nil { dataReady = false }
+if _, err := os.Stat("../data/mapJumps.csv"); err != nil { dataReady = false }
+if _, err := os.Stat("../data/invTypes.csv"); err != nil { dataReady = false }
+if _, err := os.Stat("../data/mapRegions.csv"); err != nil { dataReady = false }
+
+if _, err := os.Stat("./cache/auth.json"); err != nil { authReady = false }
+
+return dataReady, authReady
+}
+
+type serveMuxCORS struct {
+mux *http.ServeMux
+}
+
+func (s *serveMuxCORS) ServeHTTP(w http.ResponseWriter, r *http.Request) {
+w.Header().Set("Access-Control-Allow-Origin", "http://localhost:3000")
+w.Header().Set("Access-Control-Allow-Credentials", "true")
+w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
+w.Header().Set("Access-Control-Allow-Headers", "Content-Type, Authorization")
+if r.Method == "OPTIONS" {
+w.WriteHeader(http.StatusOK)
+return
+}
+s.mux.ServeHTTP(w, r)
+}
+
+func httpNewServeOptionsMux(manager *core.Manager, ready bool) http.Handler {
+mux := http.NewServeMux()
+
+// Setup Routes
+mux.HandleFunc("/api/setup/status", api.StatusHandler)
+mux.HandleFunc("/api/setup/data", api.DownloadDataHandler)
+mux.HandleFunc("/api/setup/auth/url", api.GetAuthURLHandler)
+mux.HandleFunc("/api/setup/auth/callback", api.CompleteAuthHandler)
+
+mux.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+json.NewEncoder(w).Encode(map[string]bool{"ready": ready})
+})
+
+if !ready {
+// Degraded mode routes
+mux.HandleFunc("/data_stream", func(w http.ResponseWriter, r *http.Request) {
+w.Header().Set("Content-Type", "text/event-stream")
+w.Header().Set("Cache-Control", "no-cache")
+w.Header().Set("Connection", "keep-alive")
+fmt.Fprintf(w, "data: %s\n\n", `{"route": [], "route_info": null, "log": ["System pending setup."]}`)
+w.(http.Flusher).Flush()
+})
+return &serveMuxCORS{mux: mux}
+}
+
+mux.HandleFunc("/route", func(w http.ResponseWriter, r *http.Request) {
+if manager.Route == nil {
+json.NewEncoder(w).Encode([]core.RouteStep{})
+return
+}
+json.NewEncoder(w).Encode(manager.Route)
+})
+
+mux.HandleFunc("/route_info", func(w http.ResponseWriter, r *http.Request) {
+if manager.RouteInfo == nil {
+json.NewEncoder(w).Encode(nil)
+return
+}
+json.NewEncoder(w).Encode(manager.RouteInfo)
+})
+
+mux.HandleFunc("/create_route", func(w http.ResponseWriter, r *http.Request) {
+if r.Method != http.MethodPost {
+http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+return
+}
+go manager.CreateRoute(context.Background())
+json.NewEncoder(w).Encode(map[string]string{"status": "Route creation initiated"})
+})
+
+mux.HandleFunc("/data_stream", func(w http.ResponseWriter, r *http.Request) {
+w.Header().Set("Content-Type", "text/event-stream")
+w.Header().Set("Cache-Control", "no-cache")
+w.Header().Set("Connection", "keep-alive")
+
+flusher, ok := w.(http.Flusher)
+if !ok {
+http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
+return
+}
+
+for {
+data := EventStreamData{
+Route: manager.Route,
+RouteInfo: manager.RouteInfo,
+Log: manager.Log,
+}
+
+b, _ := json.Marshal(data)
+fmt.Fprintf(w, "data: %s\n\n", string(b))
+flusher.Flush()
+
+select {
+case <-r.Context().Done():
+return
+case <-time.After(1 * time.Second):
+}
+}
+})
+
+mux.HandleFunc("/logs", func(w http.ResponseWriter, r *http.Request) {
+json.NewEncoder(w).Encode(manager.Log)
+})
+
+mux.HandleFunc("/open_market_window/", func(w http.ResponseWriter, r *http.Request) {
+if r.Method != http.MethodPost {
+http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+return
+}
+parts := strings.Split(r.URL.Path, "/")
+if len(parts) > 2 {
+typeID := parts[2]
+_ = typeID
+log.Printf("Opened market window for type_id %s", typeID)
+}
+json.NewEncoder(w).Encode(map[string]string{"status": "opened"})
+})
+
+mux.HandleFunc("/settings", func(w http.ResponseWriter, r *http.Request) {
+if r.Method == http.MethodGet {
+json.NewEncoder(w).Encode(manager.Ship)
+return
+}
+
+if r.Method == http.MethodPost {
+var newSettings core.Ship
+if err := json.NewDecoder(r.Body).Decode(&newSettings); err != nil {
+http.Error(w, err.Error(), http.StatusBadRequest)
+return
+}
+manager.Ship = newSettings
+manager.Graph.Ship = &manager.Ship
+
+json.NewEncoder(w).Encode(map[string]interface{}{
+"status": "success",
+"settings": manager.Ship,
+})
+return
+}
+http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+})
+
+mux.HandleFunc("/set_waypoints", func(w http.ResponseWriter, r *http.Request) {
+if r.Method != http.MethodPost {
+http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
+return
+}
+
+err := manager.SetWaypoints()
+if err != nil {
+http.Error(w, err.Error(), http.StatusInternalServerError)
+return
+}
+json.NewEncoder(w).Encode(map[string]string{"status": "success"})
+})
+
+return &serveMuxCORS{mux: mux}
+}