- FastAPI backend with JWT auth, roles (admin/moderator/user) - PDF upload (up to 500MB) with streaming, PyMuPDF text extraction - ChromaDB vectorization per page with metadata - LiteLLM AI question extraction from PDF (not generation) - Image extraction from PDF pages, graceful fallback - Quiz modes: timed (countdown timer) + learning (answers shown inline) - Page-by-page question navigation with dot navigator - TTS endpoint using LiteLLM (Google Vertex / OpenAI voices) - Admin dashboard: AI model management per task, user role management - Moderator role: upload PDFs, create sections, generate quizzes - Spaced repetition reminders via SMTP email (SM-2 intervals) - APScheduler daily reminder jobs - Celery + Redis for background PDF processing - React frontend with all pages - Docker Compose deployment (nginx + backend + celery + redis) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
64 lines
2.1 KiB
Python
64 lines
2.1 KiB
Python
import logging
|
|
|
|
from fastapi_mail import FastMail, MessageSchema, MessageType, ConnectionConfig
|
|
|
|
from app.config import settings
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def get_mail_config() -> ConnectionConfig:
|
|
return ConnectionConfig(
|
|
MAIL_USERNAME=settings.MAIL_USERNAME,
|
|
MAIL_PASSWORD=settings.MAIL_PASSWORD,
|
|
MAIL_FROM=settings.MAIL_FROM,
|
|
MAIL_PORT=settings.MAIL_PORT,
|
|
MAIL_SERVER=settings.MAIL_SERVER,
|
|
MAIL_STARTTLS=settings.MAIL_STARTTLS,
|
|
MAIL_SSL_TLS=settings.MAIL_SSL_TLS,
|
|
USE_CREDENTIALS=bool(settings.MAIL_USERNAME and settings.MAIL_PASSWORD),
|
|
)
|
|
|
|
|
|
async def send_reminder_email(
|
|
email: str,
|
|
user_name: str,
|
|
quiz_title: str,
|
|
score: float,
|
|
next_date: str,
|
|
):
|
|
"""Send a spaced-repetition reminder email."""
|
|
html = f"""
|
|
<html>
|
|
<body style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
|
|
<h2 style="color: #2563eb;">Quiz Reminder</h2>
|
|
<p>Hi {user_name},</p>
|
|
<p>It's time to review <strong>{quiz_title}</strong>!</p>
|
|
<p>Your last score was <strong>{score:.0f}%</strong>.
|
|
{'Great progress! Keep it up.' if score >= 70 else 'Practice makes perfect — give it another try!'}</p>
|
|
<div style="background: #f3f4f6; padding: 16px; border-radius: 8px; margin: 16px 0;">
|
|
<p style="margin: 0;"><strong>Tip:</strong> Spaced repetition helps you remember more over time.
|
|
Each review strengthens your memory!</p>
|
|
</div>
|
|
<p>Log in to take the quiz again.</p>
|
|
<p style="color: #6b7280; font-size: 12px;">
|
|
You're receiving this because you have active quiz reminders.
|
|
</p>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
message = MessageSchema(
|
|
subject=f"Quiz Reminder: {quiz_title}",
|
|
recipients=[email],
|
|
body=html,
|
|
subtype=MessageType.html,
|
|
)
|
|
|
|
try:
|
|
conf = get_mail_config()
|
|
fm = FastMail(conf)
|
|
await fm.send_message(message)
|
|
logger.info(f"Reminder sent to {email} for quiz '{quiz_title}'")
|
|
except Exception as e:
|
|
logger.error(f"Failed to send reminder to {email}: {e}")
|