Добавлен функционал для отслеживания истории банов пользователей.
- Введена новая модель `BlacklistHistoryRecord` для хранения информации о банах и разблокировках. - Обновлены методы `set_user_blacklist` и `delete_user_blacklist` в `AsyncBotDB` для логирования событий в историю. - Обновлена схема базы данных для создания таблицы `blacklist_history` и соответствующих индексов. - Обновлены тесты для проверки нового функционала и обработки ошибок при записи в историю.
This commit is contained in:
@@ -3,7 +3,7 @@ from datetime import datetime
|
||||
from typing import Optional, List, Dict, Any, Tuple
|
||||
from database.repository_factory import RepositoryFactory
|
||||
from database.models import (
|
||||
User, BlacklistUser, UserMessage, TelegramPost, PostContent,
|
||||
User, BlacklistUser, BlacklistHistoryRecord, UserMessage, TelegramPost, PostContent,
|
||||
Admin, AudioMessage
|
||||
)
|
||||
|
||||
@@ -196,7 +196,10 @@ class AsyncBotDB:
|
||||
date_to_unban: int = None,
|
||||
ban_author: Optional[int] = None,
|
||||
):
|
||||
"""Добавляет пользователя в черный список."""
|
||||
"""
|
||||
Добавляет пользователя в черный список.
|
||||
Также создает запись в истории банов для отслеживания.
|
||||
"""
|
||||
blacklist_user = BlacklistUser(
|
||||
user_id=user_id,
|
||||
message_for_user=message_for_user,
|
||||
@@ -204,9 +207,40 @@ class AsyncBotDB:
|
||||
ban_author=ban_author,
|
||||
)
|
||||
await self.factory.blacklist.add_user(blacklist_user)
|
||||
|
||||
# Логируем в историю банов
|
||||
try:
|
||||
date_ban = int(datetime.now().timestamp())
|
||||
history_record = BlacklistHistoryRecord(
|
||||
user_id=user_id,
|
||||
message_for_user=message_for_user,
|
||||
date_ban=date_ban,
|
||||
date_unban=None, # Будет установлено при разбане
|
||||
ban_author=ban_author,
|
||||
)
|
||||
await self.factory.blacklist_history.add_record_on_ban(history_record)
|
||||
except Exception as e:
|
||||
# Ошибка записи в историю не должна ломать процесс бана
|
||||
self.logger.error(
|
||||
f"Ошибка записи в историю банов для user_id={user_id}: {e}"
|
||||
)
|
||||
|
||||
async def delete_user_blacklist(self, user_id: int) -> bool:
|
||||
"""Удаляет пользователя из черного списка."""
|
||||
"""
|
||||
Удаляет пользователя из черного списка.
|
||||
Также обновляет запись в истории банов, устанавливая date_unban.
|
||||
"""
|
||||
# Сначала обновляем историю (если есть открытая запись)
|
||||
try:
|
||||
date_unban = int(datetime.now().timestamp())
|
||||
await self.factory.blacklist_history.set_unban_date(user_id, date_unban)
|
||||
except Exception as e:
|
||||
# Ошибка записи в историю не должна ломать критический путь разбана
|
||||
self.logger.error(
|
||||
f"Ошибка обновления истории при разбане для user_id={user_id}: {e}"
|
||||
)
|
||||
|
||||
# Удаляем из черного списка (критический путь)
|
||||
return await self.factory.blacklist.remove_user(user_id)
|
||||
|
||||
async def check_user_in_blacklist(self, user_id: int) -> bool:
|
||||
|
||||
@@ -29,6 +29,19 @@ class BlacklistUser:
|
||||
ban_author: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class BlacklistHistoryRecord:
|
||||
"""Модель записи истории банов/разбанов."""
|
||||
user_id: int
|
||||
message_for_user: Optional[str] = None
|
||||
date_ban: int = 0
|
||||
date_unban: Optional[int] = None
|
||||
ban_author: Optional[int] = None
|
||||
id: Optional[int] = None
|
||||
created_at: Optional[int] = None
|
||||
updated_at: Optional[int] = None
|
||||
|
||||
|
||||
@dataclass
|
||||
class UserMessage:
|
||||
"""Модель сообщения пользователя."""
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
Содержит репозитории для разных сущностей:
|
||||
- user_repository: работа с пользователями
|
||||
- blacklist_repository: работа с черным списком
|
||||
- blacklist_history_repository: работа с историей банов/разбанов
|
||||
- message_repository: работа с сообщениями
|
||||
- post_repository: работа с постами
|
||||
- admin_repository: работа с администраторами
|
||||
@@ -12,12 +13,13 @@
|
||||
|
||||
from .user_repository import UserRepository
|
||||
from .blacklist_repository import BlacklistRepository
|
||||
from .blacklist_history_repository import BlacklistHistoryRepository
|
||||
from .message_repository import MessageRepository
|
||||
from .post_repository import PostRepository
|
||||
from .admin_repository import AdminRepository
|
||||
from .audio_repository import AudioRepository
|
||||
|
||||
__all__ = [
|
||||
'UserRepository', 'BlacklistRepository', 'MessageRepository', 'PostRepository',
|
||||
'AdminRepository', 'AudioRepository'
|
||||
'UserRepository', 'BlacklistRepository', 'BlacklistHistoryRepository',
|
||||
'MessageRepository', 'PostRepository', 'AdminRepository', 'AudioRepository'
|
||||
]
|
||||
|
||||
119
database/repositories/blacklist_history_repository.py
Normal file
119
database/repositories/blacklist_history_repository.py
Normal file
@@ -0,0 +1,119 @@
|
||||
from typing import Optional
|
||||
from database.base import DatabaseConnection
|
||||
from database.models import BlacklistHistoryRecord
|
||||
|
||||
|
||||
class BlacklistHistoryRepository(DatabaseConnection):
|
||||
"""Репозиторий для работы с историей банов/разбанов."""
|
||||
|
||||
async def create_tables(self):
|
||||
"""Создание таблицы истории банов/разбанов."""
|
||||
query = '''
|
||||
CREATE TABLE IF NOT EXISTS blacklist_history (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
message_for_user TEXT,
|
||||
date_ban INTEGER NOT NULL,
|
||||
date_unban INTEGER,
|
||||
ban_author INTEGER,
|
||||
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
FOREIGN KEY (user_id) REFERENCES our_users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (ban_author) REFERENCES our_users(user_id) ON DELETE SET NULL
|
||||
)
|
||||
'''
|
||||
await self._execute_query(query)
|
||||
|
||||
# Создаем индексы
|
||||
await self._execute_query(
|
||||
"CREATE INDEX IF NOT EXISTS idx_blacklist_history_user_id ON blacklist_history(user_id)"
|
||||
)
|
||||
await self._execute_query(
|
||||
"CREATE INDEX IF NOT EXISTS idx_blacklist_history_date_ban ON blacklist_history(date_ban)"
|
||||
)
|
||||
await self._execute_query(
|
||||
"CREATE INDEX IF NOT EXISTS idx_blacklist_history_date_unban ON blacklist_history(date_unban)"
|
||||
)
|
||||
|
||||
self.logger.info("Таблица истории банов/разбанов создана")
|
||||
|
||||
async def add_record_on_ban(self, record: BlacklistHistoryRecord) -> None:
|
||||
"""Добавляет запись о бане в историю."""
|
||||
query = """
|
||||
INSERT INTO blacklist_history (
|
||||
user_id, message_for_user, date_ban, date_unban, ban_author, created_at, updated_at
|
||||
)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
"""
|
||||
# Используем текущее время, если не указано
|
||||
from datetime import datetime
|
||||
current_timestamp = int(datetime.now().timestamp())
|
||||
|
||||
params = (
|
||||
record.user_id,
|
||||
record.message_for_user,
|
||||
record.date_ban,
|
||||
record.date_unban,
|
||||
record.ban_author,
|
||||
record.created_at if record.created_at is not None else current_timestamp,
|
||||
record.updated_at if record.updated_at is not None else current_timestamp,
|
||||
)
|
||||
|
||||
await self._execute_query(query, params)
|
||||
self.logger.info(
|
||||
f"Запись о бане добавлена в историю: user_id={record.user_id}, "
|
||||
f"date_ban={record.date_ban}"
|
||||
)
|
||||
|
||||
async def set_unban_date(self, user_id: int, date_unban: int) -> bool:
|
||||
"""
|
||||
Обновляет date_unban и updated_at в последней записи (date_unban IS NULL) для пользователя.
|
||||
|
||||
Args:
|
||||
user_id: ID пользователя
|
||||
date_unban: Timestamp даты разбана
|
||||
|
||||
Returns:
|
||||
True если запись обновлена, False если не найдена открытая запись
|
||||
"""
|
||||
try:
|
||||
from datetime import datetime
|
||||
current_timestamp = int(datetime.now().timestamp())
|
||||
|
||||
# SQLite не поддерживает ORDER BY в UPDATE, поэтому используем подзапрос
|
||||
# Сначала проверяем, есть ли открытая запись
|
||||
check_query = """
|
||||
SELECT id FROM blacklist_history
|
||||
WHERE user_id = ? AND date_unban IS NULL
|
||||
ORDER BY id DESC
|
||||
LIMIT 1
|
||||
"""
|
||||
rows = await self._execute_query_with_result(check_query, (user_id,))
|
||||
|
||||
if not rows:
|
||||
self.logger.warning(
|
||||
f"Не найдена открытая запись в истории для обновления: user_id={user_id}"
|
||||
)
|
||||
return False
|
||||
|
||||
# Обновляем найденную запись
|
||||
update_query = """
|
||||
UPDATE blacklist_history
|
||||
SET date_unban = ?,
|
||||
updated_at = ?
|
||||
WHERE id = ?
|
||||
"""
|
||||
|
||||
record_id = rows[0][0]
|
||||
params = (date_unban, current_timestamp, record_id)
|
||||
await self._execute_query(update_query, params)
|
||||
|
||||
self.logger.info(
|
||||
f"Дата разбана обновлена в истории: user_id={user_id}, date_unban={date_unban}"
|
||||
)
|
||||
return True
|
||||
except Exception as e:
|
||||
self.logger.error(
|
||||
f"Ошибка обновления даты разбана в истории для user_id={user_id}: {str(e)}"
|
||||
)
|
||||
return False
|
||||
@@ -1,6 +1,7 @@
|
||||
from typing import Optional
|
||||
from database.repositories.user_repository import UserRepository
|
||||
from database.repositories.blacklist_repository import BlacklistRepository
|
||||
from database.repositories.blacklist_history_repository import BlacklistHistoryRepository
|
||||
from database.repositories.message_repository import MessageRepository
|
||||
from database.repositories.post_repository import PostRepository
|
||||
from database.repositories.admin_repository import AdminRepository
|
||||
@@ -14,6 +15,7 @@ class RepositoryFactory:
|
||||
self.db_path = db_path
|
||||
self._user_repo: Optional[UserRepository] = None
|
||||
self._blacklist_repo: Optional[BlacklistRepository] = None
|
||||
self._blacklist_history_repo: Optional[BlacklistHistoryRepository] = None
|
||||
self._message_repo: Optional[MessageRepository] = None
|
||||
self._post_repo: Optional[PostRepository] = None
|
||||
self._admin_repo: Optional[AdminRepository] = None
|
||||
@@ -33,6 +35,13 @@ class RepositoryFactory:
|
||||
self._blacklist_repo = BlacklistRepository(self.db_path)
|
||||
return self._blacklist_repo
|
||||
|
||||
@property
|
||||
def blacklist_history(self) -> BlacklistHistoryRepository:
|
||||
"""Возвращает репозиторий истории банов/разбанов."""
|
||||
if self._blacklist_history_repo is None:
|
||||
self._blacklist_history_repo = BlacklistHistoryRepository(self.db_path)
|
||||
return self._blacklist_history_repo
|
||||
|
||||
@property
|
||||
def messages(self) -> MessageRepository:
|
||||
"""Возвращает репозиторий сообщений."""
|
||||
@@ -65,6 +74,7 @@ class RepositoryFactory:
|
||||
"""Создает все таблицы в базе данных."""
|
||||
await self.users.create_tables()
|
||||
await self.blacklist.create_tables()
|
||||
await self.blacklist_history.create_tables()
|
||||
await self.messages.create_tables()
|
||||
await self.posts.create_tables()
|
||||
await self.admins.create_tables()
|
||||
|
||||
@@ -40,6 +40,20 @@ CREATE TABLE IF NOT EXISTS blacklist (
|
||||
FOREIGN KEY (user_id) REFERENCES our_users(user_id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
-- Blacklist history for tracking all ban/unban events
|
||||
CREATE TABLE IF NOT EXISTS blacklist_history (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
user_id INTEGER NOT NULL,
|
||||
message_for_user TEXT,
|
||||
date_ban INTEGER NOT NULL,
|
||||
date_unban INTEGER,
|
||||
ban_author INTEGER,
|
||||
created_at INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
updated_at INTEGER DEFAULT (strftime('%s', 'now')),
|
||||
FOREIGN KEY (user_id) REFERENCES our_users(user_id) ON DELETE CASCADE,
|
||||
FOREIGN KEY (ban_author) REFERENCES our_users(user_id) ON DELETE SET NULL
|
||||
);
|
||||
|
||||
-- User message history
|
||||
CREATE TABLE IF NOT EXISTS user_messages (
|
||||
id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -109,6 +123,9 @@ CREATE INDEX IF NOT EXISTS idx_audio_message_reference_author_id ON audio_messag
|
||||
CREATE INDEX IF NOT EXISTS idx_user_messages_user_id ON user_messages(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_post_from_telegram_suggest_author_id ON post_from_telegram_suggest(author_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_blacklist_date_to_unban ON blacklist(date_to_unban);
|
||||
CREATE INDEX IF NOT EXISTS idx_blacklist_history_user_id ON blacklist_history(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_blacklist_history_date_ban ON blacklist_history(date_ban);
|
||||
CREATE INDEX IF NOT EXISTS idx_blacklist_history_date_unban ON blacklist_history(date_unban);
|
||||
CREATE INDEX IF NOT EXISTS idx_user_messages_date ON user_messages(date);
|
||||
CREATE INDEX IF NOT EXISTS idx_audio_message_reference_date ON audio_message_reference(date_added);
|
||||
CREATE INDEX IF NOT EXISTS idx_post_from_telegram_suggest_date ON post_from_telegram_suggest(created_at);
|
||||
|
||||
Reference in New Issue
Block a user