281 lines
10 KiB
Python
281 lines
10 KiB
Python
"""
|
||
Сервис для управления вопросами
|
||
"""
|
||
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)
|