Garmin auth/sync routes move under /api/garmin/*; sync.Service becomes garmin.Sync with garmin.SyncConfig/ClientConfig; applog becomes internal/log; the test mock moves into the garmin package as MockClient (breaking the test-only import cycle the merge created); stale test URLs and type names updated to match. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
76 lines
2.2 KiB
Go
76 lines
2.2 KiB
Go
package api
|
|
|
|
import (
|
|
"encoding/json"
|
|
|
|
"geniusrun/backend/internal/store"
|
|
)
|
|
|
|
// activityResponse adds back ActivityName/ActivityType as fields decoded
|
|
// from RawJSON. They're no longer stored as their own columns (Phase 2 of
|
|
// the 2026-07 duplication cleanup -- see store.Activity's doc comment):
|
|
// Garmin's raw JSON is the only place they live now, decoded fresh for each
|
|
// API response instead of kept as a redundant copy in the database.
|
|
type activityResponse struct {
|
|
store.Activity
|
|
ActivityName string `json:"ActivityName"`
|
|
ActivityType string `json:"ActivityType"`
|
|
}
|
|
|
|
func toActivityResponse(a store.Activity) activityResponse {
|
|
name, typeKey := decodeActivityDisplayFields(a.RawJSON)
|
|
return activityResponse{Activity: a, ActivityName: name, ActivityType: typeKey}
|
|
}
|
|
|
|
type rawActivityDisplayFields struct {
|
|
ActivityName string `json:"activityName"`
|
|
ActivityType struct {
|
|
TypeKey string `json:"typeKey"`
|
|
} `json:"activityType"`
|
|
}
|
|
|
|
func decodeActivityDisplayFields(rawJSON string) (name, typeKey string) {
|
|
var f rawActivityDisplayFields
|
|
_ = json.Unmarshal([]byte(rawJSON), &f) // best-effort -- zero values are fine if rawJSON is empty/malformed
|
|
return f.ActivityName, f.ActivityType.TypeKey
|
|
}
|
|
|
|
// lapResponse adds back DurationSeconds/AvgHR as fields decoded from
|
|
// RawJSON, same rationale as activityResponse -- the frontend chart still
|
|
// needs them (x-axis timing, dashed average-HR line) even though nothing on
|
|
// the backend does.
|
|
type lapResponse struct {
|
|
store.Lap
|
|
DurationSeconds float64 `json:"DurationSeconds"`
|
|
AvgHR *float64 `json:"AvgHR"`
|
|
}
|
|
|
|
func toLapResponse(l store.Lap) lapResponse {
|
|
duration, avgHR := decodeLapDisplayFields(l.RawJSON)
|
|
return lapResponse{Lap: l, DurationSeconds: duration, AvgHR: avgHR}
|
|
}
|
|
|
|
func toLapResponses(laps []store.Lap) []lapResponse {
|
|
out := make([]lapResponse, len(laps))
|
|
for i, l := range laps {
|
|
out[i] = toLapResponse(l)
|
|
}
|
|
return out
|
|
}
|
|
|
|
type rawLapDisplayFields struct {
|
|
Duration float64 `json:"duration"`
|
|
AverageHR float64 `json:"averageHR"`
|
|
}
|
|
|
|
func decodeLapDisplayFields(rawJSON string) (durationSeconds float64, avgHR *float64) {
|
|
var f rawLapDisplayFields
|
|
_ = json.Unmarshal([]byte(rawJSON), &f)
|
|
durationSeconds = f.Duration
|
|
if f.AverageHR > 0 {
|
|
v := f.AverageHR
|
|
avgHR = &v
|
|
}
|
|
return
|
|
}
|