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

213 lines
6.7 KiB
Python

"""Routes d'administration reservees au sysadmin."""
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import func, select
from pydantic import BaseModel
from app.core.database import get_db
from app.core.security import create_token, get_current_membre, is_sysadmin, verify_pin
from app.services.admin_database import (
assurer_sysadmin_instance,
creer_groupe,
initialiser_demo,
modifier_groupe,
reinitialiser_groupe,
reinitialiser_demo,
supprimer_groupe,
)
from app.services.group_backups import (
creer_sauvegarde_groupe,
lister_sauvegardes_groupe,
restaurer_sauvegarde_groupe,
)
from app.models.groupe import Groupe
from app.models.membre import Membre
from app.models.reunion import Reunion
from app.models.historique_modification import HistoriqueModification
router = APIRouter(prefix="/admin", tags=["Administration"])
class SysadminLoginRequest(BaseModel):
pin: str
class GroupeAdminRequest(BaseModel):
nom: str
district: str = "87-16"
adresse_local: str | None = None
jour_reunion: str | None = "mardi"
heure_reunion: str | None = "19:30"
assemblee_affaires_ordre: str = "premiere"
format_reunion: str | None = "Discussion ouverte"
lien_aa87: str = "https://aa87.org"
class RestoreGroupeRequest(BaseModel):
fichier: str
async def require_sysadmin(membre=Depends(get_current_membre)):
if not is_sysadmin(membre):
raise HTTPException(status_code=403, detail="Réservé au sysadmin")
return membre
@router.post("/sysadmin-login")
async def sysadmin_login(req: SysadminLoginRequest, db: AsyncSession = Depends(get_db)):
admin = await assurer_sysadmin_instance(db)
if admin.actif and verify_pin(req.pin, admin.pin_hash):
token = create_token(str(admin.id), None, expire_jours=1, subject_type="instance_admin")
return {
"access_token": token,
"token_type": "bearer",
"sysadmin_instance": True,
"membre": {
"id": str(admin.id),
"prenom": admin.prenom,
"courriel": admin.identifiant,
"groupe_id": None,
"date_abstinence": None,
"is_sysadmin": True,
},
}
raise HTTPException(status_code=401, detail="PIN Sysadmin invalide")
@router.post("/demo")
async def demo_database(
_sysadmin=Depends(require_sysadmin),
db: AsyncSession = Depends(get_db),
):
return await initialiser_demo(db)
@router.post("/demo/reset")
async def reset_demo_database(
_sysadmin=Depends(require_sysadmin),
db: AsyncSession = Depends(get_db),
):
return await reinitialiser_demo(db)
@router.post("/groupes/{groupe_id}/reset")
async def reset_groupe_admin(
groupe_id: str,
_sysadmin=Depends(require_sysadmin),
db: AsyncSession = Depends(get_db),
):
try:
return await reinitialiser_groupe(db, groupe_id)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.post("/groupes/{groupe_id}/backup")
async def backup_groupe_admin(
groupe_id: str,
_sysadmin=Depends(require_sysadmin),
db: AsyncSession = Depends(get_db),
):
try:
return await creer_sauvegarde_groupe(db, groupe_id)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.get("/groupes/{groupe_id}/backups")
async def backups_groupe_admin(
groupe_id: str,
_sysadmin=Depends(require_sysadmin),
):
return lister_sauvegardes_groupe(groupe_id)
@router.post("/groupes/{groupe_id}/restore")
async def restore_groupe_admin(
groupe_id: str,
req: RestoreGroupeRequest,
_sysadmin=Depends(require_sysadmin),
db: AsyncSession = Depends(get_db),
):
try:
return await restaurer_sauvegarde_groupe(db, groupe_id, req.fichier)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.get("/groupes")
async def liste_groupes_admin(
_sysadmin=Depends(require_sysadmin),
db: AsyncSession = Depends(get_db),
):
await initialiser_demo(db)
groupes = (await db.execute(select(Groupe))).scalars().all()
groupes.sort(key=lambda g: (g.nom == "Groupe Démonstration", g.nom.lower()))
out = []
for groupe in groupes:
nb_membres = (await db.execute(
select(func.count()).select_from(Membre).where(Membre.groupe_id == groupe.id)
)).scalar_one()
nb_reunions = (await db.execute(
select(func.count()).select_from(Reunion).where(Reunion.groupe_id == groupe.id)
)).scalar_one()
nb_traces = (await db.execute(
select(func.count()).select_from(HistoriqueModification).where(HistoriqueModification.groupe_id == groupe.id)
)).scalar_one()
out.append({
"id": str(groupe.id),
"nom": groupe.nom,
"district": groupe.district,
"adresse_local": groupe.adresse_local,
"jour_reunion": groupe.jour_reunion,
"heure_reunion": groupe.heure_reunion.strftime("%H:%M") if groupe.heure_reunion else None,
"assemblee_affaires_ordre": groupe.assemblee_affaires_ordre,
"format_reunion": groupe.format_reunion,
"lien_aa87": groupe.lien_aa87,
"cree_le": groupe.cree_le,
"membres": nb_membres,
"reunions": nb_reunions,
"traces": nb_traces,
"demo": groupe.nom == "Groupe Démonstration",
"protege": groupe.nom == "Groupe Démonstration",
})
return out
@router.post("/groupes")
async def creer_groupe_admin(
req: GroupeAdminRequest,
_sysadmin=Depends(require_sysadmin),
db: AsyncSession = Depends(get_db),
):
try:
groupe = await creer_groupe(db, req.model_dump())
return {"message": "Groupe créé", "id": str(groupe.id)}
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.patch("/groupes/{groupe_id}")
async def modifier_groupe_admin(
groupe_id: str,
req: GroupeAdminRequest,
_sysadmin=Depends(require_sysadmin),
db: AsyncSession = Depends(get_db),
):
try:
groupe = await modifier_groupe(db, groupe_id, req.model_dump())
return {"message": "Groupe modifié", "id": str(groupe.id)}
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))
@router.delete("/groupes/{groupe_id}")
async def supprimer_groupe_admin(
groupe_id: str,
_sysadmin=Depends(require_sysadmin),
db: AsyncSession = Depends(get_db),
):
try:
return await supprimer_groupe(db, groupe_id)
except ValueError as exc:
raise HTTPException(status_code=400, detail=str(exc))