feat: update all endpoints and schemas for account-based model
Replace team_id with account_id across all API endpoints (trees, categories, tags, steps, step_categories, admin, auth). Add new accounts and webhooks endpoints. Registration now atomically creates Account + Subscription, with account_invite_code bypassing the platform invite gate. Schemas updated for account_id/account_role. 82 tests passing including 18 new tests for accounts, subscriptions, and permissions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -15,6 +15,7 @@ from app.models.folder import UserFolder, user_folder_trees
|
||||
from app.schemas.tree import TreeCreate, TreeUpdate, TreeResponse, TreeListResponse, CategoryInfo
|
||||
from app.api.deps import get_current_active_user, require_engineer_or_admin, require_admin
|
||||
from app.core.permissions import can_edit_tree, can_access_tree
|
||||
from app.core.subscriptions import check_tree_limit
|
||||
from app.core.audit import log_audit
|
||||
|
||||
router = APIRouter(prefix="/trees", tags=["trees"])
|
||||
@@ -37,8 +38,8 @@ def build_tree_access_filter(current_user: User):
|
||||
Tree.is_public == True,
|
||||
Tree.author_id == current_user.id,
|
||||
]
|
||||
if current_user.team_id:
|
||||
conditions.append(Tree.team_id == current_user.team_id)
|
||||
if current_user.account_id:
|
||||
conditions.append(Tree.account_id == current_user.account_id)
|
||||
return or_(*conditions)
|
||||
|
||||
|
||||
@@ -61,7 +62,7 @@ def build_tree_response(tree: Tree) -> TreeListResponse:
|
||||
category_info=category_info,
|
||||
tags=tree.tag_names,
|
||||
author_id=tree.author_id,
|
||||
team_id=tree.team_id,
|
||||
account_id=tree.account_id,
|
||||
is_active=tree.is_active,
|
||||
is_public=tree.is_public,
|
||||
is_default=tree.is_default,
|
||||
@@ -92,7 +93,7 @@ def build_full_tree_response(tree: Tree) -> TreeResponse:
|
||||
tags=tree.tag_names,
|
||||
tree_structure=tree.tree_structure,
|
||||
author_id=tree.author_id,
|
||||
team_id=tree.team_id,
|
||||
account_id=tree.account_id,
|
||||
is_active=tree.is_active,
|
||||
is_public=tree.is_public,
|
||||
is_default=tree.is_default,
|
||||
@@ -289,7 +290,7 @@ async def create_tree(
|
||||
detail="Category not found"
|
||||
)
|
||||
# Check category access
|
||||
if category.team_id and category.team_id != current_user.team_id and not current_user.is_super_admin:
|
||||
if category.account_id and category.account_id != current_user.account_id and not current_user.is_super_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You don't have access to this category"
|
||||
@@ -302,16 +303,25 @@ async def create_tree(
|
||||
category_id=tree_data.category_id,
|
||||
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,
|
||||
account_id=None if is_default else current_user.account_id,
|
||||
is_public=True if is_default else tree_data.is_public, # Default trees are always public
|
||||
is_default=is_default
|
||||
)
|
||||
# Check subscription tree limit
|
||||
if not is_default and current_user.account_id:
|
||||
can_create, limit, count = await check_tree_limit(current_user.account_id, db)
|
||||
if not can_create:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_402_PAYMENT_REQUIRED,
|
||||
detail=f"Tree limit reached ({count}/{limit}). Upgrade your plan to create more trees."
|
||||
)
|
||||
|
||||
db.add(new_tree)
|
||||
await db.flush() # Get the ID
|
||||
|
||||
# Handle tags
|
||||
if tree_data.tags:
|
||||
tree_team_id = new_tree.team_id or (current_user.team_id if not current_user.is_super_admin else None)
|
||||
tree_account_id = new_tree.account_id or (current_user.account_id if not current_user.is_super_admin else None)
|
||||
|
||||
# Collect tags to add
|
||||
tags_to_add = []
|
||||
@@ -322,8 +332,8 @@ async def create_tree(
|
||||
tag_query = select(TreeTag).where(
|
||||
TreeTag.slug == slug,
|
||||
or_(
|
||||
TreeTag.team_id.is_(None),
|
||||
TreeTag.team_id == tree_team_id
|
||||
TreeTag.account_id.is_(None),
|
||||
TreeTag.account_id == tree_account_id
|
||||
)
|
||||
)
|
||||
tag_result = await db.execute(tag_query)
|
||||
@@ -334,7 +344,7 @@ async def create_tree(
|
||||
tag = TreeTag(
|
||||
name=tag_name,
|
||||
slug=slug,
|
||||
team_id=tree_team_id,
|
||||
account_id=tree_account_id,
|
||||
created_by=current_user.id
|
||||
)
|
||||
db.add(tag)
|
||||
@@ -420,7 +430,7 @@ async def update_tree(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail="Category not found"
|
||||
)
|
||||
if category.team_id and category.team_id != current_user.team_id and not current_user.is_super_admin:
|
||||
if category.account_id and category.account_id != current_user.account_id and not current_user.is_super_admin:
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail="You don't have access to this category"
|
||||
@@ -450,7 +460,7 @@ async def update_tree(
|
||||
)
|
||||
|
||||
# Add new tags
|
||||
tree_team_id = tree.team_id or (current_user.team_id if not current_user.is_super_admin else None)
|
||||
tree_account_id = tree.account_id or (current_user.account_id if not current_user.is_super_admin else None)
|
||||
added_tag_ids = set()
|
||||
|
||||
for tag_name in tags_data:
|
||||
@@ -459,8 +469,8 @@ async def update_tree(
|
||||
tag_query = select(TreeTag).where(
|
||||
TreeTag.slug == slug,
|
||||
or_(
|
||||
TreeTag.team_id.is_(None),
|
||||
TreeTag.team_id == tree_team_id
|
||||
TreeTag.account_id.is_(None),
|
||||
TreeTag.account_id == tree_account_id
|
||||
)
|
||||
)
|
||||
tag_result = await db.execute(tag_query)
|
||||
@@ -470,7 +480,7 @@ async def update_tree(
|
||||
tag = TreeTag(
|
||||
name=tag_name,
|
||||
slug=slug,
|
||||
team_id=tree_team_id,
|
||||
account_id=tree_account_id,
|
||||
created_by=current_user.id
|
||||
)
|
||||
db.add(tag)
|
||||
|
||||
Reference in New Issue
Block a user