Source code for app.core.tenant

"""Tenant API router - tenant info, usage, and auth bootstrap."""

from typing import List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
import jwt

from app.dependencies import get_kumiho_client, get_user_token, ensure_tenant_context
from typing import Any
import kumiho

router = APIRouter()


[docs] class UserInfo(BaseModel): """User information response.""" email: str id: str
[docs] class BootstrapResponse(BaseModel): """Session bootstrap response for frontend initialization.""" tenant_id: str project_names: List[str] anonymous_allowed: bool
[docs] class TenantUsageResponse(BaseModel): """Tenant usage and limits.""" tenant_id: str node_count: int node_limit: int
[docs] @router.get("/bootstrap", response_model=BootstrapResponse) async def bootstrap( client: Any = Depends(get_kumiho_client) ): """ Return public tenant configuration for frontend bootstrapping. This replaces the need for NEXT_PUBLIC_TENANT_ID and other build-time secrets. The frontend can call this endpoint to discover tenant configuration dynamically. """ try: context = ensure_tenant_context() projects = kumiho.get_projects() project_names = [p.name for p in projects] return BootstrapResponse( tenant_id=context.tenant_id, project_names=project_names, anonymous_allowed=True # Can be made configurable via tenant settings ) except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to bootstrap session: {str(e)}")
[docs] @router.get("/whoami", response_model=UserInfo) async def whoami( project_name: Optional[str] = Query(None, description="Project name to validate membership against"), client: Any = Depends(get_kumiho_client), token: Optional[str] = Depends(get_user_token) ): """ Return information about the authenticated user. Optionally validates membership in a specific project. """ try: if not token: return UserInfo(email="anonymous", id="anonymous") # Validate against Kumiho Backend (Tenant Membership) if project_name: try: kumiho.get_project(project_name) except Exception as e: raise HTTPException(status_code=401, detail=f"User not authorized: {str(e)}") decoded = jwt.decode(token, options={"verify_signature": False}) email = decoded.get('email') or decoded.get('sub') or "Unknown" user_id = decoded.get('sub') or "Unknown" return UserInfo(email=email, id=user_id) except HTTPException: raise except Exception as e: raise HTTPException(status_code=401, detail="Invalid authentication")
[docs] @router.get("/usage", response_model=TenantUsageResponse) async def get_tenant_usage( client: Any = Depends(get_kumiho_client) ): """Get the current tenant's usage statistics and limits.""" try: usage = client.get_tenant_usage() return TenantUsageResponse( tenant_id=usage.get("tenant_id", ""), node_count=usage.get("node_count", 0), node_limit=usage.get("node_limit", 0) ) except Exception as e: raise HTTPException(status_code=500, detail=str(e))