# Backend API Layer - REST Endpoints

## Overview

The `app/api/` directory contains REST API endpoints for the Paper Dynasty game backend. This layer provides traditional HTTP request/response endpoints for operations like authentication, game creation, health checks, and data retrieval.

**Architecture Note**: Most real-time gameplay happens via WebSocket (see `app/websocket/`). REST API is primarily for:
- Authentication & authorization
- Game lifecycle (create, list, retrieve)
- Health monitoring
- Data queries that don't require real-time updates

## Directory Structure

```
app/api/
├── __init__.py           # Empty package marker
├── routes/               # API route modules
│   ├── __init__.py       # Empty package marker
│   ├── health.py         # Health check endpoints (✅ complete)
│   ├── auth.py           # Authentication endpoints (stub)
│   └── games.py          # Game CRUD endpoints (stub)
└── CLAUDE.md             # This file
```

**Note**: No `dependencies.py` file yet. Common dependencies (auth, DB sessions) will be added as needed during Phase 2+ implementation.

## Current Implementation Status

### ✅ Fully Implemented

#### Health Endpoints (`routes/health.py`)

**Purpose**: Service health monitoring for deployment and operations

**Endpoints**:

1. **GET `/api/health`** - Application health check
   - Returns: Service status, timestamp, environment, version
   - Use: Load balancer health probes, monitoring systems
   - Response time: < 10ms

2. **GET `/api/health/db`** - Database connectivity check
   - Returns: Database connection status
   - Use: Verify database availability
   - Response time: < 50ms
   - Graceful error handling (returns unhealthy status instead of 500)

**Example Responses**:

```json
// GET /api/health
{
  "status": "healthy",
  "timestamp": "2025-10-31T14:23:45.123456+00:00",
  "environment": "development",
  "version": "1.0.0"
}

// GET /api/health/db (success)
{
  "status": "healthy",
  "database": "connected",
  "timestamp": "2025-10-31T14:23:45.123456+00:00"
}

// GET /api/health/db (failure)
{
  "status": "unhealthy",
  "database": "disconnected",
  "error": "connection refused",
  "timestamp": "2025-10-31T14:23:45.123456+00:00"
}
```

### 🟡 Stub Implementation (Phase 1)

#### Authentication Endpoints (`routes/auth.py`)

**Purpose**: User authentication via Discord OAuth (planned for Phase 1, currently stub)

**Endpoints**:

1. **POST `/api/auth/token`** - Create JWT token
   - Request: `TokenRequest` (user_id, username, discord_id)
   - Response: `TokenResponse` (access_token, token_type)
   - Current: Directly creates token from provided data (no OAuth yet)
   - TODO: Implement full Discord OAuth flow

2. **GET `/api/auth/verify`** - Verify authentication status
   - Response: Stub acknowledgment
   - TODO: Implement token verification with user context

**TODO Items**:
- [ ] Discord OAuth callback handler
- [ ] State parameter validation
- [ ] Token refresh endpoint
- [ ] User session management
- [ ] Authentication middleware/dependency

#### Game Endpoints (`routes/games.py`)

**Purpose**: Game lifecycle management (planned for Phase 2+, currently stub)

**Endpoints**:

1. **GET `/api/games/`** - List all games
   - Response: List of `GameListItem` (game_id, league_id, status, teams)
   - TODO: Implement with database query, pagination, filters

2. **GET `/api/games/{game_id}`** - Get game details
   - Response: Full game state and metadata
   - TODO: Load from database, include plays, lineups, current state

3. **POST `/api/games/`** - Create new game
   - Request: Game creation parameters (league, teams, settings)
   - Response: Created game details
   - TODO: Create game in DB, initialize state, return game_id

**TODO Items**:
- [ ] Game creation with validation
- [ ] Game listing with filters (by league, status, user)
- [ ] Game detail retrieval with related data
- [ ] Game deletion/cancellation
- [ ] Game history and statistics

## FastAPI Patterns Used

### 1. APIRouter Pattern

Each route module creates its own `APIRouter` instance:

```python
from fastapi import APIRouter

router = APIRouter()

@router.get("/endpoint")
async def handler():
    ...
```

Routers are registered in `app/main.py`:

```python
from app.api.routes import health, auth, games

app.include_router(health.router, prefix="/api", tags=["health"])
app.include_router(auth.router, prefix="/api/auth", tags=["auth"])
app.include_router(games.router, prefix="/api/games", tags=["games"])
```

