fix(combat): handle JSON string from DB (double-encoded stats_data)

The combat_stats DB column stores stats_data as JSON, but SQLAlchemy
returns it as a string (not a parsed dict). This caused:
- _combat_lifetime_cache loaded a string, merge failed silently
- API endpoints returned string instead of object for lifetime
- Frontend saw lifetime as a string, couldn't read .monsters

Fix: parse JSON string with json.loads() wherever stats_data is read
from DB — in the lifetime cache loader, single-character endpoint,
and all-characters endpoint.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-14 11:14:50 +02:00
parent 0a0fdc5b3d
commit 17f4d4b2aa

29
main.py
View file

@ -1752,10 +1752,17 @@ async def get_combat_stats(character_name: str):
)
if not row:
return {"character_name": character_name, "session": None, "lifetime": None}
sd = row["stats_data"]
if isinstance(sd, str):
import json as _json
try:
sd = _json.loads(sd)
except Exception:
sd = None
return {
"character_name": character_name,
"session": None,
"lifetime": row["stats_data"],
"lifetime": sd,
}
@ -1776,10 +1783,15 @@ async def get_all_combat_stats():
rows = await database.fetch_all("SELECT character_name, stats_data FROM combat_stats")
for r in rows:
if r["character_name"] not in seen:
sd = r["stats_data"]
if isinstance(sd, str):
import json as _json
try: sd = _json.loads(sd)
except Exception: sd = None
results.append({
"character_name": r["character_name"],
"session": None,
"lifetime": r["stats_data"],
"lifetime": sd,
})
results.sort(key=lambda x: x["character_name"])
return {"stats": results}
@ -3028,7 +3040,18 @@ async def ws_receive_snapshots(
"SELECT stats_data FROM combat_stats WHERE character_name = :n",
{"n": char},
)
_combat_lifetime_cache[char] = row["stats_data"] if row else {}
if row and row["stats_data"]:
sd = row["stats_data"]
# DB may return JSON as string — parse if needed
if isinstance(sd, str):
import json as _json
try:
sd = _json.loads(sd)
except Exception:
sd = {}
_combat_lifetime_cache[char] = sd
else:
_combat_lifetime_cache[char] = {}
lifetime = _combat_merge_into_lifetime(
_combat_lifetime_cache[char], delta