todaysplan 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Build Status](https://travis-ci.org/9kSoftware/todaysplan-ruby.svg?branch=master)](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
|