Files
geniusrun/backend/internal/api/fields.go
Christophe Vila e2b2bf9611 refactor: merge internal/sync into internal/garmin, regroup api files and routes
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>
2026-08-04 16:04:18 +02:00

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
}