package activity import ( "encoding/json" "log" "net/http" "strconv" "rideaware/internal/config" "rideaware/internal/equipment" "rideaware/internal/middleware" ) type Handler struct { service *Service equipmentSvc *equipment.Service } func NewHandler() *Handler { return &Handler{ service: NewService(), equipmentSvc: equipment.NewService(), } } // ImportActivity POST /api/protected/workouts/import // Accepts multipart form with: // - file: activity file (FIT/TCX/GPX) - required // - workout_id: existing workout ID to update (optional) // - title: custom title (optional) // - equipment_id: equipment to associate (optional) // - notes: optional notes func (h *Handler) ImportActivity(w http.ResponseWriter, r *http.Request) { claims := r.Context().Value(middleware.UserContextKey).(*config.CustomClaims) if claims == nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusUnauthorized) json.NewEncoder(w).Encode(map[string]string{"error": "unauthorized"}) return } if err := r.ParseMultipartForm(20 << 20); err != nil { // 20MB max w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": "file too large or invalid form data"}) return } file, handler, err := r.FormFile("file") if err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": "no file provided"}) return } defer file.Close() fileData := make([]byte, handler.Size) if _, err := file.Read(fileData); err != nil { w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": "failed to read file"}) return } opts := ImportOptions{ Title: r.FormValue("title"), Notes: r.FormValue("notes"), } if idStr := r.FormValue("workout_id"); idStr != "" { if id, err := strconv.ParseUint(idStr, 10, 32); err == nil { opts.WorkoutID = uint(id) } } if eqIDStr := r.FormValue("equipment_id"); eqIDStr != "" { if eqID, err := strconv.ParseUint(eqIDStr, 10, 32); err == nil { id := uint(eqID) opts.EquipmentID = &id } } workout, err := h.service.ImportActivity(claims.UserID, fileData, handler.Filename, opts) if err != nil { log.Printf("Activity import error: %v", err) w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusBadRequest) json.NewEncoder(w).Encode(map[string]string{"error": err.Error()}) return } // Auto-update equipment mileage if equipment is assigned if workout.EquipmentID != nil && workout.Status == "completed" { if err := h.equipmentSvc.IncrementMileage(*workout.EquipmentID, claims.UserID, workout.Distance, workout.Duration); err != nil { log.Printf("Equipment mileage update error: %v", err) } } w.Header().Set("Content-Type", "application/json") w.WriteHeader(http.StatusCreated) json.NewEncoder(w).Encode(workout) }