diff --git a/cmd/garth/activities.go b/cmd/garth/activities.go new file mode 100644 index 0000000..85f0393 --- /dev/null +++ b/cmd/garth/activities.go @@ -0,0 +1 @@ +package main \ No newline at end of file diff --git a/cmd/garth/auth.go b/cmd/garth/auth.go new file mode 100644 index 0000000..85f0393 --- /dev/null +++ b/cmd/garth/auth.go @@ -0,0 +1 @@ +package main \ No newline at end of file diff --git a/cmd/garth/health.go b/cmd/garth/health.go new file mode 100644 index 0000000..85f0393 --- /dev/null +++ b/cmd/garth/health.go @@ -0,0 +1 @@ +package main \ No newline at end of file diff --git a/cmd/garth/main.go b/cmd/garth/main.go index 20b55ee..8ff8379 100644 --- a/cmd/garth/main.go +++ b/cmd/garth/main.go @@ -8,18 +8,19 @@ import ( "os" "time" - "garmin-connect/garth" - "garmin-connect/garth/credentials" + "garmin-connect/pkg/garmin" + "garmin-connect/internal/auth/credentials" ) func main() { // Parse command line flags - outputTokens := flag.Bool("tokens", false, "Output OAuth tokens in JSON format") - dataType := flag.String("data", "", "Data type to fetch (bodybattery, sleep, hrv, weight)") - statsType := flag.String("stats", "", "Stats type to fetch (steps, stress, hydration, intensity, sleep, hrv)") - dateStr := flag.String("date", "", "Date in YYYY-MM-DD format (default: yesterday)") - days := flag.Int("days", 1, "Number of days to fetch") - outputFile := flag.String("output", "", "Output file for JSON results") + var outputTokens = flag.Bool("tokens", false, "Output OAuth tokens in JSON format") + var dataType = flag.String("data", "", "Data type to fetch (bodybattery, sleep, hrv, weight)") + var statsType = flag.String("stats", "", "Stats type to fetch (steps, stress, hydration, intensity, sleep, hrv)") + + var dateStr = flag.String("date", "", "Date in YYYY-MM-DD format (default: yesterday)") + var days = flag.Int("days", 1, "Number of days to fetch") + var outputFile = flag.String("output", "", "Output file for JSON results") flag.Parse() // Load credentials from .env file @@ -29,7 +30,7 @@ func main() { } // Create client - garminClient, err := garth.NewClient(domain) + garminClient, err := garmin.NewClient(domain) if err != nil { log.Fatalf("Failed to create client: %v", err) } @@ -77,13 +78,13 @@ func main() { displayActivities(activities) } -func outputTokensJSON(c *garth.Client) { +func outputTokensJSON(c *garmin.Client) { tokens := struct { - OAuth1 *garth.OAuth1Token `json:"oauth1"` - OAuth2 *garth.OAuth2Token `json:"oauth2"` + OAuth1 *garmin.OAuth1Token `json:"oauth1"` + OAuth2 *garmin.OAuth2Token `json:"oauth2"` }{ - OAuth1: c.OAuth1Token, - OAuth2: c.OAuth2Token, + OAuth1: c.OAuth1Token(), + OAuth2: c.OAuth2Token(), } jsonBytes, err := json.MarshalIndent(tokens, "", " ") @@ -93,7 +94,7 @@ func outputTokensJSON(c *garth.Client) { fmt.Println(string(jsonBytes)) } -func handleDataRequest(c *garth.Client, dataType, dateStr string, days int, outputFile string) { +func handleDataRequest(c *garmin.Client, dataType, dateStr string, days int, outputFile string) { endDate := time.Now().AddDate(0, 0, -1) // default to yesterday if dateStr != "" { parsedDate, err := time.Parse("2006-01-02", dateStr) @@ -108,17 +109,13 @@ func handleDataRequest(c *garth.Client, dataType, dateStr string, days int, outp switch dataType { case "bodybattery": - bb := &garth.BodyBatteryData{} - result, err = bb.Get(endDate, c) + result, err = c.GetBodyBattery(endDate) case "sleep": - sleep := &garth.SleepData{} - result, err = sleep.Get(endDate, c) + result, err = c.GetSleep(endDate) case "hrv": - hrv := &garth.HRVData{} - result, err = hrv.Get(endDate, c) + result, err = c.GetHRV(endDate) case "weight": - weight := &garth.WeightData{} - result, err = weight.Get(endDate, c) + result, err = c.GetWeight(endDate) default: log.Fatalf("Unknown data type: %s", dataType) } @@ -130,7 +127,7 @@ func handleDataRequest(c *garth.Client, dataType, dateStr string, days int, outp outputResult(result, outputFile) } -func handleStatsRequest(c *garth.Client, statsType, dateStr string, days int, outputFile string) { +func handleStatsRequest(c *garmin.Client, statsType, dateStr string, days int, outputFile string) { endDate := time.Now().AddDate(0, 0, -1) // default to yesterday if dateStr != "" { parsedDate, err := time.Parse("2006-01-02", dateStr) @@ -140,25 +137,25 @@ func handleStatsRequest(c *garth.Client, statsType, dateStr string, days int, ou endDate = parsedDate } - var stats garth.Stats + var stats garmin.Stats switch statsType { case "steps": - stats = garth.NewDailySteps() + stats = garmin.NewDailySteps() case "stress": - stats = garth.NewDailyStress() + stats = garmin.NewDailyStress() case "hydration": - stats = garth.NewDailyHydration() + stats = garmin.NewDailyHydration() case "intensity": - stats = garth.NewDailyIntensityMinutes() + stats = garmin.NewDailyIntensityMinutes() case "sleep": - stats = garth.NewDailySleep() + stats = garmin.NewDailySleep() case "hrv": - stats = garth.NewDailyHRV() + stats = garmin.NewDailyHRV() default: log.Fatalf("Unknown stats type: %s", statsType) } - result, err := stats.List(endDate, days, c) + result, err := stats.List(endDate, days, c.Client) if err != nil { log.Fatalf("Failed to get %s stats: %v", statsType, err) } @@ -182,7 +179,7 @@ func outputResult(data interface{}, outputFile string) { } } -func displayActivities(activities []garth.Activity) { +func displayActivities(activities []garmin.Activity) { fmt.Printf("\n=== Recent Activities ===\n") for i, activity := range activities { fmt.Printf("%d. %s\n", i+1, activity.ActivityName) @@ -197,4 +194,4 @@ func displayActivities(activities []garth.Activity) { } fmt.Println() } -} +} \ No newline at end of file diff --git a/cmd/garth/root.go b/cmd/garth/root.go new file mode 100644 index 0000000..85f0393 --- /dev/null +++ b/cmd/garth/root.go @@ -0,0 +1 @@ +package main \ No newline at end of file diff --git a/cmd/garth/stats.go b/cmd/garth/stats.go new file mode 100644 index 0000000..85f0393 --- /dev/null +++ b/cmd/garth/stats.go @@ -0,0 +1 @@ +package main \ No newline at end of file diff --git a/garmin-connect b/garmin-connect index e273794..8edbdee 100755 Binary files a/garmin-connect and b/garmin-connect differ diff --git a/garth/__init__.go b/garth/__init__.go deleted file mode 100644 index 2cc67f4..0000000 --- a/garth/__init__.go +++ /dev/null @@ -1,42 +0,0 @@ -package garth - -import ( - "garmin-connect/garth/client" - "garmin-connect/garth/data" - "garmin-connect/garth/errors" - "garmin-connect/garth/stats" - "garmin-connect/garth/types" -) - -// Re-export main types for convenience -type Client = client.Client - -// Data types -type BodyBatteryData = data.DailyBodyBatteryStress -type HRVData = data.HRVData -type SleepData = data.DailySleepDTO -type WeightData = data.WeightData - -// Stats types -type DailySteps = stats.DailySteps -type DailyStress = stats.DailyStress -type DailyHRV = stats.DailyHRV -type DailyHydration = stats.DailyHydration -type DailyIntensityMinutes = stats.DailyIntensityMinutes -type DailySleep = stats.DailySleep - -// Activity type -type Activity = types.Activity - -// Error types -type APIError = errors.APIError -type IOError = errors.IOError -type AuthError = errors.AuthenticationError -type OAuthError = errors.OAuthError -type ValidationError = errors.ValidationError - -// Main functions -var ( - NewClient = client.NewClient - Login = client.Login -) diff --git a/garth/client/auth_test.go b/garth/client/auth_test.go deleted file mode 100644 index 554f440..0000000 --- a/garth/client/auth_test.go +++ /dev/null @@ -1,60 +0,0 @@ -package client_test - -import ( - "net/http" - "net/url" - "testing" - - "garmin-connect/garth/testutils" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "garmin-connect/garth/client" - "garmin-connect/garth/errors" -) - -func TestClient_Login_Success(t *testing.T) { - // Create mock SSO server - ssoServer := testutils.MockJSONResponse(http.StatusOK, `{ - "access_token": "test_token", - "token_type": "Bearer", - "expires_in": 3600 - }`) - defer ssoServer.Close() - - // Create client with test configuration - c, err := client.NewClient("example.com") - require.NoError(t, err) - // Set domain to just the host (without scheme) - u, _ := url.Parse(ssoServer.URL) - c.Domain = u.Host - - // Perform login - err = c.Login("test@example.com", "password") - - // Verify login - require.NoError(t, err) - assert.Equal(t, "Bearer test_token", c.AuthToken) -} - -func TestClient_Login_Failure(t *testing.T) { - // Create mock SSO server returning error - ssoServer := testutils.MockJSONResponse(http.StatusUnauthorized, `{ - "error": "invalid_credentials" - }`) - defer ssoServer.Close() - - // Create client with test configuration - c, err := client.NewClient("example.com") - require.NoError(t, err) - c.Domain = ssoServer.URL - - // Perform login - err = c.Login("test@example.com", "wrongpassword") - - // Verify error - require.Error(t, err) - assert.IsType(t, &errors.AuthenticationError{}, err) - assert.Contains(t, err.Error(), "SSO login failed") -} diff --git a/garth/garth.go b/garth/garth.go deleted file mode 100644 index b98d8c1..0000000 --- a/garth/garth.go +++ /dev/null @@ -1,64 +0,0 @@ -package garth - -import ( - "garmin-connect/garth/client" - "garmin-connect/garth/data" - "garmin-connect/garth/errors" - "garmin-connect/garth/stats" - "garmin-connect/garth/types" -) - -// Client is the main Garmin Connect client type -type Client = client.Client - -// OAuth1Token represents OAuth 1.0 token -type OAuth1Token = types.OAuth1Token - -// OAuth2Token represents OAuth 2.0 token -type OAuth2Token = types.OAuth2Token - -// Data types -type ( - BodyBatteryData = data.DailyBodyBatteryStress - HRVData = data.HRVData - SleepData = data.DailySleepDTO - WeightData = data.WeightData -) - -// Stats types -type ( - Stats = stats.Stats - DailySteps = stats.DailySteps - DailyStress = stats.DailyStress - DailyHRV = stats.DailyHRV - DailyHydration = stats.DailyHydration - DailyIntensityMinutes = stats.DailyIntensityMinutes - DailySleep = stats.DailySleep -) - -// Activity represents a Garmin activity -type Activity = types.Activity - -// Error types -type ( - APIError = errors.APIError - IOError = errors.IOError - AuthError = errors.AuthenticationError - OAuthError = errors.OAuthError - ValidationError = errors.ValidationError -) - -// Main functions -var ( - NewClient = client.NewClient -) - -// Stats constructor functions -var ( - NewDailySteps = stats.NewDailySteps - NewDailyStress = stats.NewDailyStress - NewDailyHydration = stats.NewDailyHydration - NewDailyIntensityMinutes = stats.NewDailyIntensityMinutes - NewDailySleep = stats.NewDailySleep - NewDailyHRV = stats.NewDailyHRV -) diff --git a/garth/client/auth.go b/internal/api/client/auth.go similarity index 100% rename from garth/client/auth.go rename to internal/api/client/auth.go diff --git a/internal/api/client/auth_test.go b/internal/api/client/auth_test.go new file mode 100644 index 0000000..26da5c4 --- /dev/null +++ b/internal/api/client/auth_test.go @@ -0,0 +1,37 @@ +package client_test + +import ( + "testing" + + "garmin-connect/internal/api/client" + "garmin-connect/internal/auth/credentials" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestClient_Login_Functional(t *testing.T) { + if testing.Short() { + t.Skip("Skipping functional test in short mode") + } + + // Load credentials from .env file + email, password, domain, err := credentials.LoadEnvCredentials() + require.NoError(t, err, "Failed to load credentials from .env file. Please ensure GARMIN_EMAIL, GARMIN_PASSWORD, and GARMIN_DOMAIN are set.") + + // Create client + c, err := client.NewClient(domain) + require.NoError(t, err, "Failed to create client") + + // Perform login + err = c.Login(email, password) + require.NoError(t, err, "Login failed") + + // Verify login + assert.NotEmpty(t, c.AuthToken, "AuthToken should not be empty after login") + assert.NotEmpty(t, c.Username, "Username should not be empty after login") + + // Logout for cleanup + err = c.Logout() + assert.NoError(t, err, "Logout failed") +} \ No newline at end of file diff --git a/garth/client/client.go b/internal/api/client/client.go similarity index 91% rename from garth/client/client.go rename to internal/api/client/client.go index 0bacc35..6bd81d8 100644 --- a/garth/client/client.go +++ b/internal/api/client/client.go @@ -14,9 +14,9 @@ import ( "strings" "time" - "garmin-connect/garth/errors" - "garmin-connect/garth/sso" - "garmin-connect/garth/types" + "garmin-connect/internal/errors" + "garmin-connect/internal/auth/sso" + "garmin-connect/internal/types" ) // Client represents the Garmin Connect API client @@ -121,9 +121,31 @@ func (c *Client) Login(email, password string) error { return nil } +// Logout clears the current session and tokens. +func (c *Client) Logout() error { + c.AuthToken = "" + c.Username = "" + c.OAuth1Token = nil + c.OAuth2Token = nil + + // Clear cookies + if c.HTTPClient != nil && c.HTTPClient.Jar != nil { + // Create a dummy URL for the domain to clear all cookies associated with it + dummyURL, err := url.Parse(fmt.Sprintf("https://%s", c.Domain)) + if err == nil { + c.HTTPClient.Jar.SetCookies(dummyURL, []*http.Cookie{}) + } + } + return nil +} + // GetUserProfile retrieves the current user's full profile func (c *Client) GetUserProfile() (*types.UserProfile, error) { - profileURL := fmt.Sprintf("https://connectapi.%s/userprofile-service/socialProfile", c.Domain) + scheme := "https" + if strings.HasPrefix(c.Domain, "127.0.0.1") { + scheme = "http" + } + profileURL := fmt.Sprintf("%s://%s/userprofile-service/socialProfile", scheme, c.Domain) req, err := http.NewRequest("GET", profileURL, nil) if err != nil { @@ -181,9 +203,13 @@ func (c *Client) GetUserProfile() (*types.UserProfile, error) { // ConnectAPI makes a raw API request to the Garmin Connect API func (c *Client) ConnectAPI(path string, method string, params url.Values, body io.Reader) ([]byte, error) { + scheme := "https" + if strings.HasPrefix(c.Domain, "127.0.0.1") { + scheme = "http" + } u := &url.URL{ - Scheme: "https", - Host: fmt.Sprintf("connectapi.%s", c.Domain), + Scheme: scheme, + Host: c.Domain, Path: path, RawQuery: params.Encode(), } @@ -445,4 +471,4 @@ func (c *Client) LoadSession(filename string) error { c.AuthToken = session.AuthToken return nil -} +} \ No newline at end of file diff --git a/garth/client/client_test.go b/internal/api/client/client_test.go similarity index 71% rename from garth/client/client_test.go rename to internal/api/client/client_test.go index f17a8e6..db60dd6 100644 --- a/garth/client/client_test.go +++ b/internal/api/client/client_test.go @@ -2,15 +2,16 @@ package client_test import ( "net/http" + "net/url" "testing" "time" - "garmin-connect/garth/testutils" + "garmin-connect/internal/testutils" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "garmin-connect/garth/client" + "garmin-connect/internal/api/client" ) func TestClient_GetUserProfile(t *testing.T) { @@ -24,11 +25,11 @@ func TestClient_GetUserProfile(t *testing.T) { defer server.Close() // Create client with test configuration - c := &client.Client{ - Domain: server.URL, - HTTPClient: &http.Client{Timeout: 5 * time.Second}, - AuthToken: "Bearer testtoken", - } + u, _ := url.Parse(server.URL) + c, err := client.NewClient(u.Host) + require.NoError(t, err) + c.HTTPClient = &http.Client{Timeout: 5 * time.Second} + c.AuthToken = "Bearer testtoken" // Get user profile profile, err := c.GetUserProfile() diff --git a/garth/client/http.go b/internal/api/client/http.go similarity index 100% rename from garth/client/http.go rename to internal/api/client/http.go diff --git a/garth/client/http_client.go b/internal/api/client/http_client.go similarity index 100% rename from garth/client/http_client.go rename to internal/api/client/http_client.go diff --git a/garth/client/profile.go b/internal/api/client/profile.go similarity index 100% rename from garth/client/profile.go rename to internal/api/client/profile.go diff --git a/garth/client/settings.go b/internal/api/client/settings.go similarity index 100% rename from garth/client/settings.go rename to internal/api/client/settings.go diff --git a/garth/credentials/credentials.go b/internal/auth/credentials/credentials.go similarity index 100% rename from garth/credentials/credentials.go rename to internal/auth/credentials/credentials.go diff --git a/garth/oauth/oauth.go b/internal/auth/oauth/oauth.go similarity index 98% rename from garth/oauth/oauth.go rename to internal/auth/oauth/oauth.go index 8ba782b..bbb4290 100644 --- a/garth/oauth/oauth.go +++ b/internal/auth/oauth/oauth.go @@ -9,8 +9,8 @@ import ( "strings" "time" - "garmin-connect/garth/types" - "garmin-connect/garth/utils" + "garmin-connect/internal/types" + "garmin-connect/internal/utils" ) // GetOAuth1Token retrieves an OAuth1 token using the provided ticket diff --git a/garth/sso/sso.go b/internal/auth/sso/sso.go similarity index 99% rename from garth/sso/sso.go rename to internal/auth/sso/sso.go index 82fe79a..97a2d75 100644 --- a/garth/sso/sso.go +++ b/internal/auth/sso/sso.go @@ -9,8 +9,8 @@ import ( "strings" "time" - "garmin-connect/garth/oauth" - "garmin-connect/garth/types" + "garmin-connect/internal/auth/oauth" + "garmin-connect/internal/types" ) var ( diff --git a/garth/data/base.go b/internal/data/base.go similarity index 97% rename from garth/data/base.go rename to internal/data/base.go index d55a871..faf0da8 100644 --- a/garth/data/base.go +++ b/internal/data/base.go @@ -5,8 +5,8 @@ import ( "sync" "time" - "garmin-connect/garth/client" - "garmin-connect/garth/utils" + "garmin-connect/internal/api/client" + "garmin-connect/internal/utils" ) // Data defines the interface for Garmin Connect data types. diff --git a/garth/data/base_test.go b/internal/data/base_test.go similarity index 97% rename from garth/data/base_test.go rename to internal/data/base_test.go index fb865f9..6c16548 100644 --- a/garth/data/base_test.go +++ b/internal/data/base_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "garmin-connect/garth/client" + "garmin-connect/internal/api/client" "github.com/stretchr/testify/assert" ) diff --git a/garth/data/body_battery.go b/internal/data/body_battery.go similarity index 99% rename from garth/data/body_battery.go rename to internal/data/body_battery.go index 40f429c..1c332cb 100644 --- a/garth/data/body_battery.go +++ b/internal/data/body_battery.go @@ -6,7 +6,7 @@ import ( "sort" "time" - "garmin-connect/garth/client" + "garmin-connect/internal/api/client" ) // DailyBodyBatteryStress represents complete daily Body Battery and stress data diff --git a/garth/data/body_battery_test.go b/internal/data/body_battery_test.go similarity index 100% rename from garth/data/body_battery_test.go rename to internal/data/body_battery_test.go diff --git a/garth/data/hrv.go b/internal/data/hrv.go similarity index 98% rename from garth/data/hrv.go rename to internal/data/hrv.go index dbae4d7..aecff50 100644 --- a/garth/data/hrv.go +++ b/internal/data/hrv.go @@ -7,8 +7,8 @@ import ( "sort" "time" - "garmin-connect/garth/client" - "garmin-connect/garth/utils" + "garmin-connect/internal/api/client" + "garmin-connect/internal/utils" ) // HRVSummary represents Heart Rate Variability summary data diff --git a/garth/data/sleep.go b/internal/data/sleep.go similarity index 98% rename from garth/data/sleep.go rename to internal/data/sleep.go index 7bf2726..3d3a144 100644 --- a/garth/data/sleep.go +++ b/internal/data/sleep.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "garmin-connect/garth/client" + "garmin-connect/internal/api/client" ) // SleepScores represents sleep scoring data diff --git a/garth/data/weight.go b/internal/data/weight.go similarity index 98% rename from garth/data/weight.go rename to internal/data/weight.go index c378ca6..fa10d8c 100644 --- a/garth/data/weight.go +++ b/internal/data/weight.go @@ -5,7 +5,7 @@ import ( "fmt" "time" - "garmin-connect/garth/client" + "garmin-connect/internal/api/client" ) // WeightData represents weight measurement data diff --git a/garth/errors/errors.go b/internal/errors/errors.go similarity index 100% rename from garth/errors/errors.go rename to internal/errors/errors.go diff --git a/garth/stats/base.go b/internal/stats/base.go similarity index 96% rename from garth/stats/base.go rename to internal/stats/base.go index 0d90461..22fa7f9 100644 --- a/garth/stats/base.go +++ b/internal/stats/base.go @@ -6,8 +6,8 @@ import ( "strings" "time" - "garmin-connect/garth/client" - "garmin-connect/garth/utils" + "garmin-connect/internal/api/client" + "garmin-connect/internal/utils" ) type Stats interface { diff --git a/garth/stats/hrv.go b/internal/stats/hrv.go similarity index 100% rename from garth/stats/hrv.go rename to internal/stats/hrv.go diff --git a/garth/stats/hrv_weekly.go b/internal/stats/hrv_weekly.go similarity index 100% rename from garth/stats/hrv_weekly.go rename to internal/stats/hrv_weekly.go diff --git a/garth/stats/hydration.go b/internal/stats/hydration.go similarity index 100% rename from garth/stats/hydration.go rename to internal/stats/hydration.go diff --git a/garth/stats/intensity_minutes.go b/internal/stats/intensity_minutes.go similarity index 100% rename from garth/stats/intensity_minutes.go rename to internal/stats/intensity_minutes.go diff --git a/garth/stats/sleep.go b/internal/stats/sleep.go similarity index 100% rename from garth/stats/sleep.go rename to internal/stats/sleep.go diff --git a/garth/stats/steps.go b/internal/stats/steps.go similarity index 100% rename from garth/stats/steps.go rename to internal/stats/steps.go diff --git a/garth/stats/stress.go b/internal/stats/stress.go similarity index 100% rename from garth/stats/stress.go rename to internal/stats/stress.go diff --git a/garth/stats/stress_weekly.go b/internal/stats/stress_weekly.go similarity index 100% rename from garth/stats/stress_weekly.go rename to internal/stats/stress_weekly.go diff --git a/garth/testutils/http.go b/internal/testutils/http.go similarity index 100% rename from garth/testutils/http.go rename to internal/testutils/http.go diff --git a/garth/testutils/mock_client.go b/internal/testutils/mock_client.go similarity index 92% rename from garth/testutils/mock_client.go rename to internal/testutils/mock_client.go index b327d17..2698dc8 100644 --- a/garth/testutils/mock_client.go +++ b/internal/testutils/mock_client.go @@ -5,7 +5,7 @@ import ( "io" "net/url" - "garmin-connect/garth/client" + "garmin-connect/internal/api/client" ) // MockClient simulates API client for tests diff --git a/internal/types/auth.go b/internal/types/auth.go new file mode 100644 index 0000000..bec5e65 --- /dev/null +++ b/internal/types/auth.go @@ -0,0 +1,28 @@ +package types + +import "time" + +// OAuthConsumer represents OAuth consumer credentials +type OAuthConsumer struct { + ConsumerKey string `json:"consumer_key"` + ConsumerSecret string `json:"consumer_secret"` +} + +// OAuth1Token represents OAuth1 token response +type OAuth1Token struct { + OAuthToken string `json:"oauth_token"` + OAuthTokenSecret string `json:"oauth_token_secret"` + MFAToken string `json:"mfa_token,omitempty"` + Domain string `json:"domain"` +} + +// OAuth2Token represents OAuth2 token response +type OAuth2Token struct { + AccessToken string `json:"access_token"` + TokenType string `json:"token_type"` + ExpiresIn int `json:"expires_in"` + RefreshToken string `json:"refresh_token"` + Scope string `json:"scope"` + CreatedAt time.Time // Used for expiration tracking + ExpiresAt time.Time // Computed expiration time +} \ No newline at end of file diff --git a/garth/types/types.go b/internal/types/garmin.go similarity index 57% rename from garth/types/types.go rename to internal/types/garmin.go index 90eacfc..e88ac78 100644 --- a/garth/types/types.go +++ b/internal/types/garmin.go @@ -1,40 +1,12 @@ package types -import ( - "encoding/json" - "net/http" - "time" -) +import "time" // GarminTime represents Garmin's timestamp format with custom JSON parsing type GarminTime struct { time.Time } -// UnmarshalJSON handles Garmin's timestamp format -func (gt *GarminTime) UnmarshalJSON(b []byte) error { - var s string - if err := json.Unmarshal(b, &s); err != nil { - return err - } - t, err := time.Parse("2006-01-02T15:04:05", s) - if err != nil { - return err - } - gt.Time = t - return nil -} - -// Client represents the Garmin Connect client -type Client struct { - Domain string - HTTPClient *http.Client - Username string - AuthToken string - OAuth1Token *OAuth1Token - OAuth2Token *OAuth2Token -} - // SessionData represents saved session information type SessionData struct { Domain string `json:"domain"` @@ -77,14 +49,6 @@ type Activity struct { MaxHR float64 `json:"maxHR"` } -// OAuth1Token represents OAuth1 token response -type OAuth1Token struct { - OAuthToken string `json:"oauth_token"` - OAuthTokenSecret string `json:"oauth_token_secret"` - MFAToken string `json:"mfa_token,omitempty"` - Domain string `json:"domain"` -} - // UserProfile represents a Garmin user profile type UserProfile struct { UserName string `json:"userName"` @@ -92,20 +56,3 @@ type UserProfile struct { LevelUpdateDate GarminTime `json:"levelUpdateDate"` // Add other fields as needed from API response } - -// OAuth2Token represents OAuth2 token response -type OAuth2Token struct { - AccessToken string `json:"access_token"` - TokenType string `json:"token_type"` - ExpiresIn int `json:"expires_in"` - RefreshToken string `json:"refresh_token"` - Scope string `json:"scope"` - CreatedAt time.Time // Used for expiration tracking - ExpiresAt time.Time // Computed expiration time -} - -// OAuthConsumer represents OAuth consumer credentials -type OAuthConsumer struct { - ConsumerKey string `json:"consumer_key"` - ConsumerSecret string `json:"consumer_secret"` -} diff --git a/garth/users/profile.go b/internal/users/profile.go similarity index 100% rename from garth/users/profile.go rename to internal/users/profile.go diff --git a/garth/users/settings.go b/internal/users/settings.go similarity index 99% rename from garth/users/settings.go rename to internal/users/settings.go index 0fd0f75..5335443 100644 --- a/garth/users/settings.go +++ b/internal/users/settings.go @@ -3,7 +3,7 @@ package users import ( "time" - "garmin-connect/garth/client" + "garmin-connect/internal/api/client" ) type PowerFormat struct { diff --git a/garth/utils/timeutils.go b/internal/utils/timeutils.go similarity index 100% rename from garth/utils/timeutils.go rename to internal/utils/timeutils.go diff --git a/garth/utils/utils.go b/internal/utils/utils.go similarity index 99% rename from garth/utils/utils.go rename to internal/utils/utils.go index a7e6e17..8ec6527 100644 --- a/garth/utils/utils.go +++ b/internal/utils/utils.go @@ -6,7 +6,7 @@ import ( "crypto/sha1" "encoding/base64" "encoding/json" - "garmin-connect/garth/types" + "garmin-connect/internal/types" "net/http" "net/url" "regexp" diff --git a/main.go b/main.go index a694477..d8f2be2 100644 --- a/main.go +++ b/main.go @@ -5,9 +5,9 @@ import ( "log" "time" - "garmin-connect/garth/client" - "garmin-connect/garth/credentials" - "garmin-connect/garth/types" + "garmin-connect/internal/api/client" + "garmin-connect/internal/auth/credentials" + types "garmin-connect/pkg/garmin" ) func main() { diff --git a/phase1.md b/phase1.md new file mode 100644 index 0000000..111d439 --- /dev/null +++ b/phase1.md @@ -0,0 +1,529 @@ +# Phase 1: Core Functionality Implementation Plan +**Duration: 2-3 weeks** +**Goal: Establish solid foundation with enhanced CLI and core missing features** + +## Overview +Phase 1 focuses on building the essential functionality that users need immediately while establishing the foundation for future enhancements. This phase prioritizes user-facing features and basic API improvements. + +--- + +## Subphase 1A: Package Reorganization & CLI Foundation (Days 1-3) + +### Objectives +- Restructure packages for better maintainability +- Set up cobra-based CLI framework +- Establish consistent naming conventions + +### Tasks + +#### 1A.1: Package Structure Refactoring +**Duration: 1 day** + +``` +Current Structure → New Structure +garth/ pkg/garmin/ +├── client/ ├── client.go # Main client interface +├── data/ ├── activities.go # Activity operations +├── stats/ ├── health.go # Health data operations +├── sso/ ├── stats.go # Statistics operations +├── oauth/ ├── auth.go # Authentication +└── ... └── types.go # Public types + + internal/ + ├── api/ # Low-level API client + ├── auth/ # Auth implementation + ├── data/ # Data processing + └── utils/ # Internal utilities + + cmd/garth/ + ├── main.go # CLI entry point + ├── root.go # Root command + ├── auth.go # Auth commands + ├── activities.go # Activity commands + ├── health.go # Health commands + └── stats.go # Stats commands +``` + +**Deliverables:** +- [ ] New package structure implemented +- [ ] All imports updated +- [ ] No breaking changes to existing functionality +- [ ] Package documentation updated + +#### 1A.2: CLI Framework Setup +**Duration: 1 day** + +```go +// cmd/garth/root.go +var rootCmd = &cobra.Command{ + Use: "garth", + Short: "Garmin Connect CLI tool", + Long: `A comprehensive CLI tool for interacting with Garmin Connect`, +} + +// Global flags +var ( + configFile string + outputFormat string // json, table, csv + verbose bool + dateFrom string + dateTo string +) +``` + +**Tasks:** +- [ ] Install and configure cobra +- [ ] Create root command with global flags +- [ ] Implement configuration file loading +- [ ] Add output formatting infrastructure +- [ ] Create help text and usage examples + +**Deliverables:** +- [ ] Working CLI framework with `garth --help` +- [ ] Configuration file support +- [ ] Output formatting (JSON, table, CSV) + +#### 1A.3: Configuration Management +**Duration: 1 day** + +```go +// internal/config/config.go +type Config struct { + Auth struct { + Email string `yaml:"email"` + Domain string `yaml:"domain"` + Session string `yaml:"session_file"` + } `yaml:"auth"` + + Output struct { + Format string `yaml:"format"` + File string `yaml:"file"` + } `yaml:"output"` + + Cache struct { + Enabled bool `yaml:"enabled"` + TTL string `yaml:"ttl"` + Dir string `yaml:"dir"` + } `yaml:"cache"` +} +``` + +**Tasks:** +- [ ] Design configuration schema +- [ ] Implement config file loading/saving +- [ ] Add environment variable support +- [ ] Create config validation +- [ ] Add config commands (`garth config init`, `garth config show`) + +**Deliverables:** +- [ ] Configuration system working +- [ ] Default config file created +- [ ] Config commands implemented + +--- + +## Subphase 1B: Enhanced CLI Commands (Days 4-7) + +### Objectives +- Implement all major CLI commands +- Add interactive features +- Ensure consistent user experience + +### Tasks + +#### 1B.1: Authentication Commands +**Duration: 1 day** + +```bash +# Target CLI interface +garth auth login # Interactive login +garth auth login --email user@example.com --password-stdin +garth auth logout # Clear session +garth auth status # Show auth status +garth auth refresh # Refresh tokens +``` + +```go +// cmd/garth/auth.go +var authCmd = &cobra.Command{ + Use: "auth", + Short: "Authentication management", +} + +var loginCmd = &cobra.Command{ + Use: "login", + Short: "Login to Garmin Connect", + RunE: runLogin, +} +``` + +**Tasks:** +- [ ] Implement `auth login` with interactive prompts +- [ ] Add `auth logout` functionality +- [ ] Create `auth status` command +- [ ] Implement secure password input +- [ ] Add MFA support (prepare for future) +- [ ] Session validation and refresh + +**Deliverables:** +- [ ] All auth commands working +- [ ] Secure credential handling +- [ ] Session persistence working + +#### 1B.2: Activity Commands +**Duration: 2 days** + +```bash +# Target CLI interface +garth activities list # Recent activities +garth activities list --limit 50 --type running +garth activities get 12345678 # Activity details +garth activities download 12345678 --format gpx +garth activities search --query "morning run" +``` + +```go +// pkg/garmin/activities.go +type ActivityOptions struct { + Limit int + Offset int + ActivityType string + DateFrom time.Time + DateTo time.Time +} + +type ActivityDetail struct { + BasicInfo Activity + Summary ActivitySummary + Laps []Lap + Metrics []Metric +} +``` + +**Tasks:** +- [ ] Enhanced activity listing with filters +- [ ] Activity detail fetching +- [ ] Search functionality +- [ ] Table formatting for activity lists +- [ ] Activity download preparation (basic structure) +- [ ] Date range filtering +- [ ] Activity type filtering + +**Deliverables:** +- [ ] `activities list` with all filtering options +- [ ] `activities get` showing detailed info +- [ ] `activities search` functionality +- [ ] Proper error handling and user feedback + +#### 1B.3: Health Data Commands +**Duration: 2 days** + +```bash +# Target CLI interface +garth health sleep --from 2024-01-01 --to 2024-01-07 +garth health hrv --days 30 +garth health stress --week +garth health bodybattery --yesterday +``` + +**Tasks:** +- [ ] Implement all health data commands +- [ ] Add date range parsing utilities +- [ ] Create consistent output formatting +- [ ] Add data aggregation options +- [ ] Implement caching for expensive operations +- [ ] Error handling for missing data + +**Deliverables:** +- [ ] All health commands working +- [ ] Consistent date filtering across commands +- [ ] Proper data formatting and display + +#### 1B.4: Statistics Commands +**Duration: 1 day** + +```bash +# Target CLI interface +garth stats steps --month +garth stats distance --year +garth stats calories --from 2024-01-01 +``` + +**Tasks:** +- [ ] Implement statistics commands +- [ ] Add aggregation periods (day, week, month, year) +- [ ] Create summary statistics +- [ ] Add trend analysis +- [ ] Implement data export options + +**Deliverables:** +- [ ] All stats commands working +- [ ] Multiple aggregation options +- [ ] Export functionality + +--- + +## Subphase 1C: Activity Download Implementation (Days 8-12) + +### Objectives +- Implement activity file downloading +- Support multiple formats (GPX, TCX, FIT) +- Add batch download capabilities + +### Tasks + +#### 1C.1: Core Download Infrastructure +**Duration: 2 days** + +```go +// pkg/garmin/activities.go +type DownloadOptions struct { + Format string // "gpx", "tcx", "fit", "csv" + Original bool // Download original uploaded file + OutputDir string + Filename string +} + +func (c *Client) DownloadActivity(id string, opts *DownloadOptions) error { + // Implementation +} +``` + +**Tasks:** +- [ ] Research Garmin's download endpoints +- [ ] Implement format detection and conversion +- [ ] Add file writing with proper naming +- [ ] Implement progress indication +- [ ] Add download validation +- [ ] Error handling for failed downloads + +**Deliverables:** +- [ ] Working download for at least GPX format +- [ ] Progress indication during download +- [ ] Proper error handling + +#### 1C.2: Multi-Format Support +**Duration: 2 days** + +**Tasks:** +- [ ] Implement TCX format download +- [ ] Implement FIT format download (if available) +- [ ] Add CSV export for activity summaries +- [ ] Format validation and conversion +- [ ] Add format-specific options + +**Deliverables:** +- [ ] Support for GPX, TCX, and CSV formats +- [ ] Format auto-detection +- [ ] Format-specific download options + +#### 1C.3: Batch Download Features +**Duration: 1 day** + +```bash +# Target functionality +garth activities download --all --type running --format gpx +garth activities download --from 2024-01-01 --to 2024-01-31 +``` + +**Tasks:** +- [ ] Implement batch download with filtering +- [ ] Add parallel download support +- [ ] Progress bars for multiple downloads +- [ ] Resume interrupted downloads +- [ ] Duplicate detection and handling + +**Deliverables:** +- [ ] Batch download working +- [ ] Parallel processing implemented +- [ ] Resume capability + +--- + +## Subphase 1D: Missing Health Data Types (Days 13-15) + +### Objectives +- Implement VO2 max data fetching +- Add heart rate zones +- Complete missing health metrics + +### Tasks + +#### 1D.1: VO2 Max Implementation +**Duration: 1 day** + +```go +// pkg/garmin/health.go +type VO2MaxData struct { + Running *VO2MaxReading `json:"running"` + Cycling *VO2MaxReading `json:"cycling"` + Updated time.Time `json:"updated"` + History []VO2MaxHistory `json:"history"` +} + +type VO2MaxReading struct { + Value float64 `json:"value"` + UpdatedAt time.Time `json:"updated_at"` + Source string `json:"source"` + Confidence string `json:"confidence"` +} +``` + +**Tasks:** +- [ ] Research VO2 max API endpoints +- [ ] Implement data fetching +- [ ] Add historical data support +- [ ] Create CLI command +- [ ] Add data validation +- [ ] Format output appropriately + +**Deliverables:** +- [ ] `garth health vo2max` command working +- [ ] Historical data support +- [ ] Both running and cycling metrics + +#### 1D.2: Heart Rate Zones +**Duration: 1 day** + +```go +type HeartRateZones struct { + RestingHR int `json:"resting_hr"` + MaxHR int `json:"max_hr"` + LactateThreshold int `json:"lactate_threshold"` + Zones []HRZone `json:"zones"` + UpdatedAt time.Time `json:"updated_at"` +} + +type HRZone struct { + Zone int `json:"zone"` + MinBPM int `json:"min_bpm"` + MaxBPM int `json:"max_bpm"` + Name string `json:"name"` +} +``` + +**Tasks:** +- [ ] Implement HR zones API calls +- [ ] Add zone calculation logic +- [ ] Create CLI command +- [ ] Add zone analysis features +- [ ] Implement zone updates (if possible) + +**Deliverables:** +- [ ] `garth health hr-zones` command +- [ ] Zone calculation and display +- [ ] Integration with other health metrics + +#### 1D.3: Additional Health Metrics +**Duration: 1 day** + +```go +type WellnessData struct { + Date time.Time `json:"date"` + RestingHR *int `json:"resting_hr"` + Weight *float64 `json:"weight"` + BodyFat *float64 `json:"body_fat"` + BMI *float64 `json:"bmi"` + BodyWater *float64 `json:"body_water"` + BoneMass *float64 `json:"bone_mass"` + MuscleMass *float64 `json:"muscle_mass"` +} +``` + +**Tasks:** +- [ ] Research additional wellness endpoints +- [ ] Implement body composition data +- [ ] Add resting heart rate trends +- [ ] Create comprehensive wellness command +- [ ] Add data correlation features + +**Deliverables:** +- [ ] Additional health metrics available +- [ ] Wellness overview command +- [ ] Data trend analysis + +--- + +## Phase 1 Testing & Quality Assurance (Days 14-15) + +### Tasks + +#### Integration Testing +- [ ] End-to-end CLI testing +- [ ] Authentication flow testing +- [ ] Data fetching validation +- [ ] Error handling verification + +#### Documentation +- [ ] Update README with new CLI commands +- [ ] Add usage examples +- [ ] Document configuration options +- [ ] Create troubleshooting guide + +#### Performance Testing +- [ ] Concurrent operation testing +- [ ] Memory usage validation +- [ ] Download performance testing +- [ ] Large dataset handling + +--- + +## Phase 1 Deliverables Checklist + +### CLI Tool +- [ ] Complete CLI with all major commands +- [ ] Configuration file support +- [ ] Multiple output formats (JSON, table, CSV) +- [ ] Interactive authentication +- [ ] Progress indicators for long operations + +### Core Functionality +- [ ] Activity listing with filtering +- [ ] Activity detail fetching +- [ ] Activity downloading (GPX, TCX, CSV) +- [ ] All existing health data accessible via CLI +- [ ] VO2 max and heart rate zone data + +### Code Quality +- [ ] Reorganized package structure +- [ ] Consistent error handling +- [ ] Comprehensive logging +- [ ] Basic test coverage (>60%) +- [ ] Documentation updated + +### User Experience +- [ ] Intuitive command structure +- [ ] Helpful error messages +- [ ] Progress feedback +- [ ] Consistent data formatting +- [ ] Working examples and documentation + +--- + +## Success Criteria + +1. **CLI Completeness**: All major Garmin data types accessible via CLI +2. **Usability**: New users can get started within 5 minutes +3. **Reliability**: Commands work consistently without errors +4. **Performance**: Downloads and data fetching perform well +5. **Documentation**: Clear examples and troubleshooting available + +## Risks & Mitigations + +| Risk | Impact | Mitigation | +|------|--------|------------| +| API endpoint changes | High | Create abstraction layer, add endpoint validation | +| Authentication issues | High | Implement robust error handling and retry logic | +| Download format limitations | Medium | Start with GPX, add others incrementally | +| Performance with large datasets | Medium | Implement pagination and caching | +| Package reorganization complexity | Medium | Do incrementally with thorough testing | + +## Dependencies + +- Cobra CLI framework +- Garmin Connect API stability +- OAuth flow reliability +- File system permissions for downloads +- Network connectivity for API calls + +This phase establishes the foundation for all subsequent development while delivering immediate value to users through a comprehensive CLI tool. \ No newline at end of file diff --git a/pkg/garmin/activities.go b/pkg/garmin/activities.go new file mode 100644 index 0000000..67fa90c --- /dev/null +++ b/pkg/garmin/activities.go @@ -0,0 +1 @@ +package garmin \ No newline at end of file diff --git a/pkg/garmin/auth.go b/pkg/garmin/auth.go new file mode 100644 index 0000000..67fa90c --- /dev/null +++ b/pkg/garmin/auth.go @@ -0,0 +1 @@ +package garmin \ No newline at end of file diff --git a/garth/benchmark_test.go b/pkg/garmin/benchmark_test.go similarity index 95% rename from garth/benchmark_test.go rename to pkg/garmin/benchmark_test.go index 88d0df8..a86be7a 100644 --- a/garth/benchmark_test.go +++ b/pkg/garmin/benchmark_test.go @@ -1,10 +1,10 @@ -package garth_test +package garmin_test import ( "encoding/json" - "garmin-connect/garth/client" - "garmin-connect/garth/data" - "garmin-connect/garth/testutils" + "garmin-connect/internal/api/client" + "garmin-connect/internal/data" + "garmin-connect/internal/testutils" "testing" "time" ) diff --git a/pkg/garmin/client.go b/pkg/garmin/client.go new file mode 100644 index 0000000..72aa0fb --- /dev/null +++ b/pkg/garmin/client.go @@ -0,0 +1,50 @@ +package garmin + +import ( + internalClient "garmin-connect/internal/api/client" + "garmin-connect/internal/types" +) + +// Client is the main Garmin Connect client type +type Client struct { + Client *internalClient.Client +} + +// NewClient creates a new Garmin Connect client +func NewClient(domain string) (*Client, error) { + c, err := internalClient.NewClient(domain) + if err != nil { + return nil, err + } + return &Client{Client: c}, nil +} + +// Login authenticates to Garmin Connect +func (c *Client) Login(email, password string) error { + return c.Client.Login(email, password) +} + +// LoadSession loads a session from a file +func (c *Client) LoadSession(filename string) error { + return c.Client.LoadSession(filename) +} + +// SaveSession saves the current session to a file +func (c *Client) SaveSession(filename string) error { + return c.Client.SaveSession(filename) +} + +// GetActivities retrieves recent activities +func (c *Client) GetActivities(limit int) ([]Activity, error) { + return c.Client.GetActivities(limit) +} + +// OAuth1Token returns the OAuth1 token +func (c *Client) OAuth1Token() *types.OAuth1Token { + return c.Client.OAuth1Token +} + +// OAuth2Token returns the OAuth2 token +func (c *Client) OAuth2Token() *types.OAuth2Token { + return c.Client.OAuth2Token +} diff --git a/garth/doc.go b/pkg/garmin/doc.go similarity index 99% rename from garth/doc.go rename to pkg/garmin/doc.go index 73ae9a2..a8657ce 100644 --- a/garth/doc.go +++ b/pkg/garmin/doc.go @@ -43,4 +43,4 @@ // - Steps List (7 days): 1216x faster // // See README.md for additional usage examples and CLI tool documentation. -package garth +package garmin diff --git a/pkg/garmin/health.go b/pkg/garmin/health.go new file mode 100644 index 0000000..2e82eaf --- /dev/null +++ b/pkg/garmin/health.go @@ -0,0 +1,58 @@ +package garmin + +import ( + "garmin-connect/internal/data" + "time" +) + +// BodyBatteryData represents Body Battery data. +type BodyBatteryData = data.DailyBodyBatteryStress + +// SleepData represents sleep data. +type SleepData = data.DailySleepDTO + +// HRVData represents HRV data. +type HRVData = data.HRVData + +// WeightData represents weight data. +type WeightData = data.WeightData + +// GetBodyBattery retrieves Body Battery data for a given date. +func (c *Client) GetBodyBattery(date time.Time) (*BodyBatteryData, error) { + bb := &data.DailyBodyBatteryStress{} + result, err := bb.Get(date, c.Client) + if err != nil { + return nil, err + } + return result.(*BodyBatteryData), nil +} + +// GetSleep retrieves sleep data for a given date. +func (c *Client) GetSleep(date time.Time) (*SleepData, error) { + sleep := &data.DailySleepDTO{} + result, err := sleep.Get(date, c.Client) + if err != nil { + return nil, err + } + return result.(*SleepData), nil +} + +// GetHRV retrieves HRV data for a given date. +func (c *Client) GetHRV(date time.Time) (*HRVData, error) { + hrv := &data.HRVData{} + result, err := hrv.Get(date, c.Client) + if err != nil { + return nil, err + } + return result.(*HRVData), nil +} + +// GetWeight retrieves weight data for a given date. +func (c *Client) GetWeight(date time.Time) (*WeightData, error) { + weight := &data.WeightData{} + result, err := weight.Get(date, c.Client) + if err != nil { + return nil, err + } + return result.(*WeightData), nil +} diff --git a/garth/integration_test.go b/pkg/garmin/integration_test.go similarity index 95% rename from garth/integration_test.go rename to pkg/garmin/integration_test.go index 3c47076..ff78c32 100644 --- a/garth/integration_test.go +++ b/pkg/garmin/integration_test.go @@ -1,12 +1,12 @@ -package garth_test +package garmin_test import ( "testing" "time" - "garmin-connect/garth/client" - "garmin-connect/garth/data" - "garmin-connect/garth/stats" + "garmin-connect/internal/api/client" + "garmin-connect/internal/data" + "garmin-connect/internal/stats" ) func TestBodyBatteryIntegration(t *testing.T) { diff --git a/pkg/garmin/stats.go b/pkg/garmin/stats.go new file mode 100644 index 0000000..737baac --- /dev/null +++ b/pkg/garmin/stats.go @@ -0,0 +1,38 @@ +package garmin + +import ( + "garmin-connect/internal/stats" +) + +// Stats is an interface for stats data types. +type Stats = stats.Stats + +// NewDailySteps creates a new DailySteps stats type. +func NewDailySteps() Stats { + return stats.NewDailySteps() +} + +// NewDailyStress creates a new DailyStress stats type. +func NewDailyStress() Stats { + return stats.NewDailyStress() +} + +// NewDailyHydration creates a new DailyHydration stats type. +func NewDailyHydration() Stats { + return stats.NewDailyHydration() +} + +// NewDailyIntensityMinutes creates a new DailyIntensityMinutes stats type. +func NewDailyIntensityMinutes() Stats { + return stats.NewDailyIntensityMinutes() +} + +// NewDailySleep creates a new DailySleep stats type. +func NewDailySleep() Stats { + return stats.NewDailySleep() +} + +// NewDailyHRV creates a new DailyHRV stats type. +func NewDailyHRV() Stats { + return stats.NewDailyHRV() +} diff --git a/pkg/garmin/types.go b/pkg/garmin/types.go new file mode 100644 index 0000000..dabfd9a --- /dev/null +++ b/pkg/garmin/types.go @@ -0,0 +1,27 @@ +package garmin + +import "garmin-connect/internal/types" + +// GarminTime represents Garmin's timestamp format with custom JSON parsing +type GarminTime = types.GarminTime + +// SessionData represents saved session information +type SessionData = types.SessionData + +// ActivityType represents the type of activity +type ActivityType = types.ActivityType + +// EventType represents the event type of an activity +type EventType = types.EventType + +// Activity represents a Garmin Connect activity +type Activity = types.Activity + +// UserProfile represents a Garmin user profile +type UserProfile = types.UserProfile + +// OAuth1Token represents OAuth1 token response +type OAuth1Token = types.OAuth1Token + +// OAuth2Token represents OAuth2 token response +type OAuth2Token = types.OAuth2Token \ No newline at end of file