""" User consent tracking models for GDPR compliance """ from sqlalchemy import Column, String, Boolean, DateTime, Text, ForeignKey, Index from sqlalchemy.dialects.postgresql import UUID, JSON from datetime import datetime, timezone import uuid from shared.database.base import Base class UserConsent(Base): """ Tracks user consent for various data processing activities GDPR Article 7 - Conditions for consent """ __tablename__ = "user_consents" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) # Consent types terms_accepted = Column(Boolean, nullable=False, default=False) privacy_accepted = Column(Boolean, nullable=False, default=False) marketing_consent = Column(Boolean, nullable=False, default=False) analytics_consent = Column(Boolean, nullable=False, default=False) # Consent metadata consent_version = Column(String(20), nullable=False, default="1.0") consent_method = Column(String(50), nullable=False) # registration, settings_update, cookie_banner ip_address = Column(String(45), nullable=True) user_agent = Column(Text, nullable=True) # Consent text at time of acceptance terms_text_hash = Column(String(64), nullable=True) privacy_text_hash = Column(String(64), nullable=True) # Timestamps consented_at = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc)) withdrawn_at = Column(DateTime(timezone=True), nullable=True) # Additional metadata (renamed from 'metadata' to avoid SQLAlchemy reserved word) extra_data = Column(JSON, nullable=True) __table_args__ = ( Index('idx_user_consent_user_id', 'user_id'), Index('idx_user_consent_consented_at', 'consented_at'), ) def __repr__(self): return f"" def to_dict(self): return { "id": str(self.id), "user_id": str(self.user_id), "terms_accepted": self.terms_accepted, "privacy_accepted": self.privacy_accepted, "marketing_consent": self.marketing_consent, "analytics_consent": self.analytics_consent, "consent_version": self.consent_version, "consent_method": self.consent_method, "consented_at": self.consented_at.isoformat() if self.consented_at else None, "withdrawn_at": self.withdrawn_at.isoformat() if self.withdrawn_at else None, } class ConsentHistory(Base): """ Historical record of all consent changes Provides audit trail for GDPR compliance """ __tablename__ = "consent_history" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) user_id = Column(UUID(as_uuid=True), nullable=False, index=True) consent_id = Column(UUID(as_uuid=True), ForeignKey("user_consents.id", ondelete="SET NULL"), nullable=True) # Action type action = Column(String(50), nullable=False) # granted, updated, withdrawn, revoked # Consent state at time of action consent_snapshot = Column(JSON, nullable=False) # Context ip_address = Column(String(45), nullable=True) user_agent = Column(Text, nullable=True) consent_method = Column(String(50), nullable=True) # Timestamp created_at = Column(DateTime(timezone=True), nullable=False, default=lambda: datetime.now(timezone.utc), index=True) __table_args__ = ( Index('idx_consent_history_user_id', 'user_id'), Index('idx_consent_history_created_at', 'created_at'), Index('idx_consent_history_action', 'action'), ) def __repr__(self): return f"" def to_dict(self): return { "id": str(self.id), "user_id": str(self.user_id), "action": self.action, "consent_snapshot": self.consent_snapshot, "created_at": self.created_at.isoformat() if self.created_at else None, }