v3 : socle Rust (moteur pur, accès trustinWallet, serveur) ; maquette Nuxt rangée dans maquette/

- crates/ld-engine : formule inertielle (parité avec la maquette, garde C),
  coquille / changement de fond, assistant d'édition à règles, composition
  d'un document sous vote permanent (adoptions, aperçus, rivales).
- crates/trustin-access : qui voit, qui ouvre, qui gère les accès.
- crates/ld-server : API axum + SQLite, connexion Ğ1 (client OAuth2 de
  connect.monnaie-libre.fr), invitations, entrée en vigueur automatique,
  signalements de contradiction, traductions éditoriales, publication git,
  taille de la toile lue sur la chaîne.
- seeds/acte-engagement-certification.json : 33 articles, fr + en + es ;
  en vigueur : préambule, E1, E2 (seed a minima).
- maquette/ : frontend, backend et docker v2 déplacés tels quels.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Yvv
2026-10-21 02:04:42 +02:00
co-authored by Claude Opus 5.5
parent 4b1732916d
commit 67ddee28b2
275 changed files with 8979 additions and 0 deletions
+105
View File
@@ -0,0 +1,105 @@
"""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)