297 lines
8.6 KiB
Markdown
297 lines
8.6 KiB
Markdown
# 🔄 API-First Architecture Refactor
|
|
|
|
## Overview
|
|
|
|
This refactor moves game logic from the Telegram bot to the FastAPI server, making the API the **single source of truth** for all game operations.
|
|
|
|
## Benefits
|
|
|
|
✅ **Single Source of Truth** - All game logic in one place
|
|
✅ **Consistency** - Web and Telegram bot behave identically
|
|
✅ **Easier Maintenance** - Fix bugs once, applies everywhere
|
|
✅ **Better Testing** - Test game logic via API endpoints
|
|
✅ **Scalability** - Can add more frontends (Discord, mobile app, etc.)
|
|
✅ **Performance** - Direct database access from API
|
|
|
|
## Architecture
|
|
|
|
```
|
|
┌─────────────────┐ ┌──────────────────┐
|
|
│ Telegram Bot │◄────────►│ FastAPI API │
|
|
│ (Frontend) │ HTTP │ (Game Engine) │
|
|
└─────────────────┘ └──────────────────┘
|
|
│
|
|
┌────────▼────────┐
|
|
│ PostgreSQL │
|
|
│ Database │
|
|
└─────────────────┘
|
|
|
|
┌─────────────────┐
|
|
│ React PWA │◄────────►│ FastAPI API │
|
|
│ (Frontend) │ HTTP │ (Game Engine) │
|
|
└─────────────────┘ └──────────────────┘
|
|
```
|
|
|
|
## Implementation Status
|
|
|
|
### ✅ Completed
|
|
|
|
1. **API Client** (`bot/api_client.py`)
|
|
- Async HTTP client using httpx
|
|
- Methods for all game operations
|
|
- Error handling and retry logic
|
|
|
|
2. **Internal API** (`api/internal.py`)
|
|
- Protected endpoints with internal API key
|
|
- Player management (get, create, update)
|
|
- Movement logic
|
|
- Location queries
|
|
- Inventory operations
|
|
- Combat system
|
|
|
|
3. **Environment Configuration**
|
|
- `API_INTERNAL_KEY` - Secret key for bot-to-API auth
|
|
- `API_BASE_URL` - API endpoint for bot to call
|
|
|
|
4. **Dependencies**
|
|
- Added `httpx==0.25.2` to requirements.txt
|
|
|
|
### 🔄 To Be Migrated
|
|
|
|
The following bot files need to be updated to use the API client instead of direct database access:
|
|
|
|
1. **`bot/handlers.py`** - Telegram command handlers
|
|
- Use `api_client.get_player()` instead of `database.get_player()`
|
|
- Use `api_client.move_player()` instead of direct location updates
|
|
- Use `api_client.start_combat()` for combat initiation
|
|
|
|
2. **`bot/logic.py`** - Game logic functions
|
|
- Movement should call API
|
|
- Item usage should call API
|
|
- Status effects should be managed by API
|
|
|
|
3. **`bot/combat.py`** - Combat system
|
|
- Can keep combat logic here OR move to API
|
|
- Recommendation: Move to API for consistency
|
|
|
|
## Internal API Endpoints
|
|
|
|
All internal endpoints require the `X-Internal-Key` header for authentication.
|
|
|
|
### Player Management
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| GET | `/api/internal/player/telegram/{telegram_id}` | Get player by Telegram ID |
|
|
| POST | `/api/internal/player` | Create new player |
|
|
| PATCH | `/api/internal/player/telegram/{telegram_id}` | Update player data |
|
|
|
|
### Location & Movement
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| GET | `/api/internal/location/{location_id}` | Get location details |
|
|
| POST | `/api/internal/player/telegram/{telegram_id}/move` | Move player |
|
|
|
|
### Inventory
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| GET | `/api/internal/player/telegram/{telegram_id}/inventory` | Get inventory |
|
|
| POST | `/api/internal/player/telegram/{telegram_id}/use_item` | Use item |
|
|
| POST | `/api/internal/player/telegram/{telegram_id}/equip` | Equip/unequip item |
|
|
|
|
### Combat
|
|
|
|
| Method | Endpoint | Description |
|
|
|--------|----------|-------------|
|
|
| POST | `/api/internal/combat/start` | Start combat |
|
|
| GET | `/api/internal/combat/telegram/{telegram_id}` | Get combat state |
|
|
| POST | `/api/internal/combat/telegram/{telegram_id}/action` | Combat action |
|
|
|
|
## Security
|
|
|
|
### Internal API Key
|
|
|
|
The internal API uses a shared secret key (`API_INTERNAL_KEY`) to authenticate bot requests:
|
|
|
|
- **Not exposed to users** - Only bot and API know it
|
|
- **Different from JWT tokens** - User auth uses JWT
|
|
- **Should be changed in production** - Use strong random key
|
|
|
|
### Network Security
|
|
|
|
- Bot and API communicate via Docker internal network
|
|
- No public exposure of internal endpoints
|
|
- Traefik only exposes public API and PWA
|
|
|
|
## Migration Guide
|
|
|
|
### Step 1: Deploy Updated Services
|
|
|
|
```bash
|
|
# Rebuild both bot and API with new code
|
|
docker compose up -d --build echoes_of_the_ashes_bot echoes_of_the_ashes_api
|
|
```
|
|
|
|
### Step 2: Test Internal API
|
|
|
|
```bash
|
|
# Test from bot container
|
|
docker exec echoes_of_the_ashes_bot python -c "
|
|
import asyncio
|
|
from bot.api_client import api_client
|
|
|
|
async def test():
|
|
player = await api_client.get_player(10101691)
|
|
print(f'Player: {player}')
|
|
|
|
asyncio.run(test())
|
|
"
|
|
```
|
|
|
|
### Step 3: Migrate Bot Handlers
|
|
|
|
Update `bot/handlers.py` to use API client:
|
|
|
|
**Before:**
|
|
```python
|
|
from bot.database import get_player, update_player
|
|
|
|
async def move_command(update, context):
|
|
player = await get_player(telegram_id=user_id)
|
|
# ... movement logic ...
|
|
await update_player(telegram_id=user_id, updates={...})
|
|
```
|
|
|
|
**After:**
|
|
```python
|
|
from bot.api_client import api_client
|
|
|
|
async def move_command(update, context):
|
|
result = await api_client.move_player(user_id, direction)
|
|
if result.get('success'):
|
|
# Handle success
|
|
else:
|
|
# Handle error
|
|
```
|
|
|
|
### Step 4: Remove Direct Database Access
|
|
|
|
Once all handlers are migrated, bot should only use:
|
|
- `api_client.*` for game operations
|
|
- `database.*` only for legacy compatibility (if needed)
|
|
|
|
## Testing
|
|
|
|
### Manual Testing
|
|
|
|
1. **Test Player Creation**
|
|
```bash
|
|
curl -X POST http://localhost:8000/api/internal/player \
|
|
-H "X-Internal-Key: bot-internal-key-9f8e7d6c5b4a3210fedcba9876543210" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"telegram_id": 12345, "name": "TestPlayer"}'
|
|
```
|
|
|
|
2. **Test Movement**
|
|
```bash
|
|
curl -X POST http://localhost:8000/api/internal/player/telegram/12345/move \
|
|
-H "X-Internal-Key: bot-internal-key-9f8e7d6c5b4a3210fedcba9876543210" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"direction": "north"}'
|
|
```
|
|
|
|
3. **Test Location Query**
|
|
```bash
|
|
curl -X GET http://localhost:8000/api/internal/location/start_point \
|
|
-H "X-Internal-Key: bot-internal-key-9f8e7d6c5b4a3210fedcba9876543210"
|
|
```
|
|
|
|
### Integration Testing
|
|
|
|
1. Send `/start` to Telegram bot - should still work
|
|
2. Try moving via bot - should use API
|
|
3. Try moving via PWA - should use same API
|
|
4. Verify both show same state
|
|
|
|
## Rollback Plan
|
|
|
|
If issues occur, rollback is simple:
|
|
|
|
```bash
|
|
# Revert to previous bot image
|
|
docker compose down echoes_of_the_ashes_bot
|
|
git checkout HEAD~1 bot/
|
|
docker compose up -d --build echoes_of_the_ashes_bot
|
|
```
|
|
|
|
Bot will continue using direct database access until refactor is complete.
|
|
|
|
## Performance Considerations
|
|
|
|
### Latency
|
|
|
|
- **Before:** Direct database query (~10-50ms)
|
|
- **After:** HTTP request + database query (~20-100ms)
|
|
- **Impact:** Negligible for human interaction
|
|
|
|
### Caching
|
|
|
|
Consider caching in API for:
|
|
- Location data (rarely changes)
|
|
- Item definitions (static)
|
|
- NPC templates (static)
|
|
|
|
### Connection Pooling
|
|
|
|
- httpx client reuses connections
|
|
- Database connection pool in API
|
|
- No need for bot to manage DB connections
|
|
|
|
## Monitoring
|
|
|
|
Add logging to track API calls:
|
|
|
|
```python
|
|
# In api_client.py
|
|
import logging
|
|
logger = logging.getLogger(__name__)
|
|
|
|
async def get_player(self, telegram_id: int):
|
|
logger.info(f"API call: get_player({telegram_id})")
|
|
# ... rest of method ...
|
|
```
|
|
|
|
## Future Enhancements
|
|
|
|
1. **Rate Limiting** - Prevent API abuse
|
|
2. **Request Metrics** - Track endpoint usage
|
|
3. **Error Recovery** - Automatic retry with backoff
|
|
4. **API Versioning** - `/api/v1/internal/...`
|
|
5. **GraphQL** - Consider for complex queries
|
|
|
|
## Status: IN PROGRESS
|
|
|
|
- [x] Create API client
|
|
- [x] Create internal endpoints
|
|
- [x] Add authentication
|
|
- [x] Update environment config
|
|
- [x] Fix location endpoint bug
|
|
- [ ] Migrate bot handlers
|
|
- [ ] Update bot logic
|
|
- [ ] Remove direct database access from bot
|
|
- [ ] Integration testing
|
|
- [ ] Documentation
|
|
|
|
---
|
|
|
|
**Next Steps:**
|
|
1. Deploy current changes (API fixes are ready)
|
|
2. Test internal API endpoints
|
|
3. Begin migrating bot handlers one by one
|
|
4. Full integration testing
|
|
5. Remove old database calls from bot
|
|
|
|
This refactor sets the foundation for a scalable, maintainable architecture! 🚀
|