package handler import ( "encoding/json" "net/http" "github.com/prabhupadasays/spsays-bff/internal/middleware" "github.com/prabhupadasays/spsays-bff/internal/service" "github.com/prabhupadasays/spsays-bff/pkg/response" "github.com/prabhupadasays/spsays-bff/pkg/validator" ) // FeedbackHandler exposes POST /api/v1/feedback. The router wraps it // with OptionalAuth so a session cookie populates user_id when one is // present, and anonymous callers are still accepted (they have to // supply contact_email instead). type FeedbackHandler struct{ svc *service.FeedbackService } func NewFeedbackHandler(svc *service.FeedbackService) *FeedbackHandler { return &FeedbackHandler{svc: svc} } type submitFeedbackReq struct { Category string `json:"category"` Message string `json:"message"` ContactEmail *string `json:"contact_email,omitempty"` } func (h *FeedbackHandler) Submit(w http.ResponseWriter, r *http.Request) { var req submitFeedbackReq if err := json.NewDecoder(r.Body).Decode(&req); err != nil { response.Error(w, http.StatusBadRequest, "invalid JSON body") return } v := validator.New() validator.Required(v, "category", req.Category) validator.Required(v, "message", req.Message) validator.MaxLength(v, "message", req.Message, 5000) if !v.Valid() { response.Error(w, http.StatusBadRequest, v.Error()) return } // OptionalAuth seeds user_id when a valid session cookie was // present; "" means anonymous. uid := middleware.UserID(r.Context()) var userIDPtr *string if uid != "" { userIDPtr = &uid } // Soft-validate the contact email for the anonymous path. Authed // callers' contact_email is ignored downstream, so skip the check. if userIDPtr == nil && req.ContactEmail != nil && *req.ContactEmail != "" { ev := validator.New() validator.Email(ev, "contact_email", *req.ContactEmail) if !ev.Valid() { response.Error(w, http.StatusBadRequest, ev.Error()) return } } f, err := h.svc.Submit(r.Context(), userIDPtr, req.Category, req.Message, req.ContactEmail) if err != nil { response.Error(w, http.StatusBadRequest, err.Error()) return } response.JSON(w, http.StatusCreated, f) } // Mine returns the authenticated caller's own feedback submissions, // newest first — each with its status (acknowledged / resolved) and any // admin reply. Registered under RequireAuth, so UserID is always set; // the guard is belt-and-suspenders. func (h *FeedbackHandler) Mine(w http.ResponseWriter, r *http.Request) { uid := middleware.UserID(r.Context()) if uid == "" { response.Error(w, http.StatusUnauthorized, "authentication required") return } items, err := h.svc.ListForUser(r.Context(), uid, 50) if err != nil { response.Error(w, http.StatusInternalServerError, "could not load your feedback") return } response.JSON(w, http.StatusOK, items) }