"""Protocols router: voting protocols and formula configurations.""" 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.protocol import FormulaConfig, VotingProtocol from app.models.user import DuniterIdentity from app.schemas.protocol import ( FormulaConfigCreate, FormulaConfigOut, VotingProtocolCreate, VotingProtocolOut, ) from app.services.auth_service import get_current_identity router = APIRouter() # ── Helpers ───────────────────────────────────────────────────────────────── async def _get_protocol(db: AsyncSession, protocol_id: uuid.UUID) -> VotingProtocol: """Fetch a voting protocol by ID with its formula config, or raise 404.""" result = await db.execute( select(VotingProtocol) .options(selectinload(VotingProtocol.formula_config)) .where(VotingProtocol.id == protocol_id) ) protocol = result.scalar_one_or_none() if protocol is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Protocole introuvable") return protocol # ── Voting Protocol routes ────────────────────────────────────────────────── @router.get("/", response_model=list[VotingProtocolOut]) async def list_protocols( db: AsyncSession = Depends(get_db), vote_type: str | None = Query(default=None, description="Filtrer par type de vote (binary, nuanced)"), skip: int = Query(default=0, ge=0), limit: int = Query(default=50, ge=1, le=200), ) -> list[VotingProtocolOut]: """List all voting protocols with their formula configurations.""" stmt = select(VotingProtocol).options(selectinload(VotingProtocol.formula_config)) if vote_type is not None: stmt = stmt.where(VotingProtocol.vote_type == vote_type) stmt = stmt.order_by(VotingProtocol.created_at.desc()).offset(skip).limit(limit) result = await db.execute(stmt) protocols = result.scalars().unique().all() return [VotingProtocolOut.model_validate(p) for p in protocols] @router.post("/", response_model=VotingProtocolOut, status_code=status.HTTP_201_CREATED) async def create_protocol( payload: VotingProtocolCreate, db: AsyncSession = Depends(get_db), identity: DuniterIdentity = Depends(get_current_identity), ) -> VotingProtocolOut: """Create a new voting protocol. The formula_config_id must reference an existing FormulaConfig. """ # Verify formula config exists fc_result = await db.execute( select(FormulaConfig).where(FormulaConfig.id == payload.formula_config_id) ) if fc_result.scalar_one_or_none() is None: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Configuration de formule introuvable", ) protocol = VotingProtocol(**payload.model_dump()) db.add(protocol) await db.commit() await db.refresh(protocol) # Reload with formula config protocol = await _get_protocol(db, protocol.id) return VotingProtocolOut.model_validate(protocol) @router.get("/{id}", response_model=VotingProtocolOut) async def get_protocol( id: uuid.UUID, db: AsyncSession = Depends(get_db), ) -> VotingProtocolOut: """Get a single voting protocol with its formula configuration.""" protocol = await _get_protocol(db, id) return VotingProtocolOut.model_validate(protocol) # ── Formula Config routes ─────────────────────────────────────────────────── @router.get("/formulas", response_model=list[FormulaConfigOut]) async def list_formulas( db: AsyncSession = Depends(get_db), skip: int = Query(default=0, ge=0), limit: int = Query(default=50, ge=1, le=200), ) -> list[FormulaConfigOut]: """List all formula configurations.""" stmt = ( select(FormulaConfig) .order_by(FormulaConfig.created_at.desc()) .offset(skip) .limit(limit) ) result = await db.execute(stmt) formulas = result.scalars().all() return [FormulaConfigOut.model_validate(f) for f in formulas] @router.post("/formulas", response_model=FormulaConfigOut, status_code=status.HTTP_201_CREATED) async def create_formula( payload: FormulaConfigCreate, db: AsyncSession = Depends(get_db), identity: DuniterIdentity = Depends(get_current_identity), ) -> FormulaConfigOut: """Create a new formula configuration for WoT threshold computation.""" formula = FormulaConfig(**payload.model_dump()) db.add(formula) await db.commit() await db.refresh(formula) return FormulaConfigOut.model_validate(formula)