yammer-client 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.
Files changed (67) hide show
  1. data/.gitignore +29 -0
  2. data/.travis.yml +9 -0
  3. data/.yardopts +10 -0
  4. data/CHANGELOG.md +0 -0
  5. data/Gemfile +13 -0
  6. data/LICENSE.md +20 -0
  7. data/README.md +265 -0
  8. data/Rakefile +12 -0
  9. data/certs/tiabas-public.pem +21 -0
  10. data/lib/yammer.rb +28 -0
  11. data/lib/yammer/api.rb +10 -0
  12. data/lib/yammer/api/autocomplete.rb +25 -0
  13. data/lib/yammer/api/group.rb +78 -0
  14. data/lib/yammer/api/group_membership.rb +32 -0
  15. data/lib/yammer/api/message.rb +196 -0
  16. data/lib/yammer/api/network.rb +21 -0
  17. data/lib/yammer/api/notification.rb +18 -0
  18. data/lib/yammer/api/search.rb +28 -0
  19. data/lib/yammer/api/thread.rb +23 -0
  20. data/lib/yammer/api/topic.rb +21 -0
  21. data/lib/yammer/api/user.rb +156 -0
  22. data/lib/yammer/client.rb +101 -0
  23. data/lib/yammer/configurable.rb +46 -0
  24. data/lib/yammer/error.rb +61 -0
  25. data/lib/yammer/http_connection.rb +184 -0
  26. data/lib/yammer/identity_map.rb +42 -0
  27. data/lib/yammer/model.rb +6 -0
  28. data/lib/yammer/model/base.rb +133 -0
  29. data/lib/yammer/model/group.rb +13 -0
  30. data/lib/yammer/model/group_membership.rb +11 -0
  31. data/lib/yammer/model/message.rb +17 -0
  32. data/lib/yammer/model/message_body.rb +13 -0
  33. data/lib/yammer/model/thread.rb +44 -0
  34. data/lib/yammer/model/user.rb +46 -0
  35. data/lib/yammer/response.rb +43 -0
  36. data/lib/yammer/version.rb +18 -0
  37. data/spec/api/autocomplete_spec.rb +23 -0
  38. data/spec/api/group_membership_spec.rb +30 -0
  39. data/spec/api/group_spec.rb +58 -0
  40. data/spec/api/message_spec.rb +118 -0
  41. data/spec/api/network_spec.rb +23 -0
  42. data/spec/api/notification_spec.rb +23 -0
  43. data/spec/api/search_spec.rb +23 -0
  44. data/spec/api/thread_spec.rb +23 -0
  45. data/spec/api/topic_spec.rb +23 -0
  46. data/spec/api/user_spec.rb +76 -0
  47. data/spec/client_spec.rb +208 -0
  48. data/spec/connection_spec.rb +280 -0
  49. data/spec/error_spec.rb +69 -0
  50. data/spec/fixtures/group.json +1 -0
  51. data/spec/fixtures/message.json +1 -0
  52. data/spec/fixtures/messages_in_thread.json +1 -0
  53. data/spec/fixtures/portal_thread.json +1 -0
  54. data/spec/fixtures/private_thread.json +1 -0
  55. data/spec/fixtures/public_thread.json +1 -0
  56. data/spec/fixtures/user.json +1 -0
  57. data/spec/identity_map_spec.rb +108 -0
  58. data/spec/model/base_spec.rb +155 -0
  59. data/spec/model/group_membership_spec.rb +27 -0
  60. data/spec/model/group_spec.rb +44 -0
  61. data/spec/model/message_spec.rb +45 -0
  62. data/spec/model/thread_spec.rb +66 -0
  63. data/spec/model/user_spec.rb +150 -0
  64. data/spec/response_spec.rb +66 -0
  65. data/spec/spec_helper.rb +42 -0
  66. data/yammer.gemspec +29 -0
  67. metadata +270 -0
