ruby-trello 0.2.1 → 0.3.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.
- data/README.md +6 -1
- data/lib/trello.rb +23 -3
- data/lib/trello/action.rb +1 -0
- data/lib/trello/authorization.rb +77 -0
- data/lib/trello/basic_data.rb +1 -2
- data/lib/trello/board.rb +33 -16
- data/lib/trello/card.rb +24 -3
- data/lib/trello/checklist.rb +1 -0
- data/lib/trello/client.rb +33 -48
- data/lib/trello/item.rb +1 -0
- data/lib/trello/item_state.rb +1 -0
- data/lib/trello/list.rb +19 -0
- data/lib/trello/member.rb +1 -0
- data/lib/trello/net.rb +35 -0
- data/lib/trello/notification.rb +1 -0
- data/lib/trello/organization.rb +1 -0
- data/lib/trello/string.rb +1 -1
- data/spec/action_spec.rb +39 -10
- data/spec/basic_auth_policy_spec.rb +56 -0
- data/spec/board_spec.rb +156 -13
- data/spec/card_spec.rb +58 -23
- data/spec/client_spec.rb +123 -22
- data/spec/integration/how_to_authorize_spec.rb +53 -0
- data/spec/integration/how_to_use_boards_spec.rb +48 -0
- data/spec/integration/integration_test.rb +40 -0
- data/spec/list_spec.rb +15 -9
- data/spec/member_spec.rb +14 -11
- data/spec/oauth_policy_spec.rb +83 -0
- data/spec/spec_helper.rb +27 -11
- data/spec/string_spec.rb +50 -0
- metadata +23 -9
data/README.md
CHANGED
@@ -15,6 +15,11 @@ Seriously, [check it out](http://www.trello.com/).
|
|
15
15
|
|
16
16
|
Full Disclosure: This library is not complete, but it does function enough to be useful.
|
17
17
|
|
18
|
+
## Special thanks
|
19
|
+
|
20
|
+
A special thanks goes out to [Ben Biddington](https://github.com/ben-biddington) who has contributed a significant amount
|
21
|
+
of refactoring and functionality to be deserving of a beer and this special thanks.
|
22
|
+
|
18
23
|
## Contributing
|
19
24
|
|
20
25
|
Pick up an editor, fix a test! (If you want to, of course.) Send a pull
|
@@ -24,4 +29,4 @@ request, and I'll look at it. I only ask a few things:
|
|
24
29
|
2. Adding or refactoring existing features, ensure there are tests.
|
25
30
|
|
26
31
|
Also, don't be afraid to send a pull request if your changes aren't done. Just
|
27
|
-
let me know.
|
32
|
+
let me know.
|
data/lib/trello.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'oauth'
|
2
2
|
require 'json'
|
3
|
+
require 'logger'
|
3
4
|
|
4
5
|
# Ruby wrapper around the Trello[http://trello.com] API
|
5
6
|
#
|
@@ -28,6 +29,7 @@ module Trello
|
|
28
29
|
autoload :BasicData, 'trello/basic_data'
|
29
30
|
autoload :Board, 'trello/board'
|
30
31
|
autoload :Card, 'trello/card'
|
32
|
+
autoload :Checklist, 'trello/checklist'
|
31
33
|
autoload :Client, 'trello/client'
|
32
34
|
autoload :Item, 'trello/item'
|
33
35
|
autoload :ItemState, 'trello/item_state'
|
@@ -35,10 +37,28 @@ module Trello
|
|
35
37
|
autoload :Member, 'trello/member'
|
36
38
|
autoload :Notification, 'trello/notification'
|
37
39
|
autoload :Organization, 'trello/organization'
|
40
|
+
autoload :Request, 'trello/net'
|
41
|
+
autoload :TInternet, 'trello/net'
|
38
42
|
|
39
43
|
# Version of the Trello API that we use by default.
|
40
44
|
API_VERSION = 1
|
41
45
|
|
42
|
-
# Raise this when we
|
43
|
-
class
|
44
|
-
|
46
|
+
# Raise this when we hit a Trello error.
|
47
|
+
class Error < StandardError; end
|
48
|
+
|
49
|
+
def self.logger
|
50
|
+
@@logger || Logger.new(STDOUT)
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.logger=(logger)
|
54
|
+
@@logger = logger
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
module Trello
|
59
|
+
module Authorization
|
60
|
+
autoload :AuthPolicy, 'trello/authorization'
|
61
|
+
autoload :BasicAuthPolicy, 'trello/authorization'
|
62
|
+
autoload :OAuthPolicy, 'trello/authorization'
|
63
|
+
end
|
64
|
+
end
|
data/lib/trello/action.rb
CHANGED
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'securerandom'
|
2
|
+
|
3
|
+
module Trello
|
4
|
+
module Authorization
|
5
|
+
|
6
|
+
AuthPolicy = Class.new
|
7
|
+
|
8
|
+
class BasicAuthPolicy
|
9
|
+
class << self
|
10
|
+
attr_accessor :developer_public_key, :member_token
|
11
|
+
|
12
|
+
def authorize(request)
|
13
|
+
the_uri = Addressable::URI.parse(request.uri)
|
14
|
+
existing_values = the_uri.query_values.nil? ? {} : the_uri.query_values
|
15
|
+
new_values = { :key => @developer_public_key, :token => @member_token }
|
16
|
+
the_uri.query_values = new_values.merge existing_values
|
17
|
+
|
18
|
+
Request.new request.verb, the_uri, request.headers
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class Clock
|
24
|
+
def self.timestamp; Time.now.to_i; end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Nonce
|
28
|
+
def self.next
|
29
|
+
SecureRandom.hex()
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
OAuthCredential = Struct.new "OAuthCredential", :key, :secret
|
34
|
+
|
35
|
+
class OAuthPolicy
|
36
|
+
class << self
|
37
|
+
attr_accessor :consumer_credential, :token
|
38
|
+
|
39
|
+
def authorize(request)
|
40
|
+
fail "The consumer_credential has not been supplied." unless consumer_credential
|
41
|
+
|
42
|
+
request.headers = {"Authorization" => get_auth_header(request.uri, :get)}
|
43
|
+
request
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def get_auth_header(url, verb)
|
49
|
+
require "oauth"
|
50
|
+
|
51
|
+
self.token ||= OAuthCredential.new
|
52
|
+
|
53
|
+
consumer = OAuth::Consumer.new(
|
54
|
+
consumer_credential.key,
|
55
|
+
consumer_credential.secret,
|
56
|
+
{
|
57
|
+
:scheme => :header,
|
58
|
+
:scope => 'read,write,account',
|
59
|
+
:http_method => verb
|
60
|
+
}
|
61
|
+
)
|
62
|
+
|
63
|
+
request = Net::HTTP::Get.new Addressable::URI.parse(url).to_s
|
64
|
+
|
65
|
+
consumer.options[:signature_method] = 'HMAC-SHA1'
|
66
|
+
consumer.options[:nonce] = Nonce.next
|
67
|
+
consumer.options[:timestamp] = Clock.timestamp
|
68
|
+
consumer.options[:uri] = url
|
69
|
+
|
70
|
+
consumer.sign!(request, OAuth::Token.new(token.key, token.secret))
|
71
|
+
|
72
|
+
request['authorization']
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/trello/basic_data.rb
CHANGED
@@ -5,7 +5,6 @@ module Trello
|
|
5
5
|
attr_reader :id
|
6
6
|
|
7
7
|
class << self
|
8
|
-
# Perform a query to retrieve some information at a specific path for a specific id.
|
9
8
|
def find(path, id)
|
10
9
|
Client.get("/#{path}/#{id}").json_into(self)
|
11
10
|
end
|
@@ -29,4 +28,4 @@ module Trello
|
|
29
28
|
id == other.id
|
30
29
|
end
|
31
30
|
end
|
32
|
-
end
|
31
|
+
end
|
data/lib/trello/board.rb
CHANGED
@@ -1,31 +1,44 @@
|
|
1
1
|
module Trello
|
2
|
-
|
2
|
+
|
3
3
|
class Board < BasicData
|
4
|
-
attr_reader :id, :name, :description, :
|
4
|
+
attr_reader :id, :name, :description, :url, :organization_id
|
5
5
|
|
6
6
|
class << self
|
7
|
-
|
7
|
+
|
8
8
|
def find(id)
|
9
9
|
super(:boards, id)
|
10
10
|
end
|
11
|
+
|
12
|
+
def create(attributes)
|
13
|
+
Client.post("/boards/", attributes).json_into Board
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def save!
|
18
|
+
fail "Cannot save new instance." unless self.id
|
19
|
+
|
20
|
+
Client.put("/boards/#{self.id}/", {
|
21
|
+
:name => @name,
|
22
|
+
:description => @description,
|
23
|
+
:closed => @closed,
|
24
|
+
:url => @url,
|
25
|
+
:organisation_id => @organisation_id
|
26
|
+
}).json_into(self)
|
11
27
|
end
|
12
28
|
|
13
|
-
# Update the fields of a board.
|
14
|
-
#
|
15
|
-
# Supply a hash of string keyed data retrieved from the Trello API representing
|
16
|
-
# a board.
|
17
29
|
def update_fields(fields)
|
18
|
-
@id = fields['id']
|
19
|
-
@name = fields['name']
|
20
|
-
@description = fields['desc']
|
21
|
-
@closed = fields['closed']
|
22
|
-
@url = fields['url']
|
23
|
-
@organization_id = fields['idOrganization']
|
30
|
+
@id = fields['id'] if fields['id']
|
31
|
+
@name = fields['name'] if fields['name']
|
32
|
+
@description = fields['desc'] if fields['desc']
|
33
|
+
@closed = fields['closed'] if fields.has_key?('closed')
|
34
|
+
@url = fields['url'] if fields['url']
|
35
|
+
@organization_id = fields['idOrganization'] if fields['idOrganization']
|
36
|
+
|
37
|
+
self
|
24
38
|
end
|
25
39
|
|
26
|
-
# Check if the board is active.
|
27
40
|
def closed?
|
28
|
-
closed
|
41
|
+
@closed
|
29
42
|
end
|
30
43
|
|
31
44
|
# Return a timeline of actions related to this board.
|
@@ -44,6 +57,10 @@ module Trello
|
|
44
57
|
@cards = Client.get("/boards/#{id}/cards").json_into(Card)
|
45
58
|
end
|
46
59
|
|
60
|
+
def has_lists?
|
61
|
+
lists.size > 0
|
62
|
+
end
|
63
|
+
|
47
64
|
# Returns all the lists on this board.
|
48
65
|
#
|
49
66
|
# The options hash may have a filter key which can have its value set as any
|
@@ -61,7 +78,7 @@ module Trello
|
|
61
78
|
# :filter => [ :none, :normal, :owners, :all ] # default :all
|
62
79
|
def members(options = { :filter => :all })
|
63
80
|
return @members if @members
|
64
|
-
@members = Client.get("/boards/#{id}/members").json_into(Member)
|
81
|
+
@members = Client.get("/boards/#{id}/members", options).json_into(Member)
|
65
82
|
end
|
66
83
|
|
67
84
|
# Returns a reference to the organization this board belongs to.
|
data/lib/trello/card.rb
CHANGED
@@ -14,7 +14,7 @@ module Trello
|
|
14
14
|
def create(options)
|
15
15
|
new('name' => options[:name],
|
16
16
|
'idList' => options[:list_id],
|
17
|
-
'desc'
|
17
|
+
'desc' => options[:description]).save!
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
@@ -31,6 +31,7 @@ module Trello
|
|
31
31
|
@board_id = fields['idBoard']
|
32
32
|
@member_ids = fields['idMembers']
|
33
33
|
@list_id = fields['idList']
|
34
|
+
self
|
34
35
|
end
|
35
36
|
|
36
37
|
# Returns a list of the actions associated with this card.
|
@@ -69,15 +70,35 @@ module Trello
|
|
69
70
|
end
|
70
71
|
end
|
71
72
|
|
73
|
+
# Change the name of the card
|
74
|
+
def name=(val)
|
75
|
+
Client.put("/card/#{id}/name", :value => val)
|
76
|
+
@name = val
|
77
|
+
end
|
78
|
+
|
79
|
+
# Change the description of the card
|
80
|
+
def description=(val)
|
81
|
+
Client.put("/card/#{id}/desc", :value => val)
|
82
|
+
@description = val
|
83
|
+
end
|
84
|
+
|
85
|
+
# Change the list this card is a part of
|
86
|
+
def list=(other)
|
87
|
+
Client.put("/card/#{id}/idList", :value => other.id)
|
88
|
+
@list_id = other.id
|
89
|
+
other
|
90
|
+
end
|
91
|
+
|
72
92
|
# Saves a record.
|
73
93
|
def save!
|
94
|
+
# If we have an id, just update our fields.
|
74
95
|
return update! if id
|
75
96
|
|
76
97
|
Client.post("/cards", {
|
77
98
|
:name => @name,
|
78
99
|
:desc => @description,
|
79
100
|
:idList => @list_id
|
80
|
-
})
|
101
|
+
}).json_into(self)
|
81
102
|
end
|
82
103
|
|
83
104
|
# Update an existing record.
|
@@ -93,7 +114,7 @@ module Trello
|
|
93
114
|
|
94
115
|
# Add a comment with the supplied text.
|
95
116
|
def add_comment(text)
|
96
|
-
Client.
|
117
|
+
Client.post("/cards/#{id}/actions/comments", :text => text)
|
97
118
|
end
|
98
119
|
end
|
99
120
|
end
|
data/lib/trello/checklist.rb
CHANGED
data/lib/trello/client.rb
CHANGED
@@ -1,66 +1,51 @@
|
|
1
1
|
require 'addressable/uri'
|
2
2
|
|
3
3
|
module Trello
|
4
|
-
# Client is used to handle the OAuth connection to Trello as well as send requests over that authenticated socket.
|
5
4
|
class Client
|
6
|
-
|
7
|
-
class EnterYourSecret < StandardError; end
|
5
|
+
extend Authorization
|
8
6
|
|
9
7
|
class << self
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
# get(path, params)
|
14
|
-
# post(path, params)
|
15
|
-
# put(path, params)
|
16
|
-
# delete(path, params)
|
17
|
-
# query(api_version, path, options)
|
18
|
-
#
|
19
|
-
# Makes a query to a specific path via one of the four HTTP methods, optionally
|
20
|
-
# with a hash specifying parameters to pass to Trello.
|
21
|
-
#
|
22
|
-
# You should use one of _.get_, _.post_, _.put_ or _.delete_ instead of this method.
|
23
|
-
def query(api_version, path, options = { :method => :get, :params => {} })
|
8
|
+
def get(path, params = {})
|
9
|
+
api_version = 1
|
10
|
+
|
24
11
|
uri = Addressable::URI.parse("https://api.trello.com/#{api_version}#{path}")
|
25
|
-
uri.query_values =
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
e.request.each_header do |k,v|
|
31
|
-
headers << [k, v]
|
32
|
-
end
|
33
|
-
|
34
|
-
logger.error("[#{@access_token}] Disposing of access token.\n#{e.inspect}")
|
35
|
-
logger.info("[#{@access_token}] Headers: #{headers.inspect}\nRequest Body: #{e.request.body}")
|
36
|
-
@access_token = nil
|
37
|
-
end
|
12
|
+
uri.query_values = params unless params.empty?
|
13
|
+
|
14
|
+
request = Request.new :get, uri, {}
|
15
|
+
|
16
|
+
response = TInternet.execute AuthPolicy.authorize(request)
|
38
17
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
params = args[1] || {}
|
43
|
-
query(API_VERSION, path, :method => http_method, :params => params).read_body
|
44
|
-
end
|
18
|
+
raise Error, response.body unless response.code.to_i == 200
|
19
|
+
|
20
|
+
response.body
|
45
21
|
end
|
46
22
|
|
47
|
-
|
23
|
+
def post(path, body = {})
|
24
|
+
api_version = 1
|
25
|
+
|
26
|
+
uri = Addressable::URI.parse("https://api.trello.com/#{api_version}#{path}")
|
27
|
+
|
28
|
+
request = Request.new :post, uri, {}, body
|
29
|
+
|
30
|
+
response = TInternet.execute AuthPolicy.authorize(request)
|
48
31
|
|
49
|
-
|
50
|
-
raise EnterYourPublicKey if @public_key.to_s.empty?
|
51
|
-
raise EnterYourSecret if @secret.to_s.empty?
|
32
|
+
raise Error, response.body unless response.code.to_i == 200
|
52
33
|
|
53
|
-
|
54
|
-
:request_token_path => '/1/OAuthGetRequestToken',
|
55
|
-
:authorize_path => '/1/OAuthAuthorizeToken',
|
56
|
-
:access_token_path => '/1/OAuthGetAccessToken',
|
57
|
-
:http_method => :get)
|
34
|
+
response.body
|
58
35
|
end
|
59
36
|
|
60
|
-
def
|
61
|
-
|
37
|
+
def put(path, body = {})
|
38
|
+
api_version = 1
|
39
|
+
|
40
|
+
uri = Addressable::URI.parse("https://api.trello.com/#{api_version}#{path}")
|
41
|
+
|
42
|
+
request = Request.new :put, uri, {}, body
|
43
|
+
|
44
|
+
response = TInternet.execute AuthPolicy.authorize(request)
|
45
|
+
|
46
|
+
raise Error, response.body unless response.code.to_i == 200
|
62
47
|
|
63
|
-
|
48
|
+
response.body
|
64
49
|
end
|
65
50
|
end
|
66
51
|
end
|
data/lib/trello/item.rb
CHANGED
data/lib/trello/item_state.rb
CHANGED
data/lib/trello/list.rb
CHANGED
@@ -8,6 +8,11 @@ module Trello
|
|
8
8
|
def find(id)
|
9
9
|
super(:lists, id)
|
10
10
|
end
|
11
|
+
|
12
|
+
def create(options)
|
13
|
+
new('name' => options[:name],
|
14
|
+
'idBoard' => options[:board_id]).save!
|
15
|
+
end
|
11
16
|
end
|
12
17
|
|
13
18
|
# Updates the fields of a list.
|
@@ -19,6 +24,20 @@ module Trello
|
|
19
24
|
@name = fields['name']
|
20
25
|
@closed = fields['closed']
|
21
26
|
@board_id = fields['idBoard']
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def save!
|
31
|
+
return update! if id
|
32
|
+
|
33
|
+
Client.post("/lists", {
|
34
|
+
:name => @name,
|
35
|
+
:closed => @closed || false,
|
36
|
+
:idBoard => @board_id
|
37
|
+
}).json_into(self)
|
38
|
+
end
|
39
|
+
|
40
|
+
def update!
|
22
41
|
end
|
23
42
|
|
24
43
|
# Check if the list is not active anymore.
|