podio 0.1.0 → 0.2.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 +32 -0
- data/Rakefile +13 -1
- data/lib/podio.rb +39 -10
- data/lib/podio/areas/app_store.rb +45 -0
- data/lib/podio/areas/application.rb +19 -0
- data/lib/podio/areas/contact.rb +36 -0
- data/lib/podio/areas/file.rb +23 -0
- data/lib/podio/areas/form.rb +11 -0
- data/lib/podio/areas/item.rb +32 -0
- data/lib/podio/areas/organization.rb +67 -0
- data/lib/podio/areas/organization_member.rb +35 -0
- data/lib/podio/areas/search.rb +15 -0
- data/lib/podio/areas/space.rb +46 -0
- data/lib/podio/areas/user.rb +19 -0
- data/lib/podio/areas/user_status.rb +11 -0
- data/lib/podio/areas/widget.rb +45 -0
- data/lib/podio/client.rb +51 -22
- data/lib/podio/error.rb +10 -0
- data/lib/podio/middleware/date_conversion.rb +40 -0
- data/lib/podio/middleware/error_response.rb +6 -6
- data/lib/podio/middleware/logger.rb +7 -1
- data/lib/podio/middleware/oauth2.rb +2 -2
- data/lib/podio/middleware/response_recorder.rb +16 -0
- data/lib/podio/response_wrapper.rb +15 -0
- data/lib/podio/version.rb +1 -1
- data/podio.gemspec +2 -4
- data/test/app_store_test.rb +40 -0
- data/test/client_test.rb +50 -57
- data/test/contact_test.rb +26 -0
- data/test/item_test.rb +37 -0
- data/test/organization_test.rb +20 -0
- data/test/space_test.rb +17 -0
- data/test/test_helper.rb +44 -46
- data/test/widget_test.rb +55 -0
- metadata +36 -31
- data/lib/podio/middleware/mashify.rb +0 -27
@@ -0,0 +1,19 @@
|
|
1
|
+
module Podio
|
2
|
+
module User
|
3
|
+
include Podio::ResponseWrapper
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def current
|
7
|
+
member Podio.connection.get("/user/").body
|
8
|
+
end
|
9
|
+
|
10
|
+
def create(token, attributes)
|
11
|
+
response = Podio.connection.post do |req|
|
12
|
+
req.url '/user/'
|
13
|
+
req.body = attributes.merge(:token => token)
|
14
|
+
end
|
15
|
+
|
16
|
+
response.body['user_id']
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Podio
|
2
|
+
module Widget
|
3
|
+
include Podio::ResponseWrapper
|
4
|
+
extend self
|
5
|
+
|
6
|
+
def create(ref_type, ref_id, attributes)
|
7
|
+
response = Podio.connection.post do |req|
|
8
|
+
req.url "/widget/#{ref_type}/#{ref_id}/"
|
9
|
+
req.body = attributes
|
10
|
+
end
|
11
|
+
|
12
|
+
response.body['widget_id']
|
13
|
+
end
|
14
|
+
|
15
|
+
def update(id, attributes)
|
16
|
+
response = Podio.connection.put do |req|
|
17
|
+
req.url "/widget/#{id}"
|
18
|
+
req.body = attributes
|
19
|
+
end
|
20
|
+
|
21
|
+
response.status
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(id)
|
25
|
+
Podio.connection.delete("/widget/#{id}").status
|
26
|
+
end
|
27
|
+
|
28
|
+
def update_order(ref_type, ref_id, widget_list)
|
29
|
+
response = Podio.connection.put do |req|
|
30
|
+
req.url "/widget/#{ref_type}/#{ref_id}/order"
|
31
|
+
req.body = widget_list
|
32
|
+
end
|
33
|
+
|
34
|
+
response.status
|
35
|
+
end
|
36
|
+
|
37
|
+
def find(id)
|
38
|
+
member Podio.connection.get("/widget/#{id}").body
|
39
|
+
end
|
40
|
+
|
41
|
+
def find_all_for_reference(ref_type, ref_id)
|
42
|
+
list Podio.connection.get("/widget/#{ref_type}/#{ref_id}/display/").body
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
data/lib/podio/client.rb
CHANGED
@@ -1,13 +1,7 @@
|
|
1
|
-
require 'podio/middleware/logger'
|
2
|
-
require 'podio/middleware/oauth2'
|
3
|
-
require 'podio/middleware/mashify'
|
4
|
-
require 'podio/middleware/podio_api'
|
5
|
-
require 'podio/middleware/yajl_response'
|
6
|
-
require 'podio/middleware/error_response'
|
7
|
-
|
8
1
|
module Podio
|
9
2
|
class Client
|
10
|
-
attr_reader :api_url, :api_key, :api_secret, :debug, :
|
3
|
+
attr_reader :api_url, :api_key, :api_secret, :debug, :connection
|
4
|
+
attr_accessor :oauth_token, :stubs
|
11
5
|
|
12
6
|
def initialize(options = {})
|
13
7
|
@api_url = options[:api_url] || Podio.api_url || 'https://api.podio.com'
|
@@ -16,12 +10,23 @@ module Podio
|
|
16
10
|
@debug = options[:debug] || Podio.debug
|
17
11
|
@oauth_token = options[:oauth_token]
|
18
12
|
|
19
|
-
|
20
|
-
|
13
|
+
if options[:enable_stubs]
|
14
|
+
@enable_stubs = true
|
15
|
+
@stubs = Faraday::Adapter::Test::Stubs.new
|
16
|
+
end
|
17
|
+
@test_mode = options[:test_mode]
|
18
|
+
@record_mode = options[:record_mode]
|
19
|
+
|
20
|
+
setup_connections
|
21
|
+
end
|
22
|
+
|
23
|
+
def reset
|
24
|
+
setup_connections
|
21
25
|
end
|
22
26
|
|
27
|
+
# Sign in as a user
|
23
28
|
def get_access_token(username, password)
|
24
|
-
response =
|
29
|
+
response = @oauth_connection.post do |req|
|
25
30
|
req.url '/oauth/token', :grant_type => 'password', :client_id => api_key, :client_secret => api_secret, :username => username, :password => password
|
26
31
|
end
|
27
32
|
|
@@ -29,6 +34,17 @@ module Podio
|
|
29
34
|
configure_oauth
|
30
35
|
@oauth_token
|
31
36
|
end
|
37
|
+
|
38
|
+
# Sign in as an app
|
39
|
+
def get_access_token_as_app(app_id, app_token)
|
40
|
+
response = @oauth_connection.post do |req|
|
41
|
+
req.url '/oauth/token', :grant_type => 'app', :client_id => api_key, :client_secret => api_secret, :app_id => app_id, :app_token => app_token
|
42
|
+
end
|
43
|
+
|
44
|
+
@oauth_token = OAuthToken.new(response.body)
|
45
|
+
configure_oauth
|
46
|
+
@oauth_token
|
47
|
+
end
|
32
48
|
|
33
49
|
def refresh_access_token
|
34
50
|
response = @oauth_connection.post do |req|
|
@@ -39,37 +55,50 @@ module Podio
|
|
39
55
|
configure_oauth
|
40
56
|
end
|
41
57
|
|
58
|
+
def configured_headers
|
59
|
+
headers = {}
|
60
|
+
headers['User-Agent'] = 'Podio Ruby Library'
|
61
|
+
headers['authorization'] = "OAuth2 #{oauth_token.access_token}" if oauth_token
|
62
|
+
headers['X-Podio-Dry-Run'] = '1' if @test_mode
|
63
|
+
|
64
|
+
headers
|
65
|
+
end
|
66
|
+
|
42
67
|
private
|
43
68
|
|
44
|
-
def
|
45
|
-
|
46
|
-
|
69
|
+
def setup_connections
|
70
|
+
@connection = configure_connection
|
71
|
+
@oauth_connection = configure_oauth_connection
|
72
|
+
end
|
47
73
|
|
48
|
-
|
74
|
+
def configure_connection
|
75
|
+
Faraday::Connection.new(:url => api_url, :headers => configured_headers, :request => {:client => self}) do |builder|
|
49
76
|
builder.use Faraday::Request::Yajl
|
50
77
|
builder.use Middleware::PodioApi
|
51
78
|
builder.use Middleware::OAuth2
|
52
79
|
builder.use Middleware::Logger
|
53
|
-
builder.adapter
|
80
|
+
builder.adapter(*default_adapter)
|
81
|
+
builder.use Middleware::ResponseRecorder if @record_mode
|
54
82
|
builder.use Middleware::YajlResponse
|
55
|
-
builder.use Middleware::Mashify
|
56
83
|
builder.use Middleware::ErrorResponse
|
84
|
+
builder.use Middleware::DateConversion
|
57
85
|
end
|
58
86
|
end
|
59
87
|
|
88
|
+
def default_adapter
|
89
|
+
@enable_stubs ? [:test, @stubs] : Faraday.default_adapter
|
90
|
+
end
|
91
|
+
|
60
92
|
def configure_oauth_connection
|
61
93
|
conn = @connection.dup
|
62
94
|
conn.options[:client] = self
|
63
|
-
conn.
|
95
|
+
conn.headers.delete('authorization')
|
96
|
+
conn.headers.delete('X-Podio-Dry-Run') if @test_mode # oauth requests don't really work well in test mode
|
64
97
|
conn
|
65
98
|
end
|
66
99
|
|
67
100
|
def configure_oauth
|
68
101
|
@connection = configure_connection
|
69
102
|
end
|
70
|
-
|
71
|
-
def default_headers
|
72
|
-
{ :user_agent => 'Podio Ruby Library' }
|
73
|
-
end
|
74
103
|
end
|
75
104
|
end
|
data/lib/podio/error.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
module Podio
|
2
|
+
module Error
|
3
|
+
class TokenExpired < StandardError; end
|
4
|
+
class AuthorizationError < StandardError; end
|
5
|
+
class BadRequestError < StandardError; end
|
6
|
+
class ServerError < StandardError; end
|
7
|
+
class NotFoundError < StandardError; end
|
8
|
+
class GoneError < StandardError; end
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Podio
|
2
|
+
module Middleware
|
3
|
+
class DateConversion < Faraday::Response::Middleware
|
4
|
+
def self.register_on_complete(env)
|
5
|
+
env[:response].on_complete do |finished_env|
|
6
|
+
convert_dates(finished_env[:body])
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
# Converts all attributes ending with "_on" to datetime and ending with "date" to date
|
11
|
+
def self.convert_dates(body)
|
12
|
+
[body].flatten.compact.each do |hash|
|
13
|
+
hash.each do |key, value|
|
14
|
+
if value.is_a?(Hash)
|
15
|
+
self.convert_dates(value)
|
16
|
+
elsif value.is_a?(Array)
|
17
|
+
value.each_with_index { |item, index| hash[key][index] = self.convert_field(key, item) }
|
18
|
+
else
|
19
|
+
hash[key] = self.convert_field(key, value)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def self.convert_field(key, value)
|
28
|
+
if key.present?
|
29
|
+
if key[-3..-1] == "_on"
|
30
|
+
return value.try(:to_s).try(:to_datetime)
|
31
|
+
elsif key[-4..-1] == "date"
|
32
|
+
return value.try(:to_s).try(:to_date)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
value
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -7,21 +7,21 @@ module Podio
|
|
7
7
|
env[:response].on_complete do |finished_env|
|
8
8
|
case finished_env[:status]
|
9
9
|
when 400
|
10
|
-
raise Error::BadRequestError, finished_env[:body]
|
10
|
+
raise Error::BadRequestError, finished_env[:body]
|
11
11
|
when 401
|
12
|
-
if finished_env[:body]['
|
13
|
-
raise Error::TokenExpired, finished_env[:body].
|
12
|
+
if finished_env[:body]['error_description'] =~ /expired_token/
|
13
|
+
raise Error::TokenExpired, finished_env[:body].inspect
|
14
14
|
else
|
15
|
-
raise Error::AuthorizationError, finished_env[:body].
|
15
|
+
raise Error::AuthorizationError, finished_env[:body].inspect
|
16
16
|
end
|
17
17
|
when 403
|
18
|
-
raise Error::AuthorizationError, finished_env[:body].
|
18
|
+
raise Error::AuthorizationError, finished_env[:body].inspect
|
19
19
|
when 404
|
20
20
|
raise Error::NotFoundError, "#{finished_env[:method].to_s.upcase} #{finished_env[:url]}"
|
21
21
|
when 410
|
22
22
|
raise Error::GoneError, "#{finished_env[:method].to_s.upcase} #{finished_env[:url]}"
|
23
23
|
when 500
|
24
|
-
raise Error::ServerError, finished_env[:body].
|
24
|
+
raise Error::ServerError, finished_env[:body].inspect
|
25
25
|
end
|
26
26
|
end
|
27
27
|
end
|
@@ -8,7 +8,13 @@ module Podio
|
|
8
8
|
puts "\n==> #{env[:method].to_s.upcase} #{env[:url]} \n\n"
|
9
9
|
end
|
10
10
|
|
11
|
-
|
11
|
+
begin
|
12
|
+
@app.call(env)
|
13
|
+
ensure
|
14
|
+
if env[:request][:client].debug
|
15
|
+
puts "\n== (#{env[:status]}) ==> #{env[:body]}\n\n"
|
16
|
+
end
|
17
|
+
end
|
12
18
|
end
|
13
19
|
|
14
20
|
def initialize(app)
|
@@ -12,8 +12,8 @@ module Podio
|
|
12
12
|
rescue Error::TokenExpired
|
13
13
|
podio_client.refresh_access_token
|
14
14
|
|
15
|
-
|
16
|
-
orig_env[:
|
15
|
+
# new access token needs to be put into the header
|
16
|
+
orig_env[:request_headers].merge!(podio_client.configured_headers)
|
17
17
|
|
18
18
|
# redo the request with the new access token
|
19
19
|
@app.call(orig_env)
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'digest/md5'
|
2
|
+
|
3
|
+
module Podio
|
4
|
+
module Middleware
|
5
|
+
class ResponseRecorder < Faraday::Response::Middleware
|
6
|
+
def self.register_on_complete(env)
|
7
|
+
env[:response].on_complete do |finished_env|
|
8
|
+
response = "['#{Faraday::Utils.normalize_path(finished_env[:url])}', :#{finished_env[:method]}, #{finished_env[:status]}, #{finished_env[:response_headers]}, '#{finished_env[:body]}']"
|
9
|
+
|
10
|
+
filename = Digest::MD5.hexdigest(finished_env[:url].request_uri)
|
11
|
+
File.open("#{filename}.rack", 'w') { |f| f.write(response) }
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
module Podio
|
2
|
+
module ResponseWrapper
|
3
|
+
def member(response)
|
4
|
+
response
|
5
|
+
end
|
6
|
+
|
7
|
+
def list(response)
|
8
|
+
response
|
9
|
+
end
|
10
|
+
|
11
|
+
def collection(response)
|
12
|
+
Struct.new(:all, :count, :total_count).new(response['items'], response['filtered'], response['total'])
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
data/lib/podio/version.rb
CHANGED
data/podio.gemspec
CHANGED
@@ -16,10 +16,8 @@ Gem::Specification.new do |s|
|
|
16
16
|
s.require_paths = ['lib']
|
17
17
|
|
18
18
|
s.add_runtime_dependency 'faraday', '~> 0.5.1'
|
19
|
-
s.add_runtime_dependency '
|
20
|
-
s.add_runtime_dependency 'yajl-ruby', '~> 0.7
|
21
|
-
|
22
|
-
s.add_development_dependency 'fakeweb', '~> 1.3'
|
19
|
+
s.add_runtime_dependency 'activesupport', '~> 3.0'
|
20
|
+
s.add_runtime_dependency 'yajl-ruby', '~> 0.7'
|
23
21
|
|
24
22
|
s.description = <<desc
|
25
23
|
The humble beginnings of the Ruby wrapper for the Podio API.
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class AppStoreTest < ActiveSupport::TestCase
|
4
|
+
test 'should find single category' do
|
5
|
+
category = Podio::Category.find(1)
|
6
|
+
|
7
|
+
assert_equal 1, category['category_id']
|
8
|
+
end
|
9
|
+
|
10
|
+
test 'should find all categories' do
|
11
|
+
categories = Podio::Category.find_all
|
12
|
+
|
13
|
+
assert_equal Array, categories.functional.class
|
14
|
+
assert_not_nil categories.functional.first['category_id']
|
15
|
+
|
16
|
+
assert_equal Array, categories.vertical.class
|
17
|
+
assert_not_nil categories.vertical.first['category_id']
|
18
|
+
end
|
19
|
+
|
20
|
+
test 'should create category' do
|
21
|
+
login_as_user(2)
|
22
|
+
|
23
|
+
status = Podio::Category.create(:type => 'vertical', :name => 'New Category')
|
24
|
+
assert_equal 200, status
|
25
|
+
end
|
26
|
+
|
27
|
+
test 'should update category' do
|
28
|
+
login_as_user(2)
|
29
|
+
|
30
|
+
status = Podio::Category.update(1, :name => 'New name')
|
31
|
+
assert_equal 204, status
|
32
|
+
end
|
33
|
+
|
34
|
+
test 'should delete category' do
|
35
|
+
login_as_user(2)
|
36
|
+
|
37
|
+
status = Podio::Category.delete(3)
|
38
|
+
assert_equal 204, status
|
39
|
+
end
|
40
|
+
end
|
data/test/client_test.rb
CHANGED
@@ -1,81 +1,74 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
config.api_secret = 'client_secret'
|
10
|
-
end
|
3
|
+
class ClientTest < ActiveSupport::TestCase
|
4
|
+
def setup
|
5
|
+
Podio.configure do |config|
|
6
|
+
config.api_url = 'https://api.podio.com'
|
7
|
+
config.api_key = 'client_id'
|
8
|
+
config.api_secret = 'client_secret'
|
11
9
|
end
|
10
|
+
end
|
12
11
|
|
13
|
-
|
14
|
-
|
12
|
+
test 'should configure defaults' do
|
13
|
+
podio = Podio::Client.new
|
15
14
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
15
|
+
assert_equal 'https://api.podio.com', podio.api_url
|
16
|
+
assert_equal 'client_id', podio.api_key
|
17
|
+
assert_equal 'client_secret', podio.api_secret
|
18
|
+
end
|
20
19
|
|
21
|
-
|
22
|
-
|
20
|
+
test 'should overwrite defaults' do
|
21
|
+
podio = Podio::Client.new(:api_url => 'https://new.podio.com', :api_key => 'new_client_id', :api_secret => 'new_client_secret')
|
23
22
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
23
|
+
assert_equal 'https://new.podio.com', podio.api_url
|
24
|
+
assert_equal 'new_client_id', podio.api_key
|
25
|
+
assert_equal 'new_client_secret', podio.api_secret
|
26
|
+
end
|
28
27
|
|
29
|
-
|
30
|
-
|
31
|
-
|
28
|
+
test 'should setup connection' do
|
29
|
+
token = Podio::OAuthToken.new('access_token' => 'access', 'refresh_token' => 'refresh')
|
30
|
+
podio = Podio::Client.new(:oauth_token => token)
|
32
31
|
|
33
|
-
|
32
|
+
assert_equal token, podio.oauth_token
|
34
33
|
|
35
|
-
|
36
|
-
end
|
34
|
+
assert_equal 'OAuth2 access', podio.connection.headers['authorization']
|
37
35
|
end
|
38
36
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
stub_post(
|
45
|
-
'/oauth/token?grant_type=password&client_id=client_id&client_secret=client_secret&username=username&password=password',
|
46
|
-
{'access_token' => 'access', 'refresh_token' => 'refresh', 'expires_in' => 3600}
|
47
|
-
)
|
37
|
+
test 'should get an access token' do
|
38
|
+
client = Podio.client
|
39
|
+
client.oauth_token = nil
|
40
|
+
assert_nil client.oauth_token
|
48
41
|
|
49
|
-
|
42
|
+
client.get_access_token('pollas@hoisthq.com', 'secret')
|
50
43
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
test 'should be able to refresh access token' do
|
56
|
-
client = podio_test_client
|
44
|
+
assert_not_nil client.oauth_token.access_token
|
45
|
+
assert_not_nil client.oauth_token.refresh_token
|
46
|
+
end
|
57
47
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
)
|
48
|
+
test 'should be able to refresh access token' do
|
49
|
+
client = Podio.client
|
50
|
+
old_token = client.oauth_token.dup
|
62
51
|
|
63
|
-
|
52
|
+
client.refresh_access_token
|
64
53
|
|
65
|
-
|
66
|
-
|
67
|
-
end
|
54
|
+
assert_not_equal old_token.access_token, client.oauth_token.access_token
|
55
|
+
assert_equal old_token.refresh_token, client.oauth_token.refresh_token
|
68
56
|
end
|
69
57
|
|
70
|
-
|
71
|
-
|
72
|
-
|
58
|
+
test 'should automatically refresh an expired token' do
|
59
|
+
# this token is already expired in the test database
|
60
|
+
Podio.client.oauth_token = Podio::OAuthToken.new('access_token' => '30da4594eef93528c11df7fb5deb989cd629ea7060a1ce1ced628d19398214c942bcfe0334cf953ef70a80ea1afdfd80183d5c75d19c1f5526ca4c6f6f3471ef', 'refresh_token' => '82e7a11ae187f28a25261599aa6229cd89f8faee48cba18a75d70efae88ba665ced11d43143b7f5bebb31a4103662b851dd2db1879a3947b843259479fccfad3', 'expires_in' => -10)
|
61
|
+
Podio.client.reset
|
73
62
|
|
74
|
-
|
75
|
-
|
76
|
-
response = client.connection.get('/')
|
63
|
+
assert_nothing_raised do
|
64
|
+
response = Podio.client.connection.get('/org/')
|
77
65
|
assert_equal 200, response.status
|
78
|
-
assert_equal({ 'messaging' => true, 'version' => '1.0.1' }, response.body)
|
79
66
|
end
|
80
67
|
end
|
68
|
+
|
69
|
+
test 'should be able to make arbitrary requests' do
|
70
|
+
response = Podio.client.connection.get('/org/')
|
71
|
+
assert_equal 200, response.status
|
72
|
+
assert response.body.is_a?(Array)
|
73
|
+
end
|
81
74
|
end
|