From c1f035b13d8802fbdb2bc34198e1c06fcc4c7e63 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 17 Jan 2026 01:14:57 +0300 Subject: [PATCH 1/6] Add files via upload --- app/middlewares/auth.py | 60 ++++++++++++++++-------------- app/middlewares/channel_checker.py | 34 ++++++++--------- 2 files changed, 49 insertions(+), 45 deletions(-) diff --git a/app/middlewares/auth.py b/app/middlewares/auth.py index a2f3cb5d..47493b2f 100644 --- a/app/middlewares/auth.py +++ b/app/middlewares/auth.py @@ -7,7 +7,7 @@ from aiogram.types import Message, CallbackQuery, TelegramObject, User as TgUser from aiogram.fsm.context import FSMContext from app.config import settings -from app.database.database import get_db +from app.database.database import AsyncSessionLocal from app.database.crud.user import get_user_by_telegram_id, create_user from app.services.remnawave_service import RemnaWaveService from app.states import RegistrationStates @@ -54,26 +54,26 @@ class AuthMiddleware(BaseMiddleware): if user.is_bot: return await handler(event, data) - async for db in get_db(): + async with AsyncSessionLocal() as db: try: db_user = await get_user_by_telegram_id(db, user.id) - + if not db_user: state: FSMContext = data.get('state') current_state = None - + if state: current_state = await state.get_state() is_reg_process = is_registration_process(event, current_state) - - is_channel_check = (isinstance(event, CallbackQuery) + + is_channel_check = (isinstance(event, CallbackQuery) and event.data == "sub_channel_check") - - is_start_command = (isinstance(event, Message) - and event.text + + is_start_command = (isinstance(event, Message) + and event.text and event.text.startswith('/start')) - + if is_reg_process or is_channel_check or is_start_command: if is_start_command: logger.info(f"πŸš€ ΠŸΡ€ΠΎΠΏΡƒΡΠΊΠ°Π΅ΠΌ ΠΊΠΎΠΌΠ°Π½Π΄Ρƒ /start ΠΎΡ‚ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ {user.id}") @@ -84,7 +84,9 @@ class AuthMiddleware(BaseMiddleware): data['db'] = db data['db_user'] = None data['is_admin'] = False - return await handler(event, data) + result = await handler(event, data) + await db.commit() + return result else: if isinstance(event, Message): await event.answer( @@ -99,7 +101,7 @@ class AuthMiddleware(BaseMiddleware): return else: from app.database.models import UserStatus - + if db_user.status == UserStatus.BLOCKED.value: if isinstance(event, Message): await event.answer("🚫 Π’Π°Ρˆ Π°ΠΊΠΊΠ°ΡƒΠ½Ρ‚ Π·Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½ администратором.") @@ -107,14 +109,14 @@ class AuthMiddleware(BaseMiddleware): await event.answer("🚫 Π’Π°Ρˆ Π°ΠΊΠΊΠ°ΡƒΠ½Ρ‚ Π·Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½ администратором.", show_alert=True) logger.info(f"🚫 Π—Π°Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²Π°Π½Π½Ρ‹ΠΉ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ {user.id} попытался ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π±ΠΎΡ‚Π°") return - + if db_user.status == UserStatus.DELETED.value: state: FSMContext = data.get('state') current_state = None - + if state: current_state = await state.get_state() - + registration_states = [ RegistrationStates.waiting_for_language.state, RegistrationStates.waiting_for_rules_accept.state, @@ -134,13 +136,15 @@ class AuthMiddleware(BaseMiddleware): ) ) ) - + if is_start_or_registration: logger.info(f"πŸ”„ Π£Π΄Π°Π»Π΅Π½Π½Ρ‹ΠΉ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ {user.id} Π½Π°Ρ‡ΠΈΠ½Π°Π΅Ρ‚ ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½ΡƒΡŽ Ρ€Π΅Π³ΠΈΡΡ‚Ρ€Π°Ρ†ΠΈΡŽ") data['db'] = db - data['db_user'] = None + data['db_user'] = None data['is_admin'] = False - return await handler(event, data) + result = await handler(event, data) + await db.commit() + return result else: if isinstance(event, Message): await event.answer( @@ -154,16 +158,16 @@ class AuthMiddleware(BaseMiddleware): ) logger.info(f"❌ Π£Π΄Π°Π»Π΅Π½Π½Ρ‹ΠΉ ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ {user.id} попытался ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚ΡŒ Π±ΠΎΡ‚Π° Π±Π΅Π· /start") return - - + + profile_updated = False - + if db_user.username != user.username: old_username = db_user.username db_user.username = user.username logger.info(f"πŸ”„ [Middleware] Username ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ для {user.id}: '{old_username}' β†’ '{db_user.username}'") profile_updated = True - + safe_first = sanitize_telegram_name(user.first_name) safe_last = sanitize_telegram_name(user.last_name) if db_user.first_name != safe_first: @@ -171,13 +175,13 @@ class AuthMiddleware(BaseMiddleware): db_user.first_name = safe_first logger.info(f"πŸ”„ [Middleware] Имя ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½ΠΎ для {user.id}: '{old_first_name}' β†’ '{db_user.first_name}'") profile_updated = True - + if db_user.last_name != safe_last: old_last_name = db_user.last_name db_user.last_name = safe_last logger.info(f"πŸ”„ [Middleware] Ѐамилия ΠΎΠ±Π½ΠΎΠ²Π»Π΅Π½Π° для {user.id}: '{old_last_name}' β†’ '{db_user.last_name}'") profile_updated = True - + db_user.last_activity = datetime.utcnow() if profile_updated: @@ -198,14 +202,14 @@ class AuthMiddleware(BaseMiddleware): ) ) - await db.commit() - data['db'] = db data['db_user'] = db_user data['is_admin'] = settings.is_admin(user.id) - return await handler(event, data) - + result = await handler(event, data) + await db.commit() + return result + except Exception as e: logger.error(f"Ошибка Π² AuthMiddleware: {e}") logger.error(f"Event type: {type(event)}") diff --git a/app/middlewares/channel_checker.py b/app/middlewares/channel_checker.py index 67fa2db0..3e873843 100644 --- a/app/middlewares/channel_checker.py +++ b/app/middlewares/channel_checker.py @@ -9,7 +9,7 @@ from aiogram.enums import ChatMemberStatus from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings -from app.database.database import get_db +from app.database.database import AsyncSessionLocal from app.database.crud.campaign import get_campaign_by_start_parameter from app.database.crud.subscription import deactivate_subscription, reactivate_subscription from app.database.crud.user import get_user_by_telegram_id @@ -228,7 +228,7 @@ class ChannelCheckerMiddleware(BaseMiddleware): if state_data.get("campaign_notification_sent"): return - async for db in get_db(): + async with AsyncSessionLocal() as db: try: campaign = await get_campaign_by_start_parameter( db, @@ -236,7 +236,7 @@ class ChannelCheckerMiddleware(BaseMiddleware): only_active=True, ) if not campaign: - break + return user = await get_user_by_telegram_id(db, telegram_user.id) @@ -249,14 +249,14 @@ class ChannelCheckerMiddleware(BaseMiddleware): ) if sent: await state.update_data(campaign_notification_sent=True) + await db.commit() except Exception as error: logger.error( "❌ Ошибка ΠΎΡ‚ΠΏΡ€Π°Π²ΠΊΠΈ увСдомлСния ΠΎ ΠΏΠ΅Ρ€Π΅Ρ…ΠΎΠ΄Π΅ ΠΏΠΎ ΠΊΠ°ΠΌΠΏΠ°Π½ΠΈΠΈ %s: %s", payload, error, ) - finally: - break + await db.rollback() async def _deactivate_subscription_on_unsubscribe( self, telegram_id: int, bot: Bot, channel_link: Optional[str] @@ -265,21 +265,21 @@ class ChannelCheckerMiddleware(BaseMiddleware): if not settings.CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE and not settings.CHANNEL_REQUIRED_FOR_ALL: return - async for db in get_db(): + async with AsyncSessionLocal() as db: try: user = await get_user_by_telegram_id(db, telegram_id) if not user or not user.subscription: - break + return subscription = user.subscription if subscription.status != SubscriptionStatus.ACTIVE.value: - break + return if settings.CHANNEL_REQUIRED_FOR_ALL: pass elif not subscription.is_trial: - break + return await deactivate_subscription(db, subscription) sub_type = "Π’Ρ€ΠΈΠ°Π»ΡŒΠ½Π°Ρ" if subscription.is_trial else "ΠŸΠ»Π°Ρ‚Π½Π°Ρ" @@ -316,35 +316,35 @@ class ChannelCheckerMiddleware(BaseMiddleware): telegram_id, notify_error, ) + await db.commit() except Exception as db_error: logger.error( "❌ Ошибка Π΄Π΅Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ подписки ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ %s послС отписки: %s", telegram_id, db_error, ) - finally: - break + await db.rollback() async def _reactivate_subscription_on_subscribe(self, telegram_id: int, bot: Bot) -> None: """РСактивация подписки послС ΠΏΠΎΠ²Ρ‚ΠΎΡ€Π½ΠΎΠΉ подписки Π½Π° ΠΊΠ°Π½Π°Π».""" if not settings.CHANNEL_DISABLE_TRIAL_ON_UNSUBSCRIBE and not settings.CHANNEL_REQUIRED_FOR_ALL: return - async for db in get_db(): + async with AsyncSessionLocal() as db: try: user = await get_user_by_telegram_id(db, telegram_id) if not user or not user.subscription: - break + return subscription = user.subscription # Π Π΅Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΡƒΠ΅ΠΌ Ρ‚ΠΎΠ»ΡŒΠΊΠΎ DISABLED подписки if subscription.status != SubscriptionStatus.DISABLED.value: - break + return # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ Ρ‡Ρ‚ΠΎ подписка Π΅Ρ‰Ρ‘ Π½Π΅ истСкла if subscription.end_date and subscription.end_date <= datetime.utcnow(): - break + return # Π Π΅Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΡƒΠ΅ΠΌ Π² Π‘Π” await reactivate_subscription(db, subscription) @@ -382,14 +382,14 @@ class ChannelCheckerMiddleware(BaseMiddleware): telegram_id, notify_error, ) + await db.commit() except Exception as db_error: logger.error( "❌ Ошибка Ρ€Π΅Π°ΠΊΡ‚ΠΈΠ²Π°Ρ†ΠΈΠΈ подписки ΠΏΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»Ρ %s: %s", telegram_id, db_error, ) - finally: - break + await db.rollback() @staticmethod async def _deny_message( From e7bd52463d461c7abd0e1d111a3a90fac9ec4360 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 17 Jan 2026 01:15:28 +0300 Subject: [PATCH 2/6] Add files via upload --- app/services/backup_service.py | 14 +-- app/services/daily_subscription_service.py | 100 +++++++++++---------- app/services/monitoring_service.py | 39 ++++---- 3 files changed, 80 insertions(+), 73 deletions(-) diff --git a/app/services/backup_service.py b/app/services/backup_service.py index 08bb4efc..35bdd117 100644 --- a/app/services/backup_service.py +++ b/app/services/backup_service.py @@ -19,7 +19,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.config import settings -from app.database.database import get_db, engine +from app.database.database import AsyncSessionLocal, engine from app.database.models import ( User, Subscription, Transaction, PromoCode, PromoCodeUse, ReferralEarning, Squad, ServiceRule, SystemSetting, MonitoringLog, @@ -475,7 +475,7 @@ class BackupService: backup_data: Dict[str, List[Dict[str, Any]]] = {} total_records = 0 - async for db in get_db(): + async with AsyncSessionLocal() as db: try: for model in models_to_backup: table_name = model.__tablename__ @@ -533,10 +533,6 @@ class BackupService: except Exception as exc: logger.error("Ошибка ΠΏΡ€ΠΈ экспортС Π΄Π°Π½Π½Ρ‹Ρ…: %s", exc) raise exc - finally: - await db.close() - - return backup_data, {}, total_records, len(models_to_backup) async def _collect_files(self, staging_dir: Path, include_logs: bool) -> List[Dict[str, Any]]: files_info: List[Dict[str, Any]] = [] @@ -815,7 +811,7 @@ class BackupService: restored_records = 0 restored_tables = 0 - async for db in get_db(): + async with AsyncSessionLocal() as db: try: if clear_existing: logger.warning("πŸ—‘οΈ ΠžΡ‡ΠΈΡ‰Π°Π΅ΠΌ ΡΡƒΡ‰Π΅ΡΡ‚Π²ΡƒΡŽΡ‰ΠΈΠ΅ Π΄Π°Π½Π½Ρ‹Π΅...") @@ -903,14 +899,10 @@ class BackupService: await db.commit() - break - except Exception as exc: await db.rollback() logger.error("Ошибка ΠΏΡ€ΠΈ восстановлСнии: %s", exc) raise exc - finally: - await db.close() return restored_tables, restored_records diff --git a/app/services/daily_subscription_service.py b/app/services/daily_subscription_service.py index f0f15063..fcbb5e76 100644 --- a/app/services/daily_subscription_service.py +++ b/app/services/daily_subscription_service.py @@ -13,7 +13,7 @@ from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings -from app.database.database import get_db +from app.database.database import AsyncSessionLocal from app.database.crud.subscription import ( get_daily_subscriptions_for_charge, update_daily_charge_time, @@ -65,25 +65,30 @@ class DailySubscriptionService: } try: - async for db in get_db(): - subscriptions = await get_daily_subscriptions_for_charge(db) - stats["checked"] = len(subscriptions) + async with AsyncSessionLocal() as db: + try: + subscriptions = await get_daily_subscriptions_for_charge(db) + stats["checked"] = len(subscriptions) - for subscription in subscriptions: - try: - result = await self._process_single_charge(db, subscription) - if result == "charged": - stats["charged"] += 1 - elif result == "suspended": - stats["suspended"] += 1 - elif result == "error": + for subscription in subscriptions: + try: + result = await self._process_single_charge(db, subscription) + if result == "charged": + stats["charged"] += 1 + elif result == "suspended": + stats["suspended"] += 1 + elif result == "error": + stats["errors"] += 1 + except Exception as e: + logger.error( + f"Ошибка ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ суточной подписки {subscription.id}: {e}", + exc_info=True + ) stats["errors"] += 1 - except Exception as e: - logger.error( - f"Ошибка ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ суточной подписки {subscription.id}: {e}", - exc_info=True - ) - stats["errors"] += 1 + await db.commit() + except Exception as e: + logger.error(f"Ошибка ΠΏΡ€ΠΈ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ΅ подписок: {e}", exc_info=True) + await db.rollback() except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠΈ подписок для списания: {e}", exc_info=True) @@ -272,35 +277,40 @@ class DailySubscriptionService: from app.database.models import TrafficPurchase try: - async for db in get_db(): - # Находим всС ΠΈΡΡ‚Π΅ΠΊΡˆΠΈΠ΅ Π΄ΠΎΠΊΡƒΠΏΠΊΠΈ - now = datetime.utcnow() - query = ( - select(TrafficPurchase) - .where(TrafficPurchase.expires_at <= now) - ) - result = await db.execute(query) - expired_purchases = result.scalars().all() - stats["checked"] = len(expired_purchases) + async with AsyncSessionLocal() as db: + try: + # Находим всС ΠΈΡΡ‚Π΅ΠΊΡˆΠΈΠ΅ Π΄ΠΎΠΊΡƒΠΏΠΊΠΈ + now = datetime.utcnow() + query = ( + select(TrafficPurchase) + .where(TrafficPurchase.expires_at <= now) + ) + result = await db.execute(query) + expired_purchases = result.scalars().all() + stats["checked"] = len(expired_purchases) - # Π“Ρ€ΡƒΠΏΠΏΠΈΡ€ΡƒΠ΅ΠΌ ΠΏΠΎ подпискам для обновлСния - subscriptions_to_update = {} - for purchase in expired_purchases: - if purchase.subscription_id not in subscriptions_to_update: - subscriptions_to_update[purchase.subscription_id] = [] - subscriptions_to_update[purchase.subscription_id].append(purchase) + # Π“Ρ€ΡƒΠΏΠΏΠΈΡ€ΡƒΠ΅ΠΌ ΠΏΠΎ подпискам для обновлСния + subscriptions_to_update = {} + for purchase in expired_purchases: + if purchase.subscription_id not in subscriptions_to_update: + subscriptions_to_update[purchase.subscription_id] = [] + subscriptions_to_update[purchase.subscription_id].append(purchase) - # УдаляСм ΠΈΡΡ‚Π΅ΠΊΡˆΠΈΠ΅ Π΄ΠΎΠΊΡƒΠΏΠΊΠΈ ΠΈ обновляСм подписки - for subscription_id, purchases in subscriptions_to_update.items(): - try: - await self._reset_subscription_traffic(db, subscription_id, purchases) - stats["reset"] += len(purchases) - except Exception as e: - logger.error( - f"Ошибка сброса Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ° подписки {subscription_id}: {e}", - exc_info=True - ) - stats["errors"] += 1 + # УдаляСм ΠΈΡΡ‚Π΅ΠΊΡˆΠΈΠ΅ Π΄ΠΎΠΊΡƒΠΏΠΊΠΈ ΠΈ обновляСм подписки + for subscription_id, purchases in subscriptions_to_update.items(): + try: + await self._reset_subscription_traffic(db, subscription_id, purchases) + stats["reset"] += len(purchases) + except Exception as e: + logger.error( + f"Ошибка сброса Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ° подписки {subscription_id}: {e}", + exc_info=True + ) + stats["errors"] += 1 + await db.commit() + except Exception as e: + logger.error(f"Ошибка ΠΏΡ€ΠΈ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠ΅ сброса Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ°: {e}", exc_info=True) + await db.rollback() except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈ ΠΏΠΎΠ»ΡƒΡ‡Π΅Π½ΠΈΠΈ подписок для сброса Ρ‚Ρ€Π°Ρ„ΠΈΠΊΠ°: {e}", exc_info=True) diff --git a/app/services/monitoring_service.py b/app/services/monitoring_service.py index fffcfc9a..37f41efe 100644 --- a/app/services/monitoring_service.py +++ b/app/services/monitoring_service.py @@ -12,7 +12,7 @@ from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.orm import selectinload from app.config import settings -from app.database.database import get_db +from app.database.database import AsyncSessionLocal from app.database.crud.discount_offer import ( deactivate_expired_offers, get_latest_claimed_offer_for_user, @@ -190,7 +190,7 @@ class MonitoringService: pass async def _monitoring_cycle(self): - async for db in get_db(): + async with AsyncSessionLocal() as db: try: await self._cleanup_notification_cache() @@ -219,23 +219,26 @@ class MonitoringService: await self._process_autopayments(db) await self._cleanup_inactive_users(db) await self._sync_with_remnawave(db) - + await self._log_monitoring_event( - db, "monitoring_cycle_completed", - "Π¦ΠΈΠΊΠ» ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π° ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½", + db, "monitoring_cycle_completed", + "Π¦ΠΈΠΊΠ» ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π° ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ Π·Π°Π²Π΅Ρ€ΡˆΠ΅Π½", {"timestamp": datetime.utcnow().isoformat()} ) - + await db.commit() + except Exception as e: logger.error(f"Ошибка Π² Ρ†ΠΈΠΊΠ»Π΅ ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π°: {e}") - await self._log_monitoring_event( - db, "monitoring_cycle_error", - f"Ошибка Π² Ρ†ΠΈΠΊΠ»Π΅ ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π°: {str(e)}", - {"error": str(e)}, - is_success=False - ) - finally: - break + try: + await self._log_monitoring_event( + db, "monitoring_cycle_error", + f"Ошибка Π² Ρ†ΠΈΠΊΠ»Π΅ ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π°: {str(e)}", + {"error": str(e)}, + is_success=False + ) + except Exception: + pass + await db.rollback() async def _cleanup_notification_cache(self): current_time = datetime.utcnow() @@ -1724,11 +1727,13 @@ class MonitoringService: interval_seconds = 60 while self.is_running: try: - async for db in get_db(): + async with AsyncSessionLocal() as db: try: await self._check_ticket_sla(db) - finally: - break + await db.commit() + except Exception as e: + logger.error(f"Ошибка Π² SLA-ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠ΅: {e}") + await db.rollback() except asyncio.CancelledError: break except Exception as e: From 5cb0ce3030c43b2f90cef9cdb3adec0ab34b41a3 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 17 Jan 2026 01:16:10 +0300 Subject: [PATCH 3/6] Add files via upload --- app/handlers/stars_payments.py | 5 ++--- app/handlers/webhooks.py | 41 +++++++++++++++++----------------- 2 files changed, 22 insertions(+), 24 deletions(-) diff --git a/app/handlers/stars_payments.py b/app/handlers/stars_payments.py index f64f337b..5e239ab5 100644 --- a/app/handlers/stars_payments.py +++ b/app/handlers/stars_payments.py @@ -262,9 +262,9 @@ async def handle_pre_checkout_query(query: types.PreCheckoutQuery): return try: - from app.database.database import get_db + from app.database.database import AsyncSessionLocal - async for db in get_db(): + async with AsyncSessionLocal() as db: user = await get_user_by_telegram_id(db, query.from_user.id) if not user: logger.warning(f"ΠŸΠΎΠ»ΡŒΠ·ΠΎΠ²Π°Ρ‚Π΅Π»ΡŒ {query.from_user.id} Π½Π΅ Π½Π°ΠΉΠ΄Π΅Π½ Π² Π‘Π”") @@ -277,7 +277,6 @@ async def handle_pre_checkout_query(query: types.PreCheckoutQuery): ) return texts = get_texts(user.language or DEFAULT_LANGUAGE) - break except Exception as db_error: logger.error(f"Ошибка ΠΏΠΎΠ΄ΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΡ ΠΊ Π‘Π” Π² pre_checkout_query: {db_error}") await query.answer( diff --git a/app/handlers/webhooks.py b/app/handlers/webhooks.py index c6cd8ee7..d7a81759 100644 --- a/app/handlers/webhooks.py +++ b/app/handlers/webhooks.py @@ -4,7 +4,7 @@ from aiohttp import web from sqlalchemy.ext.asyncio import AsyncSession from app.config import settings -from app.database.database import get_db +from app.database.database import AsyncSessionLocal from app.database.crud.user import get_user_by_id, add_user_balance from app.database.crud.transaction import create_transaction, get_transaction_by_external_id from app.database.models import TransactionType, PaymentMethod @@ -31,25 +31,25 @@ async def tribute_webhook(request): logger.error("Ошибка ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ Tribute webhook") return web.Response(status=400, text="Invalid webhook data") - async for db in get_db(): + async with AsyncSessionLocal() as db: try: existing_transaction = await get_transaction_by_external_id( db, processed_data['payment_id'], PaymentMethod.TRIBUTE ) - + if existing_transaction: logger.info(f"ΠŸΠ»Π°Ρ‚Π΅ΠΆ {processed_data['payment_id']} ΡƒΠΆΠ΅ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π½") return web.Response(status=200, text="Already processed") - + if processed_data['status'] == 'completed': user = await get_user_by_id(db, processed_data['user_id']) - + if user: await add_user_balance( db, user, processed_data['amount_kopeks'], f"ПополнСниС Ρ‡Π΅Ρ€Π΅Π· Tribute: {processed_data['payment_id']}" ) - + await create_transaction( db=db, user_id=user.id, @@ -59,17 +59,16 @@ async def tribute_webhook(request): payment_method=PaymentMethod.TRIBUTE, external_id=processed_data['payment_id'] ) - + logger.info(f"βœ… ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π½ Tribute ΠΏΠ»Π°Ρ‚Π΅ΠΆ: {processed_data['payment_id']}") - + + await db.commit() return web.Response(status=200, text="OK") - + except Exception as e: logger.error(f"Ошибка ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ Tribute webhook: {e}") await db.rollback() return web.Response(status=500, text="Internal error") - finally: - break except Exception as e: logger.error(f"Ошибка Π² Tribute webhook: {e}") @@ -85,24 +84,24 @@ async def handle_successful_payment(message: types.Message): user_id = int(payload_parts[1]) amount_kopeks = int(payload_parts[2]) - async for db in get_db(): + async with AsyncSessionLocal() as db: try: existing_transaction = await get_transaction_by_external_id( db, payment.telegram_payment_charge_id, PaymentMethod.TELEGRAM_STARS ) - + if existing_transaction: logger.info(f"Stars ΠΏΠ»Π°Ρ‚Π΅ΠΆ {payment.telegram_payment_charge_id} ΡƒΠΆΠ΅ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π½") return - + user = await get_user_by_id(db, user_id) - + if user: await add_user_balance( db, user, amount_kopeks, f"ПополнСниС Ρ‡Π΅Ρ€Π΅Π· Telegram Stars" ) - + await create_transaction( db=db, user_id=user.id, @@ -112,7 +111,7 @@ async def handle_successful_payment(message: types.Message): payment_method=PaymentMethod.TELEGRAM_STARS, external_id=payment.telegram_payment_charge_id ) - + await message.answer( f"βœ… Баланс ΡƒΡΠΏΠ΅ΡˆΠ½ΠΎ ΠΏΠΎΠΏΠΎΠ»Π½Π΅Π½ Π½Π° {settings.format_price(amount_kopeks)}!\n\n" "⚠️ Π’Π°ΠΆΠ½ΠΎ: ПополнСниС баланса Π½Π΅ Π°ΠΊΡ‚ΠΈΠ²ΠΈΡ€ΡƒΠ΅Ρ‚ подписку автоматичСски. " @@ -120,14 +119,14 @@ async def handle_successful_payment(message: types.Message): f"πŸ”„ ΠŸΡ€ΠΈ Π½Π°Π»ΠΈΡ‡ΠΈΠΈ сохранённой ΠΊΠΎΡ€Π·ΠΈΠ½Ρ‹ подписки ΠΈ Π²ΠΊΠ»ΡŽΡ‡Π΅Π½Π½ΠΎΠΉ Π°Π²Ρ‚ΠΎΠΏΠΎΠΊΡƒΠΏΠΊΠ΅, " f"подписка Π±ΡƒΠ΄Π΅Ρ‚ ΠΏΡ€ΠΈΠΎΠ±Ρ€Π΅Ρ‚Π΅Π½Π° автоматичСски послС пополнСния баланса." ) - + logger.info(f"βœ… ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π½ Stars ΠΏΠ»Π°Ρ‚Π΅ΠΆ: {payment.telegram_payment_charge_id}") - + + await db.commit() + except Exception as e: logger.error(f"Ошибка ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ Stars ΠΏΠ»Π°Ρ‚Π΅ΠΆΠ°: {e}") await db.rollback() - finally: - break except Exception as e: logger.error(f"Ошибка Π² ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠ΅ Stars ΠΏΠ»Π°Ρ‚Π΅ΠΆΠ°: {e}") From 1b4758cdbfc2144e2480a00b7789cc28197d3e60 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 17 Jan 2026 01:16:50 +0300 Subject: [PATCH 4/6] Update monitoring.py --- app/handlers/admin/monitoring.py | 81 +++++++++++++++----------------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/app/handlers/admin/monitoring.py b/app/handlers/admin/monitoring.py index 178d3e0c..6efae06c 100644 --- a/app/handlers/admin/monitoring.py +++ b/app/handlers/admin/monitoring.py @@ -8,7 +8,7 @@ from aiogram.fsm.context import FSMContext from aiogram.exceptions import TelegramBadRequest from app.config import settings -from app.database.database import get_db +from app.database.database import AsyncSessionLocal from app.services.monitoring_service import monitoring_service from app.services.nalogo_queue_service import nalogo_queue_service from app.services.traffic_monitoring_service import ( @@ -383,12 +383,12 @@ async def _render_notification_settings_for_state( @admin_required async def admin_monitoring_menu(callback: CallbackQuery): try: - async for db in get_db(): + async with AsyncSessionLocal() as db: status = await monitoring_service.get_monitoring_status(db) - + running_status = "🟒 Π Π°Π±ΠΎΡ‚Π°Π΅Ρ‚" if status['is_running'] else "πŸ”΄ ΠžΡΡ‚Π°Π½ΠΎΠ²Π»Π΅Π½" last_update = status['last_update'].strftime('%H:%M:%S') if status['last_update'] else "Никогда" - + text = f""" πŸ” БистСма ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π° @@ -404,12 +404,11 @@ async def admin_monitoring_menu(callback: CallbackQuery): πŸ”§ Π’Ρ‹Π±Π΅Ρ€ΠΈΡ‚Π΅ дСйствиС: """ - + language = callback.from_user.language_code or settings.DEFAULT_LANGUAGE keyboard = get_monitoring_keyboard(language) await callback.message.edit_text(text, parse_mode="HTML", reply_markup=keyboard) - break - + except Exception as e: logger.error(f"Ошибка Π² Π°Π΄ΠΌΠΈΠ½ мСню ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π°: {e}") await callback.answer("❌ Ошибка получСния Π΄Π°Π½Π½Ρ‹Ρ…", show_alert=True) @@ -742,7 +741,7 @@ async def force_check_callback(callback: CallbackQuery): try: await callback.answer("⏳ ВыполняСм ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΡƒ подписок...") - async for db in get_db(): + async with AsyncSessionLocal() as db: results = await monitoring_service.force_check_subscriptions(db) text = f""" @@ -764,7 +763,6 @@ async def force_check_callback(callback: CallbackQuery): ]) await callback.message.edit_text(text, parse_mode="HTML", reply_markup=keyboard) - break except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈΠ½ΡƒΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠΉ ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ: {e}") @@ -843,46 +841,45 @@ async def monitoring_logs_callback(callback: CallbackQuery): if "_page_" in callback.data: page = int(callback.data.split("_page_")[1]) - async for db in get_db(): + async with AsyncSessionLocal() as db: all_logs = await monitoring_service.get_monitoring_logs(db, limit=1000) - + if not all_logs: text = "πŸ“‹ Π›ΠΎΠ³ΠΈ ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π° пусты\n\nБистСма Π΅Ρ‰Π΅ Π½Π΅ Π²Ρ‹ΠΏΠΎΠ»Π½ΠΈΠ»Π° ΠΏΡ€ΠΎΠ²Π΅Ρ€ΠΊΠΈ." keyboard = get_monitoring_logs_back_keyboard() await callback.message.edit_text(text, parse_mode="HTML", reply_markup=keyboard) return - + per_page = 8 paginated_logs = paginate_list(all_logs, page=page, per_page=per_page) - + text = f"πŸ“‹ Π›ΠΎΠ³ΠΈ ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π° (стр. {page}/{paginated_logs.total_pages})\n\n" - + for log in paginated_logs.items: icon = "βœ…" if log['is_success'] else "❌" time_str = log['created_at'].strftime('%m-%d %H:%M') event_type = log['event_type'].replace('_', ' ').title() - + message = log['message'] if len(message) > 45: message = message[:45] + "..." - + text += f"{icon} {time_str} {event_type}\n" text += f" πŸ“„ {message}\n\n" - + total_success = sum(1 for log in all_logs if log['is_success']) total_failed = len(all_logs) - total_success success_rate = round(total_success / len(all_logs) * 100, 1) if all_logs else 0 - + text += f"πŸ“Š ΠžΠ±Ρ‰Π°Ρ статистика:\n" text += f"β€’ ВсСго событий: {len(all_logs)}\n" text += f"β€’ Π£ΡΠΏΠ΅ΡˆΠ½Ρ‹Ρ…: {total_success}\n" text += f"β€’ Ошибок: {total_failed}\n" text += f"β€’ Π£ΡΠΏΠ΅ΡˆΠ½ΠΎΡΡ‚ΡŒ: {success_rate}%" - + keyboard = get_monitoring_logs_keyboard(page, paginated_logs.total_pages) await callback.message.edit_text(text, parse_mode="HTML", reply_markup=keyboard) - break - + except Exception as e: logger.error(f"Ошибка получСния Π»ΠΎΠ³ΠΎΠ²: {e}") await callback.answer("❌ Ошибка получСния Π»ΠΎΠ³ΠΎΠ²", show_alert=True) @@ -892,17 +889,17 @@ async def monitoring_logs_callback(callback: CallbackQuery): @admin_required async def clear_logs_callback(callback: CallbackQuery): try: - async for db in get_db(): - deleted_count = await monitoring_service.cleanup_old_logs(db, days=0) - + async with AsyncSessionLocal() as db: + deleted_count = await monitoring_service.cleanup_old_logs(db, days=0) + await db.commit() + if deleted_count > 0: await callback.answer(f"πŸ—‘οΈ Π£Π΄Π°Π»Π΅Π½ΠΎ {deleted_count} записСй Π»ΠΎΠ³ΠΎΠ²") else: await callback.answer("ℹ️ Π›ΠΎΠ³ΠΈ ΡƒΠΆΠ΅ пусты") - + await monitoring_logs_callback(callback) - break - + except Exception as e: logger.error(f"Ошибка очистки Π»ΠΎΠ³ΠΎΠ²: {e}") await callback.answer(f"❌ Ошибка очистки: {str(e)}", show_alert=True) @@ -942,19 +939,19 @@ async def test_notifications_callback(callback: CallbackQuery): @admin_required async def monitoring_statistics_callback(callback: CallbackQuery): try: - async for db in get_db(): + async with AsyncSessionLocal() as db: from app.database.crud.subscription import get_subscriptions_statistics sub_stats = await get_subscriptions_statistics(db) - + mon_status = await monitoring_service.get_monitoring_status(db) - + week_ago = datetime.now() - timedelta(days=7) week_logs = await monitoring_service.get_monitoring_logs(db, limit=1000) week_logs = [log for log in week_logs if log['created_at'] >= week_ago] - + week_success = sum(1 for log in week_logs if log['is_success']) week_errors = len(week_logs) - week_success - + text = f""" πŸ“Š Бтатистика ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π° @@ -1028,8 +1025,7 @@ async def monitoring_statistics_callback(callback: CallbackQuery): keyboard = InlineKeyboardMarkup(inline_keyboard=buttons) await callback.message.edit_text(text, parse_mode="HTML", reply_markup=keyboard) - break - + except Exception as e: logger.error(f"Ошибка получСния статистики: {e}") await callback.answer(f"❌ Ошибка получСния статистики: {str(e)}", show_alert=True) @@ -1068,7 +1064,7 @@ async def nalogo_force_process_callback(callback: CallbackQuery): from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton # ΠŸΠ΅Ρ€Π΅Π·Π°Π³Ρ€ΡƒΠΆΠ°Π΅ΠΌ статистику - async for db in get_db(): + async with AsyncSessionLocal() as db: from app.database.crud.subscription import get_subscriptions_statistics sub_stats = await get_subscriptions_statistics(db) mon_status = await monitoring_service.get_monitoring_status(db) @@ -1139,7 +1135,6 @@ async def nalogo_force_process_callback(callback: CallbackQuery): keyboard = InlineKeyboardMarkup(inline_keyboard=buttons) await callback.message.edit_text(stats_text, parse_mode="HTML", reply_markup=keyboard) - break except Exception as e: logger.error(f"Ошибка ΠΏΡ€ΠΈΠ½ΡƒΠ΄ΠΈΡ‚Π΅Π»ΡŒΠ½ΠΎΠΉ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ Ρ‡Π΅ΠΊΠΎΠ²: {e}") @@ -1309,7 +1304,7 @@ async def receipts_link_old_callback(callback: CallbackQuery): TRACKING_START_DATE = datetime(2024, 12, 29, 0, 0, 0) - async for db in get_db(): + async with AsyncSessionLocal() as db: # ΠŸΠΎΠ»ΡƒΡ‡Π°Π΅ΠΌ старыС Ρ‚Ρ€Π°Π½Π·Π°ΠΊΡ†ΠΈΠΈ Π±Π΅Π· Ρ‡Π΅ΠΊΠΎΠ² query = select(Transaction).where( and_( @@ -1387,7 +1382,6 @@ async def receipts_link_old_callback(callback: CallbackQuery): ]) await callback.message.edit_text(text, parse_mode="HTML", reply_markup=keyboard) - break except Exception as e: logger.error(f"Ошибка привязки старых Ρ‡Π΅ΠΊΠΎΠ²: {e}", exc_info=True) @@ -1695,11 +1689,11 @@ def get_monitoring_logs_back_keyboard(): @admin_required async def monitoring_command(message: Message): try: - async for db in get_db(): + async with AsyncSessionLocal() as db: status = await monitoring_service.get_monitoring_status(db) - + running_status = "🟒 Π Π°Π±ΠΎΡ‚Π°Π΅Ρ‚" if status['is_running'] else "πŸ”΄ ΠžΡΡ‚Π°Π½ΠΎΠ²Π»Π΅Π½" - + text = f""" πŸ” Быстрый статус ΠΌΠΎΠ½ΠΈΡ‚ΠΎΡ€ΠΈΠ½Π³Π° @@ -1709,10 +1703,9 @@ async def monitoring_command(message: Message): Для ΠΏΠΎΠ΄Ρ€ΠΎΠ±Π½ΠΎΠ³ΠΎ управлСния ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠΉΡ‚Π΅ Π°Π΄ΠΌΠΈΠ½-панСль. """ - + await message.answer(text, parse_mode="HTML") - break - + except Exception as e: logger.error(f"Ошибка ΠΊΠΎΠΌΠ°Π½Π΄Ρ‹ /monitoring: {e}") await message.answer(f"❌ Ошибка: {str(e)}") From 5fcc20254291a28b947639bb827461f591e959e1 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 17 Jan 2026 01:18:02 +0300 Subject: [PATCH 5/6] Add files via upload --- app/external/heleket_webhook.py | 12 +++++++++--- app/external/wata_webhook.py | 15 +++++++++------ app/external/yookassa_webhook.py | 14 +++++++++----- 3 files changed, 27 insertions(+), 14 deletions(-) diff --git a/app/external/heleket_webhook.py b/app/external/heleket_webhook.py index ace0fd90..bb26b201 100644 --- a/app/external/heleket_webhook.py +++ b/app/external/heleket_webhook.py @@ -6,7 +6,7 @@ from typing import Any, Dict, Optional from aiohttp import web from app.config import settings -from app.database.database import get_db +from app.database.database import AsyncSessionLocal from app.external.heleket import HeleketService from app.services.payment_service import PaymentService @@ -33,8 +33,14 @@ class HeleketWebhookHandler: return web.json_response({"status": "error", "reason": "invalid_signature"}, status=401) processed: Optional[bool] = None - async for db in get_db(): - processed = await self.payment_service.process_heleket_webhook(db, payload) + async with AsyncSessionLocal() as db: + try: + processed = await self.payment_service.process_heleket_webhook(db, payload) + await db.commit() + except Exception as e: + logger.error(f"Ошибка ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ Heleket webhook: {e}") + await db.rollback() + return web.json_response({"status": "error", "reason": "internal_error"}, status=500) if processed: return web.json_response({"status": "ok"}, status=200) diff --git a/app/external/wata_webhook.py b/app/external/wata_webhook.py index b600af21..9e3de0d7 100644 --- a/app/external/wata_webhook.py +++ b/app/external/wata_webhook.py @@ -14,7 +14,7 @@ from cryptography.hazmat.primitives import hashes, serialization from cryptography.hazmat.primitives.asymmetric import padding from app.config import settings -from app.database.database import get_db +from app.database.database import AsyncSessionLocal from app.services.payment_service import PaymentService logger = logging.getLogger(__name__) @@ -170,11 +170,14 @@ class WataWebhookHandler: ) processed: Optional[bool] = None - async for db in get_db(): - processed = await self.payment_service.process_wata_webhook(db, payload) - # Allow the generator to finish naturally so it can commit/rollback. - # get_db() yields only once, so exiting the loop body without breaking - # triggers the generator cleanup logic on the next iteration attempt. + async with AsyncSessionLocal() as db: + try: + processed = await self.payment_service.process_wata_webhook(db, payload) + await db.commit() + except Exception as e: + logger.error(f"Ошибка ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ WATA webhook: {e}") + await db.rollback() + return web.json_response({"status": "error", "reason": "internal_error"}, status=500) if processed is None: logger.error("НС ΡƒΠ΄Π°Π»ΠΎΡΡŒ ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Π°Ρ‚ΡŒ WATA webhook: Π½Π΅Ρ‚ сСссии Π‘Π”") diff --git a/app/external/yookassa_webhook.py b/app/external/yookassa_webhook.py index 246c948a..b9bb3a4e 100644 --- a/app/external/yookassa_webhook.py +++ b/app/external/yookassa_webhook.py @@ -15,7 +15,7 @@ from typing import Iterable, Optional, Dict, Any, List, Union, Tuple, TYPE_CHECK from aiohttp import web from app.config import settings -from app.database.database import get_db +from app.database.database import AsyncSessionLocal if TYPE_CHECKING: from app.services.payment_service import PaymentService @@ -273,7 +273,7 @@ class YooKassaWebhookHandler: logger.info(f"ℹ️ Π˜Π³Π½ΠΎΡ€ΠΈΡ€ΡƒΠ΅ΠΌ событиС YooKassa: {event_type}") return web.Response(status=200, text="OK") - async for db in get_db(): + async with AsyncSessionLocal() as db: try: # ΠŸΡ€ΠΎΠ²Π΅Ρ€ΡΠ΅ΠΌ, Π½Π΅ обрабатываСтся Π»ΠΈ этот ΠΏΠ»Π°Ρ‚Π΅ΠΆ ΡƒΠΆΠ΅ (Π·Π°Ρ‰ΠΈΡ‚Π° ΠΎΡ‚ дублирования) from app.database.models import PaymentMethod @@ -285,18 +285,22 @@ class YooKassaWebhookHandler: if existing_transaction and event_type == "payment.succeeded": logger.info(f"ℹ️ ΠŸΠ»Π°Ρ‚Π΅ΠΆ YooKassa {yookassa_payment_id} ΡƒΠΆΠ΅ Π±Ρ‹Π» ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π½. ΠŸΡ€ΠΎΠΏΡƒΡΠΊΠ°Π΅ΠΌ Π΄ΡƒΠ±Π»ΠΈΡ€ΡƒΡŽΡ‰ΠΈΠΉ Π²Π΅Π±Ρ…ΡƒΠΊ.") return web.Response(status=200, text="OK") - + success = await self.payment_service.process_yookassa_webhook(db, webhook_data) if success: + await db.commit() logger.info(f"βœ… УспСшно ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚Π°Π½ webhook YooKassa: {event_type} для ΠΏΠ»Π°Ρ‚Π΅ΠΆΠ° {yookassa_payment_id}") return web.Response(status=200, text="OK") else: + await db.rollback() logger.error(f"❌ Ошибка ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ webhook YooKassa: {event_type} для ΠΏΠ»Π°Ρ‚Π΅ΠΆΠ° {yookassa_payment_id}") return web.Response(status=500, text="Processing error") - finally: - await db.close() + except Exception as e: + await db.rollback() + logger.error(f"❌ Ошибка ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ webhook YooKassa: {e}", exc_info=True) + return web.Response(status=500, text="Processing error") except Exception as e: logger.error(f"❌ ΠšΡ€ΠΈΡ‚ΠΈΡ‡Π΅ΡΠΊΠ°Ρ ошибка ΠΎΠ±Ρ€Π°Π±ΠΎΡ‚ΠΊΠΈ webhook YooKassa: {e}", exc_info=True) From c61cd0b42e24129103ae2cf070eeaf42ba1365a0 Mon Sep 17 00:00:00 2001 From: Egor Date: Sat, 17 Jan 2026 01:19:12 +0300 Subject: [PATCH 6/6] Update texts.py --- app/localization/texts.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/app/localization/texts.py b/app/localization/texts.py index d66f2a15..768dce3a 100644 --- a/app/localization/texts.py +++ b/app/localization/texts.py @@ -207,15 +207,14 @@ def get_texts(language: str = DEFAULT_LANGUAGE) -> Texts: async def get_rules_from_db(language: str = DEFAULT_LANGUAGE) -> str: try: - from app.database.database import get_db + from app.database.database import AsyncSessionLocal from app.database.crud.rules import get_current_rules_content - async for db in get_db(): + async with AsyncSessionLocal() as db: rules = await get_current_rules_content(db, language) if rules: _cached_rules[language] = rules return rules - break except Exception as error: # pragma: no cover - defensive logging _logger.warning("Failed to load rules from DB for %s: %s", language, error)