package handler import ( "encoding/json" "errors" "net/http" "strconv" "github.com/go-chi/chi/v5" "github.com/daniil/homelab-api/internal/middleware" "github.com/daniil/homelab-api/internal/model" "github.com/daniil/homelab-api/internal/repository" "github.com/daniil/homelab-api/internal/service" ) type HabitHandler struct { habitService *service.HabitService } func NewHabitHandler(habitService *service.HabitService) *HabitHandler { return &HabitHandler{habitService: habitService} } func (h *HabitHandler) List(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) includeArchived := r.URL.Query().Get("archived") == "true" habits, err := h.habitService.List(userID, includeArchived) if err != nil { writeError(w, "failed to fetch habits", http.StatusInternalServerError) return } writeJSON(w, habits, http.StatusOK) } func (h *HabitHandler) Create(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) var req model.CreateHabitRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, "invalid request body", http.StatusBadRequest) return } if req.Name == "" { writeError(w, "name is required", http.StatusBadRequest) return } habit, err := h.habitService.Create(userID, &req) if err != nil { writeError(w, "failed to create habit", http.StatusInternalServerError) return } writeJSON(w, habit, http.StatusCreated) } func (h *HabitHandler) Get(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) habitID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { writeError(w, "invalid habit id", http.StatusBadRequest) return } habit, err := h.habitService.Get(habitID, userID) if err != nil { if errors.Is(err, repository.ErrHabitNotFound) { writeError(w, "habit not found", http.StatusNotFound) return } writeError(w, "failed to fetch habit", http.StatusInternalServerError) return } writeJSON(w, habit, http.StatusOK) } func (h *HabitHandler) Update(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) habitID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { writeError(w, "invalid habit id", http.StatusBadRequest) return } var req model.UpdateHabitRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, "invalid request body", http.StatusBadRequest) return } habit, err := h.habitService.Update(habitID, userID, &req) if err != nil { if errors.Is(err, repository.ErrHabitNotFound) { writeError(w, "habit not found", http.StatusNotFound) return } writeError(w, "failed to update habit", http.StatusInternalServerError) return } writeJSON(w, habit, http.StatusOK) } func (h *HabitHandler) Delete(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) habitID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { writeError(w, "invalid habit id", http.StatusBadRequest) return } if err := h.habitService.Delete(habitID, userID); err != nil { if errors.Is(err, repository.ErrHabitNotFound) { writeError(w, "habit not found", http.StatusNotFound) return } writeError(w, "failed to delete habit", http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) } func (h *HabitHandler) Log(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) habitID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { writeError(w, "invalid habit id", http.StatusBadRequest) return } var req model.LogHabitRequest if err := json.NewDecoder(r.Body).Decode(&req); err != nil { // Allow empty body - means log today with count=1 req = model.LogHabitRequest{} } log, err := h.habitService.Log(habitID, userID, &req) if err != nil { if errors.Is(err, repository.ErrHabitNotFound) { writeError(w, "habit not found", http.StatusNotFound) return } writeError(w, "failed to log habit", http.StatusInternalServerError) return } writeJSON(w, log, http.StatusCreated) } func (h *HabitHandler) GetLogs(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) habitID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { writeError(w, "invalid habit id", http.StatusBadRequest) return } days := 30 if d := r.URL.Query().Get("days"); d != "" { if parsed, err := strconv.Atoi(d); err == nil && parsed > 0 { days = parsed } } logs, err := h.habitService.GetLogs(habitID, userID, days) if err != nil { if errors.Is(err, repository.ErrHabitNotFound) { writeError(w, "habit not found", http.StatusNotFound) return } writeError(w, "failed to fetch logs", http.StatusInternalServerError) return } writeJSON(w, logs, http.StatusOK) } func (h *HabitHandler) DeleteLog(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) logID, err := strconv.ParseInt(chi.URLParam(r, "logId"), 10, 64) if err != nil { writeError(w, "invalid log id", http.StatusBadRequest) return } if err := h.habitService.DeleteLog(logID, userID); err != nil { if errors.Is(err, repository.ErrLogNotFound) { writeError(w, "log not found", http.StatusNotFound) return } writeError(w, "failed to delete log", http.StatusInternalServerError) return } w.WriteHeader(http.StatusNoContent) } func (h *HabitHandler) Stats(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) stats, err := h.habitService.GetOverallStats(userID) if err != nil { writeError(w, "failed to fetch stats", http.StatusInternalServerError) return } writeJSON(w, stats, http.StatusOK) } func (h *HabitHandler) HabitStats(w http.ResponseWriter, r *http.Request) { userID := middleware.GetUserID(r.Context()) habitID, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { writeError(w, "invalid habit id", http.StatusBadRequest) return } stats, err := h.habitService.GetHabitStats(habitID, userID) if err != nil { if errors.Is(err, repository.ErrHabitNotFound) { writeError(w, "habit not found", http.StatusNotFound) return } writeError(w, "failed to fetch stats", http.StatusInternalServerError) return } writeJSON(w, stats, http.StatusOK) }