aboutsummaryrefslogtreecommitdiffstats
path: root/backend
diff options
context:
space:
mode:
Diffstat (limited to 'backend')
-rw-r--r--backend/cmd/api/main.go12
-rw-r--r--backend/handlers/goal_handler.go243
-rw-r--r--backend/internal/api/v1/goals/goals.go43
3 files changed, 298 insertions, 0 deletions
diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go
index fd5a0ea..e1d0824 100644
--- a/backend/cmd/api/main.go
+++ b/backend/cmd/api/main.go
@@ -7,6 +7,7 @@ import (
"finance/backend/internal/api/auth"
"finance/backend/internal/api/v1/accounts"
+ "finance/backend/internal/api/v1/goals"
"finance/backend/internal/api/v1/loans"
"finance/backend/internal/api/v1/transactions"
"finance/backend/internal/config"
@@ -121,6 +122,17 @@ func main() {
transactionRoutes.DELETE("/:id", transactions.DeleteTransaction())
}
+ // Goal routes
+ goalRoutes := protected.Group("/goals")
+ {
+ goalRoutes.GET("", goals.GetGoals())
+ goalRoutes.GET("/:id", goals.GetGoalByID())
+ goalRoutes.POST("", goals.CreateGoal())
+ goalRoutes.PUT("/:id", goals.UpdateGoal())
+ goalRoutes.DELETE("/:id", goals.DeleteGoal())
+ goalRoutes.PATCH("/:id/progress", goals.UpdateGoalProgress()) // Specific endpoint for updating progress
+ }
+
// Loan routes
loanRoutes := protected.Group("/loans")
{
diff --git a/backend/handlers/goal_handler.go b/backend/handlers/goal_handler.go
new file mode 100644
index 0000000..dd2791c
--- /dev/null
+++ b/backend/handlers/goal_handler.go
@@ -0,0 +1,243 @@
+package handlers
+
+import (
+ "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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ 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 {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create goal"})
+ return
+ }
+
+ 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 {
+ 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 {
+ c.JSON(http.StatusNotFound, gin.H{"error": "Goal not found"})
+ return
+ }
+
+ var input UpdateGoalInput
+ if err := c.ShouldBindJSON(&input); err != nil {
+ 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 {
+ 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"
+ }
+
+ if err := database.DB.Save(&goal).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update goal"})
+ return
+ }
+
+ 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 {
+ 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 {
+ c.JSON(http.StatusNotFound, gin.H{"error": "Goal not found"})
+ return
+ }
+
+ var input UpdateGoalProgressInput
+ if err := c.ShouldBindJSON(&input); err != nil {
+ 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"
+ }
+
+ if err := database.DB.Save(&goal).Error; err != nil {
+ c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update goal progress"})
+ return
+ }
+
+ 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 {
+ 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 {
+ c.JSON(http.StatusNotFound, gin.H{"error": "Goal not found"})
+ return
+ }
+
+ if err := database.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"})
+}
diff --git a/backend/internal/api/v1/goals/goals.go b/backend/internal/api/v1/goals/goals.go
new file mode 100644
index 0000000..1d2cd6f
--- /dev/null
+++ b/backend/internal/api/v1/goals/goals.go
@@ -0,0 +1,43 @@
+package goals
+
+import (
+ "finance/backend/handlers"
+
+ "github.com/gin-gonic/gin"
+)
+
+// GetGoals returns all goals for the authenticated user
+func GetGoals() gin.HandlerFunc {
+ handler := handlers.NewGoalHandler()
+ return handler.GetGoals
+}
+
+// GetGoalByID returns a specific goal by ID
+func GetGoalByID() gin.HandlerFunc {
+ handler := handlers.NewGoalHandler()
+ return handler.GetGoal
+}
+
+// CreateGoal creates a new financial goal
+func CreateGoal() gin.HandlerFunc {
+ handler := handlers.NewGoalHandler()
+ return handler.CreateGoal
+}
+
+// UpdateGoal updates an existing goal
+func UpdateGoal() gin.HandlerFunc {
+ handler := handlers.NewGoalHandler()
+ return handler.UpdateGoal
+}
+
+// DeleteGoal deletes a goal
+func DeleteGoal() gin.HandlerFunc {
+ handler := handlers.NewGoalHandler()
+ return handler.DeleteGoal
+}
+
+// UpdateGoalProgress updates just the progress (current amount) of a goal
+func UpdateGoalProgress() gin.HandlerFunc {
+ handler := handlers.NewGoalHandler()
+ return handler.UpdateGoalProgress
+}