mirror of
https://github.com/sstent/garminsync-go.git
synced 2026-01-25 08:35:13 +00:00
partital fix
This commit is contained in:
@@ -42,7 +42,7 @@ ENV TZ=UTC \
|
|||||||
DB_PATH=/data/garmin.db
|
DB_PATH=/data/garmin.db
|
||||||
|
|
||||||
# Create data volume and set permissions
|
# Create data volume and set permissions
|
||||||
RUN mkdir /data && chown nobody:nobody /data
|
RUN mkdir -p /data/activities && chown -R nobody:nobody /data
|
||||||
VOLUME /data
|
VOLUME /data
|
||||||
|
|
||||||
# Run as non-root user
|
# Run as non-root user
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
version: '3.8'
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
garminsync:
|
garminsync:
|
||||||
build: .
|
build: .
|
||||||
@@ -14,7 +12,8 @@ services:
|
|||||||
- /etc/localtime:/etc/localtime:ro
|
- /etc/localtime:/etc/localtime:ro
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
depends_on:
|
depends_on:
|
||||||
- garmin-api
|
garmin-api:
|
||||||
|
condition: service_healthy
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8888/health"]
|
test: ["CMD", "wget", "--quiet", "--tries=1", "--spider", "http://localhost:8888/health"]
|
||||||
interval: 30s
|
interval: 30s
|
||||||
@@ -35,7 +34,8 @@ services:
|
|||||||
- "8081:8081"
|
- "8081:8081"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:8081/health"]
|
test: ["CMD-SHELL", "curl -f http://localhost:8081/health | grep -q 'authenticated' || exit 1"]
|
||||||
interval: 30s
|
interval: 10s
|
||||||
timeout: 10s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 10
|
||||||
|
start_period: 30s
|
||||||
|
|||||||
@@ -6,6 +6,11 @@ RUN pip install --no-cache-dir -r requirements.txt
|
|||||||
|
|
||||||
# Final stage
|
# Final stage
|
||||||
FROM python:3.12-slim
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
# Install curl
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends curl \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy dependencies from builder stage
|
# Copy dependencies from builder stage
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import os
|
import os
|
||||||
import json
|
import json
|
||||||
|
import time
|
||||||
|
import logging
|
||||||
from flask import Flask, request, jsonify, send_file
|
from flask import Flask, request, jsonify, send_file
|
||||||
import io
|
import io
|
||||||
from garminconnect import Garmin
|
from garminconnect import Garmin
|
||||||
import logging
|
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
@@ -11,20 +12,33 @@ app = Flask(__name__)
|
|||||||
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s')
|
logging.basicConfig(level=logging.INFO, format='%(asctime)s %(levelname)s: %(message)s')
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
# Global API client
|
||||||
|
api = None
|
||||||
|
last_init_time = 0
|
||||||
|
init_retry_interval = 60 # seconds
|
||||||
|
|
||||||
# Environment variables
|
# Environment variables
|
||||||
GARMIN_EMAIL = os.getenv("GARMIN_EMAIL")
|
GARMIN_EMAIL = os.getenv("GARMIN_EMAIL")
|
||||||
GARMIN_PASSWORD = os.getenv("GARMIN_PASSWORD")
|
GARMIN_PASSWORD = os.getenv("GARMIN_PASSWORD")
|
||||||
|
|
||||||
def init_api():
|
def init_api():
|
||||||
"""Initializes the Garmin API client."""
|
"""Initializes the Garmin API client (with retries)."""
|
||||||
|
global api, last_init_time
|
||||||
|
if api and time.time() - last_init_time < init_retry_interval:
|
||||||
|
return api
|
||||||
|
|
||||||
try:
|
try:
|
||||||
api = Garmin(GARMIN_EMAIL, GARMIN_PASSWORD)
|
new_api = Garmin(GARMIN_EMAIL, GARMIN_PASSWORD)
|
||||||
api.login()
|
new_api.login()
|
||||||
|
api = new_api
|
||||||
|
last_init_time = time.time()
|
||||||
logger.info("Successfully authenticated with Garmin API")
|
logger.info("Successfully authenticated with Garmin API")
|
||||||
return api
|
return api
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Error initializing Garmin API: {e}")
|
logger.error(f"Error initializing Garmin API: {e}")
|
||||||
return None
|
if not api:
|
||||||
|
logger.critical("Critical: API initialization failed")
|
||||||
|
return api
|
||||||
|
|
||||||
@app.route('/stats', methods=['GET'])
|
@app.route('/stats', methods=['GET'])
|
||||||
def get_stats():
|
def get_stats():
|
||||||
@@ -100,8 +114,15 @@ def download_activity(activity_id):
|
|||||||
|
|
||||||
@app.route('/health', methods=['GET'])
|
@app.route('/health', methods=['GET'])
|
||||||
def health_check():
|
def health_check():
|
||||||
"""Health check endpoint."""
|
"""Health check endpoint with authentication status."""
|
||||||
return jsonify({"status": "healthy", "service": "garmin-api"})
|
if api:
|
||||||
|
return jsonify({"status": "healthy", "auth_status": "authenticated", "service": "garmin-api"}), 200
|
||||||
|
else:
|
||||||
|
# Attempt to reinitialize if not tried recently
|
||||||
|
init_api()
|
||||||
|
if api:
|
||||||
|
return jsonify({"status": "healthy", "auth_status": "reauthenticated", "service": "garmin-api"}), 200
|
||||||
|
return jsonify({"status": "unhealthy", "auth_status": "unauthenticated", "service": "garmin-api"}), 503
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
app.run(host='0.0.0.0', port=8081)
|
app.run(host='0.0.0.0', port=8081)
|
||||||
|
|||||||
@@ -1,156 +1,222 @@
|
|||||||
// internal/garmin/client.go
|
|
||||||
package garmin
|
package garmin
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"log"
|
||||||
|
"math"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Client struct {
|
type Client struct {
|
||||||
httpClient *http.Client
|
httpClient *http.Client
|
||||||
baseURL string
|
baseURL string
|
||||||
|
retries int // Number of retries for failed requests
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewClient() *Client {
|
||||||
|
return &Client{
|
||||||
|
httpClient: &http.Client{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
},
|
||||||
|
baseURL: "http://garmin-api:8081",
|
||||||
|
retries: 3, // Default to 3 retries
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type GarminActivity struct {
|
type GarminActivity struct {
|
||||||
ActivityID int `json:"activityId"`
|
ActivityID int `json:"activityId"`
|
||||||
ActivityName string `json:"activityName"`
|
ActivityName string `json:"activityName"`
|
||||||
StartTimeLocal string `json:"startTimeLocal"`
|
StartTimeLocal string `json:"startTimeLocal"`
|
||||||
ActivityType map[string]interface{} `json:"activityType"`
|
ActivityType map[string]interface{} `json:"activityType"`
|
||||||
Distance float64 `json:"distance"`
|
Distance float64 `json:"distance"`
|
||||||
Duration float64 `json:"duration"`
|
Duration float64 `json:"duration"`
|
||||||
MaxHR float64 `json:"maxHR"`
|
MaxHR float64 `json:"maxHR"`
|
||||||
AvgHR float64 `json:"avgHR"`
|
AvgHR float64 `json:"avgHR"`
|
||||||
AvgPower float64 `json:"avgPower"`
|
AvgPower float64 `json:"avgPower"`
|
||||||
Calories float64 `json:"calories"`
|
Calories float64 `json:"calories"`
|
||||||
StartLatitude float64 `json:"startLatitude"`
|
StartLatitude float64 `json:"startLatitude"`
|
||||||
StartLongitude float64 `json:"startLongitude"`
|
StartLongitude float64 `json:"startLongitude"`
|
||||||
Steps float64 `json:"steps"`
|
Steps float64 `json:"steps"`
|
||||||
ElevationGain float64 `json:"elevationGain"`
|
ElevationGain float64 `json:"elevationGain"`
|
||||||
ElevationLoss float64 `json:"elevationLoss"`
|
ElevationLoss float64 `json:"elevationLoss"`
|
||||||
AvgTemperature float64 `json:"avgTemperature"`
|
AvgTemperature float64 `json:"avgTemperature"`
|
||||||
MinTemperature float64 `json:"minTemperature"`
|
MinTemperature float64 `json:"minTemperature"`
|
||||||
MaxTemperature float64 `json:"maxTemperature"`
|
MaxTemperature float64 `json:"maxTemperature"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewClient creates a new Garmin API client
|
|
||||||
func NewClient() *Client {
|
|
||||||
return &Client{
|
|
||||||
httpClient: &http.Client{
|
|
||||||
Timeout: 30 * time.Second,
|
|
||||||
},
|
|
||||||
baseURL: "http://garmin-api:8081",
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetStats retrieves user statistics for a specific date via the Python API service
|
|
||||||
func (c *Client) GetStats(date string) (map[string]interface{}, error) {
|
func (c *Client) GetStats(date string) (map[string]interface{}, error) {
|
||||||
// Construct request URL
|
url := fmt.Sprintf("%s/stats?date=%s", c.baseURL, url.QueryEscape(date))
|
||||||
url := fmt.Sprintf("%s/stats?date=%s", c.baseURL, url.QueryEscape(date))
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
return nil, err
|
||||||
if err != nil {
|
}
|
||||||
return nil, err
|
|
||||||
}
|
var resp *http.Response
|
||||||
|
var bodyBytes []byte
|
||||||
resp, err := c.httpClient.Do(req)
|
reqErr := error(nil)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
for i := 0; i <= c.retries; i++ {
|
||||||
}
|
resp, reqErr = c.httpClient.Do(req)
|
||||||
defer resp.Body.Close()
|
if reqErr != nil || (resp != nil && resp.StatusCode >= 500) {
|
||||||
|
if i < c.retries {
|
||||||
if resp.StatusCode != http.StatusOK {
|
backoff := time.Duration(math.Pow(2, float64(i))) * time.Second
|
||||||
body, _ := io.ReadAll(resp.Body)
|
log.Printf("Request failed (attempt %d/%d), retrying in %v: %v", i+1, c.retries, backoff, reqErr)
|
||||||
return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, body)
|
time.Sleep(backoff)
|
||||||
}
|
continue
|
||||||
|
}
|
||||||
var stats map[string]interface{}
|
}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&stats); err != nil {
|
break
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
|
if reqErr != nil {
|
||||||
return stats, nil
|
return nil, fmt.Errorf("request failed after %d retries: %w", c.retries, reqErr)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
bodyBytes, readErr := io.ReadAll(resp.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response body: %w", readErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, bodyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
var stats map[string]interface{}
|
||||||
|
if jsonErr := json.Unmarshal(bodyBytes, &stats); jsonErr != nil {
|
||||||
|
return nil, jsonErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return stats, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetActivities retrieves activities from the Python API wrapper
|
|
||||||
func (c *Client) GetActivities(start, limit int) ([]GarminActivity, error) {
|
func (c *Client) GetActivities(start, limit int) ([]GarminActivity, error) {
|
||||||
url := fmt.Sprintf("%s/activities?start=%d&limit=%d", c.baseURL, start, limit)
|
url := fmt.Sprintf("%s/activities?start=%d&limit=%d", c.baseURL, start, limit)
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
|
var resp *http.Response
|
||||||
resp, err := c.httpClient.Do(req)
|
var bodyBytes []byte
|
||||||
if err != nil {
|
reqErr := error(nil)
|
||||||
return nil, err
|
|
||||||
}
|
for i := 0; i <= c.retries; i++ {
|
||||||
defer resp.Body.Close()
|
resp, reqErr = c.httpClient.Do(req)
|
||||||
|
if reqErr != nil || (resp != nil && resp.StatusCode >= 500) {
|
||||||
if resp.StatusCode != http.StatusOK {
|
if i < c.retries {
|
||||||
body, _ := io.ReadAll(resp.Body)
|
backoff := time.Duration(math.Pow(2, float64(i))) * time.Second
|
||||||
return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, body)
|
log.Printf("Request failed (attempt %d/%d), retrying in %v: %v", i+1, c.retries, backoff, reqErr)
|
||||||
}
|
time.Sleep(backoff)
|
||||||
|
continue
|
||||||
var activities []GarminActivity
|
}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&activities); err != nil {
|
}
|
||||||
return nil, err
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return activities, nil
|
if reqErr != nil {
|
||||||
|
return nil, fmt.Errorf("request failed after %d retries: %w", c.retries, reqErr)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
bodyBytes, readErr := io.ReadAll(resp.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response body: %w", readErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, bodyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
var activities []GarminActivity
|
||||||
|
if jsonErr := json.Unmarshal(bodyBytes, &activities); jsonErr != nil {
|
||||||
|
return nil, jsonErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return activities, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function removed - no longer needed
|
|
||||||
|
|
||||||
// DownloadActivity downloads an activity via the Python API wrapper
|
|
||||||
func (c *Client) DownloadActivity(activityID int, format string) ([]byte, error) {
|
func (c *Client) DownloadActivity(activityID int, format string) ([]byte, error) {
|
||||||
url := fmt.Sprintf("%s/activities/%d/download?format=%s", c.baseURL, activityID, format)
|
url := fmt.Sprintf("%s/activities/%d/download?format=%s", c.baseURL, activityID, format)
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
|
var resp *http.Response
|
||||||
resp, err := c.httpClient.Do(req)
|
reqErr := error(nil)
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
for i := 0; i <= c.retries; i++ {
|
||||||
}
|
resp, reqErr = c.httpClient.Do(req)
|
||||||
defer resp.Body.Close()
|
if reqErr != nil || (resp != nil && resp.StatusCode >= 500) {
|
||||||
|
if i < c.retries {
|
||||||
if resp.StatusCode != http.StatusOK {
|
backoff := time.Duration(math.Pow(2, float64(i))) * time.Second
|
||||||
return nil, fmt.Errorf("API returned status %d", resp.StatusCode)
|
log.Printf("Download failed (attempt %d/%d), retrying in %v: %v", i+1, c.retries, backoff, reqErr)
|
||||||
}
|
time.Sleep(backoff)
|
||||||
|
continue
|
||||||
return io.ReadAll(resp.Body)
|
}
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if reqErr != nil {
|
||||||
|
return nil, fmt.Errorf("download failed after %d retries: %w", c.retries, reqErr)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("API returned status %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return io.ReadAll(resp.Body)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetActivityDetails retrieves details for a specific activity from the Python API wrapper
|
|
||||||
func (c *Client) GetActivityDetails(activityID int) (*GarminActivity, error) {
|
func (c *Client) GetActivityDetails(activityID int) (*GarminActivity, error) {
|
||||||
url := fmt.Sprintf("%s/activities/%d", c.baseURL, activityID)
|
url := fmt.Sprintf("%s/activities/%d", c.baseURL, activityID)
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
req, err := http.NewRequest("GET", url, nil)
|
if err != nil {
|
||||||
if err != nil {
|
return nil, err
|
||||||
return nil, err
|
}
|
||||||
}
|
|
||||||
|
var resp *http.Response
|
||||||
resp, err := c.httpClient.Do(req)
|
var bodyBytes []byte
|
||||||
if err != nil {
|
reqErr := error(nil)
|
||||||
return nil, err
|
|
||||||
}
|
for i := 0; i <= c.retries; i++ {
|
||||||
defer resp.Body.Close()
|
resp, reqErr = c.httpClient.Do(req)
|
||||||
|
if reqErr != nil || (resp != nil && resp.StatusCode >= 500) {
|
||||||
if resp.StatusCode != http.StatusOK {
|
if i < c.retries {
|
||||||
body, _ := io.ReadAll(resp.Body)
|
backoff := time.Duration(math.Pow(2, float64(i))) * time.Second
|
||||||
return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, body)
|
log.Printf("Request failed (attempt %d/%d), retrying in %v: %v", i+1, c.retries, backoff, reqErr)
|
||||||
}
|
time.Sleep(backoff)
|
||||||
|
continue
|
||||||
var activity GarminActivity
|
}
|
||||||
if err := json.NewDecoder(resp.Body).Decode(&activity); err != nil {
|
}
|
||||||
return nil, err
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
return &activity, nil
|
if reqErr != nil {
|
||||||
|
return nil, fmt.Errorf("request failed after %d retries: %w", c.retries, reqErr)
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
bodyBytes, readErr := io.ReadAll(resp.Body)
|
||||||
|
if readErr != nil {
|
||||||
|
return nil, fmt.Errorf("failed to read response body: %w", readErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("API returned status %d: %s", resp.StatusCode, bodyBytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
var activity GarminActivity
|
||||||
|
if jsonErr := json.Unmarshal(bodyBytes, &activity); jsonErr != nil {
|
||||||
|
return nil, jsonErr
|
||||||
|
}
|
||||||
|
|
||||||
|
return &activity, nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"time"
|
"time"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/sstent/garminsync-go/internal/database"
|
"github.com/sstent/garminsync-go/internal/database"
|
||||||
"github.com/sstent/garminsync-go/internal/garmin"
|
"github.com/sstent/garminsync-go/internal/garmin"
|
||||||
@@ -26,18 +27,46 @@ func NewSyncService(garminClient *garmin.Client, db *database.SQLiteDB, dataDir
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *SyncService) testAPIConnectivity() error {
|
||||||
|
// Try a simple API call to check connectivity
|
||||||
|
_, err := s.garminClient.GetActivities(0, 1)
|
||||||
|
if err != nil {
|
||||||
|
// Analyze error for troubleshooting hints
|
||||||
|
if strings.Contains(err.Error(), "connection refused") {
|
||||||
|
return fmt.Errorf("API connection failed: service might not be running. Verify garmin-api container is up. Original error: %w", err)
|
||||||
|
} else if strings.Contains(err.Error(), "timeout") {
|
||||||
|
return fmt.Errorf("API connection timeout: service might be slow to start. Original error: %w", err)
|
||||||
|
} else if strings.Contains(err.Error(), "status 5") {
|
||||||
|
return fmt.Errorf("API server error: check garmin-api logs. Original error: %w", err)
|
||||||
|
}
|
||||||
|
return fmt.Errorf("API connectivity test failed: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *SyncService) FullSync(ctx context.Context) error {
|
func (s *SyncService) FullSync(ctx context.Context) error {
|
||||||
fmt.Println("=== Starting full sync ===")
|
fmt.Println("=== Starting full sync ===")
|
||||||
defer fmt.Println("=== Sync completed ===")
|
defer fmt.Println("=== Sync completed ===")
|
||||||
|
|
||||||
|
// Check API connectivity before proceeding
|
||||||
|
if err := s.testAPIConnectivity(); err != nil {
|
||||||
|
return fmt.Errorf("API connectivity test failed: %w", err)
|
||||||
|
}
|
||||||
|
fmt.Println("✅ API connectivity verified")
|
||||||
|
|
||||||
// Check credentials first
|
// Check credentials first
|
||||||
email := os.Getenv("GARMIN_EMAIL")
|
email := os.Getenv("GARMIN_EMAIL")
|
||||||
password := os.Getenv("GARMIN_PASSWORD")
|
password := os.Getenv("GARMIN_PASSWORD")
|
||||||
|
|
||||||
if email == "" || password == "" {
|
if email == "" || password == "" {
|
||||||
return fmt.Errorf("Missing credentials - GARMIN_EMAIL: '%s', GARMIN_PASSWORD: %s",
|
errorMsg := fmt.Sprintf("Missing credentials - GARMIN_EMAIL: '%s', GARMIN_PASSWORD: %s",
|
||||||
email,
|
email,
|
||||||
map[bool]string{true: "SET", false: "EMPTY"}[password != ""])
|
map[bool]string{true: "SET", false: "EMPTY"}[password != ""])
|
||||||
|
errorMsg += "\nTroubleshooting:"
|
||||||
|
errorMsg += "\n1. Ensure the .env file exists with GARMIN_EMAIL and GARMIN_PASSWORD"
|
||||||
|
errorMsg += "\n2. Verify docker-compose.yml mounts the .env file"
|
||||||
|
errorMsg += "\n3. Check container env vars: docker-compose exec garminsync env | grep GARMIN"
|
||||||
|
return fmt.Errorf(errorMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Using credentials - Email: %s, Password: %s\n", email,
|
fmt.Printf("Using credentials - Email: %s, Password: %s\n", email,
|
||||||
|
|||||||
Reference in New Issue
Block a user