127 lines
3.1 KiB
Go
127 lines
3.1 KiB
Go
package activity
|
|
|
|
import (
|
|
"fmt"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"rideaware/internal/workout"
|
|
)
|
|
|
|
type Service struct {
|
|
workoutRepo *workout.Repository
|
|
}
|
|
|
|
func NewService() *Service {
|
|
return &Service{
|
|
workoutRepo: workout.NewRepository(),
|
|
}
|
|
}
|
|
|
|
// ImportActivity parses an activity file and creates or updates a workout with the metrics.
|
|
func (s *Service) ImportActivity(userID uint, fileData []byte, filename string, opts ImportOptions) (*workout.Workout, error) {
|
|
ext := strings.ToLower(filepath.Ext(filename))
|
|
|
|
var parsed *ParsedActivity
|
|
var err error
|
|
|
|
switch ext {
|
|
case ".fit":
|
|
parsed, err = ParseFIT(fileData)
|
|
case ".tcx":
|
|
parsed, err = ParseTCX(fileData)
|
|
case ".gpx":
|
|
parsed, err = ParseGPX(fileData)
|
|
default:
|
|
return nil, fmt.Errorf("unsupported file type: %s (supported: .fit, .tcx, .gpx)", ext)
|
|
}
|
|
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to parse %s file: %w", ext, err)
|
|
}
|
|
|
|
// If updating an existing workout, apply metrics to it
|
|
if opts.WorkoutID > 0 {
|
|
return s.updateExistingWorkout(userID, opts.WorkoutID, parsed, opts)
|
|
}
|
|
|
|
// Create a new workout from parsed data
|
|
return s.createNewWorkout(userID, parsed, ext, opts)
|
|
}
|
|
|
|
func (s *Service) updateExistingWorkout(userID uint, workoutID uint, parsed *ParsedActivity, opts ImportOptions) (*workout.Workout, error) {
|
|
w, err := s.workoutRepo.GetWorkoutByID(workoutID, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
w.Status = "completed"
|
|
w.Duration = parsed.Duration
|
|
w.Distance = parsed.Distance
|
|
w.ElevGain = parsed.ElevGain
|
|
w.AvgPower = parsed.AvgPower
|
|
w.MaxPower = parsed.MaxPower
|
|
w.AvgHR = parsed.AvgHR
|
|
w.MaxHR = parsed.MaxHR
|
|
w.CaloriesBurned = parsed.CaloriesBurned
|
|
|
|
if opts.EquipmentID != nil {
|
|
w.EquipmentID = opts.EquipmentID
|
|
}
|
|
|
|
if err := s.workoutRepo.UpdateWorkout(w); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return w, nil
|
|
}
|
|
|
|
func (s *Service) createNewWorkout(userID uint, parsed *ParsedActivity, fileExt string, opts ImportOptions) (*workout.Workout, error) {
|
|
title := opts.Title
|
|
if title == "" && parsed.Title != "" {
|
|
title = parsed.Title
|
|
}
|
|
if title == "" {
|
|
title = "Imported Ride"
|
|
}
|
|
|
|
scheduledDate := parsed.StartTime
|
|
if scheduledDate.IsZero() {
|
|
scheduledDate = time.Now()
|
|
}
|
|
|
|
w := &workout.Workout{
|
|
UserID: userID,
|
|
Title: title,
|
|
Type: "ride",
|
|
Status: "completed",
|
|
ScheduledDate: scheduledDate,
|
|
Duration: parsed.Duration,
|
|
Distance: parsed.Distance,
|
|
ElevGain: parsed.ElevGain,
|
|
AvgPower: parsed.AvgPower,
|
|
MaxPower: parsed.MaxPower,
|
|
AvgHR: parsed.AvgHR,
|
|
MaxHR: parsed.MaxHR,
|
|
CaloriesBurned: parsed.CaloriesBurned,
|
|
FileType: strings.TrimPrefix(fileExt, "."),
|
|
EquipmentID: opts.EquipmentID,
|
|
Notes: opts.Notes,
|
|
}
|
|
|
|
if err := s.workoutRepo.CreateWorkout(w); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return w, nil
|
|
}
|
|
|
|
// ImportOptions holds optional parameters for activity import.
|
|
type ImportOptions struct {
|
|
WorkoutID uint // If set, update existing workout instead of creating new
|
|
Title string // Custom title override
|
|
EquipmentID *uint // Equipment to associate
|
|
Notes string // Optional notes
|
|
}
|