mirror of
https://github.com/sstent/go-garth.git
synced 2026-01-25 16:42:28 +00:00
307 lines
8.3 KiB
Go
307 lines
8.3 KiB
Go
package garth
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"io"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/url"
|
|
"strconv"
|
|
"time"
|
|
)
|
|
|
|
// Activity represents a summary of a Garmin activity
|
|
type Activity struct {
|
|
ActivityID int64 `json:"activityId"`
|
|
Name string `json:"activityName"`
|
|
Type string `json:"activityType"`
|
|
StartTime time.Time `json:"startTime"`
|
|
Distance float64 `json:"distance"`
|
|
Duration float64 `json:"duration"`
|
|
Calories int `json:"calories"`
|
|
}
|
|
|
|
// ActivityDetails contains detailed information about an activity
|
|
type ActivityDetails struct {
|
|
ActivityID int64 `json:"activityId"`
|
|
Name string `json:"activityName"`
|
|
Description string `json:"description"`
|
|
Type string `json:"activityType"`
|
|
StartTime time.Time `json:"startTime"`
|
|
Distance float64 `json:"distance"`
|
|
Duration float64 `json:"duration"`
|
|
Calories int `json:"calories"`
|
|
ElevationGain float64 `json:"elevationGain"`
|
|
ElevationLoss float64 `json:"elevationLoss"`
|
|
MaxHeartRate int `json:"maxHeartRate"`
|
|
AvgHeartRate int `json:"avgHeartRate"`
|
|
MaxSpeed float64 `json:"maxSpeed"`
|
|
AvgSpeed float64 `json:"avgSpeed"`
|
|
Steps int `json:"steps"`
|
|
Stress int `json:"stress"`
|
|
TotalSteps int `json:"totalSteps"`
|
|
Device json.RawMessage `json:"device"`
|
|
Location json.RawMessage `json:"location"`
|
|
Weather json.RawMessage `json:"weather"`
|
|
HeartRateZones json.RawMessage `json:"heartRateZones"`
|
|
TrainingEffect json.RawMessage `json:"trainingEffect"`
|
|
ActivityMetrics json.RawMessage `json:"activityMetrics"`
|
|
}
|
|
|
|
// ActivityListOptions provides filtering options for listing activities
|
|
type ActivityListOptions struct {
|
|
Limit int
|
|
StartDate time.Time
|
|
EndDate time.Time
|
|
ActivityType string
|
|
NameContains string
|
|
}
|
|
|
|
// ActivityUpdate represents fields that can be updated on an activity
|
|
type ActivityUpdate struct {
|
|
Name string `json:"activityName,omitempty"`
|
|
Description string `json:"description,omitempty"`
|
|
Type string `json:"activityType,omitempty"`
|
|
StartTime time.Time `json:"startTime,omitempty"`
|
|
Distance float64 `json:"distance,omitempty"`
|
|
Duration float64 `json:"duration,omitempty"`
|
|
}
|
|
|
|
// ActivityService provides access to activity operations
|
|
type ActivityService struct {
|
|
client *APIClient
|
|
}
|
|
|
|
// NewActivityService creates a new ActivityService instance
|
|
func NewActivityService(client *APIClient) *ActivityService {
|
|
return &ActivityService{client: client}
|
|
}
|
|
|
|
// List retrieves a list of activities for the current user with optional filters
|
|
func (s *ActivityService) List(ctx context.Context, opts ActivityListOptions) ([]Activity, error) {
|
|
params := url.Values{}
|
|
if opts.Limit > 0 {
|
|
params.Set("limit", strconv.Itoa(opts.Limit))
|
|
}
|
|
if !opts.StartDate.IsZero() {
|
|
params.Set("startDate", opts.StartDate.Format(time.RFC3339))
|
|
}
|
|
if !opts.EndDate.IsZero() {
|
|
params.Set("endDate", opts.EndDate.Format(time.RFC3339))
|
|
}
|
|
if opts.ActivityType != "" {
|
|
params.Set("activityType", opts.ActivityType)
|
|
}
|
|
if opts.NameContains != "" {
|
|
params.Set("nameContains", opts.NameContains)
|
|
}
|
|
|
|
path := "/activitylist-service/activities/search/activities"
|
|
if len(params) > 0 {
|
|
path += "?" + params.Encode()
|
|
}
|
|
|
|
resp, err := s.client.Get(ctx, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, &APIError{
|
|
StatusCode: resp.StatusCode,
|
|
Message: "Failed to get activities list",
|
|
}
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, &APIError{
|
|
StatusCode: http.StatusInternalServerError,
|
|
Message: "Failed to read activities response",
|
|
Cause: err,
|
|
}
|
|
}
|
|
|
|
var activities []Activity
|
|
if err := json.Unmarshal(body, &activities); err != nil {
|
|
return nil, &APIError{
|
|
StatusCode: http.StatusInternalServerError,
|
|
Message: "Failed to parse activities data",
|
|
Cause: err,
|
|
}
|
|
}
|
|
|
|
return activities, nil
|
|
}
|
|
|
|
// Create creates a new activity
|
|
func (s *ActivityService) Create(ctx context.Context, activity Activity) (*Activity, error) {
|
|
jsonBody, err := json.Marshal(activity)
|
|
if err != nil {
|
|
return nil, &APIError{
|
|
StatusCode: http.StatusInternalServerError,
|
|
Message: "Failed to marshal activity",
|
|
Cause: err,
|
|
}
|
|
}
|
|
|
|
resp, err := s.client.Post(ctx, "/activity-service/activity", bytes.NewReader(jsonBody))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusCreated {
|
|
return nil, &APIError{
|
|
StatusCode: resp.StatusCode,
|
|
Message: "Failed to create activity",
|
|
}
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, &APIError{
|
|
StatusCode: http.StatusInternalServerError,
|
|
Message: "Failed to read activity response",
|
|
Cause: err,
|
|
}
|
|
}
|
|
|
|
var createdActivity Activity
|
|
if err := json.Unmarshal(body, &createdActivity); err != nil {
|
|
return nil, &APIError{
|
|
StatusCode: http.StatusInternalServerError,
|
|
Message: "Failed to parse activity data",
|
|
Cause: err,
|
|
}
|
|
}
|
|
|
|
return &createdActivity, nil
|
|
}
|
|
|
|
// Update updates an existing activity
|
|
func (s *ActivityService) Update(ctx context.Context, activityID int64, update ActivityUpdate) (*Activity, error) {
|
|
jsonBody, err := json.Marshal(update)
|
|
if err != nil {
|
|
return nil, &APIError{
|
|
StatusCode: http.StatusInternalServerError,
|
|
Message: "Failed to marshal activity update",
|
|
Cause: err,
|
|
}
|
|
}
|
|
|
|
path := "/activity-service/activity/" + strconv.FormatInt(activityID, 10)
|
|
resp, err := s.client.Put(ctx, path, bytes.NewReader(jsonBody))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, &APIError{
|
|
StatusCode: resp.StatusCode,
|
|
Message: "Failed to update activity",
|
|
}
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, &APIError{
|
|
StatusCode: http.StatusInternalServerError,
|
|
Message: "Failed to read activity response",
|
|
Cause: err,
|
|
}
|
|
}
|
|
|
|
var updatedActivity Activity
|
|
if err := json.Unmarshal(body, &updatedActivity); err != nil {
|
|
return nil, &APIError{
|
|
StatusCode: http.StatusInternalServerError,
|
|
Message: "Failed to parse activity data",
|
|
Cause: err,
|
|
}
|
|
}
|
|
|
|
return &updatedActivity, nil
|
|
}
|
|
|
|
// Delete deletes an existing activity
|
|
func (s *ActivityService) Delete(ctx context.Context, activityID int64) error {
|
|
path := "/activity-service/activity/" + strconv.FormatInt(activityID, 10)
|
|
resp, err := s.client.Delete(ctx, path, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusNoContent && resp.StatusCode != http.StatusOK {
|
|
return &APIError{
|
|
StatusCode: resp.StatusCode,
|
|
Message: "Failed to delete activity",
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Get retrieves detailed information about a specific activity
|
|
func (s *ActivityService) Get(ctx context.Context, activityID int64) (*ActivityDetails, error) {
|
|
path := "/activity-service/activity/" + strconv.FormatInt(activityID, 10)
|
|
|
|
resp, err := s.client.Get(ctx, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return nil, &APIError{
|
|
StatusCode: resp.StatusCode,
|
|
Message: "Failed to get activity details",
|
|
}
|
|
}
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
if err != nil {
|
|
return nil, &APIError{
|
|
StatusCode: http.StatusInternalServerError,
|
|
Message: "Failed to read activity response",
|
|
Cause: err,
|
|
}
|
|
}
|
|
|
|
var details ActivityDetails
|
|
if err := json.Unmarshal(body, &details); err != nil {
|
|
return nil, &APIError{
|
|
StatusCode: http.StatusInternalServerError,
|
|
Message: "Failed to parse activity data",
|
|
Cause: err,
|
|
}
|
|
}
|
|
|
|
return &details, nil
|
|
}
|
|
|
|
// Export exports an activity in the specified format (gpx, tcx, original)
|
|
func (s *ActivityService) Export(ctx context.Context, activityID int64, format string) (io.ReadCloser, error) {
|
|
path := "/download-service/export/" + format + "/activity/" + strconv.FormatInt(activityID, 10)
|
|
|
|
resp, err := s.client.Get(ctx, path)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
defer resp.Body.Close()
|
|
return nil, &APIError{
|
|
StatusCode: resp.StatusCode,
|
|
Message: "Failed to export activity",
|
|
}
|
|
}
|
|
|
|
return resp.Body, nil
|
|
}
|