| """ |
| Background service to check and send task reminders. |
| """ |
| from datetime import datetime, timedelta |
| from typing import List |
| from uuid import UUID |
|
|
| from sqlalchemy.orm import Session |
| from sqlalchemy import and_ |
|
|
| from src.models.todo import Todo, Status |
| from src.models.user import User |
| from src.services.email_service import email_service |
|
|
|
|
| def check_and_send_reminders(session: Session) -> dict: |
| """ |
| Check for tasks due in ~24 hours and send reminders. |
| |
| This function should be called hourly by the scheduler. |
| It finds tasks that are: |
| - Not completed |
| - Have a due_date set |
| - Due within the next 25 hours |
| - Haven't had a reminder sent yet |
| |
| Args: |
| session: Database session |
| |
| Returns: |
| dict with count of reminders sent and errors |
| """ |
| |
| |
| now = datetime.utcnow() |
| reminder_window_start = now |
| reminder_window_end = now + timedelta(hours=25) |
|
|
| print(f"π Checking for tasks due between {reminder_window_start} and {reminder_window_end}") |
|
|
| |
| query = session.query(Todo, User).join( |
| User, Todo.user_id == User.id |
| ).filter( |
| and_( |
| Todo.status != Status.COMPLETED, |
| Todo.due_date.isnot(None), |
| Todo.due_date >= reminder_window_start, |
| Todo.due_date <= reminder_window_end, |
| Todo.reminder_sent == False |
| ) |
| ) |
|
|
| results = query.all() |
| sent_count = 0 |
| error_count = 0 |
| skipped_count = 0 |
|
|
| print(f"π Found {len(results)} tasks eligible for reminders") |
|
|
| for todo, user in results: |
| try: |
| |
| success = email_service.send_reminder( |
| to_email=user.email, |
| task_title=todo.title, |
| task_description=todo.description, |
| due_date=todo.due_date, |
| priority=todo.priority.value if todo.priority else None, |
| tags=todo.tags, |
| task_id=str(todo.id) |
| ) |
|
|
| if success: |
| |
| todo.reminder_sent = True |
| session.commit() |
| sent_count += 1 |
| print(f"β
Reminder sent for task '{todo.title}' to {user.email}") |
| else: |
| error_count += 1 |
| print(f"β Failed to send reminder for task '{todo.title}'") |
|
|
| except Exception as e: |
| print(f"β Error sending reminder for task {todo.id}: {e}") |
| error_count += 1 |
| session.rollback() |
|
|
| summary = { |
| "checked": len(results), |
| "sent": sent_count, |
| "errors": error_count, |
| "skipped": skipped_count |
| } |
|
|
| print(f"π Reminder check complete: {summary}") |
| return summary |
|
|
|
|
| def send_test_reminder(session: Session, user_id: UUID) -> bool: |
| """ |
| Send a test reminder to verify email configuration. |
| |
| Args: |
| session: Database session |
| user_id: User ID to send test email to |
| |
| Returns: |
| bool: True if test email sent successfully |
| """ |
| from src.models.user import User |
|
|
| user = session.query(User).filter(User.id == user_id).first() |
| if not user: |
| print(f"User not found: {user_id}") |
| return False |
|
|
| test_due_date = datetime.utcnow() + timedelta(hours=24) |
|
|
| return email_service.send_reminder( |
| to_email=user.email, |
| task_title="π§ͺ Test Task - Todo App Reminder", |
| task_description="This is a test reminder email to verify your email configuration is working correctly!", |
| due_date=test_due_date, |
| priority="medium", |
| tags=["test"], |
| task_id="test-task-id" |
| ) |
|
|