DispelEvilBot/common/repositories/users_admin_repo.py

114 lines
4.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.

"""Чтение данных пользователей для админки (Блок 4.3).
Баланс — контракт ``SUM(bot.money.amount)``; яички — ``SUM(bot.eggs.amount)``.
Подписка на рассылки — отсутствие строки в ``bot.unsubscribe``.
"""
from __future__ import annotations
from typing import Any
from common.db.pool import get_pool
#: Общая часть выборки: агрегаты по сообщениям, деньгам, яичкам и очищениям.
_FROM = """
FROM bot.tg_users AS tg
LEFT JOIN bot.de_users AS du ON du.id_user = tg.id_user
LEFT JOIN bot.titles AS t ON t.id_title = du.id_title
LEFT JOIN (
SELECT id_user, count(*) AS cnt, max(dt) AS last_dt
FROM bot.messages GROUP BY id_user
) AS msg ON msg.id_user = tg.id_user
LEFT JOIN (
SELECT id_user, sum(amount) AS balance
FROM bot.money GROUP BY id_user
) AS money ON money.id_user = tg.id_user
LEFT JOIN (
SELECT id_user, sum(amount) AS eggs
FROM bot.eggs GROUP BY id_user
) AS eggs ON eggs.id_user = tg.id_user
LEFT JOIN (
SELECT id_user, count(*) AS cnt, max(dt) AS last_dt
FROM bot.purifications GROUP BY id_user
) AS pur ON pur.id_user = tg.id_user
LEFT JOIN bot.unsubscribe AS unsub ON unsub.id_user = tg.id_user
"""
_SELECT = """
SELECT tg.id_user, tg.username, tg.first_name, tg.last_name,
du.nick, du.lvl, du.expr, du.expr_streak, du.dt_create,
t.title,
coalesce(msg.cnt, 0) AS msg_count,
msg.last_dt AS last_message_at,
coalesce(money.balance, 0) AS balance,
coalesce(eggs.eggs, 0) AS eggs,
coalesce(pur.cnt, 0) AS purification_count,
pur.last_dt AS last_purification_at,
(unsub.id_user IS NULL) AS subscribed
"""
#: Колонки, по которым идёт поиск (в порядке подстановки параметров).
_SEARCH_COLUMNS = ("tg.username", "tg.first_name", "tg.last_name", "du.nick")
def _search_clause(term: str) -> tuple[str, list[Any]]:
"""WHERE по нику, username, имени и Telegram-ID."""
term = term.strip()
if not term:
return "", []
like = f"%{term}%"
clauses = [f"{column} ILIKE %s" for column in _SEARCH_COLUMNS]
params: list[Any] = [like] * len(_SEARCH_COLUMNS)
telegram_id = term.lstrip("@")
if telegram_id.isdigit():
clauses.append("tg.id_user = %s")
params.append(int(telegram_id))
return " WHERE (" + " OR ".join(clauses) + ")", params
async def count_users(search: str = "") -> int:
"""Сколько пользователей подходит под поиск."""
where, params = _search_clause(search)
async with get_pool().connection() as conn:
cur = await conn.execute("SELECT count(*) AS total " + _FROM + where, tuple(params))
row = await cur.fetchone()
return int(row["total"]) if row else 0
async def list_users(search: str = "", *, limit: int = 25, offset: int = 0) -> list[dict[str, Any]]:
"""Страница списка пользователей (с поиском)."""
where, params = _search_clause(search)
async with get_pool().connection() as conn:
cur = await conn.execute(
_SELECT + _FROM + where + " ORDER BY du.nick NULLS LAST, tg.id_user LIMIT %s OFFSET %s",
(*params, limit, offset),
)
return await cur.fetchall()
async def get_user(user_id: int) -> dict[str, Any] | None:
"""Карточка пользователя со сводными показателями."""
async with get_pool().connection() as conn:
cur = await conn.execute(_SELECT + _FROM + " WHERE tg.id_user = %s", (user_id,))
return await cur.fetchone()
async def money_history(user_id: int, *, limit: int = 10) -> list[dict[str, Any]]:
"""Последние операции по балансу с названием действия."""
async with get_pool().connection() as conn:
cur = await conn.execute(
"""
SELECT m.id_money, m.amount, m.dt_action, a.title AS action
FROM bot.money AS m
LEFT JOIN bot.actions AS a ON a.id_action = m.id_action
WHERE m.id_user = %s
ORDER BY m.dt_action DESC, m.id_money DESC
LIMIT %s
""",
(user_id, limit),
)
return await cur.fetchall()