Files
rideaware-api/internal/goal/handler.go
2026-05-17 20:39:47 -05:00

211 lines
5.1 KiB
Go

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})
}