Files
remnawave-bedolaga-telegram…/app/handlers/admin/updates.py
T
Fringg 1f0fef114b refactor: complete structlog migration with contextvars, kwargs, and logging hardening
- Add ContextVarsMiddleware for automatic user_id/chat_id/username binding
  via structlog contextvars (aiogram) and http_method/http_path (FastAPI)
- Use bound_contextvars() context manager instead of clear_contextvars()
  to safely restore previous state instead of wiping all context
- Register ContextVarsMiddleware as outermost middleware (before GlobalError)
  so all error logs include user context
- Replace structlog.get_logger() with structlog.get_logger(__name__) across
  270 calls in 265 files for meaningful logger names
- Switch wrapper_class from BoundLogger to make_filtering_bound_logger()
  for pre-processor level filtering (performance optimization)
- Migrate 1411 %-style positional arg logger calls to structlog kwargs
  style across 161 files via AST script
- Migrate log_rotation_service.py from stdlib logging to structlog
- Add payment module prefixes to TelegramNotifierProcessor.IGNORED_LOGGER_PREFIXES
  and ExcludePaymentFilter.PAYMENT_MODULES to prevent payment data leaking
  to Telegram notifications and general log files
- Fix LoggingMiddleware: add from_user null-safety for channel posts,
  switch time.time() to time.monotonic() for duration measurement
- Remove duplicate logger assignments in purchase.py, config.py,
  inline.py, and admin/payments.py
2026-02-16 09:18:12 +03:00

