Add tree organization system with categories, tags, and folders
Features: - Categories: Global and team-specific tree categorization (admin-managed) - Tags: Flexible tree tagging with autocomplete (author + admin) - User folders: Personal tree collections with subfolder support - Hierarchical structure (max 3 levels deep) - Right-click context menu for folder management - Cascade delete for subfolders - Filter trees by category, tags, and folder in library view Backend: - New models: Category, Tag, UserFolder with relationships - New API endpoints for categories, tags, and folders - Tree organization migrations (005, 006) Frontend: - FolderSidebar with hierarchical folder tree - FolderEditModal for create/edit with color picker - AddToFolderMenu for quick tree organization - TagInput with autocomplete and TagBadges display - Updated TreeMetadataForm and TreeLibraryPage Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
54
backend/alembic/versions/006_add_folder_hierarchy.py
Normal file
54
backend/alembic/versions/006_add_folder_hierarchy.py
Normal file
@@ -0,0 +1,54 @@
|
||||
"""Add folder hierarchy support
|
||||
|
||||
Revision ID: 006
|
||||
Revises: 005
|
||||
Create Date: 2026-02-02
|
||||
|
||||
"""
|
||||
from typing import Sequence, Union
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects import postgresql
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision: str = '006'
|
||||
down_revision: Union[str, None] = '005'
|
||||
branch_labels: Union[str, Sequence[str], None] = None
|
||||
depends_on: Union[str, Sequence[str], None] = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
# Add parent_id column for folder hierarchy
|
||||
op.add_column(
|
||||
'user_folders',
|
||||
sa.Column(
|
||||
'parent_id',
|
||||
postgresql.UUID(as_uuid=True),
|
||||
sa.ForeignKey('user_folders.id', ondelete='CASCADE'),
|
||||
nullable=True
|
||||
)
|
||||
)
|
||||
op.create_index('ix_user_folders_parent_id', 'user_folders', ['parent_id'])
|
||||
|
||||
# Update unique constraint to allow same name in different parent folders
|
||||
# Old constraint: (user_id, name) must be unique
|
||||
# New constraint: (user_id, name, parent_id) must be unique
|
||||
# This allows folders with same name under different parents
|
||||
op.drop_constraint('uq_user_folders_user_name', 'user_folders', type_='unique')
|
||||
op.create_unique_constraint(
|
||||
'uq_user_folders_user_name_parent',
|
||||
'user_folders',
|
||||
['user_id', 'name', 'parent_id']
|
||||
)
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
# Restore original unique constraint
|
||||
op.drop_constraint('uq_user_folders_user_name_parent', 'user_folders', type_='unique')
|
||||
op.create_unique_constraint('uq_user_folders_user_name', 'user_folders', ['user_id', 'name'])
|
||||
|
||||
# Remove parent_id column and index
|
||||
op.drop_index('ix_user_folders_parent_id', table_name='user_folders')
|
||||
op.drop_column('user_folders', 'parent_id')
|
||||
Reference in New Issue
Block a user