Initial commit
This commit is contained in:
5
backend/app/api/public/v1/__init__.py
Normal file
5
backend/app/api/public/v1/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
"""
|
||||
Public API v1 package.
|
||||
|
||||
This module provides v1 public API endpoints.
|
||||
"""
|
||||
162
backend/app/api/public/v1/matches.py
Normal file
162
backend/app/api/public/v1/matches.py
Normal 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
|
||||
}
|
||||
189
backend/app/api/public/v1/predictions.py
Normal file
189
backend/app/api/public/v1/predictions.py
Normal 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
|
||||
}
|
||||
Reference in New Issue
Block a user