Avinash
baseline: initial monorepo snapshot
2d5e892
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"