"""Sanctuary router: IPFS + on-chain anchoring entries.""" 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 app.database import get_db from app.models.sanctuary import SanctuaryEntry from app.models.user import DuniterIdentity from app.schemas.sanctuary import SanctuaryEntryCreate, SanctuaryEntryOut from app.services import sanctuary_service from app.services.auth_service import get_current_identity router = APIRouter() @router.get("/", response_model=list[SanctuaryEntryOut]) async def list_entries( db: AsyncSession = Depends(get_db), entry_type: str | None = Query(default=None, description="Filtrer par type (document, decision, vote_result)"), skip: int = Query(default=0, ge=0), limit: int = Query(default=50, ge=1, le=200), ) -> list[SanctuaryEntryOut]: """List all sanctuary entries with optional type filter.""" stmt = select(SanctuaryEntry) if entry_type is not None: stmt = stmt.where(SanctuaryEntry.entry_type == entry_type) stmt = stmt.order_by(SanctuaryEntry.created_at.desc()).offset(skip).limit(limit) result = await db.execute(stmt) entries = result.scalars().all() return [SanctuaryEntryOut.model_validate(e) for e in entries] @router.post("/", response_model=SanctuaryEntryOut, status_code=status.HTTP_201_CREATED) async def create_entry( payload: SanctuaryEntryCreate, db: AsyncSession = Depends(get_db), identity: DuniterIdentity = Depends(get_current_identity), ) -> SanctuaryEntryOut: """Create a new sanctuary entry. This endpoint is typically called by internal services after: 1. Content is hashed (SHA-256) 2. Content is pinned to IPFS 3. Hash is anchored on-chain via system.remark The IPFS CID and chain TX hash can be added later via updates. """ entry = SanctuaryEntry(**payload.model_dump()) db.add(entry) await db.commit() await db.refresh(entry) return SanctuaryEntryOut.model_validate(entry) @router.get("/by-reference/{reference_id}", response_model=list[SanctuaryEntryOut]) async def get_entries_by_reference( reference_id: uuid.UUID, db: AsyncSession = Depends(get_db), ) -> list[SanctuaryEntryOut]: """Get all sanctuary entries for a given reference ID. Useful for finding all sanctuary entries associated with a document, decision, or vote result. """ entries = await sanctuary_service.get_entries_by_reference(reference_id, db) return [SanctuaryEntryOut.model_validate(e) for e in entries] @router.get("/{id}/verify") async def verify_entry( id: uuid.UUID, db: AsyncSession = Depends(get_db), ) -> dict: """Verify integrity of a sanctuary entry. Re-fetches the content (from IPFS if available), re-hashes it, and compares with the stored content_hash. """ try: result = await sanctuary_service.verify_entry(id, db) except ValueError as exc: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)) return result @router.get("/{id}", response_model=SanctuaryEntryOut) async def get_entry( id: uuid.UUID, db: AsyncSession = Depends(get_db), ) -> SanctuaryEntryOut: """Get a single sanctuary entry by ID.""" result = await db.execute(select(SanctuaryEntry).where(SanctuaryEntry.id == id)) entry = result.scalar_one_or_none() if entry is None: raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Entree sanctuaire introuvable") return SanctuaryEntryOut.model_validate(entry)