"""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": "Islam", "color": "#10B981", "icon": "moon"}, {"name": "Homelab", "color": "#6366F1", "icon": "server"}, {"name": "Vanlife", "color": "#F59E0B", "icon": "truck"}, {"name": "3D Printing", "color": "#EC4899", "icon": "printer"}, {"name": "Documents", "color": "#78716C", "icon": "file-text"}, ] # Default tags to seed for new users DEFAULT_TAGS = [ "diy", "reference", "home-assistant", "shopping", "video", "tutorial", "server", "kids", "books", "travel", "churning", "lawn-garden", "piracy", "work", "3d-printing", "lectures", "vanlife", "yusuf", "madiha", "hafsa", "mustafa", "medical", "legal", "vehicle", "insurance", "financial", "homeschool", ] 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()