"""Mandates router: CRUD for mandates and their steps.""" from __future__ import annotations import uuid from fastapi import APIRouter, Depends, HTTPException, Query, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.database import get_db from app.models.mandate import Mandate, MandateStep from app.models.user import DuniterIdentity from app.schemas.mandate import ( MandateCreate, MandateOut, MandateStepCreate, MandateStepOut, ) from app.services.auth_service import get_current_identity router = APIRouter() # ── Helpers ───────────────────────────────────────────────────────────────── async def _get_mandate(db: AsyncSession, mandate_id: uuid.UUID) -> Mandate: """Fetch a mandate by ID with its steps eagerly loaded, or raise 404.""" result = await db.execute( select(Mandate) .options(selectinload(Mandate.steps)) .where(Mandate.id == mandate_id) ) mandate = result.scalar_one_or_none() if mandate is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Mandat introuvable") return mandate # ── Mandate routes ────────────────────────────────────────────────────────── @router.get("/", response_model=list[MandateOut]) async def list_mandates( db: AsyncSession = Depends(get_db), mandate_type: str | None = Query(default=None, description="Filtrer par type de mandat"), status_filter: str | None = Query(default=None, alias="status", description="Filtrer par statut"), skip: int = Query(default=0, ge=0), limit: int = Query(default=50, ge=1, le=200), ) -> list[MandateOut]: """List all mandates with optional filters.""" stmt = select(Mandate).options(selectinload(Mandate.steps)) if mandate_type is not None: stmt = stmt.where(Mandate.mandate_type == mandate_type) if status_filter is not None: stmt = stmt.where(Mandate.status == status_filter) stmt = stmt.order_by(Mandate.created_at.desc()).offset(skip).limit(limit) result = await db.execute(stmt) mandates = result.scalars().unique().all() return [MandateOut.model_validate(m) for m in mandates] @router.post("/", response_model=MandateOut, status_code=status.HTTP_201_CREATED) async def create_mandate( payload: MandateCreate, db: AsyncSession = Depends(get_db), identity: DuniterIdentity = Depends(get_current_identity), ) -> MandateOut: """Create a new mandate.""" mandate = Mandate(**payload.model_dump()) db.add(mandate) await db.commit() await db.refresh(mandate) # Reload with steps (empty at creation) mandate = await _get_mandate(db, mandate.id) return MandateOut.model_validate(mandate) @router.get("/{id}", response_model=MandateOut) async def get_mandate( id: uuid.UUID, db: AsyncSession = Depends(get_db), ) -> MandateOut: """Get a single mandate with all its steps.""" mandate = await _get_mandate(db, id) return MandateOut.model_validate(mandate) @router.put("/{id}", response_model=MandateOut) async def update_mandate( id: uuid.UUID, payload: MandateCreate, db: AsyncSession = Depends(get_db), identity: DuniterIdentity = Depends(get_current_identity), ) -> MandateOut: """Update a mandate's metadata.""" mandate = await _get_mandate(db, id) update_data = payload.model_dump(exclude_unset=True) for field, value in update_data.items(): setattr(mandate, field, value) await db.commit() await db.refresh(mandate) # Reload with steps mandate = await _get_mandate(db, mandate.id) return MandateOut.model_validate(mandate) @router.delete("/{id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_mandate( id: uuid.UUID, db: AsyncSession = Depends(get_db), identity: DuniterIdentity = Depends(get_current_identity), ) -> None: """Delete a mandate (only if in draft status).""" mandate = await _get_mandate(db, id) if mandate.status != "draft": raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Seuls les mandats en brouillon peuvent etre supprimes", ) await db.delete(mandate) await db.commit() # ── Mandate Step routes ───────────────────────────────────────────────────── @router.post("/{id}/steps", response_model=MandateStepOut, status_code=status.HTTP_201_CREATED) async def add_step( id: uuid.UUID, payload: MandateStepCreate, db: AsyncSession = Depends(get_db), identity: DuniterIdentity = Depends(get_current_identity), ) -> MandateStepOut: """Add a step to a mandate process.""" mandate = await _get_mandate(db, id) step = MandateStep( mandate_id=mandate.id, **payload.model_dump(), ) db.add(step) await db.commit() await db.refresh(step) return MandateStepOut.model_validate(step) @router.get("/{id}/steps", response_model=list[MandateStepOut]) async def list_steps( id: uuid.UUID, db: AsyncSession = Depends(get_db), ) -> list[MandateStepOut]: """List all steps for a mandate, ordered by step_order.""" mandate = await _get_mandate(db, id) return [MandateStepOut.model_validate(s) for s in mandate.steps]