Initial commit: CLEAN architecture foundation for fantasy hockey backend

Implemented CLEAN architecture with clear separation of concerns:
- Domain layer with entities (Player, Team, Stats, FantasyTeam) and repository interfaces
- Application layer with use case implementations
- Infrastructure layer with NHL API and Yahoo Fantasy API adapters
- Presentation layer with FastAPI configuration and dependency injection

Key features:
- Swappable data source adapters (NHL API, Yahoo Fantasy API)
- Repository pattern for data access abstraction
- Dependency injection for loose coupling
- FastAPI framework with async support
- PostgreSQL database configuration
- Environment-based configuration management

Technology stack: Python 3.11+, FastAPI, PostgreSQL, nhl-api-py, yfpy

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

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Michael Simard
2025-11-23 17:13:58 -06:00
commit 337a6377de
26 changed files with 973 additions and 0 deletions

13
.env.example Normal file
View File

@@ -0,0 +1,13 @@
# Application Configuration
DEBUG=false
# Database
DATABASE_URL=postgresql+asyncpg://user:password@localhost:5432/fantasy_hockey
# Yahoo Fantasy API Credentials
YAHOO_CONSUMER_KEY=your_consumer_key_here
YAHOO_CONSUMER_SECRET=your_consumer_secret_here
# API Configuration
API_PREFIX=/api/v1
CORS_ORIGINS=["http://localhost:3000"]

54
.gitignore vendored Normal file
View File

@@ -0,0 +1,54 @@
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual Environment
venv/
env/
ENV/
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
# Environment variables
.env
.env.local
# Database
*.db
*.sqlite3
# Testing
.pytest_cache/
.coverage
htmlcov/
# Logs
*.log
logs/
# OS
.DS_Store
Thumbs.db

94
ARCHITECTURE.md Normal file
View File

@@ -0,0 +1,94 @@
# Architecture Documentation
## CLEAN Architecture Overview
This application implements CLEAN architecture principles to ensure maintainability, testability, and flexibility.
## Layer Responsibilities
### 1. Domain Layer (`src/domain/`)
**Purpose**: Contains enterprise business rules and core entities.
- **Entities** (`entities/`): Core business objects that represent the problem domain
- `Player`: NHL player information
- `SkaterStats`, `GoalieStats`: Player statistics
- `NHLTeam`: NHL team information
- `FantasyTeam`: Yahoo Fantasy team data
- **Repository Interfaces** (`repositories/`): Abstract contracts for data access
- `PlayerRepository`: Interface for player data operations
- `TeamRepository`: Interface for team data operations
- `FantasyRepository`: Interface for fantasy team operations
**Dependencies**: None. This layer is completely independent.
### 2. Application Layer (`src/application/`)
**Purpose**: Contains application-specific business rules.
- **Use Cases** (`use_cases/`): Application business logic
- `GetPlayerStatsUseCase`: Example use case for retrieving player statistics
- Future: `AnalyzeFantasyRosterUseCase`, `ComparePlayersUseCase`, etc.
- **DTOs** (`dto/`): Data transfer objects for cross-layer communication
**Dependencies**: Depends only on Domain layer abstractions.
### 3. Infrastructure Layer (`src/infrastructure/`)
**Purpose**: Contains implementations of external interfaces and frameworks.
- **Adapters** (`adapters/`): Implementations of repository interfaces
- `nhl/NHLPlayerAdapter`: Implements PlayerRepository using nhl-api-py
- `nhl/NHLTeamAdapter`: Implements TeamRepository using nhl-api-py
- `yahoo_fantasy/YahooFantasyAdapter`: Implements FantasyRepository using yfpy
- **Database** (`database/`): Database models and ORM configurations
- **Config** (`config/`): Application configuration and settings
**Dependencies**: Implements Domain interfaces using external libraries.
### 4. Presentation Layer (`src/presentation/`)
**Purpose**: Contains API routes and controllers.
- **API Routes** (`api/routes/`): FastAPI endpoint definitions
- **Dependencies** (`api/dependencies.py`): Dependency injection configuration
**Dependencies**: Uses Application use cases and Infrastructure implementations.
## Dependency Flow
```
Presentation Layer
Application Layer
Domain Layer (Abstractions)
Infrastructure Layer (Implementations)
```
**Key Principle**: Dependencies point inward. Outer layers depend on inner layers, never the reverse.
## Swapping Data Sources
To replace the NHL API with a different data source:
1. Create a new adapter implementing `PlayerRepository` or `TeamRepository`
2. Update the dependency injection in `src/presentation/api/dependencies.py`
3. **No changes required** to domain entities, use cases, or API routes
Example:
```python
# In dependencies.py
def get_player_repository() -> PlayerRepository:
# Change this line to use a different implementation
return AlternativePlayerAdapter() # Instead of NHLPlayerAdapter()
```
## Testing Strategy
- **Unit Tests**: Test domain entities and use cases in isolation
- **Integration Tests**: Test adapters against real or mocked external APIs
- **API Tests**: Test FastAPI routes using TestClient
All business logic can be tested without external dependencies by using mock repository implementations.