@@ -0,0 +1,101 @@
1
+ require 'multi_json'
2
+ require 'yammer/configurable'
3
+ require 'yammer/http_connection'
4
+ require 'yammer/api'
5
+
6
+ module Yammer
7
+ class Client
8
+ include Yammer::Configurable
9
+ include Yammer::Api::User
10
+ include Yammer::Api::Group
11
+ include Yammer::Api::GroupMembership
12
+ include Yammer::Api::Message
13
+ include Yammer::Api::Thread
14
+ include Yammer::Api::Topic
15
+ include Yammer::Api::Network
16
+ include Yammer::Api::Search
17
+ include Yammer::Api::Notification
18
+ include Yammer::Api::Autocomplete
19
+
20
+ attr_writer :connection_options
21
+ attr_accessor :site_url, :http_adapter
22
+
23
+ def initialize(opts={})
24
+ Yammer::Configurable.keys.each do |key|
25
+ instance_variable_set(:"@#{key}", opts.fetch(key, Yammer.instance_variable_get(:"@#{key}")))
26
+ end
27
+ end
28
+
29
+ # @return [Hash] the options used to setup the http connection
30
+ def connection_options
31
+ @connection_options ||= {}
32
+ end
33
+
34
+ # set the url to be used for creating an http connection
35
+ # @param url [string]
36
+ def site_url=(url)
37
+ @connection = nil
38
+ @site_url = url
39
+ end
40
+
41
+ # (see #request)
42
+ # @note makes a GET request
43
+ def get(path, params={})
44
+ request(:get, path, params)
45
+ end
46
+
47
+ # (see #request)
48
+ # @note makes a PUT request
49
+ def put(path, params={})
50
+ request(:put, path, params)
51
+ end
52
+
53
+ # (see #request)
54
+ # @note makes a POST request
55
+ def post(path, params={})
56
+ request(:post, path, params)
57
+ end
58
+
59
+ # (see #request)
60
+ # @note makes a DELETE request
61
+ def delete(path, params={})
62
+ request(:delete, path, params)
63
+ end
64
+
65
+ private
66
+
67
+ # Makes an HTTP request using the provided parameters
68
+ # @raise [Yammer::Error::Unauthorized]
69
+ # @param method [string]
70
+ # @param path [string]
71
+ # @param params [Hash]
72
+ # @param opts [Hash]
73
+ # @return [Yammer::Response]
74
+ # @!visibility private
75
+ def request(method, path, params={}, opts={})
76
+ # Log request here
77
+ response = connection.send_request(method, path, {
78
+ :params => params,
79
+ :headers => {
80
+ 'Authorization' => "Bearer #{@access_token}"
81
+ }
82
+ })
83
+ result = Yammer::Response.new(response)
84
+ status = result.code
85
+ case status
86
+ when 200...400
87
+ result
88
+ else
89
+ message = result.empty? ? '' : result.body[:response][:message]
90
+ raise Yammer::Error.from_status(status).new(message)
91
+ end
92
+ end
93
+
94
+ # returns an instance of the http adapter
95
+ # if none is specified, the default is Yammer::HttpConnection
96
+ # @!visibility private
97
+ def connection
98
+ @connection ||= @http_adapter.new(@site_url, connection_options)
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,46 @@
1
+ module Yammer
2
+ module Configurable
3
+
4
+ ENDPOINT = 'https://www.yammer.com' unless defined? ENDPOINT
5
+ HTTP_CONNECTION = Yammer::HttpConnection unless defined? HTTP_CONNECTION
6
+
7
+ attr_accessor :hostname, :client_id, :client_secret, :access_token, :site_url, :http_adapter,
8
+ :connection_options
9
+
10
+ # Return a hash with the default options
11
+ # @return [Hash]
12
+ def self.default_options
13
+ {
14
+ :site_url => ENDPOINT,
15
+ :client_id => ENV['YAMMER_CLIENT_ID'],
16
+ :client_secret => ENV['YAMMER_CLIENT_SECRET'],
17
+ :access_token => ENV['YAMMER_ACCESS_TOKEN'],
18
+ :http_adapter => HTTP_CONNECTION,
19
+ :connection_options => { :max_redirects => 5, :use_ssl => true }
20
+ }
21
+ end
22
+
23
+ # @return [Array<String>
24
+ def self.keys
25
+ Yammer::Configurable.default_options.keys
26
+ end
27
+
28
+ # @return [Hash]
29
+ def options
30
+ Hash[Yammer::Configurable.keys.map{|key| [key, instance_variable_get(:"@#{key}")]}]
31
+ end
32
+
33
+ def reset!
34
+ Yammer::Configurable.keys.each do |key|
35
+ instance_variable_set(:"@#{key}", Yammer::Configurable.default_options[key.to_sym])
36
+ end
37
+ self
38
+ end
39
+
40
+ # Convenience method to allow configuration options to be set in a block
41
+ def configure
42
+ yield self
43
+ self
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,61 @@
1
+ module Yammer
2
+ module Error
3
+
4
+ class << self
5
+ def from_status(status=nil)
6
+ case status
7
+ when 400
8
+ BadRequest
9
+ when 401
10
+ Unauthorized
11
+ when 403
12
+ Forbidden
13
+ when 404
14
+ NotFound
15
+ when 406
16
+ NotAcceptable
17
+ when 429
18
+ RateLimitExceeded
19
+ when 500
20
+ InternalServerError
21
+ when 502
22
+ BadGateway
23
+ when 503
24
+ ServiceUnavailable
25
+ else
26
+ ApiError
27
+ end
28
+ end
29
+ end
30
+
31
+ # Raised when Yammer returns unknown HTTP status code
32
+ class ApiError < StandardError; end
33
+
34
+ # Raised when Yammer returns the HTTP status code 400
35
+ class BadRequest < ApiError; end
36
+
37
+ # Raised when Yammer returns the HTTP status code 401
38
+ class Unauthorized < ApiError; end
39
+
40
+ # Raised when Yammer returns the HTTP status code 403
41
+ class Forbidden < ApiError; end
42
+
43
+ # Raised when Yammer returns the HTTP status code 404
44
+ class NotFound < ApiError; end
45
+
46
+ # Raised when Yammer returns the HTTP status code 406
47
+ class NotAcceptable < ApiError; end
48
+
49
+ # Raised when Yammer returns the HTTP status code 429
50
+ class RateLimitExceeded < ApiError; end
51
+
52
+ # Raised when Yammer returns the HTTP status code 500
53
+ class InternalServerError < ApiError; end
54
+
55
+ # Raised when Yammer returns the HTTP status code 502
56
+ class BadGateway < ApiError; end
57
+
58
+ # Raised when Yammer returns the HTTP status code 503
59
+ class ServiceUnavailable < ApiError; end
60
+ end
61
+ end
@@ -0,0 +1,184 @@
1
+ begin
2
+ require 'net/https'
3
+ rescue LoadError
4
+ warn "Warning: no such file to load -- net/https. Make sure openssl is installed if you want ssl support"
5
+ require 'net/http'
6
+ end
7
+ require 'zlib'
8
+ require 'addressable/uri'
9
+
10
+ module Yammer
11
+ class HttpConnection
12
+
13
+ class UnhandledHTTPMethodError < StandardError; end
14
+ class UnsupportedSchemeError < StandardError; end
15
+
16
+ NET_HTTP_EXCEPTIONS = [
17
+ EOFError,
18
+ Errno::ECONNABORTED,
19
+ Errno::ECONNREFUSED,
20
+ Errno::ECONNRESET,
21
+ Errno::EINVAL,
22
+ Net::HTTPBadResponse,
23
+ Net::HTTPHeaderSyntaxError,
24
+ Net::ProtocolError,
25
+ SocketError,
26
+ Zlib::GzipFile::Error,
27
+ ] unless defined? NET_HTTP_EXCEPTIONS
28
+
29
+ attr_accessor :config, :scheme, :host, :port, :max_redirects, :ssl,
30
+ :user_agent, :accept, :max_redirects, :headers, :uri
31
+
32
+ def self.default_options
33
+ {
34
+ :headers => {
35
+ 'Accept' => 'application/json',
36
+ 'User-Agent' => "Yammer Ruby Gem #{Yammer::Version}"
37
+ },
38
+ :ssl => {:verify => true},
39
+ :max_redirects => 5
40
+ }
41
+ end
42
+
43
+ def initialize(url, options={})
44
+ @uri = Addressable::URI.parse(url)
45
+ self.class.default_options.keys.each do |key|
46
+ instance_variable_set(:"@#{key}", options.fetch(key, self.class.default_options[key]))
47
+ end
48
+ end
49
+
50
+ def default_headers
51
+ self.class.default_options[:headers]
52
+ end
53
+
54
+ def scheme=(scheme)
55
+ unless ['http', 'https'].include? scheme
56
+ raise UnsupportedSchemeError.new "#{scheme} is not supported, only http and https"
57
+ end
58
+ @scheme = scheme
59
+ end
60
+
61
+ def scheme
62
+ @scheme ||= @uri.scheme
63
+ end
64
+
65
+ def host
66
+ @host ||= @uri.host
67
+ end
68
+
69
+ def port
70
+ _port = ssl? ? 443 : 80
71
+ @port = @uri.port || _port
72
+ end
73
+
74
+ def absolute_url(path='')
75
+ "#{scheme}://#{host}#{path}"
76
+ end
77
+
78
+ def ssl?
79
+ scheme == "https" ? true : false
80
+ end
81
+
82
+ def ssl=(opts)
83
+ raise "Expected Hash but got #{opts.class.name}" unless opts.is_a?(Hash)
84
+ @ssl.merge!(opts)
85
+ end
86
+
87
+ def http_connection(opts={})
88
+ _host = opts[:host] || host
89
+ _port = opts[:port] || port
90
+ _scheme = opts[:scheme] || scheme
91
+
92
+ @http_client = Net::HTTP.new(_host, _port)
93
+
94
+ configure_ssl(@http_client) if _scheme == 'https'
95
+
96
+ @http_client
97
+ end
98
+
99
+ def send_request(method, path, opts={})
100
+ headers = @headers.merge(opts.fetch(:headers, {}))
101
+ params = opts[:params] || {}
102
+ query = Addressable::URI.form_encode(params)
103
+ method = method.to_sym
104
+ normalized_path = query.empty? ? path : [path, query].join("?")
105
+ client = http_connection(opts.fetch(:connection_options, {}))
106
+
107
+ if (method == :post || method == :put)
108
+ headers['Content-Type'] ||= 'application/x-www-form-urlencoded'
109
+ end
110
+
111
+ case method
112
+ when :get, :delete
113
+ response = client.send(method, normalized_path, headers)
114
+ when :post, :put
115
+ response = client.send(method, path, query, headers)
116
+ else
117
+ raise UnhandledHTTPMethodError.new("Unsupported HTTP method, #{method}")
118
+ end
119
+
120
+ status = response.code.to_i
121
+
122
+ case status
123
+ when 301, 302, 303, 307
124
+ unless redirect_limit_reached?
125
+ if status == 303
126
+ method = :get
127
+ params = nil
128
+ headers.delete('Content-Type')
129
+ end
130
+ redirect_uri = Addressable::URI.parse(response.header['Location'])
131
+ conn = {
132
+ :scheme => redirect_uri.scheme,
133
+ :host => redirect_uri.host,
134
+ :port => redirect_uri.port
135
+ }
136
+ return send_request(method, redirect_uri.path, :params => params, :headers => headers, :connection_options => conn)
137
+ end
138
+ when 100..599
139
+ @redirect_count = 0
140
+ else
141
+ raise "Unhandled status code value of #{response.code}"
142
+ end
143
+ response
144
+ rescue *NET_HTTP_EXCEPTIONS
145
+ raise "Error::ConnectionFailed, #{$!.backtrace}"
146
+ end
147
+
148
+ private
149
+
150
+ def configure_ssl(http)
151
+ http.use_ssl = true
152
+ http.verify_mode = ssl_verify_mode
153
+ http.cert_store = ssl_cert_store
154
+
155
+ http.cert = ssl[:client_cert] if ssl[:client_cert]
156
+ http.key = ssl[:client_key] if ssl[:client_key]
157
+ http.ca_file = ssl[:ca_file] if ssl[:ca_file]
158
+ http.ca_path = ssl[:ca_path] if ssl[:ca_path]
159
+ http.verify_depth = ssl[:verify_depth] if ssl[:verify_depth]
160
+ http.ssl_version = ssl[:version] if ssl[:version]
161
+ end
162
+
163
+ def ssl_verify_mode
164
+ if ssl.fetch(:verify, true)
165
+ OpenSSL::SSL::VERIFY_PEER
166
+ else
167
+ OpenSSL::SSL::VERIFY_NONE
168
+ end
169
+ end
170
+
171
+ def ssl_cert_store
172
+ return ssl[:cert_store] if ssl[:cert_store]
173
+ cert_store = OpenSSL::X509::Store.new
174
+ cert_store.set_default_paths
175
+ cert_store
176
+ end
177
+
178
+ def redirect_limit_reached?
179
+ @redirect_count ||= 0
180
+ @redirect_count += 1
181
+ @redirect_count > @max_redirects
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,42 @@
1
+ module Yammer
2
+ class IdentityMap
3
+
4
+ class InvalidKeyError < StandardError; end
5
+
6
+ def initialize
7
+ @map = {}
8
+ @size = 0
9
+ end
10
+
11
+ # @note retrives key from identity map
12
+ # @return [Hash]
13
+ # @param key [string]
14
+ # @param default [Hash]
15
+ def get(key, default=nil)
16
+ @map["#{key}"] || default
17
+ end
18
+
19
+ # @note inserts a hash of attributes into identity map
20
+ # @return [Hash]
21
+ # @param key [string]
22
+ # @param value [Hash]
23
+ def put(key, value)
24
+ if key.nil? || key.empty?
25
+ raise InvalidKeyError.new
26
+ end
27
+ @map["#{key}"] = value
28
+ end
29
+
30
+ # @note returns the current size of identity map
31
+ # @return [Integer]
32
+ def size
33
+ @map.keys.count
34
+ end
35
+
36
+ # clears the entire identity map
37
+ # @return [Hash]
38
+ def purge!
39
+ @map = {}
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,6 @@
1
+ require 'yammer/model/base'
2
+ require 'yammer/model/user'
3
+ require 'yammer/model/group'
4
+ require 'yammer/model/group_membership'
5
+ require 'yammer/model/message'
6
+ require 'yammer/model/thread'