Files
rideaware-api/internal/activity/service.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
}