1f0fef114b
- 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
225 lines
9.7 KiB
Python
225 lines
9.7 KiB
Python
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')
|