"""Foodlinkk Email Agent — IMAP sync + CEO approval workflow.""" from __future__ import annotations import asyncio from contextlib import asynccontextmanager from typing import Any from fastapi import FastAPI, HTTPException from pydantic import BaseModel, Field from app.config import settings from app.db import close_pool, fetch_one, init_pool from app import imap_sync class DraftIn(BaseModel): title: str = Field(..., min_length=3, max_length=255) to: str subject: str body: str async def _sync_loop() -> None: while True: try: imap_sync.sync_all_accounts() except Exception: pass await asyncio.sleep(settings.SYNC_INTERVAL_SEC) @asynccontextmanager async def lifespan(app: FastAPI): init_pool() task = asyncio.create_task(_sync_loop()) yield task.cancel() close_pool() app = FastAPI(title="Foodlinkk Email Agent", version="1.0.0", lifespan=lifespan) @app.get("/health") def health() -> dict[str, Any]: db_ok = bool(fetch_one("SELECT 1 AS ok")) accounts = fetch_one( "SELECT COUNT(*) AS n FROM email_accounts WHERE is_active = TRUE AND sync_enabled = TRUE" ) return { "status": "ok" if db_ok else "degraded", "database": "connected" if db_ok else "error", "imap_accounts": int((accounts or {}).get("n") or 0), } @app.post("/sync") def sync_now() -> dict[str, Any]: return imap_sync.sync_all_accounts() @app.get("/drafts/pending") def pending_drafts() -> dict[str, Any]: items = imap_sync.list_pending_drafts() return {"items": items, "count": len(items)} @app.post("/drafts") def create_draft(payload: DraftIn) -> dict[str, Any]: row = imap_sync.create_draft(payload.title, payload.body, payload.to, payload.subject) return {"draft": row} @app.post("/drafts/{rec_id}/approve") def approve_draft(rec_id: int) -> dict[str, Any]: try: return imap_sync.approve_draft(rec_id) except ValueError as exc: raise HTTPException(400, str(exc)) from exc except Exception as exc: # noqa: BLE001 raise HTTPException(502, str(exc)) from exc