Files
platform/services/brain/app/models/taxonomy.py
Yusuf Suleman 3531360827 feat: brain colored folders/tags — color + icon fields, mobile pills
Backend:
- Added color (hex) and icon (lucide name) columns to folders and tags
- Default folders seeded with colors: Home=green, Work=indigo, Travel=blue, etc.
- API returns color/icon in sidebar and CRUD responses
- Create/update endpoints accept color and icon

Frontend:
- Mobile: horizontal scrollable pill tabs with colored dots
- Desktop sidebar: colored dots next to folder names
- Active pill gets tinted border matching folder color

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 22:32:40 -05:00

117 lines
4.2 KiB
Python

"""Database models for folders and tags — editable taxonomy."""
import re
import uuid
from datetime import datetime
from sqlalchemy import Column, String, Integer, Boolean, DateTime, ForeignKey, Index, UniqueConstraint
from sqlalchemy.dialects.postgresql import UUID
from sqlalchemy.orm import relationship
from app.database import Base
def new_id():
return str(uuid.uuid4())
def slugify(text: str) -> str:
s = text.lower().strip()
s = re.sub(r'[^\w\s-]', '', s)
s = re.sub(r'[\s_]+', '-', s)
return s.strip('-')
class Folder(Base):
__tablename__ = "folders"
id = Column(UUID(as_uuid=False), primary_key=True, default=new_id)
user_id = Column(String(64), nullable=False, index=True)
name = Column(String(128), nullable=False)
slug = Column(String(128), nullable=False)
color = Column(String(7), nullable=True) # hex like #4F46E5
icon = Column(String(32), nullable=True) # lucide icon name like "home"
is_active = Column(Boolean, default=True, nullable=False)
sort_order = Column(Integer, default=0, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
__table_args__ = (
UniqueConstraint('user_id', 'slug', name='uq_folder_user_slug'),
)
class Tag(Base):
__tablename__ = "tags"
id = Column(UUID(as_uuid=False), primary_key=True, default=new_id)
user_id = Column(String(64), nullable=False, index=True)
name = Column(String(128), nullable=False)
slug = Column(String(128), nullable=False)
color = Column(String(7), nullable=True)
icon = Column(String(32), nullable=True)
is_active = Column(Boolean, default=True, nullable=False)
sort_order = Column(Integer, default=0, nullable=False)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow, nullable=False)
__table_args__ = (
UniqueConstraint('user_id', 'slug', name='uq_tag_user_slug'),
)
class ItemTag(Base):
__tablename__ = "item_tags"
item_id = Column(UUID(as_uuid=False), ForeignKey("items.id", ondelete="CASCADE"), primary_key=True)
tag_id = Column(UUID(as_uuid=False), ForeignKey("tags.id", ondelete="CASCADE"), primary_key=True)
created_at = Column(DateTime, default=datetime.utcnow, nullable=False)
# Default folders with colors and icons
DEFAULT_FOLDERS = [
{"name": "Home", "color": "#059669", "icon": "home"},
{"name": "Family", "color": "#D97706", "icon": "heart"},
{"name": "Work", "color": "#4338CA", "icon": "briefcase"},
{"name": "Travel", "color": "#0EA5E9", "icon": "plane"},
{"name": "Knowledge", "color": "#8B5CF6", "icon": "book-open"},
{"name": "Faith", "color": "#10B981", "icon": "moon"},
{"name": "Projects", "color": "#F43F5E", "icon": "folder"},
]
# Default tags to seed for new users
DEFAULT_TAGS = [
"reference", "important", "legal", "financial", "insurance",
"research", "idea", "guide", "tutorial", "setup", "how-to",
"tools", "dev", "server", "selfhosted", "home-assistant",
"shopping", "compare", "buy", "product",
"family", "kids", "health", "travel", "faith",
"video", "read-later", "books",
]
async def ensure_user_taxonomy(db, user_id: str):
"""Seed default folders and tags for a new user if they have none."""
from sqlalchemy import select, func
folder_count = (await db.execute(
select(func.count()).where(Folder.user_id == user_id)
)).scalar() or 0
if folder_count == 0:
for i, f in enumerate(DEFAULT_FOLDERS):
db.add(Folder(id=new_id(), user_id=user_id, name=f["name"], slug=slugify(f["name"]),
color=f.get("color"), icon=f.get("icon"), sort_order=i))
await db.flush()
tag_count = (await db.execute(
select(func.count()).where(Tag.user_id == user_id)
)).scalar() or 0
if tag_count == 0:
for i, name in enumerate(DEFAULT_TAGS):
db.add(Tag(id=new_id(), user_id=user_id, name=name, slug=slugify(name), sort_order=i))
await db.flush()
await db.commit()