114 lines
3.4 KiB
Python
114 lines
3.4 KiB
Python
|
|
"""
|
||
|
|
ReminderScheduler
|
||
|
|
|
||
|
|
Schedules and manages user reminders.
|
||
|
|
"""
|
||
|
|
|
||
|
|
from datetime import UTC, datetime, timedelta
|
||
|
|
from typing import TYPE_CHECKING
|
||
|
|
|
||
|
|
from sqlalchemy import select, and_
|
||
|
|
|
||
|
|
from app.models.reminder import Reminder
|
||
|
|
|
||
|
|
if TYPE_CHECKING:
|
||
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
|
|
||
|
|
|
||
|
|
class ReminderScheduler:
|
||
|
|
"""Schedule and manage user reminders."""
|
||
|
|
|
||
|
|
async def create_reminder(
|
||
|
|
self,
|
||
|
|
db: "AsyncSession",
|
||
|
|
user_id: str,
|
||
|
|
content: str,
|
||
|
|
trigger_at: datetime,
|
||
|
|
trigger_type: str = "time",
|
||
|
|
context_memory_id: str | None = None,
|
||
|
|
) -> Reminder:
|
||
|
|
"""Create a new reminder."""
|
||
|
|
reminder = Reminder(
|
||
|
|
user_id=user_id,
|
||
|
|
content=content,
|
||
|
|
trigger_type=trigger_type,
|
||
|
|
trigger_at=trigger_at,
|
||
|
|
context_memory_id=context_memory_id,
|
||
|
|
status="pending",
|
||
|
|
)
|
||
|
|
db.add(reminder)
|
||
|
|
await db.commit()
|
||
|
|
await db.refresh(reminder)
|
||
|
|
return reminder
|
||
|
|
|
||
|
|
async def get_due_reminders(self, db: "AsyncSession", user_id: str) -> list[Reminder]:
|
||
|
|
"""Get reminders that are due (status=pending, trigger_at <= now, not snoozed)."""
|
||
|
|
now = datetime.now(UTC)
|
||
|
|
result = await db.execute(
|
||
|
|
select(Reminder)
|
||
|
|
.where(
|
||
|
|
Reminder.user_id == user_id,
|
||
|
|
Reminder.status == "pending",
|
||
|
|
Reminder.trigger_at <= now,
|
||
|
|
((Reminder.snoozed_until.is_(None)) | (Reminder.snoozed_until <= now)),
|
||
|
|
)
|
||
|
|
.order_by(Reminder.trigger_at.asc())
|
||
|
|
)
|
||
|
|
return list(result.scalars().all())
|
||
|
|
|
||
|
|
async def snooze(
|
||
|
|
self,
|
||
|
|
db: "AsyncSession",
|
||
|
|
reminder_id: int,
|
||
|
|
minutes: int,
|
||
|
|
) -> Reminder | None:
|
||
|
|
"""Snooze reminder for N minutes."""
|
||
|
|
result = await db.execute(select(Reminder).where(Reminder.id == reminder_id))
|
||
|
|
reminder = result.scalar_one_or_none()
|
||
|
|
if not reminder:
|
||
|
|
return None
|
||
|
|
|
||
|
|
reminder.status = "snoozed"
|
||
|
|
reminder.snoozed_until = datetime.now(UTC) + timedelta(minutes=minutes)
|
||
|
|
await db.commit()
|
||
|
|
await db.refresh(reminder)
|
||
|
|
return reminder
|
||
|
|
|
||
|
|
async def dismiss(self, db: "AsyncSession", reminder_id: int) -> bool:
|
||
|
|
"""Mark reminder as dismissed."""
|
||
|
|
result = await db.execute(select(Reminder).where(Reminder.id == reminder_id))
|
||
|
|
reminder = result.scalar_one_or_none()
|
||
|
|
if not reminder:
|
||
|
|
return False
|
||
|
|
|
||
|
|
reminder.status = "dismissed"
|
||
|
|
await db.commit()
|
||
|
|
return True
|
||
|
|
|
||
|
|
async def mark_sent(self, db: "AsyncSession", reminder_id: int) -> bool:
|
||
|
|
"""Mark reminder as sent."""
|
||
|
|
result = await db.execute(select(Reminder).where(Reminder.id == reminder_id))
|
||
|
|
reminder = result.scalar_one_or_none()
|
||
|
|
if not reminder:
|
||
|
|
return False
|
||
|
|
|
||
|
|
reminder.status = "sent"
|
||
|
|
await db.commit()
|
||
|
|
return True
|
||
|
|
|
||
|
|
async def get_pending_reminders(
|
||
|
|
self,
|
||
|
|
db: "AsyncSession",
|
||
|
|
user_id: str,
|
||
|
|
) -> list[Reminder]:
|
||
|
|
"""Get all pending reminders for a user."""
|
||
|
|
result = await db.execute(
|
||
|
|
select(Reminder)
|
||
|
|
.where(
|
||
|
|
Reminder.user_id == user_id,
|
||
|
|
Reminder.status.in_(["pending", "snoozed"]),
|
||
|
|
)
|
||
|
|
.order_by(Reminder.trigger_at.asc())
|
||
|
|
)
|
||
|
|
return list(result.scalars().all())
|