trelloapi 0.1.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.
Files changed (47) hide show
  1. checksums.yaml +7 -0
  2. data/.DS_Store +0 -0
  3. data/.gitignore +12 -0
  4. data/.rspec +2 -0
  5. data/.travis.yml +5 -0
  6. data/Gemfile +4 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +11 -0
  9. data/Rakefile +6 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/lib/.DS_Store +0 -0
  13. data/lib/trello.rb +163 -0
  14. data/lib/trello/.DS_Store +0 -0
  15. data/lib/trello/action.rb +68 -0
  16. data/lib/trello/association.rb +14 -0
  17. data/lib/trello/association_proxy.rb +42 -0
  18. data/lib/trello/attachment.rb +40 -0
  19. data/lib/trello/authorization.rb +187 -0
  20. data/lib/trello/basic_data.rb +132 -0
  21. data/lib/trello/board.rb +201 -0
  22. data/lib/trello/card.rb +456 -0
  23. data/lib/trello/checklist.rb +142 -0
  24. data/lib/trello/client.rb +120 -0
  25. data/lib/trello/comment.rb +62 -0
  26. data/lib/trello/configuration.rb +68 -0
  27. data/lib/trello/core_ext/array.rb +6 -0
  28. data/lib/trello/core_ext/hash.rb +6 -0
  29. data/lib/trello/core_ext/string.rb +6 -0
  30. data/lib/trello/cover_image.rb +8 -0
  31. data/lib/trello/has_actions.rb +9 -0
  32. data/lib/trello/item.rb +37 -0
  33. data/lib/trello/item_state.rb +30 -0
  34. data/lib/trello/json_utils.rb +64 -0
  35. data/lib/trello/label.rb +108 -0
  36. data/lib/trello/label_name.rb +31 -0
  37. data/lib/trello/list.rb +114 -0
  38. data/lib/trello/member.rb +112 -0
  39. data/lib/trello/multi_association.rb +12 -0
  40. data/lib/trello/net.rb +39 -0
  41. data/lib/trello/notification.rb +61 -0
  42. data/lib/trello/organization.rb +68 -0
  43. data/lib/trello/plugin_datum.rb +34 -0
  44. data/lib/trello/token.rb +36 -0
  45. data/lib/trello/webhook.rb +103 -0
  46. data/trello.gemspec +41 -0
  47. metadata +161 -0
