mirror of
https://github.com/sstent/go-garth.git
synced 2026-03-16 01:55:22 +00:00
chaned go mod name
This commit is contained in:
@@ -15,7 +15,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"go-garth/pkg/garmin"
|
"github.com/sstent/go-garth/pkg/garmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import (
|
|||||||
|
|
||||||
"golang.org/x/term"
|
"golang.org/x/term"
|
||||||
|
|
||||||
"go-garth/pkg/garmin"
|
"github.com/sstent/go-garth/pkg/garmin"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/auth/credentials"
|
"github.com/sstent/go-garth/internal/auth/credentials"
|
||||||
"go-garth/pkg/garmin"
|
"github.com/sstent/go-garth/pkg/garmin"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/auth/credentials"
|
"github.com/sstent/go-garth/internal/auth/credentials"
|
||||||
"go-garth/pkg/garmin"
|
"github.com/sstent/go-garth/pkg/garmin"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/auth/credentials"
|
"github.com/sstent/go-garth/internal/auth/credentials"
|
||||||
"go-garth/pkg/garmin"
|
"github.com/sstent/go-garth/pkg/garmin"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
|
||||||
"go-garth/internal/auth/credentials"
|
"github.com/sstent/go-garth/internal/auth/credentials"
|
||||||
"go-garth/pkg/garmin"
|
"github.com/sstent/go-garth/pkg/garmin"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
"go-garth/internal/config"
|
"github.com/sstent/go-garth/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
@@ -12,9 +12,9 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"go-garth/internal/data" // Import the data package
|
"github.com/sstent/go-garth/internal/data" // Import the data package
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
"go-garth/pkg/garmin"
|
"github.com/sstent/go-garth/pkg/garmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
"go-garth/internal/config"
|
"github.com/sstent/go-garth/internal/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -11,8 +11,8 @@ import (
|
|||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"github.com/spf13/viper"
|
"github.com/spf13/viper"
|
||||||
|
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
"go-garth/pkg/garmin"
|
"github.com/sstent/go-garth/pkg/garmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -1,4 +1,4 @@
|
|||||||
module go-garth
|
module github.com/sstent/go-garth
|
||||||
|
|
||||||
go 1.24.2
|
go 1.24.2
|
||||||
|
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ package client_test
|
|||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"go-garth/internal/api/client"
|
"github.com/sstent/go-garth/internal/api/client"
|
||||||
"go-garth/internal/auth/credentials"
|
"github.com/sstent/go-garth/internal/auth/credentials"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|||||||
@@ -14,11 +14,11 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/auth/sso"
|
"github.com/sstent/go-garth/internal/auth/sso"
|
||||||
"go-garth/internal/errors"
|
"github.com/sstent/go-garth/internal/errors"
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
shared "go-garth/shared/interfaces"
|
shared "github.com/sstent/go-garth/shared/interfaces"
|
||||||
models "go-garth/shared/models"
|
models "github.com/sstent/go-garth/shared/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client represents the Garmin Connect API client
|
// Client represents the Garmin Connect API client
|
||||||
|
|||||||
@@ -7,12 +7,12 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/testutils"
|
"github.com/sstent/go-garth/internal/testutils"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"go-garth/internal/api/client"
|
"github.com/sstent/go-garth/internal/api/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestClient_GetUserProfile(t *testing.T) {
|
func TestClient_GetUserProfile(t *testing.T) {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
// LoadEnvCredentials loads credentials from .env file
|
// LoadEnvCredentials loads credentials from .env file
|
||||||
func LoadEnvCredentials() (email, password, domain string, err error) {
|
func LoadEnvCredentials() (email, password, domain string, err error) {
|
||||||
// Determine project root (assuming .env is in the project root)
|
// Determine project root (assuming .env is in the project root)
|
||||||
projectRoot := "/home/sstent/Projects/go-garth"
|
projectRoot := "/home/sstent/Projects/github.com/sstent/go-garth"
|
||||||
envPath := filepath.Join(projectRoot, ".env")
|
envPath := filepath.Join(projectRoot, ".env")
|
||||||
|
|
||||||
// Load .env file
|
// Load .env file
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/models/types"
|
"github.com/sstent/go-garth/internal/models/types"
|
||||||
"go-garth/internal/utils"
|
"github.com/sstent/go-garth/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetOAuth1Token retrieves an OAuth1 token using the provided ticket
|
// GetOAuth1Token retrieves an OAuth1 token using the provided ticket
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/auth/oauth"
|
"github.com/sstent/go-garth/internal/auth/oauth"
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/api/client"
|
"github.com/sstent/go-garth/internal/api/client"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
shared "go-garth/shared/interfaces"
|
shared "github.com/sstent/go-garth/shared/interfaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
// BodyBatteryReading represents a single body battery data point
|
// BodyBatteryReading represents a single body battery data point
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package data
|
package data
|
||||||
|
|
||||||
import (
|
import (
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"sort"
|
"sort"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
shared "go-garth/shared/interfaces"
|
shared "github.com/sstent/go-garth/shared/interfaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DailyHRVDataWithMethods embeds types.DailyHRVData and adds methods
|
// DailyHRVDataWithMethods embeds types.DailyHRVData and adds methods
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
shared "go-garth/shared/interfaces"
|
shared "github.com/sstent/go-garth/shared/interfaces"
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DailySleepDTO represents daily sleep data
|
// DailySleepDTO represents daily sleep data
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
shared "go-garth/shared/interfaces"
|
shared "github.com/sstent/go-garth/shared/interfaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DetailedSleepDataWithMethods embeds types.DetailedSleepData and adds methods
|
// DetailedSleepDataWithMethods embeds types.DetailedSleepData and adds methods
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
shared "go-garth/shared/interfaces"
|
shared "github.com/sstent/go-garth/shared/interfaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
// TrainingStatusWithMethods embeds types.TrainingStatus and adds methods
|
// TrainingStatusWithMethods embeds types.TrainingStatus and adds methods
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
shared "go-garth/shared/interfaces"
|
shared "github.com/sstent/go-garth/shared/interfaces"
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
// VO2MaxData implements the Data interface for VO2 max retrieval
|
// VO2MaxData implements the Data interface for VO2 max retrieval
|
||||||
|
|||||||
@@ -4,8 +4,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/api/client"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
"go-garth/internal/models"
|
"github.com/sstent/go-garth/shared/interfaces"
|
||||||
|
"github.com/sstent/go-garth/shared/models"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
@@ -14,9 +15,9 @@ func TestVO2MaxData_Get(t *testing.T) {
|
|||||||
// Setup
|
// Setup
|
||||||
runningVO2 := 45.0
|
runningVO2 := 45.0
|
||||||
cyclingVO2 := 50.0
|
cyclingVO2 := 50.0
|
||||||
settings := &client.UserSettings{
|
settings := &models.UserSettings{
|
||||||
ID: 12345,
|
ID: 12345,
|
||||||
UserData: client.UserData{
|
UserData: models.UserData{
|
||||||
VO2MaxRunning: &runningVO2,
|
VO2MaxRunning: &runningVO2,
|
||||||
VO2MaxCycling: &cyclingVO2,
|
VO2MaxCycling: &cyclingVO2,
|
||||||
},
|
},
|
||||||
@@ -25,14 +26,14 @@ func TestVO2MaxData_Get(t *testing.T) {
|
|||||||
vo2Data := NewVO2MaxData()
|
vo2Data := NewVO2MaxData()
|
||||||
|
|
||||||
// Mock the get function
|
// Mock the get function
|
||||||
vo2Data.GetFunc = func(day time.Time, c *client.Client) (interface{}, error) {
|
vo2Data.GetFunc = func(day time.Time, c interfaces.APIClient) (interface{}, error) {
|
||||||
vo2Profile := &models.VO2MaxProfile{
|
vo2Profile := &types.VO2MaxProfile{
|
||||||
UserProfilePK: settings.ID,
|
UserProfilePK: settings.ID,
|
||||||
LastUpdated: time.Now(),
|
LastUpdated: time.Now(),
|
||||||
}
|
}
|
||||||
|
|
||||||
if settings.UserData.VO2MaxRunning != nil && *settings.UserData.VO2MaxRunning > 0 {
|
if settings.UserData.VO2MaxRunning != nil && *settings.UserData.VO2MaxRunning > 0 {
|
||||||
vo2Profile.Running = &models.VO2MaxEntry{
|
vo2Profile.Running = &types.VO2MaxEntry{
|
||||||
Value: *settings.UserData.VO2MaxRunning,
|
Value: *settings.UserData.VO2MaxRunning,
|
||||||
ActivityType: "running",
|
ActivityType: "running",
|
||||||
Date: day,
|
Date: day,
|
||||||
@@ -41,7 +42,7 @@ func TestVO2MaxData_Get(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if settings.UserData.VO2MaxCycling != nil && *settings.UserData.VO2MaxCycling > 0 {
|
if settings.UserData.VO2MaxCycling != nil && *settings.UserData.VO2MaxCycling > 0 {
|
||||||
vo2Profile.Cycling = &models.VO2MaxEntry{
|
vo2Profile.Cycling = &types.VO2MaxEntry{
|
||||||
Value: *settings.UserData.VO2MaxCycling,
|
Value: *settings.UserData.VO2MaxCycling,
|
||||||
ActivityType: "cycling",
|
ActivityType: "cycling",
|
||||||
Date: day,
|
Date: day,
|
||||||
@@ -58,7 +59,7 @@ func TestVO2MaxData_Get(t *testing.T) {
|
|||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
assert.NotNil(t, result)
|
assert.NotNil(t, result)
|
||||||
|
|
||||||
profile, ok := result.(*models.VO2MaxProfile)
|
profile, ok := result.(*types.VO2MaxProfile)
|
||||||
assert.True(t, ok)
|
assert.True(t, ok)
|
||||||
assert.Equal(t, 12345, profile.UserProfilePK)
|
assert.Equal(t, 12345, profile.UserProfilePK)
|
||||||
assert.NotNil(t, profile.Running)
|
assert.NotNil(t, profile.Running)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
shared "go-garth/shared/interfaces"
|
shared "github.com/sstent/go-garth/shared/interfaces"
|
||||||
)
|
)
|
||||||
|
|
||||||
// WeightData represents weight data
|
// WeightData represents weight data
|
||||||
|
|||||||
469
internal/models/types/types.go
Normal file
469
internal/models/types/types.go
Normal file
@@ -0,0 +1,469 @@
|
|||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TokenRefresher is an interface for refreshing a token.
|
||||||
|
type TokenRefresher interface {
|
||||||
|
RefreshSession() error
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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"`
|
||||||
|
ExpiresAt time.Time `json:"expires_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SessionData represents the data stored in a session file
|
||||||
|
type SessionData struct {
|
||||||
|
Domain string `json:"domain"`
|
||||||
|
Username string `json:"username"`
|
||||||
|
AuthToken string `json:"auth_token"`
|
||||||
|
OAuth2Token *OAuth2Token `json:"oauth2_token"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expired checks if token is expired
|
||||||
|
func (t *OAuth2Token) Expired() bool {
|
||||||
|
return time.Now().After(t.ExpiresAt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RefreshIfNeeded refreshes token if expired
|
||||||
|
func (t *OAuth2Token) RefreshIfNeeded(client TokenRefresher) error {
|
||||||
|
if !t.Expired() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return client.RefreshSession()
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// Default location for conversions (set to UTC by default)
|
||||||
|
defaultLocation *time.Location
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
defaultLocation, err = time.LoadLocation("UTC")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseTimestamp converts a millisecond timestamp to time.Time in default location
|
||||||
|
func ParseTimestamp(ts int) time.Time {
|
||||||
|
return time.Unix(0, int64(ts)*int64(time.Millisecond)).In(defaultLocation)
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseAggregationKey is a helper function to parse aggregation key back to a time.Time object
|
||||||
|
func ParseAggregationKey(key, aggregate string) time.Time {
|
||||||
|
switch aggregate {
|
||||||
|
case "day":
|
||||||
|
t, _ := time.Parse("2006-01-02", key)
|
||||||
|
return t
|
||||||
|
case "week":
|
||||||
|
year, _ := strconv.Atoi(key[:4])
|
||||||
|
week, _ := strconv.Atoi(key[6:])
|
||||||
|
t := time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC)
|
||||||
|
// Find the first Monday of the year
|
||||||
|
for t.Weekday() != time.Monday {
|
||||||
|
t = t.AddDate(0, 0, 1)
|
||||||
|
}
|
||||||
|
// Add weeks
|
||||||
|
return t.AddDate(0, 0, (week-1)*7)
|
||||||
|
case "month":
|
||||||
|
t, _ := time.Parse("2006-01", key)
|
||||||
|
return t
|
||||||
|
case "year":
|
||||||
|
t, _ := time.Parse("2006", key)
|
||||||
|
return t
|
||||||
|
}
|
||||||
|
return time.Time{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GarminTime represents Garmin's timestamp format with custom JSON parsing
|
||||||
|
type GarminTime struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
// It parses Garmin's specific timestamp format.
|
||||||
|
func (gt *GarminTime) UnmarshalJSON(b []byte) (err error) {
|
||||||
|
s := strings.Trim(string(b), `"`)
|
||||||
|
if s == "null" {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try parsing with milliseconds (e.g., "2018-09-01T00:13:25.000")
|
||||||
|
// Garmin sometimes returns .0 for milliseconds, which Go's time.Parse handles as .000
|
||||||
|
// The 'Z' in the layout indicates a UTC time without a specific offset, which is often how these are interpreted.
|
||||||
|
// If the input string does not contain 'Z', it will be parsed as local time.
|
||||||
|
// For consistency, we'll assume UTC if no timezone is specified.
|
||||||
|
layouts := []string{
|
||||||
|
"2006-01-02T15:04:05.0", // Example: 2018-09-01T00:13:25.0
|
||||||
|
"2006-01-02T15:04:05", // Example: 2018-09-01T00:13:25
|
||||||
|
"2006-01-02 15:04:05", // Example: 2025-09-18 15:14:40
|
||||||
|
"2006-01-02", // Example: 2018-09-01
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, layout := range layouts {
|
||||||
|
if t, err := time.Parse(layout, s); err == nil {
|
||||||
|
gt.Time = t
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf("cannot parse %q into a GarminTime", s)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ActivityType represents the type of activity
|
||||||
|
type ActivityType struct {
|
||||||
|
TypeID int `json:"typeId"`
|
||||||
|
TypeKey string `json:"typeKey"`
|
||||||
|
ParentTypeID *int `json:"parentTypeId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// EventType represents the event type of an activity
|
||||||
|
type EventType struct {
|
||||||
|
TypeID int `json:"typeId"`
|
||||||
|
TypeKey string `json:"typeKey"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Activity represents a Garmin Connect activity
|
||||||
|
type Activity struct {
|
||||||
|
ActivityID int64 `json:"activityId"`
|
||||||
|
ActivityName string `json:"activityName"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
StartTimeLocal GarminTime `json:"startTimeLocal"`
|
||||||
|
StartTimeGMT GarminTime `json:"startTimeGMT"`
|
||||||
|
ActivityType ActivityType `json:"activityType"`
|
||||||
|
EventType EventType `json:"eventType"`
|
||||||
|
Distance float64 `json:"distance"`
|
||||||
|
Duration float64 `json:"duration"`
|
||||||
|
ElapsedDuration float64 `json:"elapsedDuration"`
|
||||||
|
MovingDuration float64 `json:"movingDuration"`
|
||||||
|
ElevationGain float64 `json:"elevationGain"`
|
||||||
|
ElevationLoss float64 `json:"elevationLoss"`
|
||||||
|
AverageSpeed float64 `json:"averageSpeed"`
|
||||||
|
MaxSpeed float64 `json:"maxSpeed"`
|
||||||
|
Calories float64 `json:"calories"`
|
||||||
|
AverageHR float64 `json:"averageHR"`
|
||||||
|
MaxHR float64 `json:"maxHR"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserProfile represents a Garmin user profile
|
||||||
|
type UserProfile struct {
|
||||||
|
UserName string `json:"userName"`
|
||||||
|
DisplayName string `json:"displayName"`
|
||||||
|
LevelUpdateDate GarminTime `json:"levelUpdateDate"`
|
||||||
|
// Add other fields as needed from API response
|
||||||
|
}
|
||||||
|
|
||||||
|
// VO2MaxData represents VO2 max data
|
||||||
|
type VO2MaxData struct {
|
||||||
|
Date time.Time `json:"calendarDate"`
|
||||||
|
VO2MaxRunning *float64 `json:"vo2MaxRunning"`
|
||||||
|
VO2MaxCycling *float64 `json:"vo2MaxCycling"`
|
||||||
|
UserProfilePK int `json:"userProfilePk"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add these new structs
|
||||||
|
type VO2MaxEntry struct {
|
||||||
|
Value float64 `json:"value"`
|
||||||
|
ActivityType string `json:"activityType"` // "running" or "cycling"
|
||||||
|
Date time.Time `json:"date"`
|
||||||
|
Source string `json:"source"` // "user_settings", "activity", etc.
|
||||||
|
}
|
||||||
|
|
||||||
|
type VO2Max struct {
|
||||||
|
Value float64 `json:"vo2Max"`
|
||||||
|
FitnessLevel string `json:"fitnessLevel"`
|
||||||
|
UpdatedDate time.Time `json:"date"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// VO2MaxProfile represents the current VO2 max profile from user settings
|
||||||
|
type VO2MaxProfile struct {
|
||||||
|
UserProfilePK int `json:"userProfilePk"`
|
||||||
|
LastUpdated time.Time `json:"lastUpdated"`
|
||||||
|
Running *VO2MaxEntry `json:"running,omitempty"`
|
||||||
|
Cycling *VO2MaxEntry `json:"cycling,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SleepLevel represents different sleep stages
|
||||||
|
type SleepLevel struct {
|
||||||
|
StartGMT time.Time `json:"startGmt"`
|
||||||
|
EndGMT time.Time `json:"endGmt"`
|
||||||
|
ActivityLevel float64 `json:"activityLevel"`
|
||||||
|
SleepLevel string `json:"sleepLevel"` // "deep", "light", "rem", "awake"
|
||||||
|
}
|
||||||
|
|
||||||
|
// SleepMovement represents movement during sleep
|
||||||
|
type SleepMovement struct {
|
||||||
|
StartGMT time.Time `json:"startGmt"`
|
||||||
|
EndGMT time.Time `json:"endGmt"`
|
||||||
|
ActivityLevel float64 `json:"activityLevel"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// SleepScore represents detailed sleep scoring
|
||||||
|
type SleepScore struct {
|
||||||
|
Overall int `json:"overall"`
|
||||||
|
Composition SleepScoreBreakdown `json:"composition"`
|
||||||
|
Revitalization SleepScoreBreakdown `json:"revitalization"`
|
||||||
|
Duration SleepScoreBreakdown `json:"duration"`
|
||||||
|
DeepPercentage float64 `json:"deepPercentage"`
|
||||||
|
LightPercentage float64 `json:"lightPercentage"`
|
||||||
|
RemPercentage float64 `json:"remPercentage"`
|
||||||
|
RestfulnessValue float64 `json:"restfulnessValue"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SleepScoreBreakdown struct {
|
||||||
|
QualifierKey string `json:"qualifierKey"`
|
||||||
|
OptimalStart float64 `json:"optimalStart"`
|
||||||
|
OptimalEnd float64 `json:"optimalEnd"`
|
||||||
|
Value float64 `json:"value"`
|
||||||
|
IdealStartSecs *int `json:"idealStartInSeconds"`
|
||||||
|
IdealEndSecs *int `json:"idealEndInSeconds"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetailedSleepData represents comprehensive sleep data
|
||||||
|
type DetailedSleepData struct {
|
||||||
|
UserProfilePK int `json:"userProfilePk"`
|
||||||
|
CalendarDate time.Time `json:"calendarDate"`
|
||||||
|
SleepStartTimestampGMT time.Time `json:"sleepStartTimestampGmt"`
|
||||||
|
SleepEndTimestampGMT time.Time `json:"sleepEndTimestampGmt"`
|
||||||
|
SleepStartTimestampLocal time.Time `json:"sleepStartTimestampLocal"`
|
||||||
|
SleepEndTimestampLocal time.Time `json:"sleepEndTimestampLocal"`
|
||||||
|
UnmeasurableSleepSeconds int `json:"unmeasurableSleepSeconds"`
|
||||||
|
DeepSleepSeconds int `json:"deepSleepSeconds"`
|
||||||
|
LightSleepSeconds int `json:"lightSleepSeconds"`
|
||||||
|
RemSleepSeconds int `json:"remSleepSeconds"`
|
||||||
|
AwakeSleepSeconds int `json:"awakeSleepSeconds"`
|
||||||
|
DeviceRemCapable bool `json:"deviceRemCapable"`
|
||||||
|
SleepLevels []SleepLevel `json:"sleepLevels"`
|
||||||
|
SleepMovement []SleepMovement `json:"sleepMovement"`
|
||||||
|
SleepScores *SleepScore `json:"sleepScores"`
|
||||||
|
AverageSpO2Value *float64 `json:"averageSpO2Value"`
|
||||||
|
LowestSpO2Value *int `json:"lowestSpO2Value"`
|
||||||
|
HighestSpO2Value *int `json:"highestSpO2Value"`
|
||||||
|
AverageRespirationValue *float64 `json:"averageRespirationValue"`
|
||||||
|
LowestRespirationValue *float64 `json:"lowestRespirationValue"`
|
||||||
|
HighestRespirationValue *float64 `json:"highestRespirationValue"`
|
||||||
|
AvgSleepStress *float64 `json:"avgSleepStress"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HRVBaseline represents HRV baseline data
|
||||||
|
type HRVBaseline struct {
|
||||||
|
LowUpper int `json:"lowUpper"`
|
||||||
|
BalancedLow int `json:"balancedLow"`
|
||||||
|
BalancedUpper int `json:"balancedUpper"`
|
||||||
|
MarkerValue float64 `json:"markerValue"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DailyHRVData represents comprehensive daily HRV data
|
||||||
|
type DailyHRVData struct {
|
||||||
|
UserProfilePK int `json:"userProfilePk"`
|
||||||
|
CalendarDate time.Time `json:"calendarDate"`
|
||||||
|
WeeklyAvg *float64 `json:"weeklyAvg"`
|
||||||
|
LastNightAvg *float64 `json:"lastNightAvg"`
|
||||||
|
LastNight5MinHigh *float64 `json:"lastNight5MinHigh"`
|
||||||
|
Baseline HRVBaseline `json:"baseline"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
FeedbackPhrase string `json:"feedbackPhrase"`
|
||||||
|
CreateTimeStamp time.Time `json:"createTimeStamp"`
|
||||||
|
HRVReadings []HRVReading `json:"hrvReadings"`
|
||||||
|
StartTimestampGMT time.Time `json:"startTimestampGmt"`
|
||||||
|
EndTimestampGMT time.Time `json:"endTimestampGmt"`
|
||||||
|
StartTimestampLocal time.Time `json:"startTimestampLocal"`
|
||||||
|
EndTimestampLocal time.Time `json:"endTimestampLocal"`
|
||||||
|
SleepStartTimestampGMT time.Time `json:"sleepStartTimestampGmt"`
|
||||||
|
SleepEndTimestampGMT time.Time `json:"sleepEndTimestampGmt"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyBatteryEvent represents events that impact Body Battery
|
||||||
|
type BodyBatteryEvent struct {
|
||||||
|
EventType string `json:"eventType"` // "sleep", "activity", "stress"
|
||||||
|
EventStartTimeGMT time.Time `json:"eventStartTimeGmt"`
|
||||||
|
TimezoneOffset int `json:"timezoneOffset"`
|
||||||
|
DurationInMilliseconds int `json:"durationInMilliseconds"`
|
||||||
|
BodyBatteryImpact int `json:"bodyBatteryImpact"`
|
||||||
|
FeedbackType string `json:"feedbackType"`
|
||||||
|
ShortFeedback string `json:"shortFeedback"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DetailedBodyBatteryData represents comprehensive Body Battery data
|
||||||
|
type DetailedBodyBatteryData struct {
|
||||||
|
UserProfilePK int `json:"userProfilePk"`
|
||||||
|
CalendarDate time.Time `json:"calendarDate"`
|
||||||
|
StartTimestampGMT time.Time `json:"startTimestampGmt"`
|
||||||
|
EndTimestampGMT time.Time `json:"endTimestampGmt"`
|
||||||
|
StartTimestampLocal time.Time `json:"startTimestampLocal"`
|
||||||
|
EndTimestampLocal time.Time `json:"endTimestampLocal"`
|
||||||
|
MaxStressLevel int `json:"maxStressLevel"`
|
||||||
|
AvgStressLevel int `json:"avgStressLevel"`
|
||||||
|
BodyBatteryValuesArray [][]interface{} `json:"bodyBatteryValuesArray"`
|
||||||
|
StressValuesArray [][]int `json:"stressValuesArray"`
|
||||||
|
Events []BodyBatteryEvent `json:"bodyBatteryEvents"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrainingStatus represents current training status
|
||||||
|
type TrainingStatus struct {
|
||||||
|
CalendarDate time.Time `json:"calendarDate"`
|
||||||
|
TrainingStatusKey string `json:"trainingStatusKey"` // "DETRAINING", "RECOVERY", "MAINTAINING", "PRODUCTIVE", "PEAKING", "OVERREACHING", "UNPRODUCTIVE", "NONE"
|
||||||
|
TrainingStatusTypeKey string `json:"trainingStatusTypeKey"`
|
||||||
|
TrainingStatusValue int `json:"trainingStatusValue"`
|
||||||
|
LoadRatio float64 `json:"loadRatio"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TrainingLoad represents training load data
|
||||||
|
type TrainingLoad struct {
|
||||||
|
CalendarDate time.Time `json:"calendarDate"`
|
||||||
|
AcuteTrainingLoad float64 `json:"acuteTrainingLoad"`
|
||||||
|
ChronicTrainingLoad float64 `json:"chronicTrainingLoad"`
|
||||||
|
TrainingLoadRatio float64 `json:"trainingLoadRatio"`
|
||||||
|
TrainingEffectAerobic float64 `json:"trainingEffectAerobic"`
|
||||||
|
TrainingEffectAnaerobic float64 `json:"trainingEffectAnaerobic"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// FitnessAge represents fitness age calculation
|
||||||
|
type FitnessAge struct {
|
||||||
|
FitnessAge int `json:"fitnessAge"`
|
||||||
|
ChronologicalAge int `json:"chronologicalAge"`
|
||||||
|
VO2MaxRunning float64 `json:"vo2MaxRunning"`
|
||||||
|
LastUpdated time.Time `json:"lastUpdated"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HeartRateZones represents heart rate zone data
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HRZone represents a single heart rate zone
|
||||||
|
type HRZone struct {
|
||||||
|
Zone int `json:"zone"`
|
||||||
|
MinBPM int `json:"min_bpm"`
|
||||||
|
MaxBPM int `json:"max_bpm"`
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// WellnessData represents additional wellness metrics
|
||||||
|
type WellnessData struct {
|
||||||
|
Date time.Time `json:"calendarDate"`
|
||||||
|
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"`
|
||||||
|
// Add more fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// SleepData represents sleep summary data
|
||||||
|
type SleepData struct {
|
||||||
|
Date time.Time `json:"calendarDate"`
|
||||||
|
SleepScore int `json:"sleepScore"`
|
||||||
|
TotalSleepSeconds int `json:"totalSleepSeconds"`
|
||||||
|
DeepSleepSeconds int `json:"deepSleepSeconds"`
|
||||||
|
LightSleepSeconds int `json:"lightSleepSeconds"`
|
||||||
|
RemSleepSeconds int `json:"remSleepSeconds"`
|
||||||
|
AwakeSleepSeconds int `json:"awakeSleepSeconds"`
|
||||||
|
// Add more fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// HrvData represents Heart Rate Variability data
|
||||||
|
type HrvData struct {
|
||||||
|
Date time.Time `json:"calendarDate"`
|
||||||
|
HrvValue float64 `json:"hrvValue"`
|
||||||
|
// Add more fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// HRVStatus represents HRV status and baseline
|
||||||
|
type HRVStatus struct {
|
||||||
|
Status string `json:"status"` // "BALANCED", "UNBALANCED", "POOR"
|
||||||
|
FeedbackPhrase string `json:"feedbackPhrase"`
|
||||||
|
BaselineLowUpper int `json:"baselineLowUpper"`
|
||||||
|
BalancedLow int `json:"balancedLow"`
|
||||||
|
BalancedUpper int `json:"balancedUpper"`
|
||||||
|
MarkerValue float64 `json:"markerValue"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// HRVReading represents an individual HRV reading
|
||||||
|
type HRVReading struct {
|
||||||
|
Timestamp int `json:"timestamp"`
|
||||||
|
StressLevel int `json:"stressLevel"`
|
||||||
|
HeartRate int `json:"heartRate"`
|
||||||
|
RRInterval int `json:"rrInterval"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
SignalQuality float64 `json:"signalQuality"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// TimestampAsTime converts the reading timestamp to time.Time using timeutils
|
||||||
|
func (r *HRVReading) TimestampAsTime() time.Time {
|
||||||
|
return ParseTimestamp(r.Timestamp)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RRSeconds converts the RR interval to seconds
|
||||||
|
func (r *HRVReading) RRSeconds() float64 {
|
||||||
|
return float64(r.RRInterval) / 1000.0
|
||||||
|
}
|
||||||
|
|
||||||
|
// StressData represents stress level data
|
||||||
|
type StressData struct {
|
||||||
|
Date time.Time `json:"calendarDate"`
|
||||||
|
StressLevel int `json:"stressLevel"`
|
||||||
|
RestStressLevel int `json:"restStressLevel"`
|
||||||
|
// Add more fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// BodyBatteryData represents Body Battery data
|
||||||
|
type BodyBatteryData struct {
|
||||||
|
Date time.Time `json:"calendarDate"`
|
||||||
|
BatteryLevel int `json:"batteryLevel"`
|
||||||
|
Charge int `json:"charge"`
|
||||||
|
Drain int `json:"drain"`
|
||||||
|
// Add more fields as needed
|
||||||
|
}
|
||||||
|
|
||||||
|
// StepsData represents steps statistics
|
||||||
|
type StepsData struct {
|
||||||
|
Date time.Time `json:"calendarDate"`
|
||||||
|
Steps int `json:"steps"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// DistanceData represents distance statistics
|
||||||
|
type DistanceData struct {
|
||||||
|
Date time.Time `json:"calendarDate"`
|
||||||
|
Distance float64 `json:"distance"` // in meters
|
||||||
|
}
|
||||||
|
|
||||||
|
// CaloriesData represents calories statistics
|
||||||
|
type CaloriesData struct {
|
||||||
|
Date time.Time `json:"calendarDate"`
|
||||||
|
Calories int `json:"activeCalories"`
|
||||||
|
}
|
||||||
@@ -6,8 +6,8 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/api/client"
|
"github.com/sstent/go-garth/internal/api/client"
|
||||||
"go-garth/internal/utils"
|
"github.com/sstent/go-garth/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Stats interface {
|
type Stats interface {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
|
||||||
"go-garth/internal/api/client"
|
"github.com/sstent/go-garth/internal/api/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MockClient simulates API client for tests
|
// MockClient simulates API client for tests
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package users
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/api/client"
|
"github.com/sstent/go-garth/internal/api/client"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PowerFormat struct {
|
type PowerFormat struct {
|
||||||
|
|||||||
6
main.go
6
main.go
@@ -5,9 +5,9 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/api/client"
|
"github.com/sstent/go-garth/internal/api/client"
|
||||||
"go-garth/internal/auth/credentials"
|
"github.com/sstent/go-garth/internal/auth/credentials"
|
||||||
types "go-garth/pkg/garmin"
|
types "github.com/sstent/go-garth/pkg/garmin"
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ package garmin_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"go-garth/internal/api/client"
|
"github.com/sstent/go-garth/internal/api/client"
|
||||||
"go-garth/internal/data"
|
"github.com/sstent/go-garth/internal/data"
|
||||||
"go-garth/internal/testutils"
|
"github.com/sstent/go-garth/internal/testutils"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -8,11 +8,11 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
internalClient "go-garth/internal/api/client"
|
internalClient "github.com/sstent/go-garth/internal/api/client"
|
||||||
"go-garth/internal/errors"
|
"github.com/sstent/go-garth/internal/errors"
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
shared "go-garth/shared/interfaces"
|
shared "github.com/sstent/go-garth/shared/interfaces"
|
||||||
models "go-garth/shared/models"
|
models "github.com/sstent/go-garth/shared/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client is the main Garmin Connect client type
|
// Client is the main Garmin Connect client type
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
internalClient "go-garth/internal/api/client"
|
internalClient "github.com/sstent/go-garth/internal/api/client"
|
||||||
"go-garth/internal/models/types"
|
"github.com/sstent/go-garth/internal/models/types"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (c *Client) GetDailyHRVData(date time.Time) (*types.DailyHRVData, error) {
|
func (c *Client) GetDailyHRVData(date time.Time) (*types.DailyHRVData, error) {
|
||||||
|
|||||||
@@ -4,9 +4,9 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/api/client"
|
"github.com/sstent/go-garth/internal/api/client"
|
||||||
"go-garth/internal/data"
|
"github.com/sstent/go-garth/internal/data"
|
||||||
"go-garth/internal/stats"
|
"github.com/sstent/go-garth/internal/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBodyBatteryIntegration(t *testing.T) {
|
func TestBodyBatteryIntegration(t *testing.T) {
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package garmin
|
|||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/stats"
|
"github.com/sstent/go-garth/internal/stats"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Stats is an interface for stats data types.
|
// Stats is an interface for stats data types.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
package garmin
|
package garmin
|
||||||
|
|
||||||
import types "go-garth/internal/models/types"
|
import types "github.com/sstent/go-garth/internal/models/types"
|
||||||
|
|
||||||
// GarminTime represents Garmin's timestamp format with custom JSON parsing
|
// GarminTime represents Garmin's timestamp format with custom JSON parsing
|
||||||
type GarminTime = types.GarminTime
|
type GarminTime = types.GarminTime
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import (
|
|||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
types "go-garth/internal/models/types"
|
types "github.com/sstent/go-garth/internal/models/types"
|
||||||
"go-garth/shared/models"
|
"github.com/sstent/go-garth/shared/models"
|
||||||
)
|
)
|
||||||
|
|
||||||
// APIClient defines the interface for making API calls that data packages need.
|
// APIClient defines the interface for making API calls that data packages need.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"go-garth/internal/utils"
|
"github.com/sstent/go-garth/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Data defines the interface for Garmin Connect data models.
|
// Data defines the interface for Garmin Connect data models.
|
||||||
|
|||||||
Reference in New Issue
Block a user