# Tenant Deletion System - Quick Reference ## Quick Start ### Test a Service Deletion ```bash # 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 ```bash # 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) ```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 ```bash # 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 ```python # 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 ```python # 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) ```python # 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 ```python .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: - [x] `tenant_deletion_service.py` created - [x] Extends `BaseTenantDataDeletionService` - [x] DELETE endpoint added to API - [x] GET preview endpoint added - [x] Service registered in orchestrator - [x] Tested with real tenant data - [x] Logs show successful deletion --- For detailed information, see [deletion-system.md](deletion-system.md) **Last Updated**: 2025-11-04