groupe-meditation/backend/app/routers/journal.py
Daniel Allaire 6677b0f57b Ajouter historique métier et protection concurrente
Ajoute modifie_le aux objets sensibles et fait transmettre la version affichée par le frontend pour refuser les écritures périmées avec un message HTTP 409.

Ajoute une table historique_modifications et un service d'historique métier afin de journaliser automatiquement les raisons des changements importants sans intervention de l'utilisateur.

Expose l'historique métier dans le module Journal, distinct du journal technique, et documente les processus utilisateurs ainsi qu'une présentation sobre aux membres.
2026-05-31 10:59:41 -04:00

77 lines
2.6 KiB
Python

"""Consultation du journal d'audit."""
from fastapi import APIRouter, Depends
from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.database import get_db
from app.core.security import require_executif
from app.models.journal_action import JournalAction
from app.models.historique_modification import HistoriqueModification
from app.models.membre import Membre
router = APIRouter(prefix="/journal", tags=["Journal"])
@router.get("/actions")
async def journal_actions(
limite: int = 100,
executif=Depends(require_executif),
db: AsyncSession = Depends(get_db),
):
limite = max(1, min(limite, 500))
result = await db.execute(
select(JournalAction)
.where(JournalAction.groupe_id == executif.groupe_id)
.order_by(JournalAction.cree_le.desc())
.limit(limite)
)
return [
{
"id": str(item.id),
"groupe_id": str(item.groupe_id) if item.groupe_id else None,
"membre_id": str(item.membre_id) if item.membre_id else None,
"admin_id": str(item.admin_id) if item.admin_id else None,
"methode": item.methode,
"chemin": item.chemin,
"statut_http": item.statut_http,
"adresse_ip": item.adresse_ip,
"user_agent": item.user_agent,
"details": item.details,
"cree_le": item.cree_le.isoformat(),
}
for item in result.scalars().all()
]
@router.get("/historique")
async def historique_modifications(
limite: int = 100,
executif=Depends(require_executif),
db: AsyncSession = Depends(get_db),
):
limite = max(1, min(limite, 500))
result = await db.execute(
select(HistoriqueModification)
.where(HistoriqueModification.groupe_id == executif.groupe_id)
.order_by(HistoriqueModification.cree_le.desc())
.limit(limite)
)
items = result.scalars().all()
lignes = []
for item in items:
membre = await db.get(Membre, item.membre_id) if item.membre_id else None
lignes.append({
"id": str(item.id),
"table_cible": item.table_cible,
"objet_id": str(item.objet_id),
"action": item.action,
"raison": item.raison,
"membre_id": str(item.membre_id) if item.membre_id else None,
"membre_prenom": membre.prenom if membre else None,
"admin_id": str(item.admin_id) if item.admin_id else None,
"chemin_api": item.chemin_api,
"avant": item.avant,
"apres": item.apres,
"cree_le": item.cree_le.isoformat(),
})
return lignes