Initial commit

This commit is contained in:
2026-02-01 09:31:38 +01:00
commit e02db93960
4396 changed files with 1511612 additions and 0 deletions

View File

@@ -0,0 +1,5 @@
"""
Public API package.
This module provides public API endpoints with OpenAPI documentation.
"""

View File

@@ -0,0 +1,5 @@
"""
Public API v1 package.
This module provides v1 public API endpoints.
"""

View File

@@ -0,0 +1,162 @@
"""
Public API endpoints for matches.
This module provides public endpoints for retrieving matches without authentication.
"""
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Query, HTTPException, status, Depends
from sqlalchemy.orm import Session
from app.database import get_db
from app.models.match import Match
from app.schemas.public import (
PublicMatchResponse,
SuccessResponse,
SuccessMeta
)
router = APIRouter(prefix="/api/public/v1", tags=["public-matches"])
@router.get("/matches", response_model=SuccessResponse)
def get_public_matches(
limit: int = Query(20, ge=1, le=100, description="Maximum number of matches to return (max 100)"),
offset: int = Query(0, ge=0, description="Number of matches to skip"),
league: Optional[str] = Query(None, description="Filter by league name (case-insensitive)"),
status_filter: Optional[str] = Query(None, alias="status", description="Filter by match status"),
db: Session = Depends(get_db)
):
"""
Get public matches with pagination and filters.
This endpoint provides publicly accessible matches without authentication.
Data is limited to non-sensitive information only.
Args:
limit: Maximum number of matches to return (1-100, default: 20)
offset: Number of matches to skip (default: 0)
league: Optional filter by league name (case-insensitive)
status: Optional filter by match status (e.g., "scheduled", "completed", "ongoing")
db: Database session (injected)
Returns:
Paginated list of public matches
Example Requests:
GET /api/public/v1/matches
GET /api/public/v1/matches?limit=10&offset=0
GET /api/public/v1/matches?league=Ligue%201
GET /api/public/v1/matches?status=scheduled
Example Response:
{
"data": [
{
"id": 1,
"home_team": "PSG",
"away_team": "Olympique de Marseille",
"date": "2026-01-18T20:00:00Z",
"league": "Ligue 1",
"status": "scheduled"
}
],
"meta": {
"total": 45,
"limit": 20,
"offset": 0,
"timestamp": "2026-01-17T14:30:00Z",
"version": "v1"
}
}
"""
# Build query
query = db.query(Match)
# Apply filters
if league:
query = query.filter(Match.league.ilike(f"%{league}%"))
if status_filter:
query = query.filter(Match.status == status_filter)
# Get total count
total = query.count()
# Apply pagination
matches = query.order_by(Match.date).offset(offset).limit(limit).all()
# Build response
match_responses = []
for match in matches:
match_responses.append({
"id": match.id,
"home_team": match.home_team,
"away_team": match.away_team,
"date": match.date.isoformat() if match.date else None,
"league": match.league,
"status": match.status
})
return {
"data": match_responses,
"meta": {
"total": total,
"limit": limit,
"offset": offset,
"timestamp": datetime.utcnow().isoformat() + "Z",
"version": "v1"
}
}
@router.get("/matches/{match_id}", response_model=PublicMatchResponse)
def get_public_match(
match_id: int,
db: Session = Depends(get_db)
):
"""
Get a specific public match by ID.
This endpoint provides a publicly accessible match without authentication.
Args:
match_id: ID of match
db: Database session (injected)
Returns:
Public match details
Raises:
404: If match doesn't exist
Example Request:
GET /api/public/v1/matches/1
Example Response:
{
"id": 1,
"home_team": "PSG",
"away_team": "Olympique de Marseille",
"date": "2026-01-18T20:00:00Z",
"league": "Ligue 1",
"status": "scheduled"
}
"""
match = db.query(Match).filter(Match.id == match_id).first()
if not match:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Match with id {match_id} not found"
)
return {
"id": match.id,
"home_team": match.home_team,
"away_team": match.away_team,
"date": match.date.isoformat() if match.date else None,
"league": match.league,
"status": match.status
}

View File

