mirror of
https://github.com/sstent/go-garth.git
synced 2026-01-25 16:42:28 +00:00
sync
This commit is contained in:
306
activities.go
Normal file
306
activities.go
Normal file
@@ -0,0 +1,306 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user