strava-ruby-client 0.4.3 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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