"""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, hash_pin, 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.services.tenant_schemas import refresh_all_tenant_schemas, refresh_group_tenant_schema 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 class ChangePinRequest(BaseModel): pin_actuel: str nouveau_pin: 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("/sysadmin-pin") async def changer_pin_sysadmin( req: ChangePinRequest, sysadmin=Depends(require_sysadmin), ): if getattr(sysadmin, "groupe_id", None) is not None: raise HTTPException(status_code=403, detail="Réservé au sysadmin d'instance") if not req.nouveau_pin.isdigit() or len(req.nouveau_pin) != 4: raise HTTPException(status_code=400, detail="Le nouveau PIN doit contenir 4 chiffres") if not verify_pin(req.pin_actuel, sysadmin.pin_hash): raise HTTPException(status_code=401, detail="PIN actuel invalide") sysadmin.pin_hash = hash_pin(req.nouveau_pin) return {"message": "PIN sysadmin mis à jour"} @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.post("/groupes/{groupe_id}/tenant-schema/refresh") async def refresh_tenant_schema_groupe_admin( groupe_id: str, _sysadmin=Depends(require_sysadmin), db: AsyncSession = Depends(get_db), ): try: return await refresh_group_tenant_schema(db, groupe_id) except ValueError as exc: raise HTTPException(status_code=400, detail=str(exc)) @router.post("/tenant-schemas/refresh") async def refresh_tenant_schemas_admin( _sysadmin=Depends(require_sysadmin), db: AsyncSession = Depends(get_db), ): return await refresh_all_tenant_schemas(db) @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, "tenant_schema": groupe.tenant_schema, "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))