mirror of
https://github.com/sstent/go-garth.git
synced 2026-01-26 00:52:40 +00:00
feat(refactor): Implement 1A.1 Package Structure Refactoring
This commit implements the package structure refactoring as outlined in phase1.md (Task 1A.1). Key changes include: - Reorganized packages into `pkg/garmin` for public API and `internal/` for internal implementations. - Updated all import paths to reflect the new structure. - Consolidated types and client logic into their respective new packages. - Updated `cmd/garth/main.go` to use the new public API. - Fixed various compilation and test issues encountered during the refactoring process. - Converted `internal/api/client/auth_test.go` to a functional test. This establishes a solid foundation for future enhancements and improves maintainability.
This commit is contained in:
101
internal/stats/base.go
Normal file
101
internal/stats/base.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"garmin-connect/internal/api/client"
|
||||
"garmin-connect/internal/utils"
|
||||
)
|
||||
|
||||
type Stats interface {
|
||||
List(end time.Time, period int, client *client.Client) ([]interface{}, error)
|
||||
}
|
||||
|
||||
type BaseStats struct {
|
||||
Path string
|
||||
PageSize int
|
||||
}
|
||||
|
||||
func (b *BaseStats) List(end time.Time, period int, client *client.Client) ([]interface{}, error) {
|
||||
endDate := utils.FormatEndDate(end)
|
||||
var allData []interface{}
|
||||
var errs []error
|
||||
|
||||
for period > 0 {
|
||||
pageSize := b.PageSize
|
||||
if period < pageSize {
|
||||
pageSize = period
|
||||
}
|
||||
|
||||
page, err := b.fetchPage(endDate, pageSize, client)
|
||||
if err != nil {
|
||||
errs = append(errs, err)
|
||||
// Continue to next page even if current fails
|
||||
} else {
|
||||
allData = append(page, allData...)
|
||||
}
|
||||
|
||||
// Move to previous page
|
||||
endDate = endDate.AddDate(0, 0, -pageSize)
|
||||
period -= pageSize
|
||||
}
|
||||
|
||||
// Return partial data with aggregated errors
|
||||
var finalErr error
|
||||
if len(errs) > 0 {
|
||||
finalErr = fmt.Errorf("partial failure: %v", errs)
|
||||
}
|
||||
return allData, finalErr
|
||||
}
|
||||
|
||||
func (b *BaseStats) fetchPage(end time.Time, period int, client *client.Client) ([]interface{}, error) {
|
||||
var start time.Time
|
||||
var path string
|
||||
|
||||
if strings.Contains(b.Path, "daily") {
|
||||
start = end.AddDate(0, 0, -(period - 1))
|
||||
path = strings.Replace(b.Path, "{start}", start.Format("2006-01-02"), 1)
|
||||
path = strings.Replace(path, "{end}", end.Format("2006-01-02"), 1)
|
||||
} else {
|
||||
path = strings.Replace(b.Path, "{end}", end.Format("2006-01-02"), 1)
|
||||
path = strings.Replace(path, "{period}", fmt.Sprintf("%d", period), 1)
|
||||
}
|
||||
|
||||
data, err := client.ConnectAPI(path, "GET", nil, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) == 0 {
|
||||
return []interface{}{}, nil
|
||||
}
|
||||
|
||||
var responseSlice []map[string]interface{}
|
||||
if err := json.Unmarshal(data, &responseSlice); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(responseSlice) == 0 {
|
||||
return []interface{}{}, nil
|
||||
}
|
||||
|
||||
var results []interface{}
|
||||
for _, itemMap := range responseSlice {
|
||||
// Handle nested "values" structure
|
||||
if values, exists := itemMap["values"]; exists {
|
||||
valuesMap := values.(map[string]interface{})
|
||||
for k, v := range valuesMap {
|
||||
itemMap[k] = v
|
||||
}
|
||||
delete(itemMap, "values")
|
||||
}
|
||||
|
||||
snakeItem := utils.CamelToSnakeDict(itemMap)
|
||||
results = append(results, snakeItem)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
21
internal/stats/hrv.go
Normal file
21
internal/stats/hrv.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package stats
|
||||
|
||||
import "time"
|
||||
|
||||
const BASE_HRV_PATH = "/usersummary-service/stats/hrv"
|
||||
|
||||
type DailyHRV struct {
|
||||
CalendarDate time.Time `json:"calendar_date"`
|
||||
RestingHR *int `json:"resting_hr"`
|
||||
HRV *int `json:"hrv"`
|
||||
BaseStats
|
||||
}
|
||||
|
||||
func NewDailyHRV() *DailyHRV {
|
||||
return &DailyHRV{
|
||||
BaseStats: BaseStats{
|
||||
Path: BASE_HRV_PATH + "/daily/{start}/{end}",
|
||||
PageSize: 28,
|
||||
},
|
||||
}
|
||||
}
|
||||
40
internal/stats/hrv_weekly.go
Normal file
40
internal/stats/hrv_weekly.go
Normal file
@@ -0,0 +1,40 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
const WEEKLY_HRV_PATH = "/wellness-service/wellness/weeklyHrv"
|
||||
|
||||
type WeeklyHRV struct {
|
||||
CalendarDate time.Time `json:"calendar_date"`
|
||||
AverageHRV float64 `json:"average_hrv"`
|
||||
MaxHRV float64 `json:"max_hrv"`
|
||||
MinHRV float64 `json:"min_hrv"`
|
||||
HRVQualifier string `json:"hrv_qualifier"`
|
||||
WellnessDataDaysCount int `json:"wellness_data_days_count"`
|
||||
BaseStats
|
||||
}
|
||||
|
||||
func NewWeeklyHRV() *WeeklyHRV {
|
||||
return &WeeklyHRV{
|
||||
BaseStats: BaseStats{
|
||||
Path: WEEKLY_HRV_PATH + "/{end}/{period}",
|
||||
PageSize: 52,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WeeklyHRV) Validate() error {
|
||||
if w.CalendarDate.IsZero() {
|
||||
return errors.New("calendar_date is required")
|
||||
}
|
||||
if w.AverageHRV < 0 || w.MaxHRV < 0 || w.MinHRV < 0 {
|
||||
return errors.New("HRV values must be non-negative")
|
||||
}
|
||||
if w.MaxHRV < w.MinHRV {
|
||||
return errors.New("max_hrv must be greater than min_hrv")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
20
internal/stats/hydration.go
Normal file
20
internal/stats/hydration.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package stats
|
||||
|
||||
import "time"
|
||||
|
||||
const BASE_HYDRATION_PATH = "/usersummary-service/stats/hydration"
|
||||
|
||||
type DailyHydration struct {
|
||||
CalendarDate time.Time `json:"calendar_date"`
|
||||
TotalWaterML *int `json:"total_water_ml"`
|
||||
BaseStats
|
||||
}
|
||||
|
||||
func NewDailyHydration() *DailyHydration {
|
||||
return &DailyHydration{
|
||||
BaseStats: BaseStats{
|
||||
Path: BASE_HYDRATION_PATH + "/daily/{start}/{end}",
|
||||
PageSize: 28,
|
||||
},
|
||||
}
|
||||
}
|
||||
21
internal/stats/intensity_minutes.go
Normal file
21
internal/stats/intensity_minutes.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package stats
|
||||
|
||||
import "time"
|
||||
|
||||
const BASE_INTENSITY_PATH = "/usersummary-service/stats/intensity_minutes"
|
||||
|
||||
type DailyIntensityMinutes struct {
|
||||
CalendarDate time.Time `json:"calendar_date"`
|
||||
ModerateIntensity *int `json:"moderate_intensity"`
|
||||
VigorousIntensity *int `json:"vigorous_intensity"`
|
||||
BaseStats
|
||||
}
|
||||
|
||||
func NewDailyIntensityMinutes() *DailyIntensityMinutes {
|
||||
return &DailyIntensityMinutes{
|
||||
BaseStats: BaseStats{
|
||||
Path: BASE_INTENSITY_PATH + "/daily/{start}/{end}",
|
||||
PageSize: 28,
|
||||
},
|
||||
}
|
||||
}
|
||||
27
internal/stats/sleep.go
Normal file
27
internal/stats/sleep.go
Normal file
@@ -0,0 +1,27 @@
|
||||
package stats
|
||||
|
||||
import "time"
|
||||
|
||||
const BASE_SLEEP_PATH = "/usersummary-service/stats/sleep"
|
||||
|
||||
type DailySleep struct {
|
||||
CalendarDate time.Time `json:"calendar_date"`
|
||||
TotalSleepTime *int `json:"total_sleep_time"`
|
||||
RemSleepTime *int `json:"rem_sleep_time"`
|
||||
DeepSleepTime *int `json:"deep_sleep_time"`
|
||||
LightSleepTime *int `json:"light_sleep_time"`
|
||||
AwakeTime *int `json:"awake_time"`
|
||||
SleepScore *int `json:"sleep_score"`
|
||||
SleepStartTimestamp *int64 `json:"sleep_start_timestamp"`
|
||||
SleepEndTimestamp *int64 `json:"sleep_end_timestamp"`
|
||||
BaseStats
|
||||
}
|
||||
|
||||
func NewDailySleep() *DailySleep {
|
||||
return &DailySleep{
|
||||
BaseStats: BaseStats{
|
||||
Path: BASE_SLEEP_PATH + "/daily/{start}/{end}",
|
||||
PageSize: 28,
|
||||
},
|
||||
}
|
||||
}
|
||||
41
internal/stats/steps.go
Normal file
41
internal/stats/steps.go
Normal file
@@ -0,0 +1,41 @@
|
||||
package stats
|
||||
|
||||
import "time"
|
||||
|
||||
const BASE_STEPS_PATH = "/usersummary-service/stats/steps"
|
||||
|
||||
type DailySteps struct {
|
||||
CalendarDate time.Time `json:"calendar_date"`
|
||||
TotalSteps *int `json:"total_steps"`
|
||||
TotalDistance *int `json:"total_distance"`
|
||||
StepGoal int `json:"step_goal"`
|
||||
BaseStats
|
||||
}
|
||||
|
||||
func NewDailySteps() *DailySteps {
|
||||
return &DailySteps{
|
||||
BaseStats: BaseStats{
|
||||
Path: BASE_STEPS_PATH + "/daily/{start}/{end}",
|
||||
PageSize: 28,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
type WeeklySteps struct {
|
||||
CalendarDate time.Time `json:"calendar_date"`
|
||||
TotalSteps int `json:"total_steps"`
|
||||
AverageSteps float64 `json:"average_steps"`
|
||||
AverageDistance float64 `json:"average_distance"`
|
||||
TotalDistance float64 `json:"total_distance"`
|
||||
WellnessDataDaysCount int `json:"wellness_data_days_count"`
|
||||
BaseStats
|
||||
}
|
||||
|
||||
func NewWeeklySteps() *WeeklySteps {
|
||||
return &WeeklySteps{
|
||||
BaseStats: BaseStats{
|
||||
Path: BASE_STEPS_PATH + "/weekly/{end}/{period}",
|
||||
PageSize: 52,
|
||||
},
|
||||
}
|
||||
}
|
||||
24
internal/stats/stress.go
Normal file
24
internal/stats/stress.go
Normal file
@@ -0,0 +1,24 @@
|
||||
package stats
|
||||
|
||||
import "time"
|
||||
|
||||
const BASE_STRESS_PATH = "/usersummary-service/stats/stress"
|
||||
|
||||
type DailyStress struct {
|
||||
CalendarDate time.Time `json:"calendar_date"`
|
||||
OverallStressLevel int `json:"overall_stress_level"`
|
||||
RestStressDuration *int `json:"rest_stress_duration"`
|
||||
LowStressDuration *int `json:"low_stress_duration"`
|
||||
MediumStressDuration *int `json:"medium_stress_duration"`
|
||||
HighStressDuration *int `json:"high_stress_duration"`
|
||||
BaseStats
|
||||
}
|
||||
|
||||
func NewDailyStress() *DailyStress {
|
||||
return &DailyStress{
|
||||
BaseStats: BaseStats{
|
||||
Path: BASE_STRESS_PATH + "/daily/{start}/{end}",
|
||||
PageSize: 28,
|
||||
},
|
||||
}
|
||||
}
|
||||
36
internal/stats/stress_weekly.go
Normal file
36
internal/stats/stress_weekly.go
Normal file
@@ -0,0 +1,36 @@
|
||||
package stats
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
const WEEKLY_STRESS_PATH = "/wellness-service/wellness/weeklyStress"
|
||||
|
||||
type WeeklyStress struct {
|
||||
CalendarDate time.Time `json:"calendar_date"`
|
||||
TotalStressDuration int `json:"total_stress_duration"`
|
||||
AverageStressLevel float64 `json:"average_stress_level"`
|
||||
MaxStressLevel int `json:"max_stress_level"`
|
||||
StressQualifier string `json:"stress_qualifier"`
|
||||
BaseStats
|
||||
}
|
||||
|
||||
func NewWeeklyStress() *WeeklyStress {
|
||||
return &WeeklyStress{
|
||||
BaseStats: BaseStats{
|
||||
Path: WEEKLY_STRESS_PATH + "/{end}/{period}",
|
||||
PageSize: 52,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (w *WeeklyStress) Validate() error {
|
||||
if w.CalendarDate.IsZero() {
|
||||
return errors.New("calendar_date is required")
|
||||
}
|
||||
if w.TotalStressDuration < 0 {
|
||||
return errors.New("total_stress_duration must be non-negative")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
Reference in New Issue
Block a user