120
README.md Normal file
View File

@@ -0,0 +1,120 @@
# Project Kempe - Fantasy Hockey Backend
A CLEAN architecture backend application for managing and analyzing Yahoo Fantasy Hockey teams using NHL live data.
## Architecture
This application follows CLEAN architecture principles with clear separation of concerns:
```
src/
├── domain/ # Enterprise business rules
│ ├── entities/ # Core business entities
│ └── repositories/ # Repository interfaces (ports)
├── application/ # Application business rules
│ ├── use_cases/ # Use case implementations
│ └── dto/ # Data Transfer Objects
├── infrastructure/ # Frameworks and drivers
│ ├── adapters/ # External service adapters
│ │ ├── nhl/ # NHL API implementation
│ │ └── yahoo_fantasy/ # Yahoo Fantasy API implementation
│ ├── database/ # Database models and repositories
│ └── config/ # Configuration management
└── presentation/ # Interface adapters
└── api/ # FastAPI routes and controllers
```
## Key Design Principles
- **Dependency Inversion**: Core business logic depends on abstractions, not implementations
- **Separation of Concerns**: Each layer has a single, well-defined responsibility
- **Testability**: Business logic can be tested without external dependencies
- **Swappable Adapters**: Data sources (NHL API, Yahoo Fantasy API) can be replaced without changing business logic
## Technology Stack
- **Framework**: FastAPI
- **Database**: PostgreSQL
- **Language**: Python 3.11+
- **Data Sources**:
- NHL Unofficial API (via nhl-api-py)
- Yahoo Fantasy Sports API (via yfpy)
## Setup
### Prerequisites
- Python 3.11 or higher
- PostgreSQL 14 or higher
- Yahoo Developer Account (for Fantasy API access)
### Installation
1. Clone the repository:
```bash
cd /Users/michaelsimard/dev/services/project-kempe-backend
```
2. Create and activate a virtual environment:
```bash
python -m venv venv
source venv/bin/activate # On Windows: venv\Scripts\activate
```
3. Install dependencies:
```bash
pip install -r requirements.txt
```
4. Configure environment variables:
```bash
cp .env.example .env
# Edit .env with your actual credentials
```
5. Set up the database:
```bash
# Create PostgreSQL database
createdb fantasy_hockey
# Run migrations (once implemented)
alembic upgrade head
```
### Running the Application
Development server:
```bash
uvicorn src.presentation.api.main:app --reload --host 0.0.0.0 --port 8000
```
The API will be available at:
- Base URL: http://localhost:8000
- Interactive docs: http://localhost:8000/docs
- Alternative docs: http://localhost:8000/redoc
## Development
### Running Tests
```bash
pytest tests/ -v
pytest tests/ --cov=src --cov-report=html
```
### Code Quality
```bash
# Format code
black src/ tests/
# Lint code
ruff check src/ tests/
# Type checking
mypy src/
```
### Project Structure
See [ARCHITECTURE.md](ARCHITECTURE.md) for detailed architecture documentation.
## API Documentation
Once running, visit http://localhost:8000/docs for interactive API documentation.

26
requirements.txt Normal file
View File

@@ -0,0 +1,26 @@
# Web Framework
fastapi==0.115.5
uvicorn[standard]==0.32.1
pydantic==2.10.3
pydantic-settings==2.6.1
# Database
sqlalchemy==2.0.36
asyncpg==0.30.0
alembic==1.14.0
# External APIs
nhl-api-py==1.1.0
yfpy==14.1.1
# Utilities
python-dotenv==1.0.1
httpx==0.28.1
# Development
pytest==8.3.4
pytest-asyncio==0.24.0
pytest-cov==6.0.0
black==24.10.0
ruff==0.8.4
mypy==1.13.0

