Files
bakery-ia/services/tenant/app/api/tenants.py

253 lines
8.9 KiB
Python
Raw Normal View History

2025-07-19 17:49:03 +02:00
"""
2025-10-06 15:27:01 +02:00
Tenant API - ATOMIC operations
Handles basic CRUD operations for tenants
2025-07-19 17:49:03 +02:00
"""
import structlog
2025-11-05 13:34:56 +01:00
from fastapi import APIRouter, Depends, HTTPException, status, Path, Query
from typing import Dict, Any, List
2025-08-08 09:08:41 +02:00
from uuid import UUID
2025-07-19 17:49:03 +02:00
2025-10-06 15:27:01 +02:00
from app.schemas.tenants import TenantResponse, TenantUpdate
2025-08-08 09:08:41 +02:00
from app.services.tenant_service import EnhancedTenantService
2025-10-06 15:27:01 +02:00
from shared.auth.decorators import get_current_user_dep
from shared.auth.access_control import admin_role_required
2025-10-06 15:27:01 +02:00
from shared.routing.route_builder import RouteBuilder
2025-08-08 09:08:41 +02:00
from shared.database.base import create_database_manager
from shared.monitoring.metrics import track_endpoint_metrics
2025-07-19 17:49:03 +02:00
logger = structlog.get_logger()
router = APIRouter()
2025-10-06 15:27:01 +02:00
route_builder = RouteBuilder("tenants")
2025-07-19 17:49:03 +02:00
2025-08-08 09:08:41 +02:00
# Dependency injection for enhanced tenant service
def get_enhanced_tenant_service():
2025-08-15 22:40:19 +02:00
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")
2025-08-08 09:08:41 +02:00
2025-11-05 13:34:56 +01:00
@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
2025-10-06 15:27:01 +02:00
@router.get(route_builder.build_base_route("{tenant_id}", include_tenant_prefix=False), response_model=TenantResponse)
2025-08-08 09:08:41 +02:00
@track_endpoint_metrics("tenant_get")
2025-10-06 15:27:01 +02:00
async def get_tenant(
2025-07-26 18:46:52 +02:00
tenant_id: UUID = Path(..., description="Tenant ID"),
2025-07-21 14:41:33 +02:00
current_user: Dict[str, Any] = Depends(get_current_user_dep),
2025-08-08 09:08:41 +02:00
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
2025-07-19 17:49:03 +02:00
):
"""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")
)
2025-10-06 15:27:01 +02:00
2025-08-08 09:08:41 +02:00
tenant = await tenant_service.get_tenant_by_id(str(tenant_id))
2025-07-19 17:49:03 +02:00
if not tenant:
logger.warning(
"Tenant not found",
tenant_id=str(tenant_id),
user_id=current_user.get("user_id")
)
2025-07-19 17:49:03 +02:00
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")
)
2025-08-08 09:08:41 +02:00
return tenant
2025-10-06 15:27:01 +02:00
@router.put(route_builder.build_base_route("{tenant_id}", include_tenant_prefix=False), response_model=TenantResponse)
@admin_role_required
2025-10-06 15:27:01 +02:00
async def update_tenant(
2025-07-19 17:49:03 +02:00
update_data: TenantUpdate,
2025-07-26 18:46:52 +02:00
tenant_id: UUID = Path(..., description="Tenant ID"),
2025-07-21 14:41:33 +02:00
current_user: Dict[str, Any] = Depends(get_current_user_dep),
2025-08-08 09:08:41 +02:00
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
2025-07-19 17:49:03 +02:00
):
"""Update tenant information - ATOMIC operation (Admin+ only)"""
2025-07-21 14:41:33 +02:00
2025-07-19 17:49:03 +02:00
try:
2025-08-08 09:08:41 +02:00
result = await tenant_service.update_tenant(
2025-10-06 15:27:01 +02:00
str(tenant_id),
update_data,
2025-08-08 09:08:41 +02:00
current_user["user_id"]
)
2025-07-19 17:49:03 +02:00
return result
2025-10-06 15:27:01 +02:00
2025-07-19 17:49:03 +02:00
except HTTPException:
raise
except Exception as e:
2025-08-08 09:08:41 +02:00
logger.error("Tenant update failed",
tenant_id=str(tenant_id),
user_id=current_user["user_id"],
error=str(e))
2025-07-19 17:49:03 +02:00
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Tenant update failed"
)
2025-10-31 11:54:19 +01:00
2025-12-05 20:07:01 +01:00
@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)
2025-12-05 20:07:01 +01:00
)
# 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"):
2025-12-05 20:07:01 +01:00
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)
2025-12-05 20:07:01 +01:00
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"
)
2025-10-31 11:54:19 +01:00
@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"
)