diff --git a/README.md b/README.md index 609d7de..ebeb4af 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,10 @@ HttpOnly-cookie (`ADMIN_SESSION_SECRET`), все формы защищены CSR таймзона, шаблон и вкл/выкл. Бот раз в минуту читает активные расписания из БД; если база недоступна — использует расписание из кода (совпадает с сидом `V16`). +Раздел «Пользователи» — список с поиском по нику/username/имени/Telegram-ID и карточка: +уровень, опыт и серия, баланс (`SUM(money.amount)`), яички, сообщения, очищения, +подписка на рассылки, дата вступления и последние операции по балансу. + ## Аналитика (ClickHouse) Все значимые действия бота пишутся в ClickHouse в единую таблицу `analytics.events`. @@ -133,7 +137,7 @@ make backup E=test # на домашнем сервере: ./scripts/clickho ## Версионирование -- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.4.5`. +- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.4.6`. - Схема — SemVer-подобная (`MAJOR.MINOR.PATCH`); git-тег = `v` + версия (`v0.1`). - Docker-образы тегируются версией (Блок 2), события ClickHouse получают `bot_version` (Блок 3). - Выпуск: изменить `common/version.py` → коммит → аннотированный тег `vX.Y` → `git push --tags`. diff --git a/admin/app.py b/admin/app.py index 8e94d5c..efe5cf3 100644 --- a/admin/app.py +++ b/admin/app.py @@ -13,7 +13,7 @@ from fastapi.templating import Jinja2Templates from starlette.middleware.sessions import SessionMiddleware from admin.middleware import AdminAuthMiddleware -from admin.routes import auth, health, messages, pages, schedules +from admin.routes import auth, health, messages, pages, schedules, users from common import __version__ from common.config import AdminSettings, get_admin_settings from common.db.pool import close_pool, init_pool @@ -87,4 +87,5 @@ def create_app(settings: AdminSettings | None = None, *, init_db: bool = True) - app.include_router(pages.router) app.include_router(messages.router) app.include_router(schedules.router) + app.include_router(users.router) return app diff --git a/admin/routes/pages.py b/admin/routes/pages.py index 62c9507..162ca48 100644 --- a/admin/routes/pages.py +++ b/admin/routes/pages.py @@ -24,7 +24,7 @@ SECTIONS: tuple[dict[str, str | None], ...] = ( { "title": "Пользователи", "description": "Ники, звания, баланс и подписка", - "url": None, + "url": "/users", }, { "title": "Логи", diff --git a/admin/routes/users.py b/admin/routes/users.py new file mode 100644 index 0000000..31615e1 --- /dev/null +++ b/admin/routes/users.py @@ -0,0 +1,57 @@ +"""Раздел «Пользователи»: список с поиском и карточка игрока.""" + +from __future__ import annotations + +from fastapi import APIRouter, HTTPException, Request +from fastapi.responses import HTMLResponse + +from common import __version__ +from common.repositories import users_admin_repo + +router = APIRouter(prefix="/users", tags=["users"]) + +#: Сколько пользователей показывать на странице. +PAGE_SIZE = 25 + + +@router.get("", response_class=HTMLResponse) +async def list_users(request: Request, q: str = "", page: int = 1) -> HTMLResponse: + """Список пользователей: поиск по нику/username/имени/ID и пагинация.""" + total = await users_admin_repo.count_users(q) + pages = max((total + PAGE_SIZE - 1) // PAGE_SIZE, 1) + page = min(max(page, 1), pages) + + return request.app.state.templates.TemplateResponse( + request=request, + name="users/list.html", + context={ + "version": __version__, + "users": await users_admin_repo.list_users( + q, + limit=PAGE_SIZE, + offset=(page - 1) * PAGE_SIZE, + ), + "q": q, + "page": page, + "pages": pages, + "total": total, + }, + ) + + +@router.get("/{user_id}", response_class=HTMLResponse) +async def user_card(request: Request, user_id: int) -> HTMLResponse: + """Карточка пользователя со сводкой и последними операциями по балансу.""" + user = await users_admin_repo.get_user(user_id) + if user is None: + raise HTTPException(status_code=404, detail="Пользователь не найден") + + return request.app.state.templates.TemplateResponse( + request=request, + name="users/card.html", + context={ + "version": __version__, + "user": user, + "money": await users_admin_repo.money_history(user_id), + }, + ) diff --git a/admin/templates/base.html b/admin/templates/base.html index d555c63..7430314 100644 --- a/admin/templates/base.html +++ b/admin/templates/base.html @@ -15,6 +15,7 @@ Главная Сообщения Расписания + Пользователи {% if request.session.get('admin_id') %} {{ request.session.get('admin_username') or 'админ' }}