from datetime import datetime, timezone from sqlalchemy import ( Column, Integer, String, Text, ForeignKey, DateTime, UniqueConstraint, ) from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.orm import relationship from .database import Base class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String(64), unique=True, nullable=False, index=True) email = Column(String(255), unique=True, nullable=False, index=True) password_hash = Column(String(255), nullable=False) created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False) # Relationships pdfs = relationship("PDF", back_populates="owner", cascade="all, delete-orphan") annotations = relationship("Annotation", back_populates="owner", cascade="all, delete-orphan") class PDF(Base): __tablename__ = "pdfs" id = Column(Integer, primary_key=True, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) title = Column(String(255), nullable=False) file_path = Column(Text, nullable=False) # path inside Docker volume total_pages = Column(Integer, nullable=True) # populated after upload processing created_at = Column(DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), nullable=False) # Relationships owner = relationship("User", back_populates="pdfs") annotations = relationship("Annotation", back_populates="pdf", cascade="all, delete-orphan") class Annotation(Base): __tablename__ = "annotations" __table_args__ = ( # One annotation record per (pdf, user, page) — upsert target UniqueConstraint("pdf_id", "user_id", "page_number", name="uq_annotation_pdf_user_page"), ) id = Column(Integer, primary_key=True, index=True) pdf_id = Column(Integer, ForeignKey("pdfs.id", ondelete="CASCADE"), nullable=False, index=True) user_id = Column(Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) page_number = Column(Integer, nullable=False) # 1-based canvas_data = Column(JSONB, nullable=False, default=dict) # Fabric.js JSON state updated_at = Column( DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), onupdate=lambda: datetime.now(timezone.utc), nullable=False, ) # Relationships pdf = relationship("PDF", back_populates="annotations") owner = relationship("User", back_populates="annotations")