🤖 AnonBot - Telegram бот для анонимных вопросов

Telegram-бот для приема и обработки анонимных вопросов с использованием aiogram 3.x.

Возможности

  • 🔗 Персональные ссылки - каждый пользователь получает уникальную ссылку для приема вопросов
  • 👤 Анонимность - вопросы отправляются анонимно, личность отправителя скрыта
  • 💬 Интерактивные ответы - удобный интерфейс для ответов на вопросы через inline кнопки
  • 🔢 Локальная нумерация - каждый пользователь видит свои вопросы с номерами #1, #2, #3... вместо глобальных ID
  • 📊 Статистика - подробная статистика для администраторов
  • 🗄️ База данных - SQLite для хранения пользователей и вопросов с автоматической нумерацией
  • 👑 Админ панель - управление ботом для администраторов
  • 🔍 Суперпользователи - расширенные права для модерации с отображением информации об авторах вопросов
  • 🏗️ Современная архитектура - система инъекции зависимостей для лучшей тестируемости
  • 📝 Продвинутое логирование - автоматические декораторы, контекстное логирование, FSM отслеживание
  • 🛡️ Безопасность - валидация данных, обработка ошибок, система ролей
  • 🔍 Централизованная валидация - автоматическая валидация всех входных данных с санитизацией
  • 🔐 Система разрешений - гибкая система разрешений с соблюдением принципа OCP
  • 📈 Prometheus метрики - полный мониторинг производительности и состояния бота

🚀 Быстрый старт

1. Установка зависимостей

pip install -r requirements.txt
pip install dependency-injector  # Для системы инъекции зависимостей

2. Настройка конфигурации

Создайте файл .env в корневой директории проекта:

# Токен бота от @BotFather
BOT_TOKEN=your_bot_token_here

# ID администраторов через запятую (можно получить у @userinfobot)
ADMINS=123456789,987654321

# Путь к базе данных SQLite
DATABASE_PATH=database/anon_qna.db

# Режим отладки (true/false)
DEBUG=false


# Максимальная длина вопроса
MAX_QUESTION_LENGTH=1000

# Максимальная длина ответа
MAX_ANSWER_LENGTH=2000

3. Запуск бота

Локальный запуск

python main.py

или

python bot.py

Запуск в Docker

  1. Соберите Docker образ:
docker build -t anon-bot .
  1. Запустите контейнер:
docker run -d \
  --name anon-bot \
  --restart unless-stopped \
  -p 8081:8081 \
  -v $(pwd)/database:/app/database \
  -v $(pwd)/logs:/app/logs \
  -e BOT_TOKEN=your_bot_token_here \
  -e ADMINS=123456789,987654321 \
  -e DEBUG=false \
  anon-bot
  1. Проверьте статус:
docker logs anon-bot
  1. Проверьте метрики:
curl http://localhost:8081/health

📄 PID файл и мониторинг процесса

AnonBot автоматически создает PID файл для отслеживания процесса и предоставляет детальную информацию о состоянии через HTTP эндпоинты.

PID файл

  • Расположение: /tmp/anon_bot.pid
  • Содержимое: PID процесса бота
  • Автоматическое управление: создается при запуске, удаляется при остановке
  • Проверка дублирования: предотвращает запуск нескольких экземпляров

Эндпоинт /status

Предоставляет детальную информацию о процессе:

curl http://localhost:8081/status

Пример ответа:

{
  "status": "running",
  "pid": 12345,
  "uptime": "2ч 15м",
  "memory_usage_mb": 45.2,
  "cpu_percent": 0.1,
  "timestamp": 1705312200.5
}

Поля ответа:

  • status - статус процесса (running/stopped/not_found/error)
  • pid - идентификатор процесса
  • uptime - время работы в человекочитаемом формате
  • memory_usage_mb - использование памяти в МБ
  • cpu_percent - загрузка CPU в процентах
  • timestamp - время ответа

Тестирование

Для тестирования PID функционала можно использовать curl:

# Проверка статуса процесса
curl http://localhost:8081/status

# Проверка всех эндпоинтов
curl http://localhost:8081/

📁 Структура проекта

AnonBot/
├── main.py                    # Точка входа
├── bot.py                     # Основной файл бота
├── loader.py                  # Инициализация бота
├── dependencies.py            # Система инъекции зависимостей
├── utils.py                   # Общие утилиты
├── requirements.txt           # Зависимости
├── README.md                  # Документация
├── Dockerfile                 # Docker образ
├── .dockerignore              # Исключения для Docker
├── .env_example               # Пример переменных окружения
├── prometheus.yml             # Конфигурация Prometheus
├── config/                    # Конфигурация
│   ├── __init__.py
│   ├── config.py             # Основная конфигурация
│   └── constants.py          # Константы приложения
├── handlers/                  # Обработчики сообщений
│   ├── __init__.py
│   ├── start.py              # Команды /start, /help
│   ├── questions.py          # Обработка анонимных вопросов
│   ├── answers.py            # Обработка ответов
│   ├── admin.py              # Админ функции
│   └── errors.py             # Глобальная обработка ошибок
├── keyboards/                 # Клавиатуры
│   ├── __init__.py
│   ├── inline.py             # Inline клавиатуры
│   └── reply.py              # Reply клавиатуры
├── models/                    # Модели данных
│   ├── __init__.py
│   ├── user.py               # Модель пользователя
│   ├── question.py           # Модель вопроса
│   ├── user_block.py         # Модель блокировки
│   └── user_settings.py      # Модель настроек
├── services/                  # Сервисы (реорганизованы по категориям)
│   ├── __init__.py
│   ├── utils.py              # Общие утилиты
│   ├── auth/                 # Авторизация и разрешения
│   │   ├── __init__.py
│   │   └── auth_new.py       # Сервис авторизации с системой разрешений
│   ├── validation/           # Валидация входных данных
│   │   ├── __init__.py
│   │   └── input_validator.py # Централизованный валидатор
│   ├── business/             # Бизнес-логика
│   │   ├── __init__.py
│   │   ├── user_service.py   # Сервис пользователей
│   │   ├── question_service.py # Сервис вопросов
│   │   ├── message_service.py  # Сервис сообщений
│   │   └── pagination_service.py # Сервис пагинации
│   ├── infrastructure/       # Инфраструктурные сервисы
│   │   ├── __init__.py
│   │   ├── database.py       # Работа с БД
│   │   ├── logger.py         # Система логирования
│   │   ├── logging_decorators.py  # Декораторы для автоматического логирования
│   │   ├── logging_utils.py  # Утилиты для контекстного логирования
│   │   ├── metrics.py        # Prometheus метрики
│   │   ├── http_server.py    # HTTP сервер для метрик
│   │   └── pid_manager.py    # Менеджер PID файлов
│   ├── rate_limiting/        # Rate limiting
│   │   ├── __init__.py
│   │   ├── rate_limit_config.py # Конфигурация rate limiting
│   │   ├── rate_limiter.py   # Основной rate limiter
│   │   └── rate_limit_service.py # Сервис rate limiting
│   └── permissions/          # Система разрешений
│       ├── __init__.py
│       ├── base.py           # Базовые классы
│       ├── registry.py       # Реестр разрешений
│       ├── permissions.py    # Стандартные разрешения
│       ├── decorators.py     # Декораторы для проверки
│       └── init_permissions.py # Инициализация
├── examples/                 # Примеры использования
│   └── dependency_injection_example.py
├── database/                 # База данных
│   ├── __init__.py
│   ├── schema.sql            # Схема БД
│   ├── crud.py               # CRUD операции
│   └── examples.py           # Примеры использования
├── middlewares/              # Middleware
│   ├── __init__.py
│   ├── rate_limit_middleware.py # Middleware для rate limiting
│   └── validation_middleware.py # Middleware для валидации данных
├── docs/                     # Документация
└── logs/                     # Логи приложения

