""" 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" )