@@ -0,0 +1,189 @@
"""
Public API endpoints for predictions.
This module provides public endpoints for retrieving predictions without authentication.
"""
from datetime import datetime
from typing import Optional
from fastapi import APIRouter, Query, Depends
from sqlalchemy.orm import Session
from app.database import get_db
from app.services.prediction_service import PredictionService
from app.schemas.public import (
PublicPredictionResponse,
SuccessResponse,
SuccessMeta
)
router = APIRouter(prefix="/api/public/v1", tags=["public-predictions"])
@router.get("/predictions", response_model=SuccessResponse)
def get_public_predictions(
limit: int = Query(20, ge=1, le=100, description="Maximum number of predictions to return (max 100)"),
offset: int = Query(0, ge=0, description="Number of predictions to skip"),
league: Optional[str] = Query(None, description="Filter by league name (case-insensitive)"),
db: Session = Depends(get_db)
):
"""
Get public predictions with pagination and filters.
This endpoint provides publicly accessible predictions without authentication.
Data is limited to non-sensitive information only.
Args:
limit: Maximum number of predictions to return (1-100, default: 20)
offset: Number of predictions to skip (default: 0)
league: Optional filter by league name (case-insensitive partial match)
db: Database session (injected)
Returns:
Paginated list of public predictions with match details
Example Requests:
GET /api/public/v1/predictions
GET /api/public/v1/predictions?limit=10&offset=0
GET /api/public/v1/predictions?league=Ligue%201
Example Response:
{
"data": [
{
"id": 1,
"match_id": 1,
"match": {
"id": 1,
"home_team": "PSG",
"away_team": "Olympique de Marseille",
"date": "2026-01-18T20:00:00Z",
"league": "Ligue 1",
"status": "scheduled"
},
"energy_score": "high",
"confidence": "65.0%",
"predicted_winner": "PSG",
"created_at": "2026-01-17T12:00:00Z"
}
],
"meta": {
"total": 45,
"limit": 20,
"offset": 0,
"timestamp": "2026-01-17T14:30:00Z",
"version": "v1"
}
}
"""
service = PredictionService(db)
predictions, total = service.get_predictions_with_pagination(
limit=limit,
offset=offset,
team_id=None, # No team filter for public API
league=league,
date_min=None, # No date filter for public API
date_max=None
)
# Build response with match details
prediction_responses = []
for prediction in predictions:
match = prediction.match
prediction_responses.append({
"id": prediction.id,
"match_id": prediction.match_id,
"match": {
"id": match.id,
"home_team": match.home_team,
"away_team": match.away_team,
"date": match.date.isoformat() if match.date else None,
"league": match.league,
"status": match.status
},
"energy_score": prediction.energy_score,
"confidence": prediction.confidence,
"predicted_winner": prediction.predicted_winner,
"created_at": prediction.created_at.isoformat() if prediction.created_at else None
})
return {
"data": prediction_responses,
"meta": {
"total": total,
"limit": limit,
"offset": offset,
"timestamp": datetime.utcnow().isoformat() + "Z",
"version": "v1"
}
}
@router.get("/predictions/{prediction_id}", response_model=PublicPredictionResponse)
def get_public_prediction(
prediction_id: int,
db: Session = Depends(get_db)
):
"""
Get a specific public prediction by ID.
This endpoint provides a publicly accessible prediction without authentication.
Args:
prediction_id: ID of the prediction
db: Database session (injected)
Returns:
Public prediction with match details
Raises:
404: If prediction doesn't exist
Example Request:
GET /api/public/v1/predictions/1
Example Response:
{
"id": 1,
"match_id": 1,
"match": {
"id": 1,
"home_team": "PSG",
"away_team": "Olympique de Marseille",
"date": "2026-01-18T20:00:00Z",
"league": "Ligue 1",
"status": "scheduled"
},
"energy_score": "high",
"confidence": "65.0%",
"predicted_winner": "PSG",
"created_at": "2026-01-17T12:00:00Z"
}
"""
service = PredictionService(db)
prediction = service.get_prediction_by_id(prediction_id)
if not prediction:
from fastapi import HTTPException, status
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Prediction with id {prediction_id} not found"
)
match = prediction.match
return {
"id": prediction.id,
"match_id": prediction.match_id,
"match": {
"id": match.id,
"home_team": match.home_team,
"away_team": match.away_team,
"date": match.date.isoformat() if match.date else None,
"league": match.league,
"status": match.status
},
"energy_score": prediction.energy_score,
"confidence": prediction.confidence,
"predicted_winner": prediction.predicted_winner,
"created_at": prediction.created_at.isoformat() if prediction.created_at else None
}