🏗️ Архитектурные улучшения

Проект был реорганизован для улучшения структуры и масштабируемости:

📁 Новая структура services

До реорганизации:

  • Все сервисы в корне services/
  • Смешанные категории (бизнес-логика + инфраструктура)
  • Дублирование функциональности

После реорганизации:

  • services/auth/ - авторизация и разрешения
  • services/business/ - бизнес-логика (пользователи, вопросы, сообщения)
  • services/infrastructure/ - инфраструктурные сервисы (БД, логи, метрики)
  • services/rate_limiting/ - rate limiting компоненты
  • services/permissions/ - система разрешений (без изменений)

📁 Новая структура config

До реорганизации:

  • config.py и constants.py в корне проекта

После реорганизации:

  • config/ - папка для всех конфигурационных файлов
  • config/config.py - основная конфигурация
  • config/constants.py - константы приложения

Преимущества новой структуры

  1. Логическая группировка - связанные сервисы в одних папках
  2. Разделение ответственности - бизнес-логика отдельно от инфраструктуры
  3. Масштабируемость - легко добавлять новые сервисы в нужные категории
  4. Читаемость - понятно, где что находится
  5. Обратная совместимость - старые импорты продолжают работать

🔄 Обратная совместимость

Все импорты обновлены, но старые импорты продолжают работать благодаря __init__.py файлам:

# Старый способ (все еще работает)
from services.database import DatabaseService
from config import config

# Новый способ (рекомендуется)
from services.infrastructure.database import DatabaseService
from config import config

🔍 Система валидации входных данных

Реализована централизованная система валидации всех входящих данных для обеспечения безопасности и стабильности:

📋 Что валидируется

  • Telegram ID - проверка диапазона и типа данных
  • Username - валидация формата и допустимых символов
  • Текстовый контент - проверка длины, HTML санитизация, защита от спама
  • Deep links - валидация формата и длины
  • Callback data - проверка безопасности и формата
  • Параметры пагинации - валидация диапазонов

🛡️ Безопасность

  • HTML санитизация - автоматическое экранирование опасных тегов
  • Защита от спама - проверка на повторяющиеся символы и слова
  • Валидация ID - проверка корректности всех идентификаторов
  • Логирование - полное логирование всех ошибок валидации с автоматическими декораторами

🏗️ Архитектура валидации

# Централизованный валидатор
from services.validation import InputValidator

validator = InputValidator()

# Валидация текста вопроса
result = validator.validate_question_text(text, max_length=1000)
if not result:
    print(f"Ошибка: {result.error_message}")
else:
    sanitized_text = result.sanitized_value

# Валидация callback data
result = validator.validate_callback_data(callback_data)

🔄 Middleware валидации

Автоматическая валидация всех входящих данных через middleware:

# ValidationMiddleware автоматически валидирует:
# - Все callback queries
# - Все сообщения
# - Telegram ID пользователей
# - Username (если есть)
# - Chat ID

Преимущества

  1. Безопасность - защита от некорректных данных и атак
  2. Стабильность - предотвращение ошибок от невалидных данных
  3. UX - понятные сообщения об ошибках для пользователей
  4. Мониторинг - полное логирование проблем валидации
  5. Централизация - единая точка валидации для всего приложения

🎯 Как это работает

Для пользователей:

  1. Регистрация: Пользователь запускает бота командой /start
  2. Получение ссылки: Бот генерирует персональную ссылку формата t.me/bot_username?start=ref_{anonymous_id}
  3. Публикация ссылки: Пользователь делится ссылкой в социальных сетях
  4. Получение вопросов: Друзья переходят по ссылке и задают анонимные вопросы
  5. Ответы: Пользователь получает уведомления и может отвечать на вопросы

Для отправителей вопросов:

  1. Переход по ссылке: Отправитель переходит по персональной ссылке пользователя
  2. Задание вопроса: Отправляет вопрос боту
  3. Анонимность: Вопрос передается получателю анонимно
  4. Получение ответа: Если получатель ответит, ответ будет показан

🔧 Команды бота

Основные команды:

  • /start - Запуск бота и получение персональной ссылки
  • /help - Справка по использованию бота

Админ команды:

  • /stats - Показать статистику бота (только для админов)

👑 Админ панель

Администраторы имеют доступ к дополнительным функциям:

  • 📊 Статистика - общая статистика бота, пользователей и вопросов
  • 👥 Пользователи - список всех пользователей бота
  • Все вопросы - статистика по всем вопросам
  • 📢 Рассылка - функция рассылки (планируется)
  • ⚙️ Настройки - просмотр текущих настроек бота

🔍 Система ролей и суперпользователи

Бот поддерживает трехуровневую систему ролей:

👑 Администраторы (admin)

  • Определяются в конфигурации (ADMINS в .env)
  • Имеют все права доступа
  • Не могут быть изменены через базу данных

🔍 Суперпользователи (superuser)

  • Определяются в базе данных (поле is_superuser = TRUE)
  • Имеют расширенные права для модерации
  • Могут видеть информацию об авторах вопросов

Особенности для суперпользователей:

Отображение списка вопросов:

10. ✅ #2 Вопрос от @username FirstName LastName

Уведомления о новых вопросах:

