Migrating to Postgres from sqlite3

This commit is contained in:
machka
2025-05-20 18:41:32 +00:00
parent ac2f83c061
commit a56d804b58
38 changed files with 5017 additions and 4389 deletions
+86 -65
View File
@@ -2,95 +2,115 @@ import logging
import asyncio
from aiogram import Bot
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from datetime import datetime
from datetime import datetime, timezone
from config.settings import Settings
from .subscription_service import SubscriptionService
from db.database import get_db_connection_manager
from sqlalchemy.orm import sessionmaker
from bot.middlewares.i18n import JsonI18n
from .panel_api_service import PanelApiService
from bot.services.panel_api_service import PanelApiService
from bot.services.subscription_service import SubscriptionService
async def send_expiration_warnings(bot: Bot, settings: Settings,
i18n: JsonI18n,
panel_service: PanelApiService):
"""
Sends expiration warnings to users whose subscriptions are ending soon.
This function is called by the scheduler.
"""
panel_service: PanelApiService,
async_session_factory: sessionmaker):
logging.info(
f"Scheduler job: Checking for expiring subscriptions at {datetime.now()}..."
f"Scheduler job 'send_expiration_warnings' started at {datetime.now(timezone.utc)} UTC."
)
sub_service = SubscriptionService(get_db_connection_manager, settings,
panel_service)
expiring_subs = await sub_service.get_subscriptions_ending_soon(
settings.SUBSCRIPTION_EXPIRATION_NOTIFICATION_DAYS)
if not expiring_subs:
logging.info("No subscriptions found ending soon for notification.")
if async_session_factory is None:
logging.error(
"NotificationService: AsyncSessionFactory not provided to send_expiration_warnings!"
)
return
logging.info(
f"Found {len(expiring_subs)} subscriptions ending soon for notification."
)
for sub_info in expiring_subs:
user_id = sub_info['user_id']
if not user_id:
logging.warning(
f"Skipping notification for subscription without user_id: {sub_info}"
)
continue
user_lang = sub_info.get('language_code') if sub_info.get(
'language_code') else getattr(settings, 'DEFAULT_LANGUAGE', 'en')
first_name = sub_info.get('first_name', 'User')
end_date_str = sub_info['end_date_str']
days_left_float = sub_info.get('days_left')
days_left_display = 'N/A'
if days_left_float is not None:
days_left_display = max(0, int(round(days_left_float)))
_ = lambda key, **kwargs: i18n.gettext(user_lang, key, **kwargs)
message_text = _("subscription_ending_soon_notification",
user_name=first_name,
end_date=end_date_str,
days_left=days_left_display)
async with async_session_factory() as session:
try:
await bot.send_message(user_id, message_text)
await sub_service.update_last_notification_sent(
user_id, end_date_str)
sub_service = SubscriptionService(settings, panel_service)
expiring_subs_details_list = await sub_service.get_subscriptions_ending_soon(
session, settings.SUBSCRIPTION_EXPIRATION_NOTIFICATION_DAYS)
if not expiring_subs_details_list:
logging.info(
"No subscriptions found ending soon for notification.")
return
logging.info(
f"Sent expiration warning to user {user_id} for subscription ending {end_date_str}"
f"Found {len(expiring_subs_details_list)} subscriptions for expiration warning."
)
except Exception as e:
for sub_details in expiring_subs_details_list:
user_id = sub_details['user_id']
user_lang = sub_details.get('language_code',
settings.DEFAULT_LANGUAGE)
first_name = sub_details.get('first_name', f"User {user_id}")
end_date_str_for_msg = sub_details.get('end_date_str', "N/A")
days_left_display = sub_details.get('days_left', "N/A")
subscription_actual_end_date_obj: Optional[
datetime] = sub_details.get(
'subscription_end_date_iso_for_update')
_ = lambda key, **kwargs: i18n.gettext(user_lang, key, **kwargs
)
message_text = _("subscription_ending_soon_notification",
user_name=first_name,
end_date=end_date_str_for_msg,
days_left=days_left_display)
try:
await bot.send_message(user_id, message_text)
logging.info(
f"Sent expiration warning to user {user_id} for subscription ending {end_date_str_for_msg}."
)
if subscription_actual_end_date_obj:
await sub_service.update_last_notification_sent(
session, user_id, subscription_actual_end_date_obj)
else:
logging.warning(
f"Could not find exact subscription end_date_obj for user {user_id} to update notification time."
)
except Exception as e:
logging.error(
f"Failed to send expiration warning or update notification status for user {user_id}: {e}",
exc_info=True)
await asyncio.sleep(0.1)
await session.commit()
logging.info(
"Finished processing expiration warnings. Session committed.")
except Exception as e_session:
logging.error(
f"Failed to send expiration warning to user {user_id}: {e}")
await asyncio.sleep(0.1)
f"Error during send_expiration_warnings session: {e_session}",
exc_info=True)
await session.rollback()
logging.info(
"Session rolled back due to error in send_expiration_warnings."
)
async def schedule_subscription_notifications(bot: Bot, settings: Settings,
i18n: JsonI18n,
scheduler: AsyncIOScheduler):
"""Schedules the daily job to send expiration warnings."""
async def schedule_subscription_notifications(
bot: Bot, settings: Settings, i18n: JsonI18n,
scheduler: AsyncIOScheduler, panel_service: PanelApiService,
async_session_factory: sessionmaker):
async def job_wrapper():
panel_service = PanelApiService(settings)
try:
await send_expiration_warnings(bot, settings, i18n, panel_service)
try:
await send_expiration_warnings(bot, settings, i18n, panel_service,
async_session_factory)
except Exception as e:
logging.error(
f"Error in scheduled job 'send_expiration_warnings': {e}",
f"Unhandled error in scheduled job 'send_expiration_warnings' (job_wrapper): {e}",
exc_info=True)
finally:
await panel_service.close_session()
try:
notification_hour = int(settings.SUBSCRIPTION_NOTIFICATION_HOUR_UTC)
@@ -98,7 +118,7 @@ async def schedule_subscription_notifications(bot: Bot, settings: Settings,
settings.SUBSCRIPTION_NOTIFICATION_MINUTE_UTC)
except (ValueError, TypeError):
logging.warning(
"SUBSCRIPTION_NOTIFICATION_HOUR_UTC or MINUTE_UTC is invalid. Defaulting to 9:00 UTC."
"SUBSCRIPTION_NOTIFICATION_HOUR_UTC or MINUTE_UTC is invalid in settings. Defaulting to 9:00 UTC."
)
notification_hour = 9
notification_minute = 0
@@ -107,8 +127,9 @@ async def schedule_subscription_notifications(bot: Bot, settings: Settings,
'cron',
hour=notification_hour,
minute=notification_minute,
name="daily_subscription_expiration_warnings",
misfire_grace_time=60 * 15)
name="daily_subscription_expiration_warnings_v2",
misfire_grace_time=60 * 15,
replace_existing=True)
logging.info(
f"Subscription expiration warning job scheduled daily at {notification_hour:02d}:{notification_minute:02d} UTC."
)