File size: 3,480 Bytes
18ade12
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
"""
Category model for the Todo application.

Represents a grouping mechanism for organizing todos.
Per data-model.md specification.
"""

import uuid
from datetime import datetime, timezone
from typing import TYPE_CHECKING, List, Optional

from sqlalchemy import Column, ForeignKey, Index, String
from sqlmodel import Field, Relationship, SQLModel

if TYPE_CHECKING:
    from .todo import Todo
    from .user import User


class Category(SQLModel, table=True):
    """
    Category entity representing a grouping for todos.

    Attributes:
        id: UUID primary key
        name: Category name (max 50 chars)
        color: Hex color code for UI (default #6B7280)
        icon: Optional icon identifier (max 50 chars)
        user_id: Foreign key to users table (CASCADE delete)
        created_at: Timestamp when record was created
        updated_at: Timestamp when record was last updated
    """

    __tablename__ = "categories"

    # Primary key
    id: uuid.UUID = Field(
        default_factory=uuid.uuid4,
        primary_key=True,
        description="Unique identifier"
    )

    # Required fields
    name: str = Field(
        sa_column=Column(String(50), nullable=False),
        description="Category name"
    )

    # Optional fields with defaults
    color: str = Field(
        default="#6B7280",
        sa_column=Column(String(7), default="#6B7280"),
        description="Hex color code for UI"
    )
    icon: Optional[str] = Field(
        default=None,
        sa_column=Column(String(50), nullable=True),
        description="Optional icon identifier"
    )

    # Foreign key - CASCADE delete when user is deleted
    user_id: uuid.UUID = Field(
        sa_column=Column(
            ForeignKey("users.id", ondelete="CASCADE"),
            nullable=False,
            index=True
        ),
        description="Owner user reference"
    )

    # Timestamps
    created_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        description="Record creation timestamp"
    )
    updated_at: datetime = Field(
        default_factory=lambda: datetime.now(timezone.utc),
        sa_column_kwargs={"onupdate": lambda: datetime.now(timezone.utc)},
        description="Last update timestamp"
    )

    # Relationships
    user: "User" = Relationship(back_populates="categories")
    todos: List["Todo"] = Relationship(
        back_populates="category",
        sa_relationship_kwargs={"cascade": "all, delete-orphan"}
    )

    # Table indexes
    __table_args__ = (
        Index("idx_categories_user_id", "user_id"),
        Index("idx_categories_user_name", "user_id", "name", unique=True),
    )


class CategoryCreate(SQLModel):
    """Schema for creating a new category."""

    name: str = Field(max_length=50, description="Category name")
    color: Optional[str] = Field(default="#6B7280", max_length=7, description="Hex color code")
    icon: Optional[str] = Field(default=None, max_length=50, description="Icon identifier")


class CategoryRead(SQLModel):
    """Schema for reading category data."""

    id: uuid.UUID
    name: str
    color: str
    icon: Optional[str]
    user_id: uuid.UUID
    created_at: datetime
    updated_at: datetime


class CategoryUpdate(SQLModel):
    """Schema for updating category data."""

    name: Optional[str] = Field(default=None, max_length=50)
    color: Optional[str] = Field(default=None, max_length=7)
    icon: Optional[str] = Field(default=None, max_length=50)