❓ Новый вопрос от @username FirstName LastName!

📝 Вопрос:
Текст вопроса...

📅 05.09.2025 23:27

👤 Обычные пользователи (user)

  • Стандартные права доступа
  • Видят анонимные вопросы без информации об авторах

Назначение суперпользователя

Суперпользователей можно назначать через базу данных:

UPDATE users SET is_superuser = TRUE WHERE telegram_id = 123456789;

Или программно:

from services.infrastructure.database import DatabaseService

async def make_superuser(telegram_id: int):
    db_service = DatabaseService("database/anon_qna.db")
    user = await db_service.get_user(telegram_id)
    if user:
        user.is_superuser = True
        await db_service.update_user(user)

🔐 Система разрешений

AnonBot использует современную систему разрешений, построенную с соблюдением принципа открытости/закрытости (OCP):

Преимущества новой системы

  • Открытость для расширения - новые разрешения добавляются без изменения существующего кода
  • Закрытость для модификации - существующий код не изменяется при добавлении новых разрешений
  • Типобезопасность - использование классов вместо строк
  • Единая точка входа - все проверки разрешений через один интерфейс
  • Удобные декораторы - простое применение проверок разрешений

🎯 Стандартные разрешения

Разрешение Описание Доступ
admin Права администратора Только администраторы
superuser Права суперпользователя Только суперпользователи
view_stats Просмотр статистики Администраторы + суперпользователи
admin_panel Доступ к админ панели Администраторы + суперпользователи
manage_users Управление пользователями Администраторы + суперпользователи
broadcast Рассылка сообщений Только администраторы
view_questions Просмотр вопросов Все активные пользователи
ask_questions Задавание вопросов Все активные незабаненные пользователи
answer_questions Ответы на вопросы Все активные незабаненные пользователи

🚀 Использование

С декораторами (рекомендуется)

from services.permissions.decorators import require_permission

@router.message(Command("my_command"))
@require_permission("view_stats", "❌ У вас нет прав для выполнения этой команды.")
async def my_command_handler(message: Message):
    # Логика обработчика
    await message.answer("Команда выполнена!")

Готовые декораторы

@require_permission("view_stats")           # Конкретное разрешение
@require_admin()                            # Только администраторы
@require_superuser()                        # Только суперпользователи
@require_admin_or_superuser()               # Администраторы или суперпользователи
@require_active_user()                      # Активные пользователи
@require_unbanned_user()                    # Незабаненные пользователи

Добавление нового разрешения

# 1. Создаем класс разрешения
class MyCustomPermission(Permission):
    async def check(self, user_id: int, database: DatabaseService, config) -> bool:
        return user_id in config.ADMINS

# 2. Регистрируем разрешение
register_permission(MyCustomPermission())

# 3. Используем в обработчике
@require_permission("my_custom_permission")
async def my_handler(message: Message):
    # Логика обработчика
    pass

🛡️ Безопасность

  • Валидация: Проверка всех входящих данных
  • Обработка ошибок: Глобальная обработка ошибок с уведомлениями админов
  • Логирование: Автоматическое логирование с декораторами, контекстная информация, FSM отслеживание
  • Система ролей: Трехуровневая система доступа (админ/суперпользователь/пользователь)
  • Система разрешений: Гибкая система разрешений с соблюдением принципа OCP

🏗️ Система инъекции зависимостей

AnonBot использует современную систему инъекции зависимостей, построенную на основе MagicData из aiogram 3.x. Это обеспечивает:

  • Тестируемость: Легко мокать зависимости в тестах
  • Читаемость: Явные зависимости в сигнатурах функций
  • Гибкость: Легко заменять реализации сервисов
  • Обратная совместимость: Старый код продолжает работать

Доступные сервисы

  1. DatabaseService - работа с базой данных, CRUD операции
  2. AuthService - авторизация, проверка прав, управление ролями
  3. InputValidator - централизованная валидация всех входных данных
  4. UtilsService - форматирование данных, утилиты
  5. RateLimitService - управление rate limiting, статистика
  6. Config - доступ к конфигурации приложения

Быстрый старт с DI

# Подключение в loader.py
from dependencies import DependencyMiddleware, get_dependencies

async def init_dispatcher() -> Dispatcher:
    dp = Dispatcher(storage=storage)
    
    # Инициализируем зависимости
    deps = get_dependencies()
    await deps.init()
    
    # Добавляем middleware
    dp.update.middleware(DependencyMiddleware(deps))
    
    return dp

# Использование в обработчиках
from dependencies import inject_start_services, inject_question_services, inject_answer_services
from services.infrastructure.database import DatabaseService
from services.auth.auth_new import AuthService
from services.validation import InputValidator

@router.message(Command("start"))
@inject_start_services
async def cmd_start(
    message: Message,
    user_service: UserService,
    auth: AuthService,
    utils: UtilsService,
    message_service: MessageService,
    validator: InputValidator
):
    # Валидируем входные данные
    user_id_validation = validator.validate_telegram_id(message.from_user.id)
    if not user_id_validation:
        await message.answer("❌ Ошибка: недопустимый ID пользователя")
        return
    
    # Используем сервисы без хардкода
    is_admin = auth.is_admin(message.from_user.id)
    user = await user_service.get_user_by_telegram_id(message.from_user.id)

Способы инъекции

  1. Специализированные декораторы (рекомендуется):

    • @inject_question_services - для обработки вопросов
    • @inject_answer_services - для обработки ответов
    • @inject_start_services - для команды /start
    • @inject_link_services - для кнопки "Моя ссылка"
    • @inject_main_menu_services - для кнопки "Главное меню"
  2. Базовые декораторы:

    • @inject_database - только DatabaseService
    • @inject_auth - только AuthService
    • @inject_utils - только UtilsService
  3. Автоматически: aiogram инжектит зависимости из middleware

  4. Обратная совместимость: старые функции продолжают работать

Примеры использования специализированных декораторов

Обработка вопросов

@router.message(StateFilter(QuestionStates.waiting_for_question))
@inject_question_services
async def process_anonymous_question(
    message: Message, 
    state: FSMContext,
    question_service: QuestionService,
    user_service: UserService,
    message_service: MessageService,
    validator: InputValidator
):
    # Валидируем и обрабатываем вопрос
    validation_result = validator.validate_question_text(message.text)
    if not validation_result:
        await message_service.send_message(message, "❌ Неверный формат вопроса")
        return
    
    # Создаем вопрос через сервис
    question = await question_service.create_question(
        message.from_user.id,
        target_user_id,
        validation_result.sanitized_value
    )

