todaysplan 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +43 -0
- data/Rakefile +20 -0
- data/lib/todays_plan.rb +47 -0
- data/lib/todays_plan/activity.rb +67 -0
- data/lib/todays_plan/athlete.rb +43 -0
- data/lib/todays_plan/client.rb +27 -0
- data/lib/todays_plan/connector.rb +39 -0
- data/lib/todays_plan/version.rb +3 -0
- data/spec/fixtures/activities/incomplete.json +20 -0
- data/spec/fixtures/activities/incomplete_response.json +43 -0
- data/spec/fixtures/athletes/response.json +25 -0
- data/spec/spec_helper.rb +31 -0
- data/spec/todays_plan/activity_spec.rb +23 -0
- data/spec/todays_plan/athlete_spec.rb +42 -0
- data/spec/todays_plan/client_spec.rb +28 -0
- data/spec/todays_plan/connector_spec.rb +41 -0
- data/spec/todays_plan_spec.rb +27 -0
- metadata +141 -0
data/README.md
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
[](https://travis-ci.org/9kSoftware/todaysplan-ruby)
|
2
|
+
# todaysplan-ruby
|
3
|
+
A Ruby Library for TodaysPlan
|
4
|
+
|
5
|
+
This library is currently limited to only a few API calls needed. Feel free to do
|
6
|
+
Pull Request to include additional calls.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add the gem to your app
|
11
|
+
|
12
|
+
```ruby
|
13
|
+
gem 'todaysplan-ruby'
|
14
|
+
```
|
15
|
+
|
16
|
+
### Usage
|
17
|
+
|
18
|
+
To authenticate with TodaysPlan API by setting the username and password variables
|
19
|
+
```ruby
|
20
|
+
TodaysPlan.username = 'email@example.com'
|
21
|
+
TodaysPlan.password = 'secret'
|
22
|
+
athletes = TodaysPlan::Athlete.all
|
23
|
+
```
|
24
|
+
or create a TodaysPlan client and pass the client to the method
|
25
|
+
```ruby
|
26
|
+
client = TodaysPlan::Client.new('email@example.com','secret')
|
27
|
+
athletes = TodaysPlan::Athlete.all(client)
|
28
|
+
```
|
29
|
+
|
30
|
+
### Testing
|
31
|
+
You can test live data by setting your credentials in todays_plan.yml. If this file
|
32
|
+
exists, live connections can be made and log the response to the console.
|
33
|
+
|
34
|
+
```yaml
|
35
|
+
---
|
36
|
+
username: 'email@example.com'
|
37
|
+
password: 'secret'
|
38
|
+
endpoint:
|
39
|
+
timeout: 120
|
40
|
+
logger: 'stdout'
|
41
|
+
debug: true
|
42
|
+
```
|
43
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
|
8
|
+
Bundler::GemHelper.install_tasks
|
9
|
+
|
10
|
+
Dir[File.join(File.dirname(__FILE__), 'tasks/**/*.rake')].each {|f| load f }
|
11
|
+
|
12
|
+
begin
|
13
|
+
require 'rspec/core/rake_task'
|
14
|
+
|
15
|
+
RSpec::Core::RakeTask.new(:spec)
|
16
|
+
|
17
|
+
task :default => :spec
|
18
|
+
rescue LoadError
|
19
|
+
# no rspec available
|
20
|
+
end
|
data/lib/todays_plan.rb
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'todays_plan/version'
|
2
|
+
require 'todays_plan/client'
|
3
|
+
require 'todays_plan/connector'
|
4
|
+
require 'todays_plan/athlete'
|
5
|
+
require 'todays_plan/activity'
|
6
|
+
require 'rest_client'
|
7
|
+
|
8
|
+
module TodaysPlan
|
9
|
+
|
10
|
+
class << self
|
11
|
+
attr_accessor :username,
|
12
|
+
:password,
|
13
|
+
:endpoint,
|
14
|
+
:timeout,
|
15
|
+
:logger,
|
16
|
+
:debug
|
17
|
+
|
18
|
+
def configure(&block)
|
19
|
+
raise ArgumentError, "must provide a block" unless block_given?
|
20
|
+
block.arity.zero? ? instance_eval(&block) : yield(self)
|
21
|
+
end
|
22
|
+
|
23
|
+
# set default endpoint
|
24
|
+
def endpoint
|
25
|
+
@endpoint ||= 'https://whats.todaysplan.com.au/rest'
|
26
|
+
end
|
27
|
+
|
28
|
+
#set default timeout
|
29
|
+
def timeout
|
30
|
+
@timeout ||= 120 #rest-client default
|
31
|
+
end
|
32
|
+
|
33
|
+
#set default logger
|
34
|
+
def logger
|
35
|
+
if @debug
|
36
|
+
@logger = 'stdout'
|
37
|
+
return
|
38
|
+
end
|
39
|
+
@logger ||= nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def debug
|
43
|
+
@debug ||= false
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module TodaysPlan
|
2
|
+
# Uses the TodaysPlan API for workouts and athlete information
|
3
|
+
|
4
|
+
class Activity
|
5
|
+
|
6
|
+
attr_reader :id
|
7
|
+
attr_reader :athlete_id
|
8
|
+
attr_reader :name
|
9
|
+
attr_reader :state
|
10
|
+
attr_reader :ts
|
11
|
+
attr_reader :type
|
12
|
+
attr_reader :completed_time
|
13
|
+
attr_reader :planned_time
|
14
|
+
attr_reader :description
|
15
|
+
attr_reader :completed_tscorepwr
|
16
|
+
attr_reader :planned_tscorepwr
|
17
|
+
|
18
|
+
def initialize(fields)
|
19
|
+
@id = fields['id'].to_i
|
20
|
+
@athlete_id = fields['user']['id'].to_i
|
21
|
+
@name = fields['name']
|
22
|
+
@state = fields['state']
|
23
|
+
@ts = Time.at(fields['ts'].to_i/1000).to_i
|
24
|
+
@type = fields['type']
|
25
|
+
@completed_time = fields["training"]
|
26
|
+
@planned_time = fields.has_key?('scheduled') ? fields['scheduled']["durationSecs"] : nil
|
27
|
+
@description = fields.has_key?('scheduled')? fields['scheduled']["preDescr"] : nil
|
28
|
+
@completed_tscorepwr = fields["tscorepwr"]
|
29
|
+
@planned_tscorepwr = fields.has_key?('scheduled')? fields['scheduled']["tscorepwr"] : nil
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
# Find all coaches athletes workouts
|
34
|
+
# +payload+:: they payload to send to in json format
|
35
|
+
# +offset+:: record count to start out
|
36
|
+
# +total+:: number of records to return
|
37
|
+
# +client+:: the authenticated client
|
38
|
+
# payload example:
|
39
|
+
# {
|
40
|
+
# "criteria": {
|
41
|
+
# "fromTs": 1451566800000,
|
42
|
+
# "toTs": 1496239200000,
|
43
|
+
# "isNull": [
|
44
|
+
# "fileId"
|
45
|
+
# ],
|
46
|
+
# "excludeWorkouts": [
|
47
|
+
# "rest"
|
48
|
+
# ]
|
49
|
+
# },
|
50
|
+
# "fields": [
|
51
|
+
# "scheduled.name",
|
52
|
+
# "scheduled.day",
|
53
|
+
# "workout",
|
54
|
+
# “state”,
|
55
|
+
# “reason”
|
56
|
+
# ],
|
57
|
+
# "opts": 1
|
58
|
+
#}
|
59
|
+
def self.all(payload,offset = 0, total = 100, client: nil)
|
60
|
+
Connector.new("/users/activities/search/#{offset}/#{total}/", client).
|
61
|
+
post(payload)['result']['results'].map do |data|
|
62
|
+
new(data)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module TodaysPlan
|
2
|
+
# Uses the TodaysPlan API for workouts and athlete information
|
3
|
+
|
4
|
+
class Athlete
|
5
|
+
|
6
|
+
attr_reader :id
|
7
|
+
attr_reader :name
|
8
|
+
attr_reader :first_name
|
9
|
+
attr_reader :last_name
|
10
|
+
attr_reader :timezone
|
11
|
+
attr_reader :coach
|
12
|
+
attr_reader :email
|
13
|
+
attr_reader :dob
|
14
|
+
|
15
|
+
|
16
|
+
def initialize(fields)
|
17
|
+
@id = fields['id'].to_i
|
18
|
+
@name = fields['_name']
|
19
|
+
@first_name = fields['firstname']
|
20
|
+
@last_name = fields['lastname']
|
21
|
+
@timezone = fields['timezone']
|
22
|
+
@coach = fields['coach']["id"].to_i if fields.has_key?("coach")
|
23
|
+
@email = fields['email']
|
24
|
+
@dob = Time.at(fields['dob'].to_i/1000)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Find a single athlete
|
28
|
+
# +client+:: the authenticated client
|
29
|
+
# +id+:: the athlete id
|
30
|
+
# +options+:: has of options
|
31
|
+
def self.find(id)
|
32
|
+
all().find{|a| a.id==id}
|
33
|
+
end
|
34
|
+
|
35
|
+
# Find all the coaches athletes
|
36
|
+
# +client+:: the authenticated client
|
37
|
+
def self.all(client = nil)
|
38
|
+
Connector.new('/users/delegates/users', client).get.map do |data|
|
39
|
+
new(data)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module TodaysPlan
|
2
|
+
class Client
|
3
|
+
|
4
|
+
attr_reader :token
|
5
|
+
|
6
|
+
def initialize(username,password)
|
7
|
+
RestClient.log=TodaysPlan.logger
|
8
|
+
@token = authenticate(username,password)
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
def logout
|
13
|
+
url = TodaysPlan.endpoint+"/auth/logout"
|
14
|
+
response= RestClient.get(url, {:Authorization => "Bearer #{token}"})
|
15
|
+
response.body
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
def authenticate(username,password)
|
20
|
+
payload= {'username'=>username,"password"=>password, "token"=> true}
|
21
|
+
url = TodaysPlan.endpoint+"/auth/login"
|
22
|
+
response= RestClient.post(url, payload.to_json,{content_type: :json, accept: :json} )
|
23
|
+
body = JSON.parse(response.body)
|
24
|
+
body['token']
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module TodaysPlan
|
2
|
+
class Connector
|
3
|
+
attr_reader :uri, :response, :body, :timeout, :client
|
4
|
+
|
5
|
+
def initialize(path, client = nil)
|
6
|
+
@client = client || Client.new(TodaysPlan.username, TodaysPlan.password)
|
7
|
+
@uri = TodaysPlan.endpoint + path
|
8
|
+
@timeout = TodaysPlan.timeout
|
9
|
+
end
|
10
|
+
|
11
|
+
# Internal: Run GET request.
|
12
|
+
#
|
13
|
+
# Returns the parsed JSON response body.
|
14
|
+
def get()
|
15
|
+
@response = RestClient::Request.execute(method: :get, url: @uri,
|
16
|
+
timeout: @timeout,
|
17
|
+
headers: {:Authorization => "Bearer #{@client.token}",accept: :json} )
|
18
|
+
puts "#{@response.body}" if TodaysPlan.debug
|
19
|
+
@body = JSON.parse(@response.body)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Internal: Run GET request.
|
23
|
+
#
|
24
|
+
# Returns the parsed JSON response body.
|
25
|
+
def post(payload = {})
|
26
|
+
@response = RestClient::Request.execute(method: :post,
|
27
|
+
payload: payload, url: @uri,
|
28
|
+
timeout: @timeout,
|
29
|
+
headers:{:Authorization => "Bearer #{@client.token}",
|
30
|
+
content_type: :json,
|
31
|
+
accept: :json})
|
32
|
+
puts "#{@response.body}" if TodaysPlan.debug
|
33
|
+
@body = JSON.parse(@response.body)
|
34
|
+
@body
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
{
|
2
|
+
"criteria": {
|
3
|
+
"fromTs": 1451566800000,
|
4
|
+
"toTs": 1496239200000,
|
5
|
+
"isNull": [
|
6
|
+
"fileId"
|
7
|
+
],
|
8
|
+
"excludeWorkouts": [
|
9
|
+
"rest"
|
10
|
+
]
|
11
|
+
},
|
12
|
+
"fields": [
|
13
|
+
"scheduled.name",
|
14
|
+
"scheduled.day",
|
15
|
+
"workout",
|
16
|
+
"state",
|
17
|
+
"reason"
|
18
|
+
],
|
19
|
+
"opts": 1
|
20
|
+
}
|
@@ -0,0 +1,43 @@
|
|
1
|
+
{
|
2
|
+
"cnt": 1.0,
|
3
|
+
"offset": 0.0,
|
4
|
+
"result": {
|
5
|
+
"fields": {},
|
6
|
+
"results": [
|
7
|
+
{
|
8
|
+
"id": 60390.0,
|
9
|
+
"scheduled": {
|
10
|
+
"name": "Efforts",
|
11
|
+
"day": 1
|
12
|
+
},
|
13
|
+
"source": "file",
|
14
|
+
"user": {
|
15
|
+
"id": 64.0,
|
16
|
+
"dob": 4.853952E11,
|
17
|
+
"firstname": "Joe",
|
18
|
+
"lastname": "Athlete",
|
19
|
+
"isPremium": true
|
20
|
+
},
|
21
|
+
"name": "Efforts",
|
22
|
+
"workoutId": 46.0,
|
23
|
+
"activityId": 46.0,
|
24
|
+
"activity": "workouts",
|
25
|
+
"ts": 1.4786712E12,
|
26
|
+
"tz": "Europe/Madrid",
|
27
|
+
"type": "ride",
|
28
|
+
"equipment": "road",
|
29
|
+
"workout": "training",
|
30
|
+
"permissions": [
|
31
|
+
"edit1",
|
32
|
+
"del",
|
33
|
+
"edit2",
|
34
|
+
"view2",
|
35
|
+
"view1",
|
36
|
+
"view3",
|
37
|
+
"edit3"
|
38
|
+
]
|
39
|
+
}
|
40
|
+
],
|
41
|
+
"keys": []
|
42
|
+
}
|
43
|
+
}
|
@@ -0,0 +1,25 @@
|
|
1
|
+
[{
|
2
|
+
"_name": "Joe Athlete",
|
3
|
+
"id": 7187033,
|
4
|
+
"email": "joeathlete@example.com",
|
5
|
+
"md5": "df1cc92fbe295b86fdceb070d5458e84",
|
6
|
+
"dob": 315532800000,
|
7
|
+
"_dob-display": "1/1/80",
|
8
|
+
"firstname": "Joe",
|
9
|
+
"lastname": "Athlete",
|
10
|
+
"timezone": "US/Mountain",
|
11
|
+
"relationship": "coach",
|
12
|
+
"_relationship-display": "Coach",
|
13
|
+
"isPremium": false,
|
14
|
+
"hasPwr": false,
|
15
|
+
"hasBpm": false,
|
16
|
+
"lastLogin": 1478439753344,
|
17
|
+
"_lastLogin-display": "11/6/16 6:42 AM",
|
18
|
+
"coach": {
|
19
|
+
"id": 7187033
|
20
|
+
},
|
21
|
+
"schedulePref": "prefer_single",
|
22
|
+
"_schedulePref-display": "Single sessions",
|
23
|
+
"roles": ["pi", "user"],
|
24
|
+
"attsMask": 514
|
25
|
+
}]
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'shoulda-matchers'
|
2
|
+
require 'todays_plan'
|
3
|
+
require 'webmock/rspec'
|
4
|
+
|
5
|
+
CONFIG={}
|
6
|
+
if File.exists?('todays_plan.yml')
|
7
|
+
CONFIG=YAML.load(ERB.new(File.read("todays_plan.yml")).result)
|
8
|
+
end
|
9
|
+
|
10
|
+
TodaysPlan.configure do |config|
|
11
|
+
config.username = CONFIG["username"] ||= 'id'
|
12
|
+
config.password = CONFIG["password"] ||= 'secret'
|
13
|
+
#config.endpoint = 'https://whats.todaysplan.com.au/rest/'
|
14
|
+
config.timeout = CONFIG["timeout"] ||= 120
|
15
|
+
config.logger = CONFIG["logger"] ||= nil
|
16
|
+
config.debug = CONFIG["debug"] ||= false
|
17
|
+
end
|
18
|
+
|
19
|
+
WebMock.allow_net_connect! if TodaysPlan.debug
|
20
|
+
|
21
|
+
RSpec.configure do |config|
|
22
|
+
config.mock_with :rspec
|
23
|
+
config.order = "random"
|
24
|
+
config.before {
|
25
|
+
stub_request(:post, TodaysPlan.endpoint+'/auth/login').
|
26
|
+
with(:body => "{\"username\":\"#{TodaysPlan.username}\",\"password\":\"#{TodaysPlan.password}\",\"token\":true}",
|
27
|
+
:headers => {'Accept'=>'application/json',
|
28
|
+
'Content-Type'=>'application/json', }).
|
29
|
+
to_return(:status => 200, :body => '{"token":"abc-123"}', :headers => {}) unless TodaysPlan.debug
|
30
|
+
}
|
31
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TodaysPlan::Activity do
|
4
|
+
|
5
|
+
|
6
|
+
#let(:json){File.read("spec/fixtures/activities/incomplete.json")}
|
7
|
+
let(:json){'{"criteria":{"fromTs":1482735600000.0,"toTs":1486364400000.0,"userIds":[7184519]},"fields":["scheduled.name","scheduled.day","workout","state","reason"],"opts":1}'}
|
8
|
+
let(:response){File.read("spec/fixtures/activities/incomplete_response.json")}
|
9
|
+
|
10
|
+
it "expect to get incomplete activites" do
|
11
|
+
|
12
|
+
stub_request(:post, "#{TodaysPlan.endpoint}/users/activities/search/0/100/").
|
13
|
+
with(:body => json,
|
14
|
+
:headers => {'Accept'=>'application/json',
|
15
|
+
'Authorization'=>'Bearer abc-123',
|
16
|
+
'Content-Type'=>'application/json', }).
|
17
|
+
to_return(:status => 200, :body => response, :headers => {})
|
18
|
+
all = TodaysPlan::Activity.all(json,0,100)
|
19
|
+
expect(all).to be_a(Array)
|
20
|
+
expect(all[0]).to be_a(TodaysPlan::Activity)
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|