Files
bakery-ia/docs/03-features/tenant-management/deletion-quick-reference.md
2025-11-05 13:34:56 +01:00

8.8 KiB

Tenant Deletion System - Quick Reference

Quick Start

Test a Service Deletion

# Step 1: Preview what will be deleted (dry-run)
curl -X GET "http://localhost:8000/api/v1/pos/tenant/YOUR_TENANT_ID/deletion-preview" \
  -H "Authorization: Bearer YOUR_SERVICE_TOKEN"

# Step 2: Execute deletion
curl -X DELETE "http://localhost:8000/api/v1/pos/tenant/YOUR_TENANT_ID" \
  -H "Authorization: Bearer YOUR_SERVICE_TOKEN"

Delete a Tenant

# Requires admin token and verifies no other admins exist
curl -X DELETE "http://localhost:8000/api/v1/tenants/YOUR_TENANT_ID" \
  -H "Authorization: Bearer YOUR_ADMIN_TOKEN"

Use the Orchestrator (Python)

from services.auth.app.services.deletion_orchestrator import DeletionOrchestrator

# Initialize
orchestrator = DeletionOrchestrator(auth_token="service_jwt")

# Execute parallel deletion across all services
job = await orchestrator.orchestrate_tenant_deletion(
    tenant_id="abc-123",
    tenant_name="Bakery XYZ",
    initiated_by="admin-user-456"
)

# Check results
print(f"Status: {job.status}")
print(f"Deleted: {job.total_items_deleted} items")
print(f"Services completed: {job.services_completed}/12")

Service Endpoints

All services follow the same pattern:

Endpoint Method Auth Purpose
/tenant/{tenant_id}/deletion-preview GET Service Preview counts (dry-run)
/tenant/{tenant_id} DELETE Service Permanent deletion

Full URLs by Service

# Core Business Services
http://orders-service:8000/api/v1/orders/tenant/{tenant_id}
http://inventory-service:8000/api/v1/inventory/tenant/{tenant_id}
http://recipes-service:8000/api/v1/recipes/tenant/{tenant_id}
http://sales-service:8000/api/v1/sales/tenant/{tenant_id}
http://production-service:8000/api/v1/production/tenant/{tenant_id}
http://suppliers-service:8000/api/v1/suppliers/tenant/{tenant_id}

# Integration Services
http://pos-service:8000/api/v1/pos/tenant/{tenant_id}
http://external-service:8000/api/v1/external/tenant/{tenant_id}

# AI/ML Services
http://forecasting-service:8000/api/v1/forecasting/tenant/{tenant_id}
http://training-service:8000/api/v1/training/tenant/{tenant_id}

# Alert/Notification Services
http://alert-processor-service:8000/api/v1/alerts/tenant/{tenant_id}
http://notification-service:8000/api/v1/notifications/tenant/{tenant_id}

Implementation Pattern

Creating a New Deletion Service

# 1. Create tenant_deletion_service.py
from shared.services.tenant_deletion import (
    BaseTenantDataDeletionService,
    TenantDataDeletionResult
)

class MyServiceTenantDeletionService(BaseTenantDataDeletionService):
    def __init__(self, db: AsyncSession):
        super().__init__("my-service")
        self.db = db

    async def get_tenant_data_preview(self, tenant_id: str) -> Dict[str, int]:
        # Return counts without deleting
        count = await self.db.scalar(
            select(func.count(MyModel.id)).where(MyModel.tenant_id == tenant_id)
        )
        return {"my_table": count or 0}

    async def delete_tenant_data(self, tenant_id: str) -> TenantDataDeletionResult:
        result = TenantDataDeletionResult(tenant_id, self.service_name)
        try:
            # Delete children before parents
            delete_stmt = delete(MyModel).where(MyModel.tenant_id == tenant_id)
            result_proxy = await self.db.execute(delete_stmt)
            result.add_deleted_items("my_table", result_proxy.rowcount)

            await self.db.commit()
        except Exception as e:
            await self.db.rollback()
            result.add_error(f"Deletion failed: {str(e)}")

        return result

Adding API Endpoints

# 2. Add to your API router
@router.delete("/tenant/{tenant_id}")
@service_only_access
async def delete_tenant_data(
    tenant_id: str = Path(...),
    current_user: dict = Depends(get_current_user_dep),
    db: AsyncSession = Depends(get_db)
):
    deletion_service = MyServiceTenantDeletionService(db)
    result = await deletion_service.safe_delete_tenant_data(tenant_id)

    if not result.success:
        raise HTTPException(500, detail=f"Deletion failed: {result.errors}")

    return {"message": "Success", "summary": result.to_dict()}