Обработка ответов

@router.message(StateFilter(AnswerStates.waiting_for_answer))
@inject_answer_services
async def process_new_answer(
    message: Message, 
    state: FSMContext,
    validator: InputValidator
):
    # Валидируем ответ
    validation_result = validator.validate_answer_text(message.text)
    if not validation_result:
        await message.answer("❌ Неверный формат ответа")
        return
    
    # Сохраняем ответ
    # ... логика сохранения

Локальная нумерация вопросов

Отображение вопросов с локальными номерами

# В модели Question добавлен метод get_display_number()
def get_display_number(self) -> int:
    """Получить номер вопроса для отображения (приоритет user_question_number)"""
    return self.user_question_number if self.user_question_number is not None else self.id

# В обработчиках используется локальная нумерация
questions_text += f"{i}. {emoji} <b>#{question.get_display_number()}</b>"

Автоматическая нумерация через триггеры БД

-- Триггер для автоматического вычисления номера при создании
CREATE TRIGGER calculate_user_question_number
AFTER INSERT ON questions
FOR EACH ROW
WHEN NEW.user_question_number IS NULL
BEGIN
    UPDATE questions 
    SET user_question_number = (
        SELECT COALESCE(MAX(user_question_number), 0) + 1 
        FROM questions q2 
        WHERE q2.to_user_id = NEW.to_user_id
        AND q2.status != 'deleted'
    )
    WHERE id = NEW.id;
END;

-- Триггер для пересчета номеров при удалении
CREATE TRIGGER recalculate_user_question_numbers_on_delete
AFTER UPDATE ON questions
FOR EACH ROW
WHEN NEW.status = 'deleted' AND OLD.status != 'deleted'
BEGIN
    UPDATE questions 
    SET user_question_number = user_question_number - 1
    WHERE to_user_id = NEW.to_user_id 
    AND user_question_number > OLD.user_question_number
    AND status != 'deleted';
    
    UPDATE questions 
    SET user_question_number = NULL
    WHERE id = NEW.id;
END;

Обработка команд

@router.message(Command("start"))
@inject_start_services
async def cmd_start(
    message: Message,
    state: FSMContext,
    user_service: UserService,
    auth: AuthService,
    utils: UtilsService,
    message_service: MessageService,
    validator: InputValidator
):
    # Создаем или обновляем пользователя
    user = await user_service.create_or_update_user(message.from_user, message.chat.id)
    
    # Проверяем права
    is_admin = auth.is_admin(user.telegram_id)
    
    # Отправляем приветствие
    await message_service.send_message(message, welcome_text, keyboard)

Преимущества специализированных декораторов

Нет проблем с dispatcher - aiogram не передает лишние параметры
Меньше зависимостей - инжектируются только нужные сервисы
Лучшая производительность - меньше объектов создается
Более явный код - видно, какие зависимости используются
Легче тестировать - меньше моков нужно создавать

Тестирование

Система DI значительно упрощает тестирование:

# Создание тестовых зависимостей
@pytest.fixture
async def test_dependencies():
    deps = Dependencies()
    deps._database = AsyncMock()  # Мокаем БД
    deps._auth = MagicMock()      # Мокаем авторизацию
    return deps

# Тестирование обработчиков
@pytest.mark.asyncio
async def test_cmd_start(test_dependencies):
    # Настраиваем моки
    test_dependencies._user_service.get_user_by_telegram_id.return_value = mock_user
    test_dependencies._auth.is_admin.return_value = False
    
    # Тестируем обработчик
    result = await cmd_start(
        message, 
        state, 
        user_service=test_dependencies.user_service,
        auth=test_dependencies.auth,
        utils=test_dependencies.utils,
        message_service=test_dependencies.message_service,
        validator=test_dependencies.validator
    )
    
    # Проверяем результат
    assert result is not None

Подробная документация: DI_SETUP.md

📊 База данных

Бот использует SQLite для хранения данных:

Таблицы:

  • users: Информация о пользователях (ID, имя, ссылка, статус, права суперпользователя)
  • questions: Вопросы и ответы (текст, статус, анонимность, локальная нумерация)
  • user_blocks: Блокировки пользователей
  • user_settings: Настройки пользователей (уведомления, язык)

Особенности:

  • Внешние ключи: Связи между таблицами
  • Триггеры: Автоматическое обновление timestamps и нумерация вопросов
  • Индексы: Оптимизация запросов, включая индексы для локальной нумерации
  • CRUD операции: Полный набор операций для каждой таблицы
  • Локальная нумерация: Каждый пользователь видит свои вопросы с номерами #1, #2, #3...

Автоматическая нумерация вопросов:

  • Триггер calculate_user_question_number: Автоматически присваивает номер при создании вопроса
  • Триггер recalculate_user_question_numbers_on_delete: Пересчитывает номера при удалении вопроса
  • Удаленные вопросы: Не участвуют в нумерации (status = 'deleted')
  • Уникальные номера: Гарантируется уникальность номеров в рамках каждого пользователя

Схема:

Схема базы данных находится в файле database/schema.sql

🔧 Настройка

Все настройки находятся в файле .env:

  • BOT_TOKEN - токен бота (обязательно)
  • ADMINS - список ID администраторов
  • DATABASE_PATH - путь к файлу базы данных
  • DEBUG - режим отладки
  • MAX_QUESTION_LENGTH - максимальная длина вопроса
  • MAX_ANSWER_LENGTH - максимальная длина ответа

Настройки Rate Limiting:

  • RATE_LIMIT_ENV - окружение (development/production/strict)
  • RATE_LIMIT_MESSAGES_PER_SECOND - сообщений в секунду на чат
  • RATE_LIMIT_BURST_LIMIT - максимум сообщений подряд
  • RATE_LIMIT_RETRY_MULTIPLIER - множитель для задержки при retry
  • RATE_LIMIT_MAX_RETRY_DELAY - максимальная задержка между попытками
  • RATE_LIMIT_MAX_RETRIES - максимальное количество повторных попыток

🚀 Развертывание

Локальный запуск:

python main.py

Docker (планируется):

docker build -t anonbot .
docker run -d --name anonbot anonbot

