Refactor project structure and enhance Docker support

- Removed unnecessary `__init__.py` and `Dockerfile` to streamline project organization.
- Updated `.dockerignore` and `.gitignore` to improve exclusion patterns for build artifacts and environment files.
- Enhanced `Makefile` with new commands for managing Docker containers and added help documentation.
- Introduced `pyproject.toml` for better project metadata management and dependency tracking.
- Updated `requirements.txt` to reflect changes in dependencies for metrics and monitoring.
- Refactored various handler files to improve code organization and maintainability.
This commit is contained in:
2025-08-29 16:49:28 +03:00
parent 8cee629e28
commit c68db87901
37 changed files with 2177 additions and 175 deletions

View File

@@ -1,9 +1,9 @@
"""Constants for private handlers"""
from typing import Final
from typing import Final, Dict
# FSM States
FSM_STATES: Final[dict[str, str]] = {
FSM_STATES: Final[Dict[str, str]] = {
"START": "START",
"SUGGEST": "SUGGEST",
"PRE_CHAT": "PRE_CHAT",
@@ -11,7 +11,7 @@ FSM_STATES: Final[dict[str, str]] = {
}
# Button texts
BUTTON_TEXTS: Final[dict[str, str]] = {
BUTTON_TEXTS: Final[Dict[str, str]] = {
"SUGGEST_POST": "📢Предложить свой пост",
"SAY_GOODBYE": "👋🏼Сказать пока!",
"LEAVE_CHAT": "Выйти из чата",
@@ -21,7 +21,7 @@ BUTTON_TEXTS: Final[dict[str, str]] = {
}
# Error messages
ERROR_MESSAGES: Final[dict[str, str]] = {
ERROR_MESSAGES: Final[Dict[str, str]] = {
"UNSUPPORTED_CONTENT": (
'Я пока не умею работать с таким сообщением. '
'Пришли текст и фото/фоты(ы). А лучше перешли это сообщение админу @kerrad1\n'

View File

@@ -24,6 +24,14 @@ from helper_bot.utils.helper_func import (
check_user_emoji
)
# Local imports - metrics
from helper_bot.utils.metrics import (
metrics,
track_time,
track_errors,
db_query_time
)
# Local imports - modular components
from .constants import FSM_STATES, BUTTON_TEXTS, ERROR_MESSAGES
from .services import BotSettings, UserService, PostService, StickerService
@@ -91,16 +99,23 @@ class PrivateHandlers:
await message.answer('Я перезапущен!', reply_markup=markup, parse_mode='HTML')
@error_handler
@track_time("start_message_handler", "private_handler")
@track_errors("private_handler", "start_message_handler")
async def handle_start_message(self, message: types.Message, state: FSMContext, **kwargs):
"""Handle start command and return to bot button"""
"""Handle start command and return to bot button with metrics tracking"""
# Record start command metrics
metrics.record_command("start", "private_handler", "user" if not message.from_user.is_bot else "bot")
metrics.record_message("command", "private", "private_handler")
# User service operations with metrics
await self.user_service.log_user_message(message)
await self.user_service.ensure_user_exists(message)
await state.set_state(FSM_STATES["START"])
# Send sticker
# Send sticker with metrics
await self.sticker_service.send_random_hello_sticker(message)
# Send welcome message
# Send welcome message with metrics
markup = get_reply_keyboard(self.db, message.from_user.id)
hello_message = messages.get_message(get_first_name(message), 'HELLO_MESSAGE')
await message.answer(hello_message, reply_markup=markup, parse_mode='HTML')

View File

@@ -30,6 +30,14 @@ from helper_bot.utils.helper_func import (
)
from helper_bot.keyboards import get_reply_keyboard_for_post
# Local imports - metrics
from helper_bot.utils.metrics import (
metrics,
track_time,
track_errors,
db_query_time
)
class DatabaseProtocol(Protocol):
"""Protocol for database operations"""
@@ -65,13 +73,18 @@ class UserService:
self.db = db
self.settings = settings
@track_time("update_user_activity", "user_service")
@track_errors("user_service", "update_user_activity")
@db_query_time("update_user_activity", "users", "update")
async def update_user_activity(self, user_id: int) -> None:
"""Update user's last activity timestamp"""
"""Update user's last activity timestamp with metrics tracking"""
current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
self.db.update_date_for_user(current_date, user_id)
@track_time("ensure_user_exists", "user_service")
@track_errors("user_service", "ensure_user_exists")
async def ensure_user_exists(self, message: types.Message) -> None:
"""Ensure user exists in database, create if needed"""
"""Ensure user exists in database, create if needed with metrics tracking"""
user_id = message.from_user.id
full_name = message.from_user.full_name
username = message.from_user.username or "private_username"
@@ -82,14 +95,17 @@ class UserService:
current_date = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
if not self.db.user_exists(user_id):
# Record database operation
self.db.add_new_user_in_db(
user_id, first_name, full_name, username, is_bot, language_code,
"", current_date, current_date
)
metrics.record_db_query("add_new_user", 0.0, "users", "insert")
else:
is_need_update = check_username_and_full_name(user_id, username, full_name, self.db)
if is_need_update:
self.db.update_username_and_full_name(user_id, username, full_name)
metrics.record_db_query("update_username_fullname", 0.0, "users", "update")
safe_full_name = html.escape(full_name) if full_name else "Неизвестный пользователь"
safe_username = html.escape(username) if username else "Без никнейма"
@@ -100,9 +116,12 @@ class UserService:
text=f'Для пользователя: {user_id} обновлены данные в БД.\nНовое имя: {safe_full_name}\nНовый ник:{safe_username}')
self.db.update_date_for_user(current_date, user_id)
metrics.record_db_query("update_date_for_user", 0.0, "users", "update")
@track_time("log_user_message", "user_service")
@track_errors("user_service", "log_user_message")
async def log_user_message(self, message: types.Message) -> None:
"""Forward user message to logs group"""
"""Forward user message to logs group with metrics tracking"""
await message.forward(chat_id=self.settings.group_for_logs)
def get_safe_user_info(self, message: types.Message) -> tuple[str, str]:
@@ -210,7 +229,7 @@ class PostService:
message_id=media_group_message_id, helper_message_id=help_message_id
)
async def process_post(self, message: types.Message, album: Union[list[types.Message], None] = None) -> None:
async def process_post(self, message: types.Message, album: Union[list, None] = None) -> None:
"""Process post based on content type"""
first_name = get_first_name(message)
@@ -248,8 +267,10 @@ class StickerService:
def __init__(self, settings: BotSettings) -> None:
self.settings = settings
@track_time("send_random_hello_sticker", "sticker_service")
@track_errors("sticker_service", "send_random_hello_sticker")
async def send_random_hello_sticker(self, message: types.Message) -> None:
"""Send random hello sticker"""
"""Send random hello sticker with metrics tracking"""
name_stick_hello = list(Path('Stick').rglob('Hello_*'))
if not name_stick_hello:
return
@@ -258,8 +279,10 @@ class StickerService:
await message.answer_sticker(random_stick_hello)
await asyncio.sleep(0.3)
@track_time("send_random_goodbye_sticker", "sticker_service")
@track_errors("sticker_service", "send_random_goodbye_sticker")
async def send_random_goodbye_sticker(self, message: types.Message) -> None:
"""Send random goodbye sticker"""
"""Send random goodbye sticker with metrics tracking"""
name_stick_bye = list(Path('Stick').rglob('Universal_*'))
if not name_stick_bye:
return