fitbit-omni-api 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,15 @@
1
+ ---
2
+ !binary "U0hBMQ==":
3
+ metadata.gz: !binary |-
4
+ NDNjZWY5ZGM5OWJmOWY1ZjI1ZDg5NGY1ODdiNjI1M2I1NzBmM2I3ZQ==
5
+ data.tar.gz: !binary |-
6
+ ZDk4NGZjYzI2MjE1MmQxOTgzOWIyOTNhMzllODBmYjJkZjRlOGVhYg==
7
+ SHA512:
8
+ metadata.gz: !binary |-
9
+ NjNlNTQ4ZmE2NTQyNWNiOGUxNGVhYmM4MmQ1ZmZiMjViZjFjZmE3NTg4YWI4
10
+ MmQ4ZWFlYTI5OTAwMDdlNjIyMGRmMGRhMzg4ZWM0ZWIzNzdjYmE5Njg5YWYy
11
+ YmM1MjQ2YWYwMjk3MzMzNzU4NTQwZGM3ODA0NDliZTVmZDA5YmQ=
12
+ data.tar.gz: !binary |-
13
+ ZDkwZjc5MjQxMDVlNTY0NjczNTBkZThlZTA3NDQ0NmNmMjI3MDM5MGZhY2M4
14
+ NjNjOWUzZTY1ZTliMjlhMDFmZTU3YTg0MWFlODZhNmNiMzdkODdhNmU3Nzli
15
+ NzQ4ZWI1YTY3NTMzOTI3ZjExZmUxMWJiMTI5NTk5OTExMTIzOGI=
data/LICENSE.md CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2012 TK Gospodinov, (c) Scott McGrath
1
+ Copyright (c) 2014 TK Gospodinov, (c) Scott McGrath
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -1,75 +1,51 @@
1
- # Fitbit OmniAuth Strategy
1
+ # Fitbit-Omni-Api
2
2
 
