mirror of
https://github.com/Dvorinka/Trackeep.git
synced 2026-06-03 20:12:58 +00:00
569 lines
16 KiB
Go
569 lines
16 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/trackeep/backend/models"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// GoalsHabitsHandler handles goals and habits operations
|
|
type GoalsHabitsHandler struct {
|
|
db *gorm.DB
|
|
}
|
|
|
|
// NewGoalsHabitsHandler creates a new goals and habits handler
|
|
func NewGoalsHabitsHandler(db *gorm.DB) *GoalsHabitsHandler {
|
|
return &GoalsHabitsHandler{db: db}
|
|
}
|
|
|
|
// Goal Handlers
|
|
|
|
// CreateGoal creates a new goal
|
|
func (h *GoalsHabitsHandler) CreateGoal(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
|
|
var req struct {
|
|
Title string `json:"title" binding:"required"`
|
|
Description string `json:"description"`
|
|
Category string `json:"category"`
|
|
TargetValue float64 `json:"target_value"`
|
|
Unit string `json:"unit"`
|
|
Deadline time.Time `json:"deadline"`
|
|
Priority string `json:"priority"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
goal := models.Goal{
|
|
UserID: userID,
|
|
Title: req.Title,
|
|
Description: req.Description,
|
|
Category: req.Category,
|
|
TargetValue: req.TargetValue,
|
|
CurrentValue: 0,
|
|
Unit: req.Unit,
|
|
Deadline: req.Deadline,
|
|
Status: "active",
|
|
Priority: req.Priority,
|
|
Progress: 0,
|
|
IsCompleted: false,
|
|
}
|
|
|
|
if err := h.db.Create(&goal).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create goal"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, goal)
|
|
}
|
|
|
|
// GetGoals retrieves user's goals
|
|
func (h *GoalsHabitsHandler) GetGoals(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
|
|
category := c.Query("category")
|
|
status := c.Query("status")
|
|
priority := c.Query("priority")
|
|
limit := 20
|
|
if l := c.Query("limit"); l != "" {
|
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 100 {
|
|
limit = parsed
|
|
}
|
|
}
|
|
|
|
query := h.db.Where("user_id = ?", userID)
|
|
if category != "" {
|
|
query = query.Where("category = ?", category)
|
|
}
|
|
if status != "" {
|
|
query = query.Where("status = ?", status)
|
|
}
|
|
if priority != "" {
|
|
query = query.Where("priority = ?", priority)
|
|
}
|
|
|
|
var goals []models.Goal
|
|
if err := query.Preload("Milestones").Order("created_at DESC").Limit(limit).Find(&goals).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch goals"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"goals": goals,
|
|
"limit": limit,
|
|
})
|
|
}
|
|
|
|
// GetGoal retrieves a specific goal
|
|
func (h *GoalsHabitsHandler) GetGoal(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
goalID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid goal ID"})
|
|
return
|
|
}
|
|
|
|
var goal models.Goal
|
|
if err := h.db.Where("id = ? AND user_id = ?", goalID, userID).
|
|
Preload("Milestones").
|
|
First(&goal).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Goal not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, goal)
|
|
}
|
|
|
|
// UpdateGoal updates a goal
|
|
func (h *GoalsHabitsHandler) UpdateGoal(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
goalID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid goal ID"})
|
|
return
|
|
}
|
|
|
|
var goal models.Goal
|
|
if err := h.db.Where("id = ? AND user_id = ?", goalID, userID).First(&goal).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Goal not found"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Title string `json:"title"`
|
|
Description string `json:"description"`
|
|
Category string `json:"category"`
|
|
TargetValue float64 `json:"target_value"`
|
|
CurrentValue float64 `json:"current_value"`
|
|
Unit string `json:"unit"`
|
|
Deadline time.Time `json:"deadline"`
|
|
Status string `json:"status"`
|
|
Priority string `json:"priority"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Update fields
|
|
if req.Title != "" {
|
|
goal.Title = req.Title
|
|
}
|
|
if req.Description != "" {
|
|
goal.Description = req.Description
|
|
}
|
|
if req.Category != "" {
|
|
goal.Category = req.Category
|
|
}
|
|
if req.TargetValue > 0 {
|
|
goal.TargetValue = req.TargetValue
|
|
}
|
|
if req.CurrentValue >= 0 {
|
|
goal.CurrentValue = req.CurrentValue
|
|
}
|
|
if req.Unit != "" {
|
|
goal.Unit = req.Unit
|
|
}
|
|
if !req.Deadline.IsZero() {
|
|
goal.Deadline = req.Deadline
|
|
}
|
|
if req.Status != "" {
|
|
goal.Status = req.Status
|
|
}
|
|
if req.Priority != "" {
|
|
goal.Priority = req.Priority
|
|
}
|
|
|
|
if err := h.db.Save(&goal).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update goal"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, goal)
|
|
}
|
|
|
|
// DeleteGoal deletes a goal
|
|
func (h *GoalsHabitsHandler) DeleteGoal(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
goalID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid goal ID"})
|
|
return
|
|
}
|
|
|
|
var goal models.Goal
|
|
if err := h.db.Where("id = ? AND user_id = ?", goalID, userID).First(&goal).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Goal not found"})
|
|
return
|
|
}
|
|
|
|
if err := h.db.Delete(&goal).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete goal"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Goal deleted successfully"})
|
|
}
|
|
|
|
// Habit Handlers
|
|
|
|
// CreateHabit creates a new habit
|
|
func (h *GoalsHabitsHandler) CreateHabit(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
|
|
var req struct {
|
|
Name string `json:"name" binding:"required"`
|
|
Description string `json:"description"`
|
|
Category string `json:"category"`
|
|
TargetFrequency int `json:"target_frequency"`
|
|
FrequencyUnit string `json:"frequency_unit"`
|
|
TargetValue float64 `json:"target_value"`
|
|
Unit string `json:"unit"`
|
|
TimeOfDay string `json:"time_of_day"`
|
|
DaysOfWeek []string `json:"days_of_week"`
|
|
IsActive bool `json:"is_active"`
|
|
IsPublic bool `json:"is_public"`
|
|
GoalID *uint `json:"goal_id"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
habit := models.Habit{
|
|
UserID: userID,
|
|
Name: req.Name,
|
|
Description: req.Description,
|
|
Category: req.Category,
|
|
TargetFrequency: req.TargetFrequency,
|
|
FrequencyUnit: req.FrequencyUnit,
|
|
TargetValue: req.TargetValue,
|
|
Unit: req.Unit,
|
|
TimeOfDay: req.TimeOfDay,
|
|
DaysOfWeek: req.DaysOfWeek,
|
|
IsActive: req.IsActive,
|
|
IsPublic: req.IsPublic,
|
|
GoalID: req.GoalID,
|
|
Streak: 0,
|
|
LongestStreak: 0,
|
|
CompletionRate: 0,
|
|
}
|
|
|
|
if err := h.db.Create(&habit).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create habit"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, habit)
|
|
}
|
|
|
|
// GetHabits retrieves user's habits
|
|
func (h *GoalsHabitsHandler) GetHabits(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
|
|
category := c.Query("category")
|
|
isActive := c.Query("is_active")
|
|
limit := 20
|
|
if l := c.Query("limit"); l != "" {
|
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 100 {
|
|
limit = parsed
|
|
}
|
|
}
|
|
|
|
query := h.db.Where("user_id = ?", userID)
|
|
if category != "" {
|
|
query = query.Where("category = ?", category)
|
|
}
|
|
if isActive != "" {
|
|
active := isActive == "true"
|
|
query = query.Where("is_active = ?", active)
|
|
}
|
|
|
|
var habits []models.Habit
|
|
if err := query.Preload("Goal").Order("created_at DESC").Limit(limit).Find(&habits).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch habits"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"habits": habits,
|
|
"limit": limit,
|
|
})
|
|
}
|
|
|
|
// GetHabit retrieves a specific habit
|
|
func (h *GoalsHabitsHandler) GetHabit(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
habitID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid habit ID"})
|
|
return
|
|
}
|
|
|
|
var habit models.Habit
|
|
if err := h.db.Where("id = ? AND user_id = ?", habitID, userID).
|
|
Preload("Goal").
|
|
Preload("HabitEntries", "entry_date >= ?", time.Now().AddDate(0, 0, -30)).
|
|
First(&habit).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Habit not found"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, habit)
|
|
}
|
|
|
|
// UpdateHabit updates a habit
|
|
func (h *GoalsHabitsHandler) UpdateHabit(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
habitID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid habit ID"})
|
|
return
|
|
}
|
|
|
|
var habit models.Habit
|
|
if err := h.db.Where("id = ? AND user_id = ?", habitID, userID).First(&habit).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Habit not found"})
|
|
return
|
|
}
|
|
|
|
var req struct {
|
|
Name string `json:"name"`
|
|
Description string `json:"description"`
|
|
Category string `json:"category"`
|
|
TargetFrequency int `json:"target_frequency"`
|
|
FrequencyUnit string `json:"frequency_unit"`
|
|
TargetValue float64 `json:"target_value"`
|
|
Unit string `json:"unit"`
|
|
TimeOfDay string `json:"time_of_day"`
|
|
DaysOfWeek []string `json:"days_of_week"`
|
|
IsActive bool `json:"is_active"`
|
|
IsPublic bool `json:"is_public"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Update fields
|
|
if req.Name != "" {
|
|
habit.Name = req.Name
|
|
}
|
|
if req.Description != "" {
|
|
habit.Description = req.Description
|
|
}
|
|
if req.Category != "" {
|
|
habit.Category = req.Category
|
|
}
|
|
if req.TargetFrequency > 0 {
|
|
habit.TargetFrequency = req.TargetFrequency
|
|
}
|
|
if req.FrequencyUnit != "" {
|
|
habit.FrequencyUnit = req.FrequencyUnit
|
|
}
|
|
if req.TargetValue > 0 {
|
|
habit.TargetValue = req.TargetValue
|
|
}
|
|
if req.Unit != "" {
|
|
habit.Unit = req.Unit
|
|
}
|
|
if req.TimeOfDay != "" {
|
|
habit.TimeOfDay = req.TimeOfDay
|
|
}
|
|
if req.DaysOfWeek != nil {
|
|
habit.DaysOfWeek = req.DaysOfWeek
|
|
}
|
|
habit.IsActive = req.IsActive
|
|
habit.IsPublic = req.IsPublic
|
|
|
|
if err := h.db.Save(&habit).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update habit"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, habit)
|
|
}
|
|
|
|
// DeleteHabit deletes a habit
|
|
func (h *GoalsHabitsHandler) DeleteHabit(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
habitID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid habit ID"})
|
|
return
|
|
}
|
|
|
|
var habit models.Habit
|
|
if err := h.db.Where("id = ? AND user_id = ?", habitID, userID).First(&habit).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Habit not found"})
|
|
return
|
|
}
|
|
|
|
if err := h.db.Delete(&habit).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete habit"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{"message": "Habit deleted successfully"})
|
|
}
|
|
|
|
// HabitEntry Handlers
|
|
|
|
// CreateHabitEntry creates a new habit entry
|
|
func (h *GoalsHabitsHandler) CreateHabitEntry(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
|
|
var req struct {
|
|
HabitID uint `json:"habit_id" binding:"required"`
|
|
EntryDate time.Time `json:"entry_date" binding:"required"`
|
|
Value float64 `json:"value"`
|
|
TargetValue float64 `json:"target_value"`
|
|
Unit string `json:"unit"`
|
|
Notes string `json:"notes"`
|
|
Quality int `json:"quality"`
|
|
TimeSpent int `json:"time_spent"`
|
|
Location string `json:"location"`
|
|
Mood string `json:"mood"`
|
|
}
|
|
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
|
|
// Verify habit ownership
|
|
var habit models.Habit
|
|
if err := h.db.Where("id = ? AND user_id = ?", req.HabitID, userID).First(&habit).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Habit not found"})
|
|
return
|
|
}
|
|
|
|
entry := models.HabitEntry{
|
|
HabitID: req.HabitID,
|
|
EntryDate: req.EntryDate,
|
|
Value: req.Value,
|
|
TargetValue: req.TargetValue,
|
|
Unit: req.Unit,
|
|
Notes: req.Notes,
|
|
Quality: req.Quality,
|
|
TimeSpent: req.TimeSpent,
|
|
Location: req.Location,
|
|
Mood: req.Mood,
|
|
}
|
|
|
|
if err := h.db.Create(&entry).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create habit entry"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusCreated, entry)
|
|
}
|
|
|
|
// GetHabitEntries retrieves habit entries
|
|
func (h *GoalsHabitsHandler) GetHabitEntries(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
habitID, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid habit ID"})
|
|
return
|
|
}
|
|
|
|
// Verify habit ownership
|
|
var habit models.Habit
|
|
if err := h.db.Where("id = ? AND user_id = ?", habitID, userID).First(&habit).Error; err != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "Habit not found"})
|
|
return
|
|
}
|
|
|
|
startDate := c.Query("start_date")
|
|
endDate := c.Query("end_date")
|
|
limit := 50
|
|
if l := c.Query("limit"); l != "" {
|
|
if parsed, err := strconv.Atoi(l); err == nil && parsed > 0 && parsed <= 100 {
|
|
limit = parsed
|
|
}
|
|
}
|
|
|
|
query := h.db.Where("habit_id = ?", habitID)
|
|
if startDate != "" {
|
|
if parsed, err := time.Parse("2006-01-02", startDate); err == nil {
|
|
query = query.Where("entry_date >= ?", parsed)
|
|
}
|
|
}
|
|
if endDate != "" {
|
|
if parsed, err := time.Parse("2006-01-02", endDate); err == nil {
|
|
query = query.Where("entry_date <= ?", parsed)
|
|
}
|
|
}
|
|
|
|
var entries []models.HabitEntry
|
|
if err := query.Order("entry_date DESC").Limit(limit).Find(&entries).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch habit entries"})
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, gin.H{
|
|
"entries": entries,
|
|
"limit": limit,
|
|
})
|
|
}
|
|
|
|
// GetDashboardStats retrieves dashboard statistics for goals and habits
|
|
func (h *GoalsHabitsHandler) GetDashboardStats(c *gin.Context) {
|
|
userID := c.GetUint("user_id")
|
|
|
|
// Goal stats
|
|
var totalGoals, activeGoals, completedGoals int64
|
|
h.db.Model(&models.Goal{}).Where("user_id = ?", userID).Count(&totalGoals)
|
|
h.db.Model(&models.Goal{}).Where("user_id = ? AND status = ?", userID, "active").Count(&activeGoals)
|
|
h.db.Model(&models.Goal{}).Where("user_id = ? AND status = ?", userID, "completed").Count(&completedGoals)
|
|
|
|
// Habit stats
|
|
var totalHabits, activeHabits int64
|
|
h.db.Model(&models.Habit{}).Where("user_id = ?", userID).Count(&totalHabits)
|
|
h.db.Model(&models.Habit{}).Where("user_id = ? AND is_active = ?", userID, true).Count(&activeHabits)
|
|
|
|
// Today's habit entries
|
|
today := time.Now().Truncate(24 * time.Hour)
|
|
tomorrow := today.Add(24 * time.Hour)
|
|
var todayEntries int64
|
|
h.db.Model(&models.HabitEntry{}).
|
|
Joins("JOIN habits ON habit_entries.habit_id = habits.id").
|
|
Where("habits.user_id = ? AND habit_entries.entry_date >= ? AND habit_entries.entry_date < ?", userID, today, tomorrow).
|
|
Count(&todayEntries)
|
|
|
|
// Current week streak
|
|
weekAgo := time.Now().AddDate(0, 0, -7)
|
|
var weekEntries int64
|
|
h.db.Model(&models.HabitEntry{}).
|
|
Joins("JOIN habits ON habit_entries.habit_id = habits.id").
|
|
Where("habits.user_id = ? AND habit_entries.entry_date >= ? AND habit_entries.is_completed = ?", userID, weekAgo, true).
|
|
Count(&weekEntries)
|
|
|
|
stats := gin.H{
|
|
"goals": gin.H{
|
|
"total": totalGoals,
|
|
"active": activeGoals,
|
|
"completed": completedGoals,
|
|
},
|
|
"habits": gin.H{
|
|
"total": totalHabits,
|
|
"active": activeHabits,
|
|
"today_entries": todayEntries,
|
|
"week_streak": weekEntries,
|
|
},
|
|
}
|
|
|
|
c.JSON(http.StatusOK, stats)
|
|
}
|