Add user delete process

This commit is contained in:
Urtzi Alfaro
2025-10-31 11:54:19 +01:00
parent 63f5c6d512
commit 269d3b5032
74 changed files with 16783 additions and 213 deletions

View File

@@ -0,0 +1,197 @@
"""
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