""" Application FastAPI avec authentification intégrée. Ce fichier remplace main.py pour résoudre le problème de 404. """ import json import secrets import string from datetime import datetime from typing import Optional from fastapi import FastAPI, Depends, HTTPException, status, Request from fastapi.middleware.cors import CORSMiddleware from sqlalchemy.orm import Session from pydantic import BaseModel, EmailStr from passlib.context import CryptContext from app.database import get_db from app.models.user import User # App FastAPI app = FastAPI( title="Chartbastan API", version="1.0.0", docs_url="/docs", redoc_url="/redoc", ) # CORS app.add_middleware( CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) # Password hashing - using pbkdf2_sha256 instead of bcrypt for Windows compatibility pwd_context = CryptContext(schemes=["pbkdf2_sha256"], deprecated="auto") # ==================== SCHEMAS ==================== class LoginRequest(BaseModel): email: EmailStr password: str class RegisterRequest(BaseModel): email: EmailStr password: str name: Optional[str] = None referral_code: Optional[str] = None # ==================== HELPER FUNCTIONS ==================== 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) def generate_referral_code() -> str: alphabet = string.ascii_uppercase + string.digits return ''.join(secrets.choice(alphabet) for _ in range(8)) # ==================== ENDPOINTS ==================== @app.get("/") def read_root(): return {"message": "Chartbastan API"} @app.get("/health") def health_check(): return {"status": "healthy"} @app.post("/api/v1/auth/login") def login(request: LoginRequest, db: Session = Depends(get_db)): """Login endpoint.""" 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) if user.created_at else None, }, "meta": { "timestamp": datetime.utcnow().isoformat(), "version": "v1" } } @app.post("/api/v1/auth/register", status_code=status.HTTP_201_CREATED) def register(request: RegisterRequest, db: Session = Depends(get_db)): """Register endpoint.""" existing = db.query(User).filter(User.email == request.email).first() if existing: raise HTTPException( status_code=status.HTTP_409_CONFLICT, detail="Cet email est déjà utilisé" ) referral_code = generate_referral_code() while db.query(User).filter(User.referral_code == referral_code).first(): referral_code = generate_referral_code() new_user = User( email=request.email, password_hash=get_password_hash(request.password), 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(): """Logout endpoint.""" return { "data": {"message": "Déconnexion réussie"}, "meta": { "timestamp": datetime.utcnow().isoformat(), "version": "v1" } } # ==================== AUTRES ROUTERS ==================== from app.api.v1 import users, predictions, backtesting, leaderboard, badges from app.api.public.v1 import predictions as public_predictions from app.api.public.v1 import matches as public_matches app.include_router(users.router, prefix="/api/v1/users", tags=["users"]) 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"]) app.include_router(public_predictions.router) app.include_router(public_matches.router)