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:
280
services/business/question_service.py
Normal file
280
services/business/question_service.py
Normal 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)
|
||||
Reference in New Issue
Block a user