VPS/Сервер:

  1. Загрузите код на сервер
  2. Установите зависимости: pip install -r requirements.txt
  3. Настройте .env файл
  4. Для обновления существующей базы данных (если нужно добавить поле is_superuser):
    python3 -c "
    import asyncio
    import aiosqlite
    
    async def migrate():
        async with aiosqlite.connect('database/anon_qna.db') as conn:
            await conn.execute('ALTER TABLE users ADD COLUMN is_superuser BOOLEAN DEFAULT FALSE')
            await conn.execute('CREATE INDEX IF NOT EXISTS idx_users_is_superuser ON users(is_superuser)')
            await conn.commit()
            print('Миграция завершена!')
    
    asyncio.run(migrate())
    "
    
  5. Запустите: python main.py
  6. Рекомендуется использовать systemd или supervisor для автозапуска

📝 Система логирования

В проекте настроена продвинутая система логирования с использованием библиотеки loguru и автоматических декораторов. Логи выводятся в stderr для корректной работы в Docker контейнерах.

Основные компоненты

  • services/infrastructure/logger.py - основная настройка системы логирования
  • services/infrastructure/logging_decorators.py - декораторы для автоматического логирования
  • services/infrastructure/logging_utils.py - утилиты для контекстного логирования
  • loguru - библиотека для логирования (уже добавлена в requirements.txt)

Уровни логирования

  • INFO - основная информация о работе бота
  • WARNING - предупреждения о потенциальных проблемах
  • ERROR - ошибки, требующие внимания

Формат логов

2024-01-15 10:30:45 | INFO     | bot:main:25 - 🚀 Запуск бота в режиме polling
2024-01-15 10:30:45 | INFO     | loader:init_bot:28 - 🤖 Инициализация Telegram бота
2024-01-15 10:30:45 | INFO     | loader:init_bot:33 - ✅ Бот успешно инициализирован

🎯 Автоматические декораторы логирования

Система включает в себя набор декораторов для автоматического логирования:

1. Основные декораторы

@log_function_call - логирование входа/выхода из функций

@log_function_call(log_params=True, log_result=True)
async def create_question(self, from_user_id: int, to_user_id: int, message_text: str):
    # Автоматически логирует вход с параметрами и выход с результатом

@log_business_event - логирование бизнес-событий

@log_business_event("create_question", log_params=True, log_result=True)
async def create_question(self, ...):
    # Логирует бизнес-событие с контекстом

@log_fsm_transition - логирование FSM переходов

@log_fsm_transition(to_state="waiting_for_answer")
async def answer_question_callback(callback: CallbackQuery, state: FSMContext):
    # Логирует переходы между состояниями FSM

2. Оптимизированные декораторы

@log_middleware - тихое логирование для middleware

@log_middleware(log_params=True, log_result=False)
async def __call__(self, handler, event, data):
    # Логирует только ошибки, вход/выход в DEBUG режиме

@log_utility - декоратор для служебных функций

@log_utility
def _has_attachments(message: Message) -> bool:
    # Логирует только ошибки

3. Контекстное логирование

LoggingContext - контекстное логирование с дополнительной информацией

context = get_logging_context(__name__)
context.add_context("user_id", user_id)
context.log_info("Пользователь выполнил действие")

Специальные функции для бизнес-событий:

log_question_created(logger, question_id, from_user_id, to_user_id)
log_user_created(logger, user_id, username)
log_user_blocked(logger, user_id, reason)

Покрытие логированием

1. Запуск и инициализация

  • Инициализация бота
  • Инициализация диспетчера
  • Инициализация базы данных
  • Регистрация обработчиков
  • Уведомления администраторов

2. База данных (CRUD операции)

  • Создание пользователей
  • Создание вопросов
  • Обновление вопросов
  • Инициализация БД

3. Обработчики команд

  • Команда /start
  • Обработка deep links
  • Создание/обновление пользователей

4. Бизнес-логика

  • Отправка ответов авторам
  • Обработка ошибок
  • Валидация данных

5. Системные события

  • Ошибки в обработчиках
  • Очистка ресурсов
  • Остановка бота

6. FSM состояния

  • Переходы между состояниями
  • Обработка FSM событий
  • Отслеживание пользовательских сессий

Использование в коде

Импорт логгера и декораторов

from services.infrastructure.logger import get_logger
from services.infrastructure.logging_decorators import (
    log_function_call, log_business_event, log_fsm_transition,
    log_middleware, log_utility
)
from services.infrastructure.logging_utils import (
    log_user_action, log_business_operation, log_question_created
)

logger = get_logger(__name__)

Примеры использования декораторов

# Бизнес-события
@log_business_event("create_question", log_params=True, log_result=True)
async def create_question(self, from_user_id: int, to_user_id: int, message_text: str):
    # Автоматически логирует создание вопроса
    pass

# FSM переходы
@log_fsm_transition(to_state="waiting_for_answer")
async def answer_question_callback(callback: CallbackQuery, state: FSMContext):
    # Логирует переход в состояние ответа
    pass

# Middleware (тихое логирование)
@log_middleware(log_params=True, log_result=False)
async def __call__(self, handler, event, data):
    # Логирует только ошибки
    pass

# Служебные функции
@log_utility
def _has_attachments(message: Message) -> bool:
    # Логирует только ошибки
    pass

Контекстное логирование

# Создание контекста
context = get_logging_context(__name__)
context.add_context("user_id", user_id)
context.add_context("question_id", question_id)
context.log_info("Пользователь ответил на вопрос")

# Специальные функции для бизнес-событий
log_question_created(logger, question_id, from_user_id, to_user_id)
log_user_created(logger, user_id, username)
log_user_blocked(logger, user_id, reason)

Традиционное логирование

# Информационные сообщения
logger.info("🚀 Запуск бота в режиме polling")
logger.info(f"👤 Создание пользователя: {user.telegram_id} ({user.first_name})")

# Предупреждения
logger.warning("⚠️ Список администраторов пуст")
logger.warning(f"⚠️ Не удалось отправить уведомление админу {admin_id}: {e}")

# Ошибки
logger.error(f"💥 Ошибка при запуске бота: {e}")
logger.error(f"💥 Ошибка в обработчике /start: {e}")

Docker интеграция

Логи настроены для вывода в stderr, что обеспечивает корректную работу в Docker:

# В Dockerfile
CMD ["python", "main.py"]
# Просмотр логов в Docker
docker logs <container_name>

Файловое логирование (DEBUG режим)

В режиме отладки логи также сохраняются в файлы:

  • logs/bot.log - основные логи
  • Ротация: 10 MB
  • Хранение: 7 дней
  • Сжатие: zip

Производительность и оптимизация

Тихие декораторы

Для предотвращения избыточного логирования используются тихие декораторы:

  • @log_middleware - для middleware (логирует только ошибки)
  • @log_utility - для служебных функций (логирует только ошибки)
  • quiet=True - параметр для полного отключения логирования входа/выхода

