#!/usr/bin/env python3 """ Final Ground Zero Database Purge Script Deletes ALL test users in correct cascade order. """ import asyncio import sys from sqlalchemy import text from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession from sqlalchemy.orm import sessionmaker # Import settings sys.path.insert(0, '/app') from app.core.config import settings async def purge_test_data_final(): """Final purge function with proper cascade order""" # Create async engine engine = create_async_engine( str(settings.SQLALCHEMY_DATABASE_URI), echo=False, pool_size=5, max_overflow=10, ) async_session = sessionmaker( engine, class_=AsyncSession, expire_on_commit=False ) async with async_session() as session: try: print("šŸ” Starting Ground Zero database purge (final)...") print("=" * 60) # Start transaction await session.begin() # 1. First, identify test users to delete test_email_patterns = [ '%test%', '%noreply%', '%profibot%', 'tester_pro@profibot.hu', 'test@example.com', '%example.com', '%@gmail.com' ] # Build WHERE clause for test users where_conditions = [] for pattern in test_email_patterns: where_conditions.append(f"email ILIKE '{pattern}'") where_clause = " OR ".join(where_conditions) # 2. Get user IDs to delete get_users_query = text(f""" SELECT id, email FROM identity.users WHERE {where_clause} AND is_deleted = false ORDER BY id """) result = await session.execute(get_users_query) test_users = result.fetchall() if not test_users: print("āœ… No test users found to delete.") await session.commit() return user_ids = [str(user[0]) for user in test_users] user_emails = [user[1] for user in test_users] user_ids_str = ",".join(user_ids) print(f"šŸ“‹ Found {len(test_users)} test users to delete:") for email in user_emails[:10]: print(f" - {email}") if len(test_users) > 10: print(f" ... and {len(test_users) - 10} more") # 3. Delete in correct cascade order # a) First delete from tables that reference users but have no further dependencies print("\nšŸ—‘ļø Deleting related records in cascade order...") # Tables to delete (in order of dependencies) tables_to_clean = [ # Gamification tables ("gamification.user_stats", "user_id"), ("gamification.user_badges", "user_id"), ("gamification.points_ledger", "user_id"), ("gamification.user_contributions", "user_id"), # Social tables (skip if they don't exist) ("identity.social_accounts", "user_id"), ("marketplace.service_requests", "user_id"), # Organization members first (before organizations) ("marketplace.organization_members", "user_id"), # Payment intents ("marketplace.payment_intents", "payer_id"), ("marketplace.payment_intents", "beneficiary_id"), # Withdrawal requests ("marketplace.withdrawal_requests", "user_id"), # Service reviews (if table exists) ("identity.service_reviews", "user_id"), # Vehicle ratings ("vehicle.vehicle_user_ratings", "user_id"), # Vehicle ownership ("vehicle.vehicle_ownership", "user_id"), ] deleted_counts = {} for table, column in tables_to_clean: try: # Check if table exists by trying to count check_query = text(f""" SELECT EXISTS ( SELECT FROM information_schema.tables WHERE table_schema = '{table.split('.')[0]}' AND table_name = '{table.split('.')[1]}' ) """) exists_result = await session.execute(check_query) exists = exists_result.scalar() if not exists: print(f" āš ļø Table {table} doesn't exist, skipping...") continue delete_query = text(f""" DELETE FROM {table} WHERE {column} IN ({user_ids_str}) """) result = await session.execute(delete_query) deleted = result.rowcount deleted_counts[f"{table}.{column}"] = deleted if deleted > 0: print(f" āœ… Deleted {deleted} rows from {table}") except Exception as e: if "does not exist" in str(e) or "UndefinedTableError" in str(e): print(f" āš ļø Table {table} doesn't exist, skipping...") continue else: print(f" āš ļø Error deleting from {table}: {e}") continue # b) Delete wallets (references users) try: wallet_query = text(f""" DELETE FROM identity.wallets WHERE user_id IN ({user_ids_str}) """) result = await session.execute(wallet_query) deleted = result.rowcount deleted_counts["identity.wallets"] = deleted print(f" āœ… Deleted {deleted} wallets") except Exception as e: print(f" āš ļø Error deleting wallets: {e}") # c) Delete organizations owned by these users try: # Get organization IDs org_query = text(f""" SELECT id FROM marketplace.organizations WHERE owner_id IN ({user_ids_str}) """) org_result = await session.execute(org_query) org_ids = [str(row[0]) for row in org_result.fetchall()] if org_ids: org_ids_str = ",".join(org_ids) # Delete branches for these organizations branch_query = text(f""" DELETE FROM marketplace.branches WHERE organization_id IN ({org_ids_str}) """) result = await session.execute(branch_query) deleted = result.rowcount deleted_counts["marketplace.branches"] = deleted print(f" āœ… Deleted {deleted} branches") # Delete organization members for these organizations org_member_query = text(f""" DELETE FROM marketplace.organization_members WHERE organization_id IN ({org_ids_str}) """) result = await session.execute(org_member_query) deleted = result.rowcount deleted_counts["marketplace.organization_members_org"] = deleted print(f" āœ… Deleted {deleted} organization members (by org)") # Delete organizations org_delete_query = text(f""" DELETE FROM marketplace.organizations WHERE id IN ({org_ids_str}) """) result = await session.execute(org_delete_query) deleted = result.rowcount deleted_counts["marketplace.organizations"] = deleted print(f" āœ… Deleted {deleted} organizations") except Exception as e: print(f" āš ļø Error deleting organizations: {e}") # d) Delete assets owned by these users try: # Get asset IDs asset_query = text(f""" SELECT id FROM data.assets WHERE owner_id IN ({user_ids_str}) """) asset_result = await session.execute(asset_query) asset_ids = [str(row[0]) for row in asset_result.fetchall()] if asset_ids: asset_ids_str = ",".join(asset_ids) # Delete asset costs asset_cost_query = text(f""" DELETE FROM data.asset_costs WHERE asset_id IN ({asset_ids_str}) """) result = await session.execute(asset_cost_query) deleted = result.rowcount deleted_counts["data.asset_costs"] = deleted print(f" āœ… Deleted {deleted} asset costs") # Delete asset events asset_event_query = text(f""" DELETE FROM data.asset_events WHERE asset_id IN ({asset_ids_str}) """) result = await session.execute(asset_event_query) deleted = result.rowcount deleted_counts["data.asset_events"] = deleted print(f" āœ… Deleted {deleted} asset events") # Delete assets asset_delete_query = text(f""" DELETE FROM data.assets WHERE id IN ({asset_ids_str}) """) result = await session.execute(asset_delete_query) deleted = result.rowcount deleted_counts["data.assets"] = deleted print(f" āœ… Deleted {deleted} assets") except Exception as e: print(f" āš ļø Error deleting assets: {e}") # e) Finally delete users print("\nšŸ—‘ļø Deleting test users...") user_delete_query = text(f""" DELETE FROM identity.users WHERE id IN ({user_ids_str}) AND is_deleted = false """) result = await session.execute(user_delete_query) deleted_users = result.rowcount deleted_counts["identity.users"] = deleted_users print(f" āœ… Deleted {deleted_users} users") # f) Delete orphaned persons person_delete_query = text(""" DELETE FROM identity.persons WHERE NOT EXISTS ( SELECT 1 FROM identity.users WHERE users.person_id = persons.id ) """) result = await session.execute(person_delete_query) deleted_persons = result.rowcount deleted_counts["identity.persons"] = deleted_persons if deleted_persons > 0: print(f" āœ… Deleted {deleted_persons} orphaned persons") # Commit transaction await session.commit() print("=" * 60) print("āœ… Ground Zero purge completed successfully!") print("\nšŸ“Š Deletion Summary:") total_deleted = 0 for key, count in deleted_counts.items(): print(f" {key}: {count} rows") total_deleted += count print(f"\nšŸ“ˆ Total rows deleted: {total_deleted}") # Verify cleanup print("\nšŸ” Verifying cleanup...") verify_query = text(f""" SELECT COUNT(*) FROM identity.users WHERE {where_clause} AND is_deleted = false """) result = await session.execute(verify_query) remaining = result.scalar() if remaining == 0: print(f"āœ… Verification: All test users have been removed.") else: print(f"āš ļø Verification: {remaining} test users still remain.") return deleted_counts except Exception as e: await session.rollback() print(f"āŒ Error during purge: {e}") raise if __name__ == "__main__": asyncio.run(purge_test_data_final())