from __future__ import annotations import json import os from pathlib import Path from typing import Any, Dict from dotenv import load_dotenv from fastapi import FastAPI, HTTPException from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse, PlainTextResponse from backend.worker.gmail_client import GmailClient app = FastAPI(title="PDF Trainer API", version="1.0") # Allow Vite dev server app.add_middleware( CORSMiddleware, allow_origins=[ "http://localhost:5173", "http://127.0.0.1:5173", ], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) REPO_ROOT = Path(__file__).resolve().parents[1] BACKEND_DIR = REPO_ROOT / "backend" UPLOADS_DIR = BACKEND_DIR / "worker" / "uploads" # Load backend/.env explicitly ONCE for this process load_dotenv(BACKEND_DIR / ".env", override=True) CREDENTIALS_JSON = Path(os.environ.get("GMAIL_CREDENTIALS_JSON", str(BACKEND_DIR / "credentials.json"))) TOKEN_JSON = Path(os.environ.get("GMAIL_TOKEN_JSON", str(BACKEND_DIR / "token.json"))) def _gmail() -> GmailClient: return GmailClient(CREDENTIALS_JSON, TOKEN_JSON) def _get_env_required(key: str) -> str: v = (os.environ.get(key) or "").strip() if not v: raise HTTPException(status_code=500, detail=f"Server missing {key} env var") return v @app.get("/health") def health(): return {"ok": True} @app.get("/api/pdf/{pdf_id}") def get_pdf(pdf_id: str): path = UPLOADS_DIR / f"{pdf_id}.pdf" if not path.exists(): raise HTTPException(status_code=404, detail="PDF not found") name_path = UPLOADS_DIR / f"{pdf_id}.name.txt" pdf_name = name_path.read_text(encoding="utf-8").strip() if name_path.exists() else f"{pdf_id}.pdf" resp = FileResponse(path, media_type="application/pdf", filename=pdf_name) resp.headers["X-PDF-Name"] = pdf_name return resp @app.post("/api/send-config") async def send_config(payload: Dict[str, Any]): """ PIPELINE SUBMISSION EMAIL (after rep saves config) REQUIRED payload: - pdf_id: str - template_id: str - config: dict Sends to PIPELINE inbox: - PDF_PIPELINE_PIPELINE_NOTIFY_TO Requirements: - Subject includes template_id - Body includes pdf_id - Attachments: JSON + PDF """ pdf_id = (payload.get("pdf_id") or "").strip() template_id = (payload.get("template_id") or "").strip() config = payload.get("config") if not pdf_id: raise HTTPException(status_code=400, detail="Missing pdf_id") if not template_id: raise HTTPException(status_code=400, detail="Missing template_id") if not isinstance(config, dict): raise HTTPException(status_code=400, detail="Missing config object") pipeline_to = _get_env_required("PDF_PIPELINE_PIPELINE_NOTIFY_TO") notify_from = _get_env_required("PDF_PIPELINE_NOTIFY_FROM") trainer_base_url = (os.environ.get("PDF_TRAINER_BASE_URL") or "http://localhost:5173").strip() pdf_path = UPLOADS_DIR / f"{pdf_id}.pdf" if not pdf_path.exists(): raise HTTPException(status_code=404, detail="PDF not found for pdf_id") name_path = UPLOADS_DIR / f"{pdf_id}.name.txt" pdf_name = name_path.read_text(encoding="utf-8").strip() if name_path.exists() else f"{pdf_id}.pdf" trainer_link = f"{trainer_base_url.rstrip('/')}/?pdf_id={pdf_id}" subject = f"PDF_TRAINER_CONFIG_SUBMITTED | template_id={template_id}" body = ( "Hi,\n\n" "A PDF Trainer configuration was submitted.\n\n" f"template_id: {template_id}\n" f"pdf_id: {pdf_id}\n" f"trainer_link: {trainer_link}\n\n" "Attachments:\n" f"- trainer_config_{pdf_id}_{template_id}.json\n" f"- {pdf_name}\n\n" "Thank you,\n" "Inserio Automation\n" ) cfg_bytes = json.dumps( {"pdf_id": pdf_id, "template_id": template_id, "config": config}, indent=2, ).encode("utf-8") attachments = [ (f"trainer_config_{pdf_id}_{template_id}.json", cfg_bytes), (pdf_name, pdf_path.read_bytes()), ] gmail = _gmail() gmail.send_email( to_email=pipeline_to, from_email=notify_from, subject=subject, body_text=body, attachments=attachments, ) return {"ok": True} @app.post("/api/notify-unknown") async def notify_unknown(payload: Dict[str, Any]): """ UNKNOWN TEMPLATE NOTIFICATION (rep email) REQUIRED payload: - pdf_id: str OPTIONAL: - reason: str Sends to REP inbox: - PDF_PIPELINE_NOTIFY_TO Requirements: - Includes trainer link with PDF pre-loaded - Attaches PDF - No JSON """ pdf_id = (payload.get("pdf_id") or "").strip() reason = (payload.get("reason") or "").strip() if not pdf_id: raise HTTPException(status_code=400, detail="Missing pdf_id") rep_to = _get_env_required("PDF_PIPELINE_NOTIFY_TO") notify_from = _get_env_required("PDF_PIPELINE_NOTIFY_FROM") trainer_base_url = (os.environ.get("PDF_TRAINER_BASE_URL") or "http://localhost:5173").strip() pdf_path = UPLOADS_DIR / f"{pdf_id}.pdf" if not pdf_path.exists(): raise HTTPException(status_code=404, detail="PDF not found for pdf_id") name_path = UPLOADS_DIR / f"{pdf_id}.name.txt" pdf_name = name_path.read_text(encoding="utf-8").strip() if name_path.exists() else f"{pdf_id}.pdf" trainer_link = f"{trainer_base_url.rstrip('/')}/?pdf_id={pdf_id}" subject = "Action required: Unknown PDF format (template not found)" body = ( "Hi,\n\n" "We received a PDF that does not match any existing templates in the system.\n\n" + (f"Reason: {reason}\n\n" if reason else "") + "Please open the PDF Trainer using the link below and create or update the template configuration:\n" f"{trainer_link}\n\n" "The original PDF is attached for reference.\n\n" "Thank you,\n" "Inserio Automation\n" ) attachments = [(pdf_name, pdf_path.read_bytes())] gmail = _gmail() gmail.send_email( to_email=rep_to, from_email=notify_from, subject=subject, body_text=body, attachments=attachments, ) return {"ok": True} @app.get("/", response_class=PlainTextResponse) def root(): return "PDF Trainer API. Use /health"