111 lines
4.1 KiB
Python
111 lines
4.1 KiB
Python
|
|
"""
|
||
|
|
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"<UserConsent(user_id={self.user_id}, version={self.consent_version})>"
|
||
|
|
|
||
|
|
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"<ConsentHistory(user_id={self.user_id}, action={self.action})>"
|
||
|
|
|
||
|
|
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,
|
||
|
|
}
|