"""Admin right-to-erasure endpoint (152-ФЗ) — ЭТАП 4 B2C launch, part C. Auth не нужен в этом файле — вся ветка `/api/v1/admin/*` уже гейтится `rbac_guard` middleware в app/main.py (`_ADMIN_API_RE`, role != admin → 403), тем же паттерном, что app/api/v1/audit.py. Мутационный (DELETE), поэтому осторожно: это НЕ self-service для конечного пользователя. Оператор поддержки/admin вызывает это ПОСЛЕ того, как убедился (вне этого API — телефон/estimate-ссылка/переписка), что запрос на удаление реально пришёл от владельца данных, а не от третьего лица, знающего чей-то номер телефона. Идентификация анонима — см. app/services/data_erasure.py module docstring (честно про то, что не всегда разрешимо). """ from __future__ import annotations import asyncio import logging from typing import Annotated from uuid import UUID from fastapi import APIRouter, Depends, HTTPException from pydantic import BaseModel, Field from sqlalchemy.orm import Session from app.core.db import get_db logger = logging.getLogger(__name__) router = APIRouter() class DataErasureRequest(BaseModel): """Хотя бы одно поле обязательно — см. erase_person_data ValueError guard.""" username: str | None = Field(default=None, max_length=200) estimate_ids: list[UUID] | None = None phone: str | None = Field(default=None, max_length=32) tg_chat_id: int | None = None @router.post("/privacy/erase") async def erase_person_data_endpoint( payload: DataErasureRequest, db: Annotated[Session, Depends(get_db)], ) -> dict[str, dict[str, int]]: """Физически удалить данные человека по одному или нескольким идентификаторам. Идентификаторы (хотя бы один): - username — B2B-пилот: удаляет ВСЕ его оценки (created_by=username, CASCADE подчищает фото/IMV-оценки), связанные лиды, веб-чат поддержки. - estimate_ids — конкретные оценки по UUID (анонимный путь: человек прислал ссылку/PDF со своим estimate_id) + лиды, привязанные к ним. - phone — лиды с этим номером телефона (независимо от привязки к оценке). - tg_chat_id — Telegram-поддержка (@MERAsupport_bot), включая переписку В ЭТОЙ БД. НЕ удаляет зеркало в Telegram-топике (см. app/services/data_erasure.py — ВАЖНЫЙ ФАКТ, честно, не скрываем). 422 если ни один идентификатор не передан (не даём случайно вызвать "удали всё" пустым телом). """ if not any([payload.username, payload.estimate_ids, payload.phone, payload.tg_chat_id]): raise HTTPException( status_code=422, detail="at least one identifier required: username / estimate_ids / phone / tg_chat_id", ) from app.services.data_erasure import erase_person_data counters = await asyncio.to_thread( erase_person_data, db, username=payload.username, estimate_ids=payload.estimate_ids, phone=payload.phone, tg_chat_id=payload.tg_chat_id, ) logger.info("admin privacy erase requested -> %s", counters) return {"deleted": counters}