strava-ruby-client 0.4.3 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +12 -1
  3. data/README.md +90 -88
  4. data/bin/strava-oauth-token +2 -2
  5. data/bin/strava-webhooks +2 -2
  6. data/lib/strava/api/client.rb +21 -2
  7. data/lib/strava/api/endpoints/routes.rb +2 -2
  8. data/lib/strava/api/endpoints/running_races.rb +0 -12
  9. data/lib/strava/api/endpoints/segments.rb +0 -46
  10. data/lib/strava/api/pagination.rb +60 -0
  11. data/lib/strava/api/ratelimit.rb +159 -0
  12. data/lib/strava/deep_copyable.rb +16 -0
  13. data/lib/strava/models/achievement.rb +1 -1
  14. data/lib/strava/models/activity.rb +48 -2
  15. data/lib/strava/models/activity_stats.rb +1 -1
  16. data/lib/strava/models/activity_total.rb +1 -1
  17. data/lib/strava/models/activity_zone.rb +1 -1
  18. data/lib/strava/models/athlete.rb +1 -2
  19. data/lib/strava/models/authorization.rb +1 -1
  20. data/lib/strava/models/club.rb +1 -1
  21. data/lib/strava/models/club_admin.rb +1 -1
  22. data/lib/strava/models/club_event.rb +1 -1
  23. data/lib/strava/models/club_member.rb +1 -1
  24. data/lib/strava/models/comment.rb +1 -1
  25. data/lib/strava/models/explorer_segment.rb +1 -1
  26. data/lib/strava/models/gear.rb +1 -1
  27. data/lib/strava/models/heart_rate_zone_ranges.rb +1 -1
  28. data/lib/strava/models/kudoser.rb +1 -1
  29. data/lib/strava/models/lap.rb +1 -1
  30. data/lib/strava/models/map.rb +1 -1
  31. data/lib/strava/models/mixins/http_response.rb +15 -0
  32. data/lib/strava/models/photo.rb +1 -1
  33. data/lib/strava/models/photos.rb +1 -1
  34. data/lib/strava/models/power_zone_ranges.rb +1 -1
  35. data/lib/strava/models/response.rb +9 -0
  36. data/lib/strava/models/route.rb +1 -1
  37. data/lib/strava/models/running_race.rb +1 -1
  38. data/lib/strava/models/segment.rb +1 -1
  39. data/lib/strava/models/segment_effort.rb +1 -1
  40. data/lib/strava/models/segment_stats.rb +1 -1
  41. data/lib/strava/models/similar_activities.rb +1 -1
  42. data/lib/strava/models/split.rb +1 -1
  43. data/lib/strava/models/stream.rb +1 -1
  44. data/lib/strava/models/stream_set.rb +1 -1
  45. data/lib/strava/models/timed_zone_range.rb +1 -1
  46. data/lib/strava/models/token.rb +1 -1
  47. data/lib/strava/models/trend.rb +1 -1
  48. data/lib/strava/models/upload.rb +1 -1
  49. data/lib/strava/models/zone_range.rb +1 -1
  50. data/lib/strava/models/zones.rb +1 -1
  51. data/lib/strava/version.rb +1 -1
  52. data/lib/strava/web/api_response.rb +17 -0
  53. data/lib/strava/web/client.rb +2 -2
  54. data/lib/strava/web/connection.rb +4 -4
  55. data/lib/strava/web/raise_response_error.rb +29 -0
  56. data/lib/strava/web/request.rb +3 -2
  57. data/lib/strava/web/response.rb +92 -0
  58. data/lib/strava/webhooks/models/subscription.rb +1 -1
  59. data/lib/strava-ruby-client.rb +9 -4
  60. metadata +17 -12
  61. data/lib/strava/models/segment_leaderboard.rb +0 -12
  62. data/lib/strava/models/segment_leaderboard_entry.rb +0 -14
  63. data/lib/strava/web/raise_error.rb +0 -31
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strava
4
+ module Api
5
+ class Ratelimit
6
+ def initialize(response)
7
+ @response = response
8
+ @headers = response.headers
9
+ @body = response.body
10
+ end
11
+
12
+ def to_s
13
+ to_h.map do |k, v|
14
+ "#{k}: #{v}"
15
+ end.join(', ')
16
+ end
17
+
18
+ #
19
+ # represents all valid ratelimits in a Hash
20
+ #
21
+ # @return [Hash] of ratelimits
22
+ #
23
+ def to_h
24
+ if limit?
25
+ {
26
+ limit: limit,
27
+ usage: usage,
28
+ total_day: total_day,
29
+ total_day_usage: total_day_usage,
30
+ total_day_remaining: total_day_remaining,
31
+ fifteen_minutes: fifteen_minutes,
32
+ fifteen_minutes_usage: fifteen_minutes_usage,
33
+ fifteen_minutes_remaining: fifteen_minutes_remaining
34
+ }
35
+ else
36
+ {}
37
+ end
38
+ end
39
+
40
+ #
41
+ # checks for presence of the header, as it is i.e. not included when
42
+ # asking for a token.
43
+ #
44
+ # @return [TrueClass]
45
+ #
46
+ def limit?
47
+ @headers.key?('x-ratelimit-limit')
48
+ end
49
+
50
+ #
51
+ # returns a string containing the ratelimit for 15 minutes before the comma and
52
+ # the daily ratelimit after the comma
53
+ #
54
+ # @return [String] of the ratelimits for 15 minutes and per day separated by comma
55
+ #
56
+ # @example
57
+ # ```ruby
58
+ # "600,30000"
59
+ # ```
60
+ #
61
+ def limit
62
+ @headers['x-ratelimit-limit']
63
+ end
64
+
65
+ #
66
+ # returns a string containing the used ratelimit for 15 minutes before the comma and
67
+ # the daily ratelimit after the comma
68
+ #
69
+ # @return [String] of the used ratelimits for 15 minutes and per day separated by comma
70
+ #
71
+ # @example
72
+ # ```ruby
73
+ # "333,11337"
74
+ # ```
75
+ #
76
+ def usage
77
+ @headers['x-ratelimit-usage']
78
+ end
79
+
80
+ #
81
+ # fifteen minute ratelimit
82
+ #
83
+ # @return [NilClass] if no ratelimit in http headers
84
+ # @return [Integer] representing the ratelimit
85
+ #
86
+ def fifteen_minutes
87
+ limit? ? extract_ratelimit!(limit).first : nil
88
+ end
89
+
90
+ #
91
+ # total day ratelimit
92
+ #
93
+ # @return [NilClass] if no ratelimit in http headers
94
+ # @return [Integer] representing the ratelimit
95
+ #
96
+ def total_day
97
+ limit? ? extract_ratelimit!(limit).last : nil
98
+ end
99
+
100
+ #
101
+ # fifteen minute ratelimit used
102
+ #
103
+ # @return [NilClass] if no ratelimit in http headers
104
+ # @return [Integer] representing the ratelimit
105
+ #
106
+ def fifteen_minutes_usage
107
+ limit? ? extract_ratelimit!(usage).first : nil
108
+ end
109
+
110
+ #
111
+ # total day ratelimit used
112
+ #
113
+ # @return [NilClass] if no ratelimit in http headers
114
+ # @return [Integer] representing the ratelimit
115
+ #
116
+ def total_day_usage
117
+ limit? ? extract_ratelimit!(usage).last : nil
118
+ end
119
+
120
+ #
121
+ # fifteen minute ratelimit remaining
122
+ #
123
+ # @return [NilClass] if no ratelimit in http headers
124
+ # @return [Integer] representing the ratelimit
125
+ #
126
+ def fifteen_minutes_remaining
127
+ return nil unless fifteen_minutes && fifteen_minutes_usage
128
+
129
+ fifteen_minutes - fifteen_minutes_usage
130
+ end
131
+
132
+ #
133
+ # total day ratelimit remaining
134
+ #
135
+ # @return [NilClass] if no ratelimit in http headers
136
+ # @return [Integer] representing the ratelimit
137
+ #
138
+ def total_day_remaining
139
+ return nil unless total_day && total_day_usage
140
+
141
+ total_day - total_day_usage
142
+ end
143
+
144
+ private
145
+
146
+ #
147
+ # ratelimit comes as a String of two Integers separated by comma
148
+ #
149
+ # @param [String] ratelimit
150
+ #
151
+ # @return [Array<Integer>]
152
+ #
153
+ def extract_ratelimit!(ratelimit)
154
+ @lookup_table_ratelimits ||= {}
155
+ @lookup_table_ratelimits[ratelimit] ||= ratelimit.split(',').map(&:to_i)
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strava
4
+ module DeepCopyable
5
+ #
6
+ # Ruby's way of creating a true deep copy/clone
7
+ #
8
+ # @param [Object] obj of any kind
9
+ #
10
+ # @return [Object] deep clone of what was passed into
11
+ #
12
+ def deep_copy(obj)
13
+ Marshal.load(Marshal.dump(obj))
14
+ end
15
+ end
16
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Achievement < Model
5
+ class Achievement < Strava::Models::Response
6
6
  property 'rank'