**Conventions**:
- Each module exports a `router` variable
- Prefix and tags applied during registration in main.py
- Tags group endpoints in Swagger UI docs

### 2. Pydantic Request/Response Models

Use Pydantic models for type safety and validation:

```python
from pydantic import BaseModel

class TokenRequest(BaseModel):
    """Request model for token creation"""
    user_id: str
    username: str
    discord_id: str

class TokenResponse(BaseModel):
    """Response model for token creation"""
    access_token: str
    token_type: str = "bearer"

@router.post("/token", response_model=TokenResponse)
async def create_auth_token(request: TokenRequest):
    # FastAPI automatically validates request body against TokenRequest
    # and serializes return value according to TokenResponse
    ...
```

**Benefits**:
- Automatic request validation with clear error messages
- Automatic response serialization
- OpenAPI schema generation for docs
- Type hints for IDE support

### 3. Async Handlers

All route handlers use `async def`:

```python
@router.get("/health/db")
async def database_health():
    # Can use await for DB queries, external APIs, etc.
    async with engine.connect() as conn:
        await conn.execute(text("SELECT 1"))
    ...
```

**Why Async**:
- Non-blocking I/O operations (database, external APIs)
- Better concurrency for high-traffic endpoints
- Consistent with WebSocket handlers
- Required for async database operations (asyncpg)

### 4. Logging Pattern

Module-level logger with descriptive name:

```python
import logging

logger = logging.getLogger(f'{__name__}.health')

@router.get("/endpoint")
async def handler():
    logger.info("Endpoint called")
    logger.error(f"Error occurred: {error}", exc_info=True)
```

**Logging Levels**:
- `DEBUG`: Detailed diagnostic information
- `INFO`: Normal operation events (endpoint calls, state changes)
- `WARNING`: Unusual but handled situations
- `ERROR`: Error conditions that need attention
- `CRITICAL`: Severe errors requiring immediate action

### 5. Error Handling

Use FastAPI's HTTPException for error responses:

```python
from fastapi import HTTPException

@router.post("/endpoint")
async def handler():
    try:
        # operation
        return result
    except ValidationError as e:
        logger.error(f"Validation failed: {e}")
        raise HTTPException(status_code=400, detail="Invalid request data")
    except Exception as e:
        logger.error(f"Unexpected error: {e}", exc_info=True)
        raise HTTPException(status_code=500, detail="Internal server error")
```

**Status Codes**:
- 200: Success
- 201: Created
- 400: Bad request (validation error)
- 401: Unauthorized (missing/invalid token)
- 403: Forbidden (valid token, insufficient permissions)
- 404: Not found
- 500: Internal server error

### 6. Settings Injection

Use `get_settings()` for configuration:

```python
from app.config import get_settings

settings = get_settings()

@router.get("/endpoint")
async def handler():
    api_url = settings.sba_api_url
    ...
```

**Available Settings**:
- `app_env`: "development", "staging", "production"
- `debug`: Enable debug mode
- `database_url`: PostgreSQL connection string
- `secret_key`: JWT signing key
- `discord_client_id`, `discord_client_secret`: OAuth credentials
- `sba_api_url`, `pd_api_url`: League API endpoints
- `cors_origins`: Allowed frontend origins

## Integration Points

### With Other Backend Components

#### Database Layer (`app/database/`)

```python
from app.database.session import get_session
from app.database.operations import DatabaseOperations

@router.get("/games/{game_id}")
async def get_game(game_id: str):
    db_ops = DatabaseOperations()
    game_data = await db_ops.load_game_state(game_id)
    return game_data
```

#### Models (`app/models/`)

```python
from app.models import GameState
from app.models.db_models import Game

@router.post("/games/")
async def create_game(request: CreateGameRequest):
    # Use Pydantic models for validation
    # Use SQLAlchemy models for database operations
    ...
```

#### Authentication (`app/utils/auth.py`)

```python
from app.utils.auth import create_token, verify_token

@router.post("/auth/token")
async def create_auth_token(request: TokenRequest):
    token = create_token(user_data)
    return TokenResponse(access_token=token)
```

#### State Manager (`app/core/state_manager.py`)

```python
from app.core.state_manager import state_manager

@router.get("/games/{game_id}")
async def get_game(game_id: str):
    # Check in-memory state first (fast)
    state = state_manager.get_state(game_id)
    if not state:
        # Fall back to database
        state = await state_manager.recover_game(game_id)
    return state
```

### With Frontend

Frontend applications make HTTP requests to these endpoints:

