211 lines
5.1 KiB
Go
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})
|
|
}
|
|
|