7
7
  property 'type'
8
8
  property 'type_id'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Activity < Model
5
+ class Activity < Strava::Models::Response
6
6
  include Mixins::Time
7
7
  include Mixins::Distance
8
8
  include Mixins::Elevation
@@ -13,7 +13,8 @@ module Strava
13
13
  property 'athlete', transform_with: ->(v) { Strava::Models::Athlete.new(v) }
14
14
  property 'name'
15
15
  property 'description'
16
- property 'type'
16
+ property 'type' # deprecated
17
+ property 'sport_type'
17
18
  property 'workout_type'
18
19
  property 'id'
19
20
  property 'external_id'
@@ -99,6 +100,51 @@ module Strava
99
100
  "https://www.strava.com/activities/#{id}"
100
101
  end
101
102
 
103
+ def sport_type_emoji
104
+ case sport_type
105
+ when 'AlpineSki' then '⛷️'
106
+ when 'BackcountrySki' then '🎿️'
107
+ # when "Canoeing" then ''
108
+ # when "Crossfit" then ''
109
+ # when "Elliptical" then ''
110
+ when 'Golf' then '🏌️'
111
+ # when "Handcycle" then ''
112
+ when 'Hike' then '🥾'
113
+ when 'IceSkate' then '⛸'
114
+ when 'InlineSkate' then "\u{1F6FC}"
115
+ # when "Kayaking" then ''
116
+ # when "Kitesurf" then ''
117
+ when 'MountainBikeRide', 'EMountainBikeRide' then '🚵'
118
+ # when "NordicSki" then ''
119
+ when 'Ride', 'EBikeRide', 'VirtualRide', 'GravelRide' then '🚴'
120
+ when 'RockClimbing' then '🧗'
121
+ # when 'RollerSki' then ''
122
+ when 'Rowing' then '🚣'
123
+ when 'Run', 'VirtualRun', 'TrailRun' then '🏃'
124
+ when 'Sail' then '⛵️'
125
+ when 'Skateboard' then '🛹'
126
+ when 'Snowboard' then '🏂'
127
+ # when 'Snowshoe' then ''
128
+ when 'Soccer' then '⚽️'
129
+ # when 'StairStepper' then ''
130
+ # when 'StandUpPaddling' then ''
131
+ when 'Surfing' then '🏄'
132
+ when 'Swim' then '🏊'
133
+ # when 'Velomobile' then ''
134
+ when 'Walk' then '🚶'
135
+ when 'WeightTraining' then '🏋️'
136
+ when 'Wheelchair' then '♿'
137
+ # when 'Windsurf' then ''
138
+ # when 'Workout' then ''
139
+ when 'Yoga' then '🧘'
140
+ end
141
+ end
142
+
143
+ #
144
+ # @deprecated Use {#sport_type_emoji} instead.
145
+ #
146
+ # @return [String] precisely an emoji
147
+ #
102
148
  def type_emoji
