305 lines
12 KiB
Python
305 lines
12 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Ground Zero Database Purge Script
|
|
|
|
Deletes ALL test users (e.g., tester_pro@profibot.hu, noreply@...),
|
|
and cascade deletes all their Organizations, Branches, and Assets.
|
|
Outputs the exact number of deleted rows for each table.
|
|
"""
|
|
|
|
import asyncio
|
|
import sys
|
|
from sqlalchemy import text, delete, select
|
|
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():
|
|
"""Main purge function"""
|
|
# 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
|
|
)
|
|
|
|
deleted_counts = {}
|
|
|
|
async with async_session() as session:
|
|
try:
|
|
# Start transaction
|
|
await session.begin()
|
|
|
|
print("🔍 Starting Ground Zero database purge...")
|
|
print("=" * 60)
|
|
|
|
# 1. First, identify test users to delete
|
|
test_email_patterns = [
|
|
'%test%',
|
|
'%noreply%',
|
|
'%profibot%',
|
|
'tester_pro@profibot.hu',
|
|
'test@example.com',
|
|
'%example.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 for cascade operations
|
|
query = text(f"""
|
|
SELECT id, email
|
|
FROM identity.users
|
|
WHERE {where_clause}
|
|
AND is_deleted = false
|
|
""")
|
|
|
|
result = await session.execute(query)
|
|
test_users = result.fetchall()
|
|
|
|
if not test_users:
|
|
print("✅ No test users found to delete.")
|
|
await session.commit()
|
|
return deleted_counts
|
|
|
|
user_ids = [str(user[0]) for user in test_users]
|
|
user_emails = [user[1] for user in test_users]
|
|
|
|
print(f"📋 Found {len(test_users)} test users to delete:")
|
|
for email in user_emails[:10]: # Show first 10
|
|
print(f" - {email}")
|
|
if len(test_users) > 10:
|
|
print(f" ... and {len(test_users) - 10} more")
|
|
|
|
user_ids_str = ",".join(user_ids)
|
|
|
|
# 3. Delete in reverse cascade order to respect foreign keys
|
|
|
|
# First, get person IDs for these users
|
|
person_query = text(f"""
|
|
SELECT DISTINCT person_id
|
|
FROM identity.users
|
|
WHERE id IN ({user_ids_str})
|
|
AND person_id IS NOT NULL
|
|
""")
|
|
person_result = await session.execute(person_query)
|
|
person_ids = [str(row[0]) for row in person_result.fetchall()]
|
|
person_ids_str = ",".join(person_ids) if person_ids else "NULL"
|
|
|
|
# Delete from tables that reference users/persons
|
|
|
|
# a) Delete from gamification tables
|
|
gamification_tables = [
|
|
("gamification.user_stats", "user_id"),
|
|
("gamification.user_badges", "user_id"),
|
|
("gamification.points_ledger", "user_id"),
|
|
("gamification.user_contributions", "user_id"),
|
|
]
|
|
|
|
for table, column in gamification_tables:
|
|
delete_query = text(f"""
|
|
DELETE FROM {table}
|
|
WHERE {column} IN ({user_ids_str})
|
|
RETURNING *
|
|
""")
|
|
result = await session.execute(delete_query)
|
|
deleted = result.rowcount
|
|
deleted_counts[table] = deleted
|
|
if deleted > 0:
|
|
print(f" 🗑️ Deleted {deleted} rows from {table}")
|
|
|
|
# b) Delete from social tables - with error handling for missing tables
|
|
social_tables = [
|
|
("identity.social_accounts", "user_id"),
|
|
("marketplace.service_requests", "user_id"),
|
|
]
|
|
|
|
# Try to delete from service_reviews if it exists
|
|
social_tables_to_try = social_tables + [("identity.service_reviews", "user_id")]
|
|
|
|
for table, column in social_tables_to_try:
|
|
try:
|
|
delete_query = text(f"""
|
|
DELETE FROM {table}
|
|
WHERE {column} IN ({user_ids_str})
|
|
RETURNING *
|
|
""")
|
|
result = await session.execute(delete_query)
|
|
deleted = result.rowcount
|
|
deleted_counts[table] = 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:
|
|
raise
|
|
|
|
# c) Delete from organization-related tables
|
|
# First get organization IDs owned by these users
|
|
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()]
|
|
org_ids_str = ",".join(org_ids) if org_ids else "NULL"
|
|
|
|
if org_ids:
|
|
# Delete branches for these organizations
|
|
branch_query = text(f"""
|
|
DELETE FROM marketplace.branches
|
|
WHERE organization_id IN ({org_ids_str})
|
|
RETURNING *
|
|
""")
|
|
result = await session.execute(branch_query)
|
|
deleted = result.rowcount
|
|
deleted_counts["marketplace.branches"] = deleted
|
|
if deleted > 0:
|
|
print(f" 🗑️ Deleted {deleted} branches")
|
|
|
|
# Delete organization members
|
|
org_member_query = text(f"""
|
|
DELETE FROM marketplace.organization_members
|
|
WHERE organization_id IN ({org_ids_str})
|
|
RETURNING *
|
|
""")
|
|
result = await session.execute(org_member_query)
|
|
deleted = result.rowcount
|
|
deleted_counts["marketplace.organization_members"] = deleted
|
|
if deleted > 0:
|
|
print(f" 🗑️ Deleted {deleted} organization members")
|
|
|
|
# Delete organizations
|
|
org_delete_query = text(f"""
|
|
DELETE FROM marketplace.organizations
|
|
WHERE id IN ({org_ids_str})
|
|
RETURNING *
|
|
""")
|
|
result = await session.execute(org_delete_query)
|
|
deleted = result.rowcount
|
|
deleted_counts["marketplace.organizations"] = deleted
|
|
if deleted > 0:
|
|
print(f" 🗑️ Deleted {deleted} organizations")
|
|
|
|
# d) Delete from asset-related tables
|
|
# Get asset IDs owned by these users
|
|
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()]
|
|
asset_ids_str = ",".join(asset_ids) if asset_ids else "NULL"
|
|
|
|
if asset_ids:
|
|
# Delete asset costs
|
|
asset_cost_query = text(f"""
|
|
DELETE FROM data.asset_costs
|
|
WHERE asset_id IN ({asset_ids_str})
|
|
RETURNING *
|
|
""")
|
|
result = await session.execute(asset_cost_query)
|
|
deleted = result.rowcount
|
|
deleted_counts["data.asset_costs"] = deleted
|
|
if deleted > 0:
|
|
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})
|
|
RETURNING *
|
|
""")
|
|
result = await session.execute(asset_event_query)
|
|
deleted = result.rowcount
|
|
deleted_counts["data.asset_events"] = deleted
|
|
if deleted > 0:
|
|
print(f" 🗑️ Deleted {deleted} asset events")
|
|
|
|
# Delete assets
|
|
asset_delete_query = text(f"""
|
|
DELETE FROM data.assets
|
|
WHERE id IN ({asset_ids_str})
|
|
RETURNING *
|
|
""")
|
|
result = await session.execute(asset_delete_query)
|
|
deleted = result.rowcount
|
|
deleted_counts["data.assets"] = deleted
|
|
if deleted > 0:
|
|
print(f" 🗑️ Deleted {deleted} assets")
|
|
|
|
# e) Delete wallets
|
|
wallet_query = text(f"""
|
|
DELETE FROM identity.wallets
|
|
WHERE user_id IN ({user_ids_str})
|
|
RETURNING *
|
|
""")
|
|
result = await session.execute(wallet_query)
|
|
deleted = result.rowcount
|
|
deleted_counts["identity.wallets"] = deleted
|
|
if deleted > 0:
|
|
print(f" 🗑️ Deleted {deleted} wallets")
|
|
|
|
# f) Finally delete users
|
|
user_delete_query = text(f"""
|
|
DELETE FROM identity.users
|
|
WHERE id IN ({user_ids_str})
|
|
RETURNING *
|
|
""")
|
|
result = await session.execute(user_delete_query)
|
|
deleted = result.rowcount
|
|
deleted_counts["identity.users"] = deleted
|
|
print(f" 🗑️ Deleted {deleted} users")
|
|
|
|
# g) Delete persons (if they have no other users)
|
|
if person_ids:
|
|
person_delete_query = text(f"""
|
|
DELETE FROM identity.persons
|
|
WHERE id IN ({person_ids_str})
|
|
AND NOT EXISTS (
|
|
SELECT 1 FROM identity.users
|
|
WHERE person_id = persons.id
|
|
)
|
|
RETURNING *
|
|
""")
|
|
result = await session.execute(person_delete_query)
|
|
deleted = result.rowcount
|
|
deleted_counts["identity.persons"] = deleted
|
|
if deleted > 0:
|
|
print(f" 🗑️ Deleted {deleted} persons")
|
|
|
|
# Commit transaction
|
|
await session.commit()
|
|
|
|
print("=" * 60)
|
|
print("✅ Ground Zero purge completed successfully!")
|
|
print("\n📊 Deletion Summary:")
|
|
for table, count in deleted_counts.items():
|
|
print(f" {table}: {count} rows")
|
|
|
|
total_deleted = sum(deleted_counts.values())
|
|
print(f"\n📈 Total rows deleted: {total_deleted}")
|
|
|
|
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()) |