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>
55 lines
1.7 KiB
Python
55 lines
1.7 KiB
Python
"""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')
|