ruby-trello-czuger 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +182 -0
  3. data/lib/trello.rb +163 -0
  4. data/lib/trello/action.rb +68 -0
  5. data/lib/trello/association.rb +14 -0
  6. data/lib/trello/association_proxy.rb +42 -0
  7. data/lib/trello/attachment.rb +40 -0
  8. data/lib/trello/authorization.rb +187 -0
  9. data/lib/trello/basic_data.rb +132 -0
  10. data/lib/trello/board.rb +211 -0
  11. data/lib/trello/card.rb +467 -0
  12. data/lib/trello/checklist.rb +143 -0
  13. data/lib/trello/client.rb +120 -0
  14. data/lib/trello/comment.rb +62 -0
  15. data/lib/trello/configuration.rb +68 -0
  16. data/lib/trello/core_ext/array.rb +6 -0
  17. data/lib/trello/core_ext/hash.rb +6 -0
  18. data/lib/trello/core_ext/string.rb +6 -0
  19. data/lib/trello/cover_image.rb +8 -0
  20. data/lib/trello/has_actions.rb +9 -0
  21. data/lib/trello/item.rb +37 -0
  22. data/lib/trello/item_state.rb +30 -0
  23. data/lib/trello/json_utils.rb +64 -0
  24. data/lib/trello/label.rb +108 -0
  25. data/lib/trello/label_name.rb +31 -0
  26. data/lib/trello/list.rb +114 -0
  27. data/lib/trello/member.rb +112 -0
  28. data/lib/trello/multi_association.rb +12 -0
  29. data/lib/trello/net.rb +39 -0
  30. data/lib/trello/notification.rb +61 -0
  31. data/lib/trello/organization.rb +68 -0
  32. data/lib/trello/plugin_datum.rb +34 -0
  33. data/lib/trello/token.rb +37 -0
  34. data/lib/trello/webhook.rb +103 -0
  35. data/spec/action_spec.rb +149 -0
  36. data/spec/array_spec.rb +13 -0
  37. data/spec/association_spec.rb +26 -0
  38. data/spec/basic_auth_policy_spec.rb +51 -0
  39. data/spec/board_spec.rb +442 -0
  40. data/spec/card_spec.rb +822 -0
  41. data/spec/checklist_spec.rb +296 -0
  42. data/spec/client_spec.rb +257 -0
  43. data/spec/configuration_spec.rb +95 -0
  44. data/spec/hash_spec.rb +15 -0
  45. data/spec/integration/how_to_authorize_spec.rb +53 -0
  46. data/spec/integration/how_to_use_boards_spec.rb +48 -0
  47. data/spec/integration/integration_test.rb +40 -0
  48. data/spec/item_spec.rb +75 -0
  49. data/spec/json_utils_spec.rb +73 -0
  50. data/spec/label_spec.rb +205 -0
  51. data/spec/list_spec.rb +253 -0
  52. data/spec/member_spec.rb +159 -0
  53. data/spec/notification_spec.rb +143 -0
  54. data/spec/oauth_policy_spec.rb +160 -0
  55. data/spec/organization_spec.rb +71 -0
  56. data/spec/spec_helper.rb +435 -0
  57. data/spec/string_spec.rb +55 -0
  58. data/spec/token_spec.rb +89 -0
  59. data/spec/trello_spec.rb +134 -0
  60. data/spec/webhook_spec.rb +130 -0
  61. metadata +200 -0
@@ -0,0 +1,40 @@
1
+ module Trello
2
+ # A file or url that is linked to a Trello card
3
+ #
4
+ # @!attribute id
5
+ # @return [String]
6
+ # @!attribute name
7
+ # @return [String]
8
+ # @!attribute url
9
+ # @return [String]
10
+ # @!attribute pos
11
+ # @return [Float]
12
+ # @!attribute bytes
13
+ # @return [Fixnum]
14
+ # @!attribute date
15
+ # @return [Datetime]
16
+ # @!attribute is_upload
17
+ # @return [Boolean]
18
+ # @!attribute mime_type
19
+ # @return [String]
20
+ class Attachment < BasicData
21
+ register_attributes :name, :id, :pos, :url, :bytes, :member_id, :date, :is_upload, :mime_type, :previews
22
+ # Update the fields of an attachment.
23
+ #
24
+ # Supply a hash of stringkeyed data retrieved from the Trello API representing
25
+ # an attachment.
26
+ def update_fields(fields)
27
+ attributes[:name] = fields['name'] || attributes[:name]
28
+ attributes[:id] = fields['id'] || attributes[:id]
29
+ attributes[:pos] = fields['pos'] || attributes[:pos]
30
+ attributes[:url] = fields['url'] || attributes[:url]
31
+ attributes[:bytes] = fields['bytes'].to_i || attributes[:bytes]
32
+ attributes[:member_id] = fields['idMember'] || attributes[:member_id]
33
+ attributes[:date] = Time.parse(fields['date']).presence || attributes[:date]
34
+ attributes[:is_upload] = fields['isUpload'] if fields.has_key?('isUpload')
35
+ attributes[:mime_type] = fields['mimeType'] || attributes[:mime_type]
36
+ attributes[:previews] = fields['previews'] if fields.has_key?('previews')
37
+ self
38
+ end
39
+ end
40
+ end
@@ -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,211 @@
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, :admins, :all ] # default :all
155
+ many :members, filter: :all
156
+
157
+ # Returns a list of checklists associated with the board.
158
+ #
159
+ # The options hash may have a filter key which can have its value set as any
160
+ # of the following values:
161
+ # :filter => [ :none, :all ] # default :all
162
+ many :checklists, filter: :all
163
+
164
+ # Returns a reference to the organization this board belongs to.
165
+ one :organization, path: :organizations, using: :organization_id
166
+
167
+ # Returns a list of plugins associated with the board
168
+ many :plugin_data, path: "pluginData"
169
+
170
+ def labels(params = {})
171
+ # Set the limit to as high as possible given there is no pagination in this API.
172
+ params[:limit] = 1000 unless params[:limit]
173
+ labels = Label.from_response client.get("/boards/#{id}/labels", params)
174
+ MultiAssociation.new(self, labels).proxy
175
+ end
176
+
177
+ def label_names
178
+ label_names = LabelName.from_response client.get("/boards/#{id}/labelnames")
179
+ MultiAssociation.new(self, label_names).proxy
180
+ end
181
+
182
+ # :nodoc:
183
+ def request_prefix
184
+ "/boards/#{id}"
185
+ end
186
+
187
+ private
188
+
189
+ # On creation
190
+ # https://trello.com/docs/api/board/#post-1-boards
191
+ # - permissionLevel
192
+ # - voting
193
+ # - comments
194
+ # - invitations
195
+ # - selfJoin
196
+ # - cardCovers
197
+ # - background
198
+ # - cardAging
199
+ #
200
+ # On update
201
+ # https://trello.com/docs/api/board/#put-1-boards-board-id
202
+ # Same as above plus:
203
+ # - calendarFeedEnabled
204
+ def flat_prefs
205
+ separator = id ? "/" : "_"
206
+ attributes[:prefs].inject({}) do |hash, (pref, v)|
207
+ hash.merge("prefs#{separator}#{pref}" => v)
208
+ end
209
+ end
210
+ end
211
+ end