Newer
Older
tgclient-mcp / backend / app / api / admin.py
"""Админ API: обзор всех пользователей/аккаунтов/ключей + принудительный
disconnect аккаунта. Только role admin/superadmin (require_admin) —
мульти-юзер сервис: обычные пользователи ничего админского не видят.
"""

from fastapi import APIRouter, Depends, HTTPException

from app.config import get_settings
from app.db import get_db
from app.security import now_iso, require_admin

router = APIRouter(prefix="/api/v1/admin", dependencies=[Depends(require_admin)])


@router.get("/overview")
async def overview() -> dict:
    """Сводка админки: пользователи, аккаунты, токены, счётчики."""
    db = get_db()

    users = await db.execute_fetchall(
        "SELECT user_id, email, display_name, system_role, blocked, created_at FROM users"
        " WHERE user_id != 'local' ORDER BY created_at DESC"
    )
    accounts = await db.execute_fetchall(
        """SELECT a.id, a.user_id, a.phone, a.label, a.status, a.error, a.tg_user_id,
                  a.username, a.display_name, a.created_at, a.last_used_at, u.email AS owner_email
           FROM accounts a LEFT JOIN users u ON u.user_id = a.user_id ORDER BY a.id DESC"""
    )
    tokens = await db.execute_fetchall(
        "SELECT id, user_id, name, user_email, system_role, token_hint, created_at,"
        " last_used_at, revoked_at FROM mcp_tokens ORDER BY id DESC"
    )
    cursor = await db.execute(
        "SELECT COUNT(*) AS n FROM login_sessions"
    )
    pending_logins = (await cursor.fetchone())["n"]
    return {
        "users": [dict(u) for u in users],
        "accounts": [dict(a) for a in accounts],
        "tokens": [dict(t) for t in tokens],
        "counters": {"pending_logins": pending_logins,
                     "max_active_accounts": get_settings().max_active_accounts},
    }


@router.post("/accounts/{account_id}/revoke_session")
async def revoke_account_session(account_id: int) -> dict:
    """Принудительно снять клиент (сессия остаётся в БД — админ не логаутит
    пользователя Telegram; это только управленческий disconnect пула)."""
    db = get_db()
    cursor = await db.execute("SELECT id FROM accounts WHERE id = ?", (account_id,))
    if (await cursor.fetchone()) is None:
        raise HTTPException(status_code=404, detail="account not found")
    from app.main import get_account_manager

    manager = get_account_manager()
    await manager.drop(account_id)
    await db.execute(
        "UPDATE accounts SET updated_at = ?, last_used_at = COALESCE(last_used_at, ?) WHERE id = ?",
        (now_iso(), now_iso(), account_id),
    )
    await db.commit()
    return {"status": "ok", "connected": False}