""" Database models for Neon Postgres """ from sqlalchemy import Column, String, Text, DateTime, ForeignKey, UniqueConstraint, Index from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.sql import func import uuid from database.db import Base class User(Base): __tablename__ = "users" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) email = Column(String(255), unique=True, nullable=False, index=True) password_hash = Column(String(255), nullable=True) # Nullable for OAuth users full_name = Column(String(255), nullable=True) software_background = Column(Text, nullable=True) hardware_background = Column(Text, nullable=True) experience_level = Column(String(50), nullable=True, default="Intermediate") # OAuth fields oauth_provider = Column(String(50), nullable=True) # 'google', 'facebook', or None oauth_id = Column(String(255), nullable=True) # Provider's user ID profile_picture = Column(String(500), nullable=True) # Profile picture URL from OAuth created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Index for OAuth lookup __table_args__ = ( Index('idx_user_oauth', 'oauth_provider', 'oauth_id'), ) class Personalization(Base): __tablename__ = "personalizations" 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) chapter_id = Column(String(255), nullable=False, index=True) personalized_content = Column(Text, nullable=False) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Composite unique constraint: one personalization per user per chapter __table_args__ = ( UniqueConstraint('user_id', 'chapter_id', name='uq_user_chapter'), ) class Translation(Base): __tablename__ = "translations" id = Column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) chapter_id = Column(String(255), nullable=False, index=True) content_hash = Column(String(64), nullable=False, index=True) source_language = Column(String(10), nullable=False, default="english") target_language = Column(String(10), nullable=False, index=True) original_content = Column(Text, nullable=False) translated_content = Column(Text, nullable=False) user_id = Column(UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), onupdate=func.now()) # Composite unique constraint: one translation per (chapter_id, content_hash, target_language) __table_args__ = ( UniqueConstraint('chapter_id', 'content_hash', 'target_language', name='uq_chapter_hash_language'), Index('idx_translations_lookup', 'chapter_id', 'content_hash', 'target_language'), )