1
src/__init__.py Normal file
View File

@@ -0,0 +1 @@
"""Project Kempe backend package."""

View File

@@ -0,0 +1,4 @@
"""Application use cases package."""
from .get_player_stats import GetPlayerStatsUseCase
__all__ = ["GetPlayerStatsUseCase"]

View File

@@ -0,0 +1,53 @@
"""Use case for retrieving player statistics."""
from typing import Optional, Union
from src.domain.entities import Player, SkaterStats, GoalieStats
from src.domain.repositories import PlayerRepository
class GetPlayerStatsUseCase:
"""
Use case for retrieving a player's current season statistics.
This use case demonstrates CLEAN architecture principles:
- Depends on repository abstraction, not concrete implementation
- Contains business logic independent of external frameworks
- Can be tested without any infrastructure dependencies
"""
def __init__(self, player_repository: PlayerRepository):
"""
Initializes the use case with required dependencies.
Args:
player_repository: Repository for accessing player data
"""
self.player_repository = player_repository
async def execute(
self, player_id: str
) -> Optional[tuple[Player, Union[SkaterStats, GoalieStats]]]:
"""
Retrieves a player and their current season statistics.
Args:
player_id: The unique identifier of the player
Returns:
A tuple containing the Player and their statistics, or None if not found
"""
# Get player information
player = await self.player_repository.get_player_by_id(player_id)
if not player:
return None
# Get appropriate statistics based on position
if player.is_goalie():
stats = await self.player_repository.get_goalie_stats(player_id)
else:
stats = await self.player_repository.get_skater_stats(player_id)
if not stats:
return None
return (player, stats)

View File

@@ -0,0 +1,13 @@
"""Domain entities package."""
from .player import Player
from .player_stats import GoalieStats, SkaterStats
from .team import NHLTeam
from .fantasy_team import FantasyTeam
__all__ = [
"Player",
"SkaterStats",
"GoalieStats",
"NHLTeam",
"FantasyTeam",
]

View File

@@ -0,0 +1,33 @@
"""Fantasy team domain entity."""
from dataclasses import dataclass
from typing import List
@dataclass
class FantasyTeam:
"""Represents a Yahoo Fantasy Hockey team."""
id: str
name: str
manager_name: str
league_id: str
player_ids: List[str]
wins: int
losses: int
ties: int
points_for: float
points_against: float
rank: int
@property
def win_percentage(self) -> float:
"""Calculates the team's winning percentage."""
total_games = self.wins + self.losses + self.ties
if total_games == 0:
return 0.0
return self.wins / total_games
@property
def roster_size(self) -> int:
"""Returns the number of players on the roster."""
return len(self.player_ids)

View File

@@ -0,0 +1,37 @@
"""Player domain entity."""
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
@dataclass
class Player:
"""Represents an NHL player in the domain model."""
id: str
first_name: str
last_name: str
jersey_number: Optional[int]
position: str # C, LW, RW, D, G
team_id: str
is_active: bool
birth_date: Optional[datetime]
height_inches: Optional[int]
weight_pounds: Optional[int]
@property
def full_name(self) -> str:
"""Returns the player's full name."""
return f"{self.first_name} {self.last_name}"
def is_forward(self) -> bool:
"""Checks if the player is a forward."""
return self.position in ["C", "LW", "RW"]
def is_defenseman(self) -> bool:
"""Checks if the player is a defenseman."""
return self.position == "D"
def is_goalie(self) -> bool:
"""Checks if the player is a goalie."""
return self.position == "G"

View File

@@ -0,0 +1,48 @@
"""Player statistics domain entity."""
from dataclasses import dataclass
from datetime import datetime
from typing import Optional
@dataclass
class SkaterStats:
"""Statistics for skaters (forwards and defensemen)."""
player_id: str
games_played: int
goals: int
assists: int
points: int
plus_minus: int
penalty_minutes: int
shots: int
shooting_percentage: float
time_on_ice_per_game: Optional[float]
powerplay_goals: int
powerplay_points: int
shorthanded_goals: int
game_winning_goals: int
faceoff_percentage: Optional[float]
hits: int
blocked_shots: int
updated_at: datetime
@dataclass
class GoalieStats:
"""Statistics for goalies."""
player_id: str
games_played: int
games_started: int
wins: int
losses: int
overtime_losses: int
saves: int
shots_against: int
goals_against: int
save_percentage: float
goals_against_average: float
shutouts: int
time_on_ice: Optional[float]
updated_at: datetime

