8.6 KiB
🔄 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
-
API Client (
bot/api_client.py)- Async HTTP client using httpx
- Methods for all game operations
- Error handling and retry logic
-
Internal API (
api/internal.py)- Protected endpoints with internal API key
- Player management (get, create, update)
- Movement logic
- Location queries
- Inventory operations
- Combat system
-
Environment Configuration
API_INTERNAL_KEY- Secret key for bot-to-API authAPI_BASE_URL- API endpoint for bot to call
-
Dependencies
- Added
httpx==0.25.2to requirements.txt
- Added
🔄 To Be Migrated
The following bot files need to be updated to use the API client instead of direct database access:
-
bot/handlers.py- Telegram command handlers- Use
api_client.get_player()instead ofdatabase.get_player() - Use
api_client.move_player()instead of direct location updates - Use
api_client.start_combat()for combat initiation
- Use
-
bot/logic.py- Game logic functions- Movement should call API
- Item usage should call API
- Status effects should be managed by API
-
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
# 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
# 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:
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:
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 operationsdatabase.*only for legacy compatibility (if needed)
Testing
Manual Testing
- Test Player Creation
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"}'
- Test Movement
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"}'
- Test Location Query
curl -X GET http://localhost:8000/api/internal/location/start_point \
-H "X-Internal-Key: bot-internal-key-9f8e7d6c5b4a3210fedcba9876543210"
Integration Testing
- Send
/startto Telegram bot - should still work - Try moving via bot - should use API
- Try moving via PWA - should use same API
- Verify both show same state
Rollback Plan
If issues occur, rollback is simple:
# 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:
# 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
- Rate Limiting - Prevent API abuse
- Request Metrics - Track endpoint usage
- Error Recovery - Automatic retry with backoff
- API Versioning -
/api/v1/internal/... - GraphQL - Consider for complex queries
Status: IN PROGRESS
- Create API client
- Create internal endpoints
- Add authentication
- Update environment config
- Fix location endpoint bug
- Migrate bot handlers
- Update bot logic
- Remove direct database access from bot
- Integration testing
- Documentation
Next Steps:
- Deploy current changes (API fixes are ready)
- Test internal API endpoints
- Begin migrating bot handlers one by one
- Full integration testing
- Remove old database calls from bot
This refactor sets the foundation for a scalable, maintainable architecture! 🚀