diff --git a/inventory-service/comprehensive_enum_database_v2.json b/inventory-service/comprehensive_enum_database_v2.json index fd7ec101..2afa8a16 100644 --- a/inventory-service/comprehensive_enum_database_v2.json +++ b/inventory-service/comprehensive_enum_database_v2.json @@ -2188,7 +2188,7 @@ "dictionaries": { "AttributeSetInfo": { "name": "AttributeSetInfo", - "description": "Equipment set names", + "description": "Equipment set names (complete from Dictionaries.cs)", "values": { "2": "Test", "4": "Carraida's Benediction", @@ -2210,7 +2210,7 @@ "20": "Dexterous Set", "21": "Wise Set", "22": "Swift Set", - "23": "Hardenend Set", + "23": "Hardened Set", "24": "Reinforced Set", "25": "Interlocking Set", "26": "Flame Proof Set", @@ -2232,15 +2232,102 @@ "42": "Olthoi Armor D Red", "43": "Olthoi Armor C Rat", "44": "Olthoi Armor C Red", - "45": "Olthoi Armor F Red", - "46": "Olthoi Armor K Red", - "47": "Olthoi Armor M Red", - "48": "Olthoi Armor B Red", - "49": "Olthoi Armor B Rat", - "50": "Olthoi Armor K Rat", - "51": "Olthoi Armor M Rat", - "52": "Olthoi Armor F Rat", - "53": "Olthoi Armor D Rat" + "45": "Olthoi Armor D Rat", + "46": "Upgraded Relic Alduressa Set", + "47": "Upgraded Ancient Relic Set", + "48": "Upgraded Noble Relic Set", + "49": "Weave of Alchemy", + "50": "Weave of Arcane Lore", + "51": "Weave of Armor Tinkering", + "52": "Weave of Assess Person", + "53": "Weave of Light Weapons", + "54": "Weave of Missile Weapons", + "55": "Weave of Cooking", + "56": "Weave of Creature Enchantment", + "57": "Weave of Missile Weapons", + "58": "Weave of Finesse", + "59": "Weave of Deception", + "60": "Weave of Fletching", + "61": "Weave of Healing", + "62": "Weave of Item Enchantment", + "63": "Weave of Item Tinkering", + "64": "Weave of Leadership", + "65": "Weave of Life Magic", + "66": "Weave of Loyalty", + "67": "Weave of Light Weapons", + "68": "Weave of Magic Defense", + "69": "Weave of Magic Item Tinkering", + "70": "Weave of Mana Conversion", + "71": "Weave of Melee Defense", + "72": "Weave of Missile Defense", + "73": "Weave of Salvaging", + "74": "Weave of Light Weapons", + "75": "Weave of Light Weapons", + "76": "Weave of Heavy Weapons", + "77": "Weave of Missile Weapons", + "78": "Weave of Two Handed Combat", + "79": "Weave of Light Weapons", + "80": "Weave of Void Magic", + "81": "Weave of War Magic", + "82": "Weave of Weapon Tinkering", + "83": "Weave of Assess Creature", + "84": "Weave of Dirty Fighting", + "85": "Weave of Dual Wield", + "86": "Weave of Recklessness", + "87": "Weave of Shield", + "88": "Weave of Sneak Attack", + "89": "Ninja_New", + "90": "Weave of Summoning", + "91": "Shrouded Soul", + "92": "Darkened Mind", + "93": "Clouded Spirit", + "94": "Minor Stinging Shrouded Soul", + "95": "Minor Sparking Shrouded Soul", + "96": "Minor Smoldering Shrouded Soul", + "97": "Minor Shivering Shrouded Soul", + "98": "Minor Stinging Darkened Mind", + "99": "Minor Sparking Darkened Mind", + "100": "Minor Smoldering Darkened Mind", + "101": "Minor Shivering Darkened Mind", + "102": "Minor Stinging Clouded Spirit", + "103": "Minor Sparking Clouded Spirit", + "104": "Minor Smoldering Clouded Spirit", + "105": "Minor Shivering Clouded Spirit", + "106": "Major Stinging Shrouded Soul", + "107": "Major Sparking Shrouded Soul", + "108": "Major Smoldering Shrouded Soul", + "109": "Major Shivering Shrouded Soul", + "110": "Major Stinging Darkened Mind", + "111": "Major Sparking Darkened Mind", + "112": "Major Smoldering Darkened Mind", + "113": "Major Shivering Darkened Mind", + "114": "Major Stinging Clouded Spirit", + "115": "Major Sparking Clouded Spirit", + "116": "Major Smoldering Clouded Spirit", + "117": "Major Shivering Clouded Spirit", + "118": "Blackfire Stinging Shrouded Soul", + "119": "Blackfire Sparking Shrouded Soul", + "120": "Blackfire Smoldering Shrouded Soul", + "121": "Blackfire Shivering Shrouded Soul", + "122": "Blackfire Stinging Darkened Mind", + "123": "Blackfire Sparking Darkened Mind", + "124": "Blackfire Smoldering Darkened Mind", + "125": "Blackfire Shivering Darkened Mind", + "126": "Blackfire Stinging Clouded Spirit", + "127": "Blackfire Sparking Clouded Spirit", + "128": "Blackfire Smoldering Clouded Spirit", + "129": "Blackfire Shivering Clouded Spirit", + "130": "Shimmering Shadows", + "131": "Brown Society Locket", + "132": "Yellow Society Locket", + "133": "Red Society Band", + "134": "Green Society Band", + "135": "Purple Society Band", + "136": "Blue Society Band", + "137": "Gauntlet Garb", + "138": "Paragon Missile Weapons", + "139": "Paragon Casters", + "140": "Paragon Melee Weapons" } } }, diff --git a/inventory-service/database.py b/inventory-service/database.py index 64f3c457..d1c0a502 100644 --- a/inventory-service/database.py +++ b/inventory-service/database.py @@ -232,7 +232,7 @@ class ItemRatings(Base): class ItemSpells(Base): """Spell information for items.""" __tablename__ = 'item_spells' - + item_id = Column(Integer, ForeignKey('items.id'), primary_key=True) spell_id = Column(Integer, primary_key=True) is_active = Column(Boolean, default=False) diff --git a/inventory-service/main.py b/inventory-service/main.py index ecf300be..3512f6d8 100644 --- a/inventory-service/main.py +++ b/inventory-service/main.py @@ -2085,7 +2085,7 @@ async def search_items( sort_by: str = Query("name", description="Sort field: name, value, damage, armor, workmanship, damage_rating, crit_damage_rating, level"), sort_dir: str = Query("asc", description="Sort direction: asc or desc"), page: int = Query(1, ge=1, description="Page number"), - limit: int = Query(200, ge=1, le=10000, description="Items per page") + limit: int = Query(200, ge=1, le=50000, description="Items per page") ): """ Search items across characters with comprehensive filtering options. @@ -2232,14 +2232,24 @@ async def search_items( WHEN i.object_class = 6 THEN 'Melee Weapon' WHEN i.object_class = 7 THEN 'Missile Weapon' WHEN i.object_class = 8 THEN 'Held' - + -- Check wielded location for two-handed weapons WHEN i.current_wielded_location = 67108864 THEN 'Two-Handed' - + + -- CLOAKS: Identify by name pattern + WHEN i.name ILIKE '%cloak%' THEN 'Cloak' + -- DEFAULT ELSE '-' - END as computed_slot_name - + END as computed_slot_name, + + -- Compute spell_names (spell IDs for client-side name lookup) + COALESCE( + (SELECT STRING_AGG(CAST(sp_inner.spell_id AS VARCHAR), ',' ORDER BY sp_inner.spell_id) + FROM item_spells sp_inner WHERE sp_inner.item_id = i.id), + '' + ) as computed_spell_names + FROM items i LEFT JOIN item_combat_stats cs ON i.id = cs.item_id LEFT JOIN item_requirements req ON i.id = req.item_id @@ -2495,7 +2505,11 @@ async def search_items( slot_approaches.append("(object_class = 11 AND name ILIKE '%trinket%')") # 4. Jewelry fallback: items that don't match other jewelry patterns slot_approaches.append("(object_class = 4 AND name NOT ILIKE '%ring%' AND name NOT ILIKE '%bracelet%' AND name NOT ILIKE '%amulet%' AND name NOT ILIKE '%necklace%' AND name NOT ILIKE '%gorget%')") - + elif slot_name.lower() == 'cloak': + # For cloaks: identify by name pattern + slot_approaches.append("(name ILIKE '%cloak%')") + slot_approaches.append("(computed_slot_name = 'Cloak')") + # Combine approaches with OR (any approach can match) if slot_approaches: slot_conditions.append(f"({' OR '.join(slot_approaches)})") @@ -2663,9 +2677,11 @@ async def search_items( # Add ORDER BY sort_mapping = { "name": "name", + "character_name": "character_name", "value": "value", "damage": "max_damage", - "armor": "armor_level", + "armor": "armor_level", + "armor_level": "armor_level", "workmanship": "workmanship", "level": "wield_level", "damage_rating": "damage_rating", @@ -2673,11 +2689,20 @@ async def search_items( "heal_boost_rating": "heal_boost_rating", "vitality_rating": "vitality_rating", "damage_resist_rating": "damage_resist_rating", - "crit_damage_resist_rating": "crit_damage_resist_rating" + "crit_damage_resist_rating": "crit_damage_resist_rating", + "item_set": "item_set", + "slot_name": "computed_slot_name", + "coverage": "coverage_mask", + "item_type_name": "object_class", + "last_updated": "timestamp", + "spell_names": "computed_spell_names" } sort_field = sort_mapping.get(sort_by, "name") sort_direction = "DESC" if sort_dir.lower() == "desc" else "ASC" - query_parts.append(f"ORDER BY {sort_field} {sort_direction}") + + # Handle NULLS for optional fields + nulls_clause = "NULLS LAST" if sort_direction == "ASC" else "NULLS FIRST" + query_parts.append(f"ORDER BY {sort_field} {sort_direction} {nulls_clause}") # Add pagination offset = (page - 1) * limit diff --git a/main.py b/main.py index 90db523f..d2821afd 100644 --- a/main.py +++ b/main.py @@ -2275,8 +2275,8 @@ async def proxy_inventory_service(path: str, request: Request): inventory_service_url = os.getenv('INVENTORY_SERVICE_URL', 'http://inventory-service:8000') logger.info(f"Proxying to inventory service: {inventory_service_url}/{path}") - # Forward the request to inventory service - async with httpx.AsyncClient() as client: + # Forward the request to inventory service (60s timeout for large queries) + async with httpx.AsyncClient(timeout=60.0) as client: response = await client.request( method=request.method, url=f"{inventory_service_url}/{path}", diff --git a/static/inventory.html b/static/inventory.html index d96f7c0e..ad23112e 100644 --- a/static/inventory.html +++ b/static/inventory.html @@ -265,6 +265,7 @@ border-collapse: collapse; font-size: 10px; color: #000; + table-layout: fixed; } .results-table th { @@ -276,11 +277,36 @@ position: sticky; top: 0; font-size: 9px; + position: relative; + user-select: none; + } + + /* Resizable column handle */ + .results-table th .resize-handle { + position: absolute; + right: 0; + top: 0; + bottom: 0; + width: 5px; + cursor: col-resize; + background: transparent; + } + + .results-table th .resize-handle:hover, + .results-table th .resize-handle.resizing { + background: #666; + } + + .results-table th.resizing { + background: #b0b0b0; } .results-table td { padding: 1px 3px; border: 1px solid #ddd; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; } .results-table tbody tr:hover { @@ -542,6 +568,9 @@ + @@ -944,6 +973,10 @@ +