diff --git a/lyra/poker.py b/lyra/poker.py index f06428a..2722e22 100644 --- a/lyra/poker.py +++ b/lyra/poker.py @@ -1023,6 +1023,25 @@ def upsert_player(name: str, venue: str | None = None, description: str | None = return int(cur.lastrowid) +_PLAYER_FIELDS = ("name", "venue", "description", "tendencies", "adjustment", "category") + + +def update_player(player_id: int, **fields) -> dict | None: + """Edit a player's dossier (rename, fix tendencies/category). Returns the row or None.""" + sets, vals = [], [] + for k, v in fields.items(): + if k in _PLAYER_FIELDS and v is not None: + sets.append(f"{k} = ?") + vals.append(v) + if sets: + conn = _c() + with conn: + conn.execute(f"UPDATE poker_players SET {', '.join(sets)} WHERE id = ?", + (*vals, player_id)) + row = _c().execute("SELECT * FROM poker_players WHERE id = ?", (player_id,)).fetchone() + return dict(row) if row else None + + def add_read(note: str, seat: str | None = None, name: str | None = None, session_id: int | None = None, **player_fields) -> int: """Log a live read. If `name` is given, upsert the player and link the read.""" diff --git a/lyra/web/server.py b/lyra/web/server.py index 931b9f2..921f21a 100644 --- a/lyra/web/server.py +++ b/lyra/web/server.py @@ -189,6 +189,26 @@ def create_app() -> FastAPI: ok = await asyncio.to_thread(poker.delete_entry, "hand", hand_id) return {"ok": ok} + @app.post("/session/read") + async def session_add_read(request: Request) -> dict: + """Log a read directly (no LLM); upserts the villain file when name is given.""" + body = await request.json() + rid = await asyncio.to_thread(lambda: poker.add_read( + note=body.get("note") or "", seat=body.get("seat"), name=body.get("name"), + tendencies=body.get("tendencies"), adjustment=body.get("adjustment"), + description=body.get("description"), category=body.get("category"), + venue=body.get("venue"), + )) + return {"ok": True, "id": rid} + + @app.patch("/player/{player_id}") + async def player_update(player_id: int, request: Request) -> dict: + """Edit a player's dossier (rename, fix tendencies).""" + body = await request.json() + p = await asyncio.to_thread(lambda: poker.update_player(player_id, **body)) + logbus.log("info", "player edited", id=player_id, fields=list(body)) + return {"ok": p is not None, "player": p} + @app.delete("/session/entry/{kind}/{entry_id}") async def delete_entry(kind: str, entry_id: int) -> dict: """Delete one HUD entry (hand | stack | read | ritual) by id.""" diff --git a/tests/test_poker_api.py b/tests/test_poker_api.py index 34d49d3..2b48799 100644 --- a/tests/test_poker_api.py +++ b/tests/test_poker_api.py @@ -64,3 +64,19 @@ def test_post_hand_edit_and_delete(client): r3 = c.delete(f"/hand/{hid}") assert r3.json()["ok"] is True assert poker.get_hand(hid) is None + + +def test_post_read(client): + c, poker = client + poker.start_session(buy_in=400) + r = c.post("/session/read", json={"note": "3-bets light", "name": "James K"}) + assert r.json()["ok"] is True + assert isinstance(r.json()["id"], int) + + +def test_rename_player_fixes_mislabel(client): + c, poker = client + pid = poker.upsert_player("Dave the rock", category="reg") + r = c.patch(f"/player/{pid}", json={"name": "Dave the mechanic"}) + assert r.json()["ok"] is True + assert r.json()["player"]["name"] == "Dave the mechanic" diff --git a/tests/test_poker_contract.py b/tests/test_poker_contract.py index f0521c7..2cc6bf7 100644 --- a/tests/test_poker_contract.py +++ b/tests/test_poker_contract.py @@ -14,3 +14,20 @@ def test_llm_tool_required_args_match_contract(): assert required == set(decl["required"]), ( f"{op}: tools spec required {required} != contract {set(decl['required'])}" ) + + +def test_rest_routes_registered(): + import lyra.web.server as server + registered = set() + for route in server.app.routes: + methods = getattr(route, "methods", None) + path = getattr(route, "path", None) + if not methods or not path: + continue + for m in methods: + registered.add((m, path)) + for op, decl in OPERATIONS.items(): + if not decl["rest"]: + continue + method, path = decl["rest"] + assert (method, path) in registered, f"{op}: {method} {path} not registered"