fitbit-omni-api 0.0.1

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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 3bb58a076555dbaeef38610a7e7cc2035542d1f5
4
+ data.tar.gz: 1295e14ef5ce64bb429779109eac458c318f524d
5
+ SHA512:
6
+ metadata.gz: 0dbc41a2b02902432761fd992bda76de1cb5885cdf4d231bb851c235ee57d7be5a12ad21c4c33504fc79c7ba390f8ff8808643499b523549988550d945b07127
7
+ data.tar.gz: 8fc3c5c81be51e371f440f75c3fd033412c9dcf2dce1da073704123313c75946d804bbde0fe2df524061c01fe7b83029db331782a819b2079479355b2c8bec6a
data/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 TK Gospodinov, (c) Scott McGrath
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,93 @@
1
+ # Fitbit OmniAuth Strategy
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).
4
+
5
+ ## Usage
6
+
7
+ Add the strategy to your `Gemfile`:
8
+
9
+ ```ruby
10
+ gem 'fitbit-omni-api'
11
+ ```
12
+
13
+ Then integrate the strategy into your middleware:
14
+
15
+ ```ruby
16
+ use OmniAuth::Builder do
17
+ provider :fitbit, 'consumer_key', 'consumer_secret'
18
+ end
19
+ ```
20
+
21
+ In Rails, create a new file under config/initializers called omniauth.rb to plug the strategy into your middleware stack.
22
+
23
+ ```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(
47
+ 'consumer_key',
48
+ 'consumer_secret',
49
+ params,
50
+ 'auth_token',
51
+ 'auth_secret'
52
+ )
53
+ ```
54
+
55
+ OmniAuth Fitbit supports the Fitbit Resource Access API and the Fitbit Subscriptions API.
56
+
57
+ 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:
60
+
61
+ ```ruby
62
+ 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(
70
+ 'consumer_key',
71
+ 'consumer_secret',
72
+ params,
73
+ 'auth_token',
74
+ 'auth_secret'
75
+ )
76
+ @response = request.body
77
+ end
78
+ ```
79
+
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).
84
+
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'.
90
+
91
+ ## Copyright
92
+
93
+ Copyright (c) 2012 TK Gospodinov, (c) Scott McGrath 2013. See [LICENSE](https://github.com/tkgospodinov/omniauth-fitbit/blob/master/LICENSE.md) for details.
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
@@ -0,0 +1,767 @@
1
+ module Fitbit
2
+ class Api < OmniAuth::Strategies::Fitbit
3
+
4
+ def api_call consumer_key, consumer_secret, params, auth_token="", auth_secret=""
5
+ api_params = get_lowercase_api_method(params)
6
+ api_method = api_params['api-method']
7
+ fitbit = @@fitbit_methods[api_method]
8
+ api_error = get_api_errors(api_params.keys, fitbit, auth_token, auth_secret)
9
+ raise "#{api_method} " + api_error if api_error
10
+ access_token = build_request(consumer_key, consumer_secret, auth_token, auth_secret)
11
+ send_api_request(api_params, fitbit, access_token)
12
+ end
13
+
14
+ def get_fitbit_methods
15
+ @@fitbit_methods
16
+ end
17
+
18
+ private
19
+
20
+ def get_lowercase_api_method params
21
+ params['api-method'] ||= nil
22
+ params['api-method'].downcase!
23
+ params
24
+ end
25
+
26
+ def get_api_errors params_keys, fitbit, auth_token="", auth_secret=""
27
+ if fitbit
28
+ no_auth_tokens = true if (auth_token == "" or auth_secret == "")
29
+ get_error_message(params_keys, fitbit, no_auth_tokens)
30
+ else
31
+ "is not a valid Fitbit API method."
32
+ end
33
+ end
34
+
35
+ def get_error_message params_keys, fitbit, no_auth_tokens
36
+ if missing_url_parameters? fitbit['url_parameters'], params_keys
37
+ url_parameters_error(fitbit['url_parameters'], params_keys)
38
+ elsif fitbit['post_parameters'] and missing_post_parameters? fitbit['post_parameters'], params_keys
39
+ post_parameters_error(fitbit['post_parameters'], params_keys)
40
+ elsif fitbit['auth_required'] and no_auth_tokens
41
+ auth_error(fitbit['auth_required'], params_keys.include?('user-id'))
42
+ end
43
+ end
44
+
45
+ def missing_url_parameters? required, supplied
46
+ url_parameters = get_url_parameters(required, supplied)
47
+ required and supplied & url_parameters != url_parameters
48
+ end
49
+
50
+ def get_url_parameters required, supplied
51
+ if required.is_a? Hash
52
+ get_dynamic_url_parameters(required, supplied)
53
+ else
54
+ required
55
+ end
56
+ end
57
+
58
+ def get_dynamic_url_parameters required, supplied
59
+ required.keys.each do |x|
60
+ return required[x] if supplied.include? x
61
+ end
62
+ end
63
+
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
80
+ end
81
+ end
82
+ error
83
+ end
84
+
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}."
90
+ end
91
+ end
92
+
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]} "
97
+ end
98
+ error
99
+ end
100
+
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
122
+ end
123
+ end
124
+ end
125
+
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."
131
+ end
132
+ end
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
138
+
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']
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 )
148
+ end
149
+ end
150
+
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'])
157
+
158
+ "/#{api_version}/#{api_url_resources}.#{api_format}#{api_query}#{api_post_parameters}"
159
+ end
160
+
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]
167
+ end
168
+ post_parameters = params.select { |k,v| !ignore.include? k }
169
+
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
177
+
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
186
+
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
202
+ end
203
+ end
204
+
205
+ def is_subscription? api_method
206
+ api_method == 'api-create-subscription' or api_method == 'api-delete-subscription'
207
+ end
208
+
209
+ def get_response_format api_format
210
+ api_format ? api_format.downcase : 'xml'
211
+ end
212
+
213
+ def uri_encode_query query
214
+ query ? "?" + OAuth::Helper.normalize({ 'query' => query }) : ""
215
+ end
216
+
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>'],
462
+ }
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
+ }
765
+
766
+ end
767
+ end
@@ -0,0 +1,3 @@
1
+ require 'fitbit-omni-api/version'
2
+ require 'omniauth/strategies/fitbit'
3
+ require 'api/fitbit_api'
@@ -0,0 +1,7 @@
1
+ module OmniAuth
2
+ module Fitbit
3
+ module Api
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,49 @@
1
+ require 'omniauth'
2
+ require 'omniauth/strategies/oauth'
3
+
4
+ module OmniAuth
5
+ module Strategies
6
+ class Fitbit < OmniAuth::Strategies::OAuth
7
+
8
+ option :name, "fitbit"
9
+
10
+ option :client_options, {
11
+ :site => 'http://api.fitbit.com',
12
+ :request_token_path => '/oauth/request_token',
13
+ :access_token_path => '/oauth/access_token',
14
+ :authorize_path => '/oauth/authorize'
15
+ }
16
+
17
+ uid do
18
+ access_token.params['encoded_user_id']
19
+ end
20
+
21
+ info do
22
+ {
23
+ :full_name => raw_info['user']['fullName'],
24
+ :display_name => raw_info['user']['displayName'],
25
+ :nickname => raw_info['user']['nickname'],
26
+ :gender => raw_info['user']['gender'],
27
+ :about_me => raw_info['user']['aboutMe'],
28
+ :city => raw_info['user']['city'],
29
+ :state => raw_info['user']['state'],
30
+ :country => raw_info['user']['country'],
31
+ :dob => !raw_info['user']['dateOfBirth'].empty? ? Date.strptime(raw_info['user']['dateOfBirth'], '%Y-%m-%d'):nil,
32
+ :member_since => Date.strptime(raw_info['user']['memberSince'], '%Y-%m-%d'),
33
+ :locale => raw_info['user']['locale'],
34
+ :timezone => raw_info['user']['timezone']
35
+ }
36
+ end
37
+
38
+ extra do
39
+ {
40
+ :raw_info => raw_info
41
+ }
42
+ end
43
+
44
+ def raw_info
45
+ @raw_info ||= MultiJson.load(access_token.get('http://api.fitbit.com/1/user/-/profile.json').body)
46
+ end
47
+ end
48
+ end
49
+ end
metadata ADDED
@@ -0,0 +1,81 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: fitbit-omni-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - TK Gospodinov
8
+ - Scott McGrath
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-09-28 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: omniauth-oauth
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ~>
19
+ - !ruby/object:Gem::Version
20
+ version: '1.0'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ~>
26
+ - !ruby/object:Gem::Version
27
+ version: '1.0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: multi_xml
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - '>='
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ description: OmniAuth strategy for Fitbit, plus api wrapper
43
+ email:
44
+ - tk@gospodinov.net
45
+ - Scott McGrat
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files: []
49
+ files:
50
+ - lib/api/fitbit_api.rb
51
+ - lib/fitbit-omni-api/version.rb
52
+ - lib/fitbit-omni-api.rb
53
+ - lib/omniauth/strategies/fitbit.rb
54
+ - LICENSE.md
55
+ - Rakefile
56
+ - README.md
57
+ homepage: http://github.com/scrawlon/fitbit-omni-api
58
+ licenses:
59
+ - MIT
60
+ metadata: {}
61
+ post_install_message:
62
+ rdoc_options: []
63
+ require_paths:
64
+ - lib
65
+ required_ruby_version: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - '>='
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ required_rubygems_version: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - '>='
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ requirements: []
76
+ rubyforge_project:
77
+ rubygems_version: 2.0.3
78
+ signing_key:
79
+ specification_version: 4
80
+ summary: OmniAuth strategy for Fitbit, plus api wrapper
81
+ test_files: []