Files
ForbiddenStarsApp/backend/app/routers/dev_admin.py
T

71 lines
3.5 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
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.
"""DEV-ТОЛЬКО роутер: жёсткое удаление аккаунта игрока.
Этот файл ФИЗИЧЕСКИ исключён из прод-образа (.dockerignore), а роутер
подключается лишь когда APP_ENV == development (см. app/main.py). На фронте кнопка
удаления вырезается из прод-сборки тришейкингом (import.meta.env.DEV). Так
возможность удаления не попадает в прод — там аккаунт можно только
отключить (PATCH is_active).
Семантика («вычёркивание из партий»): аккаунт удаляется, а партии сохраняются —
строки участия игрока удаляются (player_count партий НЕ пересчитывается, поэтому
их статистика может стать несогласованной — осознанный выбор). Созданные им партии
и группы в его владении переназначаются на админа (FK RESTRICT не даёт удалить
пользователя, а сами партии/группы мы сохраняем).
"""
from __future__ import annotations
from fastapi import APIRouter, Depends, Request
from sqlmodel import Session, select
from app.auth.deps import get_current_admin
from app.core.security import client_ip
from app.core.errors import NotFoundError, ValidationError
from app.db.session import get_session
from app.models import Group, Match, MatchParticipant, User
from app.schemas import api as s
from app.services import audit_service
router = APIRouter(prefix="/admin/dev", tags=["admin-dev"])
@router.delete("/users/{user_id}", response_model=s.OkResponse)
def delete_user_hard(
user_id: int,
request: Request,
session: Session = Depends(get_session),
admin: User = Depends(get_current_admin),
) -> s.OkResponse:
user = session.get(User, user_id)
if user is None:
raise NotFoundError("Пользователь не найден.")
if user.role == "admin":
raise ValidationError("Нельзя удалить администратора.")
nickname = user.nickname # сохраняем до удаления (для аудита)
# 1. Вычёркиваем игрока из всех партий (его строки участия).
for part in session.exec(
select(MatchParticipant).where(MatchParticipant.user_id == user_id)
).all():
session.delete(part)
# 2. Партии и группы, где он создатель/владелец, переназначаем на админа.
for match in session.exec(select(Match).where(Match.created_by == user_id)).all():
match.created_by = admin.id # type: ignore[assignment]
session.add(match)
for group in session.exec(select(Group).where(Group.owner_id == user_id)).all():
group.owner_id = admin.id # type: ignore[assignment]
session.add(group)
session.flush()
# 3. group_members и auth_identity уйдут каскадом, audit_log.actor_id → NULL.
session.delete(user)
audit_service.record(
session,
actor_id=admin.id,
action="delete",
entity_type="user",
entity_id=user_id,
payload={"hard": True, "nickname": nickname},
ip=client_ip(request),
)
session.commit()
return s.OkResponse()