diff --git a/app/middlewares/auth.py b/app/middlewares/auth.py index ae995ad9..d0e5bba3 100644 --- a/app/middlewares/auth.py +++ b/app/middlewares/auth.py @@ -7,6 +7,7 @@ from typing import Any from aiogram import BaseMiddleware from aiogram.fsm.context import FSMContext from aiogram.types import CallbackQuery, Message, TelegramObject, User as TgUser +from sqlalchemy.exc import InterfaceError, OperationalError from app.config import settings from app.database.crud.user import get_user_by_telegram_id @@ -204,13 +205,27 @@ class AuthMiddleware(BaseMiddleware): data['is_admin'] = settings.is_admin(user.id) result = await handler(event, data) - await db.commit() + try: + await db.commit() + except (InterfaceError, OperationalError) as conn_err: + # Соединение закрылось (таймаут после долгой операции) - просто логируем + logger.warning(f'⚠️ Соединение с БД закрыто после обработки, пропускаем commit: {conn_err}') return result + except (InterfaceError, OperationalError) as conn_err: + # Соединение с БД закрылось - не пытаемся rollback + logger.error(f'Ошибка соединения с БД в AuthMiddleware: {conn_err}') + logger.error(f'Event type: {type(event)}') + if hasattr(event, 'data'): + logger.error(f'Callback data: {event.data}') + raise except Exception as e: logger.error(f'Ошибка в AuthMiddleware: {e}') logger.error(f'Event type: {type(event)}') if hasattr(event, 'data'): logger.error(f'Callback data: {event.data}') - await db.rollback() + try: + await db.rollback() + except (InterfaceError, OperationalError): + pass # Соединение уже закрыто raise diff --git a/app/middlewares/global_error.py b/app/middlewares/global_error.py index e9a3b93a..d3e3811f 100644 --- a/app/middlewares/global_error.py +++ b/app/middlewares/global_error.py @@ -9,6 +9,7 @@ from aiogram import BaseMiddleware, Bot from aiogram.enums import ParseMode from aiogram.exceptions import TelegramBadRequest from aiogram.types import BufferedInputFile, CallbackQuery, InlineKeyboardButton, InlineKeyboardMarkup, TelegramObject +from sqlalchemy.exc import InterfaceError, OperationalError from app.config import settings from app.services.startup_notification_service import _get_error_recommendations @@ -67,6 +68,10 @@ class GlobalErrorMiddleware(BaseMiddleware): return await handler(event, data) except TelegramBadRequest as e: return await self._handle_telegram_error(event, e, data) + except (InterfaceError, OperationalError) as e: + # Ошибки соединения с БД (таймаут после долгих операций) - логируем, но не спамим админам + logger.warning('⚠️ Ошибка соединения с БД в GlobalErrorMiddleware: %s', e) + raise except Exception as e: logger.error('Неожиданная ошибка в GlobalErrorMiddleware: %s', e, exc_info=True) # Отправляем уведомление об ошибке в админский чат @@ -82,13 +87,13 @@ class GlobalErrorMiddleware(BaseMiddleware): if self._is_old_query_error(error_message): return await self._handle_old_query(event, error) if self._is_message_not_modified_error(error_message): - return await self._handle_message_not_modified(event, error) + return await self._handle_message_not_modified(event, error, data) if self._is_topic_required_error(error_message): # Канал с топиками — просто игнорируем logger.debug('[GlobalErrorMiddleware] Игнорируем ошибку топика: %s', error) return None if self._is_bad_request_error(error_message): - return await self._handle_bad_request(event, error) + return await self._handle_bad_request(event, error, data) # Неизвестная ошибка — логируем и отправляем уведомление logger.error('Неизвестная Telegram API ошибка: %s', error) @@ -117,7 +122,9 @@ class GlobalErrorMiddleware(BaseMiddleware): else: logger.warning('[GlobalErrorMiddleware] Игнорируем устаревший запрос: %s', error) - async def _handle_message_not_modified(self, event: TelegramObject, error: TelegramBadRequest): + async def _handle_message_not_modified( + self, event: TelegramObject, error: TelegramBadRequest, data: dict[str, Any] + ): logger.debug('[GlobalErrorMiddleware] Сообщение не было изменено: %s', error) if isinstance(event, CallbackQuery): @@ -127,8 +134,13 @@ class GlobalErrorMiddleware(BaseMiddleware): except TelegramBadRequest as answer_error: if not self._is_old_query_error(str(answer_error).lower()): logger.error('Ошибка при ответе на callback: %s', answer_error) + # Отправляем уведомление в админский чат + bot = data.get('bot') + if bot: + user_info = self._get_user_info(event) + schedule_error_notification(bot, answer_error, f'Callback answer error: {user_info}') - async def _handle_bad_request(self, event: TelegramObject, error: TelegramBadRequest): + async def _handle_bad_request(self, event: TelegramObject, error: TelegramBadRequest, data: dict[str, Any]): error_message = str(error).lower() if BOT_BLOCKED_PHRASE in error_message: @@ -143,6 +155,11 @@ class GlobalErrorMiddleware(BaseMiddleware): logger.warning('[GlobalErrorMiddleware] Чат или сообщение не найдено: %s', error) return logger.error('[GlobalErrorMiddleware] Неизвестная bad request ошибка: %s', error) + # Отправляем уведомление перед raise + bot = data.get('bot') + if bot: + user_info = self._get_user_info(event) + schedule_error_notification(bot, error, f'Bad request: {user_info}') raise error def _get_user_info(self, event: TelegramObject) -> str: diff --git a/app/services/user_service.py b/app/services/user_service.py index db64ff95..3c32c4fa 100644 --- a/app/services/user_service.py +++ b/app/services/user_service.py @@ -30,9 +30,11 @@ from app.database.models import ( AdvertisingCampaign, AdvertisingCampaignRegistration, BroadcastHistory, + CloudPaymentsPayment, CryptoBotPayment, FreekassaPayment, HeleketPayment, + KassaAiPayment, MulenPayPayment, Pal24Payment, PaymentMethod, @@ -957,6 +959,58 @@ class UserService: except Exception as e: logger.error(f'❌ Ошибка удаления Freekassa платежей: {e}') + # Удаляем Wata платежи (до транзакций, т.к. wata_payments.transaction_id -> transactions.id) + try: + wata_payments_result = await db.execute(select(WataPayment).where(WataPayment.user_id == user_id)) + wata_payments = wata_payments_result.scalars().all() + + if wata_payments: + logger.info(f'🔄 Удаляем {len(wata_payments)} Wata платежей') + await db.execute( + update(WataPayment).where(WataPayment.user_id == user_id).values(transaction_id=None) + ) + await db.flush() + await db.execute(delete(WataPayment).where(WataPayment.user_id == user_id)) + await db.flush() + except Exception as e: + logger.error(f'❌ Ошибка удаления Wata платежей: {e}') + + # Удаляем CloudPayments платежи + try: + cloudpayments_result = await db.execute( + select(CloudPaymentsPayment).where(CloudPaymentsPayment.user_id == user_id) + ) + cloudpayments_payments = cloudpayments_result.scalars().all() + + if cloudpayments_payments: + logger.info(f'🔄 Удаляем {len(cloudpayments_payments)} CloudPayments платежей') + await db.execute( + update(CloudPaymentsPayment) + .where(CloudPaymentsPayment.user_id == user_id) + .values(transaction_id=None) + ) + await db.flush() + await db.execute(delete(CloudPaymentsPayment).where(CloudPaymentsPayment.user_id == user_id)) + await db.flush() + except Exception as e: + logger.error(f'❌ Ошибка удаления CloudPayments платежей: {e}') + + # Удаляем KassaAi платежи + try: + kassa_ai_result = await db.execute(select(KassaAiPayment).where(KassaAiPayment.user_id == user_id)) + kassa_ai_payments = kassa_ai_result.scalars().all() + + if kassa_ai_payments: + logger.info(f'🔄 Удаляем {len(kassa_ai_payments)} KassaAi платежей') + await db.execute( + update(KassaAiPayment).where(KassaAiPayment.user_id == user_id).values(transaction_id=None) + ) + await db.flush() + await db.execute(delete(KassaAiPayment).where(KassaAiPayment.user_id == user_id)) + await db.flush() + except Exception as e: + logger.error(f'❌ Ошибка удаления KassaAi платежей: {e}') + try: transactions_result = await db.execute(select(Transaction).where(Transaction.user_id == user_id)) transactions = transactions_result.scalars().all() @@ -1056,17 +1110,6 @@ class UserService: except Exception as e: logger.error(f'❌ Ошибка удаления подписки: {e}') - try: - wata_payments_result = await db.execute(select(WataPayment).where(WataPayment.user_id == user_id)) - wata_payments = wata_payments_result.scalars().all() - - if wata_payments: - logger.info(f'🔄 Удаляем {len(wata_payments)} Wata платежей') - await db.execute(delete(WataPayment).where(WataPayment.user_id == user_id)) - await db.flush() - except Exception as e: - logger.error(f'❌ Ошибка удаления Wata платежей: {e}') - try: await db.execute(delete(User).where(User.id == user_id)) await db.commit()