""" Email Preferences API - User email report scheduling settings Stores and retrieves user preferences for daily/weekly email reports """ from fastapi import APIRouter, HTTPException, Header, Depends from pydantic import BaseModel from typing import Optional import json from pathlib import Path import logging import os import httpx from datetime import datetime from services.email_service import send_insight_email import re from database.db import get_db logger = logging.getLogger(__name__) router = APIRouter() # Storage directory for user preferences # email_prefs.py is at: backend/api/v1/endpoints/email_prefs.py # We need to get to: backend/storage/email_prefs # So we need 4 parent levels: endpoints -> v1 -> api -> backend import os # Use absolute path that matches scheduler PREFS_DIR = Path(__file__).parent.parent.parent.parent / "storage" / "email_prefs" PREFS_DIR.mkdir(parents=True, exist_ok=True) logger.info(f"Email Prefs PREFS_DIR: {PREFS_DIR.absolute()}") class EmailPreferences(BaseModel): """User email report preferences""" daily_report_enabled: bool = False daily_report_hour: int = 8 # 8 AM default daily_report_minute: int = 0 # 0 minutes default weekly_report_enabled: bool = True weekly_report_day: int = 1 # Monday (0=Sun, 1=Mon, ..., 6=Sat) weekly_report_hour: int = 9 # 9 AM default weekly_report_minute: int = 0 # 0 minutes default email_address: Optional[str] = None # Override profile email if needed timezone_offset: float = 5.5 # Default IST (UTC+5:30), can be changed by user def get_prefs_path(user_id: str) -> Path: """Get path to user's preferences file""" return PREFS_DIR / f"{user_id}_email_prefs.json" def load_user_prefs(user_id: str) -> EmailPreferences: """Load user preferences from file""" prefs_path = get_prefs_path(user_id) if prefs_path.exists(): try: with open(prefs_path, 'r') as f: data = json.load(f) return EmailPreferences(**data) except Exception as e: logger.error(f"Error loading prefs for {user_id}: {e}") # Return defaults return EmailPreferences() def save_user_prefs(user_id: str, prefs: EmailPreferences) -> None: """Save user preferences to file""" prefs_path = get_prefs_path(user_id) try: print(f"💾 SAVING email prefs for user '{user_id}' to: {prefs_path.absolute()}") with open(prefs_path, 'w') as f: json.dump(prefs.dict(), f, indent=2) print(f"✅ SAVED successfully! File exists: {prefs_path.exists()}") logger.info(f"Saved email prefs for user {user_id}") except Exception as e: print(f"❌ FAILED to save: {e}") logger.error(f"Error saving prefs for {user_id}: {e}") raise @router.get("/email-prefs") async def get_email_preferences( x_user_id: str = Header(None, alias="X-User-ID") ): """Get user's email report preferences""" user_id = x_user_id or "default_user" prefs = load_user_prefs(user_id) return { "success": True, "preferences": prefs.dict(), "day_options": [ {"value": 0, "label": "Sunday"}, {"value": 1, "label": "Monday"}, {"value": 2, "label": "Tuesday"}, {"value": 3, "label": "Wednesday"}, {"value": 4, "label": "Thursday"}, {"value": 5, "label": "Friday"}, {"value": 6, "label": "Saturday"}, ], "hour_options": [ {"value": h, "label": f"{h:02d}:00"} for h in range(24) ] } @router.put("/email-prefs") async def update_email_preferences( prefs: EmailPreferences, x_user_id: str = Header(None, alias="X-User-ID") ): """Update user's email report preferences""" user_id = x_user_id or "default_user" # Validate hour (0-23) if not (0 <= prefs.daily_report_hour <= 23): raise HTTPException(400, "daily_report_hour must be 0-23") if not (0 <= prefs.weekly_report_hour <= 23): raise HTTPException(400, "weekly_report_hour must be 0-23") # Validate day (0-6) if not (0 <= prefs.weekly_report_day <= 6): raise HTTPException(400, "weekly_report_day must be 0-6 (Sun-Sat)") try: save_user_prefs(user_id, prefs) return { "success": True, "message": "Email preferences updated successfully", "preferences": prefs.dict() } except Exception as e: raise HTTPException(500, f"Failed to save preferences: {str(e)}") class TestEmailRequest(BaseModel): """Request body for test email""" email_address: str @router.post("/email-prefs/test") async def test_email_report( request: TestEmailRequest = None, x_user_id: str = Header(None, alias="X-User-ID") ): """Send a test email report to verify configuration""" from services.email_service import send_insight_email user_id = x_user_id or "default_user" # Get email from request body OR from saved preferences email_to_use = None if request and request.email_address: email_to_use = request.email_address else: prefs = load_user_prefs(user_id) email_to_use = prefs.email_address if not email_to_use: raise HTTPException(400, "No email address provided. Please enter your email address.") try: print(f"📧 Sending test email to: {email_to_use}") await send_insight_email( to_email=email_to_use, title="DataVision - Test Email | Configuration Verified", body="This is a test email from DataVision. Your email configuration is working correctly. You will receive automated data insights based on your preferences.", workspace_id=user_id ) return { "success": True, "message": f"Test email sent to {email_to_use}" } except Exception as e: print(f"❌ Email send failed: {e}") raise HTTPException(500, f"Failed to send test email: {str(e)}") def generate_fallback_email_body(data_context: str) -> str: """Generate a nicely formatted email body when AI is unavailable""" from datetime import datetime return f"""
DataVision Autonomous Agents have compiled the latest intelligence regarding your connected datasets.
,
Here is your daily intelligence briefing...
,
Here is your weekly intelligence briefing...
" }""" user_prompt = f"""Generate a Weekly Intelligence Report in HTML format for the week ending {datetime.now().strftime('%B %d, %Y')}. USER'S DATA SUMMARY: {data_context} Create an executive-level weekly summary with trends, insights, and recommendations. Return ONLY the JSON object with "subject" and "body" (HTML).""" try: async with httpx.AsyncClient(timeout=30.0) as client: response = await client.post( "https://api.groq.com/openai/v1/chat/completions", headers={ "Authorization": f"Bearer {groq_api_key}", "Content-Type": "application/json" }, json={ "model": "llama-3.3-70b-versatile", "messages": [ {"role": "system", "content": system_prompt}, {"role": "user", "content": user_prompt} ], "temperature": 0.7, "max_tokens": 2000 } ) if response.status_code == 200: result = response.json() content = result['choices'][0]['message']['content'].strip() try: email_data = json.loads(content) subject = email_data.get('subject', f"📈 DataVision Executive Briefing - Week of {datetime.now().strftime('%B %d, %Y')}") body = email_data.get('body', content) except Exception: if content.lower().startswith("subject:"): lines = content.split("\n", 1) subject = lines[0].replace("Subject:", "").replace("subject:", "").strip() body = lines[1].strip() if len(lines) > 1 else content else: subject = f"📈 DataVision Executive Briefing - Week of {datetime.now().strftime('%B %d, %Y')}" body = content else: subject = f"📈 DataVision Executive Briefing - {datetime.now().strftime('%B %d, %Y')}" body = generate_weekly_fallback(data_context) except Exception as e: logger.warning(f"AI weekly generation failed: {e}") subject = f"📈 DataVision Weekly Report - {datetime.now().strftime('%B %d, %Y')}" body = generate_weekly_fallback(data_context) else: subject = f"📈 DataVision Weekly Report - {datetime.now().strftime('%B %d, %Y')}" body = generate_weekly_fallback(data_context) # Send the email await send_insight_email( to_email=email_to_use, title=subject, body=body, workspace_id=user_id ) return { "success": True, "message": f"🤖 AI-generated Weekly Report sent to {email_to_use}", "data_context": data_context } except HTTPException: raise except Exception as e: logger.error(f"Failed to send weekly report: {e}") raise HTTPException(500, f"Failed to send weekly report: {str(e)}") def generate_weekly_fallback(data_context: str) -> str: """Fallback weekly email body""" from datetime import datetime return f"""DataVision Autonomous Agents have compiled your weekly intelligence briefing regarding your connected datasets.