from fastapi import APIRouter, Depends, HTTPException from sqlalchemy.orm import Session from app.database import get_db from app.models.db_models import Account, ProcessedMail from app.schemas.schemas import ( AccountCreate, AccountListResponse, AccountResponse, AccountUpdate, ) from pydantic import BaseModel from app.services.encryption import decrypt, encrypt from app.services.imap_client import IMAPClient, async_test_connection router = APIRouter(prefix="/api/accounts", tags=["accounts"]) @router.get("/", response_model=list[AccountListResponse]) def list_accounts(db: Session = Depends(get_db)): accounts = db.query(Account).order_by(Account.name).all() result = [] for acc in accounts: data = AccountListResponse.model_validate(acc) data.filter_rule_count = len(acc.filter_rules) result.append(data) return result @router.get("/{account_id}", response_model=AccountResponse) def get_account(account_id: int, db: Session = Depends(get_db)): account = db.get(Account, account_id) if not account: raise HTTPException(404, "Konto nicht gefunden") return account @router.post("/", response_model=AccountResponse, status_code=201) def create_account(data: AccountCreate, db: Session = Depends(get_db)): account_data = data.model_dump() account_data["password"] = encrypt(account_data["password"]) if account_data.get("smtp_password"): account_data["smtp_password"] = encrypt(account_data["smtp_password"]) account = Account(**account_data) db.add(account) db.commit() db.refresh(account) from app.services.scheduler import add_account_job add_account_job(account) return account @router.put("/{account_id}", response_model=AccountResponse) def update_account(account_id: int, data: AccountUpdate, db: Session = Depends(get_db)): account = db.get(Account, account_id) if not account: raise HTTPException(404, "Konto nicht gefunden") for key, value in data.model_dump(exclude_unset=True).items(): if key == "password" and value: value = encrypt(value) elif key == "smtp_password" and value: value = encrypt(value) setattr(account, key, value) db.commit() db.refresh(account) from app.services.scheduler import add_account_job add_account_job(account) return account @router.delete("/{account_id}", status_code=204) def delete_account(account_id: int, db: Session = Depends(get_db)): account = db.get(Account, account_id) if not account: raise HTTPException(404, "Konto nicht gefunden") from app.services.scheduler import remove_account_job remove_account_job(account_id) db.delete(account) db.commit() @router.post("/{account_id}/test") async def test_account_connection(account_id: int, db: Session = Depends(get_db)): account = db.get(Account, account_id) if not account: raise HTTPException(404, "Konto nicht gefunden") success = await async_test_connection( host=account.imap_host, port=account.imap_port, username=account.username, password=decrypt(account.password), use_ssl=account.use_ssl, ) return {"success": success, "message": "Verbindung erfolgreich" if success else "Verbindung fehlgeschlagen"} class TestConnectionRequest(BaseModel): imap_host: str imap_port: int = 993 use_ssl: bool = True username: str password: str @router.post("/test-connection") async def test_connection_direct(data: TestConnectionRequest): success = await async_test_connection( host=data.imap_host, port=data.imap_port, username=data.username, password=data.password, use_ssl=data.use_ssl, ) return {"success": success, "message": "Verbindung erfolgreich" if success else "Verbindung fehlgeschlagen"} @router.post("/{account_id}/poll-now") async def poll_now(account_id: int, db: Session = Depends(get_db)): account = db.get(Account, account_id) if not account: raise HTTPException(404, "Konto nicht gefunden") from app.services.scheduler import poll_account await poll_account(account_id) return {"message": f"Polling für '{account.name}' durchgeführt"} @router.get("/{account_id}/processed") def get_processed_stats(account_id: int, db: Session = Depends(get_db)): account = db.get(Account, account_id) if not account: raise HTTPException(404, "Konto nicht gefunden") total = db.query(ProcessedMail).filter(ProcessedMail.account_id == account_id).count() return {"account_id": account_id, "processed_count": total} @router.delete("/{account_id}/processed") def reset_processed(account_id: int, folder: str | None = None, db: Session = Depends(get_db)): account = db.get(Account, account_id) if not account: raise HTTPException(404, "Konto nicht gefunden") query = db.query(ProcessedMail).filter(ProcessedMail.account_id == account_id) if folder: query = query.filter(ProcessedMail.folder == folder) count = query.delete() db.commit() scope = f"Ordner '{folder}'" if folder else "alle Ordner" return {"message": f"Verarbeitung zurückgesetzt für {scope} ({count} Einträge)", "deleted": count} def _get_imap_client(account: Account) -> IMAPClient: return IMAPClient( host=account.imap_host, port=account.imap_port, username=account.username, password=decrypt(account.password), use_ssl=account.use_ssl, ) @router.get("/{account_id}/folders") async def list_folders(account_id: int, debug: bool = False, db: Session = Depends(get_db)): account = db.get(Account, account_id) if not account: raise HTTPException(404, "Konto nicht gefunden") import asyncio def _list(): client = _get_imap_client(account) with client: folders = client.list_folders() raw = None if debug: status, data = client.conn.list() raw = [item.decode("utf-8", errors="replace") if isinstance(item, bytes) else str(item) for item in (data or [])] return folders, raw try: folders, raw = await asyncio.to_thread(_list) result = {"folders": folders} if debug and raw is not None: result["raw"] = raw return result except Exception as e: raise HTTPException(500, f"Fehler beim Abrufen der Ordner: {e}") class CreateFolderRequest(BaseModel): folder_name: str @router.post("/{account_id}/folders") async def create_folder(account_id: int, data: CreateFolderRequest, db: Session = Depends(get_db)): account = db.get(Account, account_id) if not account: raise HTTPException(404, "Konto nicht gefunden") import asyncio def _create(): client = _get_imap_client(account) with client: return client.create_folder(data.folder_name) try: success = await asyncio.to_thread(_create) if success: return {"success": True, "message": f"Ordner '{data.folder_name}' erstellt"} raise HTTPException(400, f"Ordner '{data.folder_name}' konnte nicht erstellt werden") except HTTPException: raise except Exception as e: raise HTTPException(500, f"Fehler: {e}")