198 lines
6.5 KiB
Python
198 lines
6.5 KiB
Python
"""
|
|
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
|