225 lines
9.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import structlog
from aiogram import Dispatcher, F, types
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from sqlalchemy.ext.asyncio import AsyncSession
from app.database.models import User
from app.services.version_service import version_service
from app.utils.decorators import admin_required, error_handler
logger = structlog.get_logger(__name__)
def get_updates_keyboard(language: str = 'ru') -> InlineKeyboardMarkup:
buttons = [
[InlineKeyboardButton(text='🔄 Проверить обновления', callback_data='admin_updates_check')],
[InlineKeyboardButton(text='📋 Информация о версии', callback_data='admin_updates_info')],
[
InlineKeyboardButton(
text='🔗 Открыть репозиторий', url=f'https://github.com/{version_service.repo}/releases'
)
],
[InlineKeyboardButton(text='◀️ Назад', callback_data='admin_panel')],
]
return InlineKeyboardMarkup(inline_keyboard=buttons)
def get_version_info_keyboard(language: str = 'ru') -> InlineKeyboardMarkup:
buttons = [
[InlineKeyboardButton(text='🔄 Обновить', callback_data='admin_updates_info')],
[InlineKeyboardButton(text='◀️ К обновлениям', callback_data='admin_updates')],
]
return InlineKeyboardMarkup(inline_keyboard=buttons)
@admin_required
@error_handler
async def show_updates_menu(callback: types.CallbackQuery, db_user: User, db: AsyncSession):
try:
version_info = await version_service.get_version_info()
current_version = version_info['current_version']
has_updates = version_info['has_updates']
total_newer = version_info['total_newer']
last_check = version_info['last_check']
status_icon = '🆕' if has_updates else ''
status_text = f'Доступно {total_newer} обновлений' if has_updates else 'Актуальная версия'
last_check_text = ''
if last_check:
last_check_text = f'\n🕐 Последняя проверка: {last_check.strftime("%d.%m.%Y %H:%M")}'
message = f"""🔄 <b>СИСТЕМА ОБНОВЛЕНИЙ</b>
📦 <b>Текущая версия:</b> <code>{current_version}</code>
{status_icon} <b>Статус:</b> {status_text}
🔗 <b>Репозиторий:</b> {version_service.repo}{last_check_text}
ℹ️ Система автоматически проверяет обновления каждый час и отправляет уведомления о новых версиях."""
await callback.message.edit_text(
message, reply_markup=get_updates_keyboard(db_user.language), parse_mode='HTML'
)
await callback.answer()
except Exception as e:
if 'message is not modified' in str(e).lower():
logger.debug('📝 Сообщение не изменено в show_updates_menu')
await callback.answer()
return
logger.error('Ошибка показа меню обновлений', error=e)
await callback.answer('❌ Ошибка загрузки меню обновлений', show_alert=True)
@admin_required
@error_handler
async def check_updates(callback: types.CallbackQuery, db_user: User, db: AsyncSession):
await callback.answer('🔄 Проверяю обновления...')
try:
has_updates, newer_releases = await version_service.check_for_updates(force=True)
if not has_updates:
message = f"""✅ <b>ОБНОВЛЕНИЯ НЕ НАЙДЕНЫ</b>
📦 <b>Текущая версия:</b> <code>{version_service.current_version}</code>
🎯 <b>Статус:</b> У вас установлена последняя версия
🔗 <b>Репозиторий:</b> {version_service.repo}"""
else:
updates_list = []
for i, release in enumerate(newer_releases[:5]):
icon = version_service.format_version_display(release).split()[0]
updates_list.append(f'{i + 1}. {icon} <code>{release.tag_name}</code> • {release.formatted_date}')
updates_text = '\n'.join(updates_list)
more_text = f'\n\n📋 И еще {len(newer_releases) - 5} обновлений...' if len(newer_releases) > 5 else ''
message = f"""🆕 <b>НАЙДЕНЫ ОБНОВЛЕНИЯ</b>
📦 <b>Текущая версия:</b> <code>{version_service.current_version}</code>
🎯 <b>Доступно обновлений:</b> {len(newer_releases)}
📋 <b>Последние версии:</b>
{updates_text}{more_text}
🔗 <b>Репозиторий:</b> {version_service.repo}"""
keyboard = get_updates_keyboard(db_user.language)
if has_updates:
keyboard.inline_keyboard.insert(
-2, [InlineKeyboardButton(text='📋 Подробнее о версиях', callback_data='admin_updates_info')]
)
await callback.message.edit_text(message, reply_markup=keyboard, parse_mode='HTML')
except Exception as e:
if 'message is not modified' in str(e).lower():
logger.debug('📝 Сообщение не изменено в check_updates')
return
logger.error('Ошибка проверки обновлений', error=e)
await callback.message.edit_text(
f'❌ <b>ОШИБКА ПРОВЕРКИ ОБНОВЛЕНИЙ</b>\n\n'
f'Не удалось связаться с сервером GitHub.\n'
f'Попробуйте позже.\n\n'
f'📦 <b>Текущая версия:</b> <code>{version_service.current_version}</code>',
reply_markup=get_updates_keyboard(db_user.language),
parse_mode='HTML',
)
@admin_required
@error_handler
async def show_version_info(callback: types.CallbackQuery, db_user: User, db: AsyncSession):
await callback.answer('📋 Загружаю информацию о версиях...')
try:
version_info = await version_service.get_version_info()
current_version = version_info['current_version']
current_release = version_info['current_release']
newer_releases = version_info['newer_releases']
has_updates = version_info['has_updates']
last_check = version_info['last_check']
current_info = '📦 <b>ТЕКУЩАЯ ВЕРСИЯ</b>\n\n'
if current_release:
current_info += f'🏷️ <b>Версия:</b> <code>{current_release.tag_name}</code>\n'
current_info += f'📅 <b>Дата релиза:</b> {current_release.formatted_date}\n'
if current_release.short_description:
current_info += f'📝 <b>Описание:</b>\n{current_release.short_description}\n'
else:
current_info += f'🏷️ <b>Версия:</b> <code>{current_version}</code>\n'
current_info += '️ <b>Статус:</b> Информация о релизе недоступна\n'
message_parts = [current_info]
if has_updates and newer_releases:
updates_info = '\n🆕 <b>ДОСТУПНЫЕ ОБНОВЛЕНИЯ</b>\n\n'
for i, release in enumerate(newer_releases):
icon = '🔥' if i == 0 else '📦'
if release.prerelease:
icon = '🧪'
elif release.is_dev:
icon = '🔧'
updates_info += f'{icon} <b>{release.tag_name}</b>\n'
updates_info += f' 📅 {release.formatted_date}\n'
if release.short_description:
updates_info += f' 📝 {release.short_description}\n'
updates_info += '\n'
message_parts.append(updates_info.rstrip())
system_info = '\n🔧 <b>СИСТЕМА ОБНОВЛЕНИЙ</b>\n\n'
system_info += f'🔗 <b>Репозиторий:</b> {version_service.repo}\n'
system_info += f'⚡ <b>Автопроверка:</b> {"Включена" if version_service.enabled else "Отключена"}\n'
system_info += '🕐 <b>Интервал:</b> Каждый час\n'
if last_check:
system_info += f'🕐 <b>Последняя проверка:</b> {last_check.strftime("%d.%m.%Y %H:%M")}\n'
message_parts.append(system_info.rstrip())
final_message = '\n'.join(message_parts)
if len(final_message) > 4000:
final_message = final_message[:3900] + '\n\n... (информация обрезана)'
await callback.message.edit_text(
final_message,
reply_markup=get_version_info_keyboard(db_user.language),
parse_mode='HTML',
disable_web_page_preview=True,
)
except Exception as e:
if 'message is not modified' in str(e).lower():
logger.debug('📝 Сообщение не изменено в show_version_info')
return
logger.error('Ошибка получения информации о версиях', error=e)
await callback.message.edit_text(
f'❌ <b>ОШИБКА ЗАГРУЗКИ</b>\n\n'
f'Не удалось получить информацию о версиях.\n\n'
f'📦 <b>Текущая версия:</b> <code>{version_service.current_version}</code>',
reply_markup=get_version_info_keyboard(db_user.language),
parse_mode='HTML',
)
def register_handlers(dp: Dispatcher):
dp.callback_query.register(show_updates_menu, F.data == 'admin_updates')
dp.callback_query.register(check_updates, F.data == 'admin_updates_check')
dp.callback_query.register(show_version_info, F.data == 'admin_updates_info')