jogosultsági szintek RBAC beállítva tesztelve

This commit is contained in:
Roo
2026-06-25 20:41:49 +00:00
parent 52011606ff
commit 36109fc722
242 changed files with 8672 additions and 2237 deletions

View File

@@ -0,0 +1,644 @@
#!/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())