View File

@@ -0,0 +1,22 @@
"""Team domain entity."""
from dataclasses import dataclass
from typing import Optional
@dataclass
class NHLTeam:
"""Represents an NHL team in the domain model."""
id: str
name: str
abbreviation: str
city: str
division: str
conference: str
venue_name: Optional[str]
is_active: bool
@property
def full_name(self) -> str:
"""Returns the team's full name with city."""
return f"{self.city} {self.name}"

View File

@@ -0,0 +1,10 @@
"""Domain repository interfaces package."""
from .player_repository import PlayerRepository
from .team_repository import TeamRepository
from .fantasy_repository import FantasyRepository
__all__ = [
"PlayerRepository",
"TeamRepository",
"FantasyRepository",
]

View File

@@ -0,0 +1,31 @@
"""Fantasy team repository interface."""
from abc import ABC, abstractmethod
from typing import List, Optional
from src.domain.entities import FantasyTeam
class FantasyRepository(ABC):
"""Abstract interface for Yahoo Fantasy Hockey data access."""
@abstractmethod
async def get_fantasy_team(
self, league_id: str, team_id: str
) -> Optional[FantasyTeam]:
"""Retrieves a specific fantasy team from a league."""
pass
@abstractmethod
async def get_user_teams(self, user_id: str) -> List[FantasyTeam]:
"""Retrieves all fantasy teams for a user."""
pass
@abstractmethod
async def get_team_roster(self, league_id: str, team_id: str) -> List[str]:
"""Retrieves the player IDs on a fantasy team's roster."""
pass
@abstractmethod
async def get_league_standings(self, league_id: str) -> List[FantasyTeam]:
"""Retrieves all teams in a league ordered by standings."""
pass

View File

@@ -0,0 +1,36 @@
"""Player repository interface."""
from abc import ABC, abstractmethod
from typing import List, Optional
from src.domain.entities import Player, SkaterStats, GoalieStats
class PlayerRepository(ABC):
"""Abstract interface for player data access."""
@abstractmethod
async def get_player_by_id(self, player_id: str) -> Optional[Player]:
"""Retrieves a player by their unique identifier."""
pass
@abstractmethod
async def get_players_by_team(self, team_id: str) -> List[Player]:
"""Retrieves all players for a specific team."""
pass
@abstractmethod
async def search_players(
self, name: str, position: Optional[str] = None
) -> List[Player]:
"""Searches for players by name and optionally by position."""
pass
@abstractmethod
async def get_skater_stats(self, player_id: str) -> Optional[SkaterStats]:
"""Retrieves current season statistics for a skater."""
pass
@abstractmethod
async def get_goalie_stats(self, player_id: str) -> Optional[GoalieStats]:
"""Retrieves current season statistics for a goalie."""
pass

View File

@@ -0,0 +1,29 @@
"""Team repository interface."""
from abc import ABC, abstractmethod
from typing import List, Optional
from src.domain.entities import NHLTeam
class TeamRepository(ABC):
"""Abstract interface for NHL team data access."""
@abstractmethod
async def get_team_by_id(self, team_id: str) -> Optional[NHLTeam]:
"""Retrieves a team by its unique identifier."""
pass
@abstractmethod
async def get_all_teams(self) -> List[NHLTeam]:
"""Retrieves all NHL teams."""
pass
@abstractmethod
async def get_teams_by_division(self, division: str) -> List[NHLTeam]:
"""Retrieves all teams in a specific division."""
pass
@abstractmethod
async def get_teams_by_conference(self, conference: str) -> List[NHLTeam]:
"""Retrieves all teams in a specific conference."""
pass

View File

@@ -0,0 +1,4 @@
"""NHL API adapter package."""
from .nhl_adapter import NHLPlayerAdapter, NHLTeamAdapter
__all__ = ["NHLPlayerAdapter", "NHLTeamAdapter"]

View File

