MosswartOverlord/go-services/tracker-go/memstate.go
Erik 5b2db439a3 feat(go-services): tracker share_* handlers (complete ingest) + shadow tuning
- share.go: cross-machine vital sharing (share_subscribe/unsubscribe/share_*),
  faithful port of the peer-state snapshot + plugin fan-out + /vital-sharing/peers.
  The last ingest handler — the Go tracker now handles every plugin event type.
- shadow consumer: drop the outbound keepalive ping (the firehose is never idle)
  and tighten the read-deadline watchdog to 12s for faster reconnect after the
  upstream's periodic eviction (full-firehose browser clients get evicted ~every
  90s; the watchdog recovers it, ~90% duty cycle). Production-bound /ws/position
  is unaffected (plugins connect to us; no eviction).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 11:27:25 +02:00

95 lines
2.7 KiB
Go

package main
import (
"encoding/json"
"net/http"
"os"
"path/filepath"
"sort"
)
// These endpoints are backed by ingest-only in-memory state in the Python
// service (populated from /ws/position events). Phase 1 has no ingest, so they
// return the same empty/default shapes the Python service emits when no data is
// present — preserving the API contract for the frontend.
// GET /quest-status (main.py:1940)
func (s *Server) handleQuestStatus(w http.ResponseWriter, r *http.Request) {
questData := map[string]any{}
playerCount := 0
if s.ingestor != nil {
qd, n := s.ingestor.questData()
playerCount = n
for c, qs := range qd {
m := map[string]any{}
for k, v := range qs {
m[k] = v
}
questData[c] = m
}
}
writeJSON(w, http.StatusOK, map[string]any{
"quest_data": questData,
"tracked_quests": []string{
"Stipend Collection Timer",
"Blank Augmentation Gem Pickup Timer",
"Insatiable Eater Jaw",
},
"player_count": playerCount,
})
}
// GET /vital-sharing/peers (main.py:1800)
func (s *Server) handleVitalSharingPeers(w http.ResponseWriter, r *http.Request) {
if s.ingestor == nil {
writeJSON(w, http.StatusOK, map[string]any{"peers": []any{}, "subscriber_count": 0})
return
}
peers, subCount := s.ingestor.vitalSharingPeers()
sort.Slice(peers, func(i, j int) bool {
return toStr(peers[i]["character_name"]) < toStr(peers[j]["character_name"])
})
writeJSON(w, http.StatusOK, map[string]any{"peers": peers, "subscriber_count": subCount})
}
// GET /equipment-cantrip-state/{name} (main.py:4167)
func (s *Server) handleEquipmentCantrip(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("name")
if s.ingestor != nil {
if v, ok := s.ingestor.getEquipmentCantrip(name); ok {
writeJSON(w, http.StatusOK, v)
return
}
}
writeJSON(w, http.StatusOK, map[string]any{
"type": "equipment_cantrip_state",
"character_name": name,
"items": []any{},
})
}
// GET /issues — flat-file issue board. (main.py:1709)
func (s *Server) handleIssues(w http.ResponseWriter, r *http.Request) {
issues := s.loadIssues()
writeJSON(w, http.StatusOK, map[string]any{"issues": issues})
}
func (s *Server) loadIssues() []any {
empty := []any{}
b, err := os.ReadFile(filepath.Join(s.staticDir, "openissues.json"))
if err != nil {
return empty
}
var v []any
if json.Unmarshal(b, &v) != nil {
return empty
}
return v
}
// GET /me — current user. Phase 1 has no session-cookie verification yet, so
// (like the Python service for an unauthenticated request) this is 401. The
// loopback internal-trust path carries no user identity. (main.py:1455)
func (s *Server) handleMe(w http.ResponseWriter, r *http.Request) {
writeJSON(w, http.StatusUnauthorized, map[string]any{"detail": "Not authenticated"})
}