```typescript
// Example frontend code (Vue/Nuxt)
const api = axios.create({
  baseURL: 'http://localhost:8000/api',
  headers: {
    'Authorization': `Bearer ${token}`
  }
});

// Health check
const health = await api.get('/health');

// Create game
const game = await api.post('/games/', {
  league_id: 'sba',
  home_team_id: 1,
  away_team_id: 2
});

// List games
const games = await api.get('/games/');
```

## Common Tasks

### Adding a New Endpoint

1. **Choose appropriate route module** (or create new one)
   ```bash
   # If creating new module
   touch app/api/routes/teams.py
   ```

2. **Define Pydantic models** for request/response
   ```python
   from pydantic import BaseModel

   class CreateTeamRequest(BaseModel):
       name: str
       league_id: str
       owner_id: str

   class TeamResponse(BaseModel):
       id: int
       name: str
       league_id: str
       created_at: str
   ```

3. **Create route handler**
   ```python
   import logging
   from fastapi import APIRouter, HTTPException
   from app.database.operations import DatabaseOperations

   logger = logging.getLogger(f'{__name__}.teams')
   router = APIRouter()

   @router.post("/", response_model=TeamResponse)
   async def create_team(request: CreateTeamRequest):
       """Create a new team"""
       logger.info(f"Creating team: {request.name}")

       try:
           db_ops = DatabaseOperations()
           team = await db_ops.create_team(
               name=request.name,
               league_id=request.league_id,
               owner_id=request.owner_id
           )
           return TeamResponse(**team)
       except Exception as e:
           logger.error(f"Failed to create team: {e}", exc_info=True)
           raise HTTPException(status_code=500, detail="Failed to create team")
   ```

4. **Register router in main.py**
   ```python
   from app.api.routes import teams

   app.include_router(teams.router, prefix="/api/teams", tags=["teams"])
   ```

5. **Test the endpoint**
   ```bash
   # Start server
   uv run python -m app.main

   # Test with curl
   curl -X POST http://localhost:8000/api/teams/ \
     -H "Content-Type: application/json" \
     -d '{"name": "Test Team", "league_id": "sba", "owner_id": "user123"}'

   # Or use Swagger UI
   # http://localhost:8000/docs
   ```

### Adding Authentication Dependency

When authentication is implemented, add a dependency for protected endpoints:

```python
from fastapi import Depends, HTTPException, Header
from app.utils.auth import verify_token

async def get_current_user(authorization: str = Header(...)):
    """Extract and verify user from JWT token"""
    try:
        scheme, token = authorization.split()
        if scheme.lower() != 'bearer':
            raise HTTPException(status_code=401, detail="Invalid auth scheme")

        payload = verify_token(token)
        return payload
    except Exception as e:
        raise HTTPException(status_code=401, detail="Invalid token")

@router.post("/games/")
async def create_game(
    request: CreateGameRequest,
    user = Depends(get_current_user)  # Automatically extracts and validates token
):
    # user contains decoded JWT payload
    owner_id = user['user_id']
    ...
```

### Adding Database Operations

For endpoints that need database access:

```python
from app.database.operations import DatabaseOperations
from app.database.session import get_session

@router.get("/games/{game_id}")
async def get_game(game_id: str):
    db_ops = DatabaseOperations()

    # Load game with all related data
    game_data = await db_ops.load_game_state(game_id)

    if not game_data:
        raise HTTPException(status_code=404, detail="Game not found")

    return game_data
```

### Adding Request Validation

Pydantic provides powerful validation:

```python
from pydantic import BaseModel, Field, validator

class CreateGameRequest(BaseModel):
    league_id: str = Field(..., regex="^(sba|pd)$")
    home_team_id: int = Field(..., gt=0)
    away_team_id: int = Field(..., gt=0)
    game_mode: str = Field(default="friendly", regex="^(ranked|friendly|practice)$")

    @validator('away_team_id')
    def teams_must_differ(cls, v, values):
        if 'home_team_id' in values and v == values['home_team_id']:
            raise ValueError('Home and away teams must be different')
        return v
```

### Adding Query Parameters

For list endpoints with filters:

```python
from typing import Optional

@router.get("/games/")
async def list_games(
    league_id: Optional[str] = None,
    status: Optional[str] = None,
    limit: int = 50,
    offset: int = 0
):
    """
    List games with optional filters

    Query params:
    - league_id: Filter by league ('sba' or 'pd')
    - status: Filter by status ('pending', 'active', 'completed')
    - limit: Number of results (default 50, max 100)
    - offset: Pagination offset (default 0)
    """
    db_ops = DatabaseOperations()
    games = await db_ops.list_games(
        league_id=league_id,
        status=status,
        limit=min(limit, 100),
        offset=offset
    )
    return games
```

