feat(api): implement inventory API endpoints
Add REST API endpoints for character inventory management: - GET /api/v1/characters/<id>/inventory - Get inventory and equipped items - POST /api/v1/characters/<id>/inventory/equip - Equip item to slot - POST /api/v1/characters/<id>/inventory/unequip - Unequip from slot - POST /api/v1/characters/<id>/inventory/use - Use consumable item - DELETE /api/v1/characters/<id>/inventory/<item_id> - Drop item All endpoints include: - Authentication via @require_auth decorator - Ownership validation through CharacterService - Comprehensive error handling with proper HTTP status codes - Full logging for debugging Includes 25 integration tests covering authentication requirements, URL patterns, and response formats. Task 2.4 of Phase 4 Combat Implementation complete.
This commit is contained in:
@@ -1498,166 +1498,68 @@ character.inventory.append(generated_item.to_dict()) # Store full item data
|
||||
|
||||
---
|
||||
|
||||
#### Task 2.4: Inventory API Endpoints (1 day / 8 hours)
|
||||
#### Task 2.4: Inventory API Endpoints (1 day / 8 hours) ✅ COMPLETE
|
||||
|
||||
**Objective:** REST API for inventory management
|
||||
|
||||
**File:** `/api/app/api/inventory.py`
|
||||
**Files Implemented:**
|
||||
- `/api/app/api/inventory.py` - API blueprint (530 lines)
|
||||
- `/api/tests/test_inventory_api.py` - Integration tests (25 tests)
|
||||
|
||||
**Endpoints:**
|
||||
**Endpoints Implemented:**
|
||||
|
||||
```python
|
||||
"""
|
||||
Inventory API Blueprint
|
||||
| Method | Endpoint | Description |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/v1/characters/<id>/inventory` | Get inventory + equipped items |
|
||||
| POST | `/api/v1/characters/<id>/inventory/equip` | Equip item to slot |
|
||||
| POST | `/api/v1/characters/<id>/inventory/unequip` | Unequip from slot |
|
||||
| POST | `/api/v1/characters/<id>/inventory/use` | Use consumable item |
|
||||
| DELETE | `/api/v1/characters/<id>/inventory/<item_id>` | Drop/remove item |
|
||||
|
||||
Endpoints:
|
||||
- GET /api/v1/characters/<id>/inventory - Get inventory
|
||||
- POST /api/v1/characters/<id>/inventory/equip - Equip item
|
||||
- POST /api/v1/characters/<id>/inventory/unequip - Unequip item
|
||||
- POST /api/v1/characters/<id>/inventory/use - Use consumable
|
||||
- DELETE /api/v1/characters/<id>/inventory/<item_id> - Drop item
|
||||
"""
|
||||
**Exception Handling:**
|
||||
- `CharacterNotFound` → 404 Not Found
|
||||
- `ItemNotFoundError` → 404 Not Found
|
||||
- `InvalidSlotError` → 422 Validation Error
|
||||
- `CannotEquipError` → 400 Bad Request
|
||||
- `CannotUseItemError` → 400 Bad Request
|
||||
- `InventoryFullError` → 400 Bad Request
|
||||
|
||||
from flask import Blueprint, request, g
|
||||
**Response Examples:**
|
||||
|
||||
from app.services.inventory_service import InventoryService, InventoryError
|
||||
from app.services.character_service import get_character_service
|
||||
from app.services.appwrite_service import get_appwrite_service
|
||||
from app.utils.response import success_response, error_response, not_found_response
|
||||
from app.utils.auth import require_auth
|
||||
from app.utils.logging import get_logger
|
||||
```json
|
||||
// GET /api/v1/characters/{id}/inventory
|
||||
{
|
||||
"result": {
|
||||
"inventory": [{"item_id": "...", "name": "...", ...}],
|
||||
"equipped": {
|
||||
"weapon": {...},
|
||||
"helmet": null,
|
||||
...
|
||||
},
|
||||
"inventory_count": 5,
|
||||
"max_inventory": 100
|
||||
}
|
||||
}
|
||||
|
||||
logger = get_logger(__file__)
|
||||
|
||||
inventory_bp = Blueprint('inventory', __name__)
|
||||
|
||||
|
||||
@inventory_bp.route('/<character_id>/inventory', methods=['GET'])
|
||||
@require_auth
|
||||
def get_inventory(character_id: str):
|
||||
"""Get character inventory."""
|
||||
char_service = get_character_service()
|
||||
character = char_service.get_character(character_id, g.user_id)
|
||||
|
||||
inventory_service = InventoryService(get_appwrite_service())
|
||||
items = inventory_service.get_inventory(character)
|
||||
|
||||
return success_response({
|
||||
"inventory": [item.to_dict() for item in items],
|
||||
"equipped": character.equipped
|
||||
})
|
||||
|
||||
|
||||
@inventory_bp.route('/<character_id>/inventory/equip', methods=['POST'])
|
||||
@require_auth
|
||||
def equip_item(character_id: str):
|
||||
"""
|
||||
Equip item.
|
||||
|
||||
Request JSON:
|
||||
{
|
||||
"item_id": "iron_sword",
|
||||
"slot": "weapon"
|
||||
}
|
||||
"""
|
||||
data = request.get_json()
|
||||
item_id = data.get('item_id')
|
||||
slot = data.get('slot')
|
||||
|
||||
if not item_id or not slot:
|
||||
return error_response("item_id and slot required", 400)
|
||||
|
||||
try:
|
||||
char_service = get_character_service()
|
||||
character = char_service.get_character(character_id, g.user_id)
|
||||
|
||||
inventory_service = InventoryService(get_appwrite_service())
|
||||
inventory_service.equip_item(character, item_id, slot)
|
||||
|
||||
# Save character
|
||||
char_service.update_character(character)
|
||||
|
||||
return success_response({
|
||||
"equipped": character.equipped,
|
||||
"message": f"Equipped {item_id} to {slot}"
|
||||
})
|
||||
|
||||
except InventoryError as e:
|
||||
return error_response(str(e), 400)
|
||||
|
||||
|
||||
@inventory_bp.route('/<character_id>/inventory/unequip', methods=['POST'])
|
||||
@require_auth
|
||||
def unequip_item(character_id: str):
|
||||
"""
|
||||
Unequip item.
|
||||
|
||||
Request JSON:
|
||||
{
|
||||
"slot": "weapon"
|
||||
}
|
||||
"""
|
||||
data = request.get_json()
|
||||
slot = data.get('slot')
|
||||
|
||||
if not slot:
|
||||
return error_response("slot required", 400)
|
||||
|
||||
char_service = get_character_service()
|
||||
character = char_service.get_character(character_id, g.user_id)
|
||||
|
||||
inventory_service = InventoryService(get_appwrite_service())
|
||||
inventory_service.unequip_item(character, slot)
|
||||
|
||||
# Save character
|
||||
char_service.update_character(character)
|
||||
|
||||
return success_response({
|
||||
"equipped": character.equipped,
|
||||
"message": f"Unequipped item from {slot}"
|
||||
})
|
||||
|
||||
|
||||
@inventory_bp.route('/<character_id>/inventory/use', methods=['POST'])
|
||||
@require_auth
|
||||
def use_item(character_id: str):
|
||||
"""
|
||||
Use consumable item.
|
||||
|
||||
Request JSON:
|
||||
{
|
||||
"item_id": "health_potion_small"
|
||||
}
|
||||
"""
|
||||
data = request.get_json()
|
||||
item_id = data.get('item_id')
|
||||
|
||||
if not item_id:
|
||||
return error_response("item_id required", 400)
|
||||
|
||||
try:
|
||||
char_service = get_character_service()
|
||||
character = char_service.get_character(character_id, g.user_id)
|
||||
|
||||
inventory_service = InventoryService(get_appwrite_service())
|
||||
result = inventory_service.use_consumable(character, item_id)
|
||||
|
||||
# Save character
|
||||
char_service.update_character(character)
|
||||
|
||||
return success_response(result)
|
||||
|
||||
except InventoryError as e:
|
||||
return error_response(str(e), 400)
|
||||
// POST /api/v1/characters/{id}/inventory/equip
|
||||
{
|
||||
"result": {
|
||||
"message": "Equipped Flaming Dagger to weapon slot",
|
||||
"equipped": {...},
|
||||
"unequipped_item": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Register blueprint in `/api/app/__init__.py`**
|
||||
**Blueprint registered in `/api/app/__init__.py`**
|
||||
|
||||
**Acceptance Criteria:**
|
||||
- All inventory endpoints functional
|
||||
- Authentication required
|
||||
- Ownership validation enforced
|
||||
- Errors handled gracefully
|
||||
**Tests:** 25 passing (`/api/tests/test_inventory_api.py`)
|
||||
|
||||
**Acceptance Criteria:** ✅ MET
|
||||
- [x] All inventory endpoints functional
|
||||
- [x] Authentication required on all endpoints
|
||||
- [x] Ownership validation enforced
|
||||
- [x] Errors handled gracefully with proper HTTP status codes
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user