Уровни логирования

  • INFO - бизнес-события и важные операции
  • DEBUG - детальная информация (только в DEBUG режиме)
  • WARNING - предупреждения
  • ERROR - ошибки (всегда логируются)

Контекстная информация

Декораторы автоматически извлекают контекстную информацию:

  • user_id - ID пользователя
  • question_id - ID вопроса
  • page - номер страницы
  • status - статус операции

Примеры оптимизированного логирования

# Middleware - тихое логирование
@log_middleware(log_params=True, log_result=False)
async def __call__(self, handler, event, data):
    # Логирует только ошибки, вход/выход в DEBUG режиме
    pass

# Служебные функции - только ошибки
@log_utility
def _has_attachments(message: Message) -> bool:
    # Логирует только ошибки
    pass

# Бизнес-функции - полное логирование
@log_business_event("create_question", log_params=True, log_result=True)
async def create_question(self, ...):
    # Полное логирование для важных операций
    pass

Мониторинг

Ключевые метрики для мониторинга

  1. Запуск/остановка бота

    • 🚀 Запуск бота в режиме polling
    • 🛑 Бот остановлен
  2. Пользователи

    • 👤 Создание пользователя
    • 👤 Обновление существующего пользователя
  3. Вопросы и ответы

    • ❓ Создание вопроса
    • 📝 Обновление вопроса
    • 📤 Отправка ответа
  4. Ошибки

    • 💥 Ошибка в обработчике
    • ⚠️ Предупреждения

Алерты

Рекомендуется настроить алерты на:

  • ERROR уровень логов
  • Отсутствие логов более 5 минут
  • Частые ошибки в обработчиках

Настройка уровней

Уровень логирования настраивается через переменную окружения:

# В .env файле
DEBUG=true  # DEBUG уровень
DEBUG=false # INFO уровень (по умолчанию)

Производительность

  • Логирование асинхронное
  • Минимальное влияние на производительность
  • Эмодзи в логах для быстрого визуального поиска
  • Структурированный формат для парсинга

🤝 Вклад в проект

  1. Fork репозитория
  2. Создайте feature branch
  3. Внесите изменения
  4. Создайте Pull Request

📄 Лицензия

MIT License

🆘 Поддержка

Если у вас возникли проблемы:

  1. Проверьте логи бота
  2. Убедитесь в правильности настройки .env
  3. Проверьте права доступа к файлам
  4. Обратитесь к администратору

Дополнительная документация:

📈 Prometheus метрики

AnonBot поддерживает экспорт метрик в формате Prometheus для мониторинга и анализа производительности.

Эндпоинты

Доступные метрики

Информационные метрики

  • anon_bot_info - Информация о боте (версия, сервис)

Счетчики сообщений

  • anon_bot_messages_total - Общее количество обработанных сообщений
    • Метки: message_type, status

Счетчики вопросов

  • anon_bot_questions_total - Общее количество вопросов
    • Метки: status (created, rejected, deleted)

Счетчики ответов

  • anon_bot_answers_total - Общее количество ответов
    • Метки: status (sent, edited, delivered, delivery_failed)

Счетчики пользователей

  • anon_bot_users_total - Общее количество пользователей
    • Метки: action (created, updated)

Счетчики ошибок

  • anon_bot_errors_total - Общее количество ошибок
    • Метки: error_type, component

HTTP метрики

  • anon_bot_http_requests_total - Общее количество HTTP запросов
    • Метки: method, endpoint, status_code
  • anon_bot_http_request_duration_seconds - Время обработки HTTP запросов
    • Метки: method, endpoint

Время обработки

  • anon_bot_message_processing_seconds - Время обработки сообщений
    • Метки: message_type
  • anon_bot_question_processing_seconds - Время обработки вопросов
  • anon_bot_answer_processing_seconds - Время обработки ответов

Gauge метрики

  • anon_bot_active_users - Количество активных пользователей
  • anon_bot_active_questions - Количество активных вопросов

Настройка Prometheus

Добавьте в конфигурацию Prometheus (prometheus.yml):

scrape_configs:
  - job_name: 'anon-bot'
    static_configs:
      - targets: ['localhost:8081']
    metrics_path: '/metrics'
    scrape_interval: 30s

Примеры запросов PromQL

Количество сообщений в секунду

rate(anon_bot_messages_total[5m])

Количество ошибок в секунду

rate(anon_bot_errors_total[5m])

Время обработки сообщений (95-й процентиль)

histogram_quantile(0.95, rate(anon_bot_message_processing_seconds_bucket[5m]))

Количество активных пользователей

anon_bot_active_users

Мониторинг в Docker

При запуске в Docker убедитесь, что порт 8081 доступен:

ports:
  - "8081:8081"

Алерты

Рекомендуется настроить алерты на:

  1. Высокий уровень ошибок

    rate(anon_bot_errors_total[5m]) > 0.1
    
  2. Медленную обработку

    histogram_quantile(0.95, rate(anon_bot_message_processing_seconds_bucket[5m])) > 5
    
  3. Недоступность сервиса

    up{job="anon-bot"} == 0
    

Безопасность

Эндпоинты метрик не требуют аутентификации. В продакшене рекомендуется:

  • Ограничить доступ к эндпоинтам через firewall
  • Использовать reverse proxy с аутентификацией
  • Настроить TLS для HTTPS

🐳 Docker

Сборка образа

docker build -t anon-bot .

Запуск контейнера

docker run -d \
  --name anon-bot \
  --restart unless-stopped \
  -p 8081:8081 \
  -v $(pwd)/database:/app/database \
  -v $(pwd)/logs:/app/logs \
  -e BOT_TOKEN=your_bot_token_here \
  -e ADMINS=123456789,987654321 \
  -e DEBUG=false \
  anon-bot

Управление контейнером

# Просмотр логов
docker logs anon-bot

# Остановка контейнера
docker stop anon-bot

# Запуск контейнера
docker start anon-bot

# Удаление контейнера
docker rm anon-bot

# Перезапуск контейнера
docker restart anon-bot

Переменные окружения

Все переменные окружения из .env_example можно передать через -e:

docker run -d \
  --name anon-bot \
  -p 8081:8081 \
  -e BOT_TOKEN=your_token \
  -e ADMINS=123456789 \
  -e DEBUG=false \
  -e MAX_QUESTION_LENGTH=1000 \
  -e MAX_ANSWER_LENGTH=2000 \
  anon-bot

Volumes

  • ./database:/app/database - персистентное хранение базы данных
  • ./logs:/app/logs - персистентное хранение логов