@@ -0,0 +1,109 @@
"""NHL API adapter implementation."""
from typing import List, Optional
from datetime import datetime
from nhl_api import NHLClient
from src.domain.entities import Player, SkaterStats, GoalieStats, NHLTeam
from src.domain.repositories import PlayerRepository, TeamRepository
class NHLPlayerAdapter(PlayerRepository):
"""Adapter for NHL API player data access."""
def __init__(self):
"""Initializes the NHL API client."""
self.client = NHLClient()
async def get_player_by_id(self, player_id: str) -> Optional[Player]:
"""Retrieves a player by their unique identifier."""
try:
# TODO: Implement actual NHL API calls
# This is a placeholder for the actual implementation
# The nhl-api-py library will be used here
pass
except Exception as e:
# Log error appropriately
return None
async def get_players_by_team(self, team_id: str) -> List[Player]:
"""Retrieves all players for a specific team."""
try:
# TODO: Implement actual NHL API calls
pass
except Exception as e:
# Log error appropriately
return []
async def search_players(
self, name: str, position: Optional[str] = None
) -> List[Player]:
"""Searches for players by name and optionally by position."""
try:
# TODO: Implement actual NHL API calls
pass
except Exception as e:
# Log error appropriately
return []
async def get_skater_stats(self, player_id: str) -> Optional[SkaterStats]:
"""Retrieves current season statistics for a skater."""
try:
# TODO: Implement actual NHL API calls
pass
except Exception as e:
# Log error appropriately
return None
async def get_goalie_stats(self, player_id: str) -> Optional[GoalieStats]:
"""Retrieves current season statistics for a goalie."""
try:
# TODO: Implement actual NHL API calls
pass
except Exception as e:
# Log error appropriately
return None
class NHLTeamAdapter(TeamRepository):
"""Adapter for NHL API team data access."""
def __init__(self):
"""Initializes the NHL API client."""
self.client = NHLClient()
async def get_team_by_id(self, team_id: str) -> Optional[NHLTeam]:
"""Retrieves a team by its unique identifier."""
try:
# TODO: Implement actual NHL API calls
pass
except Exception as e:
# Log error appropriately
return None
async def get_all_teams(self) -> List[NHLTeam]:
"""Retrieves all NHL teams."""
try:
# TODO: Implement actual NHL API calls
pass
except Exception as e:
# Log error appropriately
return []
async def get_teams_by_division(self, division: str) -> List[NHLTeam]:
"""Retrieves all teams in a specific division."""
try:
# TODO: Implement actual NHL API calls
pass
except Exception as e:
# Log error appropriately
return []
async def get_teams_by_conference(self, conference: str) -> List[NHLTeam]:
"""Retrieves all teams in a specific conference."""
try:
# TODO: Implement actual NHL API calls
pass
except Exception as e:
# Log error appropriately
return []

View File

@@ -0,0 +1,4 @@
"""Yahoo Fantasy API adapter package."""
from .yahoo_adapter import YahooFantasyAdapter
__all__ = ["YahooFantasyAdapter"]

View File

@@ -0,0 +1,62 @@
"""Yahoo Fantasy API adapter implementation."""
from typing import List, Optional
from yfpy.query import YahooFantasySportsQuery
from src.domain.entities import FantasyTeam
from src.domain.repositories import FantasyRepository
class YahooFantasyAdapter(FantasyRepository):
"""Adapter for Yahoo Fantasy Sports API data access."""
def __init__(self, consumer_key: str, consumer_secret: str):
"""
Initializes the Yahoo Fantasy API client.
Args:
consumer_key: Yahoo API consumer key
consumer_secret: Yahoo API consumer secret
"""
# TODO: Initialize YFPY client with OAuth credentials
# self.client = YahooFantasySportsQuery(...)
self.consumer_key = consumer_key
self.consumer_secret = consumer_secret
async def get_fantasy_team(
self, league_id: str, team_id: str
) -> Optional[FantasyTeam]:
"""Retrieves a specific fantasy team from a league."""
try:
# TODO: Implement actual Yahoo Fantasy API calls
pass
except Exception as e:
# Log error appropriately
return None
async def get_user_teams(self, user_id: str) -> List[FantasyTeam]:
"""Retrieves all fantasy teams for a user."""
try:
# TODO: Implement actual Yahoo Fantasy API calls
pass
except Exception as e:
# Log error appropriately
return []
async def get_team_roster(self, league_id: str, team_id: str) -> List[str]:
"""Retrieves the player IDs on a fantasy team's roster."""
try:
# TODO: Implement actual Yahoo Fantasy API calls
pass
except Exception as e:
# Log error appropriately
return []
async def get_league_standings(self, league_id: str) -> List[FantasyTeam]:
"""Retrieves all teams in a league ordered by standings."""
try:
# TODO: Implement actual Yahoo Fantasy API calls
pass
except Exception as e:
# Log error appropriately
return []

View File

