refactor(esi_sso): simplify system ID resolution by using a local cache
This commit is contained in:
197
esi_sso.go
197
esi_sso.go
@@ -50,6 +50,9 @@ type ESISSO struct {
|
||||
server *http.Server
|
||||
|
||||
db *gorm.DB
|
||||
|
||||
nameCacheOnce sync.Once
|
||||
nameToID map[string]int64 // lowercased name -> id
|
||||
}
|
||||
|
||||
type SolarSystem struct {
|
||||
@@ -556,172 +559,58 @@ func parseTokenCharacter(jwt string) (name string, id int64) {
|
||||
return
|
||||
}
|
||||
|
||||
// ResolveSystemIDByName ... DB-first then ESI fallbacks
|
||||
func (s *ESISSO) ResolveSystemIDByName(ctx context.Context, name string) (int64, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
if name == "" {
|
||||
return 0, errors.New("empty system name")
|
||||
}
|
||||
if s.db != nil {
|
||||
var sys SolarSystem
|
||||
if err := s.db.Where("solarSystemName = ?", name).First(&sys).Error; err == nil && sys.SolarSystemID != 0 {
|
||||
fmt.Printf("DB: resolved %q -> %d\n", name, sys.SolarSystemID)
|
||||
return sys.SolarSystemID, nil
|
||||
}
|
||||
}
|
||||
// Fallbacks (universe/ids -> strict search -> non-strict + names)
|
||||
type idsReq struct{ Names []string `json:"names"` }
|
||||
body, _ := json.Marshal(idsReq{Names: []string{name}})
|
||||
idsURL := esiBase + "/v3/universe/ids/?datasource=tranquility"
|
||||
fmt.Printf("ESI: resolve system id via universe/ids for name=%q\n", name)
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, idsURL, strings.NewReader(string(body)))
|
||||
if err == nil {
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
resp, errDo := http.DefaultClient.Do(req)
|
||||
if errDo == nil {
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode == http.StatusOK {
|
||||
var idsResp struct{ Systems []struct{ ID int64 `json:"id"`; Name string `json:"name"` } `json:"systems"` }
|
||||
if err := json.NewDecoder(resp.Body).Decode(&idsResp); err == nil {
|
||||
if len(idsResp.Systems) > 0 {
|
||||
fmt.Printf("ESI: universe/ids hit: %s -> %d\n", idsResp.Systems[0].Name, idsResp.Systems[0].ID)
|
||||
return idsResp.Systems[0].ID, nil
|
||||
}
|
||||
}
|
||||
// ensureNameCache loads a lowercase name->id map from the local DB once
|
||||
func (s *ESISSO) ensureNameCache() error {
|
||||
var err error
|
||||
s.nameCacheOnce.Do(func() {
|
||||
cache := make(map[string]int64, 50000)
|
||||
if s.db != nil {
|
||||
var rows []SolarSystem
|
||||
// Only select required columns
|
||||
if e := s.db.Select("solarSystemID, solarSystemName").Find(&rows).Error; e != nil {
|
||||
err = e
|
||||
return
|
||||
}
|
||||
for _, r := range rows {
|
||||
cache[strings.ToLower(r.SolarSystemName)] = r.SolarSystemID
|
||||
}
|
||||
}
|
||||
}
|
||||
q := url.Values{}
|
||||
q.Set("categories", "solar_system")
|
||||
q.Set("search", name)
|
||||
q.Set("strict", "true")
|
||||
searchURL := esiBase + "/v3/search/?" + q.Encode()
|
||||
fmt.Printf("ESI: strict search for %q\n", name)
|
||||
req2, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
req2.Header.Set("Accept", "application/json")
|
||||
resp2, err := http.DefaultClient.Do(req2)
|
||||
if err == nil {
|
||||
defer resp2.Body.Close()
|
||||
if resp2.StatusCode == http.StatusOK {
|
||||
var payload struct{ SolarSystem []int64 `json:"solar_system"` }
|
||||
if err := json.NewDecoder(resp2.Body).Decode(&payload); err == nil && len(payload.SolarSystem) > 0 {
|
||||
fmt.Printf("ESI: strict search hit: %d\n", payload.SolarSystem[0])
|
||||
return payload.SolarSystem[0], nil
|
||||
}
|
||||
}
|
||||
}
|
||||
q.Set("strict", "false")
|
||||
searchURL2 := esiBase + "/v3/search/?" + q.Encode()
|
||||
fmt.Printf("ESI: non-strict search for %q\n", name)
|
||||
req3, err := http.NewRequestWithContext(ctx, http.MethodGet, searchURL2, nil)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
req3.Header.Set("Accept", "application/json")
|
||||
resp3, err := http.DefaultClient.Do(req3)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp3.Body.Close()
|
||||
if resp3.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp3.Body)
|
||||
return 0, fmt.Errorf("search failed: %s: %s", resp3.Status, string(b))
|
||||
}
|
||||
var payload struct{ SolarSystem []int64 `json:"solar_system"` }
|
||||
if err := json.NewDecoder(resp3.Body).Decode(&payload); err != nil || len(payload.SolarSystem) == 0 {
|
||||
return 0, fmt.Errorf("system not found: %s", name)
|
||||
}
|
||||
if len(payload.SolarSystem) == 1 {
|
||||
fmt.Printf("ESI: non-strict search single hit: %d\n", payload.SolarSystem[0])
|
||||
return payload.SolarSystem[0], nil
|
||||
}
|
||||
ids := payload.SolarSystem
|
||||
namesURL := esiBase + "/v3/universe/names/?datasource=tranquility"
|
||||
idsBody, _ := json.Marshal(ids)
|
||||
req4, err := http.NewRequestWithContext(ctx, http.MethodPost, namesURL, strings.NewReader(string(idsBody)))
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
req4.Header.Set("Content-Type", "application/json")
|
||||
req4.Header.Set("Accept", "application/json")
|
||||
resp4, err := http.DefaultClient.Do(req4)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
defer resp4.Body.Close()
|
||||
if resp4.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp4.Body)
|
||||
return 0, fmt.Errorf("names lookup failed: %s: %s", resp4.Status, string(b))
|
||||
}
|
||||
var namesResp []struct{ Category string `json:"category"`; ID int64 `json:"id"`; Name string `json:"name"` }
|
||||
if err := json.NewDecoder(resp4.Body).Decode(&namesResp); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
lower := strings.ToLower(name)
|
||||
for _, n := range namesResp {
|
||||
if n.Category == "solar_system" && strings.ToLower(n.Name) == lower {
|
||||
fmt.Printf("ESI: names resolved exact: %s -> %d\n", n.Name, n.ID)
|
||||
return n.ID, nil
|
||||
}
|
||||
}
|
||||
fmt.Printf("ESI: names resolved fallback: returning %d for %q\n", namesResp[0].ID, name)
|
||||
return namesResp[0].ID, nil
|
||||
s.nameToID = cache
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// ResolveSystemIDsByNames returns IDs in the same order as names. Missing entries error.
|
||||
// ResolveSystemIDByName resolves using ONLY the local DB cache (case-insensitive)
|
||||
func (s *ESISSO) ResolveSystemIDByName(ctx context.Context, name string) (int64, error) {
|
||||
if err := s.ensureNameCache(); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
key := strings.ToLower(strings.TrimSpace(name))
|
||||
if id, ok := s.nameToID[key]; ok {
|
||||
return id, nil
|
||||
}
|
||||
return 0, fmt.Errorf("system not found in local DB: %s", name)
|
||||
}
|
||||
|
||||
// ResolveSystemIDsByNames returns IDs in the same order as names using ONLY the local DB cache
|
||||
func (s *ESISSO) ResolveSystemIDsByNames(ctx context.Context, names []string) ([]int64, error) {
|
||||
ordered := make([]int64, len(names))
|
||||
if len(names) == 0 {
|
||||
return ordered, nil
|
||||
if err := s.ensureNameCache(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// DB first
|
||||
nameLower := make([]string, len(names))
|
||||
for i, n := range names { nameLower[i] = strings.TrimSpace(n) }
|
||||
found := map[string]int64{}
|
||||
if s.db != nil {
|
||||
var rows []SolarSystem
|
||||
if err := s.db.Where("solarSystemName IN ?", nameLower).Find(&rows).Error; err == nil {
|
||||
for _, r := range rows {
|
||||
found[r.SolarSystemName] = r.SolarSystemID
|
||||
}
|
||||
}
|
||||
}
|
||||
// Collect missing for ESI batch
|
||||
out := make([]int64, len(names))
|
||||
missing := []string{}
|
||||
for _, n := range nameLower {
|
||||
if _, ok := found[n]; !ok {
|
||||
for i, n := range names {
|
||||
key := strings.ToLower(strings.TrimSpace(n))
|
||||
if id, ok := s.nameToID[key]; ok {
|
||||
out[i] = id
|
||||
} else {
|
||||
missing = append(missing, n)
|
||||
}
|
||||
}
|
||||
if len(missing) > 0 {
|
||||
idsURL := esiBase + "/v3/universe/ids/?datasource=tranquility"
|
||||
body, _ := json.Marshal(struct{ Names []string `json:"names"` }{Names: missing})
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, idsURL, strings.NewReader(string(body)))
|
||||
if err != nil { return nil, err }
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Accept", "application/json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil { return nil, err }
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
b, _ := io.ReadAll(resp.Body)
|
||||
return nil, fmt.Errorf("universe/ids failed: %s: %s", resp.Status, string(b))
|
||||
}
|
||||
var idsResp struct{ Systems []struct{ ID int64 `json:"id"`; Name string `json:"name"` } `json:"systems"` }
|
||||
if err := json.NewDecoder(resp.Body).Decode(&idsResp); err != nil { return nil, err }
|
||||
for _, it := range idsResp.Systems { found[it.Name] = it.ID }
|
||||
return nil, fmt.Errorf("systems not found in local DB: %s", strings.Join(missing, ", "))
|
||||
}
|
||||
// Build ordered list
|
||||
for i, n := range nameLower {
|
||||
id, ok := found[n]
|
||||
if !ok { return nil, fmt.Errorf("system not found: %s", n) }
|
||||
ordered[i] = id
|
||||
}
|
||||
return ordered, nil
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// PostRouteForAll clears route and posts vias then destination last
|
||||
|
Reference in New Issue
Block a user