
- Add User model with email-based authentication - Add Tag model with many-to-many relationship to todos - Add TodoTag junction table for todo-tag relationships - Enhance Todo model with priority levels (low, medium, high) - Add due_date field with datetime support - Add recurrence_pattern field for recurring todos - Add parent-child relationship for subtasks support - Create comprehensive alembic migration for all changes - Add proper indexes for performance optimization - Use Text type for todo descriptions - Implement proper SQLAlchemy relationships and foreign keys
258 lines
6.2 KiB
Python
258 lines
6.2 KiB
Python
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()
|