Files
Bellhop/app/main.py
Claude 05460237ee Implement Bellhop: Matrix-authenticated *arr request portal
- FastAPI backend with async SQLite session storage
- Matrix homeserver authentication (login/logout/session validation via whoami)
- Server-side proxy for Radarr, Sonarr, and Lidarr search & add APIs
- Fire-and-forget Matrix audit log bot for request tracking
- Rate-limited login endpoint (5 req/min per IP via slowapi)
- Single-page Alpine.js frontend with dark theme, media type tabs, search grid
- Dockerfile for single-container deployment
- Secure session cookies (httponly, samesite=strict, secure)

https://claude.ai/code/session_018iMt5qm4j9bk8wzytLC3E6
2026-02-27 21:24:53 +00:00

49 lines
1.4 KiB
Python

from contextlib import asynccontextmanager
from pathlib import Path
from fastapi import FastAPI, Request
from fastapi.responses import HTMLResponse
from fastapi.staticfiles import StaticFiles
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.errors import RateLimitExceeded
from slowapi.util import get_remote_address
from app.auth import router as auth_router
from app.arr import router as arr_router
from app.database import close_db, get_db
BASE_DIR = Path(__file__).resolve().parent
limiter = Limiter(key_func=get_remote_address)
@asynccontextmanager
async def lifespan(app: FastAPI):
await get_db()
yield
await close_db()
app = FastAPI(title="Bellhop", lifespan=lifespan)
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
# Mount static files
app.mount("/static", StaticFiles(directory=str(BASE_DIR / "static")), name="static")
# Apply rate limit to login specifically
original_login = auth_router.routes
for route in auth_router.routes:
if hasattr(route, "path") and route.path == "/login" and hasattr(route, "endpoint"):
route.endpoint = limiter.limit("5/minute")(route.endpoint)
break
app.include_router(auth_router)
app.include_router(arr_router)
@app.get("/", response_class=HTMLResponse)
async def index():
html_path = BASE_DIR / "templates" / "index.html"
return HTMLResponse(html_path.read_text())