package handlers import ( "log" "net/http" "strconv" "time" "finance/backend/internal/database" "finance/backend/internal/models" "github.com/gin-gonic/gin" ) 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"` } 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"` } type UpdateGoalProgressInput struct { CurrentAmount int64 `json:"currentAmount" binding:"required"` } // GoalHandler handles goal-related operations type GoalHandler struct { } // NewGoalHandler creates a new goal handler func NewGoalHandler() *GoalHandler { return &GoalHandler{} } // GetGoals gets all goals for the current user 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 gets a specific goal by ID 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 goal for the current 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 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 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"}) }