""" Shared tenant deletion utilities Base classes and utilities for implementing tenant data deletion across services """ from typing import Dict, Any, List from abc import ABC, abstractmethod import structlog from datetime import datetime logger = structlog.get_logger() class TenantDataDeletionResult: """Standard result for tenant data deletion operations""" def __init__(self, tenant_id: str, service_name: str): self.tenant_id = tenant_id self.service_name = service_name self.deleted_counts: Dict[str, int] = {} self.errors: List[str] = [] self.timestamp = datetime.utcnow().isoformat() self.success = True def add_deleted_items(self, entity_type: str, count: int): """Record deleted items for an entity type""" self.deleted_counts[entity_type] = count def add_error(self, error: str): """Add an error message""" self.errors.append(error) self.success = False def to_dict(self) -> Dict[str, Any]: """Convert to dictionary for API response""" return { "tenant_id": self.tenant_id, "service_name": self.service_name, "deleted_counts": self.deleted_counts, "total_deleted": sum(self.deleted_counts.values()), "errors": self.errors, "success": self.success, "timestamp": self.timestamp } class BaseTenantDataDeletionService(ABC): """ Base class for tenant data deletion services Each microservice should implement this to handle their own data cleanup """ def __init__(self, service_name: str): self.service_name = service_name self.logger = structlog.get_logger().bind(service=service_name) @abstractmethod async def delete_tenant_data(self, tenant_id: str) -> TenantDataDeletionResult: """ Delete all data associated with a tenant Args: tenant_id: The tenant whose data should be deleted Returns: TenantDataDeletionResult with deletion summary """ pass @abstractmethod async def get_tenant_data_preview(self, tenant_id: str) -> Dict[str, int]: """ Get a preview of what would be deleted (counts only) Args: tenant_id: The tenant to preview Returns: Dict mapping entity types to counts """ pass async def safe_delete_tenant_data(self, tenant_id: str) -> TenantDataDeletionResult: """ Safely delete tenant data with error handling Args: tenant_id: The tenant whose data should be deleted Returns: TenantDataDeletionResult with deletion summary """ result = TenantDataDeletionResult(tenant_id, self.service_name) try: self.logger.info("Starting tenant data deletion", tenant_id=tenant_id, service=self.service_name) # Call the implementation-specific deletion result = await self.delete_tenant_data(tenant_id) self.logger.info("Tenant data deletion completed", tenant_id=tenant_id, service=self.service_name, deleted_counts=result.deleted_counts, total_deleted=sum(result.deleted_counts.values()), errors=len(result.errors)) return result except Exception as e: self.logger.error("Tenant data deletion failed", tenant_id=tenant_id, service=self.service_name, error=str(e)) result.add_error(f"Fatal error: {str(e)}") return result def create_tenant_deletion_endpoint_handler(deletion_service: BaseTenantDataDeletionService): """ Factory function to create a FastAPI endpoint handler for tenant deletion Usage in service API file: ```python from shared.services.tenant_deletion import create_tenant_deletion_endpoint_handler deletion_service = MyServiceTenantDeletionService() delete_tenant_data = create_tenant_deletion_endpoint_handler(deletion_service) @router.delete("/tenant/{tenant_id}") async def delete_tenant_data_endpoint(tenant_id: str, current_user: dict = Depends(get_current_user)): return await delete_tenant_data(tenant_id, current_user) ``` """ async def handler(tenant_id: str, current_user: Dict[str, Any]) -> Dict[str, Any]: """Handle tenant data deletion request""" # Only allow internal service calls if current_user.get("type") != "service": from fastapi import HTTPException, status raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="This endpoint is only accessible to internal services" ) # Perform deletion result = await deletion_service.safe_delete_tenant_data(tenant_id) return { "message": f"Tenant data deletion completed in {deletion_service.service_name}", "summary": result.to_dict() } return handler def create_tenant_deletion_preview_handler(deletion_service: BaseTenantDataDeletionService): """ Factory function to create a FastAPI endpoint handler for deletion preview Usage in service API file: ```python preview_handler = create_tenant_deletion_preview_handler(deletion_service) @router.get("/tenant/{tenant_id}/deletion-preview") async def preview_endpoint(tenant_id: str, current_user: dict = Depends(get_current_user)): return await preview_handler(tenant_id, current_user) ``` """ async def handler(tenant_id: str, current_user: Dict[str, Any]) -> Dict[str, Any]: """Handle deletion preview request""" # Allow internal services and admins is_service = current_user.get("type") == "service" is_admin = current_user.get("role") in ["owner", "admin"] if not (is_service or is_admin): from fastapi import HTTPException, status raise HTTPException( status_code=status.HTTP_403_FORBIDDEN, detail="Insufficient permissions" ) # Get preview preview = await deletion_service.get_tenant_data_preview(tenant_id) return { "tenant_id": tenant_id, "service": deletion_service.service_name, "data_counts": preview, "total_items": sum(preview.values()) } return handler