package api import ( "encoding/json" "net/http" "sort" "strconv" "github.com/go-chi/chi/v5" "smartrun/backend/internal/store" ) func (s *Server) handleReviewQueue(w http.ResponseWriter, r *http.Request) { queue, err := s.DB.ReviewQueue(r.Context()) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } type item struct { store.KindAssignment Activity store.Activity `json:"activity"` Laps []store.Lap `json:"laps"` } items := make([]item, 0, len(queue)) for _, a := range queue { activity, ok, err := s.DB.GetActivity(r.Context(), a.ActivityID) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } if !ok { continue } laps, err := s.DB.LapsForActivity(r.Context(), a.ActivityID) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } items = append(items, item{KindAssignment: a, Activity: activity, Laps: laps}) } // Most recent run first, by when the activity actually happened (not // when the rule engine flagged it), so the queue reads like a log. sort.Slice(items, func(i, j int) bool { return items[i].Activity.StartTimeUTC > items[j].Activity.StartTimeUTC }) writeJSON(w, http.StatusOK, items) } func (s *Server) handleResolveReview(w http.ResponseWriter, r *http.Request) { activityID, err := strconv.ParseInt(chi.URLParam(r, "activityID"), 10, 64) if err != nil { writeError(w, http.StatusBadRequest, "invalid activity id") return } var body struct { WorkoutKindID int64 `json:"workout_kind_id"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeError(w, http.StatusBadRequest, "invalid request body") return } if body.WorkoutKindID == 0 { writeError(w, http.StatusBadRequest, "workout_kind_id is required") return } kind, ok, err := s.DB.GetWorkoutKind(r.Context(), body.WorkoutKindID) if err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } else if !ok { writeError(w, http.StatusBadRequest, "workout kind not found") return } if kind.Name == "Race" { writeError(w, http.StatusBadRequest, "Race is assigned automatically from Garmin metadata and cannot be set manually") return } kindID := body.WorkoutKindID if _, err := s.DB.InsertKindAssignment(r.Context(), store.KindAssignment{ ActivityID: activityID, WorkoutKindID: &kindID, AssignmentSource: store.AssignmentSourceManual, Status: store.AssignmentStatusAssigned, CandidateKindsJSON: "[]", }); err != nil { writeError(w, http.StatusInternalServerError, err.Error()) return } writeJSON(w, http.StatusOK, map[string]string{"status": "resolved"}) }