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:
206
database/crud.py
206
database/crud.py
@@ -331,21 +331,31 @@ class QuestionCRUD(BaseCRUD):
|
||||
"""Создание нового вопроса"""
|
||||
logger.info(f"❓ Создание вопроса от {question.from_user_id} к {question.to_user_id}")
|
||||
async with self.get_connection() as conn:
|
||||
# Вычисляем user_question_number для получателя
|
||||
if question.user_question_number is None:
|
||||
cursor = await conn.execute("""
|
||||
SELECT COALESCE(MAX(user_question_number), 0) + 1
|
||||
FROM questions
|
||||
WHERE to_user_id = ? AND status != 'deleted'
|
||||
""", (question.to_user_id,))
|
||||
result = await cursor.fetchone()
|
||||
question.user_question_number = result[0] if result else 1
|
||||
|
||||
cursor = await conn.execute("""
|
||||
INSERT INTO questions
|
||||
(from_user_id, to_user_id, message_text, answer_text, is_anonymous,
|
||||
message_id, created_at, answered_at, is_read, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
message_id, created_at, answered_at, is_read, status, user_question_number)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", (
|
||||
question.from_user_id, question.to_user_id, question.message_text,
|
||||
question.answer_text, question.is_anonymous, question.message_id,
|
||||
question.created_at.isoformat() if question.created_at else None,
|
||||
question.answered_at.isoformat() if question.answered_at else None,
|
||||
question.is_read, question.status.value
|
||||
question.is_read, question.status.value, question.user_question_number
|
||||
))
|
||||
question.id = cursor.lastrowid
|
||||
await conn.commit()
|
||||
logger.info(f"✅ Вопрос создан с ID: {question.id}")
|
||||
logger.info(f"✅ Вопрос создан с ID: {question.id}, номер для пользователя: {question.user_question_number}")
|
||||
return question
|
||||
|
||||
async def create_batch(self, questions: List[Question]) -> List[Question]:
|
||||
@@ -356,6 +366,27 @@ class QuestionCRUD(BaseCRUD):
|
||||
logger.info(f"📦 Создание {len(questions)} вопросов batch операцией")
|
||||
async with self.get_connection() as conn:
|
||||
try:
|
||||
# Группируем вопросы по получателям для вычисления user_question_number
|
||||
questions_by_user = {}
|
||||
for question in questions:
|
||||
if question.to_user_id not in questions_by_user:
|
||||
questions_by_user[question.to_user_id] = []
|
||||
questions_by_user[question.to_user_id].append(question)
|
||||
|
||||
# Вычисляем user_question_number для каждого пользователя
|
||||
for to_user_id, user_questions in questions_by_user.items():
|
||||
cursor = await conn.execute("""
|
||||
SELECT COALESCE(MAX(user_question_number), 0)
|
||||
FROM questions
|
||||
WHERE to_user_id = ? AND status != 'deleted'
|
||||
""", (to_user_id,))
|
||||
result = await cursor.fetchone()
|
||||
start_number = (result[0] if result else 0) + 1
|
||||
|
||||
for i, question in enumerate(user_questions):
|
||||
if question.user_question_number is None:
|
||||
question.user_question_number = start_number + i
|
||||
|
||||
# Подготавливаем данные для batch вставки
|
||||
batch_data = []
|
||||
for question in questions:
|
||||
@@ -364,15 +395,15 @@ class QuestionCRUD(BaseCRUD):
|
||||
question.answer_text, question.is_anonymous, question.message_id,
|
||||
question.created_at.isoformat() if question.created_at else None,
|
||||
question.answered_at.isoformat() if question.answered_at else None,
|
||||
question.is_read, question.status.value
|
||||
question.is_read, question.status.value, question.user_question_number
|
||||
))
|
||||
|
||||
# Выполняем batch вставку
|
||||
cursor = await conn.executemany("""
|
||||
INSERT INTO questions
|
||||
(from_user_id, to_user_id, message_text, answer_text, is_anonymous,
|
||||
message_id, created_at, answered_at, is_read, status)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
message_id, created_at, answered_at, is_read, status, user_question_number)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""", batch_data)
|
||||
|
||||
# Обновляем ID для всех созданных вопросов
|
||||
@@ -393,7 +424,11 @@ class QuestionCRUD(BaseCRUD):
|
||||
"""Получение вопроса по ID"""
|
||||
async with self.get_connection() as conn:
|
||||
async with conn.execute("""
|
||||
SELECT * FROM questions WHERE id = ?
|
||||
SELECT
|
||||
id, from_user_id, to_user_id, message_text, answer_text,
|
||||
is_anonymous, message_id, created_at, answered_at,
|
||||
is_read, status, user_question_number
|
||||
FROM questions WHERE id = ?
|
||||
""", (question_id,)) as cursor:
|
||||
row = await cursor.fetchone()
|
||||
if row:
|
||||
@@ -408,7 +443,7 @@ class QuestionCRUD(BaseCRUD):
|
||||
SELECT
|
||||
q.id, q.from_user_id, q.to_user_id, q.message_text, q.answer_text,
|
||||
q.is_anonymous, q.message_id, q.created_at, q.answered_at,
|
||||
q.is_read, q.status
|
||||
q.is_read, q.status, q.user_question_number
|
||||
FROM questions q
|
||||
WHERE q.to_user_id = ?
|
||||
"""
|
||||
@@ -418,7 +453,7 @@ class QuestionCRUD(BaseCRUD):
|
||||
query += " AND q.status = ?"
|
||||
params.append(status.value)
|
||||
|
||||
query += " ORDER BY q.created_at DESC LIMIT ? OFFSET ?"
|
||||
query += " ORDER BY q.user_question_number DESC LIMIT ? OFFSET ?"
|
||||
params.extend([limit, offset])
|
||||
|
||||
async with conn.execute(query, params) as cursor:
|
||||
@@ -455,7 +490,7 @@ class QuestionCRUD(BaseCRUD):
|
||||
query += " AND q.status = ?"
|
||||
params.append(status.value)
|
||||
|
||||
query += " ORDER BY q.created_at DESC LIMIT ? OFFSET ?"
|
||||
query += " ORDER BY q.user_question_number DESC LIMIT ? OFFSET ?"
|
||||
params.extend([limit, offset])
|
||||
|
||||
async with conn.execute(query, params) as cursor:
|
||||
@@ -467,28 +502,28 @@ class QuestionCRUD(BaseCRUD):
|
||||
if question is None:
|
||||
print(f"Предупреждение: вопрос не создан для строки {row[:11]}")
|
||||
continue
|
||||
|
||||
author = None
|
||||
if row[11]: # Если есть author_id
|
||||
author = User(
|
||||
id=row[11],
|
||||
telegram_id=row[12],
|
||||
username=row[13],
|
||||
first_name=row[14] or "",
|
||||
last_name=row[15],
|
||||
chat_id=row[16],
|
||||
profile_link=row[17] or "",
|
||||
is_active=bool(row[18]),
|
||||
is_superuser=bool(row[19]),
|
||||
created_at=self._parse_datetime(row[20]),
|
||||
updated_at=self._parse_datetime(row[21]),
|
||||
banned_until=self._parse_datetime(row[22]),
|
||||
ban_reason=row[23]
|
||||
)
|
||||
result.append((question, author))
|
||||
except Exception as e:
|
||||
print(f"Ошибка при создании вопроса из строки {row[:11]}: {e}")
|
||||
continue
|
||||
|
||||
author = None
|
||||
if row[11]: # Если есть author_id
|
||||
author = User(
|
||||
id=row[11],
|
||||
telegram_id=row[12],
|
||||
username=row[13],
|
||||
first_name=row[14] or "",
|
||||
last_name=row[15],
|
||||
chat_id=row[16],
|
||||
profile_link=row[17] or "",
|
||||
is_active=bool(row[18]),
|
||||
is_superuser=bool(row[19]),
|
||||
created_at=self._parse_datetime(row[20]),
|
||||
updated_at=self._parse_datetime(row[21]),
|
||||
banned_until=self._parse_datetime(row[22]),
|
||||
ban_reason=row[23]
|
||||
)
|
||||
result.append((question, author))
|
||||
return result
|
||||
|
||||
async def get_by_to_user_cursor(
|
||||
@@ -506,7 +541,7 @@ class QuestionCRUD(BaseCRUD):
|
||||
SELECT
|
||||
q.id, q.from_user_id, q.to_user_id, q.message_text, q.answer_text,
|
||||
q.is_anonymous, q.message_id, q.created_at, q.answered_at,
|
||||
q.is_read, q.status
|
||||
q.is_read, q.status, q.user_question_number
|
||||
FROM questions q
|
||||
WHERE q.to_user_id = ?
|
||||
AND (q.created_at < ? OR (q.created_at = ? AND q.id < ?))
|
||||
@@ -519,7 +554,7 @@ class QuestionCRUD(BaseCRUD):
|
||||
SELECT
|
||||
q.id, q.from_user_id, q.to_user_id, q.message_text, q.answer_text,
|
||||
q.is_anonymous, q.message_id, q.created_at, q.answered_at,
|
||||
q.is_read, q.status
|
||||
q.is_read, q.status, q.user_question_number
|
||||
FROM questions q
|
||||
WHERE q.to_user_id = ?
|
||||
AND (q.created_at > ? OR (q.created_at = ? AND q.id > ?))
|
||||
@@ -555,7 +590,7 @@ class QuestionCRUD(BaseCRUD):
|
||||
query += " AND q.status = ?"
|
||||
params.append(status.value)
|
||||
|
||||
query += " ORDER BY q.created_at ASC LIMIT ? OFFSET ?"
|
||||
query += " ORDER BY q.user_question_number ASC LIMIT ? OFFSET ?"
|
||||
params.extend([limit, offset])
|
||||
|
||||
async with conn.execute(query, params) as cursor:
|
||||
@@ -566,27 +601,105 @@ class QuestionCRUD(BaseCRUD):
|
||||
"""Обновление вопроса"""
|
||||
logger.info(f"📝 Обновление вопроса {question.id} (статус: {question.status.value})")
|
||||
async with self.get_connection() as conn:
|
||||
await conn.execute("""
|
||||
UPDATE questions SET
|
||||
answer_text = ?, status = ?, answered_at = ?, is_read = ?
|
||||
WHERE id = ?
|
||||
""", (
|
||||
question.answer_text, question.status.value,
|
||||
question.answered_at.isoformat() if question.answered_at else None,
|
||||
question.is_read, question.id
|
||||
))
|
||||
# Если вопрос помечается как удаленный, нужно пересчитать номера
|
||||
if question.status.value == 'deleted':
|
||||
# Получаем текущий статус вопроса
|
||||
cursor = await conn.execute("""
|
||||
SELECT status, to_user_id, user_question_number FROM questions WHERE id = ?
|
||||
""", (question.id,))
|
||||
old_info = await cursor.fetchone()
|
||||
|
||||
if old_info and old_info[0] != 'deleted':
|
||||
# Вопрос переходит в статус 'deleted', пересчитываем номера
|
||||
to_user_id, deleted_number = old_info[1], old_info[2]
|
||||
|
||||
# Сначала обновляем вопрос, устанавливая user_question_number в NULL
|
||||
await conn.execute("""
|
||||
UPDATE questions SET
|
||||
answer_text = ?, status = ?, answered_at = ?, is_read = ?,
|
||||
user_question_number = NULL
|
||||
WHERE id = ?
|
||||
""", (
|
||||
question.answer_text, question.status.value,
|
||||
question.answered_at.isoformat() if question.answered_at else None,
|
||||
question.is_read, question.id
|
||||
))
|
||||
|
||||
# Обновляем объект question, устанавливая user_question_number в None
|
||||
question.user_question_number = None
|
||||
|
||||
# Пересчитываем номера для всех вопросов пользователя после удаленного
|
||||
await conn.execute("""
|
||||
UPDATE questions
|
||||
SET user_question_number = user_question_number - 1
|
||||
WHERE to_user_id = ?
|
||||
AND user_question_number > ?
|
||||
AND status != 'deleted'
|
||||
AND id != ?
|
||||
""", (to_user_id, deleted_number, question.id))
|
||||
|
||||
logger.info(f"🗑️ Вопрос {question.id} помечен как удаленный, пересчитаны номера для пользователя {to_user_id}")
|
||||
else:
|
||||
# Обычное обновление
|
||||
await conn.execute("""
|
||||
UPDATE questions SET
|
||||
answer_text = ?, status = ?, answered_at = ?, is_read = ?
|
||||
WHERE id = ?
|
||||
""", (
|
||||
question.answer_text, question.status.value,
|
||||
question.answered_at.isoformat() if question.answered_at else None,
|
||||
question.is_read, question.id
|
||||
))
|
||||
else:
|
||||
# Обычное обновление
|
||||
await conn.execute("""
|
||||
UPDATE questions SET
|
||||
answer_text = ?, status = ?, answered_at = ?, is_read = ?
|
||||
WHERE id = ?
|
||||
""", (
|
||||
question.answer_text, question.status.value,
|
||||
question.answered_at.isoformat() if question.answered_at else None,
|
||||
question.is_read, question.id
|
||||
))
|
||||
|
||||
await conn.commit()
|
||||
logger.info(f"✅ Вопрос {question.id} обновлен")
|
||||
return question
|
||||
|
||||
async def delete(self, question_id: int) -> bool:
|
||||
"""Удаление вопроса"""
|
||||
"""Удаление вопроса с пересчетом user_question_number"""
|
||||
async with self.get_connection() as conn:
|
||||
# Сначала получаем информацию о вопросе
|
||||
cursor = await conn.execute("""
|
||||
SELECT to_user_id, user_question_number FROM questions WHERE id = ?
|
||||
""", (question_id,))
|
||||
question_info = await cursor.fetchone()
|
||||
|
||||
if not question_info:
|
||||
return False
|
||||
|
||||
to_user_id, deleted_number = question_info
|
||||
|
||||
# Удаляем вопрос
|
||||
cursor = await conn.execute("""
|
||||
DELETE FROM questions WHERE id = ?
|
||||
""", (question_id,))
|
||||
|
||||
if cursor.rowcount == 0:
|
||||
return False
|
||||
|
||||
# Пересчитываем номера для всех вопросов пользователя после удаленного
|
||||
await conn.execute("""
|
||||
UPDATE questions
|
||||
SET user_question_number = user_question_number - 1
|
||||
WHERE to_user_id = ?
|
||||
AND user_question_number > ?
|
||||
AND status != 'deleted'
|
||||
""", (to_user_id, deleted_number))
|
||||
|
||||
await conn.commit()
|
||||
return cursor.rowcount > 0
|
||||
logger.info(f"🗑️ Вопрос {question_id} удален, пересчитаны номера для пользователя {to_user_id}")
|
||||
return True
|
||||
|
||||
async def get_unread_count(self, to_user_id: int) -> int:
|
||||
"""Получение количества непрочитанных вопросов"""
|
||||
@@ -641,7 +754,7 @@ class QuestionCRUD(BaseCRUD):
|
||||
"""Преобразование строки БД в объект Question"""
|
||||
# Проверяем, что все необходимые поля присутствуют
|
||||
if len(row) < 11:
|
||||
raise ValueError(f"Недостаточно данных в строке БД: {len(row)} колонок, ожидается 11")
|
||||
raise ValueError(f"Недостаточно данных в строке БД: {len(row)} колонок, ожидается минимум 11")
|
||||
|
||||
# Проверяем статус
|
||||
status_value = row[10]
|
||||
@@ -665,7 +778,8 @@ class QuestionCRUD(BaseCRUD):
|
||||
created_at=self._parse_datetime(row[7]),
|
||||
answered_at=self._parse_datetime(row[8]),
|
||||
is_read=bool(row[9]),
|
||||
status=status
|
||||
status=status,
|
||||
user_question_number=row[11] if len(row) > 11 else None
|
||||
)
|
||||
return question
|
||||
|
||||
|
||||
Reference in New Issue
Block a user