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.Model(&database.Exercise{})
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.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// Routine handlers
121func getRoutinesHandler(db *gorm.DB) http.HandlerFunc {
122 return func(w http.ResponseWriter, r *http.Request) {
123 var routines []database.Routine
124 if err := db.Find(&routines).Error; err != nil {
125 jsonError(w, http.StatusInternalServerError, "Database error")
126 return
127 }
128
129 jsonResponse(w, http.StatusOK, routines)
130 }
131}
132
133func getRoutineHandler(db *gorm.DB) http.HandlerFunc {
134 return func(w http.ResponseWriter, r *http.Request) {
135 id, err := getIDFromPath(r)
136 if err != nil {
137 jsonError(w, http.StatusBadRequest, "Invalid routine ID")
138 return
139 }
140
141 var routine database.Routine
142 if err := db.Preload("Items.ExerciseItems.Exercise").
143 Preload("Items.ExerciseItems.Sets").
144 Order("Items.order_index, Items.ExerciseItems.order_index, Items.ExerciseItems.Sets.order_index").
145 First(&routine, id).Error; err != nil {
146 if err == gorm.ErrRecordNotFound {
147 jsonError(w, http.StatusNotFound, "Routine not found")
148 return
149 }
150 jsonError(w, http.StatusInternalServerError, "Database error")
151 return
152 }
153
154 jsonResponse(w, http.StatusOK, routine)
155 }
156}
157
158func createRoutineHandler(db *gorm.DB) http.HandlerFunc {
159 return func(w http.ResponseWriter, r *http.Request) {
160 var routine database.Routine
161 if err := json.NewDecoder(r.Body).Decode(&routine); err != nil {
162 jsonError(w, http.StatusBadRequest, "Invalid JSON")
163 return
164 }
165
166 if err := db.Create(&routine).Error; err != nil {
167 jsonError(w, http.StatusInternalServerError, "Failed to create routine")
168 return
169 }
170
171 jsonResponse(w, http.StatusCreated, routine)
172 }
173}
174
175func updateRoutineHandler(db *gorm.DB) http.HandlerFunc {
176 return func(w http.ResponseWriter, r *http.Request) {
177 id, err := getIDFromPath(r)
178 if err != nil {
179 jsonError(w, http.StatusBadRequest, "Invalid routine ID")
180 return
181 }
182
183 var routine database.Routine
184 if err := json.NewDecoder(r.Body).Decode(&routine); err != nil {
185 jsonError(w, http.StatusBadRequest, "Invalid JSON")
186 return
187 }
188
189 routine.ID = uint(id)
190 if err := db.Save(&routine).Error; err != nil {
191 jsonError(w, http.StatusInternalServerError, "Failed to update routine")
192 return
193 }
194
195 jsonResponse(w, http.StatusOK, routine)
196 }
197}
198
199func deleteRoutineHandler(db *gorm.DB) http.HandlerFunc {
200 return func(w http.ResponseWriter, r *http.Request) {
201 id, err := getIDFromPath(r)
202 if err != nil {
203 jsonError(w, http.StatusBadRequest, "Invalid routine ID")
204 return
205 }
206
207 if err := db.Delete(&database.Routine{}, id).Error; err != nil {
208 jsonError(w, http.StatusInternalServerError, "Failed to delete routine")
209 return
210 }
211
212 jsonResponse(w, http.StatusOK, map[string]string{"message": "Routine deleted"})
213 }
214}
215
216// Record routine handlers (workout sessions)
217func getRecordRoutinesHandler(db *gorm.DB) http.HandlerFunc {
218 return func(w http.ResponseWriter, r *http.Request) {
219 var records []database.RecordRoutine
220 query := db.Preload("Routine").Order("created_at DESC")
221
222 // Optional limit for recent workouts
223 if limit := r.URL.Query().Get("limit"); limit != "" {
224 if l, err := strconv.Atoi(limit); err == nil {
225 query = query.Limit(l)
226 }
227 }
228
229 if err := query.Find(&records).Error; err != nil {
230 jsonError(w, http.StatusInternalServerError, "Database error")
231 return
232 }
233
234 jsonResponse(w, http.StatusOK, records)
235 }
236}
237
238func getRecordRoutineHandler(db *gorm.DB) http.HandlerFunc {
239 return func(w http.ResponseWriter, r *http.Request) {
240 id, err := getIDFromPath(r)
241 if err != nil {
242 jsonError(w, http.StatusBadRequest, "Invalid record ID")
243 return
244 }
245
246 var record database.RecordRoutine
247 if err := db.Preload("Routine").
248 Preload("RecordItems.RoutineItem").
249 Preload("RecordItems.RecordExerciseItems.ExerciseItem.Exercise").
250 Preload("RecordItems.RecordExerciseItems.RecordSets.Set").
251 Order("RecordItems.order_index, RecordItems.RecordExerciseItems.order_index, RecordItems.RecordExerciseItems.RecordSets.order_index").
252 First(&record, id).Error; err != nil {
253 if err == gorm.ErrRecordNotFound {
254 jsonError(w, http.StatusNotFound, "Record not found")
255 return
256 }
257 jsonError(w, http.StatusInternalServerError, "Database error")
258 return
259 }
260
261 jsonResponse(w, http.StatusOK, record)
262 }
263}
264
265func createRecordRoutineHandler(db *gorm.DB) http.HandlerFunc {
266 return func(w http.ResponseWriter, r *http.Request) {
267 var record database.RecordRoutine
268 if err := json.NewDecoder(r.Body).Decode(&record); err != nil {
269 jsonError(w, http.StatusBadRequest, "Invalid JSON")
270 return
271 }
272
273 if err := db.Create(&record).Error; err != nil {
274 jsonError(w, http.StatusInternalServerError, "Failed to create record")
275 return
276 }
277
278 jsonResponse(w, http.StatusCreated, record)
279 }
280}
281
282func updateRecordRoutineHandler(db *gorm.DB) http.HandlerFunc {
283 return func(w http.ResponseWriter, r *http.Request) {
284 id, err := getIDFromPath(r)
285 if err != nil {
286 jsonError(w, http.StatusBadRequest, "Invalid record ID")
287 return
288 }
289
290 var record database.RecordRoutine
291 if err := json.NewDecoder(r.Body).Decode(&record); err != nil {
292 jsonError(w, http.StatusBadRequest, "Invalid JSON")
293 return
294 }
295
296 record.ID = uint(id)
297 if err := db.Save(&record).Error; err != nil {
298 jsonError(w, http.StatusInternalServerError, "Failed to update record")
299 return
300 }
301
302 jsonResponse(w, http.StatusOK, record)
303 }
304}
305
306func deleteRecordRoutineHandler(db *gorm.DB) http.HandlerFunc {
307 return func(w http.ResponseWriter, r *http.Request) {
308 id, err := getIDFromPath(r)
309 if err != nil {
310 jsonError(w, http.StatusBadRequest, "Invalid record ID")
311 return
312 }
313
314 if err := db.Delete(&database.RecordRoutine{}, id).Error; err != nil {
315 jsonError(w, http.StatusInternalServerError, "Failed to delete record")
316 return
317 }
318
319 jsonResponse(w, http.StatusOK, map[string]string{"message": "Record deleted"})
320 }
321}
322
323// Stats handler
324func getStatsHandler(db *gorm.DB) http.HandlerFunc {
325 return func(w http.ResponseWriter, r *http.Request) {
326 stats := WorkoutStats{}
327
328 // Total workouts
329 db.Model(&database.RecordRoutine{}).Count(&stats.TotalWorkouts)
330
331 // Total minutes (sum of all workout durations)
332 var totalSeconds uint
333 db.Model(&database.RecordRoutine{}).Select("COALESCE(SUM(duration), 0)").Scan(&totalSeconds)
334 stats.TotalMinutes = int(totalSeconds / 60)
335
336 // Total exercises completed
337 db.Model(&database.RecordExerciseItem{}).Count(&stats.TotalExercises)
338
339 // Most frequent exercise
340 var exerciseStats struct {
341 ExerciseName string `json:"exercise_name"`
342 Count int `json:"count"`
343 }
344 db.Raw(`
345 SELECT e.name as exercise_name, COUNT(*) as count
346 FROM record_exercise_items rei
347 JOIN exercise_items ei ON rei.exercise_item_id = ei.id
348 JOIN exercises e ON ei.exercise_id = e.id
349 GROUP BY e.id, e.name
350 ORDER BY count DESC
351 LIMIT 1
352 `).Scan(&exerciseStats)
353
354 if exerciseStats.Count > 0 {
355 stats.MostFrequentExercise = &struct {
356 Name string `json:"name"`
357 Count int `json:"count"`
358 }{
359 Name: exerciseStats.ExerciseName,
360 Count: exerciseStats.Count,
361 }
362 }
363
364 // Most frequent routine
365 var routineStats struct {
366 RoutineName string `json:"routine_name"`
367 Count int `json:"count"`
368 }
369 db.Raw(`
370 SELECT r.name as routine_name, COUNT(*) as count
371 FROM record_routines rr
372 JOIN routines r ON rr.routine_id = r.id
373 GROUP BY r.id, r.name
374 ORDER BY count DESC
375 LIMIT 1
376 `).Scan(&routineStats)
377
378 if routineStats.Count > 0 {
379 stats.MostFrequentRoutine = &struct {
380 Name string `json:"name"`
381 Count int `json:"count"`
382 }{
383 Name: routineStats.RoutineName,
384 Count: routineStats.Count,
385 }
386 }
387
388 // Recent workouts (last 5)
389 db.Preload("Routine").
390 Order("created_at DESC").
391 Limit(5).
392 Find(&stats.RecentWorkouts)
393
394 jsonResponse(w, http.StatusOK, stats)
395 }
396}