Порты

  • 8081:8081 - порт для метрик и health check

Health Check

Контейнер включает встроенный health check:

# Проверка статуса
docker inspect --format='{{.State.Health.Status}}' anon-bot

# Просмотр деталей health check
docker inspect anon-bot | jq '.[0].State.Health'

Параметры health check:

  • Интервал проверки: 30 секунд
  • Таймаут: 10 секунд
  • Период запуска: 60 секунд (время на инициализацию)
  • Количество попыток: 3
  • Команда проверки: curl -f http://localhost:8081/health

Статусы health check:

  • starting - контейнер запускается
  • healthy - контейнер здоров
  • unhealthy - контейнер нездоров

🚦 Rate Limiting

AnonBot включает комплексную систему rate limiting для предотвращения ошибок Flood Control в Telegram Bot API. Система автоматически ограничивает скорость отправки сообщений и обрабатывает ошибки с повторными попытками.

Рекомендуемые настройки на основе лимитов Telegram API

Система настроена с учетом официальных лимитов Telegram Bot API:

  • 1 сообщение в секунду в личных чатах
  • 20 сообщений в минуту в групповых чатах (0.33 в секунду)
  • 30 запросов в секунду глобально

Настройки по умолчанию используют консервативные значения (50% от лимитов) для обеспечения стабильной работы.

Компоненты системы

1. Конфигурация (services/rate_limit_config.py)

  • RateLimitSettings: Основные настройки rate limiting
  • Конфигурации для разных окружений: development, production, strict
  • Адаптивная конфигурация: Автоматическая настройка на основе уровня ошибок

2. Rate Limiter (services/rate_limiter.py)

  • ChatRateLimiter: Ограничения для конкретного чата
  • GlobalRateLimiter: Глобальные ограничения для всех чатов
  • RetryHandler: Обработка повторных попыток с экспоненциальной задержкой
  • TelegramRateLimiter: Основной класс, объединяющий все компоненты

3. Сервис (services/rate_limit_service.py)

  • RateLimitService: Высокоуровневый сервис для управления rate limiting
  • Статистика: Отслеживание успешных/неудачных запросов
  • Адаптация: Автоматическая настройка конфигурации

4. Middleware (middlewares/rate_limit_middleware.py)

  • RateLimitMiddleware: Автоматическое применение rate limiting ко всем сообщениям
  • Прозрачная интеграция: Минимальные изменения в существующем коде

5. Validation Middleware (middlewares/validation_middleware.py)

  • ValidationMiddleware: Автоматическая валидация всех входящих данных
  • Безопасность: Защита от некорректных данных и атак
  • Логирование: Оптимизированное логирование с тихими декораторами для middleware

Настройка

Переменные окружения

Добавьте следующие переменные в ваш .env файл:

# Окружение для rate limiting (development/production/strict)
RATE_LIMIT_ENV=production

# Основные настройки rate limiting
RATE_LIMIT_MESSAGES_PER_SECOND=0.5
RATE_LIMIT_BURST_LIMIT=2
RATE_LIMIT_RETRY_MULTIPLIER=1.5
RATE_LIMIT_MAX_RETRY_DELAY=30.0
RATE_LIMIT_MAX_RETRIES=3

# Задержки для разных типов сообщений
RATE_LIMIT_VOICE_DELAY=2.0
RATE_LIMIT_MEDIA_DELAY=1.5
RATE_LIMIT_TEXT_DELAY=1.0

# Множители для разных типов чатов
RATE_LIMIT_PRIVATE_MULTIPLIER=1.0
RATE_LIMIT_GROUP_MULTIPLIER=0.8
RATE_LIMIT_CHANNEL_MULTIPLIER=0.6

# Глобальные ограничения
RATE_LIMIT_GLOBAL_MESSAGES_PER_SECOND=10.0
RATE_LIMIT_GLOBAL_BURST_LIMIT=20

Конфигурации по умолчанию

Production (по умолчанию)

  • 0.5 сообщений в секунду на чат (50% от лимита Telegram API)
  • Максимум 2 сообщения подряд
  • 3 повторные попытки при ошибках
  • Максимальная задержка 30 секунд
  • 20 глобальных запросов в секунду (из 30 доступных)

Development

  • 0.8 сообщений в секунду на чат (80% от лимита для тестирования)
  • Максимум 3 сообщения подряд
  • 2 повторные попытки при ошибках
  • Максимальная задержка 15 секунд

Strict

  • 0.3 сообщений в секунду на чат (30% от лимита для максимальной стабильности)
  • Максимум 1 сообщение подряд
  • 5 повторных попыток при ошибках
  • Максимальная задержка 60 секунд
  • 10 глобальных запросов в секунду (консервативные настройки)

Использование

Автоматическое использование (рекомендуется)

Rate limiting и валидация автоматически применяются ко всем сообщениям через middleware. Никаких изменений в коде не требуется.

ValidationMiddleware автоматически валидирует:

  • Все callback queries
  • Все сообщения
  • Telegram ID пользователей
  • Username (если есть)
  • Chat ID

Ручное использование

from services.rate_limiting.rate_limit_service import RateLimitService
from dependencies import get_rate_limit_service

# Получение сервиса через DI
rate_limit_service = get_rate_limit_service()

# Отправка сообщения с rate limiting
result = await rate_limit_service.send_with_rate_limit(
    bot.send_message,
    chat_id=user_id,
    text="Привет!"
)

Прямое использование rate limiter

from services.rate_limiting.rate_limiter import send_with_rate_limit

# Отправка с автоматическим rate limiting
result = await send_with_rate_limit(
    bot.send_message,
    chat_id=user_id,
    text="Привет!"
)

Ручное использование валидатора

from services.validation import InputValidator
from dependencies import get_validator

# Получение валидатора через DI
validator = get_validator()

# Валидация текста вопроса
result = validator.validate_question_text(text, max_length=1000)
if not result:
    print(f"Ошибка: {result.error_message}")
else:
    sanitized_text = result.sanitized_value

# Валидация callback data
result = validator.validate_callback_data(callback_data)
if not result:
    await callback.answer("❌ Неверные данные", show_alert=True)
    return

Административные функции

Доступ к управлению rate limiting осуществляется через админскую панель:

  1. Перейти в админку: /admin
  2. Нажать кнопку "🚦 Rate Limiting"
  3. Выбрать нужное действие:
    • 📊 Статистика Rate Limiting - показывает подробную статистику:
      • Общее количество запросов
      • Процент успеха/ошибок
      • Количество RetryAfter ошибок
      • Среднее время ожидания
    • 🔄 Сбросить статистику - сбрасывает всю статистику rate limiting
    • ⚙️ Адаптировать конфигурацию - адаптирует настройки на основе текущей производительности

