242 lines
9.3 KiB
Python
242 lines
9.3 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Hard Delete Cleanup Script — Cascade törlés User és kapcsolódó rekordokra.
|
|
|
|
Használat:
|
|
docker compose exec sf_api python3 /app/backend/app/scripts/cleanup_user.py --email %test%
|
|
|
|
Az --email paraméter részleges egyezést is támogat (SQL LIKE, pl. %test%@%.
|
|
A szkript tranzakcióban törli:
|
|
- User rekordot
|
|
- Person rekordot (ha a User volt az egyetlen kapcsolódó)
|
|
- Organization rekordot (ha a User volt az egyetlen owner)
|
|
- Session tokeneket, SocialAccount-okat, Wallet-eket, stb.
|
|
"""
|
|
|
|
import argparse
|
|
import logging
|
|
import sys
|
|
|
|
from sqlalchemy import select, delete, text
|
|
from sqlalchemy.orm import joinedload
|
|
|
|
# Projekt importok
|
|
from app.database import async_session_factory
|
|
from app.models.identity import User, Person, UserRole
|
|
from app.models.identity.identity import (
|
|
SocialAccount, Wallet, ActiveVoucher, VerificationToken,
|
|
UserDeviceLink, UserTrustProfile, OneTimePassword,
|
|
)
|
|
from app.models.gamification.gamification import UserStats
|
|
from app.models.marketplace.service_request import ServiceRequest
|
|
from app.models.social import ServiceReview
|
|
from app.models.vehicle.asset import VehicleOwnership
|
|
|
|
logging.basicConfig(
|
|
level=logging.INFO,
|
|
format="%(asctime)s [%(levelname)s] %(message)s",
|
|
datefmt="%Y-%m-%d %H:%M:%S",
|
|
)
|
|
logger = logging.getLogger("cleanup_user")
|
|
|
|
|
|
async def cleanup_user(email_pattern: str) -> None:
|
|
"""
|
|
Megkeresi a megadott email mintára illeszkedő usereket,
|
|
és cascade hard delete-et hajt végre rajtuk.
|
|
"""
|
|
async with async_session_factory() as db:
|
|
# 1. Keresés részleges email egyezéssel
|
|
stmt = (
|
|
select(User)
|
|
.where(User.email.ilike(email_pattern))
|
|
.options(
|
|
joinedload(User.person),
|
|
joinedload(User.stats),
|
|
joinedload(User.wallet),
|
|
joinedload(User.trust_profile),
|
|
joinedload(User.ownership_history),
|
|
joinedload(User.social_accounts),
|
|
joinedload(User.service_reviews),
|
|
joinedload(User.service_requests),
|
|
)
|
|
)
|
|
result = await db.execute(stmt)
|
|
users = result.unique().scalars().all()
|
|
|
|
if not users:
|
|
logger.warning("❌ Nem található user a '%s' mintára.", email_pattern)
|
|
return
|
|
|
|
logger.info("🔍 Talált userek (%d db):", len(users))
|
|
for u in users:
|
|
logger.info(" - ID=%d, Email=%s, Role=%s", u.id, u.email, u.role.value if hasattr(u.role, 'value') else u.role)
|
|
|
|
# 2. Tranzakció indítása
|
|
async with db.begin():
|
|
for user in users:
|
|
user_id = user.id
|
|
email = user.email
|
|
logger.info("🗑️ Törlés: User ID=%d, Email=%s", user_id, email)
|
|
|
|
# --- Cascade törlés ---
|
|
|
|
# UserDeviceLink
|
|
await db.execute(
|
|
delete(UserDeviceLink).where(UserDeviceLink.user_id == user_id)
|
|
)
|
|
logger.info(" ✓ UserDeviceLink törölve")
|
|
|
|
# VerificationToken
|
|
await db.execute(
|
|
delete(VerificationToken).where(VerificationToken.user_id == user_id)
|
|
)
|
|
logger.info(" ✓ VerificationToken törölve")
|
|
|
|
# OneTimePassword
|
|
await db.execute(
|
|
delete(OneTimePassword).where(OneTimePassword.email == email)
|
|
)
|
|
logger.info(" ✓ OneTimePassword törölve")
|
|
|
|
# SocialAccount (cascade all, delete-orphan)
|
|
await db.execute(
|
|
delete(SocialAccount).where(SocialAccount.user_id == user_id)
|
|
)
|
|
logger.info(" ✓ SocialAccount törölve")
|
|
|
|
# UserTrustProfile (cascade all, delete-orphan)
|
|
await db.execute(
|
|
delete(UserTrustProfile).where(UserTrustProfile.user_id == user_id)
|
|
)
|
|
logger.info(" ✓ UserTrustProfile törölve")
|
|
|
|
# UserStats (gamification)
|
|
await db.execute(
|
|
delete(UserStats).where(UserStats.user_id == user_id)
|
|
)
|
|
logger.info(" ✓ UserStats törölve")
|
|
|
|
# Wallet + ActiveVoucher (cascade)
|
|
wallet_stmt = select(Wallet).where(Wallet.user_id == user_id)
|
|
wallet_result = await db.execute(wallet_stmt)
|
|
wallet = wallet_result.scalar_one_or_none()
|
|
if wallet:
|
|
await db.execute(
|
|
delete(ActiveVoucher).where(ActiveVoucher.wallet_id == wallet.id)
|
|
)
|
|
await db.execute(
|
|
delete(Wallet).where(Wallet.id == wallet.id)
|
|
)
|
|
logger.info(" ✓ Wallet + ActiveVoucher törölve")
|
|
|
|
# ServiceReview
|
|
await db.execute(
|
|
delete(ServiceReview).where(ServiceReview.user_id == user_id)
|
|
)
|
|
logger.info(" ✓ ServiceReview törölve")
|
|
|
|
# ServiceRequest
|
|
await db.execute(
|
|
delete(ServiceRequest).where(ServiceRequest.user_id == user_id)
|
|
)
|
|
logger.info(" ✓ ServiceRequest törölve")
|
|
|
|
# VehicleOwnership
|
|
await db.execute(
|
|
delete(VehicleOwnership).where(VehicleOwnership.user_id == user_id)
|
|
)
|
|
logger.info(" ✓ VehicleOwnership törölve")
|
|
|
|
# Person rekord kezelése
|
|
person = user.person
|
|
if person:
|
|
# Ellenőrizzük, hogy más User nem hivatkozik-e erre a Person-ra
|
|
other_users_stmt = (
|
|
select(User)
|
|
.where(User.person_id == person.id, User.id != user_id)
|
|
.limit(1)
|
|
)
|
|
other_result = await db.execute(other_users_stmt)
|
|
other_user = other_result.scalar_one_or_none()
|
|
|
|
if not other_user:
|
|
# Nincs más User ehhez a Person-hoz -> törölhető
|
|
await db.execute(
|
|
delete(Person).where(Person.id == person.id)
|
|
)
|
|
logger.info(" ✓ Person ID=%d törölve (egyedüli kapcsolat)", person.id)
|
|
else:
|
|
logger.info(
|
|
" ⚠️ Person ID=%d MEGTARTVA (más User is hivatkozik rá: ID=%d)",
|
|
person.id, other_user.id
|
|
)
|
|
|
|
# Organization kezelése (ha a User volt az egyetlen owner)
|
|
from app.models.identity.organization import Organization
|
|
org_stmt = (
|
|
select(Organization)
|
|
.where(Organization.owner_id == user_id)
|
|
)
|
|
org_result = await db.execute(org_stmt)
|
|
owned_orgs = org_result.scalars().all()
|
|
|
|
for org in owned_orgs:
|
|
# Ellenőrizzük, hogy más User nem owner-e
|
|
other_owner_stmt = (
|
|
select(User)
|
|
.where(User.id != user_id, User.id.isnot(None))
|
|
.limit(1)
|
|
)
|
|
# Egyszerűbb: csak töröljük, ha nincs más owner
|
|
# (a gyakorlatban az Organization-nak lehet több tagja)
|
|
from app.models.identity.organization import OrganizationMember
|
|
member_stmt = (
|
|
select(OrganizationMember)
|
|
.where(
|
|
OrganizationMember.organization_id == org.id,
|
|
OrganizationMember.user_id != user_id,
|
|
)
|
|
.limit(1)
|
|
)
|
|
member_result = await db.execute(member_stmt)
|
|
other_member = member_result.scalar_one_or_none()
|
|
|
|
if not other_member:
|
|
await db.execute(
|
|
delete(Organization).where(Organization.id == org.id)
|
|
)
|
|
logger.info(" ✓ Organization ID=%d törölve (egyedüli owner)", org.id)
|
|
else:
|
|
logger.info(
|
|
" ⚠️ Organization ID=%d MEGTARTVA (van más tag)",
|
|
org.id
|
|
)
|
|
|
|
# VÉGÜL: Maga a User rekord törlése
|
|
await db.execute(
|
|
delete(User).where(User.id == user_id)
|
|
)
|
|
logger.info(" ✅ User ID=%d (%s) törölve.", user_id, email)
|
|
|
|
logger.info("🎉 Tranzakció sikeresen végrehajtva.")
|
|
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(
|
|
description="Hard Delete Cleanup — User és kapcsolódó rekordok cascade törlése."
|
|
)
|
|
parser.add_argument(
|
|
"--email",
|
|
required=True,
|
|
help="Email minta (SQL LIKE, pl. %%test%% vagy teszt%%).",
|
|
)
|
|
args = parser.parse_args()
|
|
|
|
import asyncio
|
|
asyncio.run(cleanup_user(args.email))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|