@router.get("/tenant/{tenant_id}/deletion-preview")
async def preview_tenant_deletion(
    tenant_id: str = Path(...),
    current_user: dict = Depends(get_current_user_dep),
    db: AsyncSession = Depends(get_db)
):
    deletion_service = MyServiceTenantDeletionService(db)
    preview = await deletion_service.get_tenant_data_preview(tenant_id)

    return {
        "tenant_id": tenant_id,
        "service": "my-service",
        "data_counts": preview,
        "total_items": sum(preview.values())
    }

Deletion Order (Foreign Keys)

# Always delete in this order:
# 1. Child records (with foreign keys)
# 2. Parent records (referenced by children)
# 3. Independent records (no foreign keys)
# 4. Audit logs (last)

# Example:
await self.db.execute(delete(OrderItem).where(...))    # Child
await self.db.execute(delete(Order).where(...))         # Parent
await self.db.execute(delete(Customer).where(...))      # Parent
await self.db.execute(delete(AuditLog).where(...))      # Independent

Troubleshooting

Foreign Key Constraint Error

Problem: Error when deleting parent before child records Solution: Check deletion order - delete children before parents Fix: Review the delete() statements in delete_tenant_data()

Service Returns 401 Unauthorized

Problem: Endpoint rejects valid token Solution: Endpoint requires service token, not user token Fix: Use @service_only_access decorator and service JWT

Deletion Count is Zero

Problem: No records deleted even though they exist Solution: tenant_id column might be UUID vs string mismatch Fix: Use UUID(tenant_id) in WHERE clause

.where(Model.tenant_id == UUID(tenant_id))

Orchestrator Can't Reach Service

Problem: Service not responding to deletion request Solution: Check service URL in SERVICE_DELETION_ENDPOINTS Fix: Ensure service name matches Kubernetes service name Example: "orders-service" not "orders"

Key Files

Base Infrastructure

services/shared/services/tenant_deletion.py          # Base classes
services/auth/app/services/deletion_orchestrator.py  # Orchestrator

Service Implementations (12 Services)

services/orders/app/services/tenant_deletion_service.py
services/inventory/app/services/tenant_deletion_service.py
services/recipes/app/services/tenant_deletion_service.py
services/sales/app/services/tenant_deletion_service.py
services/production/app/services/tenant_deletion_service.py
services/suppliers/app/services/tenant_deletion_service.py
services/pos/app/services/tenant_deletion_service.py
services/external/app/services/tenant_deletion_service.py
services/forecasting/app/services/tenant_deletion_service.py
services/training/app/services/tenant_deletion_service.py
services/alert_processor/app/services/tenant_deletion_service.py
services/notification/app/services/tenant_deletion_service.py

Data Deletion Summary

Service Main Tables Typical Count
Orders Customers, Orders, Items 1,000-10,000
Inventory Products, Stock Movements 500-2,000
Recipes Recipes, Ingredients, Steps 100-500
Sales Sales Records, Predictions 5,000-50,000
Production Production Runs, Steps 500-5,000
Suppliers Suppliers, Orders, Contracts 100-1,000
POS Transactions, Items, Logs 10,000-100,000
External Tenant Weather Data 100-1,000
Forecasting Forecasts, Batches, Cache 5,000-50,000
Training Models, Artifacts, Logs 1,000-10,000
Alert Processor Alerts, Interactions 1,000-10,000
Notification Notifications, Preferences 5,000-50,000

Total Typical Deletion: 25,000-250,000 records per tenant

Important Reminders

Security

  • All deletion endpoints require @service_only_access
  • Tenant endpoint checks for admin permissions
  • User deletion verifies ownership before tenant deletion

Data Integrity

  • Always use database transactions
  • Delete children before parents (foreign keys)
  • Track deletion counts for audit
  • Log every step with structlog

Testing

  • Always test preview endpoint first (dry-run)
  • Test with small tenant before large ones
  • Verify counts match expected values
  • Check logs for errors

Success Criteria

Service is Complete When:

  • tenant_deletion_service.py created
  • Extends BaseTenantDataDeletionService
  • DELETE endpoint added to API
  • GET preview endpoint added
  • Service registered in orchestrator
  • Tested with real tenant data
  • Logs show successful deletion

For detailed information, see deletion-system.md

Last Updated: 2025-11-04