1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
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"})
}
|