fix: address code review findings for inventory delta feature

- Fix remaining f-string SQL injection in process_inventory (same pattern
  as single-item endpoints: parameterized ANY(:ids) queries)
- Add null guard for item_id in backend delta remove handler
- Add response status logging for inventory service HTTP calls
- Fix frontend ID fallback consistency in updateInventoryLive
- Replace debug print() with logger.debug()
- Add comment for Decal Slot_Decal magic number

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
erik 2026-02-28 15:58:10 +00:00
parent f145e6e131
commit 973c3722bc
3 changed files with 22 additions and 18 deletions

View file

@ -1357,16 +1357,15 @@ async def process_inventory(inventory: InventoryItem):
item_ids = await database.fetch_all(item_ids_query, {"character_name": inventory.character_name}) item_ids = await database.fetch_all(item_ids_query, {"character_name": inventory.character_name})
if item_ids: if item_ids:
id_list = [str(row['id']) for row in item_ids] db_ids = [row['id'] for row in item_ids]
id_placeholder = ','.join(id_list)
# Delete from all related tables first # Delete from all related tables first
await database.execute(f"DELETE FROM item_raw_data WHERE item_id IN ({id_placeholder})") for table in ('item_raw_data', 'item_combat_stats', 'item_requirements',
await database.execute(f"DELETE FROM item_combat_stats WHERE item_id IN ({id_placeholder})") 'item_enhancements', 'item_ratings', 'item_spells'):
await database.execute(f"DELETE FROM item_requirements WHERE item_id IN ({id_placeholder})") await database.execute(
await database.execute(f"DELETE FROM item_enhancements WHERE item_id IN ({id_placeholder})") sa.text(f"DELETE FROM {table} WHERE item_id = ANY(:ids)"),
await database.execute(f"DELETE FROM item_ratings WHERE item_id IN ({id_placeholder})") {"ids": db_ids}
await database.execute(f"DELETE FROM item_spells WHERE item_id IN ({id_placeholder})") )
# Finally delete from main items table # Finally delete from main items table
await database.execute( await database.execute(
@ -1419,7 +1418,7 @@ async def process_inventory(inventory: InventoryItem):
# Container/position tracking # Container/position tracking
container_id=item_data.get('ContainerId', 0), container_id=item_data.get('ContainerId', 0),
slot=int(item_data.get('IntValues', {}).get('231735296', item_data.get('IntValues', {}).get(231735296, -1))), slot=int(item_data.get('IntValues', {}).get('231735296', item_data.get('IntValues', {}).get(231735296, -1))), # Decal Slot_Decal key
# Item state # Item state
bonded=basic['bonded'], bonded=basic['bonded'],
@ -3743,7 +3742,7 @@ async def get_available_items_by_slot(
# Debug: let's see how many items Barris actually has first # Debug: let's see how many items Barris actually has first
debug_query = f"SELECT COUNT(*) as total FROM items WHERE {char_filter}" debug_query = f"SELECT COUNT(*) as total FROM items WHERE {char_filter}"
debug_result = await database.fetch_one(debug_query, query_params) debug_result = await database.fetch_one(debug_query, query_params)
print(f"DEBUG: Total items for query: {debug_result['total']}") logger.debug(f"Total items for query: {debug_result['total']}")
# Main query to get items with slot information # Main query to get items with slot information
query = f""" query = f"""

15
main.py
View file

@ -1987,18 +1987,23 @@ async def ws_receive_snapshots(
if action == "remove": if action == "remove":
item_id = data.get("item_id") item_id = data.get("item_id")
async with httpx.AsyncClient(timeout=10.0) as client: if item_id is not None:
await client.delete( async with httpx.AsyncClient(timeout=10.0) as client:
f"{INVENTORY_SERVICE_URL}/inventory/{char_name}/item/{item_id}" resp = await client.delete(
) f"{INVENTORY_SERVICE_URL}/inventory/{char_name}/item/{item_id}"
)
if resp.status_code >= 400:
logger.warning(f"Inventory service returned {resp.status_code} for delta remove item_id={item_id}")
elif action in ("add", "update"): elif action in ("add", "update"):
item = data.get("item") item = data.get("item")
if item: if item:
async with httpx.AsyncClient(timeout=10.0) as client: async with httpx.AsyncClient(timeout=10.0) as client:
await client.post( resp = await client.post(
f"{INVENTORY_SERVICE_URL}/inventory/{char_name}/item", f"{INVENTORY_SERVICE_URL}/inventory/{char_name}/item",
json=item json=item
) )
if resp.status_code >= 400:
logger.warning(f"Inventory service returned {resp.status_code} for delta {action}")
# Broadcast delta to all browser clients # Broadcast delta to all browser clients
await _broadcast_to_browser_clients(data) await _broadcast_to_browser_clients(data)

View file

@ -1036,14 +1036,14 @@ function updateInventoryLive(delta) {
if (!grid) return; if (!grid) return;
if (delta.action === 'remove') { if (delta.action === 'remove') {
const itemId = delta.item_id; const itemId = delta.item_id || (delta.item && (delta.item.Id || delta.item.id));
const existing = grid.querySelector(`[data-item-id="${itemId}"]`); const existing = grid.querySelector(`[data-item-id="${itemId}"]`);
if (existing) existing.remove(); if (existing) existing.remove();
} else if (delta.action === 'add') { } else if (delta.action === 'add') {
const newSlot = createInventorySlot(delta.item); const newSlot = createInventorySlot(delta.item);
grid.appendChild(newSlot); grid.appendChild(newSlot);
} else if (delta.action === 'update') { } else if (delta.action === 'update') {
const itemId = delta.item.Id || delta.item.id; const itemId = delta.item.Id || delta.item.id || delta.item.item_id;
const existing = grid.querySelector(`[data-item-id="${itemId}"]`); const existing = grid.querySelector(`[data-item-id="${itemId}"]`);
if (existing) { if (existing) {
const newSlot = createInventorySlot(delta.item); const newSlot = createInventorySlot(delta.item);