slidementor-api / main.py
Dhyan19's picture
Deploy SlideMentor backend
23442a0
Raw
History Blame Contribute Delete
4.69 kB
"""
SlideMentor Backend β€” FastAPI + Groq
=====================================
Endpoints:
POST /explain-slide β†’ Explain slide text or image
POST /explain-selection β†’ Explain highlighted text
POST /chat β†’ Contextual follow-up chat
Security:
- Input sanitization and prompt injection defense
- Rate limiting (in-memory, suitable for demo scale)
- CORS locked to extension origin
- No raw user content in system prompts
"""
from fastapi import FastAPI, Request, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from contextlib import asynccontextmanager
from routers.pdf import router as pdf_router
import time
import logging
from dotenv import load_dotenv
load_dotenv()
from routers.explain import router as explain_router
from routers.chat import router as chat_router
from middleware.rate_limiter import RateLimiter
from middleware.sanitizer import sanitize_input
# ─── Logging ────────────────────────────────────────────────────────────────
logging.basicConfig(
level=logging.INFO,
format="%(asctime)s | %(levelname)s | %(name)s | %(message)s",
)
logger = logging.getLogger("slidementor")
# ─── Lifespan ────────────────────────────────────────────────────────────────
@asynccontextmanager
async def lifespan(app: FastAPI):
logger.info("SlideMentor API starting up")
yield
logger.info("SlideMentor API shutting down")
# ─── App ─────────────────────────────────────────────────────────────────────
app = FastAPI(
title="SlideMentor API",
version="1.0.0",
description="AI-powered slide explanation backend using Groq LLaMA 3",
docs_url="/docs",
redoc_url=None,
lifespan=lifespan,
)
# ─── CORS ─────────────────────────────────────────────────────────────────────
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=False,
allow_methods=["*"],
allow_headers=["*"],
)
# ─── Rate Limiter ─────────────────────────────────────────────────────────────
rate_limiter = RateLimiter(max_requests=30, window_seconds=60)
@app.middleware("http")
async def rate_limit_middleware(request: Request, call_next):
if request.method == "POST":
client_ip = request.client.host if request.client else "unknown"
allowed, retry_after = rate_limiter.check(client_ip)
if not allowed:
return JSONResponse(
status_code=429,
content={
"error": "rate_limit_exceeded",
"detail": f"Too many requests. Retry after {retry_after}s.",
"retry_after": retry_after,
},
)
return await call_next(request)
# ─── Routers ─────────────────────────────────────────────────────────────────
app.include_router(explain_router, prefix="", tags=["Explain"])
app.include_router(chat_router, prefix="", tags=["Chat"])
app.include_router(pdf_router, prefix="", tags=["PDF"])
# ─── Health ───────────────────────────────────────────────────────────────────
@app.get("/health")
async def health():
return {"status": "ok", "service": "slidementor", "version": "1.0.0"}
# ─── Global Error Handler ─────────────────────────────────────────────────────
@app.exception_handler(Exception)
async def global_exception_handler(request: Request, exc: Exception):
logger.error(f"Unhandled error on {request.url.path}: {exc}", exc_info=True)
return JSONResponse(
status_code=500,
content={"error": "internal_error", "detail": "An unexpected error occurred."},
)