diff --git a/README.md b/README.md index 54da8f6..63f70bc 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ APP_ENV=prod python -m bot # prod ## Админка (Блок 4) Отдельный сервис `admin/` (FastAPI + Jinja2/HTMX), запуск `python -m admin`. -Все тексты бота — **последовательности** (`bot.message_sequences`): миграция `V24` +Все тексты бота — **шаблоны сообщений** (`bot.message_sequences`): миграция `V24` переносит каждый старый шаблон в последовательность из одного сообщения, а бот отправляет их через общий sender. Старые `message_templates` остаются фолбэком в коде. Правки применяются без перезапуска; предпросмотр использует тот же `render()`, что и бот. @@ -96,7 +96,7 @@ ID пользователя ведёт в его карточку. ClickHouse ч уведомлением «кто кого выгнал»; историю изменений показывает, кто менял ник — сам пользователь через `/ник` или админ. -Раздел «Последовательности» — набор сообщений по порядку (приветствие и любые другие): +Раздел «Шаблоны сообщений» — набор сообщений по порядку (приветствие и любые другие): у каждого свой текст, картинка из общего volume `media`, вкл/выкл и несколько кнопок. Редактор текста — textarea с тулбаром: жирный, курсив, подчёркивание, зачёркивание, код, блок кода, цитата, спойлер и ссылка; есть предпросмотр и счётчик символов @@ -137,7 +137,7 @@ docker compose up --build CI собирает два образа: `dispelevilbot-bot` и `dispelevilbot-admin`. Админка в compose по умолчанию публикуется только на loopback (`127.0.0.1:${ADMIN_HTTP_PORT}`, test 8100 / prod 8101); для nginx-proxy-manager задаётся `ADMIN_BIND_IP=172.17.0.1`, наружу выйдет -вместе с доменом и TLS (Блок 4.2). Картинки последовательностей лежат в общем +вместе с доменом и TLS (Блок 4.2). Картинки шаблонов лежат в общем Docker-volume `media` (`/app/media`), который монтируется и боту, и админке. Прод/test-контуры — один `docker-compose.prod.yml`, разделяются Compose-проектом и env-файлом: @@ -171,7 +171,7 @@ make backup E=test # на домашнем сервере: ./scripts/clickho ## Версионирование -- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.6.1`. +- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.6.2`. - Схема — 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/routes/pages.py b/admin/routes/pages.py index 27c6665..8c55c26 100644 --- a/admin/routes/pages.py +++ b/admin/routes/pages.py @@ -12,8 +12,8 @@ router = APIRouter(tags=["pages"]) #: Разделы админки по плану Блока 4 (url — None, пока раздел не готов). SECTIONS: tuple[dict[str, str | None], ...] = ( { - "title": "Последовательности", - "description": "Приветствие и другие наборы сообщений по порядку", + "title": "Шаблоны сообщений", + "description": "Тексты бота: сообщения, картинки и кнопки", "url": "/sequences", }, { diff --git a/admin/routes/sequences.py b/admin/routes/sequences.py index 4834bbd..b8b6f65 100644 --- a/admin/routes/sequences.py +++ b/admin/routes/sequences.py @@ -91,7 +91,7 @@ async def _editor_response( ) -> HTMLResponse: sequence = await sequences_repo.get_sequence(key) if sequence is None: - raise HTTPException(status_code=404, detail="Последовательность не найдена") + raise HTTPException(status_code=404, detail="Шаблон не найден") items = await sequences_repo.get_items(key) _prepare_items(items) @@ -151,7 +151,7 @@ async def create_sequence(request: Request) -> Response: ): return await _list_response( request, - errors=["Последовательность с таким ключом уже существует"], + errors=["Шаблон с таким ключом уже существует"], form=raw_form, status_code=400, ) @@ -188,7 +188,7 @@ async def update_sequence(request: Request, key: str) -> Response: description, editor_id=_editor_id(request), ): - raise HTTPException(status_code=404, detail="Последовательность не найдена") + raise HTTPException(status_code=404, detail="Шаблон не найден") return RedirectResponse(url=f"/sequences/{key}?saved=meta", status_code=303) @@ -198,7 +198,7 @@ async def add_item(request: Request, key: str) -> Response: check_csrf(request, str(form.get("csrf_token", ""))) if await sequences_repo.add_item(key, editor_id=_editor_id(request)) is None: - raise HTTPException(status_code=404, detail="Последовательность не найдена") + raise HTTPException(status_code=404, detail="Шаблон не найден") return RedirectResponse(url=f"/sequences/{key}?saved=item", status_code=303) @@ -210,7 +210,7 @@ async def update_item(request: Request, key: str, item_id: int) -> Response: items = await sequences_repo.get_items(key) item = next((row for row in items if int(row["id"]) == item_id), None) if item is None: - raise HTTPException(status_code=404, detail="Сообщение не найдено") + raise HTTPException(status_code=404, detail="Сообщение шаблона не найдено") text = str(form.get("text", "")) enabled = form.get("enabled") is not None @@ -274,7 +274,7 @@ async def update_item(request: Request, key: str, item_id: int) -> Response: if updated is None: if saved_file: _delete_image(settings, saved_file) - raise HTTPException(status_code=404, detail="Сообщение не найдено") + raise HTTPException(status_code=404, detail="Сообщение шаблона не найдено") _, old_image = updated if update_image and old_image and old_image != new_image: @@ -292,7 +292,7 @@ async def move_item(request: Request, key: str, item_id: int) -> Response: if direction not in {"up", "down"}: raise HTTPException(status_code=400, detail="Неверное направление") if await sequences_repo.move_item(item_id, direction, editor_id=_editor_id(request)) is None: - raise HTTPException(status_code=404, detail="Сообщение не найдено") + raise HTTPException(status_code=404, detail="Сообщение шаблона не найдено") return RedirectResponse(url=f"/sequences/{key}?saved=order", status_code=303) @@ -304,10 +304,10 @@ async def delete_item(request: Request, key: str, item_id: int) -> Response: items = await sequences_repo.get_items(key) item = next((row for row in items if int(row["id"]) == item_id), None) if item is None: - raise HTTPException(status_code=404, detail="Сообщение не найдено") + raise HTTPException(status_code=404, detail="Сообщение шаблона не найдено") if await sequences_repo.delete_item(item_id, editor_id=_editor_id(request)) is None: - raise HTTPException(status_code=404, detail="Сообщение не найдено") + raise HTTPException(status_code=404, detail="Сообщение шаблона не найдено") _delete_image(request.app.state.settings, item["image_name"]) return RedirectResponse(url=f"/sequences/{key}?saved=item", status_code=303) diff --git a/admin/templates/base.html b/admin/templates/base.html index bb533c5..5b04595 100644 --- a/admin/templates/base.html +++ b/admin/templates/base.html @@ -25,7 +25,7 @@ class="hidden w-full flex-col gap-2 border-t border-slate-100 pt-2 text-sm sm:flex sm:w-auto sm:flex-row sm:items-center sm:gap-4 sm:border-0 sm:pt-0" > Главная - Последовательности + Шаблоны Расписания Пользователи Звания diff --git a/admin/templates/sequences/edit.html b/admin/templates/sequences/edit.html index 562e79a..d971967 100644 --- a/admin/templates/sequences/edit.html +++ b/admin/templates/sequences/edit.html @@ -3,7 +3,7 @@ {% block title %}{{ sequence.title }}{% endblock %} {% block content %} -
+Несколько сообщений по порядку: текст, картинка и кнопки.
+Тексты бота: сообщения, картинки и кнопки.