Implement user-specific question numbering and update database schema. Added triggers for automatic question numbering and adjustments upon deletion. Enhanced CRUD operations to manage user_question_number effectively.

This commit is contained in:
2025-09-06 18:35:12 +03:00
parent 50be010026
commit 596a2fa813
111 changed files with 16847 additions and 65 deletions

174
loader.py Normal file
View File

@@ -0,0 +1,174 @@
"""
Инициализация бота, диспетчера и базы данных
"""
import asyncio
from aiogram import Bot, Dispatcher
from aiogram.enums import ParseMode
from aiogram.fsm.storage.memory import MemoryStorage
from config import config
from config.constants import ALLOWED_UPDATES
from dependencies import DependencyMiddleware, get_dependencies
from handlers import admin, answers, errors, questions, start
from middlewares.rate_limit_middleware import RateLimitMiddleware
from middlewares.validation_middleware import ValidationMiddleware
from services.infrastructure.database import DatabaseService
from services.infrastructure.logger import get_logger
# Настройка логирования
logger = get_logger(__name__)
class BotLoader:
"""Класс для инициализации и запуска бота"""
def __init__(self):
self.bot: Bot = None
self.dp: Dispatcher = None
self.db: DatabaseService = None
async def init_bot(self) -> Bot:
"""Инициализация бота"""
logger.info("🤖 Инициализация Telegram бота")
self.bot = Bot(token=config.BOT_TOKEN)
# Устанавливаем parse_mode по умолчанию для aiogram 3.3.0
self.bot.parse_mode = ParseMode.HTML
logger.info("✅ Бот успешно инициализирован")
return self.bot
async def init_dispatcher(self) -> Dispatcher:
"""Инициализация диспетчера"""
logger.info("📡 Инициализация диспетчера")
# Используем MemoryStorage для FSM
storage = MemoryStorage()
self.dp = Dispatcher(storage=storage)
# Инициализируем зависимости
logger.info("🏗️ Инициализация системы инъекции зависимостей")
deps = get_dependencies()
await deps.init()
# Добавляем middleware для инъекции зависимостей
self.dp.update.middleware(DependencyMiddleware(deps))
logger.info("✅ DependencyMiddleware зарегистрирован")
# Добавляем middleware для rate limiting
self.dp.update.middleware(RateLimitMiddleware())
logger.info("✅ RateLimitMiddleware зарегистрирован")
# Добавляем middleware для валидации
validation_middleware = ValidationMiddleware(deps.validator)
self.dp.update.middleware(validation_middleware)
logger.info("✅ ValidationMiddleware зарегистрирован")
# Регистрируем обработчики
self._register_handlers()
logger.info("✅ Диспетчер успешно инициализирован")
return self.dp
async def init_database(self) -> DatabaseService:
"""Инициализация базы данных"""
logger.info(f"💾 Инициализация базы данных: {config.DATABASE_PATH}")
self.db = DatabaseService(config.DATABASE_PATH)
await self.db.init()
logger.info("✅ База данных успешно инициализирована")
return self.db
def _register_handlers(self):
"""Регистрация всех обработчиков"""
logger.info("🔧 Регистрация обработчиков")
# Обработчики команд
self.dp.include_router(start.router)
self.dp.include_router(questions.router)
self.dp.include_router(answers.router)
self.dp.include_router(admin.router)
# Обработчик ошибок (должен быть последним)
self.dp.include_router(errors.router)
logger.info("Все обработчики зарегистрированы")
async def start_polling(self):
"""Запуск бота в режиме polling"""
try:
logger.info("🚀 Запуск бота в режиме polling")
# Инициализируем компоненты
await self.init_bot()
await self.init_dispatcher()
await self.init_database()
# Уведомляем администраторов о запуске
await self._notify_admins_startup()
# Запускаем polling
logger.info("🔄 Начинаем polling...")
await self.dp.start_polling(
self.bot,
allowed_updates=ALLOWED_UPDATES
)
except Exception as e:
logger.error(f"💥 Ошибка при запуске бота: {e}")
raise
finally:
await self.cleanup()
async def _notify_admins_startup(self):
"""Уведомление администраторов о запуске бота"""
if not config.ADMINS:
logger.warning("⚠️ Список администраторов пуст")
return
logger.info(f"📢 Уведомление {len(config.ADMINS)} администраторов о запуске")
message = "🤖 <b>Бот запущен!</b>\n\n" \
"Анонимный бот для вопросов готов к работе."
for admin_id in config.ADMINS:
try:
await self.bot.send_message(admin_id, message)
logger.info(f"✅ Уведомление отправлено админу {admin_id}")
except Exception as e:
logger.warning(f"⚠️ Не удалось отправить уведомление админу {admin_id}: {e}")
async def cleanup(self):
"""Очистка ресурсов при завершении"""
logger.info("🧹 Очистка ресурсов")
# Закрываем зависимости
try:
deps = get_dependencies()
await deps.close()
logger.info("✅ Зависимости закрыты")
except Exception as e:
logger.warning(f"⚠️ Ошибка при закрытии зависимостей: {e}")
if self.bot:
await self.bot.session.close()
logger.info("✅ Сессия бота закрыта")
if self.db:
await self.db.close()
logger.info("✅ Соединение с БД закрыто")
logger.info("🛑 Бот остановлен")
# Создаем глобальный экземпляр загрузчика
loader = BotLoader()
async def main():
"""Главная функция для запуска бота"""
await loader.start_polling()
if __name__ == "__main__":
try:
asyncio.run(main())
except KeyboardInterrupt:
logger.info("Получен сигнал остановки")
except Exception as e:
logger.error(f"Критическая ошибка: {e}")
raise