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:
chihlasm
2026-02-07 02:39:01 -05:00
parent 4ccb93ee31
commit e0089a9c5a
24 changed files with 1178 additions and 152 deletions

View File

@@ -7,7 +7,7 @@ from sqlalchemy import select, func
from app.core.database import get_db
from app.core.audit import log_audit
from app.models.user import User
from app.schemas.user import UserResponse, RoleUpdate, TeamAdminUpdate
from app.schemas.user import UserResponse, RoleUpdate, AccountRoleUpdate
from app.api.deps import require_admin
router = APIRouter(prefix="/admin", tags=["admin"])
@@ -21,7 +21,7 @@ async def list_users(
limit: int = Query(100, ge=1, le=100),
is_active: Optional[bool] = Query(None, description="Filter by active status"),
role: Optional[str] = Query(None, description="Filter by role"),
team_id: Optional[UUID] = Query(None, description="Filter by team")
account_id: Optional[UUID] = Query(None, description="Filter by account")
):
"""List all users (super admin only)."""
query = select(User)
@@ -30,8 +30,8 @@ async def list_users(
query = query.where(User.is_active == is_active)
if role:
query = query.where(User.role == role)
if team_id:
query = query.where(User.team_id == team_id)
if account_id:
query = query.where(User.account_id == account_id)
query = query.order_by(User.created_at.desc()).offset(skip).limit(limit)
@@ -91,14 +91,14 @@ async def update_user_role(
return user
@router.put("/users/{user_id}/team-admin", response_model=UserResponse)
async def toggle_team_admin(
@router.put("/users/{user_id}/account-role", response_model=UserResponse)
async def update_account_role(
user_id: UUID,
data: TeamAdminUpdate,
data: AccountRoleUpdate,
db: Annotated[AsyncSession, Depends(get_db)],
current_user: Annotated[User, Depends(require_admin)]
):
"""Toggle is_team_admin for a user (super admin only)."""
"""Change a user's account role (super admin only)."""
result = await db.execute(select(User).where(User.id == user_id))
user = result.scalar_one_or_none()
@@ -108,15 +108,10 @@ async def toggle_team_admin(
detail="User not found"
)
if data.is_team_admin and user.team_id is None:
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="User must belong to a team to be a team admin"
)
user.is_team_admin = data.is_team_admin
await log_audit(db, current_user.id, "user.team_admin_toggle", "user", user.id,
{"is_team_admin": data.is_team_admin})
old_role = user.account_role
user.account_role = data.account_role
await log_audit(db, current_user.id, "user.account_role_change", "user", user.id,
{"old_account_role": old_role, "new_account_role": data.account_role})
await db.commit()
await db.refresh(user)
return user