Files
anti-plagiarism/services/worker-notifier/app/email_sender.py
jze9 7758315632 feat: initial microservices project structure
Services:
- api: FastAPI gateway with JWT auth, async endpoints, WebSocket
- worker-gpu: CUDA sentence-transformers, FAISS IVFFlat, Ollama LLM
- worker-indexer: Winnowing+MinHash plagiarism detection, PDF/DOCX extraction
- worker-notifier: SMTP email notifications
- worker-gost: GOST 7.1-2003 and GOST R 7.0.5-2008 formatting

Infrastructure:
- docker-compose.yml (production) + docker-compose.dev.yml (hot reload)
- Nginx reverse proxy + WebSocket support
- PostgreSQL 16 with Alembic migrations
- Elasticsearch 8 with Russian/English analyzers
- MinIO, RabbitMQ, Redis, Ollama

Frontend:
- React 18 + Vite + TypeScript + TailwindCSS + Zustand + React Query v5
- 9 pages: Home, Search, Cabinet, Task, Check, Bibliography, Pricing, Login, Register

Scripts:
- Parser stubs: OpenAlex, КиберЛенинка, arXiv (Phase 0 - to be filled)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-24 19:42:39 +05:00

166 lines
6.4 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Отправка email уведомлений через SMTP."""
import logging
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from app.config import settings
logger = logging.getLogger(__name__)
TASK_TYPE_SUBJECTS = {
"search": "Источники найдены — Академический помощник",
"plagiarism": "Проверка плагиата завершена — Академический помощник",
"summarize": "Краткое изложение готово — Академический помощник",
"gost": "Библиография отформатирована — Академический помощник",
}
class EmailSender:
"""Отправщик email уведомлений."""
def _send(self, to_email: str, subject: str, html_body: str) -> None:
"""
Отправить email через SMTP SSL.
Args:
to_email: Email получателя
subject: Тема письма
html_body: HTML тело письма
"""
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = settings.SMTP_FROM
msg["To"] = to_email
msg.attach(MIMEText(html_body, "html", "utf-8"))
try:
with smtplib.SMTP_SSL(settings.SMTP_HOST, settings.SMTP_PORT) as smtp:
smtp.login(settings.SMTP_USER, settings.SMTP_PASSWORD)
smtp.send_message(msg)
logger.info(f"Email отправлен: {to_email!r}, тема: {subject!r}")
except smtplib.SMTPException as e:
logger.error(f"SMTP ошибка при отправке письма на {to_email!r}: {e}")
raise
def send_task_done(
self,
user_email: str,
user_name: str,
task_id: str,
task_type: str,
summary: str,
app_url: str,
) -> None:
"""
Отправить уведомление о завершении задачи.
Args:
user_email: Email пользователя
user_name: Имя пользователя
task_id: ID завершённой задачи
task_type: Тип задачи (search, plagiarism, и т.д.)
summary: Краткое описание результата
app_url: Базовый URL приложения
"""
subject = TASK_TYPE_SUBJECTS.get(task_type, "Задача выполнена — Академический помощник")
task_url = f"{app_url}/tasks/{task_id}"
html_body = f"""<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
</head>
<body style="margin:0;padding:0;background-color:#f8fafc;font-family:system-ui,-apple-system,sans-serif;">
<div style="max-width:600px;margin:40px auto;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 4px 6px rgba(0,0,0,0.07);">
<!-- Header -->
<div style="background:#2563eb;padding:32px 40px;">
<h1 style="margin:0;color:#fff;font-size:22px;font-weight:600;">
Академический помощник
</h1>
<p style="margin:4px 0 0;color:#bfdbfe;font-size:14px;">academic.jze9.ru</p>
</div>
<!-- Body -->
<div style="padding:40px;">
<p style="margin:0 0 16px;color:#1e293b;font-size:16px;">
Здравствуйте, <strong>{user_name}</strong>!
</p>
<p style="margin:0 0 24px;color:#475569;font-size:15px;line-height:1.6;">
{summary}
</p>
<a href="{task_url}"
style="display:inline-block;background:#2563eb;color:#fff;padding:14px 28px;
border-radius:8px;text-decoration:none;font-size:15px;font-weight:500;">
Открыть результат →
</a>
</div>
<!-- Footer -->
<div style="padding:24px 40px;border-top:1px solid #e2e8f0;background:#f8fafc;">
<p style="margin:0;color:#94a3b8;font-size:12px;">
Академический помощник · <a href="{app_url}" style="color:#94a3b8;">academic.jze9.ru</a><br>
Это автоматическое письмо, не нужно отвечать на него.
</p>
</div>
</div>
</body>
</html>"""
self._send(user_email, subject, html_body)
def send_verification(self, user_email: str, user_name: str, token: str, app_url: str) -> None:
"""
Отправить письмо для верификации email.
Args:
user_email: Email для верификации
user_name: Имя пользователя
token: Токен верификации
app_url: Базовый URL приложения
"""
verify_url = f"{app_url}/verify-email/{token}"
subject = "Подтвердите email — Академический помощник"
html_body = f"""<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="utf-8">
</head>
<body style="margin:0;padding:0;background:#f8fafc;font-family:system-ui,-apple-system,sans-serif;">
<div style="max-width:600px;margin:40px auto;background:#fff;border-radius:12px;overflow:hidden;box-shadow:0 4px 6px rgba(0,0,0,0.07);">
<div style="background:#2563eb;padding:32px 40px;">
<h1 style="margin:0;color:#fff;font-size:22px;font-weight:600;">Подтвердите ваш email</h1>
</div>
<div style="padding:40px;">
<p style="margin:0 0 16px;color:#1e293b;font-size:16px;">
Здравствуйте, <strong>{user_name}</strong>!
</p>
<p style="margin:0 0 24px;color:#475569;font-size:15px;line-height:1.6;">
Нажмите кнопку ниже, чтобы подтвердить ваш email и активировать аккаунт.
</p>
<a href="{verify_url}"
style="display:inline-block;background:#16a34a;color:#fff;padding:14px 28px;
border-radius:8px;text-decoration:none;font-size:15px;font-weight:500;">
Подтвердить email →
</a>
<p style="margin:24px 0 0;color:#94a3b8;font-size:12px;">
Ссылка действительна 48 часов. Если вы не регистрировались, проигнорируйте это письмо.
</p>
</div>
</div>
</body>
</html>"""
self._send(user_email, subject, html_body)