package handler import ( "encoding/json" "errors" "net/http" "strconv" "strings" "time" "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" ) // VaniHandler serves the public "Vāṇī of the Day" pick for the home page. type VaniHandler struct { svc *service.VaniService } func NewVaniHandler(svc *service.VaniService) *VaniHandler { return &VaniHandler{svc: svc} } // Today — GET /vani/today. Public; the same pick is returned to everyone for // the current UTC day. Responds with `data: null` if there's no content. type setVaniReq struct { // Day as YYYY-MM-DD (UTC). Empty means today. Day string `json:"day"` Kind string `json:"kind"` // RefID is the content's Mongo _id hex, which search returns as doc_id. RefID string `json:"ref_id"` } // Set — POST /admin/vani. Pins a specific item as a day's Vāṇī. func (h *VaniHandler) Set(w http.ResponseWriter, r *http.Request) { var req setVaniReq if err := json.NewDecoder(r.Body).Decode(&req); err != nil { response.Error(w, http.StatusBadRequest, "invalid JSON body") return } day, err := parseVaniDay(req.Day) if err != nil { response.Error(w, http.StatusBadRequest, "day must be YYYY-MM-DD") return } pick, err := h.svc.SetForDay(r.Context(), day, req.Kind, req.RefID) if err != nil { writeVaniError(w, err) return } response.JSON(w, http.StatusOK, map[string]any{ "day": pick.Day.Format("2006-01-02"), "kind": pick.Kind, "ref_id": pick.RefID, "payload": pick.Payload, }) } // Clear — DELETE /admin/vani/{day}. Hands the day back to the random picker. func (h *VaniHandler) Clear(w http.ResponseWriter, r *http.Request) { day, err := parseVaniDay(chi.URLParam(r, "day")) if err != nil { response.Error(w, http.StatusBadRequest, "day must be YYYY-MM-DD") return } if err := h.svc.ClearForDay(r.Context(), day); err != nil { writeVaniError(w, err) return } response.NoContent(w) } // Schedule — GET /admin/vani. Decided days from today onward. func (h *VaniHandler) Schedule(w http.ResponseWriter, r *http.Request) { limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) picks, err := h.svc.Schedule(r.Context(), limit) if err != nil { writeVaniError(w, err) return } out := make([]map[string]any, 0, len(picks)) for _, p := range picks { out = append(out, map[string]any{ "day": p.Day.Format("2006-01-02"), "kind": p.Kind, "ref_id": p.RefID, "payload": p.Payload, }) } response.JSON(w, http.StatusOK, out) } // parseVaniDay accepts YYYY-MM-DD, or empty for today (UTC — the pick is // keyed on a UTC calendar day so everyone sees the same item). func parseVaniDay(s string) (time.Time, error) { if strings.TrimSpace(s) == "" { return time.Now().UTC().Truncate(24 * time.Hour), nil } return time.Parse("2006-01-02", strings.TrimSpace(s)) } func writeVaniError(w http.ResponseWriter, err error) { switch { case errors.Is(err, repository.ErrVaniRefInvalid), errors.Is(err, service.ErrVaniBadKind): response.Error(w, http.StatusBadRequest, err.Error()) case errors.Is(err, repository.ErrVaniRefNotFound): response.Error(w, http.StatusNotFound, err.Error()) default: response.Error(w, http.StatusInternalServerError, "vāṇī request failed") } } func (h *VaniHandler) Today(w http.ResponseWriter, r *http.Request) { payload, err := h.svc.Today(r.Context()) if err != nil { response.Error(w, http.StatusInternalServerError, "could not load vāṇī of the day") return } response.JSON(w, http.StatusOK, payload) }