fitbit-omni-api 0.0.1

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