from pydantic import BaseModel, Field, EmailStr, validator from datetime import datetime from typing import Optional, List from enum import Enum # Enums for validation class PriorityLevel(str, Enum): """Priority levels for todos""" HIGH = "high" MEDIUM = "medium" LOW = "low" class RecurrencePattern(str, Enum): """Recurrence patterns for todos""" NONE = "none" DAILY = "daily" WEEKLY = "weekly" MONTHLY = "monthly" YEARLY = "yearly" # User schemas class UserBase(BaseModel): """Base user schema with common fields""" username: str = Field(..., min_length=3, max_length=50) email: EmailStr full_name: Optional[str] = Field(None, max_length=100) class UserCreate(UserBase): """Schema for creating a new user""" password: str = Field(..., min_length=6) class UserUpdate(BaseModel): """Schema for updating user information""" username: Optional[str] = Field(None, min_length=3, max_length=50) email: Optional[EmailStr] = None full_name: Optional[str] = Field(None, max_length=100) password: Optional[str] = Field(None, min_length=6) class User(UserBase): """User response schema""" id: int is_active: bool created_at: datetime updated_at: datetime class Config: from_attributes = True class UserInDB(User): """User schema with password hash for internal use""" hashed_password: str # Enhanced Todo schemas class TodoBase(BaseModel): """Base todo schema with common fields""" title: str = Field(..., min_length=1, max_length=200) description: Optional[str] = Field(None, max_length=1000) completed: bool = False priority: PriorityLevel = PriorityLevel.MEDIUM tags: List[str] = Field(default_factory=list, max_items=10) due_date: Optional[datetime] = None parent_id: Optional[int] = Field(None, description="ID of parent todo for subtasks") recurrence_pattern: RecurrencePattern = RecurrencePattern.NONE user_id: Optional[int] = Field( None, description="ID of the user who owns this todo" ) @validator("tags") def validate_tags(cls, v): """Validate tags are non-empty strings and remove duplicates""" if v: # Remove empty strings and duplicates clean_tags = list(set([tag.strip() for tag in v if tag.strip()])) # Validate tag length for tag in clean_tags: if len(tag) > 50: raise ValueError("Each tag must be 50 characters or less") return clean_tags return v @validator("due_date") def validate_due_date(cls, v): """Validate due date is not in the past""" if v and v < datetime.now(): raise ValueError("Due date cannot be in the past") return v class TodoCreate(TodoBase): """Schema for creating a new todo""" pass class TodoUpdate(BaseModel): """Schema for updating todo information""" title: Optional[str] = Field(None, min_length=1, max_length=200) description: Optional[str] = Field(None, max_length=1000) completed: Optional[bool] = None priority: Optional[PriorityLevel] = None tags: Optional[List[str]] = Field(None, max_items=10) due_date: Optional[datetime] = None parent_id: Optional[int] = Field(None, description="ID of parent todo for subtasks") recurrence_pattern: Optional[RecurrencePattern] = None @validator("tags") def validate_tags(cls, v): """Validate tags are non-empty strings and remove duplicates""" if v is not None: # Remove empty strings and duplicates clean_tags = list(set([tag.strip() for tag in v if tag.strip()])) # Validate tag length for tag in clean_tags: if len(tag) > 50: raise ValueError("Each tag must be 50 characters or less") return clean_tags return v @validator("due_date") def validate_due_date(cls, v): """Validate due date is not in the past""" if v and v < datetime.now(): raise ValueError("Due date cannot be in the past") return v class Todo(TodoBase): """Todo response schema with all fields""" id: int created_at: datetime updated_at: datetime subtasks: List["Todo"] = Field(default_factory=list, description="List of subtasks") shared_with: List[User] = Field( default_factory=list, description="Users this todo is shared with" ) class Config: from_attributes = True class TodoSummary(BaseModel): """Simplified todo schema for list views""" id: int title: str completed: bool priority: PriorityLevel tags: List[str] due_date: Optional[datetime] created_at: datetime subtask_count: int = 0 class Config: from_attributes = True # Sharing schemas class TodoShareBase(BaseModel): """Base schema for todo sharing""" todo_id: int user_id: int can_edit: bool = False class TodoShareCreate(TodoShareBase): """Schema for creating a todo share""" pass class TodoShare(TodoShareBase): """Todo share response schema""" id: int shared_at: datetime todo: TodoSummary user: User class Config: from_attributes = True # Response schemas for collections class TodoListResponse(BaseModel): """Response schema for todo lists with pagination""" todos: List[TodoSummary] total: int page: int page_size: int has_next: bool has_previous: bool class UserListResponse(BaseModel): """Response schema for user lists with pagination""" users: List[User] total: int page: int page_size: int has_next: bool has_previous: bool # Authentication schemas class Token(BaseModel): """JWT token response""" access_token: str token_type: str class TokenData(BaseModel): """Token data for JWT validation""" username: Optional[str] = None # API Response schemas class APIResponse(BaseModel): """Generic API response wrapper""" success: bool message: str data: Optional[dict] = None class HealthResponse(BaseModel): """Health check response schema""" status: str timestamp: datetime version: str = "1.0.0" # Update Todo model to handle forward references Todo.model_rebuild()