todo_chatbot / src /models /task.py
Awais68
Fix userId consistency, add history service, fix task CRUD
fa948af
Raw
History Blame Contribute Delete
4.81 kB
"""
Task model migrated to SQLModel with PostgreSQL support.
Extended for 012-advanced-todo-features with due dates, recurrence, and reminders.
"""
from typing import Optional
from datetime import datetime
from enum import Enum
from sqlmodel import SQLModel, Field, Column
from sqlalchemy import DateTime
class RecurrencePattern(str, Enum):
"""Recurrence pattern enumeration for recurring tasks."""
DAILY = "daily"
WEEKLY = "weekly"
BI_WEEKLY = "bi-weekly"
MONTHLY = "monthly"
YEARLY = "yearly"
class Task(SQLModel, table=True):
"""Task entity for todo items."""
__tablename__ = "tasks"
# Existing fields from 001-todo-ai-chatbot
id: Optional[int] = Field(default=None, primary_key=True)
user_id: int = Field(index=True) # Removed foreign_key constraint for demo users
title: str = Field(max_length=200, min_length=1)
description: str = Field(default="", max_length=1000)
completed: bool = Field(default=False)
created_at: datetime = Field(default_factory=datetime.utcnow)
updated_at: datetime = Field(default_factory=datetime.utcnow)
# Offline sync support
client_id: Optional[str] = Field(default=None, max_length=100, index=True)
version: int = Field(default=1)
# NEW FIELDS for 012-advanced-todo-features (due dates, recurrence, reminders)
due_date: Optional[datetime] = Field(
default=None,
sa_column=Column(DateTime(timezone=True)),
description="Task deadline in UTC timezone"
)
recurrence_pattern: Optional[RecurrencePattern] = Field(
default=None,
description="Recurrence type: daily/weekly/bi-weekly/monthly/yearly"
)
is_recurring: bool = Field(
default=False,
description="Whether task auto-creates next instance on completion"
)
reminder_minutes: int = Field(
default=15,
ge=0,
le=1440, # Max 24 hours
description="Minutes before due_date to send reminder (0-1440)"
)
next_occurrence: Optional[datetime] = Field(
default=None,
sa_column=Column(DateTime(timezone=True)),
description="Auto-calculated next due date for recurring tasks"
)
def validate_due_date(self) -> None:
"""
Validate due date constraints.
Raises:
ValueError: If due_date is not timezone-aware
"""
if self.due_date and self.due_date.tzinfo is None:
raise ValueError("due_date must be timezone-aware (UTC)")
def validate_recurrence(self) -> None:
"""
Validate recurrence configuration.
Raises:
ValueError: If recurrence settings are invalid
"""
if self.is_recurring and not self.recurrence_pattern:
raise ValueError("is_recurring requires recurrence_pattern to be set")
if self.is_recurring and not self.due_date:
raise ValueError("Recurring tasks must have a due_date")
if self.recurrence_pattern and not self.is_recurring:
# Auto-set is_recurring if pattern is set
self.is_recurring = True
def calculate_next_occurrence(self) -> datetime:
"""
Calculate next occurrence for recurring tasks.
Returns:
Next occurrence datetime in UTC
Raises:
ValueError: If not a recurring task or missing due_date
"""
if not self.is_recurring or not self.due_date or not self.recurrence_pattern:
raise ValueError("Can only calculate next occurrence for recurring tasks")
from ..utils.recurrence_calculator import RecurrenceCalculator
return RecurrenceCalculator.calculate_next_occurrence(
self.due_date,
self.recurrence_pattern.value
)
class TaskCreate(SQLModel):
"""Schema for creating a new task."""
title: str = Field(max_length=200, min_length=1)
description: str = Field(default="", max_length=1000)
client_id: Optional[str] = Field(default=None, max_length=100)
class TaskUpdate(SQLModel):
"""Schema for updating a task."""
title: Optional[str] = Field(default=None, max_length=200, min_length=1)
description: Optional[str] = Field(default=None, max_length=1000)
completed: Optional[bool] = Field(default=None)
class TaskResponse(SQLModel):
"""Schema for task response."""
id: int
user_id: int
title: str
description: str
completed: bool
created_at: datetime
updated_at: datetime
client_id: Optional[str] = None
version: int
# NEW FIELDS for 012-advanced-todo-features
due_date: Optional[datetime] = None
recurrence_pattern: Optional[RecurrencePattern] = None
is_recurring: bool = False
reminder_minutes: int = 15
next_occurrence: Optional[datetime] = None