Files
service-finder/backend/scripts/system_reset.py

645 lines
28 KiB
Python
Raw Permalink Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
P0 SYSTEM RESET — Deep User Purge & Private Garage Restitution
CRITICAL DIRECTIVE: The Architect has ordered a massive system reset.
We must delete all dummy/test users EXCEPT for explicitly protected IDs
and Admin/Superadmin roles. Afterward, we must ensure every surviving user
has a correctly named Private Garage according to the new naming convention.
NEW NAMING CONVENTION:
"{last_name} {first_name} - Privát Garázs (#{user_id})"
Example: "Gyöngyössy Zsolt - Privát Garázs (#28)"
Phases:
1. THE GREAT USER PURGE — Delete unprotected users and cascade safely
2. RESTORE & RENAME GARAGES — Ensure every survivor has a correctly named private garage
3. VERIFICATION — Report results
Usage:
docker compose exec sf_api python3 /app/scripts/system_reset.py
"""
import asyncio
import logging
import sys
from datetime import datetime, timezone
from sqlalchemy import text
from sqlalchemy.ext.asyncio import AsyncSession, create_async_engine
sys.path.insert(0, '/app')
from app.core.config import settings
# ──────────────────────────────────────────────
# Configuration
# ──────────────────────────────────────────────
# Protected User IDs (never delete these)
PROTECTED_USER_IDS = (100, 88, 86, 85, 7929, 28, 2, 1)
# Protected Roles (never delete users with these roles)
PROTECTED_ROLES = ('SUPERADMIN', 'ADMIN')
# Private free subscription tier ID (from system.subscription_tiers)
PRIVATE_FREE_TIER_ID = 13 # private_free_v1
DATABASE_URL = settings.DATABASE_URL
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s [%(levelname)s] %(message)s",
handlers=[logging.StreamHandler(sys.stdout)],
)
logger = logging.getLogger(__name__)
# =====================================================================
# PHASE 1: THE GREAT USER PURGE
# =====================================================================
async def phase_1_purge_users(db: AsyncSession) -> dict:
"""
Find all users NOT in the Protected List AND NOT in Protected Roles.
Delete them safely with cascade.
"""
logger.info("\n" + "=" * 60)
logger.info("PHASE 1: THE GREAT USER PURGE")
logger.info("=" * 60)
# 1. Identify users to delete
protected_ids_str = ", ".join(str(x) for x in PROTECTED_USER_IDS)
protected_roles_str = ", ".join(f"'{r}'" for r in PROTECTED_ROLES)
find_targets_sql = f"""
SELECT id, email, role
FROM identity.users
WHERE id NOT IN ({protected_ids_str})
AND role NOT IN ({protected_roles_str})
AND is_deleted = false
ORDER BY id
"""
result = await db.execute(text(find_targets_sql))
targets = result.fetchall()
if not targets:
logger.info("✅ No unprotected users found. Nothing to purge.")
return {"deleted_users": 0, "deleted_persons": 0, "target_ids": []}
target_ids = [row[0] for row in targets]
target_ids_str = ", ".join(str(x) for x in target_ids)
logger.info(f"🔍 Found {len(targets)} unprotected users to purge:")
for uid, email, role in targets:
logger.info(f" - ID={uid}: {email} (role={role})")
# 2. Collect all person_ids linked to these users (before deletion)
person_sql = f"""
SELECT DISTINCT person_id FROM identity.users
WHERE id IN ({target_ids_str})
AND person_id IS NOT NULL
"""
person_result = await db.execute(text(person_sql))
person_ids = [row[0] for row in person_result.fetchall()]
person_ids_str = ", ".join(str(x) for x in person_ids) if person_ids else "0"
logger.info(f" Associated person IDs to handle: {person_ids}")
# 3. Delete dependent records in safe order
# 3a. Delete gamification records (CASCADE for some, manual for others)
logger.info("\n🗑️ Deleting gamification records...")
for table in [
"gamification.points_ledger",
"gamification.user_badges",
"gamification.user_scores",
"gamification.user_stats",
"gamification.user_contributions",
]:
try:
del_sql = f"DELETE FROM {table} WHERE user_id IN ({target_ids_str})"
r = await db.execute(text(del_sql))
logger.info(f"{table}: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ {table}: {e}")
# 3b. Delete vehicle ratings
logger.info("\n🗑️ Deleting vehicle_user_ratings...")
try:
r = await db.execute(text(f"DELETE FROM vehicle.vehicle_user_ratings WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ vehicle.vehicle_user_ratings: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ vehicle.vehicle_user_ratings: {e}")
# 3c. Delete service requests
logger.info("\n🗑️ Deleting service_requests...")
try:
r = await db.execute(text(f"DELETE FROM marketplace.service_requests WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ marketplace.service_requests: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ marketplace.service_requests: {e}")
# 3d. NULL out service_reviews
logger.info("\n🗑️ NULLing service_reviews...")
try:
r = await db.execute(text(f"UPDATE marketplace.service_reviews SET user_id = NULL WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ marketplace.service_reviews: {r.rowcount} rows NULLed")
except Exception as e:
logger.warning(f" ⚠️ marketplace.service_reviews: {e}")
# 3e. Delete social accounts
logger.info("\n🗑️ Deleting social_accounts...")
try:
r = await db.execute(text(f"DELETE FROM identity.social_accounts WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ identity.social_accounts: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ identity.social_accounts: {e}")
# 3f. Delete verification tokens
logger.info("\n🗑️ Deleting verification_tokens...")
try:
r = await db.execute(text(f"DELETE FROM identity.verification_tokens WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ identity.verification_tokens: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ identity.verification_tokens: {e}")
# 3g. Delete user_device_links
logger.info("\n🗑️ Deleting user_device_links...")
try:
r = await db.execute(text(f"DELETE FROM identity.user_device_links WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ identity.user_device_links: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ identity.user_device_links: {e}")
# 3h. Delete user_trust_profiles
logger.info("\n🗑️ Deleting user_trust_profiles...")
try:
r = await db.execute(text(f"DELETE FROM identity.user_trust_profiles WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ identity.user_trust_profiles: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ identity.user_trust_profiles: {e}")
# 3i. Delete internal_notifications
logger.info("\n🗑️ Deleting internal_notifications...")
try:
r = await db.execute(text(f"DELETE FROM system.internal_notifications WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ system.internal_notifications: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ system.internal_notifications: {e}")
# 3j. Delete user_subscriptions
logger.info("\n🗑️ Deleting user_subscriptions...")
try:
r = await db.execute(text(f"DELETE FROM finance.user_subscriptions WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ finance.user_subscriptions: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ finance.user_subscriptions: {e}")
# 3k. Delete withdrawal_requests
logger.info("\n🗑️ Deleting withdrawal_requests...")
try:
r = await db.execute(text(f"DELETE FROM finance.withdrawal_requests WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ finance.withdrawal_requests: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ finance.withdrawal_requests: {e}")
# 3l. Delete payment_intents
logger.info("\n🗑️ Deleting payment_intents...")
try:
r = await db.execute(text(f"DELETE FROM finance.payment_intents WHERE payer_id IN ({target_ids_str}) OR beneficiary_id IN ({target_ids_str})"))
logger.info(f" ✅ finance.payment_intents: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ finance.payment_intents: {e}")
# 3m. Delete wallets
logger.info("\n🗑️ Deleting wallets...")
try:
r = await db.execute(text(f"DELETE FROM identity.wallets WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ identity.wallets: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ identity.wallets: {e}")
# 3n. Delete audit_logs (SET NULL handled by DB, but clean up)
logger.info("\n🗑️ NULLing audit_logs...")
try:
r = await db.execute(text(f"UPDATE audit.operational_logs SET user_id = NULL WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ audit.operational_logs: {r.rowcount} rows NULLed")
except Exception as e:
logger.warning(f" ⚠️ audit.operational_logs: {e}")
# 3o. Handle organizations owned by these users
logger.info("\n🗑️ Handling organizations owned by purged users...")
org_sql = f"""
SELECT id, name FROM fleet.organizations
WHERE owner_id IN ({target_ids_str})
"""
org_result = await db.execute(text(org_sql))
owned_orgs = org_result.fetchall()
for org_id, org_name in owned_orgs:
logger.info(f" 🏪 Soft-deleting org ID={org_id}: {org_name}")
# First NULL out owner_id to break FK constraint to identity.users
await db.execute(text("""
UPDATE fleet.organizations
SET owner_id = NULL, is_deleted = true, is_active = false,
status = 'deleted', last_deactivated_at = :now
WHERE id = :oid
"""), {"now": datetime.now(timezone.utc), "oid": org_id})
# Delete org members
await db.execute(text(f"DELETE FROM fleet.organization_members WHERE organization_id = :oid"), {"oid": org_id})
# Delete branches
await db.execute(text(f"DELETE FROM fleet.branches WHERE organization_id = :oid"), {"oid": org_id})
# 3p. Delete organization_members for these users
logger.info("\n🗑️ Deleting organization_members for purged users...")
try:
r = await db.execute(text(f"DELETE FROM fleet.organization_members WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ fleet.organization_members: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ fleet.organization_members: {e}")
# 3q. Delete org_sales_assignments
logger.info("\n🗑️ Deleting org_sales_assignments...")
try:
r = await db.execute(text(f"DELETE FROM fleet.org_sales_assignments WHERE agent_user_id IN ({target_ids_str})"))
logger.info(f" ✅ fleet.org_sales_assignments: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ fleet.org_sales_assignments: {e}")
# 3r. Delete ratings authored by these users
logger.info("\n🗑️ Deleting marketplace ratings...")
try:
r = await db.execute(text(f"DELETE FROM marketplace.ratings WHERE author_id IN ({target_ids_str})"))
logger.info(f" ✅ marketplace.ratings: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ marketplace.ratings: {e}")
# 3s. Delete documents uploaded by these users
logger.info("\n🗑️ NULLing documents...")
try:
r = await db.execute(text(f"UPDATE system.documents SET uploaded_by = NULL WHERE uploaded_by IN ({target_ids_str})"))
logger.info(f" ✅ system.documents: {r.rowcount} rows NULLed")
except Exception as e:
logger.warning(f" ⚠️ system.documents: {e}")
# 3t. Delete pending_actions
logger.info("\n🗑️ Deleting pending_actions...")
try:
r = await db.execute(text(f"DELETE FROM system.pending_actions WHERE requester_id IN ({target_ids_str}) OR approver_id IN ({target_ids_str})"))
logger.info(f" ✅ system.pending_actions: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ system.pending_actions: {e}")
# 3u. Delete vehicle_logbook entries
logger.info("\n🗑️ Deleting vehicle_logbook...")
try:
r = await db.execute(text(f"DELETE FROM vehicle.vehicle_logbook WHERE driver_id IN ({target_ids_str})"))
logger.info(f" ✅ vehicle.vehicle_logbook: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ vehicle.vehicle_logbook: {e}")
# 3v. Delete vehicle_ownership_history
logger.info("\n🗑️ Deleting vehicle_ownership_history...")
try:
r = await db.execute(text(f"DELETE FROM vehicle.vehicle_ownership_history WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ vehicle.vehicle_ownership_history: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ vehicle.vehicle_ownership_history: {e}")
# 3w. Delete vehicle_transfer_requests
logger.info("\n🗑️ Deleting vehicle_transfer_requests...")
try:
r = await db.execute(text(f"DELETE FROM vehicle.vehicle_transfer_requests WHERE requester_id IN ({target_ids_str})"))
logger.info(f" ✅ vehicle.vehicle_transfer_requests: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ vehicle.vehicle_transfer_requests: {e}")
# 3x. Delete asset_inspections
logger.info("\n🗑️ Deleting asset_inspections...")
try:
r = await db.execute(text(f"DELETE FROM vehicle.asset_inspections WHERE inspector_id IN ({target_ids_str})"))
logger.info(f" ✅ vehicle.asset_inspections: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ vehicle.asset_inspections: {e}")
# 3y. Delete asset_reviews
logger.info("\n🗑️ Deleting asset_reviews...")
try:
r = await db.execute(text(f"DELETE FROM vehicle.asset_reviews WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ vehicle.asset_reviews: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ vehicle.asset_reviews: {e}")
# 3z. Delete financial_ledger entries
logger.info("\n🗑️ Deleting financial_ledger...")
try:
r = await db.execute(text(f"DELETE FROM audit.financial_ledger WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ audit.financial_ledger: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ audit.financial_ledger: {e}")
# 3za. Delete security_audit_logs
logger.info("\n🗑️ Deleting security_audit_logs...")
try:
r = await db.execute(text(f"DELETE FROM audit.security_audit_logs WHERE actor_id IN ({target_ids_str})"))
logger.info(f" ✅ audit.security_audit_logs: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ audit.security_audit_logs: {e}")
# 3zb. Delete audit_logs
logger.info("\n🗑️ Deleting audit_logs...")
try:
r = await db.execute(text(f"DELETE FROM audit.audit_logs WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ audit.audit_logs: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ audit.audit_logs: {e}")
# 3zc. Delete service_providers added by these users
logger.info("\n🗑️ NULLing service_providers.added_by_user_id...")
try:
r = await db.execute(text(f"UPDATE marketplace.service_providers SET added_by_user_id = NULL WHERE added_by_user_id IN ({target_ids_str})"))
logger.info(f" ✅ marketplace.service_providers: {r.rowcount} rows NULLed")
except Exception as e:
logger.warning(f" ⚠️ marketplace.service_providers: {e}")
# 3zd. Delete votes
logger.info("\n🗑️ Deleting marketplace.votes...")
try:
r = await db.execute(text(f"DELETE FROM marketplace.votes WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ marketplace.votes: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ marketplace.votes: {e}")
# 3ze. Delete user_scores_deprecated
logger.info("\n🗑️ Deleting user_scores_deprecated...")
try:
r = await db.execute(text(f"DELETE FROM system.user_scores_deprecated WHERE user_id IN ({target_ids_str})"))
logger.info(f" ✅ system.user_scores_deprecated: {r.rowcount} rows deleted")
except Exception as e:
logger.warning(f" ⚠️ system.user_scores_deprecated: {e}")
# 4. Before deleting users, NULL out person.user_id references
# (identity.persons.user_id -> identity.users.id has NO ACTION on delete)
logger.info("\n🔗 NULLing person.user_id references...")
try:
r = await db.execute(text(f"""
UPDATE identity.persons
SET user_id = NULL
WHERE user_id IN ({target_ids_str})
"""))
logger.info(f" ✅ identity.persons.user_id: {r.rowcount} rows NULLed")
except Exception as e:
logger.warning(f" ⚠️ identity.persons.user_id: {e}")
# 5. Finally, delete the users themselves
logger.info("\n💀 Deleting users...")
r = await db.execute(text(f"DELETE FROM identity.users WHERE id IN ({target_ids_str}) RETURNING id, email"))
deleted_users = r.fetchall()
logger.info(f" ✅ Deleted {len(deleted_users)} users:")
for uid, email in deleted_users:
logger.info(f" - ID={uid}: {email}")
# 5. Handle orphaned persons (soft-delete them)
if person_ids:
logger.info("\n👤 Handling orphaned persons...")
for pid in person_ids:
# Check if person is still referenced by any remaining user
check_sql = text("SELECT COUNT(*) FROM identity.users WHERE person_id = :pid AND is_deleted = false")
ref_count = (await db.execute(check_sql, {"pid": pid})).scalar()
if ref_count == 0:
logger.info(f" 👤 Soft-deleting orphaned person ID={pid}")
await db.execute(text("""
UPDATE identity.persons
SET is_active = false, is_ghost = true, deleted_at = :now
WHERE id = :pid
"""), {"now": datetime.now(timezone.utc), "pid": pid})
else:
logger.info(f" Person ID={pid} still referenced by {ref_count} active user(s), keeping")
await db.commit()
return {
"deleted_users": len(deleted_users),
"target_ids": target_ids,
}
# =====================================================================
# PHASE 2: RESTORE & RENAME GARAGES
# =====================================================================
async def phase_2_restore_garages(db: AsyncSession) -> dict:
"""
After the purge, iterate through ALL surviving users (Protected List + Admins).
For each user:
- If they have NO private garage: Create one with the new naming convention.
- If they HAVE a private garage but wrong name: Rename it.
"""
logger.info("\n" + "=" * 60)
logger.info("PHASE 2: RESTORE & RENAME GARAGES")
logger.info("=" * 60)
# Get all surviving users (active, not deleted)
protected_ids_str = ", ".join(str(x) for x in PROTECTED_USER_IDS)
protected_roles_str = ", ".join(f"'{r}'" for r in PROTECTED_ROLES)
survivors_sql = f"""
SELECT u.id, u.email, u.role, u.person_id,
p.last_name, p.first_name
FROM identity.users u
LEFT JOIN identity.persons p ON p.id = u.person_id
WHERE u.is_deleted = false
AND (u.id IN ({protected_ids_str})
OR u.role IN ({protected_roles_str}))
ORDER BY u.id
"""
result = await db.execute(text(survivors_sql))
survivors = result.fetchall()
logger.info(f"🔍 Found {len(survivors)} surviving users to process:")
garages_created = 0
garages_renamed = 0
garages_ok = 0
skipped_no_person = 0
for row in survivors:
user_id = row[0]
email = row[1]
role = row[2]
person_id = row[3]
last_name = row[4] or "Unknown"
first_name = row[5] or "User"
logger.info(f"\n--- Processing User ID={user_id}: {email} (role={role}) ---")
if not person_id:
logger.warning(f" ⚠️ User ID={user_id} has no person record! Skipping garage creation.")
skipped_no_person += 1
continue
# Generate the new naming convention
new_garage_name = f"{last_name} {first_name} - Privát Garázs (#{user_id})"
# Find existing private garages owned by this user
org_sql = text("""
SELECT id, name, full_name, org_type
FROM fleet.organizations
WHERE owner_id = :uid
AND org_type = 'individual'
AND is_deleted = false
ORDER BY id
""")
org_result = await db.execute(org_sql, {"uid": user_id})
existing_orgs = org_result.fetchall()
if not existing_orgs:
# No private garage exists — create one
logger.info(f" 🏗️ No private garage found. Creating: '{new_garage_name}'")
# Generate a secure slug
import hashlib, uuid
folder_slug = hashlib.md5(f"{user_id}-{uuid.uuid4()}".encode()).hexdigest()[:12]
# Create the organization
insert_org_sql = text("""
INSERT INTO fleet.organizations
(full_name, name, display_name, folder_slug, org_type,
owner_id, is_active, status, country_code,
first_registered_at, current_lifecycle_started_at,
created_at, subscription_plan, base_asset_limit,
purchased_extra_slots, notification_settings,
external_integration_config, is_ownership_transferable,
subscription_tier_id)
VALUES
(:fname, :fname, :fname, :slug, 'individual',
:owner, true, 'verified', 'HU',
:now, :now, :now, 'FREE', 1, 0,
'{}'::jsonb, '{}'::jsonb, true,
:tier_id)
RETURNING id
""")
org_insert_result = await db.execute(insert_org_sql, {
"fname": new_garage_name,
"slug": folder_slug,
"owner": user_id,
"now": datetime.now(timezone.utc),
"tier_id": PRIVATE_FREE_TIER_ID,
})
new_org_id = org_insert_result.scalar()
logger.info(f" ✅ Created organization ID={new_org_id}")
# Create a branch (must provide UUID id since raw SQL bypasses ORM default)
branch_id = uuid.uuid4()
await db.execute(text("""
INSERT INTO fleet.branches
(id, organization_id, name, is_main, status, branch_rating, is_deleted, created_at)
VALUES (:bid, :oid, 'Home Base', true, 'active', 0.0, false, :now)
"""), {"bid": branch_id, "oid": new_org_id, "now": datetime.now(timezone.utc)})
# Create organization member (OWNER)
await db.execute(text("""
INSERT INTO fleet.organization_members
(organization_id, user_id, person_id, role,
is_permanent, is_verified, status, created_at)
VALUES (:oid, :uid, :pid, 'OWNER',
true, true, 'active', :now)
"""), {"oid": new_org_id, "uid": user_id, "pid": person_id, "now": datetime.now(timezone.utc)})
# Update user's scope_id
await db.execute(text("""
UPDATE identity.users
SET scope_id = :scope
WHERE id = :uid AND (scope_id IS NULL OR scope_id = '')
"""), {"scope": str(new_org_id), "uid": user_id})
garages_created += 1
else:
# Garage exists — check/rename
for org_id, org_name, org_full_name, org_type in existing_orgs:
if org_full_name != new_garage_name:
logger.info(f" 🔄 Renaming org ID={org_id}: '{org_full_name}' -> '{new_garage_name}'")
await db.execute(text("""
UPDATE fleet.organizations
SET full_name = :new_name, name = :new_name
WHERE id = :oid
"""), {"new_name": new_garage_name, "oid": org_id})
garages_renamed += 1
else:
logger.info(f" ✅ Org ID={org_id} already has correct name: '{org_full_name}'")
garages_ok += 1
await db.commit()
return {
"survivors_count": len(survivors),
"garages_created": garages_created,
"garages_renamed": garages_renamed,
"garages_ok": garages_ok,
"skipped_no_person": skipped_no_person,
}
# =====================================================================
# MAIN
# =====================================================================
async def system_reset():
"""Main execution: Phase 1 (Purge) + Phase 2 (Restore)."""
engine = create_async_engine(DATABASE_URL, echo=False)
async with AsyncSession(engine) as db:
try:
# ── Phase 1: Purge ──
purge_result = await phase_1_purge_users(db)
# ── Phase 2: Restore ──
restore_result = await phase_2_restore_garages(db)
# ── Final Report ──
logger.info("\n" + "=" * 60)
logger.info("📋 FINAL SYSTEM RESET REPORT")
logger.info("=" * 60)
logger.info(f"\n💀 PHASE 1 — USER PURGE:")
logger.info(f" Users deleted: {purge_result['deleted_users']}")
logger.info(f"\n🏪 PHASE 2 — GARAGE RESTITUTION:")
logger.info(f" Survivors processed: {restore_result['survivors_count']}")
logger.info(f" Garages created: {restore_result['garages_created']}")
logger.info(f" Garages renamed: {restore_result['garages_renamed']}")
logger.info(f" Garages already correct: {restore_result['garages_ok']}")
logger.info(f" Skipped (no person record): {restore_result['skipped_no_person']}")
logger.info(f"\n✅ System reset completed successfully!")
return {
"phase_1": purge_result,
"phase_2": restore_result,
}
except Exception as e:
await db.rollback()
logger.error(f"💥 Fatal error during system reset: {e}", exc_info=True)
sys.exit(1)
async def main():
logger.info("🚀 P0 SYSTEM RESET — Deep User Purge & Private Garage Restitution")
logger.info(f" Protected User IDs: {PROTECTED_USER_IDS}")
logger.info(f" Protected Roles: {PROTECTED_ROLES}")
logger.info(f" Database: {DATABASE_URL}")
logger.info("")
try:
await system_reset()
except Exception as e:
logger.error(f"💥 Fatal error: {e}")
sys.exit(1)
if __name__ == "__main__":
asyncio.run(main())