from datetime import datetime from sqlalchemy import create_engine, Column, String, Integer, Boolean, DateTime, Text from sqlalchemy.orm import DeclarativeBase, sessionmaker from config import settings engine = create_engine( settings.database_url, connect_args={"check_same_thread": False}, ) SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine) class Base(DeclarativeBase): pass def get_db(): db = SessionLocal() try: yield db finally: db.close() class Share(Base): __tablename__ = "shares" id = Column(String(36), primary_key=True) # UUID4 share_type = Column(String(10), nullable=False) # 'file' | 'text' # Metadata provided by client before encryption original_filename = Column(String(512), nullable=True) mimetype = Column(String(128), nullable=True) filesize = Column(Integer, nullable=True) # bytes of the encrypted blob # Storage — blob stored under a random name, never the original storage_path = Column(String(512), nullable=False) # AES-256-GCM key (hex-encoded, 64 chars). Server generated, stored at rest. encryption_key = Column(String(64), nullable=False) # Access control password_hash = Column(String(512), nullable=True) # Argon2id; NULL = no password # Lifecycle — all datetimes are naive UTC created_at = Column(DateTime, default=datetime.utcnow) expires_at = Column(DateTime, nullable=False) burn_after_read = Column(Boolean, default=False) downloaded = Column(Boolean, default=False) # Brute-force protection failed_attempts = Column(Integer, default=0) locked_until = Column(DateTime, nullable=True) class SecurityEvent(Base): __tablename__ = "security_events" id = Column(Integer, primary_key=True, autoincrement=True) timestamp = Column(DateTime, default=datetime.utcnow) event_type = Column(String(50), nullable=False) share_id = Column(String(36), nullable=True) ip_hash = Column(String(64), nullable=True) # SHA-256 of IP — no raw IPs details = Column(Text, nullable=True)