""" Routes pour les élections et les candidats. Elections are stored immutably in blockchain with cryptographic security: - SHA-256 hash chain prevents tampering - RSA-PSS signatures authenticate election data - Merkle tree verification for candidates - Complete audit trail on blockchain """ from fastapi import APIRouter, HTTPException, status, Depends from sqlalchemy.orm import Session from .. import schemas, services from ..dependencies import get_db, get_current_voter from ..models import Voter from ..blockchain_elections import ( record_election_to_blockchain, verify_election_in_blockchain, get_elections_blockchain_data, ) router = APIRouter(prefix="/api/elections", tags=["elections"]) @router.get("/debug/all") def debug_all_elections(db: Session = Depends(get_db)): """DEBUG: Return all elections with dates for troubleshooting""" from datetime import datetime from .. import models now = datetime.utcnow() all_elections = db.query(models.Election).all() return { "current_time": now.isoformat(), "elections": [ { "id": e.id, "name": e.name, "is_active": e.is_active, "start_date": e.start_date.isoformat() if e.start_date else None, "end_date": e.end_date.isoformat() if e.end_date else None, "should_be_active": ( e.start_date <= now <= e.end_date and e.is_active if e.start_date and e.end_date else False ), } for e in all_elections ], } @router.get("/active", response_model=list[schemas.ElectionResponse]) def get_active_elections(db: Session = Depends(get_db)): """Récupérer toutes les élections actives en cours""" from datetime import datetime, timedelta from .. import models now = datetime.utcnow() # Allow 1 hour buffer for timezone issues start_buffer = now - timedelta(hours=1) end_buffer = now + timedelta(hours=1) active = db.query(models.Election).filter( (models.Election.start_date <= end_buffer) & (models.Election.end_date >= start_buffer) & (models.Election.is_active == True) ).order_by(models.Election.id.asc()).all() return active @router.get("/blockchain") def get_elections_blockchain(): """ Retrieve the complete elections blockchain. Returns all election records stored immutably with cryptographic verification. Useful for auditing election creation and verifying no tampering occurred. """ return get_elections_blockchain_data() @router.get("/{election_id}/blockchain-verify") def verify_election_blockchain(election_id: int, db: Session = Depends(get_db)): """ Verify an election's blockchain integrity. Returns verification report: - hash_valid: Block hash matches computed hash - chain_valid: Entire chain integrity is valid - signature_valid: Block is properly signed - verified: All checks passed """ # First verify it exists in database election = services.ElectionService.get_election(db, election_id) if not election: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Election not found in database" ) # Then verify it's in blockchain verification = verify_election_in_blockchain(election_id) if not verification.get("verified"): # Still return data but mark as unverified return { **verification, "warning": "Election blockchain verification failed - possible tampering" } return verification # Routes with path parameters must come AFTER specific routes @router.get("/active/results") def get_active_election_results(db: Session = Depends(get_db)): """Récupérer les résultats de l'élection active""" election = services.ElectionService.get_active_election(db) if not election: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="No active election" ) results = services.VoteService.get_election_results(db, election.id) return results @router.get("/{election_id}/candidates") def get_election_candidates(election_id: int, db: Session = Depends(get_db)): """Récupérer les candidats d'une élection""" election = services.ElectionService.get_election(db, election_id) if not election: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Election not found" ) return election.candidates @router.get("/{election_id}/results", response_model=schemas.ElectionResultResponse) def get_election_results( election_id: int, db: Session = Depends(get_db) ): """ Récupérer les résultats d'une élection. Disponible après la fermeture du scrutin. """ election = services.ElectionService.get_election(db, election_id) if not election: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Election not found" ) if not election.results_published: raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Results not yet published" ) results = services.VoteService.get_election_results(db, election_id) return schemas.ElectionResultResponse( election_id=election.id, election_name=election.name, total_votes=sum(r.vote_count for r in results), results=results ) @router.post("/{election_id}/publish-results") def publish_results( election_id: int, db: Session = Depends(get_db) ): """ Publier les résultats d'une élection (admin only). À utiliser après la fermeture du scrutin. """ election = services.ElectionService.get_election(db, election_id) if not election: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Election not found" ) # Marquer les résultats comme publiés election.results_published = True db.commit() return { "message": "Results published successfully", "election_id": election.id, "election_name": election.name } @router.get("/{election_id}", response_model=schemas.ElectionResponse) def get_election(election_id: int, db: Session = Depends(get_db)): """Récupérer une élection par son ID""" election = services.ElectionService.get_election(db, election_id) if not election: raise HTTPException( status_code=status.HTTP_404_NOT_FOUND, detail="Election not found" ) return election