package goal import ( "encoding/json" "net/http" "strconv" "time" "rideaware/internal/config" "rideaware/internal/middleware" ) type Handler struct{} func NewHandler() *Handler { return &Handler{} } // CreateGoal POST /api/protected/goals func (h *Handler) CreateGoal(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) var req struct { Title string `json:"title"` Description string `json:"description"` GoalType string `json:"goal_type"` TargetValue float64 `json:"target_value"` TargetDate *time.Time `json:"target_date"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if req.Title == "" { writeError(w, http.StatusBadRequest, "title is required") return } if req.TargetValue <= 0 { writeError(w, http.StatusBadRequest, "target_value must be greater than 0") return } // Validate goal type gType := GoalType(req.GoalType) switch gType { case GoalDistance, GoalFrequency, GoalPower, GoalWeight, GoalCustom, "": if gType == "" { gType = GoalCustom } default: writeError(w, http.StatusBadRequest, "invalid goal_type") return } goal := &Goal{ UserID: claims.UserID, Title: req.Title, Description: req.Description, GoalType: gType, TargetValue: req.TargetValue, TargetDate: req.TargetDate, Status: StatusActive, } repo := NewRepository() if err := repo.Create(goal); err != nil { writeError(w, http.StatusInternalServerError, "failed to create goal") return } writeJSON(w, http.StatusCreated, goal) } // GetGoals GET /api/protected/goals?status=active func (h *Handler) GetGoals(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) statusFilter := r.URL.Query().Get("status") repo := NewRepository() var goals []Goal var err error if statusFilter != "" { goals, err = repo.ListByStatus(claims.UserID, GoalStatus(statusFilter)) } else { goals, err = repo.List(claims.UserID) } if err != nil { writeError(w, http.StatusInternalServerError, "failed to fetch goals") return } if goals == nil { goals = []Goal{} } writeJSON(w, http.StatusOK, goals) } // UpdateGoal PUT /api/protected/goals?id=X func (h *Handler) UpdateGoal(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) idStr := r.URL.Query().Get("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { writeError(w, http.StatusBadRequest, "invalid goal id") return } repo := NewRepository() goal, err := repo.GetByID(uint(id), claims.UserID) if err != nil { writeError(w, http.StatusNotFound, "goal not found") return } var req struct { Title string `json:"title"` Description string `json:"description"` GoalType string `json:"goal_type"` TargetValue *float64 `json:"target_value"` CurrentValue *float64 `json:"current_value"` TargetDate *time.Time `json:"target_date"` Status string `json:"status"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if req.Title != "" { goal.Title = req.Title } if req.Description != "" { goal.Description = req.Description } if req.GoalType != "" { gType := GoalType(req.GoalType) switch gType { case GoalDistance, GoalFrequency, GoalPower, GoalWeight, GoalCustom: goal.GoalType = gType default: writeError(w, http.StatusBadRequest, "invalid goal_type") return } } if req.TargetValue != nil { goal.TargetValue = *req.TargetValue } if req.CurrentValue != nil { goal.CurrentValue = *req.CurrentValue } if req.TargetDate != nil { goal.TargetDate = req.TargetDate } if req.Status != "" { switch GoalStatus(req.Status) { case StatusActive, StatusCompleted, StatusArchived: goal.Status = GoalStatus(req.Status) default: writeError(w, http.StatusBadRequest, "invalid status") return } } if err := repo.Update(goal); err != nil { writeError(w, http.StatusInternalServerError, "failed to update goal") return } writeJSON(w, http.StatusOK, goal) } // DeleteGoal DELETE /api/protected/goals?id=X func (h *Handler) DeleteGoal(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) idStr := r.URL.Query().Get("id") id, err := strconv.ParseUint(idStr, 10, 32) if err != nil { writeError(w, http.StatusBadRequest, "invalid goal id") return } repo := NewRepository() if err := repo.Delete(uint(id), claims.UserID); err != nil { writeError(w, http.StatusNotFound, "goal not found") return } w.WriteHeader(http.StatusNoContent) } func writeJSON(w http.ResponseWriter, status int, data interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(data) } func writeError(w http.ResponseWriter, status int, message string) { writeJSON(w, status, map[string]string{"error": message}) }