@@ -0,0 +1,4 @@
"""Configuration package."""
from .settings import Settings, get_settings
__all__ = ["Settings", "get_settings"]

View File

@@ -0,0 +1,33 @@
"""Application configuration settings."""
from pydantic_settings import BaseSettings, SettingsConfigDict
class Settings(BaseSettings):
"""Application settings loaded from environment variables."""
model_config = SettingsConfigDict(
env_file=".env",
env_file_encoding="utf-8",
case_sensitive=False,
)
# Application
app_name: str = "Project Kempe - Fantasy Hockey Backend"
app_version: str = "0.1.0"
debug: bool = False
# Database
database_url: str = "postgresql+asyncpg://user:password@localhost:5432/fantasy_hockey"
# Yahoo Fantasy API
yahoo_consumer_key: str = ""
yahoo_consumer_secret: str = ""
# API Configuration
api_prefix: str = "/api/v1"
cors_origins: list[str] = ["http://localhost:3000"]
def get_settings() -> Settings:
"""Returns the application settings singleton."""
return Settings()

View File

@@ -0,0 +1,47 @@
"""FastAPI dependency injection configuration."""
from functools import lru_cache
from src.domain.repositories import PlayerRepository, TeamRepository, FantasyRepository
from src.infrastructure.adapters.nhl import NHLPlayerAdapter, NHLTeamAdapter
from src.infrastructure.adapters.yahoo_fantasy import YahooFantasyAdapter
from src.infrastructure.config import Settings, get_settings
@lru_cache()
def get_cached_settings() -> Settings:
"""Returns cached application settings."""
return get_settings()
def get_player_repository() -> PlayerRepository:
"""
Provides the PlayerRepository implementation.
This function can be modified to return different implementations
without changing any business logic or API code.
"""
return NHLPlayerAdapter()
def get_team_repository() -> TeamRepository:
"""
Provides the TeamRepository implementation.
This function can be modified to return different implementations
without changing any business logic or API code.
"""
return NHLTeamAdapter()
def get_fantasy_repository() -> FantasyRepository:
"""
Provides the FantasyRepository implementation.
This function can be modified to return different implementations
without changing any business logic or API code.
"""
settings = get_cached_settings()
return YahooFantasyAdapter(
consumer_key=settings.yahoo_consumer_key,
consumer_secret=settings.yahoo_consumer_secret,
)

View File

@@ -0,0 +1,45 @@
"""FastAPI application entry point."""
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from src.infrastructure.config import get_settings
settings = get_settings()
app = FastAPI(
title=settings.app_name,
version=settings.app_version,
debug=settings.debug,
)
# Configure CORS
app.add_middleware(
CORSMiddleware,
allow_origins=settings.cors_origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/")
async def root():
"""Root endpoint."""
return {
"name": settings.app_name,
"version": settings.app_version,
"status": "operational",
}
@app.get("/health")
async def health_check():
"""Health check endpoint."""
return {"status": "healthy"}
# TODO: Include routers for players, teams, and fantasy endpoints
# from src.presentation.api.routes import players, teams, fantasy
# app.include_router(players.router, prefix=f"{settings.api_prefix}/players", tags=["players"])
# app.include_router(teams.router, prefix=f"{settings.api_prefix}/teams", tags=["teams"])
# app.include_router(fantasy.router, prefix=f"{settings.api_prefix}/fantasy", tags=["fantasy"])

View File

@@ -0,0 +1,41 @@
"""Player API routes."""
from fastapi import APIRouter, Depends, HTTPException
from typing import Union
from src.domain.repositories import PlayerRepository
from src.application.use_cases import GetPlayerStatsUseCase
from src.presentation.api.dependencies import get_player_repository
router = APIRouter()
@router.get("/{player_id}/stats")
async def get_player_stats(
player_id: str,
player_repo: PlayerRepository = Depends(get_player_repository),
):
"""
Retrieves a player's current season statistics.
This endpoint demonstrates CLEAN architecture:
- The route depends on the repository abstraction
- Business logic is in the use case, not the route
- The concrete implementation is injected via FastAPI's dependency system
"""
use_case = GetPlayerStatsUseCase(player_repo)
result = await use_case.execute(player_id)
if not result:
raise HTTPException(status_code=404, detail="Player not found")
player, stats = result
return {
"player": {
"id": player.id,
"name": player.full_name,
"position": player.position,
"team_id": player.team_id,
},
"stats": stats.__dict__,
}