lots of stuff, don't truly remember
This commit is contained in:
210
internal/goal/handler.go
Normal file
210
internal/goal/handler.go
Normal file
@@ -0,0 +1,210 @@
|
||||
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})
|
||||
}
|
||||
|
||||
42
internal/goal/model.go
Normal file
42
internal/goal/model.go
Normal file
@@ -0,0 +1,42 @@
|
||||
package goal
|
||||
|
||||
import "time"
|
||||
|
||||
// GoalType defines categories of goals users can set.
|
||||
type GoalType string
|
||||
|
||||
const (
|
||||
GoalDistance GoalType = "distance"
|
||||
GoalFrequency GoalType = "frequency"
|
||||
GoalPower GoalType = "power"
|
||||
GoalWeight GoalType = "weight"
|
||||
GoalCustom GoalType = "custom"
|
||||
)
|
||||
|
||||
// GoalStatus tracks the lifecycle of a goal.
|
||||
type GoalStatus string
|
||||
|
||||
const (
|
||||
StatusActive GoalStatus = "active"
|
||||
StatusCompleted GoalStatus = "completed"
|
||||
StatusArchived GoalStatus = "archived"
|
||||
)
|
||||
|
||||
type Goal struct {
|
||||
ID uint `gorm:"primaryKey" json:"id"`
|
||||
UserID uint `gorm:"not null;index" json:"user_id"`
|
||||
Title string `gorm:"not null" json:"title"`
|
||||
Description string `gorm:"default:''" json:"description"`
|
||||
GoalType GoalType `gorm:"default:'custom'" json:"goal_type"`
|
||||
TargetValue float64 `gorm:"not null;default:0" json:"target_value"`
|
||||
CurrentValue float64 `gorm:"default:0" json:"current_value"`
|
||||
TargetDate *time.Time `json:"target_date,omitempty"`
|
||||
Status GoalStatus `gorm:"default:'active'" json:"status"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
func (Goal) TableName() string {
|
||||
return "goals"
|
||||
}
|
||||
|
||||
49
internal/goal/repository.go
Normal file
49
internal/goal/repository.go
Normal file
@@ -0,0 +1,49 @@
|
||||
package goal
|
||||
|
||||
import (
|
||||
"rideaware/pkg/database"
|
||||
)
|
||||
|
||||
type Repository struct{}
|
||||
|
||||
func NewRepository() *Repository {
|
||||
return &Repository{}
|
||||
}
|
||||
|
||||
func (r *Repository) Create(goal *Goal) error {
|
||||
return database.DB.Create(goal).Error
|
||||
}
|
||||
|
||||
func (r *Repository) GetByID(id, userID uint) (*Goal, error) {
|
||||
var g Goal
|
||||
err := database.DB.Where("id = ? AND user_id = ?", id, userID).First(&g).Error
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &g, nil
|
||||
}
|
||||
|
||||
func (r *Repository) List(userID uint) ([]Goal, error) {
|
||||
var goals []Goal
|
||||
err := database.DB.Where("user_id = ?", userID).
|
||||
Order("created_at desc").
|
||||
Find(&goals).Error
|
||||
return goals, err
|
||||
}
|
||||
|
||||
func (r *Repository) ListByStatus(userID uint, status GoalStatus) ([]Goal, error) {
|
||||
var goals []Goal
|
||||
err := database.DB.Where("user_id = ? AND status = ?", userID, status).
|
||||
Order("created_at desc").
|
||||
Find(&goals).Error
|
||||
return goals, err
|
||||
}
|
||||
|
||||
func (r *Repository) Update(goal *Goal) error {
|
||||
return database.DB.Save(goal).Error
|
||||
}
|
||||
|
||||
func (r *Repository) Delete(id, userID uint) error {
|
||||
return database.DB.Where("id = ? AND user_id = ?", id, userID).Delete(&Goal{}).Error
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user