@@ -0,0 +1,187 @@
1
+ require 'securerandom'
2
+ require "oauth"
3
+
4
+ module Trello
5
+ module Authorization
6
+
7
+ AuthPolicy = Class.new do
8
+ def initialize(attrs = {}); end
9
+
10
+ def authorize(*args)
11
+ raise Trello::ConfigurationError, "Trello has not been configured to make authorized requests."
12
+ end
13
+ end
14
+
15
+ class BasicAuthPolicy
16
+ class << self
17
+ attr_accessor :developer_public_key, :member_token
18
+
19
+ def authorize(request)
20
+ new.authorize(request)
21
+ end
22
+ end
23
+
24
+ attr_accessor :developer_public_key, :member_token
25
+
26
+ def initialize(attrs = {})
27
+ @developer_public_key = attrs[:developer_public_key] || self.class.developer_public_key
28
+ @member_token = attrs[:member_token] || self.class.member_token
29
+ end
30
+
31
+ def authorize(request)
32
+ the_uri = Addressable::URI.parse(request.uri)
33
+ existing_values = the_uri.query_values.nil? ? {} : the_uri.query_values
34
+ new_values = { key: @developer_public_key, token: @member_token }
35
+ the_uri.query_values = new_values.merge existing_values
36
+
37
+ Request.new request.verb, the_uri, request.headers, request.body
38
+ end
39
+ end
40
+
41
+ class Clock
42
+ def self.timestamp; Time.now.to_i; end
43
+ end
44
+
45
+ class Nonce
46
+ def self.next
47
+ SecureRandom.hex()
48
+ end
49
+ end
50
+
51
+ OAuthCredential = Struct.new "OAuthCredential", :key, :secret
52
+
53
+ # Handles the OAuth connectivity to Trello.
54
+ #
55
+ # For 2-legged OAuth, do the following:
56
+ #
57
+ # OAuthPolicy.consumer_credential = OAuthCredential.new "public_key", "secret"
58
+ # OAuthPolicy.token = OAuthCredential.new "token_key", nil
59
+ #
60
+ # For 3-legged OAuth, do the following:
61
+ #
62
+ # OAuthPolicy.consumer_credential = OAuthCredential.new "public_key", "secret"
63
+ # OAuthPolicy.return_url = "http://your.site.com/path/to/receive/post"
64
+ # OAuthPolicy.callback = Proc.new do |request_token|
65
+ # DB.save(request_token.key, request_token.secret)
66
+ # redirect_to request_token.authorize_url
67
+ # end
68
+ #
69
+ # Then, recreate the request token given the request token key and secret you saved earlier,
70
+ # and the consumer, and pass that RequestToken instance the #get_access_token method, and
71
+ # store that in OAuthPolicy.token as a OAuthCredential.
72
+ class OAuthPolicy
73
+ class << self
74
+ attr_accessor :consumer_credential, :token, :return_url, :callback
75
+
76
+ def authorize(request)
77
+ new.authorize(request)
78
+ end
79
+ end
80
+
81
+ attr_accessor :attributes
82
+ attr_accessor :consumer_credential, :token, :return_url, :callback
83
+
84
+ def initialize(attrs = {})
85
+ @consumer_key = attrs[:consumer_key]
86
+ @consumer_secret = attrs[:consumer_secret]
87
+ @oauth_token = attrs[:oauth_token]
88
+ @oauth_token_secret = attrs[:oauth_token_secret]
89
+ @return_url = attrs[:return_url] || self.class.return_url
90
+ @callback = attrs[:callback] || self.class.callback
91
+ end
92
+
93
+ def authorize(request)
94
+ unless consumer_credential
95
+ Trello.logger.error "The consumer_credential has not been supplied."
96
+ fail "The consumer_credential has not been supplied."
97
+ end
98
+
99
+ if token
100
+ request.headers = {"Authorization" => get_auth_header(request.uri, :get)}
101
+ request
102
+ else
103
+ consumer(return_url: return_url, callback_method: :postMessage)
104
+ request_token = consumer.get_request_token(oauth_callback: return_url)
105
+ callback.call request_token
106
+ return nil
107
+ end
108
+ end
109
+
110
+ def consumer_credential
111
+ @consumer_credential ||= build_consumer_credential
112
+ end
113
+
114
+ def token
115
+ @token ||= build_token
116
+ end
117
+
118
+ def consumer_key
119
+ consumer_credential.key
120
+ end
121
+
122
+ def consumer_secret
123
+ consumer_credential.secret
124
+ end
125
+
126
+ def oauth_token
127
+ token.key
128
+ end
129
+
130
+ def oauth_token_secret
131
+ token.secret
132
+ end
133
+
134
+ private
135
+
136
+ def build_consumer_credential
137
+ if @consumer_key && @consumer_secret
138
+ OAuthCredential.new @consumer_key, @consumer_secret
139
+ else
140
+ self.class.consumer_credential
141
+ end
142
+ end
143
+
144
+ def build_token
145
+ if @oauth_token
146
+ OAuthCredential.new @oauth_token, @oauth_token_secret
147
+ else
148
+ self.class.token
149
+ end
150
+ end
151
+
152
+ def consumer_params(params = {})
153
+ {
154
+ scheme: :header,
155
+ scope: 'read,write,account',
156
+ http_method: :get,
157
+ request_token_path: "https://trello.com/1/OAuthGetRequestToken",
158
+ authorize_path: "https://trello.com/1/OAuthAuthorizeToken",
159
+ access_token_path: "https://trello.com/1/OAuthGetAccessToken"
160
+ }.merge!(params)
161
+ end
162
+
163
+ def consumer(options = {})
164
+ @consumer ||= OAuth::Consumer.new(
165
+ consumer_credential.key,
166
+ consumer_credential.secret,
167
+ consumer_params(options)
168
+ )
169
+ end
170
+
171
+ def get_auth_header(url, verb, options = {})
172
+ request = Net::HTTP::Get.new Addressable::URI.parse(url).to_s
173
+
174
+ consumer.options[:signature_method] = 'HMAC-SHA1'
175
+ consumer.options[:nonce] = Nonce.next
176
+ consumer.options[:timestamp] = Clock.timestamp
177
+ consumer.options[:uri] = url
178
+ consumer.key = consumer_credential.key
179
+ consumer.secret = consumer_credential.secret
180
+
181
+ consumer.sign!(request, OAuth::Token.new(token.key, token.secret))
182
+
183
+ request['authorization']
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,132 @@
1
+ require 'active_support/inflector'
2
+
3
+ module Trello
4
+ class BasicData
5
+ include ActiveModel::Validations
6
+ include ActiveModel::Dirty
7
+ include ActiveModel::Serializers::JSON
8
+
9
+ include Trello::JsonUtils
10
+
11
+ class << self
12
+ def path_name
13
+ name.split("::").last.underscore
14
+ end
15
+
16
+ def find(id, params = {})
17
+ client.find(path_name, id, params)
18
+ end
19
+
20
+ def create(options)
21
+ client.create(path_name, options)
22
+ end
23
+
24
+ def save(options)
25
+ new(options).tap do |basic_data|
26
+ yield basic_data if block_given?
27
+ end.save
28
+ end
29
+
30
+ def parse(response)
31
+ from_response(response).tap do |basic_data|
32
+ yield basic_data if block_given?
33
+ end
34
+ end
35
+
36
+ def parse_many(response)
37
+ from_response(response).map do |data|
38
+ data.tap do |d|
39
+ yield d if block_given?
40
+ end
41
+ end
42
+ end
43
+ end
44
+
45
+ def self.register_attributes(*names)
46
+ options = { readonly: [] }
47
+ options.merge!(names.pop) if names.last.kind_of? Hash
48
+
49
+ # Defines the attribute getter and setters.
50
+ class_eval do
51
+ define_method :attributes do
52
+ @attributes ||= names.reduce({}) { |hash, k| hash.merge(k.to_sym => nil) }
53
+ end
54
+
55
+ names.each do |key|
56
+ define_method(:"#{key}") { @attributes[key] }
57
+
58
+ unless options[:readonly].include?(key.to_sym)
59
+ define_method :"#{key}=" do |val|
60
+ send(:"#{key}_will_change!") unless val == @attributes[key]
61
+ @attributes[key] = val
62
+ end
63
+ end
64
+ end
65
+
66
+ define_attribute_methods names
67
+ end
68
+ end
69
+
70
+ def self.one(name, opts = {})
71
+ class_eval do
72
+ define_method(:"#{name}") do |*args|
73
+ options = opts.dup
74
+ klass = options.delete(:via) || Trello.const_get(name.to_s.camelize)
75
+ ident = options.delete(:using) || :id
76
+ path = options.delete(:path)
77
+
78
+ if path
79
+ client.find(path, self.send(ident))
80
+ else
81
+ klass.find(self.send(ident))
82
+ end
83
+ end
84
+ end
85
+ end
86
+
87
+ def self.many(name, opts = {})
88
+ class_eval do
89
+ define_method(:"#{name}") do |*args|
90
+ options = opts.dup
91
+ resource = options.delete(:in) || self.class.to_s.split("::").last.downcase.pluralize
92
+ klass = options.delete(:via) || Trello.const_get(name.to_s.singularize.camelize)
93
+ path = options.delete(:path) || name
94
+ params = options.merge(args[0] || {})
95
+
96
+ resources = client.find_many(klass, "/#{resource}/#{id}/#{path}", params)
97
+ MultiAssociation.new(self, resources).proxy
98
+ end
99
+ end
100
+ end
101
+
102
+ def self.client
103
+ Trello.client
104
+ end
105
+
106
+ register_attributes :id, readonly: [ :id ]
107
+
108
+ attr_writer :client
109
+
110
+ def initialize(fields = {})
111
+ update_fields(fields)
112
+ end
113
+
114
+ def update_fields(fields)
115
+ raise NotImplementedError, "#{self.class} does not implement update_fields."
116
+ end
117
+
118
+ # Refresh the contents of our object.
119
+ def refresh!
120
+ self.class.find(id)
121
+ end
122
+
123
+ # Two objects are equal if their _id_ methods are equal.
124
+ def ==(other)
125
+ id == other.id
126
+ end
127
+
128
+ def client
129
+ @client ||= self.class.client
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,201 @@
1
+ module Trello
2
+
3
+ # A board on Trello
4
+ #
5
+ # @!attribute [r] id
6
+ # @return [String]
7
+ # @!attribute [r] name
8
+ # @return [String]
9
+ # @!attribute [rw] description
10
+ # @return [String]
11
+ # @!attribute [rw] closed
12
+ # @return [Boolean]
13
+ # @!attribute [r] url
14
+ # @return [String]
15
+ # @!attribute [rw] organization_id
16
+ # @return [String] A 24-character hex string
17
+ # @!attribute [r] prefs
18
+ # @return [Hash] A 24-character hex string
19
+ class Board < BasicData
20
+ register_attributes :id, :name, :description, :closed, :starred, :url, :organization_id, :prefs, :last_activity_date,
21
+ readonly: [ :id, :url, :last_activity_date ]
22
+ validates_presence_of :id, :name
23
+ validates_length_of :name, in: 1..16384
24
+ validates_length_of :description, maximum: 16384
25
+
26
+ include HasActions
27
+
28
+ class << self
29
+ # Finds a board.
30
+ #
31
+ # @param [String] id Either the board's short ID (an alphanumeric string,
32
+ # found e.g. in the board's URL) or its long ID (a 24-character hex
33
+ # string.)
34
+ # @param [Hash] params
35
+ #
36
+ # @raise [Trello::Board] if a board with the given ID could not be found.
37
+ #
38
+ # @return [Trello::Board]
39
+ def find(id, params = {})
40
+ client.find(:board, id, params)
41
+ end
42
+
43
+ def create(fields)
44
+ data = {
45
+ 'name' => fields[:name],
46
+ 'desc' => fields[:description],
47
+ 'closed' => fields[:closed] || false,
48
+ 'starred' => fields[:starred] || false }
49
+ data.merge!('idOrganization' => fields[:organization_id]) if fields[:organization_id]
50
+ data.merge!('prefs' => fields[:prefs]) if fields[:prefs]
51
+ client.create(:board, data)
52
+ end
53
+
54
+ # @return [Array<Trello::Board>] all boards for the current user
55
+ def all
56
+ from_response client.get("/members/#{Member.find(:me).username}/boards")
57
+ end
58
+ end
59
+
60
+ def save
61
+ return update! if id
62
+
63
+ fields = { name: name }
64
+ fields.merge!(desc: description) if description
65
+ fields.merge!(idOrganization: organization_id) if organization_id
66
+ fields.merge!(flat_prefs)
67
+
68
+ from_response(client.post("/boards", fields))
69
+ end
70
+
71
+ def update!
72
+ fail "Cannot save new instance." unless self.id
73
+
74
+ @previously_changed = changes
75
+ @changed_attributes.clear
76
+
77
+ fields = {
78
+ name: attributes[:name],
79
+ description: attributes[:description],
80
+ closed: attributes[:closed],
81
+ starred: attributes[:starred],
82
+ idOrganization: attributes[:organization_id]
83
+ }
84
+ fields.merge!(flat_prefs)
85
+
86
+ from_response client.put("/boards/#{self.id}/", fields)
87
+ end
88
+
89
+ def update_fields(fields)
90
+ attributes[:id] = fields['id'] || fields[:id] if fields['id'] || fields[:id]
91
+ attributes[:name] = fields['name'] || fields[:name] if fields['name'] || fields[:name]
92
+ attributes[:description] = fields['desc'] || fields[:desc] if fields['desc'] || fields[:desc]
93
+ attributes[:closed] = fields['closed'] if fields.has_key?('closed')
94
+ attributes[:closed] = fields[:closed] if fields.has_key?(:closed)
95
+ attributes[:starred] = fields['starred'] if fields.has_key?('starred')
96
+ attributes[:starred] = fields[:starred] if fields.has_key?(:starred)
97
+ attributes[:url] = fields['url'] if fields['url']
98
+ attributes[:organization_id] = fields['idOrganization'] || fields[:organization_id] if fields['idOrganization'] || fields[:organization_id]
99
+ attributes[:prefs] = fields['prefs'] || fields[:prefs] || {}
100
+ attributes[:last_activity_date] = Time.iso8601(fields['dateLastActivity']) rescue nil
101
+ self
102
+ end
103
+
104
+ # @return [Boolean]
105
+ def closed?
106
+ attributes[:closed]
107
+ end
108
+
109
+ # @return [Boolean]
110
+ def starred?
111
+ attributes[:starred]
112
+ end
113
+
114
+ # @return [Boolean]
115
+ def has_lists?
116
+ lists.size > 0
117
+ end
118
+
119
+ # Find a card on this Board with the given ID.
120
+ # @return [Trello::Card]
121
+ def find_card(card_id)
122
+ Card.from_response client.get("/boards/#{self.id}/cards/#{card_id}")
123
+ end
124
+
125
+ # Add a member to this Board.
126
+ # type => [ :admin, :normal, :observer ]
127
+ def add_member(member, type = :normal)
128
+ client.put("/boards/#{self.id}/members/#{member.id}", { type: type })
129
+ end
130
+
131
+ # Remove a member of this Board.
132
+ def remove_member(member)
133
+ client.delete("/boards/#{self.id}/members/#{member.id}")
134
+ end
135
+
136
+ # Return all the cards on this board.
137
+ #
138
+ # This method, when called, can take a hash table with a filter key containing any
139
+ # of the following values:
140
+ # :filter => [ :none, :open, :closed, :all ] # default :open
141
+ many :cards, filter: :open
142
+
143
+ # Returns all the lists on this board.
144
+ #
145
+ # This method, when called, can take a hash table with a filter key containing any
146
+ # of the following values:
147
+ # :filter => [ :none, :open, :closed, :all ] # default :open
148
+ many :lists, filter: :open
149
+
150
+ # Returns an array of members who are associated with this board.
151
+ #
152
+ # This method, when called, can take a hash table with a filter key containing any
153
+ # of the following values:
154
+ # :filter => [ :none, :normal, :owners, :all ] # default :all
155
+ many :members, filter: :all
156
+
157
+ # Returns a reference to the organization this board belongs to.
158
+ one :organization, path: :organizations, using: :organization_id
159
+
160
+ def labels(params = {})
161
+ # Set the limit to as high as possible given there is no pagination in this API.
162
+ params[:limit] = 1000 unless params[:limit]
163
+ labels = Label.from_response client.get("/boards/#{id}/labels", params)
164
+ MultiAssociation.new(self, labels).proxy
165
+ end
166
+
167
+ def label_names
168
+ label_names = LabelName.from_response client.get("/boards/#{id}/labelnames")
169
+ MultiAssociation.new(self, label_names).proxy
170
+ end
171
+
172
+ # :nodoc:
173
+ def request_prefix
174
+ "/boards/#{id}"
175
+ end
176
+
177
+ private
178
+
179
+ # On creation
180
+ # https://trello.com/docs/api/board/#post-1-boards
181
+ # - permissionLevel
182
+ # - voting
183
+ # - comments
184
+ # - invitations
185
+ # - selfJoin
186
+ # - cardCovers
187
+ # - background
188
+ # - cardAging
189
+ #
190
+ # On update
191
+ # https://trello.com/docs/api/board/#put-1-boards-board-id
192
+ # Same as above plus:
193
+ # - calendarFeedEnabled
194
+ def flat_prefs
195
+ separator = id ? "/" : "_"
196
+ attributes[:prefs].inject({}) do |hash, (pref, v)|
197
+ hash.merge("prefs#{separator}#{pref}" => v)
198
+ end
199
+ end
200
+ end
201
+ end