114 lines
4.5 KiB
Python
114 lines
4.5 KiB
Python
"""Чтение данных пользователей для админки (Блок 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()
|