package handler import ( "database/sql" "errors" "net/http" "strconv" "github.com/go-chi/chi/v5" "github.com/wotra/wotra/internal/service" ) // SettingsHandler serves /api/settings routes. type SettingsHandler struct { svc *service.SettingsService } func NewSettingsHandler(svc *service.SettingsService) *SettingsHandler { return &SettingsHandler{svc: svc} } func (h *SettingsHandler) Routes(r chi.Router) { r.Get("/settings", h.Current) r.Put("/settings", h.Upsert) r.Get("/settings/history", h.History) r.Put("/settings/history/{id}", h.UpdateHistoryRow) r.Delete("/settings/history/{id}", h.DeleteHistoryRow) } // Current GET /api/settings func (h *SettingsHandler) Current(w http.ResponseWriter, r *http.Request) { set, err := h.svc.Current(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, set) } // Upsert PUT /api/settings func (h *SettingsHandler) Upsert(w http.ResponseWriter, r *http.Request) { var body struct { EffectiveFrom string `json:"effective_from"` HoursPerWeek float64 `json:"hours_per_week"` WorkdaysMask int `json:"workdays_mask"` Timezone string `json:"timezone"` } if err := decodeJSON(r, &body); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON") return } set, err := h.svc.Upsert(r.Context(), service.UpsertSettingsInput{ EffectiveFrom: body.EffectiveFrom, HoursPerWeek: body.HoursPerWeek, WorkdaysMask: body.WorkdaysMask, Timezone: body.Timezone, }) if err != nil { switch { case errors.Is(err, service.ErrInvalidHours), errors.Is(err, service.ErrInvalidWorkdaysMask): writeError(w, http.StatusUnprocessableEntity, err.Error()) default: writeError(w, http.StatusInternalServerError, err.Error()) } return } writeJSON(w, http.StatusOK, set) } // History GET /api/settings/history func (h *SettingsHandler) History(w http.ResponseWriter, r *http.Request) { history, err := h.svc.History(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if history == nil { writeJSON(w, http.StatusOK, []struct{}{}) return } writeJSON(w, http.StatusOK, history) } // UpdateHistoryRow PUT /api/settings/history/{id} func (h *SettingsHandler) UpdateHistoryRow(w http.ResponseWriter, r *http.Request) { id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { writeError(w, http.StatusBadRequest, "invalid id") return } var body struct { EffectiveFrom string `json:"effective_from"` HoursPerWeek float64 `json:"hours_per_week"` WorkdaysMask int `json:"workdays_mask"` Timezone string `json:"timezone"` } if err := decodeJSON(r, &body); err != nil { writeError(w, http.StatusBadRequest, "invalid JSON") return } set, err := h.svc.UpdateSettings(r.Context(), id, service.UpdateSettingsInput{ EffectiveFrom: body.EffectiveFrom, HoursPerWeek: body.HoursPerWeek, WorkdaysMask: body.WorkdaysMask, Timezone: body.Timezone, }) if err != nil { switch { case errors.Is(err, service.ErrSettingsNotFound), errors.Is(err, sql.ErrNoRows): writeError(w, http.StatusNotFound, err.Error()) case errors.Is(err, service.ErrInvalidHours), errors.Is(err, service.ErrInvalidWorkdaysMask): writeError(w, http.StatusUnprocessableEntity, err.Error()) default: writeError(w, http.StatusInternalServerError, err.Error()) } return } writeJSON(w, http.StatusOK, set) } // DeleteHistoryRow DELETE /api/settings/history/{id} func (h *SettingsHandler) DeleteHistoryRow(w http.ResponseWriter, r *http.Request) { id, err := strconv.ParseInt(chi.URLParam(r, "id"), 10, 64) if err != nil { writeError(w, http.StatusBadRequest, "invalid id") return } if err := h.svc.DeleteSettings(r.Context(), id); err != nil { switch { case errors.Is(err, service.ErrSettingsNotFound): writeError(w, http.StatusNotFound, err.Error()) case errors.Is(err, service.ErrLastSettingsRow): writeError(w, http.StatusConflict, err.Error()) default: writeError(w, http.StatusInternalServerError, err.Error()) } return } w.WriteHeader(http.StatusNoContent) }