Важно: Доступ к функциям rate limiting имеют только администраторы бота (не суперпользователи).

Мониторинг

Логирование

Система логирует:

  • Rate limiting события (DEBUG уровень)
  • RetryAfter ошибки (WARNING уровень)
  • Критические ошибки (ERROR уровень)

Статистика

RateLimitService отслеживает:

  • Общее количество запросов
  • Успешные/неудачные запросы
  • Типы ошибок (RetryAfter, API ошибки)
  • Время ожидания

Адаптивная конфигурация

Система автоматически адаптирует настройки:

  • При >10% ошибок: ужесточает ограничения
  • При <1% ошибок: ослабляет ограничения
  • Требует минимум 100 запросов для адаптации

Интеграция с DI

Rate limiting полностью интегрирован в систему инъекции зависимостей:

# В обработчиках
async def some_handler(
    message: Message,
    rate_limit_service: RateLimitService
):
    # rate_limit_service автоматически инжектируется
    pass

Архитектурные особенности

Соблюдение принципов

  1. Single Responsibility: Каждый компонент отвечает за свою задачу
  2. Open/Closed: Легко расширяется новыми типами ограничений
  3. Dependency Inversion: Зависит от абстракций, а не от конкретных реализаций

Производительность

  • Минимальные накладные расходы
  • Эффективное управление памятью
  • Асинхронная обработка

Надежность

  • Обработка всех типов ошибок Telegram API
  • Экспоненциальная задержка при повторных попытках
  • Автоматическое восстановление после ошибок

Устранение неполадок

Высокий процент ошибок

  1. Проверьте статистику: /ratelimit_stats
  2. Адаптируйте конфигурацию: /adapt_ratelimit
  3. При необходимости ужесточите настройки в .env

Медленная отправка сообщений

  1. Проверьте настройки RATE_LIMIT_MESSAGES_PER_SECOND
  2. Увеличьте значение для более быстрой отправки
  3. Убедитесь, что не превышаете лимиты Telegram API

Проблемы с интеграцией

  1. Убедитесь, что middleware зарегистрирован в loader.py

  2. Ошибка TypeError: got an unexpected keyword argument 'dispatcher':

    • Причина: aiogram 3.x передает dispatcher во все обработчики сообщений, но @inject_all не обрабатывает его правильно
    • Решение: Используйте специализированные декораторы вместо @inject_all:
      # ❌ Неправильно
      @inject_all
      async def process_question(message: Message, dispatcher, ...):
      
      # ✅ Правильно
      @inject_question_services
      async def process_question(message: Message, question_service: QuestionService, ...):
      
    • Преимущества: Нет проблем с dispatcher, меньше зависимостей, лучшая производительность
  3. Проверьте, что все зависимости корректно импортированы

  4. Проверьте логи на наличие ошибок инициализации

🔮 Планы развития

  • Система суперпользователей - расширенные права для модерации с отображением информации об авторах
  • Система инъекции зависимостей - современная архитектура с DI для лучшей тестируемости
  • Система разрешений - гибкая система разрешений с соблюдением принципа OCP
  • Prometheus метрики - мониторинг производительности и ошибок
  • Rate limiting - защита от спама и DDoS с автоматической адаптацией
  • Реорганизация структуры проекта - улучшенная архитектура с логической группировкой сервисов
  • Локальная нумерация вопросов - каждый пользователь видит свои вопросы с номерами #1, #2, #3... вместо глобальных ID
  • Unit-тесты - полное покрытие тестами с использованием DI
  • Кэширование Redis - оптимизация производительности
  • Рассылка сообщений
  • Экспорт данных
  • Веб-интерфейс для админов
  • Поддержка медиафайлов в вопросах
  • Категории вопросов
  • Модерация контента
  • Аналитика и отчеты

📝 Changelog

v1.7.0 - Продвинутая система логирования (2025-01-27)

Новые возможности

  • Автоматические декораторы логирования - @log_function_call, @log_business_event, @log_fsm_transition
  • Контекстное логирование - LoggingContext с дополнительной информацией
  • Оптимизированные декораторы - @log_middleware, @log_utility для предотвращения избыточного логирования
  • FSM отслеживание - автоматическое логирование переходов между состояниями
  • Специальные функции - log_question_created, log_user_created, log_user_blocked
  • Тихие декораторы - предотвращение рекурсивного логирования в middleware

🔧 Улучшения

  • 100% покрытие логированием - все функции теперь логируются автоматически
  • Улучшенная производительность - оптимизированное логирование для middleware
  • Детальная диагностика - полная трассировка выполнения функций
  • Контекстная информация - автоматическое извлечение user_id, question_id и других параметров

📁 Новые файлы

  • services/infrastructure/logging_decorators.py - декораторы для автоматического логирования
  • services/infrastructure/logging_utils.py - утилиты для контекстного логирования

v1.6.0 - Локальная нумерация вопросов (2025-01-27)

Новые возможности

  • Локальная нумерация вопросов: Каждый пользователь теперь видит свои вопросы с номерами #1, #2, #3... вместо глобальных ID
  • Автоматическая нумерация: Триггеры БД автоматически присваивают и пересчитывают номера вопросов
  • Умная нумерация: Удаленные вопросы не участвуют в нумерации, номера автоматически пересчитываются при удалении

🔧 Технические изменения

  • Новое поле БД: user_question_number в таблице questions
  • Триггеры БД:
    • calculate_user_question_number - автоматическое вычисление номера при создании
    • recalculate_user_question_numbers_on_delete - пересчет номеров при удалении
  • Индексы: Оптимизированные индексы для быстрого поиска по локальным номерам
  • Модель Question: Новый метод get_display_number() для получения номера для отображения

🐛 Исправления

  • Отображение номеров: Исправлена проблема с несоответствием номеров в тексте сообщений и на кнопках
  • Пагинация: Обновлена для работы с локальными номерами
  • CRUD операции: Обновлены для поддержки автоматической нумерации

📊 Улучшения UX

  • Интуитивная нумерация: Пользователи видят последовательные номера своих вопросов
  • Консистентность: Номера в тексте и на кнопках всегда совпадают
  • Автоматизация: Никаких ручных действий для поддержания нумерации
Description
Бот для анонимных вопросов ТГ
Readme 218 KiB
Languages
Python 98.7%
Shell 0.9%
Dockerfile 0.4%