""" SQLModel database models. This module defines the database entities for the task management system. The Task model includes user ownership for strict data isolation. Phase III: Adds Conversation and Message models for AI chatbot functionality. """ from sqlmodel import SQLModel, Field, Relationship from datetime import datetime from typing import Optional, List from passlib.context import CryptContext import uuid import hashlib from sqlalchemy import Column, Text # Password hashing context - using sha256_crypt as fallback for Python 3.13 compatibility pwd_context = CryptContext(schemes=["sha256_crypt"], deprecated="auto") class TaskBase(SQLModel): """ Base class for Task with common fields. Provides shared attributes for Task, TaskCreate, and TaskUpdate models. """ title: str = Field( min_length=1, max_length=200, description="Task title (1-200 characters)" ) description: Optional[str] = Field( default=None, max_length=1000, description="Optional task description (0-1000 characters)" ) completed: bool = Field( default=False, description="Task completion status" ) class Task(TaskBase, table=True): """ Task database model. Represents a task entity stored in the tasks table. Each task is owned by exactly one user (user_id references users table). Table Name: tasks """ __tablename__ = "tasks" id: Optional[int] = Field( default=None, primary_key=True, description="Auto-incrementing primary key" ) user_id: str = Field( default="", index=True, nullable=False, description="Owner's user ID (foreign key to users.id)" ) created_at: datetime = Field( default_factory=datetime.utcnow, description="Task creation timestamp" ) updated_at: datetime = Field( default_factory=datetime.utcnow, description="Last update timestamp" ) class User(SQLModel, table=True): """ User database model. Represents a user account in the users table. Stores hashed passwords for secure authentication. Table Name: users """ __tablename__ = "users" id: str = Field( default_factory=lambda: str(uuid.uuid4()), primary_key=True, description="Unique user identifier (UUID)" ) email: str = Field( unique=True, index=True, nullable=False, description="User's email address (unique)" ) name: str = Field( default="", description="User's display name" ) hashed_password: str = Field( description="Hashed password" ) created_at: datetime = Field( default_factory=datetime.utcnow, description="Account creation timestamp" ) def set_password(self, password: str) -> None: """Hash and set the user's password.""" self.hashed_password = pwd_context.hash(password) def verify_password(self, password: str) -> bool: """Verify a password against the stored hash.""" return pwd_context.verify(password, self.hashed_password) class UserRead(SQLModel): """ User response schema (excludes password). """ id: str email: str name: str class Conversation(SQLModel, table=True): """ Conversation database model. Represents a chat session between a user and the AI assistant. Stores conversation metadata and relates to messages. Table Name: conversation """ __tablename__ = "conversation" id: Optional[int] = Field(default=None, primary_key=True) user_id: str = Field( foreign_key="users.id", index=True, nullable=False, description="Owner's user ID from Better Auth" ) created_at: datetime = Field(default_factory=datetime.utcnow) updated_at: datetime = Field(default_factory=datetime.utcnow) # Relationship to messages messages: List["Message"] = Relationship(back_populates="conversation") class Message(SQLModel, table=True): """ Message database model. Represents a single message in a conversation (either user or assistant). Messages are linked to both conversation and user for efficient querying. Table Name: message """ __tablename__ = "message" id: Optional[int] = Field(default=None, primary_key=True) conversation_id: int = Field( foreign_key="conversation.id", index=True, nullable=False, description="Parent conversation" ) user_id: str = Field( nullable=False, description="Redundant copy for fast filtering" ) role: str = Field( nullable=False, description="Message sender: 'user' or 'assistant'" ) content: str = Field( sa_column=Column(Text, nullable=False), description="Message content" ) created_at: datetime = Field(default_factory=datetime.utcnow) # Relationship to conversation conversation: Optional[Conversation] = Relationship(back_populates="messages")