fix(admin): delete Remnawave user with bot account
This commit is contained in:
@@ -1,5 +1,16 @@
|
||||
# ruff: noqa: F401,F403,F405,I001
|
||||
from ._runtime import * # noqa: F403,F405
|
||||
from .auth import _require_admin_user_id
|
||||
from .common import (
|
||||
_build_admin_webapp_referral_link,
|
||||
_error,
|
||||
_ok,
|
||||
_premium_traffic_list_payload,
|
||||
_read_json,
|
||||
_serialize_payment,
|
||||
_serialize_subscription,
|
||||
_serialize_user,
|
||||
)
|
||||
|
||||
import hashlib
|
||||
from html import escape as html_escape
|
||||
@@ -861,18 +872,62 @@ async def admin_user_delete_route(request: web.Request) -> web.Response:
|
||||
target_id = int(request.match_info["user_id"])
|
||||
|
||||
settings: Settings = request.app["settings"]
|
||||
panel_service = request.app.get("panel_service")
|
||||
if panel_service is None:
|
||||
subscription_service = request.app.get("subscription_service")
|
||||
panel_service = getattr(subscription_service, "panel_service", None)
|
||||
async_session_factory: sessionmaker = request.app["async_session_factory"]
|
||||
async with async_session_factory() as session:
|
||||
user = await user_dal.get_user_by_id(session, target_id)
|
||||
if not user:
|
||||
return _error(404, "not_found")
|
||||
|
||||
panel_user_uuids = await user_dal.get_panel_user_uuids_for_user(
|
||||
session,
|
||||
target_id,
|
||||
user=user,
|
||||
)
|
||||
if panel_user_uuids and panel_service is None:
|
||||
await session.rollback()
|
||||
return _error(503, "panel_service_unavailable")
|
||||
|
||||
for panel_uuid in panel_user_uuids:
|
||||
try:
|
||||
panel_deleted = await panel_service.delete_user_from_panel(
|
||||
panel_uuid,
|
||||
log_response=False,
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.warning(
|
||||
"Admin webapp failed to delete panel user %s for user %s: %s",
|
||||
panel_uuid,
|
||||
target_id,
|
||||
exc,
|
||||
)
|
||||
await session.rollback()
|
||||
return _error(502, "panel_delete_failed", str(exc))
|
||||
|
||||
if not panel_deleted:
|
||||
await session.rollback()
|
||||
return _error(
|
||||
502,
|
||||
"panel_delete_failed",
|
||||
f"Failed to delete panel user {panel_uuid}",
|
||||
)
|
||||
|
||||
ok = await user_dal.delete_user_and_relations(session, target_id)
|
||||
if not ok:
|
||||
await session.rollback()
|
||||
return _error(404, "not_found")
|
||||
await message_log_dal.create_message_log(
|
||||
await message_log_dal.create_message_log_no_commit(
|
||||
session,
|
||||
{
|
||||
"user_id": actor_id,
|
||||
"user_id": actor_id if actor_id != target_id else None,
|
||||
"event_type": "admin_delete_user_webapp",
|
||||
"content": f"Deleted user_id={target_id}",
|
||||
"content": (
|
||||
f"Deleted user_id={target_id}; "
|
||||
f"panel_uuids={','.join(panel_user_uuids) or 'none'}"
|
||||
),
|
||||
"is_admin_event": True,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -1264,8 +1264,13 @@ async def process_delete_user_confirmation_handler(
|
||||
return
|
||||
|
||||
try:
|
||||
if user_model.panel_user_uuid:
|
||||
panel_deleted = await panel_service.delete_user_from_panel(user_model.panel_user_uuid)
|
||||
panel_user_uuids = await user_dal.get_panel_user_uuids_for_user(
|
||||
session,
|
||||
target_user_id,
|
||||
user=user_model,
|
||||
)
|
||||
for panel_uuid in panel_user_uuids:
|
||||
panel_deleted = await panel_service.delete_user_from_panel(panel_uuid)
|
||||
if not panel_deleted:
|
||||
await message.answer(
|
||||
_(
|
||||
|
||||
@@ -12,10 +12,17 @@ from sqlalchemy.orm import aliased
|
||||
|
||||
from ..models import (
|
||||
AdAttribution,
|
||||
EmailVerificationCode,
|
||||
HwidDevicePurchase,
|
||||
MessageLog,
|
||||
Payment,
|
||||
PromoCodeActivation,
|
||||
Subscription,
|
||||
SupportTicket,
|
||||
SupportTicketMessage,
|
||||
TariffChange,
|
||||
TrafficTopup,
|
||||
TrafficWarning,
|
||||
User,
|
||||
UserBilling,
|
||||
UserPaymentMethod,
|
||||
@@ -583,6 +590,40 @@ async def get_all_users_with_panel_uuid(session: AsyncSession) -> List[User]:
|
||||
return result.scalars().all()
|
||||
|
||||
|
||||
async def get_panel_user_uuids_for_user(
|
||||
session: AsyncSession,
|
||||
user_id: int,
|
||||
*,
|
||||
user: Optional[User] = None,
|
||||
) -> List[str]:
|
||||
"""Return every Remnawave user UUID linked to a bot user.
|
||||
|
||||
The canonical UUID normally lives on ``users.panel_user_uuid``, but older
|
||||
or partially-synced records can still have UUIDs only on subscription rows.
|
||||
"""
|
||||
|
||||
if user is None:
|
||||
user = await get_user_by_id(session, user_id)
|
||||
|
||||
panel_uuids: List[str] = []
|
||||
seen: set[str] = set()
|
||||
|
||||
def add_uuid(value: Any) -> None:
|
||||
panel_uuid = str(value or "").strip()
|
||||
if panel_uuid and panel_uuid not in seen:
|
||||
seen.add(panel_uuid)
|
||||
panel_uuids.append(panel_uuid)
|
||||
|
||||
add_uuid(getattr(user, "panel_user_uuid", None))
|
||||
|
||||
stmt = select(Subscription.panel_user_uuid).where(Subscription.user_id == user_id)
|
||||
result = await session.execute(stmt)
|
||||
for panel_uuid in result.scalars().all():
|
||||
add_uuid(panel_uuid)
|
||||
|
||||
return panel_uuids
|
||||
|
||||
|
||||
async def get_enhanced_user_statistics(session: AsyncSession) -> Dict[str, Any]:
|
||||
"""Get comprehensive user statistics including active users, trial users, etc."""
|
||||
from datetime import datetime, timezone
|
||||
@@ -706,19 +747,69 @@ async def delete_user_and_relations(session: AsyncSession, user_id: int) -> bool
|
||||
update(User).where(User.referred_by_id == user_id).values(referred_by_id=None)
|
||||
)
|
||||
|
||||
# Clean up dependent tables that do not cascade automatically
|
||||
subscription_ids = select(Subscription.subscription_id).where(Subscription.user_id == user_id)
|
||||
payment_ids = select(Payment.payment_id).where(Payment.user_id == user_id)
|
||||
support_ticket_ids = select(SupportTicket.ticket_id).where(SupportTicket.user_id == user_id)
|
||||
|
||||
# Clean up dependent tables that do not cascade automatically.
|
||||
await session.execute(
|
||||
delete(TrafficTopup).where(
|
||||
or_(
|
||||
TrafficTopup.subscription_id.in_(subscription_ids),
|
||||
TrafficTopup.payment_id.in_(payment_ids),
|
||||
)
|
||||
)
|
||||
)
|
||||
await session.execute(
|
||||
delete(HwidDevicePurchase).where(
|
||||
or_(
|
||||
HwidDevicePurchase.subscription_id.in_(subscription_ids),
|
||||
HwidDevicePurchase.payment_id.in_(payment_ids),
|
||||
)
|
||||
)
|
||||
)
|
||||
await session.execute(
|
||||
delete(TariffChange).where(
|
||||
or_(
|
||||
TariffChange.subscription_id.in_(subscription_ids),
|
||||
TariffChange.payment_id.in_(payment_ids),
|
||||
)
|
||||
)
|
||||
)
|
||||
await session.execute(
|
||||
delete(TrafficWarning).where(TrafficWarning.subscription_id.in_(subscription_ids))
|
||||
)
|
||||
await session.execute(
|
||||
delete(SupportTicketMessage).where(SupportTicketMessage.ticket_id.in_(support_ticket_ids))
|
||||
)
|
||||
await session.execute(
|
||||
update(SupportTicketMessage)
|
||||
.where(SupportTicketMessage.author_user_id == user_id)
|
||||
.values(author_user_id=None)
|
||||
)
|
||||
await session.execute(delete(SupportTicket).where(SupportTicket.user_id == user_id))
|
||||
await session.execute(
|
||||
delete(EmailVerificationCode).where(EmailVerificationCode.target_user_id == user_id)
|
||||
)
|
||||
await session.execute(
|
||||
delete(MessageLog).where(
|
||||
or_(MessageLog.user_id == user_id, MessageLog.target_user_id == user_id)
|
||||
)
|
||||
)
|
||||
await session.execute(delete(Payment).where(Payment.user_id == user_id))
|
||||
await session.execute(delete(Subscription).where(Subscription.user_id == user_id))
|
||||
await session.execute(delete(PromoCodeActivation).where(PromoCodeActivation.user_id == user_id))
|
||||
await session.execute(
|
||||
delete(PromoCodeActivation).where(
|
||||
or_(
|
||||
PromoCodeActivation.user_id == user_id,
|
||||
PromoCodeActivation.payment_id.in_(payment_ids),
|
||||
)
|
||||
)
|
||||
)
|
||||
await session.execute(delete(UserPaymentMethod).where(UserPaymentMethod.user_id == user_id))
|
||||
await session.execute(delete(UserBilling).where(UserBilling.user_id == user_id))
|
||||
await session.execute(delete(AdAttribution).where(AdAttribution.user_id == user_id))
|
||||
await session.execute(delete(UserTelegramAvatar).where(UserTelegramAvatar.user_id == user_id))
|
||||
await session.execute(delete(Payment).where(Payment.user_id == user_id))
|
||||
await session.execute(delete(Subscription).where(Subscription.user_id == user_id))
|
||||
|
||||
await session.delete(user)
|
||||
await session.flush()
|
||||
|
||||
Reference in New Issue
Block a user