- Added HistoriquePage component to display user's voting history with detailed statistics and vote cards. - Created UpcomingVotesPage component to show upcoming elections with a similar layout. - Developed CSS styles for both pages to enhance visual appeal and responsiveness. - Integrated API calls to fetch user's votes and upcoming elections. - Added a rebuild script for Docker environment setup and data restoration. - Created a Python script to populate the database with sample data for testing.
239 lines
6.9 KiB
Python
239 lines
6.9 KiB
Python
"""
|
|
Routes pour le vote et les bulletins.
|
|
"""
|
|
|
|
from fastapi import APIRouter, HTTPException, status, Depends, Request
|
|
from sqlalchemy.orm import Session
|
|
import base64
|
|
from .. import schemas, services
|
|
from ..dependencies import get_db, get_current_voter
|
|
from ..models import Voter
|
|
from ..crypto.hashing import SecureHash
|
|
|
|
router = APIRouter(prefix="/api/votes", tags=["votes"])
|
|
|
|
|
|
@router.post("")
|
|
async def submit_simple_vote(
|
|
vote_data: dict,
|
|
current_voter: Voter = Depends(get_current_voter),
|
|
db: Session = Depends(get_db),
|
|
request: Request = None
|
|
):
|
|
"""
|
|
Soumettre un vote simple avec just élection_id et nom du candidat.
|
|
Interface simplifiée pour l'application web.
|
|
"""
|
|
from .. import models
|
|
|
|
election_id = vote_data.get('election_id')
|
|
candidate_name = vote_data.get('choix')
|
|
|
|
if not election_id or not candidate_name:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="election_id and choix are required"
|
|
)
|
|
|
|
# Vérifier que l'électeur n'a pas déjà voté
|
|
if services.VoteService.has_voter_voted(db, current_voter.id, election_id):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Voter has already voted in this election"
|
|
)
|
|
|
|
# Vérifier que l'élection existe
|
|
election = services.ElectionService.get_election(db, election_id)
|
|
if not election:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Election not found"
|
|
)
|
|
|
|
# Trouver le candidat par nom
|
|
candidate = db.query(models.Candidate).filter(
|
|
models.Candidate.name == candidate_name,
|
|
models.Candidate.election_id == election_id
|
|
).first()
|
|
|
|
if not candidate:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Candidate not found"
|
|
)
|
|
|
|
# Enregistrer le vote (sans chiffrement pour l'MVP)
|
|
import time
|
|
from ..crypto.hashing import SecureHash
|
|
|
|
ballot_hash = SecureHash.hash_bulletin(
|
|
vote_id=current_voter.id,
|
|
candidate_id=candidate.id,
|
|
timestamp=int(time.time())
|
|
)
|
|
|
|
vote = services.VoteService.record_vote(
|
|
db=db,
|
|
voter_id=current_voter.id,
|
|
election_id=election_id,
|
|
candidate_id=candidate.id,
|
|
encrypted_vote=b"", # Empty for MVP
|
|
ballot_hash=ballot_hash,
|
|
ip_address=request.client.host if request else None
|
|
)
|
|
|
|
return {
|
|
"message": "Vote recorded successfully",
|
|
"id": vote.id,
|
|
"ballot_hash": ballot_hash,
|
|
"timestamp": vote.timestamp
|
|
}
|
|
|
|
|
|
|
|
async def submit_vote(
|
|
vote_bulletin: schemas.VoteBulletin,
|
|
current_voter: Voter = Depends(get_current_voter),
|
|
db: Session = Depends(get_db),
|
|
request: Request = None
|
|
):
|
|
"""
|
|
Soumettre un vote chiffré.
|
|
|
|
Le vote doit être:
|
|
- Chiffré avec ElGamal
|
|
- Accompagné d'une preuve ZK de validité
|
|
"""
|
|
|
|
# Vérifier que l'électeur n'a pas déjà voté
|
|
if services.VoteService.has_voter_voted(
|
|
db,
|
|
current_voter.id,
|
|
vote_bulletin.election_id
|
|
):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Voter has already voted in this election"
|
|
)
|
|
|
|
# Vérifier que l'élection existe
|
|
election = services.ElectionService.get_election(
|
|
db,
|
|
vote_bulletin.election_id
|
|
)
|
|
if not election:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Election not found"
|
|
)
|
|
|
|
# Vérifier que le candidat existe
|
|
from ..models import Candidate
|
|
candidate = db.query(Candidate).filter(
|
|
Candidate.id == vote_bulletin.candidate_id,
|
|
Candidate.election_id == vote_bulletin.election_id
|
|
).first()
|
|
if not candidate:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Candidate not found"
|
|
)
|
|
|
|
# Décoder le vote chiffré
|
|
try:
|
|
encrypted_vote_bytes = base64.b64decode(vote_bulletin.encrypted_vote)
|
|
except Exception:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_400_BAD_REQUEST,
|
|
detail="Invalid encrypted vote format"
|
|
)
|
|
|
|
# Générer le hash du bulletin
|
|
import time
|
|
ballot_hash = SecureHash.hash_bulletin(
|
|
vote_id=current_voter.id,
|
|
candidate_id=vote_bulletin.candidate_id,
|
|
timestamp=int(time.time())
|
|
)
|
|
|
|
# Enregistrer le vote
|
|
vote = services.VoteService.record_vote(
|
|
db=db,
|
|
voter_id=current_voter.id,
|
|
election_id=vote_bulletin.election_id,
|
|
candidate_id=vote_bulletin.candidate_id,
|
|
encrypted_vote=encrypted_vote_bytes,
|
|
ballot_hash=ballot_hash,
|
|
ip_address=request.client.host if request else None
|
|
)
|
|
|
|
# Marquer l'électeur comme ayant voté
|
|
services.VoterService.mark_as_voted(db, current_voter.id)
|
|
|
|
return schemas.VoteResponse(
|
|
id=vote.id,
|
|
ballot_hash=ballot_hash,
|
|
timestamp=vote.timestamp
|
|
)
|
|
|
|
|
|
@router.get("/status")
|
|
def get_vote_status(
|
|
election_id: int,
|
|
current_voter: Voter = Depends(get_current_voter),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Vérifier si l'électeur a déjà voté pour une élection"""
|
|
|
|
has_voted = services.VoteService.has_voter_voted(
|
|
db,
|
|
current_voter.id,
|
|
election_id
|
|
)
|
|
|
|
return {"has_voted": has_voted}
|
|
|
|
|
|
@router.get("/history", response_model=list)
|
|
def get_voter_history(
|
|
current_voter: Voter = Depends(get_current_voter),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Récupérer l'historique des votes de l'électeur actuel"""
|
|
from .. import models
|
|
from datetime import datetime
|
|
|
|
votes = db.query(models.Vote).filter(
|
|
models.Vote.voter_id == current_voter.id
|
|
).all()
|
|
|
|
# Retourner la structure avec infos des élections
|
|
history = []
|
|
for vote in votes:
|
|
election = db.query(models.Election).filter(
|
|
models.Election.id == vote.election_id
|
|
).first()
|
|
candidate = db.query(models.Candidate).filter(
|
|
models.Candidate.id == vote.candidate_id
|
|
).first()
|
|
|
|
if election:
|
|
# Déterminer le statut de l'élection
|
|
if election.start_date > datetime.utcnow():
|
|
status = "upcoming"
|
|
elif election.end_date < datetime.utcnow():
|
|
status = "closed"
|
|
else:
|
|
status = "active"
|
|
|
|
history.append({
|
|
"vote_id": vote.id,
|
|
"election_id": election.id,
|
|
"election_name": election.name,
|
|
"candidate_name": candidate.name if candidate else "Unknown",
|
|
"vote_date": vote.timestamp,
|
|
"election_status": status
|
|
})
|
|
|
|
return history
|