Compare commits

..

No commits in common. "master" and "main" have entirely different histories.
master ... main

17 changed files with 21 additions and 75 deletions

Binary file not shown.

View File

@ -49,19 +49,12 @@ async def check_and_send_reminders(bot: Bot) -> None:
Args:
bot: Bot instance
"""
from bot.db.base import async_session_maker
from bot.db.operations import update_reminder
if not async_session_maker:
logger.error("Session maker not initialized")
return
try:
time_service = get_time_service()
current_time = time_service.get_now()
# Get due reminders from database using proper async session
async with async_session_maker() as session:
# Get due reminders from database
async for session in get_session():
due_reminders = await get_due_reminders(session, current_time)
if not due_reminders:
@ -81,15 +74,16 @@ async def check_and_send_reminders(bot: Bot) -> None:
# Update next_run_at to prevent sending again
# (it will be properly updated when user clicks "Done" or by periodic update)
from bot.db.operations import update_reminder
from datetime import timedelta
# Temporarily set next_run_at to current + interval to avoid duplicate sends
temp_next_run = time_service.calculate_next_occurrence(
current_run=reminder.next_run_at,
days_interval=reminder.days_interval,
)
await update_reminder(session, reminder.id, next_run_at=temp_next_run)
# Commit all updates
await session.commit()
# Small delay to avoid rate limits
await asyncio.sleep(0.5)

View File

@ -176,11 +176,8 @@ async def get_due_reminders(session: AsyncSession, current_time: datetime) -> Li
Returns:
List of due Reminder instances
"""
from sqlalchemy.orm import selectinload
result = await session.execute(
select(Reminder)
.options(selectinload(Reminder.user)) # Eager load user relationship
.where(Reminder.is_active == True)
.where(Reminder.next_run_at <= current_time)
.order_by(Reminder.next_run_at)

View File

@ -128,36 +128,6 @@ async def show_reminder_details(
await callback.answer()
@router.callback_query(ReminderActionCallback.filter(F.action == "back_to_list"))
async def back_to_reminders_list(
callback: CallbackQuery,
session: AsyncSession,
) -> None:
"""
Return to reminders list from details view.
Args:
callback: Callback query
session: Database session
"""
user = await UserService.ensure_user_exists(session, callback.from_user)
reminders = await reminders_service.get_user_all_reminders(session, user.id)
if not reminders:
await callback.message.edit_text(
"У тебя пока нет напоминаний.\n\n"
"Нажми «➕ Новое напоминание», чтобы создать первое!"
)
await callback.answer()
return
await callback.message.edit_text(
f"📋 Твои напоминания ({len(reminders)}):",
reply_markup=get_reminders_list_keyboard(reminders, page=0),
)
await callback.answer()
# ==================== Edit Reminder Flow ====================

View File

@ -204,12 +204,6 @@ def get_reminder_details_keyboard(reminder_id: int, is_active: bool) -> InlineKe
callback_data=ReminderActionCallback(action="delete", reminder_id=reminder_id).pack()
),
],
[
InlineKeyboardButton(
text="⬅️ Назад к списку",
callback_data=ReminderActionCallback(action="back_to_list", reminder_id=0).pack()
),
],
]
)
return keyboard

View File

@ -38,23 +38,14 @@ async def on_shutdown() -> None:
logger.info("Shutting down reminder bot...")
# Stop scheduler
try:
stop_scheduler()
except Exception as e:
logger.error(f"Error stopping scheduler: {e}")
stop_scheduler()
# Close database
try:
await close_db()
except Exception as e:
logger.error(f"Error closing database: {e}")
await close_db()
# Close bot session
if bot and hasattr(bot, 'session'):
try:
await bot.session.close()
except Exception as e:
logger.error(f"Error closing bot session: {e}")
if bot:
await bot.session.close()
logger.info("Bot shutdown completed")

View File

@ -24,27 +24,26 @@ class TimeService:
def get_now(self) -> datetime:
"""
Get current datetime in configured timezone (as naive).
Get current datetime in configured timezone.
Returns:
Current datetime in local timezone (Moscow), naive
Current datetime
"""
# Get aware datetime in configured timezone, then strip timezone
aware_now = datetime.now(self.timezone)
return aware_now.replace(tzinfo=None)
return datetime.now(self.timezone)
def combine_date_time(self, date: datetime, time_of_day: time) -> datetime:
"""
Combine date and time as naive datetime.
Combine date and time in configured timezone.
Args:
date: Date component
time_of_day: Time component
Returns:
Combined naive datetime
Combined datetime
"""
return datetime.combine(date.date(), time_of_day)
naive_dt = datetime.combine(date.date(), time_of_day)
return naive_dt.replace(tzinfo=self.timezone)
def calculate_next_run_time(
self,
@ -110,12 +109,13 @@ class TimeService:
Format next run datetime for display.
Args:
next_run: Next run datetime (naive UTC)
next_run: Next run datetime
Returns:
Formatted string
"""
now = self.get_now()
delta = next_run - now
# If it's today
if next_run.date() == now.date():

View File

@ -45,11 +45,11 @@ def validate_days_interval(days_str: str) -> Optional[int]:
days_str: Days interval string to validate
Returns:
Integer days if valid (>0 and <=365), None otherwise
Integer days if valid (>0), None otherwise
"""
try:
days = int(days_str.strip())
if 0 < days <= 365: # Max 1 year
if days > 0:
return days
except (ValueError, AttributeError):
pass