Add user delete process
This commit is contained in:
17
shared/services/__init__.py
Normal file
17
shared/services/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
"""
|
||||
Shared services module
|
||||
Contains base classes and utilities for common service functionality
|
||||
"""
|
||||
from .tenant_deletion import (
|
||||
BaseTenantDataDeletionService,
|
||||
TenantDataDeletionResult,
|
||||
create_tenant_deletion_endpoint_handler,
|
||||
create_tenant_deletion_preview_handler,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"BaseTenantDataDeletionService",
|
||||
"TenantDataDeletionResult",
|
||||
"create_tenant_deletion_endpoint_handler",
|
||||
"create_tenant_deletion_preview_handler",
|
||||
]
|
||||
197
shared/services/tenant_deletion.py
Normal file
197
shared/services/tenant_deletion.py
Normal 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
|
||||
Reference in New Issue
Block a user