Files
nas-tools/dashboard/backend/services/email_service.py
T
Gan, Jimmy b981c06d59
Deploy Dashboard (Dev) / deploy-dev (push) Successful in 2m26s
Run Tests / Backend Tests (push) Failing after 2m36s
Run Tests / Frontend Tests (push) Failing after 2m23s
Run Tests / Test Summary (push) Failing after 13s
feat: comprehensive test infrastructure improvements
- Fix unit test imports: add env setup in conftest.py before module imports
- Add 24 new auth router tests (RBAC, preferences, password validation)
- Add 16 new tests for litellm and chat_summary routers
- Apply black formatting and ruff linting across codebase
- Add pre-commit hooks configuration (black, ruff, file checks)
- Increase CI coverage threshold from 40% to 50%

Test Results:
- 206 tests passing (91 unit + 115 integration)
- Coverage: 58.79% on core modules
- auth.py: 57% → 85%, litellm.py: 23% → 87%, chat_summary.py: 41% → 100%
- auth_service: 96.51%, config: 100%, rbac: 93.48%
2026-04-08 00:21:32 +08:00

159 lines
4.7 KiB
Python

"""
Email notification service for OPC
"""
import logging
import os
import smtplib
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
logger = logging.getLogger(__name__)
# Email configuration from environment
SMTP_HOST = os.getenv("SMTP_HOST", "smtp.gmail.com")
SMTP_PORT = int(os.getenv("SMTP_PORT", "587"))
SMTP_USER = os.getenv("SMTP_USER", "")
SMTP_PASSWORD = os.getenv("SMTP_PASSWORD", "")
SMTP_FROM = os.getenv("SMTP_FROM", SMTP_USER)
SMTP_TO = os.getenv("SMTP_TO", "")
async def send_email(subject: str, body: str, html_body: str = None):
"""Send email notification"""
if not SMTP_USER or not SMTP_PASSWORD or not SMTP_TO:
logger.warning("Email not configured, skipping notification")
return
try:
# Create message
msg = MIMEMultipart("alternative")
msg["Subject"] = subject
msg["From"] = SMTP_FROM
msg["To"] = SMTP_TO
# Add plain text part
msg.attach(MIMEText(body, "plain"))
# Add HTML part if provided
if html_body:
msg.attach(MIMEText(html_body, "html"))
# Send email
with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server:
server.starttls()
server.login(SMTP_USER, SMTP_PASSWORD)
server.send_message(msg)
logger.info(f"Email sent: {subject}")
except Exception as e:
logger.error(f"Failed to send email: {e}")
async def send_task_assigned_email(task: dict, assigned_to: str, assigned_type: str):
"""Send email when task is assigned"""
subject = f"Task Assigned: {task['title']}"
body = f"""You have been assigned a new task:
Title: {task['title']}
Priority: {task['priority']}
Status: {task['status']}
Description:
{task.get('description', 'No description')}
View task: https://nas.jimmygan.com/?page=opc
"""
html_body = f"""
<html>
<body>
<h2>Task Assigned</h2>
<p>You have been assigned a new task:</p>
<table style="border-collapse: collapse; margin: 20px 0;">
<tr><td style="padding: 8px; font-weight: bold;">Title:</td><td style="padding: 8px;">{task['title']}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Priority:</td><td style="padding: 8px;">{task['priority']}</td></tr>
<tr><td style="padding: 8px; font-weight: bold;">Status:</td><td style="padding: 8px;">{task['status']}</td></tr>
</table>
<p><strong>Description:</strong></p>
<p>{task.get('description', 'No description')}</p>
<p><a href="https://nas.jimmygan.com/?page=opc" style="background-color: #4F46E5; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">View Task</a></p>
</body>
</html>
"""
await send_email(subject, body, html_body)
async def send_agent_approval_email(agent_name: str, task: dict, reasoning: str, actions: list):
"""Send email for agent approval request"""
subject = f"Agent Approval Required: {agent_name}"
actions_text = "\n".join(
[f"- {action.get('type')}: {action.get('title', action.get('message', 'N/A'))}" for action in actions]
)
body = f"""Agent approval required:
Agent: {agent_name}
Task: {task['title']}
Reasoning:
{reasoning}
Proposed Actions:
{actions_text}
Please review and approve in the OPC dashboard:
https://nas.jimmygan.com/?page=opc
"""
html_body = f"""
<html>
<body>
<h2>🤖 Agent Approval Required</h2>
<p><strong>Agent:</strong> {agent_name}</p>
<p><strong>Task:</strong> {task['title']}</p>
<h3>Reasoning:</h3>
<p>{reasoning}</p>
<h3>Proposed Actions:</h3>
<ul>
{"".join([f"<li><strong>{action.get('type')}</strong>: {action.get('title', action.get('message', 'N/A'))}</li>" for action in actions])}
</ul>
<p><a href="https://nas.jimmygan.com/?page=opc" style="background-color: #4F46E5; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">Review & Approve</a></p>
</body>
</html>
"""
await send_email(subject, body, html_body)
async def send_agent_completion_email(agent_name: str, task: dict, actions_count: int):
"""Send email when agent completes task"""
subject = f"Agent Task Completed: {agent_name}"
body = f"""Agent has completed a task:
Agent: {agent_name}
Task: {task['title']}
Actions Executed: {actions_count}
View details: https://nas.jimmygan.com/?page=opc
"""
html_body = f"""
<html>
<body>
<h2>✅ Agent Task Completed</h2>
<p><strong>Agent:</strong> {agent_name}</p>
<p><strong>Task:</strong> {task['title']}</p>
<p><strong>Actions Executed:</strong> {actions_count}</p>
<p><a href="https://nas.jimmygan.com/?page=opc" style="background-color: #10B981; color: white; padding: 10px 20px; text-decoration: none; border-radius: 5px; display: inline-block;">View Details</a></p>
</body>
</html>
"""
await send_email(subject, body, html_body)