Files
resolutionflow/backend/app/schemas/category.py
Michael Chihlas d6f4286570 feat: add workspace system and sidebar layout (UI design system Phase A+B)
Backend: Workspace model, migration (036), schemas, CRUD API endpoints.
Adds workspace_id to trees and categories, seeds 4 default workspaces
per account, auto-assigns existing trees by tree_type.

Frontend: Complete AppLayout rewrite from top-nav to CSS Grid shell
with persistent sidebar + topbar. New components: WorkspaceSwitcher,
NavItem, CategoryList, TagCloud, TopBar, Sidebar. Dashboard components:
QuickStats, FiltersBar, SectionGroup, TreeListItem, SessionsPanel.
WorkspaceStore with localStorage persistence.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-15 01:16:33 -05:00

63 lines
1.6 KiB
Python

from datetime import datetime
from typing import Optional
from uuid import UUID
from pydantic import BaseModel, Field
import re
def slugify(name: str) -> str:
"""Convert a name to a URL-safe slug."""
# Remove non-alphanumeric chars except spaces, convert to lowercase
slug = re.sub(r'[^a-zA-Z0-9 ]', '', name.lower())
# Replace spaces with hyphens
slug = re.sub(r' +', '-', slug.strip())
return slug
class CategoryBase(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
description: Optional[str] = None
class CategoryCreate(CategoryBase):
account_id: Optional[UUID] = Field(None, description="Account ID for account-specific category. NULL for global.")
class CategoryUpdate(BaseModel):
name: Optional[str] = Field(None, min_length=1, max_length=100)
description: Optional[str] = None
display_order: Optional[int] = None
is_active: Optional[bool] = None
class CategoryResponse(CategoryBase):
id: UUID
slug: str
account_id: Optional[UUID] = None
display_order: int
is_active: bool
color: Optional[str] = None
workspace_id: Optional[UUID] = None
created_at: datetime
updated_at: datetime
tree_count: int = 0 # Computed field
class Config:
from_attributes = True
class CategoryListResponse(BaseModel):
id: UUID
name: str
slug: str
description: Optional[str] = None
account_id: Optional[UUID] = None
display_order: int
is_active: bool
color: Optional[str] = None
workspace_id: Optional[UUID] = None
tree_count: int = 0
class Config:
from_attributes = True