When the frontend requests tenants with user_id='demo-user' in demo mode, the backend now correctly maps this to the actual demo owner ID from the current_user context (set by the gateway middleware). This fixes the issue where the tenant list API was returning empty results even though it returned 200 OK, because it was looking for a user with id='demo-user' which doesn't exist in the database. The actual user IDs are: - Professional: c1a2b3c4-d5e6-47a8-b9c0-d1e2f3a4b5c6 (María García López) - Enterprise: d2e3f4a5-b6c7-48d9-e0f1-a2b3c4d5e6f7 (Director) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
253 lines
8.9 KiB
Python
253 lines
8.9 KiB
Python
"""
|
|
Tenant API - ATOMIC operations
|
|
Handles basic CRUD operations for tenants
|
|
"""
|
|
|
|
import structlog
|
|
from fastapi import APIRouter, Depends, HTTPException, status, Path, Query
|
|
from typing import Dict, Any, List
|
|
from uuid import UUID
|
|
|
|
from app.schemas.tenants import TenantResponse, TenantUpdate
|
|
from app.services.tenant_service import EnhancedTenantService
|
|
from shared.auth.decorators import get_current_user_dep
|
|
from shared.auth.access_control import admin_role_required
|
|
from shared.routing.route_builder import RouteBuilder
|
|
from shared.database.base import create_database_manager
|
|
from shared.monitoring.metrics import track_endpoint_metrics
|
|
|
|
logger = structlog.get_logger()
|
|
router = APIRouter()
|
|
route_builder = RouteBuilder("tenants")
|
|
|
|
# Dependency injection for enhanced tenant service
|
|
def get_enhanced_tenant_service():
|
|
try:
|
|
from app.core.config import settings
|
|
database_manager = create_database_manager(settings.DATABASE_URL, "tenant-service")
|
|
return EnhancedTenantService(database_manager)
|
|
except Exception as e:
|
|
logger.error("Failed to create enhanced tenant service", error=str(e))
|
|
raise HTTPException(status_code=500, detail="Service initialization failed")
|
|
|
|
@router.get(route_builder.build_base_route("", include_tenant_prefix=False), response_model=List[TenantResponse])
|
|
@track_endpoint_metrics("tenants_list")
|
|
async def get_active_tenants(
|
|
skip: int = Query(0, ge=0, description="Number of records to skip"),
|
|
limit: int = Query(100, ge=1, le=1000, description="Maximum number of records to return"),
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
|
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
|
|
):
|
|
"""Get all active tenants - Available to service accounts and admins"""
|
|
|
|
logger.info(
|
|
"Get active tenants request received",
|
|
skip=skip,
|
|
limit=limit,
|
|
user_id=current_user.get("user_id"),
|
|
user_type=current_user.get("type", "user"),
|
|
is_service=current_user.get("type") == "service",
|
|
role=current_user.get("role"),
|
|
service_name=current_user.get("service", "none")
|
|
)
|
|
|
|
# Allow service accounts to call this endpoint
|
|
if current_user.get("type") != "service":
|
|
# For non-service users, could add additional role checks here if needed
|
|
logger.debug(
|
|
"Non-service user requesting active tenants",
|
|
user_id=current_user.get("user_id"),
|
|
role=current_user.get("role")
|
|
)
|
|
|
|
tenants = await tenant_service.get_active_tenants(skip=skip, limit=limit)
|
|
|
|
logger.debug(
|
|
"Get active tenants successful",
|
|
count=len(tenants),
|
|
skip=skip,
|
|
limit=limit
|
|
)
|
|
|
|
return tenants
|
|
|
|
@router.get(route_builder.build_base_route("{tenant_id}", include_tenant_prefix=False), response_model=TenantResponse)
|
|
@track_endpoint_metrics("tenant_get")
|
|
async def get_tenant(
|
|
tenant_id: UUID = Path(..., description="Tenant ID"),
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
|
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
|
|
):
|
|
"""Get tenant by ID - ATOMIC operation - ENHANCED with logging"""
|
|
|
|
logger.info(
|
|
"Tenant GET request received",
|
|
tenant_id=str(tenant_id),
|
|
user_id=current_user.get("user_id"),
|
|
user_type=current_user.get("type", "user"),
|
|
is_service=current_user.get("type") == "service",
|
|
role=current_user.get("role"),
|
|
service_name=current_user.get("service", "none")
|
|
)
|
|
|
|
tenant = await tenant_service.get_tenant_by_id(str(tenant_id))
|
|
if not tenant:
|
|
logger.warning(
|
|
"Tenant not found",
|
|
tenant_id=str(tenant_id),
|
|
user_id=current_user.get("user_id")
|
|
)
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Tenant not found"
|
|
)
|
|
|
|
logger.debug(
|
|
"Tenant GET request successful",
|
|
tenant_id=str(tenant_id),
|
|
user_id=current_user.get("user_id")
|
|
)
|
|
|
|
return tenant
|
|
|
|
@router.put(route_builder.build_base_route("{tenant_id}", include_tenant_prefix=False), response_model=TenantResponse)
|
|
@admin_role_required
|
|
async def update_tenant(
|
|
update_data: TenantUpdate,
|
|
tenant_id: UUID = Path(..., description="Tenant ID"),
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
|
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
|
|
):
|
|
"""Update tenant information - ATOMIC operation (Admin+ only)"""
|
|
|
|
try:
|
|
result = await tenant_service.update_tenant(
|
|
str(tenant_id),
|
|
update_data,
|
|
current_user["user_id"]
|
|
)
|
|
return result
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Tenant update failed",
|
|
tenant_id=str(tenant_id),
|
|
user_id=current_user["user_id"],
|
|
error=str(e))
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Tenant update failed"
|
|
)
|
|
|
|
@router.get(route_builder.build_base_route("user/{user_id}/tenants", include_tenant_prefix=False), response_model=List[TenantResponse])
|
|
@track_endpoint_metrics("user_tenants_list")
|
|
async def get_user_tenants(
|
|
user_id: str = Path(..., description="User ID"),
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
|
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
|
|
):
|
|
"""Get all tenants accessible by a user"""
|
|
|
|
logger.info(
|
|
"Get user tenants request received",
|
|
user_id=user_id,
|
|
requesting_user=current_user.get("user_id"),
|
|
is_demo=current_user.get("is_demo", False)
|
|
)
|
|
|
|
# Allow demo users to access tenant information for demo-user
|
|
is_demo_user = current_user.get("is_demo", False)
|
|
is_service_account = current_user.get("type") == "service"
|
|
|
|
# For demo sessions, when frontend requests with "demo-user", use the actual demo owner ID
|
|
actual_user_id = user_id
|
|
if is_demo_user and user_id == "demo-user":
|
|
actual_user_id = current_user.get("user_id")
|
|
logger.info(
|
|
"Demo session: mapping demo-user to actual owner",
|
|
requested_user_id=user_id,
|
|
actual_user_id=actual_user_id
|
|
)
|
|
|
|
if current_user.get("user_id") != actual_user_id and not is_service_account and not (is_demo_user and user_id == "demo-user"):
|
|
raise HTTPException(
|
|
status_code=status.HTTP_403_FORBIDDEN,
|
|
detail="Can only access own tenants"
|
|
)
|
|
|
|
try:
|
|
tenants = await tenant_service.get_user_tenants(actual_user_id)
|
|
|
|
logger.debug(
|
|
"Get user tenants successful",
|
|
user_id=user_id,
|
|
tenant_count=len(tenants)
|
|
)
|
|
|
|
return tenants
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Get user tenants failed",
|
|
user_id=user_id,
|
|
error=str(e))
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Failed to get user tenants"
|
|
)
|
|
|
|
@router.delete(route_builder.build_base_route("{tenant_id}", include_tenant_prefix=False))
|
|
@track_endpoint_metrics("tenant_delete")
|
|
async def delete_tenant(
|
|
tenant_id: UUID = Path(..., description="Tenant ID"),
|
|
current_user: Dict[str, Any] = Depends(get_current_user_dep),
|
|
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
|
|
):
|
|
"""Delete tenant and all associated data - ATOMIC operation (Owner/Admin or System only)"""
|
|
|
|
logger.info(
|
|
"Tenant DELETE request received",
|
|
tenant_id=str(tenant_id),
|
|
user_id=current_user.get("user_id"),
|
|
user_type=current_user.get("type", "user"),
|
|
is_service=current_user.get("type") == "service",
|
|
role=current_user.get("role"),
|
|
service_name=current_user.get("service", "none")
|
|
)
|
|
|
|
try:
|
|
# Allow internal service calls to bypass admin check
|
|
skip_admin_check = current_user.get("type") == "service"
|
|
|
|
result = await tenant_service.delete_tenant(
|
|
str(tenant_id),
|
|
requesting_user_id=current_user.get("user_id"),
|
|
skip_admin_check=skip_admin_check
|
|
)
|
|
|
|
logger.info(
|
|
"Tenant DELETE request successful",
|
|
tenant_id=str(tenant_id),
|
|
user_id=current_user.get("user_id"),
|
|
deleted_items=result.get("deleted_items")
|
|
)
|
|
|
|
return {
|
|
"message": "Tenant deleted successfully",
|
|
"summary": result
|
|
}
|
|
|
|
except HTTPException:
|
|
raise
|
|
except Exception as e:
|
|
logger.error("Tenant deletion failed",
|
|
tenant_id=str(tenant_id),
|
|
user_id=current_user.get("user_id"),
|
|
error=str(e))
|
|
raise HTTPException(
|
|
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
|
|
detail="Tenant deletion failed"
|
|
)
|