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

@@ -3,7 +3,7 @@
Recipe Operations API - Business operations and complex workflows
"""
from fastapi import APIRouter, Depends, HTTPException, Header, Query
from fastapi import APIRouter, Depends, HTTPException, Header, Query, Path
from sqlalchemy.ext.asyncio import AsyncSession
from uuid import UUID
import logging
@@ -219,3 +219,84 @@ async def get_recipe_count(
except Exception as e:
logger.error(f"Error getting recipe count: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
# ============================================================================
# Tenant Data Deletion Operations (Internal Service Only)
# ============================================================================
from shared.auth.access_control import service_only_access
from app.services.tenant_deletion_service import RecipesTenantDeletionService
@router.delete(
route_builder.build_base_route("tenant/{tenant_id}", include_tenant_prefix=False),
response_model=dict
)
@service_only_access
async def delete_tenant_data(
tenant_id: str = Path(..., description="Tenant ID to delete data for"),
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""
Delete all recipes data for a tenant (Internal service only)
"""
try:
logger.info("recipes.tenant_deletion.api_called", tenant_id=tenant_id)
deletion_service = RecipesTenantDeletionService(db)
result = await deletion_service.safe_delete_tenant_data(tenant_id)
if not result.success:
raise HTTPException(
status_code=500,
detail=f"Tenant data deletion failed: {', '.join(result.errors)}"
)
return {
"message": "Tenant data deletion completed successfully",
"summary": result.to_dict()
}
except HTTPException:
raise
except Exception as e:
logger.error("recipes.tenant_deletion.api_error", tenant_id=tenant_id, error=str(e), exc_info=True)
raise HTTPException(status_code=500, detail=f"Failed to delete tenant data: {str(e)}")
@router.get(
route_builder.build_base_route("tenant/{tenant_id}/deletion-preview", include_tenant_prefix=False),
response_model=dict
)
@service_only_access
async def preview_tenant_data_deletion(
tenant_id: str = Path(..., description="Tenant ID to preview deletion for"),
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""
Preview what data would be deleted for a tenant (dry-run)
"""
try:
logger.info("recipes.tenant_deletion.preview_called", tenant_id=tenant_id)
deletion_service = RecipesTenantDeletionService(db)
result = await deletion_service.preview_deletion(tenant_id)
if not result.success:
raise HTTPException(
status_code=500,
detail=f"Tenant deletion preview failed: {', '.join(result.errors)}"
)
return {
"tenant_id": tenant_id,
"service": "recipes-service",
"data_counts": result.deleted_counts,
"total_items": sum(result.deleted_counts.values())
}
except HTTPException:
raise
except Exception as e:
logger.error("recipes.tenant_deletion.preview_error", tenant_id=tenant_id, error=str(e), exc_info=True)
raise HTTPException(status_code=500, detail=f"Failed to preview tenant data deletion: {str(e)}")

View File

@@ -390,3 +390,86 @@ async def get_recipe_deletion_summary(
except Exception as e:
logger.error(f"Error getting deletion summary: {e}")
raise HTTPException(status_code=500, detail="Internal server error")
# ===== Tenant Data Deletion Endpoints =====
@router.delete("/tenant/{tenant_id}")
async def delete_tenant_data(
tenant_id: str,
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""
Delete all recipe-related data for a tenant
Only accessible by internal services (called during tenant deletion)
"""
logger.info(f"Tenant data deletion request received for tenant: {tenant_id}")
# Only allow internal service calls
if current_user.get("type") != "service":
raise HTTPException(
status_code=403,
detail="This endpoint is only accessible to internal services"
)
try:
from app.services.tenant_deletion_service import RecipesTenantDeletionService
deletion_service = RecipesTenantDeletionService(db)
result = await deletion_service.safe_delete_tenant_data(tenant_id)
return {
"message": "Tenant data deletion completed in recipes-service",
"summary": result.to_dict()
}
except Exception as e:
logger.error(f"Tenant data deletion failed for {tenant_id}: {e}")
raise HTTPException(
status_code=500,
detail=f"Failed to delete tenant data: {str(e)}"
)
@router.get("/tenant/{tenant_id}/deletion-preview")
async def preview_tenant_data_deletion(
tenant_id: str,
current_user: dict = Depends(get_current_user_dep),
db: AsyncSession = Depends(get_db)
):
"""
Preview what data would be deleted for a tenant (dry-run)
Accessible by internal services and tenant admins
"""
# 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):
raise HTTPException(
status_code=403,
detail="Insufficient permissions"
)
try:
from app.services.tenant_deletion_service import RecipesTenantDeletionService
deletion_service = RecipesTenantDeletionService(db)
preview = await deletion_service.get_tenant_data_preview(tenant_id)
return {
"tenant_id": tenant_id,
"service": "recipes-service",
"data_counts": preview,
"total_items": sum(preview.values())
}
except Exception as e:
logger.error(f"Deletion preview failed for {tenant_id}: {e}")
raise HTTPException(
status_code=500,
detail=f"Failed to get deletion preview: {str(e)}"
)