aboutsummaryrefslogtreecommitdiffstats
path: root/backend/internal/api/handlers/goal_handler.go
diff options
context:
space:
mode:
Diffstat (limited to 'backend/internal/api/handlers/goal_handler.go')
-rw-r--r--backend/internal/api/handlers/goal_handler.go272
1 files changed, 272 insertions, 0 deletions
diff --git a/backend/internal/api/handlers/goal_handler.go b/backend/internal/api/handlers/goal_handler.go
new file mode 100644
index 0000000..09bea74
--- /dev/null
+++ b/backend/internal/api/handlers/goal_handler.go
@@ -0,0 +1,272 @@
+package handlers
+
+import (
+ "log"
+ "net/http"
+ "strconv"
+ "time"
+
+ "finance/backend/internal/database"
+ "finance/backend/internal/models"
+
+ "github.com/gin-gonic/gin"
+)
+
+// CreateGoalInput defines the structure for creating a new financial goal
+type CreateGoalInput struct {
+ Name string `json:"name" binding:"required"`
+ TargetAmount int64 `json:"targetAmount" binding:"required"`
+ CurrentAmount int64 `json:"currentAmount"`
+ TargetDate string `json:"targetDate"` // YYYY-MM-DD format
+ Status string `json:"status"`
+}
+
+// UpdateGoalInput defines the structure for updating an existing financial goal
+type UpdateGoalInput struct {
+ Name string `json:"name"`
+ TargetAmount int64 `json:"targetAmount"`
+ CurrentAmount int64 `json:"currentAmount"`
+ TargetDate string `json:"targetDate"` // YYYY-MM-DD format
+ Status string `json:"status"`
+}
+
+// UpdateGoalProgressInput defines the structure for updating just the progress of a goal
+type UpdateGoalProgressInput struct {
+ CurrentAmount int64 `json:"currentAmount" binding:"required"`
+}
+
+// GoalHandler handles all goal-related operations in the API
+type GoalHandler struct {
+}
+
+// NewGoalHandler creates and returns a new GoalHandler instance
+func NewGoalHandler() *GoalHandler {
+ return &GoalHandler{}
+}
+
+// GetGoals retrieves all goals for the authenticated user
+// Optionally filtered by status if provided as a query parameter
+func (h *GoalHandler) GetGoals(c *gin.Context) {
+ userID := c.MustGet("userID").(uint)
+ var goals []models.Goal
+
+ query := database.DB.Where("user_id = ?", userID)
+
+ // Filter by status if provided
+ status := c.Query("status")
+ if status != "" {
+ query = query.Where("status = ?", status)
+ }
+
+ if err := query.Find(&goals).Error; err != nil {
+ log.Printf("Error fetching goals for user %d: %v", userID, err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get goals"})
+ return
+ }
+
+ c.JSON(http.StatusOK, goals)
+}
+
+// GetGoal retrieves a specific goal by ID for the authenticated user
+func (h *GoalHandler) GetGoal(c *gin.Context) {
+ userID := c.MustGet("userID").(uint)
+ goalID, err := strconv.ParseUint(c.Param("id"), 10, 32)
+ if err != nil {
+ log.Printf("Error parsing goal ID: %v", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid goal ID"})
+ return
+ }
+
+ var goal models.Goal
+ if err := database.DB.Where("id = ? AND user_id = ?", goalID, userID).First(&goal).Error; err != nil {
+ log.Printf("Error fetching goal ID %d for user %d: %v", goalID, userID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": "Goal not found"})
+ return
+ }
+
+ c.JSON(http.StatusOK, goal)
+}
+
+// CreateGoal creates a new financial goal for the authenticated user
+func (h *GoalHandler) CreateGoal(c *gin.Context) {
+ userID := c.MustGet("userID").(uint)
+ var input CreateGoalInput
+
+ if err := c.ShouldBindJSON(&input); err != nil {
+ log.Printf("Error binding JSON for goal creation: %v", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ // Set default status if not provided
+ status := "Active"
+ if input.Status != "" {
+ status = input.Status
+ }
+
+ // Parse target date if provided
+ var targetDate time.Time
+ if input.TargetDate != "" {
+ parsedDate, err := time.Parse("2006-01-02", input.TargetDate)
+ if err != nil {
+ log.Printf("Error parsing target date for goal creation: %v", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid date format for targetDate. Use YYYY-MM-DD"})
+ return
+ }
+ targetDate = parsedDate
+ }
+
+ goal := models.Goal{
+ UserID: userID,
+ Name: input.Name,
+ TargetAmount: input.TargetAmount,
+ CurrentAmount: input.CurrentAmount,
+ Status: status,
+ }
+
+ // Only set target date if it was provided
+ if !targetDate.IsZero() {
+ goal.TargetDate = targetDate
+ }
+
+ if err := database.DB.Create(&goal).Error; err != nil {
+ log.Printf("Error creating goal for user %d: %v", userID, err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create goal"})
+ return
+ }
+
+ log.Printf("Goal created successfully for user %d: %s (ID: %d)", userID, goal.Name, goal.ID)
+ c.JSON(http.StatusCreated, goal)
+}
+
+// UpdateGoal updates an existing goal for the authenticated user
+func (h *GoalHandler) UpdateGoal(c *gin.Context) {
+ userID := c.MustGet("userID").(uint)
+ goalID, err := strconv.ParseUint(c.Param("id"), 10, 32)
+ if err != nil {
+ log.Printf("Error parsing goal ID for update: %v", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid goal ID"})
+ return
+ }
+
+ var goal models.Goal
+ if err := database.DB.Where("id = ? AND user_id = ?", goalID, userID).First(&goal).Error; err != nil {
+ log.Printf("Error fetching goal ID %d for user %d in update: %v", goalID, userID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": "Goal not found"})
+ return
+ }
+
+ var input UpdateGoalInput
+ if err := c.ShouldBindJSON(&input); err != nil {
+ log.Printf("Error binding JSON for goal update: %v", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ // Update fields that were provided
+ if input.Name != "" {
+ goal.Name = input.Name
+ }
+ if input.TargetAmount != 0 {
+ goal.TargetAmount = input.TargetAmount
+ }
+ if input.CurrentAmount != 0 {
+ goal.CurrentAmount = input.CurrentAmount
+ }
+ if input.TargetDate != "" {
+ parsedDate, err := time.Parse("2006-01-02", input.TargetDate)
+ if err != nil {
+ log.Printf("Error parsing target date for goal update: %v", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid date format for targetDate. Use YYYY-MM-DD"})
+ return
+ }
+ goal.TargetDate = parsedDate
+ }
+ if input.Status != "" {
+ goal.Status = input.Status
+ }
+
+ // Check if goal has been achieved
+ if goal.CurrentAmount >= goal.TargetAmount {
+ goal.Status = "Achieved"
+ log.Printf("Goal ID %d for user %d automatically marked as Achieved", goalID, userID)
+ }
+
+ if err := database.DB.Save(&goal).Error; err != nil {
+ log.Printf("Error saving updated goal ID %d for user %d: %v", goalID, userID, err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update goal"})
+ return
+ }
+
+ log.Printf("Goal ID %d updated successfully for user %d", goalID, userID)
+ c.JSON(http.StatusOK, goal)
+}
+
+// UpdateGoalProgress updates just the progress (current amount) of a goal
+func (h *GoalHandler) UpdateGoalProgress(c *gin.Context) {
+ userID := c.MustGet("userID").(uint)
+ goalID, err := strconv.ParseUint(c.Param("id"), 10, 32)
+ if err != nil {
+ log.Printf("Error parsing goal ID for progress update: %v", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid goal ID"})
+ return
+ }
+
+ var goal models.Goal
+ if err := database.DB.Where("id = ? AND user_id = ?", goalID, userID).First(&goal).Error; err != nil {
+ log.Printf("Error fetching goal ID %d for user %d in progress update: %v", goalID, userID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": "Goal not found"})
+ return
+ }
+
+ var input UpdateGoalProgressInput
+ if err := c.ShouldBindJSON(&input); err != nil {
+ log.Printf("Error binding JSON for goal progress update: %v", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
+ return
+ }
+
+ goal.CurrentAmount = input.CurrentAmount
+
+ // Check if goal has been achieved
+ if goal.CurrentAmount >= goal.TargetAmount {
+ goal.Status = "Achieved"
+ log.Printf("Goal ID %d for user %d automatically marked as Achieved during progress update", goalID, userID)
+ }
+
+ if err := database.DB.Save(&goal).Error; err != nil {
+ log.Printf("Error saving progress update for goal ID %d for user %d: %v", goalID, userID, err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update goal progress"})
+ return
+ }
+
+ log.Printf("Goal ID %d progress updated successfully for user %d: %d/%d", goalID, userID, goal.CurrentAmount, goal.TargetAmount)
+ c.JSON(http.StatusOK, goal)
+}
+
+// DeleteGoal deletes a goal belonging to the authenticated user
+func (h *GoalHandler) DeleteGoal(c *gin.Context) {
+ userID := c.MustGet("userID").(uint)
+ goalID, err := strconv.ParseUint(c.Param("id"), 10, 32)
+ if err != nil {
+ log.Printf("Error parsing goal ID for deletion: %v", err)
+ c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid goal ID"})
+ return
+ }
+
+ var goal models.Goal
+ if err := database.DB.Where("id = ? AND user_id = ?", goalID, userID).First(&goal).Error; err != nil {
+ log.Printf("Error fetching goal ID %d for user %d for deletion: %v", goalID, userID, err)
+ c.JSON(http.StatusNotFound, gin.H{"error": "Goal not found"})
+ return
+ }
+
+ if err := database.DB.Delete(&goal).Error; err != nil {
+ log.Printf("Error deleting goal ID %d for user %d: %v", goalID, userID, err)
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to delete goal"})
+ return
+ }
+
+ log.Printf("Goal ID %d deleted successfully for user %d", goalID, userID)
+ c.JSON(http.StatusOK, gin.H{"message": "Goal deleted successfully"})
+}