Files
bakery-ia/services/tenant/app/api/tenants.py
2025-09-21 11:57:03 +02:00

534 lines
20 KiB
Python

"""
Enhanced Tenant API endpoints using repository pattern and dependency injection
"""
import structlog
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, status, Path, Query
from typing import List, Dict, Any, Optional
from uuid import UUID
from app.schemas.tenants import (
BakeryRegistration, TenantResponse, TenantAccessResponse,
TenantUpdate, TenantMemberResponse, TenantSearchRequest
)
from app.services.tenant_service import EnhancedTenantService
from shared.auth.decorators import (
get_current_user_dep,
require_admin_role,
require_admin_role_dep
)
from shared.database.base import create_database_manager
from shared.monitoring.metrics import track_endpoint_metrics
logger = structlog.get_logger()
router = APIRouter()
# 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.post("/tenants/register", response_model=TenantResponse)
async def register_bakery_enhanced(
bakery_data: BakeryRegistration,
current_user: Dict[str, Any] = Depends(get_current_user_dep),
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
):
"""Register a new bakery/tenant with enhanced validation and features"""
try:
result = await tenant_service.create_bakery(
bakery_data,
current_user["user_id"]
)
logger.info("Bakery registered successfully",
name=bakery_data.name,
owner_email=current_user.get('email'),
tenant_id=result.id)
return result
except HTTPException:
raise
except Exception as e:
logger.error("Bakery registration failed",
name=bakery_data.name,
owner_id=current_user["user_id"],
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Bakery registration failed"
)
@router.get("/tenants/{tenant_id}/my-access", response_model=TenantAccessResponse)
async def get_current_user_tenant_access(
tenant_id: UUID = Path(..., description="Tenant ID"),
current_user: Dict[str, Any] = Depends(get_current_user_dep)
):
"""Get current user's access to tenant with role and permissions"""
try:
# Create tenant service directly
from app.core.config import settings
database_manager = create_database_manager(settings.DATABASE_URL, "tenant-service")
tenant_service = EnhancedTenantService(database_manager)
access_info = await tenant_service.verify_user_access(current_user["user_id"], str(tenant_id))
return access_info
except Exception as e:
logger.error("Current user access verification failed",
user_id=current_user["user_id"],
tenant_id=str(tenant_id),
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Access verification failed"
)
@router.get("/tenants/{tenant_id}/access/{user_id}", response_model=TenantAccessResponse)
async def verify_tenant_access_enhanced(
tenant_id: UUID = Path(..., description="Tenant ID"),
user_id: str = Path(..., description="User ID")
):
"""Verify if user has access to tenant - Enhanced version with detailed permissions"""
# Check if this is a service request
if user_id in ["training-service", "data-service", "forecasting-service", "auth-service"]:
# Services have access to all tenants for their operations
return TenantAccessResponse(
has_access=True,
role="service",
permissions=["read", "write"]
)
try:
# Create tenant service directly
from app.core.config import settings
database_manager = create_database_manager(settings.DATABASE_URL, "tenant-service")
tenant_service = EnhancedTenantService(database_manager)
access_info = await tenant_service.verify_user_access(user_id, str(tenant_id))
return access_info
except Exception as e:
logger.error("Access verification failed",
user_id=user_id,
tenant_id=str(tenant_id),
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Access verification failed"
)
@router.get("/tenants/{tenant_id}", response_model=TenantResponse)
@track_endpoint_metrics("tenant_get")
async def get_tenant_enhanced(
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 with enhanced data and access control"""
tenant = await tenant_service.get_tenant_by_id(str(tenant_id))
if not tenant:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Tenant not found"
)
return tenant
@router.get("/tenants/subdomain/{subdomain}", response_model=TenantResponse)
@track_endpoint_metrics("tenant_get_by_subdomain")
async def get_tenant_by_subdomain_enhanced(
subdomain: str = Path(..., description="Tenant subdomain"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
):
"""Get tenant by subdomain with enhanced validation"""
tenant = await tenant_service.get_tenant_by_subdomain(subdomain)
if not tenant:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Tenant not found"
)
# Verify user has access to this tenant
access = await tenant_service.verify_user_access(current_user["user_id"], tenant.id)
if not access.has_access:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access denied to tenant"
)
return tenant
@router.get("/tenants/user/{user_id}/owned", response_model=List[TenantResponse])
# @track_endpoint_metrics("tenant_get_user_owned") # Temporarily disabled
async def get_user_owned_tenants_enhanced(
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 owned by a user with enhanced data"""
# Users can only get their own tenants unless they're admin
user_role = current_user.get('role', '').lower()
if user_id != current_user["user_id"] and user_role != 'admin':
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Can only access your own tenants"
)
tenants = await tenant_service.get_user_tenants(user_id)
return tenants
@router.get("/tenants/search", response_model=List[TenantResponse])
@track_endpoint_metrics("tenant_search")
async def search_tenants_enhanced(
search_term: str = Query(..., description="Search term"),
business_type: Optional[str] = Query(None, description="Business type filter"),
city: Optional[str] = Query(None, description="City filter"),
skip: int = Query(0, ge=0, description="Number of records to skip"),
limit: int = Query(50, ge=1, le=100, 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)
):
"""Search tenants with advanced filters and pagination"""
tenants = await tenant_service.search_tenants(
search_term=search_term,
business_type=business_type,
city=city,
skip=skip,
limit=limit
)
return tenants
@router.get("/tenants/nearby", response_model=List[TenantResponse])
@track_endpoint_metrics("tenant_get_nearby")
async def get_nearby_tenants_enhanced(
latitude: float = Query(..., description="Latitude coordinate"),
longitude: float = Query(..., description="Longitude coordinate"),
radius_km: float = Query(10.0, ge=0.1, le=100.0, description="Search radius in kilometers"),
limit: int = Query(50, ge=1, le=100, description="Maximum number of results"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
):
"""Get tenants near a geographic location with enhanced geospatial search"""
tenants = await tenant_service.get_tenants_near_location(
latitude=latitude,
longitude=longitude,
radius_km=radius_km,
limit=limit
)
return tenants
@router.put("/tenants/{tenant_id}", response_model=TenantResponse)
async def update_tenant_enhanced(
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 with enhanced validation and permission checks"""
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.put("/tenants/{tenant_id}/model-status")
@track_endpoint_metrics("tenant_update_model_status")
async def update_tenant_model_status_enhanced(
tenant_id: UUID = Path(..., description="Tenant ID"),
model_trained: bool = Query(..., description="Whether model is trained"),
last_training_date: Optional[datetime] = Query(None, description="Last training date"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
):
"""Update tenant model training status with enhanced tracking"""
try:
result = await tenant_service.update_model_status(
str(tenant_id),
model_trained,
current_user["user_id"],
last_training_date
)
return result
except HTTPException:
raise
except Exception as e:
logger.error("Model status update failed",
tenant_id=str(tenant_id),
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to update model status"
)
@router.post("/tenants/{tenant_id}/members", response_model=TenantMemberResponse)
@track_endpoint_metrics("tenant_add_member")
async def add_team_member_enhanced(
user_id: str,
role: str,
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)
):
"""Add a team member to tenant with enhanced validation and role management"""
try:
result = await tenant_service.add_team_member(
str(tenant_id),
user_id,
role,
current_user["user_id"]
)
return result
except HTTPException:
raise
except Exception as e:
logger.error("Add team member failed",
tenant_id=str(tenant_id),
user_id=user_id,
role=role,
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to add team member"
)
@router.get("/tenants/{tenant_id}/members", response_model=List[TenantMemberResponse])
async def get_team_members_enhanced(
tenant_id: UUID = Path(..., description="Tenant ID"),
active_only: bool = Query(True, description="Only return active members"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
):
"""Get all team members for a tenant with enhanced filtering"""
try:
members = await tenant_service.get_team_members(
str(tenant_id),
current_user["user_id"],
active_only=active_only
)
return members
except HTTPException:
raise
except Exception as e:
logger.error("Get team members failed",
tenant_id=str(tenant_id),
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to get team members"
)
@router.put("/tenants/{tenant_id}/members/{member_user_id}/role", response_model=TenantMemberResponse)
@track_endpoint_metrics("tenant_update_member_role")
async def update_member_role_enhanced(
new_role: str,
tenant_id: UUID = Path(..., description="Tenant ID"),
member_user_id: str = Path(..., description="Member user ID"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
):
"""Update team member role with enhanced permission validation"""
try:
result = await tenant_service.update_member_role(
str(tenant_id),
member_user_id,
new_role,
current_user["user_id"]
)
return result
except HTTPException:
raise
except Exception as e:
logger.error("Update member role failed",
tenant_id=str(tenant_id),
member_user_id=member_user_id,
new_role=new_role,
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to update member role"
)
@router.delete("/tenants/{tenant_id}/members/{member_user_id}")
@track_endpoint_metrics("tenant_remove_member")
async def remove_team_member_enhanced(
tenant_id: UUID = Path(..., description="Tenant ID"),
member_user_id: str = Path(..., description="Member user ID"),
current_user: Dict[str, Any] = Depends(get_current_user_dep),
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
):
"""Remove team member from tenant with enhanced validation"""
try:
success = await tenant_service.remove_team_member(
str(tenant_id),
member_user_id,
current_user["user_id"]
)
if success:
return {"success": True, "message": "Team member removed successfully"}
else:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to remove team member"
)
except HTTPException:
raise
except Exception as e:
logger.error("Remove team member failed",
tenant_id=str(tenant_id),
member_user_id=member_user_id,
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to remove team member"
)
@router.post("/tenants/{tenant_id}/deactivate")
@track_endpoint_metrics("tenant_deactivate")
async def deactivate_tenant_enhanced(
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)
):
"""Deactivate a tenant (owner only) with enhanced validation"""
try:
success = await tenant_service.deactivate_tenant(
str(tenant_id),
current_user["user_id"]
)
if success:
return {"success": True, "message": "Tenant deactivated successfully"}
else:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to deactivate tenant"
)
except HTTPException:
raise
except Exception as e:
logger.error("Tenant deactivation failed",
tenant_id=str(tenant_id),
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to deactivate tenant"
)
@router.post("/tenants/{tenant_id}/activate")
@track_endpoint_metrics("tenant_activate")
async def activate_tenant_enhanced(
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)
):
"""Activate a previously deactivated tenant (owner only) with enhanced validation"""
try:
success = await tenant_service.activate_tenant(
str(tenant_id),
current_user["user_id"]
)
if success:
return {"success": True, "message": "Tenant activated successfully"}
else:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to activate tenant"
)
except HTTPException:
raise
except Exception as e:
logger.error("Tenant activation failed",
tenant_id=str(tenant_id),
error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to activate tenant"
)
@router.get("/tenants/users/{user_id}", response_model=List[TenantResponse])
@track_endpoint_metrics("tenant_get_user_tenants")
async def get_user_tenants_enhanced(
user_id: str = Path(..., description="User ID"),
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
):
"""Get all tenants owned by a user - Fixed endpoint for frontend"""
try:
tenants = await tenant_service.get_user_tenants(user_id)
logger.info("Retrieved user tenants", user_id=user_id, tenant_count=len(tenants))
return tenants
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.get("/tenants/statistics", dependencies=[Depends(require_admin_role_dep)])
@track_endpoint_metrics("tenant_get_statistics")
async def get_tenant_statistics_enhanced(
current_user: Dict[str, Any] = Depends(get_current_user_dep),
tenant_service: EnhancedTenantService = Depends(get_enhanced_tenant_service)
):
"""Get comprehensive tenant statistics (admin only) with enhanced analytics"""
try:
stats = await tenant_service.get_tenant_statistics()
return stats
except Exception as e:
logger.error("Get tenant statistics failed", error=str(e))
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to get tenant statistics"
)