"""FastAPI: collect feature requests from portal users. Stores each request to a timestamped folder on disk so the team can review later. No LLM, no external calls — pure form handler. """ import datetime import json import logging import os import re import uuid from pathlib import Path from fastapi import FastAPI, File, Form, HTTPException, UploadFile from fastapi.middleware.cors import CORSMiddleware from fastapi.responses import FileResponse from fastapi.staticfiles import StaticFiles logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) app = FastAPI(title="Feature Request") app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_methods=["*"], allow_headers=["*"]) # Volume-mounted; survives container rebuilds. STORAGE = Path(os.getenv("STORAGE_DIR", "/data/requests")) STORAGE.mkdir(parents=True, exist_ok=True) MAX_FILE_BYTES = 25 * 1024 * 1024 # 25 MB cap def _slug(text: str) -> str: text = re.sub(r"[^\w\s-]", "", text, flags=re.UNICODE).strip().lower() text = re.sub(r"[\s_-]+", "-", text) return text[:40] or "request" @app.get("/") async def root(): return FileResponse("static/index.html") @app.post("/api/submit") async def submit( title: str = Form(..., min_length=3, max_length=120), description: str = Form(..., min_length=10, max_length=8000), name: str = Form("", max_length=120), email: str = Form("", max_length=200), file: UploadFile | None = File(None), ): title = title.strip() description = description.strip() if not title or not description: raise HTTPException(400, "Vyplňte název a popis") now = datetime.datetime.now() folder_name = f"{now.strftime('%Y%m%d_%H%M%S')}_{_slug(title)}_{uuid.uuid4().hex[:8]}" folder = STORAGE / folder_name folder.mkdir(parents=True, exist_ok=True) record = { "submitted_at": now.isoformat(timespec="seconds"), "title": title, "description": description, "name": name.strip(), "email": email.strip(), "attachment": None, } if file is not None and file.filename: raw = await file.read() if len(raw) > MAX_FILE_BYTES: raise HTTPException(400, f"Soubor je příliš velký (max {MAX_FILE_BYTES // (1024*1024)} MB)") safe_name = re.sub(r"[^\w\.\-]", "_", file.filename)[:200] attachment_path = folder / safe_name attachment_path.write_bytes(raw) record["attachment"] = safe_name record["attachment_bytes"] = len(raw) record["attachment_content_type"] = file.content_type or "" (folder / "request.json").write_text( json.dumps(record, ensure_ascii=False, indent=2), encoding="utf-8" ) logger.info("Stored feature request → %s (%s)", folder_name, title) return {"ok": True, "id": folder_name} @app.get("/health") async def health(): return {"status": "ok"} app.mount("/static", StaticFiles(directory="static"), name="static")