groupe-meditation/backend/app/routers/auth.py

108 lines
3.8 KiB
Python
Raw Normal View History

2026-03-04 14:01:00 -05:00
"""Routes d'authentification."""
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select, func
from app.core.database import get_db
from app.core.security import hash_pin, verify_pin, create_token
from app.models.membre import Membre
from app.models.groupe import Groupe
from app.models.invitation import Invitation
from app.schemas.schemas import ConnexionRequest, InscriptionRequest, TokenResponse, MembrePublic
router = APIRouter(prefix="/auth", tags=["Authentification"])
@router.get("/health")
async def health(db: AsyncSession = Depends(get_db)):
result = await db.execute(select(Groupe).limit(1))
groupe = result.scalar_one_or_none()
return {
"status": "ok",
"groupe": groupe.nom if groupe else "Aucun",
"district": groupe.district if groupe else "-",
}
@router.get("/groupes")
async def liste_groupes(db: AsyncSession = Depends(get_db)):
result = await db.execute(select(Groupe).order_by(Groupe.nom))
groupes = result.scalars().all()
return [{"id": str(g.id), "nom": g.nom, "district": g.district} for g in groupes]
@router.post("/connexion", response_model=TokenResponse)
async def connexion(req: ConnexionRequest, db: AsyncSession = Depends(get_db)):
# Chercher le membre par prénom + groupe (insensible à la casse)
result = await db.execute(
select(Membre).where(
func.lower(Membre.prenom) == func.lower(req.prenom),
Membre.groupe_id == req.groupe_id,
Membre.actif == True,
)
)
membre = result.scalar_one_or_none()
if not membre or not verify_pin(req.pin, membre.pin_hash):
raise HTTPException(status_code=401, detail="Prénom ou PIN incorrect")
token = create_token(str(membre.id), str(membre.groupe_id))
return TokenResponse(
access_token=token,
membre=MembrePublic.model_validate(membre),
)
@router.post("/inscription", response_model=TokenResponse)
async def inscription(req: InscriptionRequest, db: AsyncSession = Depends(get_db)):
# Valider le code d'invitation
result = await db.execute(
select(Invitation).where(
Invitation.code == req.code_invitation,
Invitation.utilise == False,
)
)
invitation = result.scalar_one_or_none()
if not invitation:
raise HTTPException(status_code=400, detail="Code d'invitation invalide")
if invitation.expire_le < datetime.now(timezone.utc):
raise HTTPException(status_code=400, detail="Code d'invitation expiré")
# Vérifier unicité du prénom dans le groupe
exists = await db.execute(
select(Membre).where(
func.lower(Membre.prenom) == func.lower(req.prenom),
Membre.groupe_id == invitation.groupe_id,
Membre.actif == True,
)
)
if exists.scalar_one_or_none():
raise HTTPException(status_code=400, detail="Ce prénom est déjà utilisé dans ce groupe")
# Valider le PIN (4 chiffres)
if not req.pin.isdigit() or len(req.pin) != 4:
raise HTTPException(status_code=400, detail="Le PIN doit être composé de 4 chiffres")
# Créer le membre
membre = Membre(
groupe_id=invitation.groupe_id,
prenom=req.prenom.strip().title(),
nom=req.nom.strip().title(),
telephone=req.telephone,
courriel=req.courriel,
date_abstinence=req.date_abstinence,
pin_hash=hash_pin(req.pin),
)
db.add(membre)
# Marquer l'invitation utilisée
invitation.utilise = True
invitation.utilise_par = membre.id
await db.flush()
token = create_token(str(membre.id), str(membre.groupe_id))
return TokenResponse(
access_token=token,
membre=MembrePublic.model_validate(membre),
)