103
149
  case type
104
150
  when 'Run', 'VirtualRun' then '🏃'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class ActivityStats < Model
5
+ class ActivityStats < Strava::Models::Response
6
6
  property 'biggest_ride_distance'
7
7
  property 'biggest_climb_elevation_gain'
8
8
  property 'recent_ride_totals', transform_with: ->(v) { Strava::Models::ActivityTotal.new(v) }
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class ActivityTotal < Model
5
+ class ActivityTotal < Strava::Models::Response
6
6
  include Mixins::Distance
7
7
  include Mixins::Time
8
8
  include Mixins::Elevation
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class ActivityZone < Model
5
+ class ActivityZone < Strava::Models::Response
6
6
  property 'score'
7
7
  property 'distribution_buckets', transform_with: ->(v) { v.map { |r| Strava::Models::TimedZoneRange.new(r) } }
8
8
  property 'type'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Athlete < Model
5
+ class Athlete < Strava::Models::Response
6
6
  property 'id'
7
7
  property 'username'
8
8
  property 'resource_state'
@@ -18,7 +18,6 @@ module Strava
18
18
  property 'badge_type_id'
19
19
  property 'profile'
20
20
  property 'profile_medium'
21
- property 'email'
22
21
  property 'follower'
23
22
  property 'friend'
