Study-Buddy / db_models.py
Hamdy005's picture
refactor: migrate refresh token storage from PostgreSQL table to Redis and remove associated DB schema.
f4b73f4
Raw
History Blame Contribute Delete
13.3 kB
"""
SQLAlchemy ORM models matching the Supabase PostgreSQL database schema.
This file serves as the single source of truth for the database schema,
used by SQLAlchemy for query ORM mappings and Alembic for migration version control.
"""
import uuid
from datetime import datetime, timezone
from typing import Any, List, Optional
from sqlalchemy import (
ARRAY,
Boolean,
Column,
DateTime,
Float,
ForeignKey,
Integer,
String,
Text,
func,
text,
)
from sqlalchemy.dialects.postgresql import JSONB, UUID
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
try:
from pgvector.sqlalchemy import Vector
except ImportError:
# Fallback type if pgvector package is not available
from sqlalchemy.types import UserDefinedType
class Vector(UserDefinedType): # type: ignore
def __init__(self, dim: Optional[int] = None, *args, **kwargs):
self.dim = dim
def get_col_spec(self, **kw):
return f"VECTOR({self.dim})" if self.dim else "VECTOR"
class Base(DeclarativeBase):
"""Base class for all SQLAlchemy ORM models."""
pass
class Profile(Base):
__tablename__ = "profiles"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, server_default=text("gen_random_uuid()")
)
email: Mapped[Optional[str]] = mapped_column(String, nullable=True)
display_name: Mapped[Optional[str]] = mapped_column(String, nullable=True)
avatar_url: Mapped[Optional[str]] = mapped_column(String, nullable=True)
daily_requests: Mapped[int] = mapped_column(Integer, default=0, server_default=text("0"))
last_request_date: Mapped[Optional[str]] = mapped_column(String, nullable=True)
theme: Mapped[str] = mapped_column(String, default="system", server_default=text("'system'"))
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), server_default=func.now()
)
# Relationships
materials: Mapped[List["Material"]] = relationship("Material", back_populates="user", cascade="all, delete-orphan")
quizzes: Mapped[List["Quiz"]] = relationship("Quiz", back_populates="user", cascade="all, delete-orphan")
quiz_attempts: Mapped[List["QuizAttempt"]] = relationship("QuizAttempt", back_populates="user", cascade="all, delete-orphan")
summaries: Mapped[List["Summary"]] = relationship("Summary", back_populates="user", cascade="all, delete-orphan")
chat_sessions: Mapped[List["ChatSession"]] = relationship("ChatSession", back_populates="user", cascade="all, delete-orphan")
class Material(Base):
__tablename__ = "materials"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, server_default=text("gen_random_uuid()")
)
user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("profiles.id", ondelete="CASCADE"), nullable=False
)
source_type: Mapped[str] = mapped_column(String, nullable=False)
title: Mapped[str] = mapped_column(String, nullable=False)
file_path: Mapped[Optional[str]] = mapped_column(String, nullable=True)
url: Mapped[Optional[str]] = mapped_column(String, nullable=True)
status: Mapped[str] = mapped_column(String, default="pending", server_default=text("'pending'"))
error_message: Mapped[Optional[str]] = mapped_column(String, nullable=True)
vector_store_path: Mapped[Optional[str]] = mapped_column(String, nullable=True)
title_normalized: Mapped[Optional[str]] = mapped_column(String, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), server_default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), server_default=func.now()
)
# Relationships
user: Mapped["Profile"] = relationship("Profile", back_populates="materials")
chunks: Mapped[List["MaterialChunk"]] = relationship("MaterialChunk", back_populates="material", cascade="all, delete-orphan")
embeddings: Mapped[List["MaterialEmbedding"]] = relationship("MaterialEmbedding", back_populates="material", cascade="all, delete-orphan")
summaries: Mapped[List["Summary"]] = relationship("Summary", back_populates="material", cascade="all, delete-orphan")
quizzes: Mapped[List["Quiz"]] = relationship("Quiz", back_populates="material", cascade="all, delete-orphan")
chat_sessions: Mapped[List["ChatSession"]] = relationship("ChatSession", back_populates="material", cascade="all, delete-orphan")
class MaterialChunk(Base):
__tablename__ = "material_chunks"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, server_default=text("gen_random_uuid()")
)
material_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("materials.id", ondelete="CASCADE"), nullable=False
)
chunk_index: Mapped[int] = mapped_column(Integer, nullable=False)
content: Mapped[str] = mapped_column(Text, nullable=False)
token_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), server_default=func.now()
)
# Relationships
material: Mapped["Material"] = relationship("Material", back_populates="chunks")
embeddings: Mapped[List["MaterialEmbedding"]] = relationship("MaterialEmbedding", back_populates="chunk", cascade="all, delete-orphan")
class MaterialEmbedding(Base):
__tablename__ = "material_embeddings"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, server_default=text("gen_random_uuid()")
)
material_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("materials.id", ondelete="CASCADE"), nullable=False
)
chunk_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("material_chunks.id", ondelete="CASCADE"), nullable=False
)
embedding: Mapped[Any] = mapped_column(Vector(384), nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), server_default=func.now()
)
# Relationships
material: Mapped["Material"] = relationship("Material", back_populates="embeddings")
chunk: Mapped["MaterialChunk"] = relationship("MaterialChunk", back_populates="embeddings")
class Summary(Base):
__tablename__ = "summaries"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, server_default=text("gen_random_uuid()")
)
material_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("materials.id", ondelete="CASCADE"), nullable=False
)
user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("profiles.id", ondelete="CASCADE"), nullable=False
)
summary: Mapped[str] = mapped_column(Text, nullable=False)
status: Mapped[str] = mapped_column(String, default="completed", server_default=text("'completed'"))
model_name: Mapped[Optional[str]] = mapped_column(String, nullable=True)
error_message: Mapped[Optional[str]] = mapped_column(String, nullable=True)
time_taken: Mapped[Optional[float]] = mapped_column(Float, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), server_default=func.now()
)
# Relationships
material: Mapped["Material"] = relationship("Material", back_populates="summaries")
user: Mapped["Profile"] = relationship("Profile", back_populates="summaries")
class Quiz(Base):
__tablename__ = "quizzes"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, server_default=text("gen_random_uuid()")
)
user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("profiles.id", ondelete="CASCADE"), nullable=False
)
material_id: Mapped[Optional[uuid.UUID]] = mapped_column(
UUID(as_uuid=True), ForeignKey("materials.id", ondelete="CASCADE"), nullable=True
)
source_type: Mapped[Optional[str]] = mapped_column(String, nullable=True)
difficulty: Mapped[Optional[str]] = mapped_column(String, nullable=True)
mcq_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
tf_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
quiz_data: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True)
status: Mapped[str] = mapped_column(String, default="completed", server_default=text("'completed'"))
model_name: Mapped[Optional[str]] = mapped_column(String, nullable=True)
error_message: Mapped[Optional[str]] = mapped_column(String, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), server_default=func.now()
)
# Relationships
user: Mapped["Profile"] = relationship("Profile", back_populates="quizzes")
material: Mapped[Optional["Material"]] = relationship("Material", back_populates="quizzes")
attempts: Mapped[List["QuizAttempt"]] = relationship("QuizAttempt", back_populates="quiz", cascade="all, delete-orphan")
class QuizAttempt(Base):
__tablename__ = "quiz_attempts"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, server_default=text("gen_random_uuid()")
)
quiz_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("quizzes.id", ondelete="CASCADE"), nullable=False
)
user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("profiles.id", ondelete="CASCADE"), nullable=False
)
score: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
total: Mapped[Optional[int]] = mapped_column(Integer, nullable=True)
results: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), server_default=func.now()
)
# Relationships
quiz: Mapped["Quiz"] = relationship("Quiz", back_populates="attempts")
user: Mapped["Profile"] = relationship("Profile", back_populates="quiz_attempts")
class ChatSession(Base):
__tablename__ = "chat_sessions"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, server_default=text("gen_random_uuid()")
)
user_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("profiles.id", ondelete="CASCADE"), nullable=False
)
material_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("materials.id", ondelete="CASCADE"), nullable=False
)
title: Mapped[Optional[str]] = mapped_column(String, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), server_default=func.now()
)
updated_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), server_default=func.now()
)
# Relationships
user: Mapped["Profile"] = relationship("Profile", back_populates="chat_sessions")
material: Mapped["Material"] = relationship("Material", back_populates="chat_sessions")
messages: Mapped[List["ChatMessage"]] = relationship("ChatMessage", back_populates="session", cascade="all, delete-orphan")
class ChatMessage(Base):
__tablename__ = "chat_messages"
id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), primary_key=True, default=uuid.uuid4, server_default=text("gen_random_uuid()")
)
session_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("chat_sessions.id", ondelete="CASCADE"), nullable=False
)
role: Mapped[Optional[str]] = mapped_column(String, nullable=True)
content: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
message_metadata: Mapped[Optional[dict]] = mapped_column("metadata", JSONB, nullable=True)
retrieved_chunk_ids: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True)
created_at: Mapped[datetime] = mapped_column(
DateTime(timezone=True), default=lambda: datetime.now(timezone.utc), server_default=func.now()
)
# Relationships
session: Mapped["ChatSession"] = relationship("ChatSession", back_populates="messages")
class ChatMessageChunk(Base):
__tablename__ = "chat_message_chunks"
message_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("chat_messages.id", ondelete="CASCADE"), primary_key=True
)
chunk_id: Mapped[uuid.UUID] = mapped_column(
UUID(as_uuid=True), ForeignKey("material_chunks.id", ondelete="CASCADE"), primary_key=True
)
relevance_score: Mapped[Optional[float]] = mapped_column(Float, nullable=True)