import json import time import re from fastapi import APIRouter from pydantic import BaseModel, ConfigDict, Field, field_validator from src.config import get_settings router = APIRouter() class FeedbackRequest(BaseModel): model_config = ConfigDict(extra="forbid") request_id: str = Field(pattern=r"^[A-Za-z0-9_-]{8,80}$") feedback: str comment: str = Field(default="", max_length=500) # The existing LibGuides client sends this field to its Worker. Accept it # if forwarded, but do not store it in the local feedback fallback. session_id: str | None = Field(default=None, pattern=r"^[A-Za-z0-9_-]{8,80}$") @field_validator("feedback") @classmethod def valid_feedback(cls, v: str) -> str: if v not in {"up", "down"}: raise ValueError("feedback must be up or down") return v @router.post("") async def save_feedback(req: FeedbackRequest): settings = get_settings() comment = re.sub(r"[\w.+-]+@[\w-]+\.[\w.-]+", "[email]", req.comment) comment = re.sub(r"\b\d{6,}\b", "[number]", comment) payload = { "timestamp": time.time(), "request_id": req.request_id, "feedback": req.feedback, "comment": comment[:500], } settings.feedback_path.parent.mkdir(parents=True, exist_ok=True) with settings.feedback_path.open("a", encoding="utf-8") as handle: handle.write(json.dumps(payload, ensure_ascii=False) + "\n") try: from app import metrics_service metrics_service.incr("feedback_total") metrics_service.incr_bucket("feedback_breakdown", req.feedback) except Exception: pass return {"status": "ok"}