CLAUDE: Refactor backend CLAUDE.md files for conciseness

Major reduction in CLAUDE.md file sizes to follow concise documentation standard:

| File | Before | After | Reduction |
|------|--------|-------|-----------|
| backend/CLAUDE.md | 2,467 | 123 | 95% |
| models/CLAUDE.md | 1,586 | 102 | 94% |
| websocket/CLAUDE.md | 2,094 | 119 | 94% |
| config/CLAUDE.md | 1,017 | 126 | 88% |
| database/CLAUDE.md | 946 | 130 | 86% |
| api/CLAUDE.md | 906 | 140 | 85% |

Total: 9,016 -> 740 lines (92% reduction)

All files now under 150 lines with:
- Essential patterns and usage
- Cross-references to related docs
- Quick-start examples
- Updated timestamps

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Cal Corum 2025-11-19 16:10:08 -06:00
parent cbdd8cf903
commit 88a5207c2c
6 changed files with 436 additions and 8434 deletions

File diff suppressed because it is too large Load Diff

View File

@ -1,906 +1,140 @@
# Backend API Layer - REST Endpoints
# API Module - REST Endpoints
## Overview
## Purpose
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.
REST API endpoints using FastAPI. Handles authentication, health checks, and supplementary game operations not suited for WebSocket (e.g., initial page loads, file uploads).
**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
## 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
├── __init__.py # Package marker
├── dependencies.py # FastAPI dependencies (auth, db session)
└── routes/
├── health.py # Health check endpoints
├── auth.py # Discord OAuth flow
└── games.py # Game CRUD operations
```
**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:
## Routes
### Health (`/health`)
```python
from fastapi import APIRouter
router = APIRouter()
@router.get("/endpoint")
async def handler():
...
GET /health # Basic health check
GET /health/ready # Ready check with DB connectivity
```
Routers are registered in `app/main.py`:
### Auth (`/auth`)
```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"])
GET /auth/discord # Initiate OAuth flow
GET /auth/discord/callback # OAuth callback, returns JWT
POST /auth/refresh # Refresh JWT token
```
**Conventions**:
- Each module exports a `router` variable
- Prefix and tags applied during registration in main.py
- Tags group endpoints in Swagger UI docs
### Games (`/games`)
```python
POST /games # Create new game
GET /games # List user's games
GET /games/{id} # Get game details
GET /games/{id}/lineup # Get game lineup
```
### 2. Pydantic Request/Response Models
## Dependencies
Use Pydantic models for type safety and validation:
### Authentication
```python
from app.api.dependencies import get_current_user
@router.get("/games")
async def list_games(user: User = Depends(get_current_user)):
# user is authenticated
```
### Database Session
```python
from app.api.dependencies import get_db
@router.post("/games")
async def create_game(db: AsyncSession = Depends(get_db)):
# Use db for operations
```
## FastAPI Patterns
### Request Validation
```python
from pydantic import BaseModel
class TokenRequest(BaseModel):
"""Request model for token creation"""
user_id: str
username: str
discord_id: str
class CreateGameRequest(BaseModel):
league_id: str
home_team_id: int
away_team_id: int
class TokenResponse(BaseModel):
"""Response model for token creation"""
access_token: str
token_type: str = "bearer"
@router.post("/games")
async def create_game(request: CreateGameRequest):
# request is validated
```
@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
### Response Model
```python
class GameResponse(BaseModel):
id: UUID
status: str
home_score: int
away_score: int
@router.get("/games/{id}", response_model=GameResponse)
async def get_game(id: UUID):
...
```
**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:
### Error Handling
```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/');
if not game:
raise HTTPException(status_code=404, detail="Game not found")
```
## 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:
### Add New Endpoint
1. Create route in `app/api/routes/`
2. Define Pydantic request/response models
3. Add dependencies (auth, db)
4. Register router in `app/main.py`
### Protected Route
```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
@router.get("/protected")
async def protected_route(
user: User = Depends(get_current_user)
):
# user contains decoded JWT payload
owner_id = user['user_id']
...
return {"user_id": user.id}
```
### Adding Database Operations
For endpoints that need database access:
## Registration
Routes are registered in `app/main.py`:
```python
from app.database.operations import DatabaseOperations
from app.database.session import get_session
from app.api.routes import health, auth, games
@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
app.include_router(health.router, prefix="/health")
app.include_router(auth.router, prefix="/auth")
app.include_router(games.router, prefix="/games")
```
### Adding Request Validation
## API Docs
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()
```
FastAPI auto-generates documentation:
- Swagger UI: `http://localhost:8000/docs`
- ReDoc: `http://localhost:8000/redoc`
## 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
- **WebSocket**: Main game communication via `../websocket/CLAUDE.md`
- **Auth Utilities**: See `../utils/auth.py`
---
**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
**Updated**: 2025-01-19

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff