Changes:
- Add next-themes dependency for theme management
- Create ThemeProvider wrapper for app root layout
- Set dark mode as default theme
- Create ThemeToggle component with Sun/Moon icons
- Add theme toggle to home page navigation
- Add theme toggle to dashboard header
- App now starts in dark mode with ability to switch to light mode
Styling uses existing Tailwind dark mode variables configured in
tailwind.config.ts and globals.css. All existing components automatically
support dark theme.
🤖 Generated with Claude Code
Co-Authored-By: Claude <noreply@anthropic.com>
129 lines
4.2 KiB
Python
129 lines
4.2 KiB
Python
"""
|
|
Modèles de données SQLAlchemy pour la persistance.
|
|
"""
|
|
|
|
from sqlalchemy import Column, Integer, String, DateTime, Boolean, ForeignKey, Text, LargeBinary
|
|
from sqlalchemy.ext.declarative import declarative_base
|
|
from sqlalchemy.orm import relationship
|
|
from datetime import datetime, timezone
|
|
|
|
def get_utc_now():
|
|
"""Get current UTC time (timezone-aware)"""
|
|
return datetime.now(timezone.utc)
|
|
|
|
Base = declarative_base()
|
|
|
|
|
|
class Voter(Base):
|
|
"""Électeur - Enregistrement et authentification"""
|
|
__tablename__ = "voters"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
email = Column(String(255), unique=True, index=True, nullable=False)
|
|
password_hash = Column(String(255), nullable=False)
|
|
first_name = Column(String(100))
|
|
last_name = Column(String(100))
|
|
citizen_id = Column(String(50), unique=True) # Identifiant unique (CNI)
|
|
|
|
# Sécurité
|
|
public_key = Column(LargeBinary) # Clé publique ElGamal
|
|
has_voted = Column(Boolean, default=False)
|
|
|
|
created_at = Column(DateTime, default=get_utc_now)
|
|
updated_at = Column(DateTime, default=get_utc_now, onupdate=get_utc_now)
|
|
|
|
# Relations
|
|
votes = relationship("Vote", back_populates="voter")
|
|
|
|
|
|
class Election(Base):
|
|
"""Élection - Configuration et paramètres"""
|
|
__tablename__ = "elections"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
name = Column(String(255), nullable=False)
|
|
description = Column(Text)
|
|
|
|
# Dates
|
|
start_date = Column(DateTime, nullable=False)
|
|
end_date = Column(DateTime, nullable=False)
|
|
|
|
# Paramètres cryptographiques
|
|
elgamal_p = Column(Integer) # Nombre premier
|
|
elgamal_g = Column(Integer) # Générateur
|
|
public_key = Column(LargeBinary) # Clé publique pour chiffrement
|
|
|
|
# État
|
|
is_active = Column(Boolean, default=True)
|
|
results_published = Column(Boolean, default=False)
|
|
|
|
created_at = Column(DateTime, default=get_utc_now)
|
|
updated_at = Column(DateTime, default=get_utc_now, onupdate=get_utc_now)
|
|
|
|
# Relations
|
|
candidates = relationship("Candidate", back_populates="election")
|
|
votes = relationship("Vote", back_populates="election")
|
|
|
|
|
|
class Candidate(Base):
|
|
"""Candidat - Options de vote"""
|
|
__tablename__ = "candidates"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
election_id = Column(Integer, ForeignKey("elections.id"), nullable=False)
|
|
|
|
name = Column(String(255), nullable=False)
|
|
description = Column(Text)
|
|
order = Column(Integer) # Ordre d'affichage
|
|
|
|
created_at = Column(DateTime, default=get_utc_now)
|
|
|
|
# Relations
|
|
election = relationship("Election", back_populates="candidates")
|
|
votes = relationship("Vote", back_populates="candidate")
|
|
|
|
|
|
class Vote(Base):
|
|
"""Vote - Bulletin chiffré"""
|
|
__tablename__ = "votes"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
voter_id = Column(Integer, ForeignKey("voters.id"), nullable=False)
|
|
election_id = Column(Integer, ForeignKey("elections.id"), nullable=False)
|
|
candidate_id = Column(Integer, ForeignKey("candidates.id"), nullable=False)
|
|
|
|
# Vote chiffré avec ElGamal
|
|
encrypted_vote = Column(LargeBinary, nullable=False) # Ciphertext ElGamal
|
|
|
|
# Preuves
|
|
zero_knowledge_proof = Column(LargeBinary) # ZK proof que le vote est valide
|
|
ballot_hash = Column(String(64)) # Hash du bulletin pour traçabilité
|
|
|
|
# Métadonnées
|
|
timestamp = Column(DateTime, default=get_utc_now)
|
|
ip_address = Column(String(45)) # IPv4 ou IPv6
|
|
|
|
# Relations
|
|
voter = relationship("Voter", back_populates="votes")
|
|
election = relationship("Election", back_populates="votes")
|
|
candidate = relationship("Candidate", back_populates="votes")
|
|
|
|
|
|
class AuditLog(Base):
|
|
"""Journal d'audit pour la sécurité"""
|
|
__tablename__ = "audit_logs"
|
|
|
|
id = Column(Integer, primary_key=True, index=True)
|
|
action = Column(String(100), nullable=False)
|
|
description = Column(Text)
|
|
|
|
# Qui
|
|
user_id = Column(Integer, ForeignKey("voters.id"))
|
|
|
|
# Quand
|
|
timestamp = Column(DateTime, default=get_utc_now)
|
|
|
|
# Métadonnées
|
|
ip_address = Column(String(45))
|
|
user_agent = Column(String(255))
|