24
23
  property 'summit'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Authorization < Model
5
+ class Authorization < Strava::Models::Response
6
6
  property 'access_token'
7
7
  end
8
8
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Club < Model
5
+ class Club < Strava::Models::Response
6
6
  property 'id'
7
7
  property 'resource_state'
8
8
  property 'name'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class ClubAdmin < Model
5
+ class ClubAdmin < Strava::Models::Response
6
6
  property 'resource_state'
7
7
  property 'firstname'
8
8
  property 'lastname'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class ClubEvent < Model
5
+ class ClubEvent < Strava::Models::Response
6
6
  property 'id'
7
7
  property 'resource_state'
8
8
  property 'title'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class ClubMember < Model
5
+ class ClubMember < Strava::Models::Response
6
6
  property 'resource_state'
7
7
  property 'firstname'
8
8
  property 'lastname'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Comment < Model
5
+ class Comment < Strava::Models::Response
6
6
  property 'id'
7
7
  property 'activity_id'
8
8
  property 'resource_state'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class ExplorerSegment < Model
5
+ class ExplorerSegment < Strava::Models::Response
6
6
  include Mixins::Distance
7
7
 
8
8
  property 'id'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Gear < Model
5
+ class Gear < Strava::Models::Response
6
6
  include Mixins::Distance
7
7
 
8
8
  property 'id'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class HeartRateZoneRanges < Model
5
+ class HeartRateZoneRanges < Strava::Models::Response
6
6
  property 'custom_zones'
7
7
  property 'zones', transform_with: ->(v) { v.map { |r| Strava::Models::ZoneRange.new(r) } }
8
8
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Kudoser < Model
5
+ class Kudoser < Strava::Models::Response
6
6
  property 'destination_url'
7
7
  property 'display_name'
8
8
  property 'avatar_url'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Lap < Model
5
+ class Lap < Strava::Models::Response
6
6
  include Mixins::Time
7
7
  include Mixins::Distance
8
8
  include Mixins::Elevation
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Map < Model
5
+ class Map < Strava::Models::Response
6
6
  property 'id'
7
7
  property 'summary_polyline'
8
8
  property 'resource_state'
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strava
4
+ module Models
5
+ module Mixins
6
+ module HttpResponse
7
+ extend ActiveSupport::Concern
8
+
9
+ included do
10
+ property 'http_response', transform_with: ->(v) { Strava::Web::ApiResponse.new(v) }
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Photo < Model
5
+ class Photo < Strava::Models::Response
6
6
  property 'id'
7
7
  property 'unique_id'
8
8
  property 'urls'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Photos < Model
5
+ class Photos < Strava::Models::Response
6
6
  property 'primary', transform_with: ->(v) { Strava::Models::Photo.new(v) }
