diff --git a/examples/activities/.gitignore b/examples/activities/.gitignore new file mode 100644 index 0000000..5b7a7fd --- /dev/null +++ b/examples/activities/.gitignore @@ -0,0 +1,42 @@ +# Environment variables +.env +.env.local +.env.production + +# OS files +.DS_Store +Thumbs.db + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# Build artifacts +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool +*.out + +# Go workspace file +go.work +go.work.sum + +# Local data files +data/ +*.db +*.sqlite +*.sqlite3 + +# Sensitive files +*.key +*.pem +config/secrets.json \ No newline at end of file diff --git a/examples/activities/README.md b/examples/activities/README.md new file mode 100644 index 0000000..dc4f891 --- /dev/null +++ b/examples/activities/README.md @@ -0,0 +1,113 @@ +# Garmin Connect Activity Examples + +This directory contains examples demonstrating how to use the go-garth library to interact with Garmin Connect activities. + +## Prerequisites + +1. A Garmin Connect account +2. Your Garmin Connect username and password +3. Go 1.22 or later + +## Setup + +1. Create a `.env` file in this directory with your credentials: + ``` + GARMIN_USERNAME=your_username + GARMIN_PASSWORD=your_password + ``` + +2. Install dependencies: + ```bash + go mod tidy + ``` + +## Examples + +### Basic Activity Listing (`activities_example.go`) + +This example demonstrates basic authentication and activity listing functionality: + +- Authenticates with Garmin Connect +- Lists recent activities +- Shows basic activity information + +Run with: +```bash +go run activities_example.go +``` + +### Enhanced Activity Listing (`enhanced_example.go`) + +This example provides more comprehensive activity querying capabilities: + +- Lists recent activities +- Filters activities by date range +- Filters activities by activity type (e.g., running, cycling) +- Searches activities by name +- Retrieves detailed activity information including: + - Elevation data + - Heart rate metrics + - Speed metrics + - Step counts + +Run with: +```bash +go run enhanced_example.go +``` + +## Activity Types + +Common activity types you can filter by: +- `running` +- `cycling` +- `walking` +- `swimming` +- `strength_training` +- `hiking` +- `yoga` + +## Output Examples + +### Basic Listing +``` +=== RECENT ACTIVITIES === +Recent Activities (5 total): +================================================== +1. Morning Run [running] + Date: 2024-01-15 07:30 + Distance: 5.20 km + Duration: 28m + Calories: 320 + +2. Evening Ride [cycling] + Date: 2024-01-14 18:00 + Distance: 15.50 km + Duration: 45m + Calories: 280 +``` + +### Detailed Activity Information +``` +=== DETAILS FOR ACTIVITY: Morning Run === +Activity ID: 123456789 +Name: Morning Run +Type: running +Description: Easy morning run in the park +Start Time: 2024-01-15 07:30:00 +Distance: 5.20 km +Duration: 28m +Calories: 320 +Elevation Gain: 45 m +Elevation Loss: 42 m +Max Heart Rate: 165 bpm +Avg Heart Rate: 142 bpm +Max Speed: 12.5 km/h +Avg Speed: 11.1 km/h +Steps: 5200 +``` + +## Security Notes + +- Never commit your `.env` file to version control +- The `.env` file is already included in `.gitignore` +- Consider using environment variables directly in production instead of `.env` files \ No newline at end of file diff --git a/examples/activities/activities_example.go b/examples/activities/activities_example.go index 430658d..e2b16fe 100644 --- a/examples/activities/activities_example.go +++ b/examples/activities/activities_example.go @@ -13,8 +13,17 @@ import ( ) func main() { - // Load environment variables from .env file - if err := godotenv.Load("../../../.env"); err != nil { + // Load environment variables from .env file (robust path handling) + var envPath string + if _, err := os.Stat("../../.env"); err == nil { + envPath = "../../.env" + } else if _, err := os.Stat("../../../.env"); err == nil { + envPath = "../../../.env" + } else { + envPath = ".env" + } + + if err := godotenv.Load(envPath); err != nil { log.Println("Note: Using system environment variables (no .env file found)") } diff --git a/examples/activities/enhanced/main.go b/examples/activities/enhanced/main.go new file mode 100644 index 0000000..b900cbf --- /dev/null +++ b/examples/activities/enhanced/main.go @@ -0,0 +1,194 @@ +package main + +import ( + "context" + "fmt" + "log" + "net/http" + "os" + "strings" + "time" + + "github.com/joho/godotenv" + "github.com/sstent/go-garth" +) + +func main() { + // Load environment variables from .env file (robust path handling) + var envPath string + if _, err := os.Stat("../../../.env"); err == nil { + envPath = "../../../.env" + } else if _, err := os.Stat("../../.env"); err == nil { + envPath = "../../.env" + } else { + envPath = ".env" + } + + if err := godotenv.Load(envPath); err != nil { + log.Println("Note: Using system environment variables (no .env file found)") + } + + // Get credentials from environment + username := os.Getenv("GARMIN_USERNAME") + password := os.Getenv("GARMIN_PASSWORD") + if username == "" || password == "" { + log.Fatal("GARMIN_USERNAME or GARMIN_PASSWORD not set in environment") + } + + // Create context with timeout + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + // Create token storage and authenticator + storage := garth.NewMemoryStorage() + auth := garth.NewAuthenticator(garth.ClientOptions{ + Storage: storage, + TokenURL: "https://connectapi.garmin.com/oauth-service/oauth/token", + Timeout: 30 * time.Second, + }) + + // Authenticate + token, err := auth.Login(ctx, username, password, "") + if err != nil { + log.Fatalf("Authentication failed: %v", err) + } + log.Printf("Authenticated successfully! Token expires at: %s", token.Expiry.Format(time.RFC3339)) + + // Create HTTP client with authentication transport + httpClient := &http.Client{ + Transport: garth.NewAuthTransport(auth.(*garth.GarthAuthenticator), storage, nil), + } + + // Create API client + apiClient := garth.NewAPIClient("https://connectapi.garmin.com", httpClient) + + // Create activity service + activityService := garth.NewActivityService(apiClient) + + // Example 1: List recent activities + fmt.Println("\n=== RECENT ACTIVITIES ===") + recentActivities, err := activityService.List(ctx, garth.ActivityListOptions{ + Limit: 10, + }) + if err != nil { + log.Printf("Failed to get recent activities: %v", err) + } else { + printActivities(recentActivities, "Recent Activities") + } + + // Example 2: List activities from last 30 days + fmt.Println("\n=== ACTIVITIES FROM LAST 30 DAYS ===") + thirtyDaysAgo := time.Now().AddDate(0, 0, -30) + recentMonthActivities, err := activityService.List(ctx, garth.ActivityListOptions{ + StartDate: thirtyDaysAgo, + EndDate: time.Now(), + Limit: 20, + }) + if err != nil { + log.Printf("Failed to get activities from last 30 days: %v", err) + } else { + printActivities(recentMonthActivities, "Last 30 Days") + } + + // Example 3: List running activities only + fmt.Println("\n=== RUNNING ACTIVITIES ===") + runningActivities, err := activityService.List(ctx, garth.ActivityListOptions{ + ActivityType: "running", + Limit: 15, + }) + if err != nil { + log.Printf("Failed to get running activities: %v", err) + } else { + printActivities(runningActivities, "Running Activities") + } + + // Example 4: Search activities by name + fmt.Println("\n=== ACTIVITIES CONTAINING 'MORNING' ===") + morningActivities, err := activityService.List(ctx, garth.ActivityListOptions{ + NameContains: "morning", + Limit: 10, + }) + if err != nil { + log.Printf("Failed to search activities: %v", err) + } else { + printActivities(morningActivities, "Activities with 'morning' in name") + } + + // Example 5: Get detailed information for the first activity + if len(recentActivities) > 0 { + firstActivity := recentActivities[0] + fmt.Printf("\n=== DETAILS FOR ACTIVITY: %s ===\n", firstActivity.Name) + + details, err := activityService.Get(ctx, firstActivity.ActivityID) + if err != nil { + log.Printf("Failed to get activity details: %v", err) + } else { + printActivityDetails(details) + } + } + + fmt.Println("\nEnhanced activity listing example completed successfully!") +} + +func printActivities(activities []garth.Activity, title string) { + if len(activities) == 0 { + fmt.Printf("No %s found\n", title) + return + } + + fmt.Printf("\n%s (%d total):\n", title, len(activities)) + fmt.Println(strings.Repeat("=", 50)) + + for i, activity := range activities { + fmt.Printf("%d. %s [%s]\n", i+1, activity.Name, activity.Type) + fmt.Printf(" Date: %s\n", activity.StartTime.Format("2006-01-02 15:04")) + fmt.Printf(" Distance: %.2f km\n", activity.Distance/1000) + fmt.Printf(" Duration: %s\n", formatDuration(activity.Duration)) + fmt.Printf(" Calories: %d\n", activity.Calories) + fmt.Println() + } +} + +func printActivityDetails(details *garth.ActivityDetails) { + fmt.Printf("Activity ID: %d\n", details.ActivityID) + fmt.Printf("Name: %s\n", details.Name) + fmt.Printf("Type: %s\n", details.Type) + fmt.Printf("Description: %s\n", details.Description) + fmt.Printf("Start Time: %s\n", details.StartTime.Format("2006-01-02 15:04:05")) + fmt.Printf("Distance: %.2f km\n", details.Distance/1000) + fmt.Printf("Duration: %s\n", formatDuration(details.Duration)) + fmt.Printf("Calories: %d\n", details.Calories) + + if details.ElevationGain > 0 { + fmt.Printf("Elevation Gain: %.0f m\n", details.ElevationGain) + } + if details.ElevationLoss > 0 { + fmt.Printf("Elevation Loss: %.0f m\n", details.ElevationLoss) + } + if details.MaxHeartRate > 0 { + fmt.Printf("Max Heart Rate: %d bpm\n", details.MaxHeartRate) + } + if details.AvgHeartRate > 0 { + fmt.Printf("Avg Heart Rate: %d bpm\n", details.AvgHeartRate) + } + if details.MaxSpeed > 0 { + fmt.Printf("Max Speed: %.1f km/h\n", details.MaxSpeed*3.6) + } + if details.AvgSpeed > 0 { + fmt.Printf("Avg Speed: %.1f km/h\n", details.AvgSpeed*3.6) + } + if details.Steps > 0 { + fmt.Printf("Steps: %d\n", details.Steps) + } +} + +func formatDuration(seconds float64) string { + duration := time.Duration(seconds * float64(time.Second)) + hours := int(duration.Hours()) + minutes := int(duration.Minutes()) % 60 + + if hours > 0 { + return fmt.Sprintf("%dh %dm", hours, minutes) + } + return fmt.Sprintf("%dm", minutes) +} diff --git a/examples/activities/go.mod b/examples/activities/go.mod index 5747dce..871c558 100644 --- a/examples/activities/go.mod +++ b/examples/activities/go.mod @@ -5,4 +5,6 @@ go 1.22 require ( github.com/joho/godotenv v1.5.1 github.com/sstent/go-garth v0.1.0 -) \ No newline at end of file +) + +replace github.com/sstent/go-garth => ../../ diff --git a/examples/activities/go.sum b/examples/activities/go.sum new file mode 100644 index 0000000..d61b19e --- /dev/null +++ b/examples/activities/go.sum @@ -0,0 +1,2 @@ +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= diff --git a/examples/activities_example.go b/examples/activities_example.go deleted file mode 100644 index abe36f5..0000000 --- a/examples/activities_example.go +++ /dev/null @@ -1,80 +0,0 @@ -package main - -import ( - "context" - "fmt" - "log" - "net/http" - "os" - "time" - - "github.com/joho/godotenv" - "github.com/sstent/go-garth" -) - -func main() { - // Load environment variables from .env file - if err := godotenv.Load("../../.env"); err != nil { - log.Println("Note: Using system environment variables (no .env file found)") - } - - // Get credentials from environment - username := os.Getenv("GARMIN_USERNAME") - password := os.Getenv("GARMIN_PASSWORD") - if username == "" || password == "" { - log.Fatal("GARMIN_USERNAME or GARMIN_PASSWORD not set in environment") - } - - // Create context with timeout - ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) - defer cancel() - - // Create token storage and authenticator - storage := garth.NewMemoryStorage() - auth := garth.NewAuthenticator(garth.ClientOptions{ - Storage: storage, - TokenURL: "https://connectapi.garmin.com/oauth-service/oauth/token", - Timeout: 30 * time.Second, - }) - - // Authenticate - token, err := auth.Login(ctx, username, password, "") - if err != nil { - log.Fatalf("Authentication failed: %v", err) - } - log.Printf("Authenticated successfully! Token expires at: %s", token.Expiry.Format(time.RFC3339)) - - // Create HTTP client with authentication transport - httpClient := &http.Client{ - Transport: garth.NewAuthTransport(auth.(*garth.GarthAuthenticator), storage, nil), - } - - // Create API client - apiClient := garth.NewAPIClient("https://connectapi.garmin.com", httpClient) - - // Create activity service - activityService := garth.NewActivityService(apiClient) - - // List last 20 activities - activities, err := activityService.List(ctx, garth.ActivityListOptions{ - Limit: 20, - }) - if err != nil { - log.Fatalf("Failed to get activities: %v", err) - } - - // Print activities - fmt.Println("\nLast 20 Activities:") - fmt.Println("=======================================") - for i, activity := range activities { - fmt.Printf("%d. %s [%s] - %s\n", i+1, - activity.Name, - activity.Type, - activity.StartTime.Format("2006-01-02 15:04")) - fmt.Printf(" Distance: %.2f km, Duration: %.0f min\n\n", - activity.Distance/1000, - activity.Duration/60) - } - - fmt.Println("Example completed successfully!") -} diff --git a/go.mod b/go.mod index 39d1206..1fc068b 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,4 @@ module github.com/sstent/go-garth go 1.22 -require github.com/joho/godotenv v1.5.1 // indirect +require github.com/joho/godotenv v1.5.1 diff --git a/login_page_1756913368.html b/login_page_1756913368.html new file mode 100644 index 0000000..651f29a Binary files /dev/null and b/login_page_1756913368.html differ diff --git a/login_page_1756918964.html b/login_page_1756918964.html new file mode 100644 index 0000000..3171897 Binary files /dev/null and b/login_page_1756918964.html differ diff --git a/login_page_1756918983.html b/login_page_1756918983.html new file mode 100644 index 0000000..f2c9d6d Binary files /dev/null and b/login_page_1756918983.html differ diff --git a/login_page_1756919005.html b/login_page_1756919005.html new file mode 100644 index 0000000..f991d71 Binary files /dev/null and b/login_page_1756919005.html differ diff --git a/login_page_1756919015.html b/login_page_1756919015.html new file mode 100644 index 0000000..28745f4 Binary files /dev/null and b/login_page_1756919015.html differ diff --git a/login_page_1756919019.html b/login_page_1756919019.html new file mode 100644 index 0000000..98a7e64 Binary files /dev/null and b/login_page_1756919019.html differ diff --git a/login_page_1756922746.html b/login_page_1756922746.html new file mode 100644 index 0000000..af19a08 Binary files /dev/null and b/login_page_1756922746.html differ