Categories move from a hard-coded list to a feedback_categories table (migration 0062), seeded with the four existing keys, labels and descriptions so production feedback is untouched; any other key already in use gets an archived category. Keys are fixed, so renames relabel past feedback. Archive hides a category from the form; only unused categories can be deleted. Submissions are validated against active categories, and feedback responses carry category_label. New: GET /feedback/categories (public); /admin/feedback/categories CRUD + reorder behind the new feedback:categories permission (granted to Admin). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
115 lines
3.5 KiB
Go
115 lines
3.5 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
|
|
"github.com/go-chi/chi/v5"
|
|
"github.com/prabhupadasays/spsays-bff/internal/repository"
|
|
"github.com/prabhupadasays/spsays-bff/internal/service"
|
|
"github.com/prabhupadasays/spsays-bff/pkg/response"
|
|
)
|
|
|
|
// FeedbackCategoryHandler serves the /feedback form's categories: the
|
|
// public list, and their management in the admin panel.
|
|
type FeedbackCategoryHandler struct {
|
|
svc *service.FeedbackCategoryService
|
|
}
|
|
|
|
func NewFeedbackCategoryHandler(svc *service.FeedbackCategoryService) *FeedbackCategoryHandler {
|
|
return &FeedbackCategoryHandler{svc: svc}
|
|
}
|
|
|
|
func writeCategoryError(w http.ResponseWriter, err error) {
|
|
switch {
|
|
case errors.Is(err, repository.ErrFeedbackCategoryNotFound):
|
|
response.Error(w, http.StatusNotFound, err.Error())
|
|
case errors.Is(err, repository.ErrFeedbackCategoryInUse),
|
|
errors.Is(err, repository.ErrFeedbackCategoryExists),
|
|
errors.Is(err, service.ErrCategoryLastActive):
|
|
response.Error(w, http.StatusConflict, err.Error())
|
|
case errors.Is(err, service.ErrCategoryLabel), errors.Is(err, service.ErrCategoryDescription):
|
|
response.Error(w, http.StatusBadRequest, err.Error())
|
|
default:
|
|
response.Error(w, http.StatusInternalServerError, "feedback category request failed")
|
|
}
|
|
}
|
|
|
|
// Public — GET /feedback/categories: the form's options, in order.
|
|
func (h *FeedbackCategoryHandler) Public(w http.ResponseWriter, r *http.Request) {
|
|
list, err := h.svc.Active(r.Context())
|
|
if err != nil {
|
|
writeCategoryError(w, err)
|
|
return
|
|
}
|
|
response.JSON(w, http.StatusOK, list)
|
|
}
|
|
|
|
// List — GET /admin/feedback/categories: all, with usage counts.
|
|
func (h *FeedbackCategoryHandler) List(w http.ResponseWriter, r *http.Request) {
|
|
list, err := h.svc.All(r.Context())
|
|
if err != nil {
|
|
writeCategoryError(w, err)
|
|
return
|
|
}
|
|
response.JSON(w, http.StatusOK, list)
|
|
}
|
|
|
|
type categoryReq struct {
|
|
Label string `json:"label"`
|
|
Description string `json:"description"`
|
|
Archived bool `json:"archived"`
|
|
}
|
|
|
|
func (h *FeedbackCategoryHandler) Create(w http.ResponseWriter, r *http.Request) {
|
|
var req categoryReq
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
response.Error(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
c, err := h.svc.Create(r.Context(), req.Label, req.Description)
|
|
if err != nil {
|
|
writeCategoryError(w, err)
|
|
return
|
|
}
|
|
response.JSON(w, http.StatusCreated, c)
|
|
}
|
|
|
|
func (h *FeedbackCategoryHandler) Update(w http.ResponseWriter, r *http.Request) {
|
|
var req categoryReq
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
response.Error(w, http.StatusBadRequest, "invalid request body")
|
|
return
|
|
}
|
|
if err := h.svc.Update(r.Context(), chi.URLParam(r, "key"), req.Label, req.Description, req.Archived); err != nil {
|
|
writeCategoryError(w, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
func (h *FeedbackCategoryHandler) Delete(w http.ResponseWriter, r *http.Request) {
|
|
if err := h.svc.Delete(r.Context(), chi.URLParam(r, "key")); err != nil {
|
|
writeCategoryError(w, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|
|
|
|
// Reorder — PUT /admin/feedback/categories/order {"keys": [...]}.
|
|
func (h *FeedbackCategoryHandler) Reorder(w http.ResponseWriter, r *http.Request) {
|
|
var req struct {
|
|
Keys []string `json:"keys"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil || len(req.Keys) == 0 {
|
|
response.Error(w, http.StatusBadRequest, "keys are required")
|
|
return
|
|
}
|
|
if err := h.svc.Reorder(r.Context(), req.Keys); err != nil {
|
|
writeCategoryError(w, err)
|
|
return
|
|
}
|
|
w.WriteHeader(http.StatusNoContent)
|
|
}
|