Add public/private visibility for trees
- Add is_public field to Tree model (private by default) - Update access control: users see default trees, public trees, or their own - Update all tree endpoints (list, search, get, categories) with new visibility logic - Default/system trees are automatically marked as public - Add migration 004 to add is_public column and update existing defaults - Fix pydantic settings to ignore extra env vars (DATABASE_URL_SYNC) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -31,11 +31,15 @@ async def list_trees(
|
||||
if is_active is not None:
|
||||
query = query.where(Tree.is_active == is_active)
|
||||
|
||||
# Only show active trees or trees owned by user (for now)
|
||||
# Later, add team-based filtering
|
||||
# Only show trees user has access to:
|
||||
# - Default/system trees (visible to all)
|
||||
# - Public trees
|
||||
# - User's own trees (public or private)
|
||||
query = query.where(
|
||||
Tree.is_active == True,
|
||||
or_(
|
||||
Tree.is_active == True,
|
||||
Tree.is_default == True,
|
||||
Tree.is_public == True,
|
||||
Tree.author_id == current_user.id
|
||||
)
|
||||
)
|
||||
@@ -53,10 +57,15 @@ async def list_categories(
|
||||
db: Annotated[AsyncSession, Depends(get_db)],
|
||||
current_user: Annotated[User, Depends(get_current_user)]
|
||||
):
|
||||
"""List all unique categories."""
|
||||
"""List all unique categories from trees the user can access."""
|
||||
query = select(Tree.category).where(
|
||||
Tree.category.isnot(None),
|
||||
Tree.is_active == True
|
||||
Tree.is_active == True,
|
||||
or_(
|
||||
Tree.is_default == True,
|
||||
Tree.is_public == True,
|
||||
Tree.author_id == current_user.id
|
||||
)
|
||||
).distinct()
|
||||
result = await db.execute(query)
|
||||
categories = [row[0] for row in result.all() if row[0]]
|
||||
@@ -77,6 +86,11 @@ async def search_trees(
|
||||
|
||||
query = select(Tree).where(
|
||||
Tree.is_active == True,
|
||||
or_(
|
||||
Tree.is_default == True,
|
||||
Tree.is_public == True,
|
||||
Tree.author_id == current_user.id
|
||||
),
|
||||
search_vector.op('@@')(search_query)
|
||||
).order_by(
|
||||
func.ts_rank(search_vector, search_query).desc()
|
||||
@@ -103,8 +117,9 @@ async def get_tree(
|
||||
detail="Tree not found"
|
||||
)
|
||||
|
||||
# Check access: tree must be active OR user is the author
|
||||
if not tree.is_active and tree.author_id != current_user.id:
|
||||
# Check access: tree must be active AND (default OR public OR author)
|
||||
can_access = tree.is_default or tree.is_public or tree.author_id == current_user.id
|
||||
if not tree.is_active or not can_access:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You don't have access to this tree"
|
||||
@@ -130,6 +145,7 @@ async def create_tree(
|
||||
tree_structure=tree_data.tree_structure,
|
||||
author_id=None if is_default else current_user.id, # Default trees have no author
|
||||
team_id=None if is_default else current_user.team_id,
|
||||
is_public=True if is_default else tree_data.is_public, # Default trees are always public
|
||||
is_default=is_default
|
||||
)
|
||||
db.add(new_tree)
|
||||
|
||||
Reference in New Issue
Block a user