mirror of
https://github.com/Routstr/routstr-core.git
synced 2026-08-04 00:56:15 +00:00
444 lines
11 KiB
Markdown
444 lines
11 KiB
Markdown
# Code Structure
|
|
|
|
This guide provides a detailed overview of Routstr Core's codebase organization and key modules.
|
|
|
|
## Directory Layout
|
|
|
|
```
|
|
routstr-core/
|
|
├── routstr/ # Main application package
|
|
│ ├── __init__.py # Package initialization, exports FastAPI app
|
|
│ ├── algorithm.py # Model selection/mapping logic
|
|
│ ├── auth.py # Bearer/Cashu auth and payment handling
|
|
│ ├── balance.py # Balance management endpoints
|
|
│ ├── discovery.py # Nostr relay discovery
|
|
│ ├── lightning.py # Lightning invoice topups
|
|
│ ├── nip91.py # Node announcement logic
|
|
│ ├── proxy.py # Request proxying logic
|
|
│ ├── wallet.py # Cashu wallet operations
|
|
│ │
|
|
│ ├── core/ # Core infrastructure
|
|
│ │ ├── __init__.py
|
|
│ │ ├── admin.py # Admin dashboard and API
|
|
│ │ ├── db.py # Database models and connection
|
|
│ │ ├── exceptions.py # Exception handlers
|
|
│ │ ├── logging.py # Structured logging setup
|
|
│ │ ├── main.py # FastAPI app initialization
|
|
│ │ └── middleware.py # HTTP middleware components
|
|
│ │
|
|
│ ├── payment/ # Payment processing
|
|
│ │ ├── __init__.py
|
|
│ │ ├── cost_calculation.py # Usage cost calculation
|
|
│ │ ├── helpers.py # Payment utilities
|
|
│ │ ├── lnurl.py # Lightning URL support
|
|
│ │ ├── models.py # Model pricing management
|
|
│ │ └── price.py # BTC/USD price handling
|
|
│ │
|
|
│ └── upstream/ # Upstream provider integrations
|
|
│ ├── base.py # Base provider logic
|
|
│ ├── helpers.py # Provider init and model refresh
|
|
│ └── ... # Provider implementations
|
|
│
|
|
├── tests/ # Test suite
|
|
│ ├── __init__.py
|
|
│ ├── conftest.py # Pytest configuration
|
|
│ ├── unit/ # Unit tests
|
|
│ └── integration/ # Integration tests
|
|
│
|
|
├── migrations/ # Alembic database migrations
|
|
│ ├── alembic.ini
|
|
│ ├── env.py
|
|
│ ├── script.py.mako
|
|
│ └── versions/ # Migration files
|
|
│
|
|
├── scripts/ # Utility scripts
|
|
│ ├── models_meta.py # Fetch model pricing
|
|
│ └── ... # Build/update helpers
|
|
│
|
|
├── examples/ # Example clients
|
|
├── testing-clients/ # HTML test clients
|
|
├── ui/ # Next.js admin UI
|
|
│
|
|
├── docs/ # Documentation
|
|
├── logs/ # Application logs (git ignored)
|
|
│
|
|
├── .github/ # GitHub Actions workflows
|
|
├── .env.example # Environment variable template
|
|
├── .gitignore # Git ignore rules
|
|
├── .dockerignore # Docker ignore rules
|
|
├── Dockerfile # Container definition
|
|
├── Makefile # Development commands
|
|
├── README.md # Project overview
|
|
├── alembic.ini # Migration configuration
|
|
├── compose.yml # Docker Compose setup
|
|
├── compose.testing.yml # Testing environment
|
|
├── pyproject.toml # Project configuration
|
|
└── uv.lock # Locked dependencies
|
|
```
|
|
|
|
## Key Modules
|
|
|
|
### Application Entry Point
|
|
|
|
#### `routstr/__init__.py`
|
|
|
|
```python
|
|
from .core.main import app as fastapi_app
|
|
|
|
__all__ = ["fastapi_app"]
|
|
```
|
|
|
|
#### `routstr/core/main.py`
|
|
|
|
```python
|
|
# FastAPI application setup
|
|
app = FastAPI(version=__version__, lifespan=lifespan)
|
|
|
|
# Middleware registration
|
|
app.add_middleware(CORSMiddleware, ...)
|
|
app.add_middleware(LoggingMiddleware)
|
|
|
|
# Router inclusion
|
|
app.include_router(models_router)
|
|
app.include_router(admin_router)
|
|
app.include_router(balance_router)
|
|
app.include_router(deprecated_wallet_router)
|
|
app.include_router(providers_router)
|
|
app.include_router(proxy_router)
|
|
```
|
|
|
|
### Authentication Module
|
|
|
|
#### `routstr/auth.py`
|
|
|
|
Handles bearer key validation and payment lifecycle (bearer or Cashu token):
|
|
|
|
```python
|
|
async def validate_bearer_key(
|
|
bearer_key: str,
|
|
session: AsyncSession,
|
|
refund_address: Optional[str] = None,
|
|
key_expiry_time: Optional[int] = None,
|
|
) -> ApiKey:
|
|
"""Validate bearer API key or redeem Cashu token into a balance."""
|
|
```
|
|
|
|
Key functions:
|
|
|
|
- `validate_bearer_key()` - Validate API key or Cashu token
|
|
- `pay_for_request()` - Reserve max cost before upstream call
|
|
- `adjust_payment_for_tokens()` - Adjust final cost after response
|
|
- `revert_pay_for_request()` - Refund on upstream failure
|
|
|
|
### Payment Processing
|
|
|
|
#### `routstr/payment/cost_calculation.py`
|
|
|
|
Calculates request costs:
|
|
|
|
```python
|
|
async def calculate_cost(
|
|
response_data: dict, max_cost: int, session: AsyncSession
|
|
) -> CostData | MaxCostData | CostDataError:
|
|
"""Calculate cost in millisatoshis from response usage or model pricing."""
|
|
```
|
|
|
|
#### `routstr/payment/models.py`
|
|
|
|
Manages model pricing, database overrides, and pricing refresh:
|
|
|
|
```python
|
|
class Model(BaseModel):
|
|
id: str
|
|
name: str
|
|
pricing: Pricing
|
|
sats_pricing: Pricing | None = None
|
|
|
|
async def update_sats_pricing():
|
|
"""Periodic task to update sats pricing for providers and overrides."""
|
|
```
|
|
|
|
#### `routstr/proxy.py` + `routstr/upstream/*`
|
|
|
|
The `x-cashu` header is handled by the proxy route and delegated to upstream providers.
|
|
|
|
### Request Proxying
|
|
|
|
#### `routstr/proxy.py`
|
|
|
|
Core proxy functionality:
|
|
|
|
```python
|
|
@proxy_router.api_route("/{path:path}", methods=["GET", "POST"], response_model=None)
|
|
async def proxy(
|
|
request: Request, path: str, session: AsyncSession = Depends(get_session)
|
|
) -> Response | StreamingResponse:
|
|
"""Forward requests to upstream provider and charge usage."""
|
|
```
|
|
|
|
Key features:
|
|
|
|
- Streaming support
|
|
- Header preservation
|
|
- Error handling
|
|
- Usage tracking
|
|
|
|
### Database Layer
|
|
|
|
#### `routstr/core/db.py`
|
|
|
|
SQLModel definitions (selected):
|
|
|
|
```python
|
|
class ApiKey(SQLModel, table=True):
|
|
hashed_key: str = Field(primary_key=True)
|
|
balance: int
|
|
reserved_balance: int = 0
|
|
refund_address: str | None = None
|
|
key_expiry_time: int | None = None
|
|
total_spent: int = 0
|
|
total_requests: int = 0
|
|
|
|
class LightningInvoice(SQLModel, table=True):
|
|
id: str = Field(primary_key=True)
|
|
bolt11: str
|
|
amount_sats: int
|
|
status: str
|
|
|
|
class UpstreamProviderRow(SQLModel, table=True):
|
|
id: int | None = Field(default=None, primary_key=True)
|
|
provider_type: str
|
|
base_url: str
|
|
api_key: str
|
|
```
|
|
|
|
### Admin Interface
|
|
|
|
#### `routstr/core/admin.py`
|
|
|
|
Web dashboard and admin API:
|
|
|
|
```python
|
|
@admin_router.get("/admin")
|
|
async def admin_dashboard(request: Request):
|
|
"""Render admin HTML interface"""
|
|
# Authentication check
|
|
# Load statistics
|
|
# Render template
|
|
|
|
@admin_router.post("/admin/withdraw")
|
|
async def withdraw_balance(
|
|
request: Request, withdraw_request: WithdrawRequest
|
|
) -> dict[str, str]:
|
|
"""Generate eCash token for withdrawal"""
|
|
```
|
|
|
|
Features:
|
|
|
|
- HTML dashboard
|
|
- API key management
|
|
- Balance withdrawals
|
|
- Usage statistics
|
|
|
|
### Wallet Integration
|
|
|
|
#### `routstr/wallet.py`
|
|
|
|
Cashu wallet operations (function-based):
|
|
|
|
```python
|
|
async def recieve_token(token: str) -> tuple[int, str, str]:
|
|
"""Redeem eCash token and return amount/unit/mint."""
|
|
|
|
async def send_token(amount: int, unit: str, mint_url: str | None = None) -> str:
|
|
"""Create eCash token for withdrawal."""
|
|
```
|
|
|
|
### Utility Modules
|
|
|
|
#### `routstr/core/logging.py`
|
|
|
|
Structured logging configuration:
|
|
|
|
```python
|
|
def setup_logging():
|
|
"""Configure JSON structured logging"""
|
|
|
|
class RequestIdFilter(logging.Filter):
|
|
"""Attach request ID to log records."""
|
|
```
|
|
|
|
#### `routstr/core/middleware.py`
|
|
|
|
HTTP middleware components:
|
|
|
|
```python
|
|
class LoggingMiddleware:
|
|
"""Log all HTTP requests/responses"""
|
|
```
|
|
|
|
#### `routstr/core/exceptions.py`
|
|
|
|
Exception handlers:
|
|
|
|
```python
|
|
async def http_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
|
"""HTTP exception handler with request ID"""
|
|
|
|
async def general_exception_handler(request: Request, exc: Exception) -> JSONResponse:
|
|
"""Fallback exception handler with request ID"""
|
|
```
|
|
|
|
## Configuration Files
|
|
|
|
### `pyproject.toml`
|
|
|
|
Project metadata and dependencies:
|
|
|
|
```toml
|
|
[project]
|
|
name = "routstr"
|
|
version = "0.2.2"
|
|
dependencies = [
|
|
"fastapi[standard]>=0.115",
|
|
"sqlmodel>=0.0.24",
|
|
"cashu",
|
|
# ...
|
|
]
|
|
|
|
[tool.pytest.ini_options]
|
|
testpaths = ["tests"]
|
|
asyncio_mode = "auto"
|
|
|
|
[tool.ruff.lint]
|
|
select = ["E", "F", "I"]
|
|
```
|
|
|
|
### `alembic.ini`
|
|
|
|
Database migration configuration:
|
|
|
|
```ini
|
|
[alembic]
|
|
script_location = migrations
|
|
prepend_sys_path = .
|
|
version_path_separator = os
|
|
|
|
[loggers]
|
|
keys = root,sqlalchemy,alembic
|
|
```
|
|
|
|
### `Makefile`
|
|
|
|
Development commands:
|
|
|
|
```makefile
|
|
# Setup commands
|
|
setup:
|
|
uv sync
|
|
uv pip install -e .
|
|
|
|
# Development server
|
|
dev:
|
|
fastapi dev routstr --host 0.0.0.0
|
|
|
|
# Testing
|
|
test:
|
|
uv run pytest
|
|
|
|
# Code quality
|
|
lint:
|
|
uv run ruff check .
|
|
```
|
|
|
|
## Code Patterns
|
|
|
|
### Dependency Injection
|
|
|
|
Using FastAPI's DI system:
|
|
|
|
```python
|
|
# Define dependency
|
|
async def get_session() -> AsyncGenerator[AsyncSession, None]:
|
|
async with AsyncSession(engine, expire_on_commit=False) as session:
|
|
yield session
|
|
|
|
# Use in routes
|
|
@router.get("/items")
|
|
async def get_items(db: AsyncSession = Depends(get_session)):
|
|
result = await db.execute(select(Item))
|
|
return result.scalars().all()
|
|
```
|
|
|
|
### Async Context Managers
|
|
|
|
For resource management:
|
|
|
|
```python
|
|
async with httpx.AsyncClient() as client:
|
|
response = await client.get(url)
|
|
|
|
async with database.transaction():
|
|
# Atomic operations
|
|
```
|
|
|
|
### Type Safety
|
|
|
|
Leveraging Python 3.11+ features:
|
|
|
|
```python
|
|
# Union types with |
|
|
def process(value: str | int) -> dict[str, Any]:
|
|
pass
|
|
|
|
# Type aliases
|
|
Balance = int # millisatoshis
|
|
TokenList = list[dict[str, str]]
|
|
```
|
|
|
|
### Error Handling
|
|
|
|
Consistent error responses:
|
|
|
|
```python
|
|
try:
|
|
result = await risky_operation()
|
|
except SpecificError as e:
|
|
logger.error("Operation failed", exc_info=True)
|
|
raise HTTPException(
|
|
status_code=400,
|
|
detail={
|
|
"error": "specific_error",
|
|
"message": str(e)
|
|
}
|
|
)
|
|
```
|
|
|
|
## Best Practices
|
|
|
|
### Module Organization
|
|
|
|
1. **Single Responsibility**: Each module has one clear purpose
|
|
2. **Minimal Imports**: Import only what's needed
|
|
3. **Circular Dependencies**: Avoid by using dependency injection
|
|
4. **Public API**: Expose through `__init__.py`
|
|
|
|
### Function Design
|
|
|
|
1. **Type Hints**: Always include complete type annotations
|
|
2. **Async First**: Use async/await for I/O operations
|
|
3. **Error Handling**: Raise specific exceptions
|
|
4. **Documentation**: Docstrings for public functions
|
|
|
|
### Testing Structure
|
|
|
|
1. **Mirror Source**: Test structure matches source
|
|
2. **Fixtures**: Reusable test data in conftest.py
|
|
3. **Mocking**: Mock external dependencies
|
|
4. **Coverage**: Aim for >80% coverage
|
|
|
|
## Next Steps
|
|
|
|
- Review [Testing Guide](testing.md) for test structure
|
|
- Read [Architecture](architecture.md) for system design
|