src/api/crud.go (view raw)
1package api
2
3import (
4 "encoding/json"
5 "net/http"
6 "strconv"
7
8 "github.com/birabittoh/go-lift/src/database"
9 "gorm.io/gorm"
10)
11
12// User handlers
13func getUserHandler(db *gorm.DB) http.HandlerFunc {
14 return func(w http.ResponseWriter, r *http.Request) {
15 id, err := getIDFromPath(r)
16 if err != nil {
17 jsonError(w, http.StatusBadRequest, "Invalid user ID")
18 return
19 }
20
21 var user database.User
22 if err := db.First(&user, id).Error; err != nil {
23 if err == gorm.ErrRecordNotFound {
24 jsonError(w, http.StatusNotFound, "User not found")
25 return
26 }
27 jsonError(w, http.StatusInternalServerError, "Database error")
28 return
29 }
30
31 jsonResponse(w, http.StatusOK, user)
32 }
33}
34
35func updateUserHandler(db *gorm.DB) http.HandlerFunc {
36 return func(w http.ResponseWriter, r *http.Request) {
37 id, err := getIDFromPath(r)
38 if err != nil {
39 jsonError(w, http.StatusBadRequest, "Invalid user ID")
40 return
41 }
42
43 var user database.User
44 if err := db.First(&user, id).Error; err != nil {
45 if err == gorm.ErrRecordNotFound {
46 jsonError(w, http.StatusNotFound, "User not found")
47 return
48 }
49 jsonError(w, http.StatusInternalServerError, "Database error")
50 return
51 }
52
53 var updateData database.User
54 if err := json.NewDecoder(r.Body).Decode(&updateData); err != nil {
55 jsonError(w, http.StatusBadRequest, "Invalid JSON")
56 return
57 }
58
59 // Update specific fields
60 user.Name = updateData.Name
61 user.IsFemale = updateData.IsFemale
62 user.Height = updateData.Height
63 user.Weight = updateData.Weight
64 user.BirthDate = updateData.BirthDate
65
66 if err := db.Save(&user).Error; err != nil {
67 jsonError(w, http.StatusInternalServerError, "Failed to update user")
68 return
69 }
70
71 jsonResponse(w, http.StatusOK, user)
72 }
73}
74
75// Exercise handlers (read-only)
76func getExercisesHandler(db *gorm.DB) http.HandlerFunc {
77 return func(w http.ResponseWriter, r *http.Request) {
78 var exercises []database.Exercise
79 query := db.Preload("PrimaryMuscles").Preload("SecondaryMuscles")
80
81 // Optional filtering
82 if category := r.URL.Query().Get("category"); category != "" {
83 query = query.Where("category = ?", category)
84 }
85 if level := r.URL.Query().Get("level"); level != "" {
86 query = query.Where("level = ?", level)
87 }
88
89 if err := query.Find(&exercises).Error; err != nil {
90 jsonError(w, http.StatusInternalServerError, "Database error")
91 return
92 }
93
94 jsonResponse(w, http.StatusOK, exercises)
95 }
96}
97
98func getExerciseHandler(db *gorm.DB) http.HandlerFunc {
99 return func(w http.ResponseWriter, r *http.Request) {
100 id, err := getIDFromPath(r)
101 if err != nil {
102 jsonError(w, http.StatusBadRequest, "Invalid exercise ID")
103 return
104 }
105
106 var exercise database.Exercise
107 if err := db.Preload("PrimaryMuscles").Preload("SecondaryMuscles").First(&exercise, id).Error; err != nil {
108 if err == gorm.ErrRecordNotFound {
109 jsonError(w, http.StatusNotFound, "Exercise not found")
110 return
111 }
112 jsonError(w, http.StatusInternalServerError, "Database error")
113 return
114 }
115
116 jsonResponse(w, http.StatusOK, exercise)
117 }
118}
119
120// Muscle handlers (read-only)
121func getMusclesHandler(db *gorm.DB) http.HandlerFunc {
122 return func(w http.ResponseWriter, r *http.Request) {
123 var muscles []database.Muscle
124 if err := db.Find(&muscles).Error; err != nil {
125 jsonError(w, http.StatusInternalServerError, "Database error")
126 return
127 }
128
129 jsonResponse(w, http.StatusOK, muscles)
130 }
131}
132
133// Routine handlers
134func getRoutinesHandler(db *gorm.DB) http.HandlerFunc {
135 return func(w http.ResponseWriter, r *http.Request) {
136 var routines []database.Routine
137 if err := db.Find(&routines).Error; err != nil {
138 jsonError(w, http.StatusInternalServerError, "Database error")
139 return
140 }
141
142 jsonResponse(w, http.StatusOK, routines)
143 }
144}
145
146func getRoutineHandler(db *gorm.DB) http.HandlerFunc {
147 return func(w http.ResponseWriter, r *http.Request) {
148 id, err := getIDFromPath(r)
149 if err != nil {
150 jsonError(w, http.StatusBadRequest, "Invalid routine ID")
151 return
152 }
153
154 var routine database.Routine
155 if err := db.Preload("Items.ExerciseItems.Exercise.PrimaryMuscles").
156 Preload("Items.ExerciseItems.Exercise.SecondaryMuscles").
157 Preload("Items.ExerciseItems.Sets").
158 Order("Items.order_index, Items.ExerciseItems.order_index, Items.ExerciseItems.Sets.order_index").
159 First(&routine, id).Error; err != nil {
160 if err == gorm.ErrRecordNotFound {
161 jsonError(w, http.StatusNotFound, "Routine not found")
162 return
163 }
164 jsonError(w, http.StatusInternalServerError, "Database error")
165 return
166 }
167
168 jsonResponse(w, http.StatusOK, routine)
169 }
170}
171
172func createRoutineHandler(db *gorm.DB) http.HandlerFunc {
173 return func(w http.ResponseWriter, r *http.Request) {
174 var routine database.Routine
175 if err := json.NewDecoder(r.Body).Decode(&routine); err != nil {
176 jsonError(w, http.StatusBadRequest, "Invalid JSON")
177 return
178 }
179
180 if err := db.Create(&routine).Error; err != nil {
181 jsonError(w, http.StatusInternalServerError, "Failed to create routine")
182 return
183 }
184
185 jsonResponse(w, http.StatusCreated, routine)
186 }
187}
188
189func updateRoutineHandler(db *gorm.DB) http.HandlerFunc {
190 return func(w http.ResponseWriter, r *http.Request) {
191 id, err := getIDFromPath(r)
192 if err != nil {
193 jsonError(w, http.StatusBadRequest, "Invalid routine ID")
194 return
195 }
196
197 var routine database.Routine
198 if err := json.NewDecoder(r.Body).Decode(&routine); err != nil {
199 jsonError(w, http.StatusBadRequest, "Invalid JSON")
200 return
201 }
202
203 routine.ID = uint(id)
204 if err := db.Save(&routine).Error; err != nil {
205 jsonError(w, http.StatusInternalServerError, "Failed to update routine")
206 return
207 }
208
209 jsonResponse(w, http.StatusOK, routine)
210 }
211}
212
213func deleteRoutineHandler(db *gorm.DB) http.HandlerFunc {
214 return func(w http.ResponseWriter, r *http.Request) {
215 id, err := getIDFromPath(r)
216 if err != nil {
217 jsonError(w, http.StatusBadRequest, "Invalid routine ID")
218 return
219 }
220
221 if err := db.Delete(&database.Routine{}, id).Error; err != nil {
222 jsonError(w, http.StatusInternalServerError, "Failed to delete routine")
223 return
224 }
225
226 jsonResponse(w, http.StatusOK, map[string]string{"message": "Routine deleted"})
227 }
228}
229
230// Record routine handlers (workout sessions)
231func getRecordRoutinesHandler(db *gorm.DB) http.HandlerFunc {
232 return func(w http.ResponseWriter, r *http.Request) {
233 var records []database.RecordRoutine
234 query := db.Preload("Routine").Order("created_at DESC")
235
236 // Optional limit for recent workouts
237 if limit := r.URL.Query().Get("limit"); limit != "" {
238 if l, err := strconv.Atoi(limit); err == nil {
239 query = query.Limit(l)
240 }
241 }
242
243 if err := query.Find(&records).Error; err != nil {
244 jsonError(w, http.StatusInternalServerError, "Database error")
245 return
246 }
247
248 jsonResponse(w, http.StatusOK, records)
249 }
250}
251
252func getRecordRoutineHandler(db *gorm.DB) http.HandlerFunc {
253 return func(w http.ResponseWriter, r *http.Request) {
254 id, err := getIDFromPath(r)
255 if err != nil {
256 jsonError(w, http.StatusBadRequest, "Invalid record ID")
257 return
258 }
259
260 var record database.RecordRoutine
261 if err := db.Preload("Routine").
262 Preload("RecordItems.RoutineItem").
263 Preload("RecordItems.RecordExerciseItems.ExerciseItem.Exercise.PrimaryMuscles").
264 Preload("RecordItems.RecordExerciseItems.ExerciseItem.Exercise.SecondaryMuscles").
265 Preload("RecordItems.RecordExerciseItems.RecordSets.Set").
266 Order("RecordItems.order_index, RecordItems.RecordExerciseItems.order_index, RecordItems.RecordExerciseItems.RecordSets.order_index").
267 First(&record, id).Error; err != nil {
268 if err == gorm.ErrRecordNotFound {
269 jsonError(w, http.StatusNotFound, "Record not found")
270 return
271 }
272 jsonError(w, http.StatusInternalServerError, "Database error")
273 return
274 }
275
276 jsonResponse(w, http.StatusOK, record)
277 }
278}
279
280func createRecordRoutineHandler(db *gorm.DB) http.HandlerFunc {
281 return func(w http.ResponseWriter, r *http.Request) {
282 var record database.RecordRoutine
283 if err := json.NewDecoder(r.Body).Decode(&record); err != nil {
284 jsonError(w, http.StatusBadRequest, "Invalid JSON")
285 return
286 }
287
288 if err := db.Create(&record).Error; err != nil {
289 jsonError(w, http.StatusInternalServerError, "Failed to create record")
290 return
291 }
292
293 jsonResponse(w, http.StatusCreated, record)
294 }
295}
296
297func updateRecordRoutineHandler(db *gorm.DB) http.HandlerFunc {
298 return func(w http.ResponseWriter, r *http.Request) {
299 id, err := getIDFromPath(r)
300 if err != nil {
301 jsonError(w, http.StatusBadRequest, "Invalid record ID")
302 return
303 }
304
305 var record database.RecordRoutine
306 if err := json.NewDecoder(r.Body).Decode(&record); err != nil {
307 jsonError(w, http.StatusBadRequest, "Invalid JSON")
308 return
309 }
310
311 record.ID = uint(id)
312 if err := db.Save(&record).Error; err != nil {
313 jsonError(w, http.StatusInternalServerError, "Failed to update record")
314 return
315 }
316
317 jsonResponse(w, http.StatusOK, record)
318 }
319}
320
321func deleteRecordRoutineHandler(db *gorm.DB) http.HandlerFunc {
322 return func(w http.ResponseWriter, r *http.Request) {
323 id, err := getIDFromPath(r)
324 if err != nil {
325 jsonError(w, http.StatusBadRequest, "Invalid record ID")
326 return
327 }
328
329 if err := db.Delete(&database.RecordRoutine{}, id).Error; err != nil {
330 jsonError(w, http.StatusInternalServerError, "Failed to delete record")
331 return
332 }
333
334 jsonResponse(w, http.StatusOK, map[string]string{"message": "Record deleted"})
335 }
336}
337
338// Stats handler
339func getStatsHandler(db *gorm.DB) http.HandlerFunc {
340 return func(w http.ResponseWriter, r *http.Request) {
341 stats := WorkoutStats{}
342
343 // Total workouts
344 db.Model(&database.RecordRoutine{}).Count(&stats.TotalWorkouts)
345
346 // Total minutes (sum of all workout durations)
347 var totalSeconds uint
348 db.Model(&database.RecordRoutine{}).Select("COALESCE(SUM(duration), 0)").Scan(&totalSeconds)
349 stats.TotalMinutes = int(totalSeconds / 60)
350
351 // Total exercises completed
352 db.Model(&database.RecordExerciseItem{}).Count(&stats.TotalExercises)
353
354 // Most frequent exercise
355 var exerciseStats struct {
356 ExerciseName string `json:"exercise_name"`
357 Count int `json:"count"`
358 }
359 db.Raw(`
360 SELECT e.name as exercise_name, COUNT(*) as count
361 FROM record_exercise_items rei
362 JOIN exercise_items ei ON rei.exercise_item_id = ei.id
363 JOIN exercises e ON ei.exercise_id = e.id
364 GROUP BY e.id, e.name
365 ORDER BY count DESC
366 LIMIT 1
367 `).Scan(&exerciseStats)
368
369 if exerciseStats.Count > 0 {
370 stats.MostFrequentExercise = &struct {
371 Name string `json:"name"`
372 Count int `json:"count"`
373 }{
374 Name: exerciseStats.ExerciseName,
375 Count: exerciseStats.Count,
376 }
377 }
378
379 // Most frequent routine
380 var routineStats struct {
381 RoutineName string `json:"routine_name"`
382 Count int `json:"count"`
383 }
384 db.Raw(`
385 SELECT r.name as routine_name, COUNT(*) as count
386 FROM record_routines rr
387 JOIN routines r ON rr.routine_id = r.id
388 GROUP BY r.id, r.name
389 ORDER BY count DESC
390 LIMIT 1
391 `).Scan(&routineStats)
392
393 if routineStats.Count > 0 {
394 stats.MostFrequentRoutine = &struct {
395 Name string `json:"name"`
396 Count int `json:"count"`
397 }{
398 Name: routineStats.RoutineName,
399 Count: routineStats.Count,
400 }
401 }
402
403 // Recent workouts (last 5)
404 db.Preload("Routine").
405 Order("created_at DESC").
406 Limit(5).
407 Find(&stats.RecentWorkouts)
408
409 jsonResponse(w, http.StatusOK, stats)
410 }
411}