src/api/crud.go (view raw)
1package api
2
3import (
4 "encoding/json"
5 "errors"
6 "net/http"
7
8 "github.com/birabittoh/go-lift/src/database"
9 "gorm.io/gorm"
10)
11
12type WorkoutStats struct {
13 TotalWorkouts int64 `json:"totalWorkouts"`
14 TotalMinutes int `json:"totalMinutes"`
15 TotalExercises int64 `json:"totalExercises"`
16 MostFrequentExercise *struct {
17 Name string `json:"name"`
18 Count int `json:"count"`
19 } `json:"mostFrequentExercise,omitempty"`
20 MostFrequentRoutine *struct {
21 Name string `json:"name"`
22 Count int `json:"count"`
23 } `json:"mostFrequentRoutine,omitempty"`
24 RecentWorkouts []database.RecordRoutine `json:"recentWorkouts"`
25}
26
27// User handlers
28func getUserHandler(db *gorm.DB) http.HandlerFunc {
29 return func(w http.ResponseWriter, r *http.Request) {
30 id := r.PathValue("id")
31 var user database.User
32 if err := db.First(&user, id).Error; err != nil {
33 if errors.Is(err, gorm.ErrRecordNotFound) {
34 jsonError(w, http.StatusNotFound, "User not found")
35 return
36 }
37 jsonError(w, http.StatusInternalServerError, "Failed to fetch user: "+err.Error())
38 return
39 }
40 jsonResponse(w, http.StatusOK, user)
41 }
42}
43
44func updateUserHandler(db *gorm.DB) http.HandlerFunc {
45 return func(w http.ResponseWriter, r *http.Request) {
46 id := r.PathValue("id")
47 var user database.User
48 if err := db.First(&user, id).Error; err != nil {
49 if errors.Is(err, gorm.ErrRecordNotFound) {
50 jsonError(w, http.StatusNotFound, "User not found")
51 return
52 }
53 jsonError(w, http.StatusInternalServerError, "Failed to fetch user: "+err.Error())
54 return
55 }
56
57 if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
58 jsonError(w, http.StatusBadRequest, "Invalid request body: "+err.Error())
59 return
60 }
61 if err := db.Save(&user).Error; err != nil {
62 jsonError(w, http.StatusInternalServerError, "Failed to update user: "+err.Error())
63 return
64 }
65 jsonResponse(w, http.StatusOK, user)
66 }
67}
68
69// Routines handlers
70func getRoutinesHandler(db *gorm.DB) http.HandlerFunc {
71 return func(w http.ResponseWriter, r *http.Request) {
72 var routines []database.Routine
73 result := db.
74 Preload("RoutineItems").
75 Preload("RoutineItems.Exercises").
76 Preload("RoutineItems.Supersets").
77 Preload("RoutineItems.Supersets.PrimaryExercise").
78 Preload("RoutineItems.Supersets.SecondaryExercise").
79 Find(&routines)
80 if result.Error != nil {
81 jsonError(w, http.StatusInternalServerError, "Failed to fetch routines: "+result.Error.Error())
82 return
83 }
84 jsonResponse(w, http.StatusOK, routines)
85 }
86}
87
88func getRoutineHandler(db *gorm.DB) http.HandlerFunc {
89 return func(w http.ResponseWriter, r *http.Request) {
90 id := r.PathValue("id")
91 var routine database.Routine
92 result := db.
93 Preload("RoutineItems.Exercises").
94 Preload("RoutineItems.Supersets").
95 Preload("RoutineItems.Supersets.PrimaryExercise").
96 Preload("RoutineItems.Supersets.SecondaryExercise").
97 First(&routine, id)
98 if result.Error != nil {
99 if errors.Is(result.Error, gorm.ErrRecordNotFound) {
100 jsonError(w, http.StatusNotFound, "Routine not found")
101 return
102 }
103 jsonError(w, http.StatusInternalServerError, "Failed to fetch routine: "+result.Error.Error())
104 return
105 }
106 jsonResponse(w, http.StatusOK, routine)
107 }
108}
109
110func createRoutineHandler(db *gorm.DB) http.HandlerFunc {
111 return func(w http.ResponseWriter, r *http.Request) {
112 var routine database.Routine
113 if err := json.NewDecoder(r.Body).Decode(&routine); err != nil {
114 jsonError(w, http.StatusBadRequest, "Invalid request body: "+err.Error())
115 return
116 }
117
118 if err := db.Create(&routine).Error; err != nil {
119 jsonError(w, http.StatusInternalServerError, "Failed to create routine: "+err.Error())
120 return
121 }
122
123 // Reload with associations
124 db.Preload("Exercises").Preload("Supersets").Preload("Supersets.Sets").First(&routine, routine.ID)
125
126 jsonResponse(w, http.StatusCreated, routine)
127 }
128}
129
130func updateRoutineHandler(db *gorm.DB) http.HandlerFunc {
131 return func(w http.ResponseWriter, r *http.Request) {
132 id := r.PathValue("id")
133 var routine database.Routine
134
135 // Check if exists
136 if err := db.First(&routine, id).Error; err != nil {
137 if errors.Is(err, gorm.ErrRecordNotFound) {
138 jsonError(w, http.StatusNotFound, "Routine not found")
139 return
140 }
141 jsonError(w, http.StatusInternalServerError, "Database error: "+err.Error())
142 return
143 }
144
145 // Parse update data
146 if err := json.NewDecoder(r.Body).Decode(&routine); err != nil {
147 jsonError(w, http.StatusBadRequest, "Invalid request body: "+err.Error())
148 return
149 }
150
151 // Save with associations
152 if err := db.Session(&gorm.Session{FullSaveAssociations: true}).Save(&routine).Error; err != nil {
153 jsonError(w, http.StatusInternalServerError, "Failed to update routine: "+err.Error())
154 return
155 }
156
157 // Reload complete data
158 db.Preload("Exercises").Preload("Supersets").Preload("Supersets.Sets").First(&routine, id)
159 jsonResponse(w, http.StatusOK, routine)
160 }
161}
162
163func deleteRoutineHandler(db *gorm.DB) http.HandlerFunc {
164 return func(w http.ResponseWriter, r *http.Request) {
165 id := r.PathValue("id")
166 if err := db.Delete(&database.Routine{}, id).Error; err != nil {
167 jsonError(w, http.StatusInternalServerError, "Failed to delete routine: "+err.Error())
168 return
169 }
170 jsonResponse(w, http.StatusOK, map[string]string{"message": "Routine deleted successfully"})
171 }
172}
173
174// Exercises handlers
175func getExercisesHandler(db *gorm.DB) http.HandlerFunc {
176 return func(w http.ResponseWriter, r *http.Request) {
177 var exercises []database.Exercise
178 if err := db.Find(&exercises).Error; err != nil {
179 jsonError(w, http.StatusInternalServerError, "Failed to fetch exercises: "+err.Error())
180 return
181 }
182 jsonResponse(w, http.StatusOK, exercises)
183 }
184}
185
186func getExerciseHandler(db *gorm.DB) http.HandlerFunc {
187 return func(w http.ResponseWriter, r *http.Request) {
188 id := r.PathValue("id")
189 var exercise database.Exercise
190 if err := db.First(&exercise, id).Error; err != nil {
191 if errors.Is(err, gorm.ErrRecordNotFound) {
192 jsonError(w, http.StatusNotFound, "Exercise not found")
193 return
194 }
195 jsonError(w, http.StatusInternalServerError, "Failed to fetch exercise: "+err.Error())
196 return
197 }
198 jsonResponse(w, http.StatusOK, exercise)
199 }
200}
201
202func createExerciseHandler(db *gorm.DB) http.HandlerFunc {
203 return func(w http.ResponseWriter, r *http.Request) {
204 var exercise database.Exercise
205 if err := json.NewDecoder(r.Body).Decode(&exercise); err != nil {
206 jsonError(w, http.StatusBadRequest, "Invalid request body: "+err.Error())
207 return
208 }
209
210 if err := db.Create(&exercise).Error; err != nil {
211 jsonError(w, http.StatusInternalServerError, "Failed to create exercise: "+err.Error())
212 return
213 }
214
215 jsonResponse(w, http.StatusCreated, exercise)
216 }
217}
218
219func upsertExercisesHandler(dbStruct *database.Database) http.HandlerFunc {
220 return func(w http.ResponseWriter, r *http.Request) {
221 if err := dbStruct.UpdateExercises(); err != nil {
222 jsonError(w, http.StatusInternalServerError, "Failed to update exercises: "+err.Error())
223 return
224 }
225
226 jsonResponse(w, http.StatusOK, map[string]string{"message": "Exercises updated successfully"})
227 }
228}
229
230func updateExerciseHandler(db *gorm.DB) http.HandlerFunc {
231 return func(w http.ResponseWriter, r *http.Request) {
232 id := r.PathValue("id")
233
234 // Verify exercise exists
235 var exercise database.Exercise
236 if err := db.First(&exercise, id).Error; err != nil {
237 if errors.Is(err, gorm.ErrRecordNotFound) {
238 jsonError(w, http.StatusNotFound, "Exercise not found")
239 return
240 }
241 jsonError(w, http.StatusInternalServerError, "Database error: "+err.Error())
242 return
243 }
244
245 // Parse update data
246 if err := json.NewDecoder(r.Body).Decode(&exercise); err != nil {
247 jsonError(w, http.StatusBadRequest, "Invalid request body: "+err.Error())
248 return
249 }
250
251 if err := db.Save(&exercise).Error; err != nil {
252 jsonError(w, http.StatusInternalServerError, "Failed to update exercise: "+err.Error())
253 return
254 }
255
256 jsonResponse(w, http.StatusOK, exercise)
257 }
258}
259
260func deleteExerciseHandler(db *gorm.DB) http.HandlerFunc {
261 return func(w http.ResponseWriter, r *http.Request) {
262 id := r.PathValue("id")
263 if err := db.Delete(&database.Exercise{}, id).Error; err != nil {
264 jsonError(w, http.StatusInternalServerError, "Failed to delete exercise: "+err.Error())
265 return
266 }
267 jsonResponse(w, http.StatusOK, map[string]string{"message": "Exercise deleted successfully"})
268 }
269}
270
271// RecordRoutines handlers
272func getRecordRoutinesHandler(db *gorm.DB) http.HandlerFunc {
273 return func(w http.ResponseWriter, r *http.Request) {
274 var records []database.RecordRoutine
275 result := db.Preload("RecordExercises").Preload("Routine.Exercises").Preload("Routine.Supersets").Find(&records)
276 if result.Error != nil {
277 jsonError(w, http.StatusInternalServerError, "Failed to fetch record routines: "+result.Error.Error())
278 return
279 }
280 jsonResponse(w, http.StatusOK, records)
281 }
282}
283
284func getRecordRoutineHandler(db *gorm.DB) http.HandlerFunc {
285 return func(w http.ResponseWriter, r *http.Request) {
286 id := r.PathValue("id")
287 var record database.RecordRoutine
288 result := db.Preload("Routine").Preload("Routine.Exercises").Preload("Routine.Supersets").First(&record, id)
289 if result.Error != nil {
290 if errors.Is(result.Error, gorm.ErrRecordNotFound) {
291 jsonError(w, http.StatusNotFound, "Record routine not found")
292 return
293 }
294 jsonError(w, http.StatusInternalServerError, "Failed to fetch record routine: "+result.Error.Error())
295 return
296 }
297 jsonResponse(w, http.StatusOK, record)
298 }
299}
300
301func createRecordRoutineHandler(db *gorm.DB) http.HandlerFunc {
302 return func(w http.ResponseWriter, r *http.Request) {
303 var record database.RecordRoutine
304 if err := json.NewDecoder(r.Body).Decode(&record); err != nil {
305 jsonError(w, http.StatusBadRequest, "Invalid request body: "+err.Error())
306 return
307 }
308
309 if err := db.Create(&record).Error; err != nil {
310 jsonError(w, http.StatusInternalServerError, "Failed to create record routine: "+err.Error())
311 return
312 }
313
314 // Reload with associations
315 db.Preload("Routine").Preload("Routine.Exercises").Preload("Routine.Supersets").First(&record, record.ID)
316
317 jsonResponse(w, http.StatusCreated, record)
318 }
319}
320
321func updateRecordRoutineHandler(db *gorm.DB) http.HandlerFunc {
322 return func(w http.ResponseWriter, r *http.Request) {
323 id := r.PathValue("id")
324 var record database.RecordRoutine
325
326 // Check if exists
327 if err := db.First(&record, id).Error; err != nil {
328 if errors.Is(err, gorm.ErrRecordNotFound) {
329 jsonError(w, http.StatusNotFound, "Record routine not found")
330 return
331 }
332 jsonError(w, http.StatusInternalServerError, "Database error: "+err.Error())
333 return
334 }
335
336 // Parse update data
337 if err := json.NewDecoder(r.Body).Decode(&record); err != nil {
338 jsonError(w, http.StatusBadRequest, "Invalid request body: "+err.Error())
339 return
340 }
341
342 // Save with associations
343 if err := db.Session(&gorm.Session{FullSaveAssociations: true}).Save(&record).Error; err != nil {
344 jsonError(w, http.StatusInternalServerError, "Failed to update record routine: "+err.Error())
345 return
346 }
347
348 // Reload complete data
349 db.Preload("Routine").Preload("Routine.Exercises").Preload("Routine.Supersets").First(&record, id)
350 jsonResponse(w, http.StatusOK, record)
351 }
352}
353
354func deleteRecordRoutineHandler(db *gorm.DB) http.HandlerFunc {
355 return func(w http.ResponseWriter, r *http.Request) {
356 id := r.PathValue("id")
357 if err := db.Delete(&database.RecordRoutine{}, id).Error; err != nil {
358 jsonError(w, http.StatusInternalServerError, "Failed to delete record routine: "+err.Error())
359 return
360 }
361 jsonResponse(w, http.StatusOK, map[string]string{"message": "Record routine deleted successfully"})
362 }
363}
364
365func getStatsHandler(db *gorm.DB) http.HandlerFunc {
366 return func(w http.ResponseWriter, r *http.Request) {
367 stats := WorkoutStats{}
368
369 // Get total workouts
370 if err := db.Model(&database.RecordRoutine{}).Count(&stats.TotalWorkouts).Error; err != nil {
371 jsonError(w, http.StatusInternalServerError, "Failed to count workouts: "+err.Error())
372 return
373 }
374
375 // Get total minutes
376 stats.TotalMinutes = 0
377
378 // Get total exercises
379 if err := db.Model(&database.RecordExercise{}).Count(&stats.TotalExercises).Error; err != nil {
380 jsonError(w, http.StatusInternalServerError, "Failed to count exercises: "+err.Error())
381 return
382 }
383
384 // Get most frequent exercise
385 var mostFrequentExercise struct {
386 Name string `gorm:"column:name"`
387 Count int `gorm:"column:count"`
388 }
389 exerciseQuery := db.Model(&database.RecordExercise{}).
390 Select("exercises.name, COUNT(*) as count").
391 Joins("JOIN exercises ON record_exercises.exercise_id = exercises.id").
392 Group("exercises.name").
393 Order("count DESC").
394 Limit(1)
395
396 if err := exerciseQuery.Scan(&mostFrequentExercise).Error; err == nil && mostFrequentExercise.Name != "" {
397 stats.MostFrequentExercise = &struct {
398 Name string `json:"name"`
399 Count int `json:"count"`
400 }{
401 Name: mostFrequentExercise.Name,
402 Count: mostFrequentExercise.Count,
403 }
404 }
405
406 // Get most frequent routine
407 var mostFrequentRoutine struct {
408 Name string `gorm:"column:name"`
409 Count int `gorm:"column:count"`
410 }
411 routineQuery := db.Model(&database.RecordRoutine{}).
412 Select("routines.name, COUNT(*) as count").
413 Joins("JOIN routines ON record_routines.routine_id = routines.id").
414 Group("routines.name").
415 Order("count DESC").
416 Limit(1)
417
418 if err := routineQuery.Scan(&mostFrequentRoutine).Error; err == nil && mostFrequentRoutine.Name != "" {
419 stats.MostFrequentRoutine = &struct {
420 Name string `json:"name"`
421 Count int `json:"count"`
422 }{
423 Name: mostFrequentRoutine.Name,
424 Count: mostFrequentRoutine.Count,
425 }
426 }
427
428 // Get recent workouts (last 5)
429 if err := db.
430 Preload("RecordRoutineItems").
431 Preload("Routine").
432 Order("created_at DESC").
433 Limit(5).
434 Find(&stats.RecentWorkouts).Error; err != nil {
435 jsonError(w, http.StatusInternalServerError, "Failed to fetch recent workouts: "+err.Error())
436 return
437 }
438
439 jsonResponse(w, http.StatusOK, stats)
440 }
441}