## Testing

### Manual Testing with Swagger UI

FastAPI automatically generates interactive API documentation:

1. Start the backend server:
   ```bash
   uv run python -m app.main
   ```

2. Open Swagger UI:
   ```
   http://localhost:8000/docs
   ```

3. Features:
   - View all endpoints grouped by tags
   - See request/response schemas
   - Try out endpoints with test data
   - View response bodies and status codes
   - Copy curl commands

### Manual Testing with curl

```bash
# Health check
curl http://localhost:8000/api/health

# Database health check
curl http://localhost:8000/api/health/db

# Create token (stub)
curl -X POST http://localhost:8000/api/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "user_id": "test123",
    "username": "testuser",
    "discord_id": "123456789"
  }'

# List games (stub)
curl http://localhost:8000/api/games/
```

### Unit Testing

Create test files in `tests/unit/api/`:

```python
import pytest
from httpx import AsyncClient
from app.main import app

@pytest.mark.asyncio
async def test_health_endpoint():
    async with AsyncClient(app=app, base_url="http://test") as client:
        response = await client.get("/api/health")
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "healthy"
        assert "timestamp" in data
        assert "version" in data

@pytest.mark.asyncio
async def test_create_token():
    async with AsyncClient(app=app, base_url="http://test") as client:
        response = await client.post("/api/auth/token", json={
            "user_id": "test123",
            "username": "testuser",
            "discord_id": "123456789"
        })
        assert response.status_code == 200
        data = response.json()
        assert "access_token" in data
        assert data["token_type"] == "bearer"
```

### Integration Testing

Test with real database:

```python
import pytest
from httpx import AsyncClient
from app.main import app
from app.database.session import init_db

@pytest.mark.integration
@pytest.mark.asyncio
async def test_database_health_endpoint():
    await init_db()

    async with AsyncClient(app=app, base_url="http://test") as client:
        response = await client.get("/api/health/db")
        assert response.status_code == 200
        data = response.json()
        assert data["status"] == "healthy"
        assert data["database"] == "connected"
```

## Troubleshooting

### Issue: 422 Unprocessable Entity

**Cause**: Request body doesn't match Pydantic model schema

**Solution**:
1. Check Swagger UI for expected schema
2. Verify all required fields are present
3. Check field types match (string vs int, etc.)
4. Check for custom validators

**Example Error**:
```json
{
  "detail": [
    {
      "loc": ["body", "user_id"],
      "msg": "field required",
      "type": "value_error.missing"
    }
  ]
}
```

### Issue: 401 Unauthorized

**Cause**: Missing or invalid authentication token

**Solution**:
1. Verify token is included in Authorization header
2. Check token format: `Bearer <token>`
3. Verify token hasn't expired
4. Check secret_key matches between token creation and verification

### Issue: 500 Internal Server Error

**Cause**: Unhandled exception in route handler

**Solution**:
1. Check backend logs for stack trace
2. Add try/except blocks with specific error handling
3. Use HTTPException for expected errors
4. Log errors with `exc_info=True` for full stack trace

**Example**:
```python
try:
    result = await some_operation()
except ValidationError as e:
    # Expected error - return 400
    logger.warning(f"Validation failed: {e}")
    raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
    # Unexpected error - return 500 and log
    logger.error(f"Unexpected error: {e}", exc_info=True)
    raise HTTPException(status_code=500, detail="Internal server error")
```

### Issue: CORS Errors in Frontend

**Cause**: Frontend origin not in CORS allowed origins

**Solution**:
1. Check frontend URL in browser console error
2. Add origin to `.env`:
   ```bash
   CORS_ORIGINS=["http://localhost:3000", "http://localhost:3001", "http://your-frontend.com"]
   ```
3. Restart backend server

### Issue: Route Not Found (404)

**Cause**: Route not registered or incorrect path

**Solution**:
1. Verify router is imported and registered in `app/main.py`
2. Check prefix matches: `/api/games/` vs `/api/game/`
3. Verify route path in decorator matches request
4. Check method matches (GET vs POST)

**Example Registration**:
```python
# In app/main.py
from app.api.routes import games

app.include_router(
    games.router,
    prefix="/api/games",  # All routes will be /api/games/*
    tags=["games"]
)
```

