MosswartOverlord/go-services/inventory-go/inventory_char.go
Erik db534ea389 fix(inventory-go): restore GET /inventory/{name} (live Inv window was empty)
The Go cutover omitted get_character_inventory; the React InventoryWindow
fetches GET /inventory/{name} and got 404 -> empty. Port the endpoint:
per-character items with placement (current_wielded_location/container_id/
items_capacity), mana (current_mana/max_mana from original_json IntValues),
icon overlays, and join-table combat/req/enh/rating stats; material-prefixed
name. Returns {character_name,item_count,items}.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 21:44:16 +02:00

92 lines
3.6 KiB
Go

package main
import (
"net/http"
"strings"
)
// GET /inventory/{character_name} — full per-character inventory for the React
// Inventory window. Port of inventory-service get_character_inventory +
// enrich_db_item (main.py:2622 / 2338). The Go cutover omitted this endpoint
// (it was assumed unused), but the React InventoryWindow fetches it, so its
// absence (404) made the live inventory render empty.
//
// Returns {character_name, item_count, items:[...]} with the snake_case fields
// the frontend normalizeItem consumes: placement via current_wielded_location /
// container_id / items_capacity, the mana panel via current_mana / max_mana,
// icon overlays, plus tooltip combat/requirement/enhancement/rating stats. Mana
// and icon overlays are pulled straight from original_json IntValues (same keys
// the plugin/search path use); the rest come from the normalized join tables.
func (s *Server) handleCharacterInventory(w http.ResponseWriter, r *http.Request) {
name := r.PathValue("character_name")
limit := clampInt(qIntDefault(r.URL.Query(), "limit", 1000), 1, 5000)
offset := qIntDefault(r.URL.Query(), "offset", 0)
if offset < 0 {
offset = 0
}
const q = `
SELECT
i.item_id, i.name, i.icon, i.object_class, i.value, i.burden,
i.current_wielded_location, i.container_id, i.items_capacity, i.stack_size,
cs.max_damage, cs.armor_level, cs.damage_bonus, cs.attack_bonus,
cs.melee_defense_bonus, cs.magic_defense_bonus,
r.wield_level, r.skill_level, r.equip_skill, r.lore_requirement,
e.material, e.imbue, e.item_set, e.tinks, e.workmanship,
rt.damage_rating, rt.crit_rating, rt.crit_damage_rating, rt.heal_boost_rating,
NULLIF((rd.original_json->'IntValues'->>'218103815')::int, 0) AS current_mana,
NULLIF((rd.original_json->'IntValues'->>'218103814')::int, 0) AS max_mana,
NULLIF((rd.original_json->'IntValues'->>'218103849')::int, 0) AS icon_overlay_id,
NULLIF((rd.original_json->'IntValues'->>'218103850')::int, 0) AS icon_underlay_id
FROM items i
LEFT JOIN item_combat_stats cs ON i.id = cs.item_id
LEFT JOIN item_requirements r ON i.id = r.item_id
LEFT JOIN item_enhancements e ON i.id = e.item_id
LEFT JOIN item_ratings rt ON i.id = rt.item_id
LEFT JOIN item_raw_data rd ON i.id = rd.item_id
WHERE i.character_name = $1
ORDER BY i.name
LIMIT $2 OFFSET $3`
rows, err := queryRowsAsMaps(r.Context(), s.pool, q, name, limit, offset)
if err != nil {
s.dbErr(w, "inventory/"+name, err)
return
}
items := make([]map[string]any, 0, len(rows))
for _, row := range rows {
items = append(items, enrichInventoryRow(row))
}
// Unlike the Python endpoint (404 on no rows), always return 200 with a
// possibly-empty list — the window treats both as empty, and 200 avoids the
// frontend's catch-all error path.
writeJSON(w, http.StatusOK, map[string]any{
"character_name": name,
"item_count": len(items),
"items": items,
})
}
// enrichInventoryRow flattens a joined inventory row into the frontend item
// shape: drops NULL columns and applies the material-name prefix to the item
// name (enrich_db_item parity, e.g. "Pyreal" + "Chiran Helm" -> "Pyreal Chiran
// Helm"), preserving the un-prefixed name in original_name.
func enrichInventoryRow(row map[string]any) map[string]any {
out := make(map[string]any, len(row)+2)
for k, v := range row {
if v != nil {
out[k] = v
}
}
if mat, ok := out["material"].(string); ok && mat != "" {
out["material_name"] = mat
if name, ok := out["name"].(string); ok && name != "" &&
!strings.HasPrefix(strings.ToLower(name), strings.ToLower(mat)) {
out["name"] = mat + " " + name
out["original_name"] = name
}
}
return out
}