107 lines
3.8 KiB
Python
107 lines
3.8 KiB
Python
"""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),
|
|
)
|