package main import ( "context" "encoding/json" "fmt" "io" "net/http" "sync" "time" ) // Suitbuilder endpoints — port of suitbuilder.py's router (mounted at // /suitbuilder in the Python service). The live UI hits /inv/suitbuilder/* on // the tracker, which proxies here; we expose the same contract for parallel // validation. // POST /suitbuilder/search — streams SSE events (event: \ndata: \n\n). func (s *Server) handleSuitSearch(w http.ResponseWriter, r *http.Request) { body, _ := io.ReadAll(io.LimitReader(r.Body, 1<<20)) // Pydantic defaults applied before decode; json.Unmarshal only overwrites // fields present in the body. c := SearchConstraints{IncludeEquipped: true, IncludeInventory: true, MaxResults: 50, SearchTimeout: 300} if err := json.Unmarshal(body, &c); err != nil { writeJSON(w, http.StatusUnprocessableEntity, map[string]any{"detail": "invalid SearchConstraints"}) return } flusher, ok := w.(http.Flusher) if !ok { writeJSON(w, http.StatusInternalServerError, map[string]any{"detail": "streaming unsupported"}) return } h := w.Header() h.Set("Content-Type", "text/event-stream") h.Set("Cache-Control", "no-cache") h.Set("Connection", "keep-alive") h.Set("Access-Control-Allow-Origin", "*") h.Set("Access-Control-Allow-Headers", "Cache-Control") w.WriteHeader(http.StatusOK) var mu sync.Mutex emit := func(event string, data map[string]any) { b, err := json.Marshal(data) if err != nil { b, _ = json.Marshal(map[string]any{"message": "Serialization error: " + err.Error()}) event = "error" } mu.Lock() fmt.Fprintf(w, "event: %s\n", event) fmt.Fprintf(w, "data: %s\n\n", b) flusher.Flush() mu.Unlock() } cancelled := func() bool { select { case <-r.Context().Done(): return true default: return false } } sv := newSolver(s, c, emit, cancelled) sv.Search(r.Context()) } // GET /suitbuilder/characters (suitbuilder.py:2085). func (s *Server) handleSuitCharacters(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 15*time.Second) defer cancel() rows, err := queryRowsAsMaps(ctx, s.pool, `SELECT DISTINCT character_name FROM items ORDER BY character_name`) if err != nil { s.dbErr(w, "suitbuilder/characters", err) return } chars := make([]any, 0, len(rows)) for _, row := range rows { chars = append(chars, row["character_name"]) } writeJSON(w, http.StatusOK, map[string]any{"characters": chars}) } // GET /suitbuilder/sets (suitbuilder.py:2195) — the hardcoded set list. func (s *Server) handleSuitSets(w http.ResponseWriter, r *http.Request) { order := []int{14, 16, 13, 21, 40, 41, 46, 47, 48, 15, 19, 20, 22, 24, 26, 29} sets := make([]map[string]any, 0, len(order)) for _, id := range order { sets = append(sets, map[string]any{"id": id, "name": setNames[id]}) } writeJSON(w, http.StatusOK, map[string]any{"sets": sets}) }