3
- This gem is an OmniAuth 1.0+ Strategy for the [Fitbit API](https://wiki.fitbit.com/display/API/OAuth+Authentication+in+the+Fitbit+API).
3
+ This gem uses the [OmniAuth-Fitbit Strategy](https://github.com/tkgospodinov/omniauth-fitbit) for authentication.
4
+ See that repo for more info.
4
5
 
5
- ## Usage
6
+ ## Accessing the Fitbit API
7
+ ### There are breaking changes in this version.
6
8
 
7
- Add the strategy to your `Gemfile`:
9
+ ### Installation
8
10
 
11
+ Add the gem to your Gemfile
9
12
  ```ruby
10
13
  gem 'fitbit-omni-api'
11
14
  ```
12
15
 
13
- Then integrate the strategy into your middleware:
16
+ The gem class is simply 'Fitbit::Api' and Fitbit Api requests are made with the 'Fitbit::Api.request' method.
14
17
 
15
- ```ruby
16
- use OmniAuth::Builder do
17
- provider :fitbit, 'consumer_key', 'consumer_secret'
18
- end
19
- ```
18
+ Each API request needs:
19
+ * Your Fitbit consumer_key and consumer_secret (acquired from [dev.fitbit.com](http://dev.fitbit.cpm))
20
+ * A params Hash with api-method and required parameters
21
+ * Optionally, Fitbit auth tokens or user-id if required
20
22
 
21
- In Rails, create a new file under config/initializers called omniauth.rb to plug the strategy into your middleware stack.
23
+ This is the structure of an authenticated API call:
22
24
 
23
25
  ```ruby
24
- Rails.application.config.middleware.use OmniAuth::Builder do
25
- provider :fitbit, 'consumer_key', 'consumer_secret'
26
- end
27
- ```
28
-
29
- To register your application with Fitbit and obtain a consumer key and secret, go to the [Fitbit application registration](https://dev.fitbit.com/apps/new).
30
-
31
- For additional information about OmniAuth, visit [OmniAuth wiki](https://github.com/intridea/omniauth/wiki).
32
-
33
- For a short tutorial on how to use OmniAuth in your Rails application, visit [this tutsplus.com tutorial](http://net.tutsplus.com/tutorials/ruby/how-to-use-omniauth-to-authenticate-your-users/).
34
-
35
- ## Accessing the Fitbit API
36
-
37
- An API call can be instantiated with `Fitbit::Api.new({}).api_call()`
38
- Each call requires:
39
- * Fitbit consumer_key and consumer_secret
40
- * A params Hash containing the Fitbit API method, plus all required parameters
41
- * Optionally, for authenticated API calls, your user's Fitbit auth token and auth secret
42
-
43
- An example of an authenticated API call:
44
-
45
- ```ruby
46
- Fitbit::Api.new({}).api_call(
26
+ Fitbit::Api.request(
47
27
  'consumer_key',
48
28
  'consumer_secret',
49
- params,
29
+ params = { 'api-method' => 'api-method-here', 'start-time' => '00:00' }
50
30
  'auth_token',
51
31
  'auth_secret'
52
32
  )
53
33
  ```
54
34
 
55
- This gem supports the Fitbit Resource Access API and the Fitbit Subscriptions API.
35
+ This gem supports the Fitbit Resource Access API, Fitbit Subscriptions API and Fitbit Partner API.
56
36
 
57
37
  To access the Resource Access API, consult the API docs and provide the required parameters. For example,
58
- the API-Search-Foods method requires 'api-version', 'query' and 'response-format'. There's also an optional
59
- Request Header parameter, 'Accept-Locale'. A call to API-Search-Foods might look like this:
38
+ the API-Search-Foods method requires _'api-version'_, _'query'_ and _'response-format'_, plus there's an optional
39
+ _'Accept-Locale'_ Request Header parameter.
40
+
41
+ A call to API-Search-Foods might look like this:
60
42
 
61
43
  ```ruby
62
44
  def fitbit_foods_search
63
- params = {
64
- 'api-method' => 'api-search-foods',
65
- 'query' => 'buffalo chicken',
66
- 'response-format' => 'json',
67
- 'Accept-Locale' => 'en_US',
68
- }
69
- request = Fitbit::Api.new({}).api_call(
45
+ request = Fitbit::Api.request(
70
46
  'consumer_key',
71
47
  'consumer_secret',
72
- params,
48
+ params={'api-method'=>'api-search-foods','query'=>'buffalo chicken','Accept-Locale'=>'en_US'}
73
49
  'auth_token',
74
50
  'auth_secret'
75
51
  )
@@ -77,17 +53,18 @@ def fitbit_foods_search
77
53
  end
78
54
  ```
79
55
 
80
- A few notes: 'api-version' defaults to '1' and can be omitted from Fitbit-Omni-Api calls.
81
- If you omit the 'response-format', the response will be in the default xml format.
82
- Some authenticated API methods can be accessed without auth tokens, if you supply a user's
83
- user-id (see the API docs for details).
56
+ ### Fitbit Subscriptions API
57
+ Use API-Create-Subscription and API-Delete-Subscription API methods to to access the Subscription API.
58
+ Consult the Subscription API docs to discover the required parameters.
59
+ To subscribe to ALL of a user's changes, make 'collection-path' = 'all'.
60
+
61
+ ### Fitbit Partner API
62
+ These features are only accessible with approved Fitbit developer accounts.
84
63
 
85
- To access the Subscription API, two new api methods were created just for this gem:
86
- API-Create-Subscription and API-Delete-Subscription. These api methods only exist in this gem,
87
- not the Fitbit API. If you consult the Subscription API docs for adding and deleting subscriptions,
88
- and supply the required parameters, these two api methods work just as described for the
89
- Resource Access API. NOTE: To subscribe to ALL of a user's changes, make 'collection-path' = 'all'.
64
+ ### Defaults:
65
+ * 'api-version' defaults to '1'
66
+ * 'response-format' defaults to 'xml'
90
67
 
91
- ## Copyright
68
+ ### Copyright
92
69
 
93
- Copyright (c) 2012 TK Gospodinov, (c) Scott McGrath 2013. See [LICENSE](https://github.com/tkgospodinov/omniauth-fitbit/blob/master/LICENSE.md) for details.
70
+ Copyright (c) 2012 TK Gospodinov, (c) Scott McGrath 2013. See [LICENSE](https://github.com/scrawlon/fitbit-omni-api/blob/master/LICENSE.md) for details.
data/Rakefile CHANGED
File without changes
@@ -1,767 +1,776 @@
1
1
  require 'omniauth-fitbit'
2
2
 
3
3
  module Fitbit
4
- class Api < OmniAuth::Strategies::Fitbit
5
-
6
- def api_call consumer_key, consumer_secret, params, auth_token="", auth_secret=""
7
- api_params = get_lowercase_api_method(params)
8
- api_method = api_params['api-method']
9
- fitbit = @@fitbit_methods[api_method]
10
- api_error = get_api_errors(api_params.keys, fitbit, auth_token, auth_secret)
11
- raise "#{api_method} " + api_error if api_error
12
- access_token = build_request(consumer_key, consumer_secret, auth_token, auth_secret)
13
- send_api_request(api_params, fitbit, access_token)
14
- end
4
+ class Api < OmniAuth::Strategies::OAuth
5
+ class << self
6
+ def request consumer_key, consumer_secret, params, auth_token=nil, auth_secret=nil
7
+ begin
8
+ fitbit_api_method = get_api_method(params['api-method'])
9
+ verify_api_call(params, fitbit_api_method, auth_token, auth_secret)
10
+ rescue => e
11
+ raise e
12
+ end
13
+ access_token = build_request(consumer_key, consumer_secret, auth_token, auth_secret)
14
+ send_api_request(params, fitbit_api_method, access_token)
15
+ end
15
16
 
16
- def get_fitbit_methods
17
- @@fitbit_methods
18
- end
17
+ def get_fitbit_methods
18
+ @@fitbit_methods
19
+ end
19
20
 
20
- private
21
+ private
21
22
 
22
- def get_lowercase_api_method params
23
- params['api-method'] ||= nil
24
- params['api-method'].downcase!
25
- params
26
- end
23
+ def get_api_method api_method
24
+ api_method.downcase! if api_method.is_a? String
25
+ @@fitbit_methods[api_method]
26
+ end
27
27
 
28
- def get_api_errors params_keys, fitbit, auth_token="", auth_secret=""
29
- if fitbit
30
- no_auth_tokens = true if (auth_token == "" or auth_secret == "")
31
- get_error_message(params_keys, fitbit, no_auth_tokens)
32
- else
33
- "is not a valid Fitbit API method."
28
+ def verify_api_call params, fitbit_api_method, auth_token, auth_secret
29
+ error = get_error_message(params.keys, fitbit_api_method, auth_token, auth_secret)
30
+ raise "#{params['api-method']} #{error}" if error
34
31
  end
35
- end
36
32
 
37
- def get_error_message params_keys, fitbit, no_auth_tokens
38
- if missing_url_parameters? fitbit['url_parameters'], params_keys
39
- url_parameters_error(fitbit['url_parameters'], params_keys)
40
- elsif fitbit['post_parameters'] and missing_post_parameters? fitbit['post_parameters'], params_keys
41
- post_parameters_error(fitbit['post_parameters'], params_keys)
42
- elsif fitbit['auth_required'] and no_auth_tokens
43
- auth_error(fitbit['auth_required'], params_keys.include?('user-id'))
33
+ def get_error_message params_keys, fitbit_api_method, auth_token, auth_secret
34
+ error_types = [:post_parameters, :url_parameters, :auth_required]
35
+ if !fitbit_api_method
36
+ "is not a valid Fitbit API method."
37
+ else
38
+ error_types.each do |x|
39
+ case x
40
+ when :post_parameters
41
+ error = missing_post_parameters_error(fitbit_api_method[x], params_keys)
42
+ when :url_parameters
43
+ error = missing_url_parameters_error(fitbit_api_method[x], params_keys)
44
+ when :auth_required
45
+ error = missing_auth_tokens_error(fitbit_api_method[x], params_keys, auth_token, auth_secret)
46
+ end
47
+ return error if error
48
+ end
49
+ nil
50
+ end
44
51
  end
45
- end
46
52
 
47
- def missing_url_parameters? required, supplied
48
- url_parameters = get_url_parameters(required, supplied)
49
- url_parameters and url_parameters & supplied != url_parameters
50
- end
53
+ def missing_post_parameters_error required, supplied
54
+ required = {} unless required.is_a? Hash
55
+ required.each do |k,v|
56
+ supplied_required = v & supplied unless k == 'required_if'
57
+ case k
58
+ when 'required'
59
+ error = missing_required_post_parameters_error(v, supplied, supplied_required)
60
+ when 'exclusive'
61
+ error = missing_exclusive_post_parameters_error(v, supplied_required)
62
+ when 'one_required'
63
+ error = missing_one_required_post_parameters_error(v, supplied_required)
64
+ when 'required_if'
65
+ error = missing_required_if_post_parameters_error(v, supplied)
66
+ end
67
+ return error if error
68
+ end
69
+ nil
70
+ end
51
71
 
52
- def get_url_parameters required, supplied
53
- if required.is_a? Hash
54
- get_dynamic_url_parameters(required, supplied)
55
- else
56
- required
72
+ def missing_required_post_parameters_error required, supplied, supplied_required
73
+ "requires POST parameters #{required}. You're missing #{required-supplied}." if supplied_required != required
57
74
  end
58
- end
59
75
 
60
- def get_dynamic_url_parameters required, supplied
61
- required.keys.each { |k| return required[k] if supplied.include? k }
62
- end
76
+ def missing_exclusive_post_parameters_error required, supplied_required
77
+ total_supplied = supplied_required.length
78
+ if total_supplied < 1
79
+ "requires one of these POST parameters: #{required}."
80
+ elsif total_supplied > 1
81
+ "allows only one of these POST parameters: #{required}. You used #{supplied_required.join(' AND ')}."
82
+ else
83
+ nil
84
+ end
85
+ end
63
86
 
64
- def missing_post_parameters? required, supplied
65
- error = nil
66
- required.each do |k,v|
67
- supplied_required = required[k] & supplied if k != 'required_if'
68
- case k
69
- when 'required'
70
- error = k if supplied_required != required[k]
71
- when 'exclusive'
72
- error = k + '_too_few' if supplied_required.length < 1
73
- error = k + '_too_many' if supplied_required.length > 1
74
- when 'one_required'
75
- error = k if supplied_required.length < 1
76
- when 'required_if'
77
- required[k].each do |key,val|
78
- error = k if supplied.include? key and !supplied.include? val
79
- end
87
+ def missing_one_required_post_parameters_error required, supplied_required
88
+ "requires at least one of the following POST parameters: #{required}." if supplied_required.length < 1
89
+ end
90
+
91
+ def missing_required_if_post_parameters_error required, supplied
92
+ required.each do |k,v|
93
+ return "requires POST parameter #{v} when you use POST parameter #{k}." if supplied.include? k and !supplied.include? v
80
94
  end
81
95
  end
82
- error
83
- end
84
96
 
85
- def url_parameters_error required, supplied
86
- if required.is_a? Hash
87
- get_dynamic_url_error(required, supplied)
88
- else
89
- "requires #{required}. You're missing #{required-supplied}."
97
+ def missing_auth_tokens_error auth_required, params_keys, auth_token, auth_secret
98
+ error = "requires user auth_token and auth_secret"
99
+ no_auth_tokens = !auth_token or !auth_secret
100
+ if !auth_required
101
+ nil
102
+ elsif no_auth_tokens and auth_required != 'user-id'
103
+ "#{error}."
104
+ elsif no_auth_tokens and auth_required == 'user-id'
105
+ !params_keys.include?('user-id') ? "#{error}, unless you include [\"user-id\"]." : nil
106
+ end
90
107
  end
91
- end
92
108
 
93
- def get_dynamic_url_error required, supplied
94
- error = "requires 1 of #{required.length} options: "
95
- required.keys.each_with_index do |x,i|
96
- error << "(#{i+1}) #{required[x]} "
109
+ def missing_url_parameters_error required, supplied
110
+ if required.is_a? Hash
111
+ get_dynamic_url_error(required, supplied)
112
+ else
113
+ required = get_url_parameters_variables(required)
114
+ required - supplied != [] ? "requires #{required}. You're missing #{required-supplied}." : nil
115
+ end
97
116
  end
98
- error << "You supplied: #{supplied}"
99
- end
100
117
 
101
- def post_parameters_error required, supplied
102
- error_type = missing_post_parameters? required, supplied
103
- e = 'exclusive' if error_type == 'exclusive_too_few' or error_type == 'exclusive_too_many'
104
- e ||= error_type
105
-
106
- case error_type
107
- when 'required'
108
- "requires POST parameters #{required[e]}. You're missing #{required[e]-supplied}."
109
- when 'exclusive_too_few'
110
- "requires one of these POST parameters: #{required[e]}."
111
- when 'exclusive_too_many'
112
- supplied_required = required[e] & supplied
113
- supplied_required_string = supplied_required.join(' AND ')
114
- "allows only one of these POST parameters #{required[e]}. You used #{supplied_required_string}."
115
- when 'one_required'
116
- "requires at least one of the following POST parameters: #{required[e]}."
117
- when 'required_if'
118
- required[e].each do |k,v|
119
- if supplied.include? k and !supplied.include? v
120
- return "requires POST parameter #{v} when you use POST parameter #{k}."
121
- end
118
+ def get_dynamic_url_error required, supplied
119
+ if missing_dynamic_url_parameters?(required, supplied)
120
+ required = required.select { |k,v| k != 'optional' }
121
+ options = required.keys.map.with_index(1) { |x,i| "(#{i}) #{get_url_parameters_variables(required[x])}" }.join(' ')
122
+ "requires 1 of #{required.length} options: #{options}. You supplied: #{supplied}."
123
+ else
124
+ nil
122
125
  end
123
126
  end
124
- end
125
127
 
126
- def auth_error auth_required, auth_supplied
127
- if auth_required == 'user-id' and !auth_supplied
128
- "requires user auth_token and auth_secret, unless you include [\"user-id\"]."
129
- elsif auth_required != 'user-id'
130
- "requires user auth_token and auth_secret."
128
+ def missing_dynamic_url_parameters? required, supplied
129
+ required = get_dynamic_url_parameters(required, supplied)
130
+ required = get_url_parameters_variables(required) if required
131
+ required & supplied != required
131
132
  end
132
- end
133
133
 
134
- def build_request consumer_key, consumer_secret, auth_token, auth_secret
135
- fitbit = Fitbit::Api.new :fitbit, consumer_key, consumer_secret
136
- OAuth::AccessToken.new fitbit.consumer, auth_token, auth_secret
137
- end
134
+ def get_dynamic_url_parameters required, supplied
135
+ optional = get_optional_url_parameters(required, supplied)
136
+ required.keys.each { |k| return required[k] + optional if supplied.include? k }
137
+ nil
138
+ end
138
139
 
139
- def send_api_request params, fitbit, access_token
140
- http_method = fitbit['http_method']
141
- request_url = build_url(params, fitbit, http_method)
142
- request_headers = get_request_headers(params, fitbit) if fitbit['request_headers']
140
+ def get_url_parameters_variables url_parameters
141
+ url_parameters.select { |x| x.include? "<" }.map { |x| x.delete "<>" } if has_url_variables? url_parameters
142
+ end
143
143
 
144
- if http_method == 'get' or http_method == 'delete'
145
- access_token.request( http_method, "http://api.fitbit.com#{request_url}", request_headers )
146
- else
147
- access_token.request( http_method, "http://api.fitbit.com#{request_url}", "", request_headers )
144
+ def has_url_variables? url_parameters
145
+ url_parameters.each{ |x| return true if x.include? "<" }
148
146
  end
149
- end
150
147
 
151
- def build_url params, fitbit, http_method
152
- api_version = @@api_version
153
- api_url_resources = get_url_resources(params, fitbit)
154
- api_format = get_response_format(params['response-format'])
155
- api_post_parameters = get_post_parameters(params, fitbit) if http_method == 'post'
156
- api_query = uri_encode_query(params['query'])
148
+ def build_request consumer_key, consumer_secret, auth_token, auth_secret
149
+ fitbit = Fitbit::Api.new :fitbit, consumer_key, consumer_secret
150
+ OAuth::AccessToken.new fitbit.consumer, auth_token, auth_secret
151
+ end
157
152
 
158
- "/#{api_version}/#{api_url_resources}.#{api_format}#{api_query}#{api_post_parameters}"
159
- end
153
+ def get_url_parameters required, supplied
154
+ required.is_a?(Hash) ? get_dynamic_url_parameters(required, supplied) : required
155
+ end
160
156
 
161
- def get_post_parameters params, fitbit
162
- return nil if is_subscription? params['api-method']
163
- not_post_parameters = ['request_headers', 'url_parameters']
164
- ignore = ['api-method', 'response-format']
165
- not_post_parameters.each do |x|
166
- fitbit[x].each { |y| ignore.push(y) } if fitbit[x]
157
+ def get_optional_url_parameters required, supplied
158
+ optional = [] unless required['optional']
159
+ optional ? optional : required['optional'].select { |k,v| supplied.include? k }.values.flatten
167
160
  end
168
- post_parameters = params.select { |k,v| !ignore.include? k }
169
161
 
170
- "?" + OAuth::Helper.normalize(post_parameters)
171
- end
172
-
173
- def get_request_headers params, fitbit
174
- request_headers = fitbit['request_headers']
175
- params.select { |k,v| request_headers.include? k }
176
- end
162
+ def insert_dynamic_url_parameters params, url_parameters, auth_required
163
+ url_parameters.map do |x|
164
+ url_variable = x.delete "<>"
177
165
 
178
- def get_url_resources params, fitbit
179
- params_keys = params.keys
180
- api_ids = get_url_parameters(fitbit['url_parameters'], params_keys)
181
- api_resources = get_url_parameters(fitbit['resources'], params_keys)
182
- dynamic_url = add_ids(params, api_ids, api_resources, fitbit['auth_required']) if api_ids or params['user-id']
183
- dynamic_url ||= api_resources
184
- dynamic_url.join("/")
185
- end
166
+ if x == '-'
167
+ insert_user_id(auth_required, params['user-id'])
168
+ elsif params.include? url_variable and !params.include? x
169
+ is_subscription_variable?(url_variable) ? insert_subscription_variable(params, url_variable) : params[url_variable]
170
+ else
171
+ x
172
+ end
173
+ end - [nil]
174
+ end
186
175
 
187
- def add_ids params, api_ids, api_resources, auth_required
188
- api_resources_copy = api_resources.dup
189
- api_resources_copy.each_with_index do |x, i|
190
- id = x.delete "<>"
191
-
192
- if x == '-' and auth_required == 'user-id' and params['user-id']
193
- api_resources_copy[i] = params['user-id']
194
- elsif id == 'collection-path' and params[id] == 'all'
195
- api_resources_copy.delete(x)
196
- elsif id == 'subscription-id' and params['collection-path'] != 'all'
197
- api_resources_copy[i] = params[id] + "-" + params['collection-path']
198
- api_resources_copy.delete('<collection-path>')
199
- elsif api_ids and api_ids.include? id and !api_ids.include? x
200
- api_resources_copy[i] = params[id]
201
- end
176
+ def insert_user_id auth_required, user_id
177
+ (auth_required == 'user-id' and user_id) ? user_id : '-'
202
178
  end
203
- end
204
179
 
205
- def is_subscription? api_method
206
- api_method == 'api-create-subscription' or api_method == 'api-delete-subscription'
207
- end
180
+ def is_subscription_variable? url_variable
181
+ subscription_variables = ['subscription-id', 'collection-path']
182
+ subscription_variables.include? url_variable
183
+ end
208
184
 
209
- def get_response_format api_format
210
- api_format ? api_format.downcase : 'xml'
211
- end
185
+ def insert_subscription_variable params, url_variable
186
+ if url_variable == 'subscription-id'
187
+ params['collection-path'] == 'all' ? params[url_variable] : "#{params[url_variable]}-#{params['collection-path']}"
188
+ else
189
+ params['collection-path'] != 'all' ? params[url_variable] : nil
190
+ end
191
+ end
212
192
 
213
- def uri_encode_query query
214
- query ? "?" + OAuth::Helper.normalize({ 'query' => query }) : ""
215
- end
193
+ def send_api_request params, fitbit, access_token
194
+ http_method = fitbit[:http_method]
195
+ request_headers = get_request_headers(params, fitbit[:request_headers])
196
+ request_url = build_url(params, fitbit, http_method)
216
197
 
217
- @@api_version = 1
218
-
219
- @@fitbit_methods = {
220
- 'api-accept-invite' => {
221
- 'auth_required' => true,
222
- 'http_method' => 'post',
223
- 'post_parameters' => {
224
- 'required' => ['accept'],
225
- },
226
- 'url_parameters' => ['from-user-id'],
227
- 'resources' => ['user', '-', 'friends', 'invitations', '<from-user-id>'],
228
- },
229
- 'api-add-favorite-activity' => {
230
- 'auth_required' => true,
231
- 'http_method' => 'post',
232
- 'url_parameters' => ['activity-id'],
233
- 'resources' => ['user', '-', 'activities', 'favorite', '<activity-id>'],
234
- },
235
- 'api-add-favorite-food' => {
236
- 'auth_required' => true,
237
- 'http_method' => 'post',
238
- 'url_parameters' => ['food-id'],
239
- 'resources' => ['user', '-', 'foods', 'log', 'favorite', '<food-id>'],
240
- },
241
- 'api-browse-activities' => {
242
- 'auth_required' => false,
243
- 'http_method' => 'get',
244
- 'request_headers' => ['Accept-Locale'],
245
- 'resources' => ['activities'],
246
- },
247
- 'api-config-friends-leaderboard' => {
248
- 'auth_required' => true,
249
- 'http_method' => 'post',
250
- 'post_parameters' => {
251
- 'required' => ['hideMeFromLeaderboard'],
252
- },
253
- 'request_headers' => ['Accept-Language'],
254
- 'resources' => ['user', '-', 'friends', 'leaderboard'],
255
- },
256
- 'api-create-food' => {
257
- 'auth_required' => true,
258
- 'http_method' => 'post',
259
- 'post_parameters' => {
260
- 'required' => ['name', 'defaultFoodMeasurementUnitId', 'defaultServingSize', 'calories'],
261
- },
262
- 'request_headers' => ['Accept-Locale'],
263
- 'resources' => ['foods'],
264
- },
265
- 'api-create-invite' => {
266
- 'auth_required' => true,
267
- 'http_method' => 'post',
268
- 'post_parameters' => {
269
- 'exlusive' => ['invitedUserEmail', 'invitedUserId'],
270
- },
271
- 'resources' => ['user', '-', 'friends', 'invitations'],
272
- },
273
- 'api-create-subscription' => {
274
- 'auth_required' => true,
275
- 'http_method' => 'post',
276
- 'url_parameters' => ['collection-path', 'subscription-id'],
277
- 'request_headers' => ['X-Fitbit-Subscriber-Id'],
278
- 'resources' => ['user', '-', '<collection-path>', 'apiSubscriptions', '<subscription-id>', '<collection-path>']
279
- },
280
- 'api-delete-activity-log' => {
281
- 'auth_required' => true,
282
- 'http_method' => 'delete',
283
- 'url_parameters' => ['activity-log-id'],
284
- 'resources' => ['user', '-', 'activities', '<activity-log-id>'],
285
- },
286
- 'api-delete-blood-pressure-log' => {
287
- 'auth_required' => true,
288
- 'http_method' => 'delete',
289
- 'url_parameters' => ['bp-log-id'],
290
- 'resources' => ['user', '-', 'bp', '<bp-log-id>'],
291
- },
292
- 'api-delete-body-fat-log' => {
293
- 'auth_required' => true,
294
- 'http_method' => 'delete',
295
- 'url_parameters' => ['body-fat-log-id'],
296
- 'resources' => ['user', '-', 'body', 'log', 'fat', '<body-fat-log-id>'],
297
- },
298
- 'api-delete-body-weight-log' => {
299
- 'auth_required' => true,
300
- 'http_method' => 'delete',
301
- 'url_parameters' => ['body-weight-log-id'],
302
- 'resources' => ['user', '-', 'body', 'log', 'weight', '<body-weight-log-id>'],
303
- },
304
- 'api-delete-favorite-activity' => {
305
- 'auth_required' => true,
306
- 'http_method' => 'delete',
307
- 'url_parameters' => ['activity-id'],
308
- 'resources' => ['user', '-', 'activities', 'favorite', '<activity-id>'],
309
- },
310
- 'api-delete-favorite-food' => {
311
- 'auth_required' => true,
312
- 'http_method' => 'delete',
313
- 'url_parameters' => ['food-id'],
314
- 'resources' => ['user', '-', 'foods', 'log', 'favorite', '<food-id>'],
315
- },
316
- 'api-delete-food-log' => {
317
- 'auth_required' => true,
318
- 'http_method' => 'delete',
319
- 'url_parameters' => ['food-log-id'],
320
- 'resources' => ['user', '-', 'foods', 'log', '<food-log-id>'],
321
- },
322
- 'api-delete-heart-rate-log' => {
323
- 'auth_required' => true,
324
- 'http_method' => 'delete',
325
- 'url_parameters' => ['heart-log-id'],
326
- 'resources' => ['user', '-', 'heart', '<heart-log-id>'],
327
- },
328
- 'api-delete-sleep-log' => {
329
- 'auth_required' => true,
330
- 'http_method' => 'delete',
331
- 'url_parameters' => ['sleep-log-id'],
332
- 'resources' => ['user', '-', 'sleep', '<sleep-log-id>'],
333
- },
334
- 'api-delete-subscription' => {
335
- 'auth_required' => 'user-id',
336
- 'http_method' => 'delete',
337
- 'url_parameters' => ['collection-path', 'subscription-id'],
338
- 'resources' => ['user', '-', '<collection-path>', 'apiSubscriptions', '<subscription-id>', '<collection-path>']
339
- },
340
- 'api-delete-water-log' => {
341
- 'auth_required' => true,
342
- 'http_method' => 'delete',
343
- 'url_parameters' => ['water-log-id'],
344
- 'resources' => ['user', '-', 'foods', 'log', 'water', '<water-log-id>'],
345
- },
346
- 'api-devices-add-alarm' => {
347
- 'auth_required' => true,
348
- 'http_method' => 'post',
349
- 'post_parameters' => {
350
- 'required' => ['time', 'enabled', 'recurring', 'weekDays'],
351
- },
352
- 'request_headers' => ['Accept-Language'],
353
- 'url_parameters' => ['device-id'],
354
- 'resources' => ['user', '-', 'devices', 'tracker', '<device-id>', 'alarms'],
355
- },
356
- 'api-devices-delete-alarm' => {
357
- 'auth_required' => true,
358
- 'http_method' => 'delete',
359
- 'url_parameters' => ['device-id', 'alarm-id'],
360
- 'resources' => ['user', '-', 'devices', 'tracker', '<device-id>', 'alarms', '<alarm-id>'],
361
- },
362
- 'api-devices-get-alarms' => {
363
- 'auth_required' => true,
364
- 'http_method' => 'get',
365
- 'url_parameters' => ['device-id'],
366
- 'resources' => ['user', '-', 'devices', 'tracker', '<device-id>', 'alarms'],
367
- },
368
- 'api-devices-update-alarm' => {
369
- 'auth_required' => true,
370
- 'http_method' => 'post',
371
- 'post_parameters' => {
372
- 'required' => ['time', 'enabled', 'recurring', 'weekDays', 'snoozeLength', 'snoozeCount'],
373
- },
374
- 'request_headers' => ['Accept-Language'],
375
- 'url_parameters' => ['device-id', 'alarm-id'],
376
- 'resources' => ['user', '-', 'devices', 'tracker', '<device-id>', 'alarms', '<alarm-id>'],
377
- },
378
- 'api-get-activities' => {
379
- 'auth_required' => 'user-id',
380
- 'http_method' => 'get',
381
- 'request_headers' => ['Accept-Locale', 'Accept-Language'],
382
- 'url_parameters' => ['date'],
383
- 'resources' => ['user', '-', 'activities', 'date', '<date>'],
384
- },
385
- 'api-get-activity' => {
386
- 'auth_required' => false,
387
- 'http_method' => 'get',
388
- 'request_headers' => ['Accept-Locale'],
389
- 'url_parameters' => ['activity-id'],
390
- 'resources' => ['activities', '<activity-id>'],
391
- },
392
- 'api-get-activity-daily-goals' => {
393
- 'auth_required' => true,
394
- 'http_method' => 'get',
395
- 'request_headers' => ['Accept-Language'],
396
- 'resources' => ['user', '-', 'activities', 'goals', 'daily'],
397
- },
398
- 'api-get-activity-stats' => {
399
- 'auth_required' => 'user-id',
400
- 'http_method' => 'get',
401
- 'request_headers' => ['Accept-Language'],
402
- 'resources' => ['user', '-', 'activities'],
403
- },
404
- 'api-get-activity-weekly-goals' => {
405
- 'auth_required' => true,
406
- 'http_method' => 'get',
407
- 'request_headers' => ['Accept-Language'],
408
- 'resources' => ['user', '-', 'activities', 'goals', 'weekly'],
409
- },
410
- 'api-get-badges' => {
411
- 'auth_required' => 'user-id',
412
- 'http_method' => 'get',
413
- 'request_headers' => ['Accept-Locale'],
414
- 'resources' => ['user', '-', 'badges'],
415
- },
416
- 'api-get-blood-pressure' => {
417
- 'auth_required' => true,
418
- 'http_method' => 'get',
419
- 'url_parameters' => ['date'],
420
- 'resources' => ['user', '-', 'bp', 'date', '<date>'],
421
- },
422
- 'api-get-body-fat' => {
423
- 'auth_required' => true,
424
- 'http_method' => 'get',
425
- 'url_parameters' => {
426
- 'date' => ['date'],
427
- 'end-date' => ['base-date', 'end-date'],
428
- 'period' => ['base-date', 'period'],
429
- },
430
- 'request_headers' => ['Accept-Language'],
431
- 'resources' => {
432
- 'date' => ['user', '-', 'body', 'log', 'fat', 'date', '<date>'],
433
- 'end-date' => ['user', '-', 'body', 'log', 'fat', 'date', '<base-date>', '<end-date>'],
434
- 'period' => ['user', '-', 'body', 'log', 'fat', 'date', '<base-date>', '<period>'],
435
- }
436
- },
437
- 'api-get-body-fat-goal' => {
438
- 'auth_required' => true,
439
- 'http_method' => 'get',
440
- 'resources' => ['user', '-', 'body', 'log', 'fat', 'goal'],
441
- },
442
- 'api-get-body-measurements' => {
443
- 'auth_required' => 'user-id',
444
- 'http_method' => 'get',
445
- 'request_headers' => ['Accept-Language'],
446
- 'url_parameters' => ['date'],
447
- 'resources' => ['user', '-', 'body', 'date', '<date>'],
448
- },
449
- 'api-get-body-weight' => {
450
- 'auth_required' => true,
451
- 'http_method' => 'get',
452
- 'request_headers' => ['Accept-Language'],
453
- 'url_parameters' => {
454
- 'date' => ['date'],
455
- 'end-date' => ['base-date', 'end-date'],
456
- 'period' => ['base-date', 'period'],
457
- },
458
- 'resources' => {
459
- 'date' => ['user', '-', 'body', 'log', 'weight', 'date', '<date>'],
460
- 'end-date' => ['user', '-', 'body', 'log', 'weight', 'date', '<base-date>', '<end-date>'],
461
- 'period' => ['user', '-', 'body', 'log', 'weight', 'date', '<base-date>', '<period>'],
198
+ if http_method == 'get' or http_method == 'delete'
199
+ access_token.request( http_method, "http://api.fitbit.com#{request_url}", request_headers )
200
+ else
201
+ access_token.request( http_method, "http://api.fitbit.com#{request_url}", "", request_headers )
202
+ end
203
+ end
204
+
205
+ def get_request_headers params, request_headers
206
+ request_headers ? params.select { |k,v| request_headers.include? k } : nil
207
+ end
208
+
209
+ def build_url params, fitbit_api_method, http_method
210
+ api = {
211
+ :version => params['api_version'] ? params['api_version'] : @@api_version,
212
+ :url_resources => get_url_resources(params, fitbit_api_method),
213
+ :response_format => params['response-format'] ? params['response-format'].downcase : 'xml',
214
+ :post_parameters => get_post_parameters(params, fitbit_api_method, http_method),
215
+ :query => uri_encode_query(params['query']),
462
216
  }
463
- },
464
- 'api-get-body-weight-goal' => {
465
- 'auth_required' => true,
466
- 'http_method' => 'get',
467
- 'request_headers' => ['Accept-Language'],
468
- 'resources' => ['user', '-', 'body', 'log', 'weight', 'goal'],
469
- },
470
- 'api-get-device' => {
471
- 'auth_required' => 'user-id',
472
- 'http_method' => 'get',
473
- 'url_parameters' => ['device-id'],
474
- 'resources' => ['user', '-', 'devices', '<device-id>'],
475
- },
476
- 'api-get-devices' => {
477
- 'auth_required' => true,
478
- 'http_method' => 'get',
479
- 'resources' => ['user', '-', 'devices'],
480
- },
481
- 'api-get-favorite-activities' => {
482
- 'auth_required' => 'user-id',
483
- 'http_method' => 'get',
484
- 'request_headers' => ['Accept-Locale'],
485
- 'resources' => ['user', '-', 'activities', 'favorite'],
486
- },
487
- 'api-get-favorite-foods' => {
488
- 'auth_required' => true,
489
- 'http_method' => 'get',
490
- 'resources' => ['user', '-', 'foods', 'log', 'favorite'],
491
- },
492
- 'api-get-food' => {
493
- 'auth_required' => false,
494
- 'http_method' => 'get',
495
- 'request_headers' => ['Accept-Locale'],
496
- 'url_parameters' => ['food-id'],
497
- 'resources' => ['foods', '<food-id>'],
498
- },
499
- 'api-get-food-goals' => {
500
- 'auth_required' => true,
501
- 'http_method' => 'get',
502
- 'resources' => ['user', '-', 'foods', 'log', 'goal'],
503
- },
504
- 'api-get-foods' => {
505
- 'auth_required' => 'user-id',
506
- 'http_method' => 'get',
507
- 'request_headers' => ['Accept-Locale'],
508
- 'url_parameters' => ['date'],
509
- 'resources' => ['user', '-', 'foods', 'log', 'date', '<date>'],
510
- },
511
- 'api-get-food-units' => {
512
- 'auth_required' => false,
513
- 'http_method' => 'get',
514
- 'request_headers' => ['Accept-Locale'],
515
- 'resources' => ['foods', 'units'],
516
- },
517
- 'api-get-frequent-activities' => {
518
- 'auth_required' => true,
519
- 'http_method' => 'get',
520
- 'request_headers' => ['Accept-Locale', 'Accept-Language'],
521
- 'resources' => ['user', '-', 'activities', 'frequent'],
522
- },
523
- 'api-get-frequent-foods' => {
524
- 'auth_required' => true,
525
- 'http_method' => 'get',
526
- 'request_headers' => ['Accept-Locale'],
527
- 'resources' => ['user', '-', 'foods', 'log', 'frequent'],
528
- },
529
- 'api-get-friends' => {
530
- 'auth_required' => 'user-id',
531
- 'http_method' => 'get',
532
- 'request_headers' => ['Accept-Language'],
533
- 'resources' => ['user', '-', 'friends'],
534
- },
535
- 'api-get-friends-leaderboard' => {
536
- 'auth_required' => true,
537
- 'http_method' => 'get',
538
- 'request_headers' => ['Accept-Language'],
539
- 'resources' => ['user', '-', 'friends', 'leaderboard'],
540
- },
541
- 'api-get-glucose' => {
542
- 'auth_required' => true,
543
- 'http_method' => 'get',
544
- 'request_headers' => ['Accept-Language'],
545
- 'url_parameters' => ['date'],
546
- 'resources' => ['user', '-', 'glucose', 'date', '<date>'],
547
- },
548
- 'api-get-heart-rate' => {
549
- 'auth_required' => true,
550
- 'http_method' => 'get',
551
- 'url_parameters' => ['date'],
552
- 'resources' => ['user', '-', 'heart', 'date', '<date>'],
553
- },
554
- 'api-get-invites' => {
555
- 'auth_required' => true,
556
- 'http_method' => 'get',
557
- 'resources' => ['user', '-', 'friends', 'invitations'],
558
- },
559
- 'api-get-meals' => {
560
- 'auth_required' => true,
561
- 'http_method' => 'get',
562
- 'request_headers' => ['Accept-Locale'],
563
- 'resources' => ['user', '-', 'meals'],
564
- },
565
- 'api-get-recent-activities' => {
566
- 'auth_required' => true,
567
- 'http_method' => 'get',
568
- 'request_headers' => ['Accept-Locale', 'Accept-Language'],
569
- 'resources' => ['user', '-', 'activities', 'recent'],
570
- },
571
- 'api-get-recent-foods' => {
572
- 'auth_required' => true,
573
- 'http_method' => 'get',
574
- 'request_headers' => ['Accept-Locale'],
575
- 'resources' => ['user', '-', 'foods', 'log', 'recent'],
576
- },
577
- 'api-get-sleep' => {
578
- 'auth_required' => 'user-id',
579
- 'http_method' => 'get',
580
- 'url_parameters' => ['date'],
581
- 'resources' => ['user', '-', 'sleep', 'date', '<date>'],
582
- },
583
- 'api-get-time-series' => {
584
- 'auth_required' => 'user-id',
585
- 'http_method' => 'get',
586
- 'request_headers' => ['Accept-Language'],
587
- 'url_parameters' => {
588
- 'end-date' => ['base-date', 'end-date', 'resource-path'],
589
- 'period' => ['base-date', 'period', 'resource-path'],
590
- },
591
- 'resources' => {
592
- 'end-date' => ['user', '-', '<resource-path>', 'date', '<base-date>', '<end-date>'],
593
- 'period' => ['user', '-', '<resource-path>', 'date', '<base-date>', '<period>'],
594
- },
595
- },
596
- 'api-get-user-info' => {
597
- 'auth_required' => 'user-id',
598
- 'http_method' => 'get',
599
- 'request_headers' => ['Accept-Language'],
600
- 'resources' => ['user', '-', 'profile'],
601
- },
602
- 'api-get-water' => {
603
- 'auth_required' => true,
604
- 'http_method' => 'get',
605
- 'request_headers' => ['Accept-Language'],
606
- 'url_parameters' => ['date'],
607
- 'resources' => ['user', '-', 'foods', 'log', 'water', 'date', '<date>'],
608
- },
609
- 'api-log-activity' => {
610
- 'auth_required' => true,
611
- 'http_method' => 'post',
612
- 'post_parameters' => {
613
- 'exclusive' => ['activityId', 'activityName'],
614
- 'required' => ['startTime', 'durationMillis', 'date'],
615
- 'required_if' => { 'activityName' => 'manualCalories' },
616
- },
617
- 'request_headers' => ['Accept-Locale', 'Accept-Language'],
618
- 'resources' => ['user', '-', 'activities'],
619
- },
620
- 'api-log-blood-pressure' => {
621
- 'auth_required' => true,
622
- 'http_method' => 'post',
623
- 'post_parameters' => {
624
- 'required' => ['systolic', 'diastolic', 'date'],
625
- },
626
- 'resources' => ['user', '-', 'bp'],
627
- },
628
- 'api-log-body-fat' => {
629
- 'auth_required' => true,
630
- 'http_method' => 'post',
631
- 'post_parameters' => {
632
- 'required' => ['fat', 'date'],
633
- },
634
- 'resources' => ['user', '-', 'body', 'log', 'fat'],
635
- },
636
- 'api-log-body-measurements' => {
637
- 'auth_required' => true,
638
- 'http_method' => 'post',
639
- 'post_parameters' => {
640
- 'required' => ['date'],
641
- 'one_required' => ['bicep','calf','chest','fat','forearm','hips','neck','thigh','waist','weight'],
642
- },
643
- 'request_headers' => ['Accept-Language'],
644
- 'resources' => ['user', '-', 'body'],
645
- },
646
- 'api-log-body-weight' => {
647
- 'auth_required' => true,
648
- 'http_method' => 'post',
649
- 'post_parameters' => {
650
- 'required' => ['weight', 'date'],
651
- },
652
- 'request_headers' => ['Accept-Language'],
653
- 'resources' => ['user', '-', 'body', 'log', 'weight'],
654
- },
655
- 'api-log-food' => {
656
- 'auth_required' => true,
657
- 'http_method' => 'post',
658
- 'post_parameters' => {
659
- 'exclusive' => ['foodId', 'foodName'],
660
- 'required' => ['mealTypeId', 'unitId', 'amount', 'date'],
661
- },
662
- 'request_headers' => ['Accept-Locale'],
663
- 'resources' => ['user', '-', 'foods', 'log'],
664
- },
665
- 'api-log-glucose' => {
666
- 'auth_required' => true,
667
- 'http_method' => 'post',
668
- 'post_parameters' => {
669
- 'exclusive' => ['hbac1c', 'tracker'],
670
- 'required' => ['date'],
671
- 'required_if' => { 'tracker' => 'glucose' },
672
- },
673
- 'request_headers' => ['Accept-Language'],
674
- 'resources' => ['user', '-', 'glucose'],
675
- },
676
- 'api-log-heart-rate' => {
677
- 'auth_required' => true,
678
- 'http_method' => 'post',
679
- 'post_parameters' => {
680
- 'required' => ['tracker', 'heartRate', 'date'],
681
- },
682
- 'resources' => ['user', '-', 'heart'],
683
- },
684
- 'api-log-sleep' => {
685
- 'auth_required' => true,
686
- 'http_method' => 'post',
687
- 'post_parameters' => {
688
- 'required' => ['startTime', 'duration', 'date'],
689
- },
690
- 'resources' => ['user', '-', 'sleep'],
691
- },
692
- 'api-log-water' => {
693
- 'auth_required' => true,
694
- 'http_method' => 'post',
695
- 'post_parameters' => {
696
- 'required' => ['amount', 'date'],
697
- },
698
- 'request_headers' => ['Accept-Language'],
699
- 'resources' => ['user', '-', 'foods', 'log', 'water'],
700
- },
701
- 'api-search-foods' => {
702
- 'auth_required' => true,
703
- 'http_method' => 'get',
704
- 'request_headers' => ['Accept-Locale'],
705
- 'url_parameters' => ['query'],
706
- 'resources' => ['foods', 'search'],
707
- },
708
- 'api-update-activity-daily-goals' => {
709
- 'auth_required' => true,
710
- 'http_method' => 'post',
711
- 'post_parameters' => {
712
- 'one_required' => ['caloriesOut','activeMinutes','floors','distance','steps'],
713
- },
714
- 'request_headers' => ['Accept-Language'],
715
- 'resources' => ['user', '-', 'activities', 'goals', 'daily'],
716
- },
717
- 'api-update-activity-weekly-goals' => {
718
- 'auth_required' => true,
719
- 'http_method' => 'post',
720
- 'post_parameters' => {
721
- 'one_required' => ['steps','distance','floors'],
722
- },
723
- 'request_headers' => ['Accept-Language'],
724
- 'resources' => ['user', '-', 'activities', 'goals', 'weekly'],
725
- },
726
- 'api-update-fat-goal' => {
727
- 'auth_required' => true,
728
- 'http_method' => 'post',
729
- 'post_parameters' => {
730
- 'required' => ['fat'],
731
- },
732
- 'resources' => ['user', '-', 'body', 'log', 'fat', 'goal'],
733
- },
734
- 'api-update-food-goals' => {
735
- 'auth_required' => true,
736
- 'http_method' => 'post',
737
- 'post_parameters' => {
738
- 'exclusive' => ['calories', 'intensity'],
739
- },
740
- 'request_headers' => ['Accept-Locale', 'Accept-Language'],
741
- 'resources' => ['user', '-', 'foods', 'log', 'goal'],
742
- },
743
- 'api-update-user-info' => {
744
- 'auth_required' => true,
745
- 'http_method' => 'post',
746
- 'post_parameters' => {
747
- 'one_required' => [
748
- 'gender','birthday','height','nickname','aboutMe','fullname','country','state','city',
749
- 'strideLengthWalking','strideLengthRunning','weightUnit','heightUnit','waterUnit','glucoseUnit',
750
- 'timezone','foodsLocale','locale','localeLang','localeCountry'
751
- ],
752
- },
753
- 'request_headers' => ['Accept-Language'],
754
- 'resources' => ['user', '-', 'profile'],
755
- },
756
- 'api-update-weight-goal' => {
757
- 'auth_required' => true,
758
- 'http_method' => 'post',
759
- 'post_parameters' => {
760
- 'required' => ['startDate', 'startWeight'],
761
- },
762
- 'resources' => ['user', '-', 'body', 'log', 'weight', 'goal'],
763
- },
764
- }
217
+ "/#{api[:version]}/#{api[:url_resources]}.#{api[:response_format]}#{api[:query]}#{api[:post_parameters]}"
218
+ end
219
+
220
+ def get_url_resources params, fitbit_api_method
221
+ url_parameters = get_url_parameters(fitbit_api_method[:url_parameters], params.keys)
222
+ if has_url_variables? url_parameters or params['user-id']
223
+ url_parameters = insert_dynamic_url_parameters(params, url_parameters, fitbit_api_method[:auth_required])
224
+ end
225
+ url_parameters.join("/")
226
+ end
227
+
228
+ def get_post_parameters params, fitbit, http_method
229
+ if http_method == 'post' and !is_subscription? params['api-method']
230
+ post_parameters = params.select { |k,v| fitbit[:post_parameters].values.flatten.include? k }
231
+ "?#{OAuth::Helper.normalize(post_parameters)}"
232
+ else
233
+ nil
234
+ end
235
+ end
236
+
237
+ def uri_encode_query query
238
+ query ? "?#{OAuth::Helper.normalize({ 'query' => query })}" : ""
239
+ end
240
+
241
+ def is_subscription? api_method
242
+ api_method == 'api-create-subscription' or api_method == 'api-delete-subscription'
243
+ end
765
244
 
245
+ @@api_version = 1
246
+
247
+ @@fitbit_methods = {
248
+ 'api-accept-invite' => {
249
+ :auth_required => true,
250
+ :http_method => 'post',
251
+ :post_parameters => {
252
+ 'required' => ['accept'],
253
+ },
254
+ :url_parameters => ['user', '-', 'friends', 'invitations', '<from-user-id>'],
255
+ },
256
+ 'api-add-favorite-activity' => {
257
+ :auth_required => true,
258
+ :http_method => 'post',
259
+ :url_parameters => ['user', '-', 'activities', 'favorite', '<activity-id>'],
260
+ },
261
+ 'api-add-favorite-food' => {
262
+ :auth_required => true,
263
+ :http_method => 'post',
264
+ :url_parameters => ['user', '-', 'foods', 'log', 'favorite', '<food-id>'],
265
+ },
266
+ 'api-browse-activities' => {
267
+ :auth_required => false,
268
+ :http_method => 'get',
269
+ :request_headers => ['Accept-Locale'],
270
+ :url_parameters => ['activities'],
271
+ },
272
+ 'api-config-friends-leaderboard' => {
273
+ :auth_required => true,
274
+ :http_method => 'post',
275
+ :post_parameters => {
276
+ 'required' => ['hideMeFromLeaderboard'],
277
+ },
278
+ :request_headers => ['Accept-Language'],
279
+ :url_parameters => ['user', '-', 'friends', 'leaderboard'],
280
+ },
281
+ 'api-create-food' => {
282
+ :auth_required => true,
283
+ :http_method => 'post',
284
+ :post_parameters => {
285
+ 'required' => ['name', 'defaultFoodMeasurementUnitId', 'defaultServingSize', 'calories'],
286
+ 'optional' => ['formType', 'description'],
287
+ },
288
+ :request_headers => ['Accept-Locale'],
289
+ :url_parameters => ['foods'],
290
+ },
291
+ 'api-create-invite' => {
292
+ :auth_required => true,
293
+ :http_method => 'post',
294
+ :post_parameters => {
295
+ 'exclusive' => ['invitedUserEmail', 'invitedUserId'],
296
+ },
297
+ :url_parameters => ['user', '-', 'friends', 'invitations'],
298
+ },
299
+ 'api-create-subscription' => {
300
+ :auth_required => true,
301
+ :http_method => 'post',
302
+ :request_headers => ['X-Fitbit-Subscriber-Id'],
303
+ :url_parameters => ['user', '-', '<collection-path>', 'apiSubscriptions', '<subscription-id>']
304
+ },
305
+ 'api-delete-activity-log' => {
306
+ :auth_required => true,
307
+ :http_method => 'delete',
308
+ :url_parameters => ['user', '-', 'activities', '<activity-log-id>'],
309
+ },
310
+ 'api-delete-blood-pressure-log' => {
311
+ :auth_required => true,
312
+ :http_method => 'delete',
313
+ :url_parameters => ['user', '-', 'bp', '<bp-log-id>'],
314
+ },
315
+ 'api-delete-body-fat-log' => {
316
+ :auth_required => true,
317
+ :http_method => 'delete',
318
+ :url_parameters => ['user', '-', 'body', 'log', 'fat', '<body-fat-log-id>'],
319
+ },
320
+ 'api-delete-body-weight-log' => {
321
+ :auth_required => true,
322
+ :http_method => 'delete',
323
+ :url_parameters => ['user', '-', 'body', 'log', 'weight', '<body-weight-log-id>'],
324
+ },
325
+ 'api-delete-favorite-activity' => {
326
+ :auth_required => true,
327
+ :http_method => 'delete',
328
+ :url_parameters => ['user', '-', 'activities', 'favorite', '<activity-id>'],
329
+ },
330
+ 'api-delete-favorite-food' => {
331
+ :auth_required => true,
332
+ :http_method => 'delete',
333
+ :url_parameters => ['user', '-', 'foods', 'log', 'favorite', '<food-id>'],
334
+ },
335
+ 'api-delete-food-log' => {
336
+ :auth_required => true,
337
+ :http_method => 'delete',
338
+ :url_parameters => ['user', '-', 'foods', 'log', '<food-log-id>'],
339
+ },
340
+ 'api-delete-heart-rate-log' => {
341
+ :auth_required => true,
342
+ :http_method => 'delete',
343
+ :url_parameters => ['user', '-', 'heart', '<heart-log-id>'],
344
+ },
345
+ 'api-delete-sleep-log' => {
346
+ :auth_required => true,
347
+ :http_method => 'delete',
348
+ :url_parameters => ['user', '-', 'sleep', '<sleep-log-id>'],
349
+ },
350
+ 'api-delete-subscription' => {
351
+ :auth_required => 'user-id',
352
+ :http_method => 'delete',
353
+ :url_parameters => ['user', '-', '<collection-path>', 'apiSubscriptions', '<subscription-id>']
354
+ },
355
+ 'api-delete-water-log' => {
356
+ :auth_required => true,
357
+ :http_method => 'delete',
358
+ :url_parameters => ['user', '-', 'foods', 'log', 'water', '<water-log-id>'],
359
+ },
360
+ 'api-devices-add-alarm' => {
361
+ :auth_required => true,
362
+ :http_method => 'post',
363
+ :post_parameters => {
364
+ 'required' => ['time', 'enabled', 'recurring', 'weekDays'],
365
+ 'optional' => ['label', 'snoozeLength', 'snoozeCount', 'vibe'],
366
+ },
367
+ :request_headers => ['Accept-Language'],
368
+ :url_parameters => ['user', '-', 'devices', 'tracker', '<device-id>', 'alarms'],
369
+ },
370
+ 'api-devices-delete-alarm' => {
371
+ :auth_required => true,
372
+ :http_method => 'delete',
373
+ :url_parameters => ['user', '-', 'devices', 'tracker', '<device-id>', 'alarms', '<alarm-id>'],
374
+ },
375
+ 'api-devices-get-alarms' => {
376
+ :auth_required => true,
377
+ :http_method => 'get',
378
+ :url_parameters => ['user', '-', 'devices', 'tracker', '<device-id>', 'alarms'],
379
+ },
380
+ 'api-devices-update-alarm' => {
381
+ :auth_required => true,
382
+ :http_method => 'post',
383
+ :post_parameters => {
384
+ 'required' => ['time', 'enabled', 'recurring', 'weekDays', 'snoozeLength', 'snoozeCount'],
385
+ 'optional' => ['label', 'vibe'],
386
+ },
387
+ :request_headers => ['Accept-Language'],
388
+ :url_parameters => ['user', '-', 'devices', 'tracker', '<device-id>', 'alarms', '<alarm-id>'],
389
+ },
390
+ 'api-get-activities' => {
391
+ :auth_required => 'user-id',
392
+ :http_method => 'get',
393
+ :request_headers => ['Accept-Locale', 'Accept-Language'],
394
+ :url_parameters => ['user', '-', 'activities', 'date', '<date>'],
395
+ },
396
+ 'api-get-activity' => {
397
+ :auth_required => false,
398
+ :http_method => 'get',
399
+ :request_headers => ['Accept-Locale'],
400
+ :url_parameters => ['activities', '<activity-id>'],
401
+ },
402
+ 'api-get-activity-daily-goals' => {
403
+ :auth_required => true,
404
+ :http_method => 'get',
405
+ :request_headers => ['Accept-Language'],
406
+ :url_parameters => ['user', '-', 'activities', 'goals', 'daily'],
407
+ },
408
+ 'api-get-activity-stats' => {
409
+ :auth_required => 'user-id',
410
+ :http_method => 'get',
411
+ :request_headers => ['Accept-Language'],
412
+ :url_parameters => ['user', '-', 'activities'],
413
+ },
414
+ 'api-get-activity-weekly-goals' => {
415
+ :auth_required => true,
416
+ :http_method => 'get',
417
+ :request_headers => ['Accept-Language'],
418
+ :url_parameters => ['user', '-', 'activities', 'goals', 'weekly'],
419
+ },
420
+ 'api-get-badges' => {
421
+ :auth_required => 'user-id',
422
+ :http_method => 'get',
423
+ :request_headers => ['Accept-Locale'],
424
+ :url_parameters => ['user', '-', 'badges'],
425
+ },
426
+ 'api-get-blood-pressure' => {
427
+ :auth_required => true,
428
+ :http_method => 'get',
429
+ :url_parameters => ['user', '-', 'bp', 'date', '<date>'],
430
+ },
431
+ 'api-get-body-fat' => {
432
+ :auth_required => true,
433
+ :http_method => 'get',
434
+ :request_headers => ['Accept-Language'],
435
+ :url_parameters => {
436
+ 'date' => ['user', '-', 'body', 'log', 'fat', 'date', '<date>'],
437
+ 'end-date' => ['user', '-', 'body', 'log', 'fat', 'date', '<base-date>', '<end-date>'],
438
+ 'period' => ['user', '-', 'body', 'log', 'fat', 'date', '<base-date>', '<period>'],
439
+ }
440
+ },
441
+ 'api-get-body-fat-goal' => {
442
+ :auth_required => true,
443
+ :http_method => 'get',
444
+ :url_parameters => ['user', '-', 'body', 'log', 'fat', 'goal'],
445
+ },
446
+ 'api-get-body-measurements' => {
447
+ :auth_required => 'user-id',
448
+ :http_method => 'get',
449
+ :request_headers => ['Accept-Language'],
450
+ :url_parameters => ['user', '-', 'body', 'date', '<date>'],
451
+ },
452
+ 'api-get-body-weight' => {
453
+ :auth_required => true,
454
+ :http_method => 'get',
455
+ :request_headers => ['Accept-Language'],
456
+ :url_parameters => {
457
+ 'date' => ['user', '-', 'body', 'log', 'weight', 'date', '<date>'],
458
+ 'end-date' => ['user', '-', 'body', 'log', 'weight', 'date', '<base-date>', '<end-date>'],
459
+ 'period' => ['user', '-', 'body', 'log', 'weight', 'date', '<base-date>', '<period>'],
460
+ }
461
+ },
462
+ 'api-get-body-weight-goal' => {
463
+ :auth_required => true,
464
+ :http_method => 'get',
465
+ :request_headers => ['Accept-Language'],
466
+ :url_parameters => ['user', '-', 'body', 'log', 'weight', 'goal'],
467
+ },
468
+ 'api-get-device' => {
469
+ :auth_required => 'user-id',
470
+ :http_method => 'get',
471
+ :url_parameters => ['user', '-', 'devices', '<device-id>'],
472
+ },
473
+ 'api-get-devices' => {
474
+ :auth_required => true,
475
+ :http_method => 'get',
476
+ :url_parameters => ['user', '-', 'devices'],
477
+ },
478
+ 'api-get-favorite-activities' => {
479
+ :auth_required => 'user-id',
480
+ :http_method => 'get',
481
+ :request_headers => ['Accept-Locale'],
482
+ :url_parameters => ['user', '-', 'activities', 'favorite'],
483
+ },
484
+ 'api-get-favorite-foods' => {
485
+ :auth_required => true,
486
+ :http_method => 'get',
487
+ :url_parameters => ['user', '-', 'foods', 'log', 'favorite'],
488
+ },
489
+ 'api-get-food' => {
490
+ :auth_required => false,
491
+ :http_method => 'get',
492
+ :request_headers => ['Accept-Locale'],
493
+ :url_parameters => ['foods', '<food-id>'],
494
+ },
495
+ 'api-get-food-goals' => {
496
+ :auth_required => true,
497
+ :http_method => 'get',
498
+ :url_parameters => ['user', '-', 'foods', 'log', 'goal'],
499
+ },
500
+ 'api-get-foods' => {
501
+ :auth_required => 'user-id',
502
+ :http_method => 'get',
503
+ :request_headers => ['Accept-Locale'],
504
+ :url_parameters => ['user', '-', 'foods', 'log', 'date', '<date>'],
505
+ },
506
+ 'api-get-food-units' => {
507
+ :auth_required => false,
508
+ :http_method => 'get',
509
+ :request_headers => ['Accept-Locale'],
510
+ :url_parameters => ['foods', 'units'],
511
+ },
512
+ 'api-get-frequent-activities' => {
513
+ :auth_required => true,
514
+ :http_method => 'get',
515
+ :request_headers => ['Accept-Locale', 'Accept-Language'],
516
+ :url_parameters => ['user', '-', 'activities', 'frequent'],
517
+ },
518
+ 'api-get-frequent-foods' => {
519
+ :auth_required => true,
520
+ :http_method => 'get',
521
+ :request_headers => ['Accept-Locale'],
522
+ :url_parameters => ['user', '-', 'foods', 'log', 'frequent'],
523
+ },
524
+ 'api-get-friends' => {
525
+ :auth_required => 'user-id',
526
+ :http_method => 'get',
527
+ :request_headers => ['Accept-Language'],
528
+ :url_parameters => ['user', '-', 'friends'],
529
+ },
530
+ 'api-get-friends-leaderboard' => {
531
+ :auth_required => true,
532
+ :http_method => 'get',
533
+ :request_headers => ['Accept-Language'],
534
+ :url_parameters => ['user', '-', 'friends', 'leaderboard'],
535
+ },
536
+ 'api-get-glucose' => {
537
+ :auth_required => true,
538
+ :http_method => 'get',
539
+ :request_headers => ['Accept-Language'],
540
+ :url_parameters => ['user', '-', 'glucose', 'date', '<date>'],
541
+ },
542
+ 'api-get-heart-rate' => {
543
+ :auth_required => true,
544
+ :http_method => 'get',
545
+ :url_parameters => ['user', '-', 'heart', 'date', '<date>'],
546
+ },
547
+ 'api-get-intraday-time-series' => {
548
+ :auth_required => true,
549
+ :http_method => 'get',
550
+ :request_headers => ['Accept-Language'],
551
+ :url_parameters => {
552
+ 'end-date' => ['user', '-', '<resource-path>', 'date', '<base-date>', '<end-date>'],
553
+ 'period' => ['user', '-', '<resource-path>', 'date', '<base-date>', '<period>'],
554
+ 'optional' => {
555
+ 'detail-level' => [ '<detail-level>' ],
556
+ 'start-time' => ['time', '<start-time>', '<end-time>']
557
+ },
558
+ },
559
+ },
560
+ 'api-get-invites' => {
561
+ :auth_required => true,
562
+ :http_method => 'get',
563
+ :url_parameters => ['user', '-', 'friends', 'invitations'],
564
+ },
565
+ 'api-get-meals' => {
566
+ :auth_required => true,
567
+ :http_method => 'get',
568
+ :request_headers => ['Accept-Locale'],
569
+ :url_parameters => ['user', '-', 'meals'],
570
+ },
571
+ 'api-get-recent-activities' => {
572
+ :auth_required => true,
573
+ :http_method => 'get',
574
+ :request_headers => ['Accept-Locale', 'Accept-Language'],
575
+ :url_parameters => ['user', '-', 'activities', 'recent'],
576
+ },
577
+ 'api-get-recent-foods' => {
578
+ :auth_required => true,
579
+ :http_method => 'get',
580
+ :request_headers => ['Accept-Locale'],
581
+ :url_parameters => ['user', '-', 'foods', 'log', 'recent'],
582
+ },
583
+ 'api-get-sleep' => {
584
+ :auth_required => 'user-id',
585
+ :http_method => 'get',
586
+ :url_parameters => ['user', '-', 'sleep', 'date', '<date>'],
587
+ },
588
+ 'api-get-time-series' => {
589
+ :auth_required => 'user-id',
590
+ :http_method => 'get',
591
+ :request_headers => ['Accept-Language'],
592
+ :url_parameters => {
593
+ 'end-date' => ['user', '-', '<resource-path>', 'date', '<base-date>', '<end-date>'],
594
+ 'period' => ['user', '-', '<resource-path>', 'date', '<base-date>', '<period>'],
595
+ },
596
+ },
597
+ 'api-get-user-info' => {
598
+ :auth_required => 'user-id',
599
+ :http_method => 'get',
600
+ :request_headers => ['Accept-Language'],
601
+ :url_parameters => ['user', '-', 'profile'],
602
+ },
603
+ 'api-get-water' => {
604
+ :auth_required => true,
605
+ :http_method => 'get',
606
+ :request_headers => ['Accept-Language'],
607
+ :url_parameters => ['user', '-', 'foods', 'log', 'water', 'date', '<date>'],
608
+ },
609
+ 'api-log-activity' => {
610
+ :auth_required => true,
611
+ :http_method => 'post',
612
+ :post_parameters => {
613
+ 'exclusive' => ['activityId', 'activityName'],
614
+ 'required' => ['startTime', 'durationMillis', 'date'],
615
+ 'required_if' => { 'activityName' => 'manualCalories' },
616
+ 'optional' => ['distanceUnit'],
617
+ },
618
+ :request_headers => ['Accept-Locale', 'Accept-Language'],
619
+ :url_parameters => ['user', '-', 'activities'],
620
+ },
621
+ 'api-log-blood-pressure' => {
622
+ :auth_required => true,
623
+ :http_method => 'post',
624
+ :post_parameters => {
625
+ 'required' => ['systolic', 'diastolic', 'date'],
626
+ 'optional' => ['time'],
627
+ },
628
+ :url_parameters => ['user', '-', 'bp'],
629
+ },
630
+ 'api-log-body-fat' => {
631
+ :auth_required => true,
632
+ :http_method => 'post',
633
+ :post_parameters => {
634
+ 'required' => ['fat', 'date'],
635
+ 'optional' => ['time'],
636
+ },
637
+ :url_parameters => ['user', '-', 'body', 'log', 'fat'],
638
+ },
639
+ 'api-log-body-measurements' => {
640
+ :auth_required => true,
641
+ :http_method => 'post',
642
+ :post_parameters => {
643
+ 'required' => ['date'],
644
+ 'one_required' => ['bicep','calf','chest','fat','forearm','hips','neck','thigh','waist','weight'],
645
+ },
646
+ :request_headers => ['Accept-Language'],
647
+ :url_parameters => ['user', '-', 'body'],
648
+ },
649
+ 'api-log-body-weight' => {
650
+ :auth_required => true,
651
+ :http_method => 'post',
652
+ :post_parameters => {
653
+ 'required' => ['weight', 'date'],
654
+ 'optional' => ['time'],
655
+ },
656
+ :request_headers => ['Accept-Language'],
657
+ :url_parameters => ['user', '-', 'body', 'log', 'weight'],
658
+ },
659
+ 'api-log-food' => {
660
+ :auth_required => true,
661
+ :http_method => 'post',
662
+ :post_parameters => {
663
+ 'exclusive' => ['foodId', 'foodName'],
664
+ 'required' => ['mealTypeId', 'unitId', 'amount', 'date'],
665
+ 'optional' => ['brandName', 'calories', 'favorite'],
666
+ },
667
+ :request_headers => ['Accept-Locale'],
668
+ :url_parameters => ['user', '-', 'foods', 'log'],
669
+ },
670
+ 'api-log-glucose' => {
671
+ :auth_required => true,
672
+ :http_method => 'post',
673
+ :post_parameters => {
674
+ 'exclusive' => ['hbac1c', 'tracker'],
675
+ 'required' => ['date'],
676
+ 'required_if' => { 'tracker' => 'glucose' },
677
+ 'optional' => ['time'],
678
+ },
679
+ :request_headers => ['Accept-Language'],
680
+ :url_parameters => ['user', '-', 'glucose'],
681
+ },
682
+ 'api-log-heart-rate' => {
683
+ :auth_required => true,
684
+ :http_method => 'post',
685
+ :post_parameters => {
686
+ 'required' => ['tracker', 'heartRate', 'date'],
687
+ 'optional' => ['time'],
688
+ },
689
+ :url_parameters => ['user', '-', 'heart'],
690
+ },
691
+ 'api-log-sleep' => {
692
+ :auth_required => true,
693
+ :http_method => 'post',
694
+ :post_parameters => {
695
+ 'required' => ['startTime', 'duration', 'date'],
696
+ },
697
+ :url_parameters => ['user', '-', 'sleep'],
698
+ },
699
+ 'api-log-water' => {
700
+ :auth_required => true,
701
+ :http_method => 'post',
702
+ :post_parameters => {
703
+ 'required' => ['amount', 'date'],
704
+ 'optional' => ['unit'],
705
+ },
706
+ :request_headers => ['Accept-Language'],
707
+ :url_parameters => ['user', '-', 'foods', 'log', 'water'],
708
+ },
709
+ 'api-search-foods' => {
710
+ :auth_required => true,
711
+ :http_method => 'get',
712
+ :request_headers => ['Accept-Locale'],
713
+ :url_parameters => ['foods', 'search'],
714
+ },
715
+ 'api-update-activity-daily-goals' => {
716
+ :auth_required => true,
717
+ :http_method => 'post',
718
+ :post_parameters => {
719
+ 'one_required' => ['caloriesOut','activeMinutes','floors','distance','steps'],
720
+ },
721
+ :request_headers => ['Accept-Language'],
722
+ :url_parameters => ['user', '-', 'activities', 'goals', 'daily'],
723
+ },
724
+ 'api-update-activity-weekly-goals' => {
725
+ :auth_required => true,
726
+ :http_method => 'post',
727
+ :post_parameters => {
728
+ 'one_required' => ['steps','distance','floors'],
729
+ },
730
+ :request_headers => ['Accept-Language'],
731
+ :url_parameters => ['user', '-', 'activities', 'goals', 'weekly'],
732
+ },
733
+ 'api-update-fat-goal' => {
734
+ :auth_required => true,
735
+ :http_method => 'post',
736
+ :post_parameters => {
737
+ 'required' => ['fat'],
738
+ },
739
+ :url_parameters => ['user', '-', 'body', 'log', 'fat', 'goal'],
740
+ },
741
+ 'api-update-food-goals' => {
742
+ :auth_required => true,
743
+ :http_method => 'post',
744
+ :post_parameters => {
745
+ 'exclusive' => ['calories', 'intensity'],
746
+ 'optional' => ['personalized'],
747
+ },
748
+ :request_headers => ['Accept-Locale', 'Accept-Language'],
749
+ :url_parameters => ['user', '-', 'foods', 'log', 'goal'],
750
+ },
751
+ 'api-update-user-info' => {
752
+ :auth_required => true,
753
+ :http_method => 'post',
754
+ :post_parameters => {
755
+ 'one_required' => [
756
+ 'gender','birthday','height','nickname','aboutMe','fullname','country','state','city',
757
+ 'strideLengthWalking','strideLengthRunning','weightUnit','heightUnit','waterUnit','glucoseUnit',
758
+ 'timezone','foodsLocale','locale','localeLang','localeCountry'
759
+ ],
760
+ },
761
+ :request_headers => ['Accept-Language'],
762
+ :url_parameters => ['user', '-', 'profile'],
763
+ },
764
+ 'api-update-weight-goal' => {
765
+ :auth_required => true,
766
+ :http_method => 'post',
767
+ :post_parameters => {
768
+ 'required' => ['startDate', 'startWeight'],
769
+ 'optional' => ['weight'],
770
+ },
771
+ :url_parameters => ['user', '-', 'body', 'log', 'weight', 'goal'],
772
+ },
773
+ }
774
+ end
766
775
  end
767
776
  end