package handler import ( "errors" "net/http" "github.com/go-chi/chi/v5" "github.com/wotra/wotra/internal/domain" "github.com/wotra/wotra/internal/service" ) // DayHandler serves /api/days routes. type DayHandler struct { svc *service.DayService } func NewDayHandler(svc *service.DayService) *DayHandler { return &DayHandler{svc: svc} } func (h *DayHandler) Routes(r chi.Router) { r.Get("/days", h.List) r.Post("/days/{day_key}/close", h.Close) r.Post("/days/{day_key}/mark", h.Mark) r.Delete("/days/{day_key}/close", h.Reopen) } // List GET /api/days?from=YYYY-MM-DD&to=YYYY-MM-DD func (h *DayHandler) List(w http.ResponseWriter, r *http.Request) { from := r.URL.Query().Get("from") to := r.URL.Query().Get("to") if from == "" { from = "0000-01-01" } if to == "" { to = "9999-12-31" } days, err := h.svc.ListDays(r.Context(), from, to) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if days == nil { writeJSON(w, http.StatusOK, []struct{}{}) return } writeJSON(w, http.StatusOK, days) } // Close POST /api/days/{day_key}/close func (h *DayHandler) Close(w http.ResponseWriter, r *http.Request) { dayKey := chi.URLParam(r, "day_key") cd, err := h.svc.CloseDay(r.Context(), dayKey) if err != nil { switch { case errors.Is(err, service.ErrDayAlreadyClosed): writeError(w, http.StatusConflict, err.Error()) case errors.Is(err, service.ErrRunningEntryOnDay): writeError(w, http.StatusConflict, err.Error()) case errors.Is(err, service.ErrDayHasNoEntries): writeError(w, http.StatusUnprocessableEntity, err.Error()) default: writeError(w, http.StatusInternalServerError, err.Error()) } return } writeJSON(w, http.StatusOK, cd) } // Mark POST /api/days/{day_key}/mark func (h *DayHandler) Mark(w http.ResponseWriter, r *http.Request) { dayKey := chi.URLParam(r, "day_key") var body struct { Kind domain.DayKind `json:"kind"` } if err := decodeJSON(r, &body); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON") return } cd, err := h.svc.MarkDay(r.Context(), dayKey, body.Kind) if err != nil { writeError(w, http.StatusUnprocessableEntity, err.Error()) return } writeJSON(w, http.StatusOK, cd) } // Reopen DELETE /api/days/{day_key}/close func (h *DayHandler) Reopen(w http.ResponseWriter, r *http.Request) { dayKey := chi.URLParam(r, "day_key") if err := h.svc.ReopenDay(r.Context(), dayKey); err != nil { switch { case errors.Is(err, service.ErrDayNotClosed): writeError(w, http.StatusNotFound, err.Error()) default: writeError(w, http.StatusInternalServerError, err.Error()) } return } w.WriteHeader(http.StatusNoContent) }