7
7
  property 'use_primary_photo'
8
8
  property 'count'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class PowerZoneRanges < Model
5
+ class PowerZoneRanges < Strava::Models::Response
6
6
  property 'zones', transform_with: ->(v) { v.map { |r| Strava::Models::ZoneRange.new(r) } }
7
7
  end
8
8
  end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strava
4
+ module Models
5
+ class Response < Model
6
+ include Mixins::HttpResponse
7
+ end
8
+ end
9
+ end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Route < Model
5
+ class Route < Strava::Models::Response
6
6
  include Mixins::Distance
7
7
  include Mixins::Elevation
8
8
  include Mixins::Time
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class RunningRace < Model
5
+ class RunningRace < Strava::Models::Response
6
6
  include Mixins::Distance
7
7
 
8
8
  property 'id'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Segment < Model
5
+ class Segment < Strava::Models::Response
6
6
  include Mixins::Distance
7
7
  include Mixins::Elevation
8
8
  include Mixins::Time
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class SegmentEffort < Model
5
+ class SegmentEffort < Strava::Models::Response
6
6
  include Mixins::Distance
7
7
  include Mixins::Time
8
8
  include Mixins::StartDateLocal
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class SegmentStats < Model
5
+ class SegmentStats < Strava::Models::Response
6
6
  include Mixins::Time
7
7
 
8
8
  property 'pr_elapsed_time'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class SimilarActivities < Model
5
+ class SimilarActivities < Strava::Models::Response
6
6
  property 'average_speed'
7
7
  property 'resource_state'
8
8
  property 'effort_count'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Split < Model
5
+ class Split < Strava::Models::Response
6
6
  include Mixins::Distance
7
7
  include Mixins::Time
8
8
  include Mixins::Elevation
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Stream < Model
5
+ class Stream < Strava::Models::Response
6
6
  property 'original_size'
7
7
  property 'resolution'
8
8
  property 'series_type'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class StreamSet < Model
5
+ class StreamSet < Strava::Models::Response
6
6
  property 'distance', transform_with: ->(v) { Strava::Models::Stream.new(v) }
7
7
  property 'time', transform_with: ->(v) { Strava::Models::Stream.new(v) }
8
8
  property 'latlng', transform_with: ->(v) { Strava::Models::Stream.new(v) }
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class TimedZoneRange < Model
5
+ class TimedZoneRange < Strava::Models::Response
6
6
  property 'max'
7
7
  property 'min'
8
8
  property 'time'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Token < Model
5
+ class Token < Strava::Models::Response
6
6
  property 'token_type'
7
7
  property 'access_token'
8
8
  property 'refresh_token'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Trend < Model
5
+ class Trend < Strava::Models::Response
6
6
  property 'speeds'
7
7
  property 'current_activity_index'
8
8
  property 'min_speed'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Upload < Model
5
+ class Upload < Strava::Models::Response
6
6
  property 'id'
7
7
  property 'external_id'
8
8
  property 'error'
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class ZoneRange < Model
5
+ class ZoneRange < Strava::Models::Response
6
6
  property 'max'
7
7
  property 'min'
8
8
  end
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Strava
4
4
  module Models
5
- class Zones < Model
5
+ class Zones < Strava::Models::Response
6
6
  property 'heart_rate', transform_with: ->(v) { Strava::Models::HeartRateZoneRanges.new(v) }
7
7
  property 'power', transform_with: ->(v) { Strava::Models::PowerZoneRanges.new(v) }
8
8
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Strava
4
- VERSION = '0.4.3'
4
+ VERSION = '1.0.0'
5
5
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Strava
4
+ module Web
5
+ class ApiResponse
6
+ attr_accessor :response, :ratelimit
7
+
8
+ #
9
+ # @param [Faraday::Response] http_response
10
+ #
11
+ def initialize(http_response)
12
+ @response = http_response
13
+ @ratelimit = Strava::Api::Ratelimit.new(@response)
14
+ end
15
+ end
16
+ end
17
+ end