feat: add traffic % warning check using user's threshold preference

This commit is contained in:
c0mrade
2026-04-10 15:59:32 +03:00
parent 4e50419171
commit 1d96f80f60
+112 -11
View File
@@ -245,6 +245,7 @@ class MonitoringService:
await self._check_trial_expiring_soon(db)
await self._check_trial_channel_subscriptions(db)
await self._check_expired_subscription_followups(db)
await self._check_traffic_warnings(db)
await self._check_low_balance_alerts(db)
await self._retry_stuck_guest_purchases(db)
await self._cleanup_inactive_users(db)
@@ -518,17 +519,17 @@ class MonitoringService:
users_with_cards = await get_user_ids_with_active_payment_methods(db, autopay_user_ids)
from app.utils.notification_prefs import (
get_subscription_expiry_days,
is_subscription_expiry_enabled,
)
for subscription in expiring_subscriptions:
user = await get_user_by_id(db, subscription.user_id)
if not user:
continue
# Respect user notification preferences
from app.utils.notification_prefs import (
get_subscription_expiry_days,
is_subscription_expiry_enabled,
)
if not is_subscription_expiry_enabled(user):
continue
@@ -1975,6 +1976,97 @@ class MonitoringService:
except Exception:
logger.error('Error retrying stuck PENDING_ACTIVATION guest purchases', exc_info=True)
async def _check_traffic_warnings(self, db: AsyncSession):
"""Check subscriptions approaching traffic limit and notify users."""
if not self.bot:
return
try:
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from app.database.models import Subscription, User
from app.utils.notification_prefs import get_traffic_warning_percent, is_traffic_warning_enabled
# Get active subscriptions with traffic limits (not unlimited)
result = await db.execute(
select(Subscription)
.options(selectinload(Subscription.user))
.where(
Subscription.status.in_(['active', 'trial']),
Subscription.traffic_limit_gb > 0,
)
)
subscriptions = result.scalars().all()
sent_count = 0
for subscription in subscriptions:
user = subscription.user
if not user or not user.telegram_id:
continue
if not is_traffic_warning_enabled(user):
continue
traffic_limit = subscription.traffic_limit_gb or 0
traffic_used = subscription.traffic_used_gb or 0.0
if traffic_limit <= 0:
continue
current_percent = (traffic_used / traffic_limit) * 100
user_threshold = get_traffic_warning_percent(user)
if current_percent < user_threshold:
continue
# Rate-limit: 1 notification per subscription per 24 hours
cache_key_str = f'traffic_warn:{subscription.id}'
try:
already_sent = await cache.get(cache_key_str)
if already_sent:
continue
except Exception:
pass
try:
language = getattr(user, 'language', 'ru') or 'ru'
texts = get_texts(language)
message = texts.get(
'TRAFFIC_WARNING_ALERT',
'⚠️ <b>Предупреждение о трафике</b>\n\n'
'Использовано: {used:.1f} / {limit} ГБ ({percent:.0f}%)\n\n'
'Ваш лимит трафика почти исчерпан.',
)
message = message.format(
used=traffic_used,
limit=traffic_limit,
percent=current_percent,
)
await self.bot.send_message(
user.telegram_id,
message,
parse_mode='HTML',
)
try:
await cache.set(cache_key_str, '1', expire=86400)
except Exception:
pass
sent_count += 1
except Exception as send_error:
logger.debug(
'Failed to send traffic warning',
user_id=user.id,
subscription_id=subscription.id,
error=send_error,
)
if sent_count > 0:
logger.info('Traffic warnings sent', sent_count=sent_count)
except Exception as error:
logger.error('Error checking traffic warnings', error=error)
async def _check_low_balance_alerts(self, db: AsyncSession):
"""Check users with autopay enabled who have low balance and notify them."""
if not self.bot:
@@ -1983,7 +2075,6 @@ class MonitoringService:
try:
from sqlalchemy import select
from app.database.crud.notification import notification_sent, record_notification
from app.database.models import Subscription, User
from app.utils.notification_prefs import get_balance_low_threshold, is_balance_low_enabled
@@ -2011,12 +2102,18 @@ class MonitoringService:
if balance >= threshold:
continue
# Don't spam — check if already notified today
if await notification_sent(db, user.id, 0, 'low_balance'):
continue
# Rate-limit via Redis: max 1 notification per 24 hours per user
cache_key_str = f'low_balance_alert:{user.id}'
try:
already_sent = await cache.get(cache_key_str)
if already_sent:
continue
except Exception:
pass
try:
texts = get_texts(user.language)
language = getattr(user, 'language', 'ru') or 'ru'
texts = get_texts(language)
threshold_rub = threshold / 100
balance_rub = balance / 100
message = texts.get(
@@ -2035,7 +2132,11 @@ class MonitoringService:
message,
parse_mode='HTML',
)
await record_notification(db, user.id, 0, 'low_balance')
# Mark as sent for 24 hours
try:
await cache.set(cache_key_str, '1', expire=86400)
except Exception:
pass
sent_count += 1
except Exception as send_error:
logger.debug('Failed to send low balance alert', user_id=user.id, error=send_error)