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

View File

@@ -0,0 +1,280 @@
"""
Сервис для управления вопросами
"""
from datetime import datetime
from typing import List, Optional, Tuple
from aiogram import Bot
from models.question import Question, QuestionStatus
from services.infrastructure.database import DatabaseService
from services.utils import UtilsService
from services.infrastructure.logger import get_logger
from services.infrastructure.metrics import get_metrics_service
from services.infrastructure.logging_decorators import log_function_call, log_business_event
from services.infrastructure.logging_utils import log_question_created, log_question_answered
logger = get_logger(__name__)
class QuestionService:
"""Сервис для управления вопросами"""
def __init__(self, database: DatabaseService, utils: UtilsService):
self.database = database
self.utils = utils
self.metrics = get_metrics_service()
@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) -> Question:
"""
Создание нового вопроса
Args:
from_user_id: ID автора вопроса
to_user_id: ID получателя вопроса
message_text: Текст вопроса
Returns:
Созданный объект вопроса
"""
try:
question = Question(
from_user_id=from_user_id,
to_user_id=to_user_id,
message_text=message_text.strip(),
status=QuestionStatus.PENDING,
created_at=datetime.now(),
is_anonymous=True
)
question = await self.database.create_question(question)
self.metrics.increment_questions("created")
return question
except Exception as e:
logger.error(f"Ошибка при создании вопроса от {from_user_id} к {to_user_id}: {e}")
raise
@log_function_call(log_params=True, log_result=True)
async def get_question(self, question_id: int) -> Optional[Question]:
"""
Получение вопроса по ID
Args:
question_id: ID вопроса
Returns:
Объект вопроса или None
"""
try:
return await self.database.get_question(question_id)
except Exception as e:
logger.error(f"Ошибка при получении вопроса {question_id}: {e}")
return None
@log_function_call(log_params=True, log_result=True)
async def get_user_questions(self, user_id: int, limit: int = 50, offset: int = 0) -> List[Question]:
"""
Получение вопросов пользователя
Args:
user_id: ID пользователя
limit: Лимит вопросов
offset: Смещение
Returns:
Список вопросов
"""
try:
return await self.database.get_user_questions(user_id, limit=limit, offset=offset)
except Exception as e:
logger.error(f"Ошибка при получении вопросов пользователя {user_id}: {e}")
return []
@log_business_event("answer_question", log_params=True, log_result=True)
async def answer_question(self, question_id: int, answer_text: str) -> Optional[Question]:
"""
Ответ на вопрос
Args:
question_id: ID вопроса
answer_text: Текст ответа
Returns:
Обновленный объект вопроса или None
"""
try:
question = await self.database.get_question(question_id)
if not question:
return None
question.mark_as_answered(answer_text.strip())
question.answered_at = datetime.now()
question = await self.database.update_question(question)
self.metrics.increment_answers("sent")
return question
except Exception as e:
logger.error(f"Ошибка при ответе на вопрос {question_id}: {e}")
return None
@log_business_event("reject_question", log_params=True, log_result=True)
async def reject_question(self, question_id: int) -> Optional[Question]:
"""
Отклонение вопроса
Args:
question_id: ID вопроса
Returns:
Обновленный объект вопроса или None
"""
try:
question = await self.database.get_question(question_id)
if not question:
return None
question.mark_as_rejected()
question.answered_at = datetime.now()
question = await self.database.update_question(question)
self.metrics.increment_questions("rejected")
return question
except Exception as e:
logger.error(f"Ошибка при отклонении вопроса {question_id}: {e}")
return None
@log_business_event("delete_question", log_params=True, log_result=True)
async def delete_question(self, question_id: int) -> Optional[Question]:
"""
Удаление вопроса
Args:
question_id: ID вопроса
Returns:
Обновленный объект вопроса или None
"""
try:
question = await self.database.get_question(question_id)
if not question:
return None
question.mark_as_deleted()
question.answered_at = datetime.now()
question = await self.database.update_question(question)
self.metrics.increment_questions("deleted")
return question
except Exception as e:
logger.error(f"Ошибка при удалении вопроса {question_id}: {e}")
return None
@log_business_event("edit_answer", log_params=True, log_result=True)
async def edit_answer(self, question_id: int, new_answer_text: str) -> Optional[Question]:
"""
Редактирование ответа на вопрос
Args:
question_id: ID вопроса
new_answer_text: Новый текст ответа
Returns:
Обновленный объект вопроса или None
"""
try:
question = await self.database.get_question(question_id)
if not question:
return None
question.answer_text = new_answer_text.strip()
question.answered_at = datetime.now()
question = await self.database.update_question(question)
self.metrics.increment_answers("edited")
return question
except Exception as e:
logger.error(f"Ошибка при редактировании ответа на вопрос {question_id}: {e}")
return None
@log_function_call(log_params=True, log_result=True)
def validate_question_text(self, text: str, max_length: int = 1000) -> Tuple[bool, str]:
"""
Валидация текста вопроса
Args:
text: Текст вопроса
max_length: Максимальная длина
Returns:
Кортеж (валидность, сообщение об ошибке)
"""
return self.utils.is_valid_question_text(text, max_length)
@log_function_call(log_params=True, log_result=True)
def validate_answer_text(self, text: str, max_length: int = 2000) -> Tuple[bool, str]:
"""
Валидация текста ответа
Args:
text: Текст ответа
max_length: Максимальная длина
Returns:
Кортеж (валидность, сообщение об ошибке)
"""
return self.utils.is_valid_answer_text(text, max_length)
@log_function_call(log_params=True, log_result=True)
async def send_answer_to_author(self, bot: Bot, question: Question, answer_text: str) -> bool:
"""
Отправка ответа автору вопроса
Args:
bot: Экземпляр бота
question: Объект вопроса
answer_text: Текст ответа
Returns:
True если успешно отправлено
"""
try:
await self.utils.send_answer_to_author(bot, question, answer_text)
self.metrics.increment_answers("delivered")
return True
except Exception as e:
logger.error(f"Ошибка при отправке ответа автору вопроса {question.id}: {e}")
self.metrics.increment_answers("delivery_failed")
return False
@log_function_call(log_params=True, log_result=True)
def format_question_info(self, question: Question, show_answer: bool = False) -> str:
"""
Форматирование информации о вопросе
Args:
question: Объект вопроса
show_answer: Показывать ли ответ
Returns:
Отформатированная строка
"""
return self.utils.format_question_info(question, show_answer)
@log_function_call(log_params=True, log_result=True)
def get_question_preview(self, question: Question, max_length: int = 50) -> str:
"""
Получение превью вопроса
Args:
question: Объект вопроса
max_length: Максимальная длина превью
Returns:
Превью вопроса
"""
return question.get_question_preview(max_length)