2026-02-01 09:31:38 +01:00

264 lines
8.0 KiB
Python

"""Application FastAPI principale."""
import json
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from datetime import datetime
# #region agent log
def write_debug_log(hypothesisId: str, location: str, message: str, data: dict = None):
"""Écrit un log NDJSON pour le debug."""
log_entry = {
"sessionId": "debug-session",
"runId": "run1",
"hypothesisId": hypothesisId,
"location": location,
"message": message,
"data": data or {},
"timestamp": datetime.now().timestamp() * 1000
}
with open(r"d:\\dev_new_pc\\chartbastan\\.cursor\\debug.log", "a") as f:
f.write(json.dumps(log_entry) + "\n")
# #endregion
app = FastAPI(
title="Chartbastan API",
description="""
API publique pour les prédictions de matchs basées sur l'énergie collective.
## Endpoints Publics
- **Prédictions**: Accès aux prédictions de matchs avec filtres
- **Matchs**: Liste des matchs avec filtres par ligue et statut
## Authentification
Les endpoints publics ne nécessitent pas d'authentification.
Pour un usage intensif, générez une clé API via le dashboard développeur.
""",
version="1.0.0",
docs_url="/docs",
redoc_url="/redoc",
openapi_url="/openapi.json"
)
# #region agent log
write_debug_log("A", "main.py:1", "FastAPI app initialized", {"app_title": "Chartbastan API"})
# #endregion
# Rate limiting middleware DÉSACTIVÉ PERMANENTMENT
# app.add_middleware(RateLimitMiddleware, public_limit=10, authenticated_limit=100)
# #region agent log
write_debug_log("E", "main.py:27", "RateLimitMiddleware DISABLED permanently", {"status": "disabled"})
# #endregion
# CORS configuration
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allow all origins for public API
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# #region agent log
write_debug_log("E", "main.py:35", "CORSMiddleware configured", {"allow_origins": ["*"], "allow_methods": ["*"], "allow_headers": ["*"]})
# #endregion
@app.middleware("http")
async def log_requests(request: Request, call_next):
"""Log toutes les requêtes entrantes pour diagnostic."""
start_time = datetime.now()
write_debug_log("A", "main.py:44", "Incoming request", {
"method": request.method,
"url": str(request.url),
"path": request.url.path,
"headers": dict(request.headers)
})
response = await call_next(request)
process_time = (datetime.now() - start_time).total_seconds()
write_debug_log("A", "main.py:44", "Request completed", {
"method": request.method,
"url": str(request.url),
"path": request.url.path,
"status_code": response.status_code,
"process_time": process_time
})
return response
@app.get("/")
def read_root():
"""Endpoint racine de l'API."""
write_debug_log("A", "main.py:58", "Root endpoint called", {"path": "/"})
return {"message": "Chartbastan API"}
@app.get("/health")
def health_check():
"""Endpoint de vérification de santé."""
return {"status": "healthy"}
# ============================================================
# AUTH ENDPOINTS - Définis directement ici pour garantir l'enregistrement
# ============================================================
from fastapi import Depends, HTTPException, status
from sqlalchemy.orm import Session
from pydantic import BaseModel, EmailStr
from typing import Optional
from passlib.context import CryptContext
from app.database import get_db
from app.models.user import User
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
class LoginRequest(BaseModel):
email: EmailStr
password: str
class RegisterRequest(BaseModel):
email: EmailStr
password: str
name: Optional[str] = None
referral_code: Optional[str] = None
def verify_password(plain_password: str, hashed_password: str) -> bool:
return pwd_context.verify(plain_password, hashed_password)
def get_password_hash(password: str) -> str:
return pwd_context.hash(password)
@app.post("/api/v1/auth/login")
def login_user(request: LoginRequest, db: Session = Depends(get_db)):
"""Connecter un utilisateur."""
user = db.query(User).filter(User.email == request.email).first()
if not user:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Email ou mot de passe incorrect"
)
if not user.password_hash:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Email ou mot de passe incorrect"
)
if not verify_password(request.password, user.password_hash):
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Email ou mot de passe incorrect"
)
return {
"data": {
"id": user.id,
"email": user.email,
"name": user.name,
"is_premium": user.is_premium,
"referral_code": user.referral_code,
"created_at": str(user.created_at),
},
"meta": {
"timestamp": datetime.utcnow().isoformat(),
"version": "v1"
}
}
@app.post("/api/v1/auth/register", status_code=status.HTTP_201_CREATED)
def register_user(request: RegisterRequest, db: Session = Depends(get_db)):
"""Inscrire un nouvel utilisateur."""
import secrets
import string
existing_user = db.query(User).filter(User.email == request.email).first()
if existing_user:
raise HTTPException(
status_code=status.HTTP_409_CONFLICT,
detail="Cet email est déjà utilisé"
)
# Générer code de parrainage
alphabet = string.ascii_uppercase + string.digits
referral_code = ''.join(secrets.choice(alphabet) for _ in range(8))
password_hash = get_password_hash(request.password)
new_user = User(
email=request.email,
password_hash=password_hash,
name=request.name,
is_premium=False,
referral_code=referral_code,
created_at=datetime.utcnow(),
updated_at=datetime.utcnow()
)
db.add(new_user)
db.commit()
db.refresh(new_user)
return {
"data": {
"id": new_user.id,
"email": new_user.email,
"name": new_user.name,
"is_premium": new_user.is_premium,
"referral_code": new_user.referral_code,
"created_at": str(new_user.created_at),
},
"meta": {
"timestamp": datetime.utcnow().isoformat(),
"version": "v1"
}
}
@app.post("/api/v1/auth/logout")
def logout_user():
"""Déconnecter l'utilisateur."""
return {
"data": {"message": "Déconnexion réussie"},
"meta": {
"timestamp": datetime.utcnow().isoformat(),
"version": "v1"
}
}
# ============================================================
# FIN AUTH ENDPOINTS
# ============================================================
# Include API routers
from app.api.v1 import users
from app.api.v1 import auth
from app.api.v1 import predictions
from app.api.v1 import backtesting
from app.api.v1 import leaderboard
from app.api.v1 import badges
from app.api.public.v1 import predictions as public_predictions
from app.api.public.v1 import matches as public_matches
# #region agent log
write_debug_log("C", "main.py:101", "Including routers", {
"routers": ["users", "auth", "predictions", "backtesting", "leaderboard", "badges", "public_predictions", "public_matches"]
})
# #endregion
app.include_router(users.router, prefix="/api/v1/users", tags=["users"])
app.include_router(auth.router, prefix="/api/v1/auth", tags=["auth"])
app.include_router(predictions.router)
app.include_router(backtesting.router)
app.include_router(leaderboard.router)
app.include_router(badges.router, prefix="/api/v1/badges", tags=["badges"])
# Public API routers
app.include_router(public_predictions.router)
app.include_router(public_matches.router)
# Trigger reload