## Best Practices

### 1. Keep Route Handlers Thin

Route handlers should orchestrate, not implement logic:

```python
# ❌ Bad - business logic in route handler
@router.post("/games/")
async def create_game(request: CreateGameRequest):
    # Validate teams exist
    # Check user permissions
    # Create game record
    # Initialize state
    # Send notifications
    # ... 100 lines of logic

# ✅ Good - orchestrate using service layer
@router.post("/games/")
async def create_game(request: CreateGameRequest, user = Depends(get_current_user)):
    game_service = GameService()
    game = await game_service.create_game(request, user['user_id'])
    return game
```

### 2. Use Explicit Response Models

Always specify `response_model` for type safety and documentation:

```python
# ❌ Bad - no response model
@router.get("/games/{game_id}")
async def get_game(game_id: str):
    return {"game_id": game_id, "status": "active"}

# ✅ Good - explicit response model
@router.get("/games/{game_id}", response_model=GameResponse)
async def get_game(game_id: str):
    return GameResponse(game_id=game_id, status="active")
```

### 3. Document Endpoints

Use docstrings for endpoint documentation:

```python
@router.get("/games/", response_model=List[GameListItem])
async def list_games(
    league_id: Optional[str] = None,
    status: Optional[str] = None
):
    """
    List games with optional filters.

    Query Parameters:
    - league_id: Filter by league ('sba' or 'pd')
    - status: Filter by status ('pending', 'active', 'completed')

    Returns:
    - List of GameListItem objects

    Raises:
    - 400: Invalid filter parameters
    - 500: Database error
    """
    ...
```

### 4. Handle Errors Gracefully

Return meaningful error messages:

```python
@router.get("/games/{game_id}")
async def get_game(game_id: str):
    game = await db_ops.get_game(game_id)

    if not game:
        raise HTTPException(
            status_code=404,
            detail=f"Game {game_id} not found"
        )

    return game
```

### 5. Use Dependency Injection

Share common logic via dependencies:

```python
from fastapi import Depends

async def get_db_ops():
    """Dependency for database operations"""
    return DatabaseOperations()

@router.get("/games/{game_id}")
async def get_game(
    game_id: str,
    db_ops: DatabaseOperations = Depends(get_db_ops)
):
    # db_ops injected automatically
    game = await db_ops.get_game(game_id)
    return game
```

### 6. Version Your API

When making breaking changes, use API versioning:

```python
# Option 1: URL path versioning
app.include_router(games_v1.router, prefix="/api/v1/games")
app.include_router(games_v2.router, prefix="/api/v2/games")

# Option 2: Header versioning (more complex)
@router.get("/games/")
async def list_games(api_version: str = Header(default="v1")):
    if api_version == "v2":
        return new_format_games()
    else:
        return legacy_format_games()
```

## References

- **FastAPI Documentation**: https://fastapi.tiangolo.com/
- **Pydantic Documentation**: https://docs.pydantic.dev/
- **Main Application**: `app/main.py` (router registration)
- **Settings**: `app/config.py` (application configuration)
- **Database Layer**: `app/database/operations.py` (DB operations)
- **WebSocket Layer**: `app/websocket/handlers.py` (real-time events)
- **Authentication**: `app/utils/auth.py` (JWT utilities)

## Future Enhancements

### Phase 1 (Weeks 1-4)

- [ ] Complete Discord OAuth flow in `auth.py`
- [ ] Add authentication middleware
- [ ] Create user session endpoints
- [ ] Add token refresh mechanism

### Phase 2+ (Weeks 5-13)

- [ ] Implement game CRUD in `games.py`
- [ ] Add game listing with filters and pagination
- [ ] Create team management endpoints
- [ ] Add roster/lineup endpoints
- [ ] Create statistics/analytics endpoints
- [ ] Add game history endpoints
- [ ] Implement WebSocket-REST synchronization

### Beyond MVP

- [ ] Add comprehensive API rate limiting
- [ ] Implement API key authentication for external integrations
- [ ] Create admin endpoints for moderation
- [ ] Add bulk operations endpoints
- [ ] Create export endpoints (CSV, JSON)
- [ ] Add advanced search and filtering

---

**Note**: This directory is currently 33% complete (health endpoints only). Most endpoints are stubs awaiting Phase 2+ implementation. Focus on health endpoints for current operational needs.

**Current Phase**: Phase 2 - Week 8
**Last Updated**: 2025-10-31
