""" Mail API — account management and sync endpoints. """ import logging from typing import Optional, List from fastapi import APIRouter, HTTPException, Query, BackgroundTasks from pydantic import BaseModel from .models import AccountTestResult from .mail_db import ( create_email_account, get_email_accounts, get_email_account, delete_email_account, log_mail_audit, ) from .credentials import get_credentials_service from .aggregator import get_mail_aggregator logger = logging.getLogger(__name__) router = APIRouter(prefix="/api/v1/mail", tags=["Mail"]) class AccountCreateRequest(BaseModel): """Request to create an email account.""" email: str display_name: str account_type: str = "personal" imap_host: str imap_port: int = 993 imap_ssl: bool = True smtp_host: str smtp_port: int = 465 smtp_ssl: bool = True password: str @router.post("/accounts", response_model=dict) async def create_account( request: AccountCreateRequest, user_id: str = Query(..., description="User ID"), tenant_id: str = Query(..., description="Tenant ID"), ): """Create a new email account.""" credentials_service = get_credentials_service() # Store credentials securely vault_path = await credentials_service.store_credentials( account_id=f"{user_id}_{request.email}", email=request.email, password=request.password, imap_host=request.imap_host, imap_port=request.imap_port, smtp_host=request.smtp_host, smtp_port=request.smtp_port, ) # Create account in database account_id = await create_email_account( user_id=user_id, tenant_id=tenant_id, email=request.email, display_name=request.display_name, account_type=request.account_type, imap_host=request.imap_host, imap_port=request.imap_port, imap_ssl=request.imap_ssl, smtp_host=request.smtp_host, smtp_port=request.smtp_port, smtp_ssl=request.smtp_ssl, vault_path=vault_path, ) if not account_id: raise HTTPException(status_code=500, detail="Failed to create account") # Log audit await log_mail_audit( user_id=user_id, action="account_created", entity_type="account", entity_id=account_id, details={"email": request.email}, tenant_id=tenant_id, ) return {"id": account_id, "status": "created"} @router.get("/accounts", response_model=List[dict]) async def list_accounts( user_id: str = Query(..., description="User ID"), tenant_id: Optional[str] = Query(None, description="Tenant ID"), ): """List all email accounts for a user.""" accounts = await get_email_accounts(user_id, tenant_id) # Remove sensitive fields for account in accounts: account.pop("vault_path", None) return accounts @router.get("/accounts/{account_id}", response_model=dict) async def get_account( account_id: str, user_id: str = Query(..., description="User ID"), ): """Get a single email account.""" account = await get_email_account(account_id, user_id) if not account: raise HTTPException(status_code=404, detail="Account not found") account.pop("vault_path", None) return account @router.delete("/accounts/{account_id}") async def remove_account( account_id: str, user_id: str = Query(..., description="User ID"), ): """Delete an email account.""" account = await get_email_account(account_id, user_id) if not account: raise HTTPException(status_code=404, detail="Account not found") # Delete credentials credentials_service = get_credentials_service() vault_path = account.get("vault_path", "") if vault_path: await credentials_service.delete_credentials(account_id, vault_path) # Delete from database (cascades to emails) success = await delete_email_account(account_id, user_id) if not success: raise HTTPException(status_code=500, detail="Failed to delete account") await log_mail_audit( user_id=user_id, action="account_deleted", entity_type="account", entity_id=account_id, ) return {"status": "deleted"} @router.post("/accounts/{account_id}/test", response_model=AccountTestResult) async def test_account_connection( account_id: str, user_id: str = Query(..., description="User ID"), ): """Test connection for an email account.""" account = await get_email_account(account_id, user_id) if not account: raise HTTPException(status_code=404, detail="Account not found") # Get credentials credentials_service = get_credentials_service() vault_path = account.get("vault_path", "") creds = await credentials_service.get_credentials(account_id, vault_path) if not creds: return AccountTestResult( success=False, error_message="Credentials not found" ) # Test connection aggregator = get_mail_aggregator() result = await aggregator.test_account_connection( imap_host=account["imap_host"], imap_port=account["imap_port"], imap_ssl=account["imap_ssl"], smtp_host=account["smtp_host"], smtp_port=account["smtp_port"], smtp_ssl=account["smtp_ssl"], email_address=creds.email, password=creds.password, ) return result class ConnectionTestRequest(BaseModel): """Request to test connection before saving account.""" email: str imap_host: str imap_port: int = 993 imap_ssl: bool = True smtp_host: str smtp_port: int = 465 smtp_ssl: bool = True password: str @router.post("/accounts/test-connection", response_model=AccountTestResult) async def test_connection_before_save(request: ConnectionTestRequest): """ Test IMAP/SMTP connection before saving an account. This allows the wizard to verify credentials are correct before creating the account in the database. """ aggregator = get_mail_aggregator() result = await aggregator.test_account_connection( imap_host=request.imap_host, imap_port=request.imap_port, imap_ssl=request.imap_ssl, smtp_host=request.smtp_host, smtp_port=request.smtp_port, smtp_ssl=request.smtp_ssl, email_address=request.email, password=request.password, ) return result @router.post("/accounts/{account_id}/sync") async def sync_account( account_id: str, user_id: str = Query(..., description="User ID"), max_emails: int = Query(100, ge=1, le=500), background_tasks: BackgroundTasks = None, ): """Sync emails from an account.""" aggregator = get_mail_aggregator() try: new_count, total_count = await aggregator.sync_account( account_id=account_id, user_id=user_id, max_emails=max_emails, ) return { "status": "synced", "new_emails": new_count, "total_emails": total_count, } except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/sync-all") async def sync_all_accounts( user_id: str = Query(..., description="User ID"), tenant_id: Optional[str] = Query(None), ): """Sync all email accounts for a user.""" aggregator = get_mail_aggregator() results = await aggregator.sync_all_accounts(user_id, tenant_id) return {"status": "synced", "results": results}