uservoice-ruby 0.0.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.
- data/.gitignore +6 -0
- data/Gemfile +4 -0
- data/README.md +119 -0
- data/Rakefile +8 -0
- data/lib/uservoice/user_voice.rb +160 -0
- data/lib/uservoice/version.rb +3 -0
- data/lib/uservoice-ruby.rb +1 -0
- data/spec/config.yml.templ +4 -0
- data/spec/lib/user_voice_spec.rb +186 -0
- data/spec/spec_helper.rb +10 -0
- data/uservoice-ruby.gemspec +25 -0
- metadata +104 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
UserVoice gem for API connections
|
2
|
+
=================================
|
3
|
+
|
4
|
+
This gem allows you to easily:
|
5
|
+
* Generate SSO token for creating SSO users / logging them into UserVoice (http://uservoice.com).
|
6
|
+
* Do 3-legged and 2-legged UserVoice API calls safely without having to worry about the cryptographic details.
|
7
|
+
|
8
|
+
Installation
|
9
|
+
============
|
10
|
+
|
11
|
+
Place this in your Gemfile:
|
12
|
+
```ruby
|
13
|
+
gem 'uservoice', :git => 'https://github.com/uservoice/uservoice-ruby'
|
14
|
+
```
|
15
|
+
Run the bundle command and then try one of the examples below.
|
16
|
+
|
17
|
+
Examples
|
18
|
+
========
|
19
|
+
|
20
|
+
Prerequisites:
|
21
|
+
* Suppose your UserVoice site is at http://uservoice-subdomain.uservoice.com/ and **USERVOICE\_SUBDOMAIN** = uservoice-subdomain
|
22
|
+
* **SSO\_KEY** = 982c88f2df72572859e8e23423eg87ed (Admin Console -> Settings -> General -> User Authentication)
|
23
|
+
* The account has a following API client (Admin Console -> Settings -> Channels -> API):
|
24
|
+
* **API\_KEY** = oQt2BaunWNuainc8BvZpAm
|
25
|
+
* **API\_SECRET** = 3yQMSoXBpAwuK3nYHR0wpY6opE341inL9a2HynGF2
|
26
|
+
|
27
|
+
|
28
|
+
SSO-token generation using uservoice gem
|
29
|
+
----------------------------------------
|
30
|
+
|
31
|
+
SSO-token can be used to create sessions for SSO users. They are capable of synchronizing the user information from one system to another.
|
32
|
+
Generating the SSO token from SSO key and given uservoice subdomain can be done by calling UserVoice.generate\_sso\_token method like this:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'uservoice'
|
36
|
+
sso_token = UserVoice.generate_sso_token(USERVOICE_SUBDOMAIN, SSO_KEY, {
|
37
|
+
:guid => 1001,
|
38
|
+
:display_name => "John Doe",
|
39
|
+
:email => 'john.doe@example.com'
|
40
|
+
})
|
41
|
+
|
42
|
+
# Now this URL will log John Doe in:
|
43
|
+
puts "https://#{USERVOICE_SUBDOMAIN}.uservoice.com/?sso=#{sso_token}"
|
44
|
+
```
|
45
|
+
|
46
|
+
Making API calls
|
47
|
+
----------------
|
48
|
+
|
49
|
+
With the gem you need to create an instance of UserVoice::Oauth. You get
|
50
|
+
API_KEY and API_SECRET from an API client which you can create in Admin Console
|
51
|
+
-> Settings -> Channels -> API.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
require 'uservoice'
|
55
|
+
begin
|
56
|
+
uservoice_client = UserVoice::Client.new(USERVOICE_SUBDOMAIN, API_KEY, API_SECRET)
|
57
|
+
|
58
|
+
# Get users of a subdomain (requires trusted client, but no user)
|
59
|
+
users = uservoice_client.get("/api/v1/users.json?per_page=3")['users']
|
60
|
+
users.each do |user|
|
61
|
+
puts "User: \"#{user['name']}\", Profile URL: #{user['url']}"
|
62
|
+
end
|
63
|
+
|
64
|
+
# Now, let's login as mailaddress@example.com, a regular user
|
65
|
+
uservoice_client.login_as('mailaddress@example.com')
|
66
|
+
|
67
|
+
# Example request #1: Get current user.
|
68
|
+
user = uservoice_client.get("/api/v1/users/current.json")['user']
|
69
|
+
|
70
|
+
puts "User: \"#{user['name']}\", Profile URL: #{user['url']}"
|
71
|
+
|
72
|
+
# Login as account owner
|
73
|
+
uservoice_client.login_as_owner
|
74
|
+
|
75
|
+
# Example request #2: Create a new private forum limited to only example.com email domain.
|
76
|
+
forum = uservoice_client.post("/api/v1/forums.json", :forum => {
|
77
|
+
:name => 'Example.com Private Feedback',
|
78
|
+
:private => true,
|
79
|
+
:allow_by_email_domain => true,
|
80
|
+
:allowed_email_domains => [{:domain => 'example.com'}]
|
81
|
+
})['forum']
|
82
|
+
|
83
|
+
puts "Forum '#{forum['name']}' created! URL: #{forum['url']}"
|
84
|
+
rescue UserVoice::Unauthorized => e
|
85
|
+
# Thrown usually due to faulty tokens, untrusted client or if attempting
|
86
|
+
# operations without Admin Privileges
|
87
|
+
raise
|
88
|
+
end
|
89
|
+
```
|
90
|
+
|
91
|
+
Verifying a UserVoice user
|
92
|
+
--------------------------
|
93
|
+
|
94
|
+
If you want to make calls on behalf of a user, but want to make sure he or she
|
95
|
+
actually owns certain email address in UserVoice, you need to use 3-Legged API
|
96
|
+
calls. Just pass your user an authorize link to click, so that user may grant
|
97
|
+
your site permission to access his or her data in UserVoice.
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
require 'uservoice'
|
101
|
+
CALLBACK_URL = 'http://localhost:3000/' # your site
|
102
|
+
|
103
|
+
uservoice_client = UserVoice::Client.new(USERVOICE_SUBDOMAIN, API_KEY, API_SECRET, :callback => CALLBACK_URL)
|
104
|
+
|
105
|
+
# At this point you want to print/redirect to uservoice_client.authorize_url in your application.
|
106
|
+
# Here we just output them as this is a command-line example.
|
107
|
+
puts "1. Go to #{uservoice_client.authorize_url} and click \"Allow access\"."
|
108
|
+
puts "2. Then type the oauth_verifier which is passed as a GET parameter to the callback URL:"
|
109
|
+
|
110
|
+
# In a web app we would get the oauth_verifier through a redirect from UserVoice (after a redirection back to CALLBACK_URL).
|
111
|
+
# In this command-line example we just read it from stdin:
|
112
|
+
uservoice_client.login_verified_user(gets.match('\w*').to_s)
|
113
|
+
|
114
|
+
# All done. Now we can read the current user to know user's email address:
|
115
|
+
user = uservoice_client.get("/api/v1/users/current.json")['user']
|
116
|
+
|
117
|
+
puts "User logged in, Name: #{user['name']}, email: #{user['email']}"
|
118
|
+
```
|
119
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,160 @@
|
|
1
|
+
require "uservoice/version"
|
2
|
+
require 'rubygems'
|
3
|
+
require 'ezcrypto'
|
4
|
+
require 'json'
|
5
|
+
require 'cgi'
|
6
|
+
require 'base64'
|
7
|
+
require 'oauth'
|
8
|
+
|
9
|
+
module UserVoice
|
10
|
+
EMAIL_FORMAT = %r{^(\w[-+.\w!\#\$%&'\*\+\-/=\?\^_`\{\|\}~]*@([-\w]*\.)+[a-zA-Z]{2,9})$}
|
11
|
+
DEFAULT_HEADERS = { 'Content-Type'=> 'application/json', 'Accept'=> 'application/json' }
|
12
|
+
|
13
|
+
class APIError < RuntimeError
|
14
|
+
end
|
15
|
+
Unauthorized = Class.new(APIError)
|
16
|
+
NotFound = Class.new(APIError)
|
17
|
+
ApplicationError = Class.new(APIError)
|
18
|
+
|
19
|
+
def self.generate_sso_token(subdomain_key, sso_key, user_hash, valid_for = 5 * 60)
|
20
|
+
user_hash[:expires] ||= (Time.now.utc + valid_for).to_s unless valid_for.nil?
|
21
|
+
unless user_hash[:email].to_s.match(EMAIL_FORMAT)
|
22
|
+
raise Unauthorized.new("'#{user_hash[:email]}' is not a valid email address")
|
23
|
+
end
|
24
|
+
unless sso_key.to_s.length > 1
|
25
|
+
raise Unauthorized.new("Please specify your SSO key")
|
26
|
+
end
|
27
|
+
|
28
|
+
key = EzCrypto::Key.with_password(subdomain_key, sso_key)
|
29
|
+
encrypted = key.encrypt(user_hash.to_json)
|
30
|
+
encoded = Base64.encode64(encrypted).gsub(/\n/,'')
|
31
|
+
|
32
|
+
return CGI.escape(encoded)
|
33
|
+
end
|
34
|
+
|
35
|
+
class Client
|
36
|
+
|
37
|
+
def initialize(*args)
|
38
|
+
case args.size
|
39
|
+
when 3,4
|
40
|
+
init_subdomain_and_api_keys(*args)
|
41
|
+
when 1,2
|
42
|
+
init_consumer_and_access_token(*args)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def init_subdomain_and_api_keys(subdomain_name, api_key, api_secret, attrs={})
|
47
|
+
consumer = OAuth::Consumer.new(api_key, api_secret, {
|
48
|
+
:site => "#{attrs[:protocol] || 'https'}://#{subdomain_name}.#{attrs[:uservoice_domain] || 'uservoice.com'}"
|
49
|
+
})
|
50
|
+
init_consumer_and_access_token(consumer, attrs)
|
51
|
+
end
|
52
|
+
|
53
|
+
def init_consumer_and_access_token(consumer, attrs={})
|
54
|
+
@consumer = consumer
|
55
|
+
@token = OAuth::AccessToken.new(@consumer, attrs[:oauth_token] || '', attrs[:oauth_token_secret] || '')
|
56
|
+
@response_format = attrs[:response_format] || :hash
|
57
|
+
@callback = attrs[:callback]
|
58
|
+
end
|
59
|
+
|
60
|
+
def authorize_url
|
61
|
+
request_token.authorize_url
|
62
|
+
end
|
63
|
+
|
64
|
+
def login_with_verifier(oauth_verifier)
|
65
|
+
token = request_token.get_access_token(:oauth_verifier => oauth_verifier)
|
66
|
+
Client.new(@consumer, :oauth_token => token.token, :oauth_token_secret => token.secret)
|
67
|
+
end
|
68
|
+
|
69
|
+
def login_with_access_token(oauth_token, oauth_token_secret, &block)
|
70
|
+
token = Client.new(@consumer, :oauth_token => oauth_token, :oauth_token_secret => oauth_token_secret)
|
71
|
+
if block_given?
|
72
|
+
yield token
|
73
|
+
else
|
74
|
+
return token
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def token
|
79
|
+
@token.token
|
80
|
+
end
|
81
|
+
|
82
|
+
def secret
|
83
|
+
@token.secret
|
84
|
+
end
|
85
|
+
|
86
|
+
def request_token
|
87
|
+
@consumer.get_request_token(:oauth_callback => @callback)
|
88
|
+
end
|
89
|
+
|
90
|
+
def login_as_owner(&block)
|
91
|
+
token = post('/api/v1/users/login_as_owner.json', {
|
92
|
+
'request_token' => request_token.token
|
93
|
+
})['token']
|
94
|
+
if token
|
95
|
+
login_with_access_token(token['oauth_token'], token['oauth_token_secret'], &block)
|
96
|
+
else
|
97
|
+
raise Unauthorized.new("Could not get Access Token")
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
def login_as(email, &block)
|
102
|
+
unless email.to_s.match(EMAIL_FORMAT)
|
103
|
+
raise Unauthorized.new("'#{email}' is not a valid email address")
|
104
|
+
end
|
105
|
+
token = post('/api/v1/users/login_as.json', {
|
106
|
+
:user => { :email => email },
|
107
|
+
:request_token => request_token.token
|
108
|
+
})['token']
|
109
|
+
|
110
|
+
if token
|
111
|
+
login_with_access_token(token['oauth_token'], token['oauth_token_secret'], &block)
|
112
|
+
else
|
113
|
+
raise Unauthorized.new("Could not get Access Token")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def request(method, uri, request_body={}, headers={})
|
118
|
+
headers = DEFAULT_HEADERS.merge(headers)
|
119
|
+
|
120
|
+
if headers['Content-Type'] == 'application/json' && request_body.is_a?(Hash)
|
121
|
+
request_body = request_body.to_json
|
122
|
+
end
|
123
|
+
|
124
|
+
response = case method.to_sym
|
125
|
+
when :post, :put
|
126
|
+
@token.request(method, uri, request_body, headers)
|
127
|
+
when :head, :delete, :get
|
128
|
+
@token.request(method, uri, headers)
|
129
|
+
else
|
130
|
+
raise RuntimeError.new("Invalid HTTP method #{method}")
|
131
|
+
end
|
132
|
+
|
133
|
+
return case @response_format.to_s
|
134
|
+
when 'raw'
|
135
|
+
response
|
136
|
+
else
|
137
|
+
attrs = JSON.parse(response.body)
|
138
|
+
if attrs && attrs['errors']
|
139
|
+
case attrs['errors']['type']
|
140
|
+
when 'unauthorized'
|
141
|
+
raise Unauthorized.new(attrs)
|
142
|
+
when 'record_not_found'
|
143
|
+
raise NotFound.new(attrs)
|
144
|
+
when 'application_error'
|
145
|
+
raise ApplicationError.new(attrs)
|
146
|
+
else
|
147
|
+
raise APIError.new(attrs)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
attrs
|
151
|
+
end
|
152
|
+
end
|
153
|
+
|
154
|
+
%w(get post delete put).each do |method|
|
155
|
+
define_method(method) do |*args|
|
156
|
+
request(method, *args)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'uservoice/user_voice'
|
@@ -0,0 +1,186 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe UserVoice do
|
4
|
+
|
5
|
+
it "should generate SSO token" do
|
6
|
+
token = UserVoice.generate_sso_token(config['subdomain_name'], config['sso_key'], {
|
7
|
+
:display_name => "User Name",
|
8
|
+
:email => 'mailaddress@example.com'
|
9
|
+
})
|
10
|
+
encrypted_raw_data = Base64.decode64(CGI.unescape(token))
|
11
|
+
|
12
|
+
key = EzCrypto::Key.with_password(config['subdomain_name'], config['sso_key'])
|
13
|
+
key.decrypt(encrypted_raw_data).should match('mailaddress@example.com')
|
14
|
+
end
|
15
|
+
|
16
|
+
describe UserVoice::Client do
|
17
|
+
subject { UserVoice::Client.new(config['subdomain_name'],
|
18
|
+
config['api_key'],
|
19
|
+
config['api_secret'],
|
20
|
+
:uservoice_domain => config['uservoice_domain'],
|
21
|
+
:protocol => config['protocol']) }
|
22
|
+
|
23
|
+
it "should get user names from the API" do
|
24
|
+
users = subject.get("/api/v1/users.json?per_page=3")
|
25
|
+
user_names = users['users'].map { |user| user['name'] }
|
26
|
+
user_names.all?.should == true
|
27
|
+
user_names.size.should == 3
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should not get current user without logged in user" do
|
31
|
+
lambda do
|
32
|
+
user = subject.get("/api/v1/users/current.json")
|
33
|
+
end.should raise_error(UserVoice::Unauthorized)
|
34
|
+
end
|
35
|
+
|
36
|
+
it "should be able to get access token as owner" do
|
37
|
+
subject.login_as_owner do |owner|
|
38
|
+
owner.get("/api/v1/users/current.json")['user']['roles']['owner'].should == true
|
39
|
+
|
40
|
+
owner.login_as('regular@example.com') do |regular|
|
41
|
+
owner.get("/api/v1/users/current.json")['user']['roles']['owner'].should == true
|
42
|
+
@user = regular.get("/api/v1/users/current.json")['user']
|
43
|
+
@user['roles']['owner'].should == false
|
44
|
+
end
|
45
|
+
|
46
|
+
owner.get("/api/v1/users/current.json")['user']['roles']['owner'].should == true
|
47
|
+
end
|
48
|
+
# ensure blocks got run
|
49
|
+
@user['email'].should == 'regular@example.com'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should not be able to create KB article as nobody" do
|
53
|
+
lambda do
|
54
|
+
result = subject.post("/api/v1/articles.json", :article => {
|
55
|
+
:title => 'good morning'
|
56
|
+
})
|
57
|
+
end.should raise_error(UserVoice::Unauthorized)
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should be able to create and delete a forum as the owner" do
|
61
|
+
owner = subject.login_as_owner
|
62
|
+
forum = owner.post("/api/v1/forums.json", :forum => {
|
63
|
+
:name => 'Test forum from RSpec',
|
64
|
+
'private' => true,
|
65
|
+
'allow_by_email_domain' => true,
|
66
|
+
'allowed_email_domains' => [{'domain' => 'raimo.rspec.example.com'}]
|
67
|
+
})['forum']
|
68
|
+
|
69
|
+
forum['id'].should be_a(Integer)
|
70
|
+
|
71
|
+
deleted_forum = owner.delete("/api/v1/forums/#{forum['id']}.json")['forum']
|
72
|
+
deleted_forum['id'].should == forum['id']
|
73
|
+
end
|
74
|
+
|
75
|
+
it "should get current user with 2-legged call" do
|
76
|
+
user = subject.login_as('mailaddress@example.com') do |token|
|
77
|
+
token.get("/api/v1/users/current.json")['user']
|
78
|
+
end
|
79
|
+
|
80
|
+
user['email'].should == 'mailaddress@example.com'
|
81
|
+
end
|
82
|
+
|
83
|
+
it "should get current user with copied access token" do
|
84
|
+
original_token = subject.login_as('mailaddress@example.com')
|
85
|
+
|
86
|
+
client = UserVoice::Client.new(config['subdomain_name'],
|
87
|
+
config['api_key'],
|
88
|
+
config['api_secret'],
|
89
|
+
:uservoice_domain => config['uservoice_domain'],
|
90
|
+
:protocol => config['protocol'],
|
91
|
+
:oauth_token => original_token.token,
|
92
|
+
:oauth_token_secret => original_token.secret)
|
93
|
+
# Also this works but creates an extra object:
|
94
|
+
# client = client.login_with_access_token(original_token.token, original_token.secret)
|
95
|
+
|
96
|
+
user = client.get("/api/v1/users/current.json")['user']
|
97
|
+
|
98
|
+
user['email'].should == 'mailaddress@example.com'
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should login as an owner" do
|
102
|
+
me = subject.login_as_owner
|
103
|
+
|
104
|
+
owner = me.get("/api/v1/users/current.json")['user']
|
105
|
+
owner['roles']['owner'].should == true
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should not be able to delete when not deleting on behalf of anyone" do
|
109
|
+
lambda {
|
110
|
+
result = subject.delete("/api/v1/users/#{234}.json")
|
111
|
+
}.should raise_error(UserVoice::Unauthorized, /user required/i)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should not be able to delete owner" do
|
115
|
+
owner_access_token = subject.login_as_owner
|
116
|
+
|
117
|
+
owner = owner_access_token.get("/api/v1/users/current.json")['user']
|
118
|
+
|
119
|
+
lambda {
|
120
|
+
result = owner_access_token.delete("/api/v1/users/#{owner['id']}.json")
|
121
|
+
}.should raise_error(UserVoice::Unauthorized, /last owner/i)
|
122
|
+
end
|
123
|
+
|
124
|
+
it "should not be able to delete user without login" do
|
125
|
+
regular_user = subject.login_as('somebodythere@example.com').get("/api/v1/users/current.json")['user']
|
126
|
+
|
127
|
+
lambda {
|
128
|
+
subject.delete("/api/v1/users/#{regular_user['id']}.json")
|
129
|
+
}.should raise_error(UserVoice::Unauthorized)
|
130
|
+
end
|
131
|
+
|
132
|
+
it "should be able to identify suggestions" do
|
133
|
+
owner_token = subject.login_as_owner
|
134
|
+
external_scope='sync_to_moon'
|
135
|
+
suggestions = owner_token.get("/api/v1/suggestions.json?filter=with_external_id&external_scope=#{external_scope}&manual_action=#{external_scope}")['suggestions']
|
136
|
+
|
137
|
+
identifications = suggestions.map {|s| { :id => s['id'], :external_id => s['id'].to_i*10 } }
|
138
|
+
|
139
|
+
ids = owner_token.put("/api/v1/suggestions/identify.json",
|
140
|
+
:external_scope => external_scope,
|
141
|
+
:identifications => identifications)['identifications']['ids']
|
142
|
+
ids.should == identifications.map { |s| s[:id] }.sort
|
143
|
+
end
|
144
|
+
|
145
|
+
it "should be able to delete itself" do
|
146
|
+
my_token = subject.login_as('somebodythere@example.com')
|
147
|
+
|
148
|
+
# whoami
|
149
|
+
my_id = my_token.get("/api/v1/users/current.json")['user']['id']
|
150
|
+
|
151
|
+
# Delete myself!
|
152
|
+
my_token.delete("/api/v1/users/#{my_id}.json")['user']['id'].should == my_id
|
153
|
+
|
154
|
+
# I don't exist anymore
|
155
|
+
lambda {
|
156
|
+
my_token.get("/api/v1/users/current.json")
|
157
|
+
}.should raise_error(UserVoice::NotFound)
|
158
|
+
end
|
159
|
+
|
160
|
+
it "should/be able to delete random user and login as him after that" do
|
161
|
+
somebody = subject.login_as('somebodythere@example.com')
|
162
|
+
owner = subject.login_as_owner
|
163
|
+
|
164
|
+
# somebody is still there...
|
165
|
+
regular_user = somebody.get("/api/v1/users/current.json")['user']
|
166
|
+
regular_user['email'].should == 'somebodythere@example.com'
|
167
|
+
|
168
|
+
# delete somebody!
|
169
|
+
owner.delete("/api/v1/users/#{regular_user['id']}.json")['user']['id'].should == regular_user['id']
|
170
|
+
|
171
|
+
# not found anymore!
|
172
|
+
lambda {
|
173
|
+
somebody.get("/api/v1/users/current.json")['errors']['type']
|
174
|
+
}.should raise_error(UserVoice::NotFound)
|
175
|
+
|
176
|
+
# this recreates somebody
|
177
|
+
somebody = subject.login_as('somebodythere@example.com')
|
178
|
+
somebody.get("/api/v1/users/current.json")['user']['id'].should_not == regular_user['id']
|
179
|
+
end
|
180
|
+
|
181
|
+
it "should raise error with invalid email parameter" do
|
182
|
+
expect { subject.login_as('ma') }.to raise_error(UserVoice::Unauthorized)
|
183
|
+
expect { subject.login_as(nil) }.to raise_error(UserVoice::Unauthorized)
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "uservoice/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "uservoice-ruby"
|
7
|
+
s.version = Uservoice::VERSION
|
8
|
+
s.authors = ["Raimo Tuisku"]
|
9
|
+
s.email = ["dev@usevoice.com"]
|
10
|
+
s.homepage = "http://developer.uservoice.com"
|
11
|
+
s.summary = %q{Client library for UserVoice API}
|
12
|
+
s.description = %q{The gem provides Ruby-bindings to UserVoice API and helps generating Single-Sign-On tokens.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "uservoice-ruby"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
s.add_development_dependency "rspec"
|
22
|
+
s.add_runtime_dependency 'ezcrypto'
|
23
|
+
s.add_runtime_dependency 'json'
|
24
|
+
s.add_runtime_dependency 'oauth'
|
25
|
+
end
|
metadata
ADDED
@@ -0,0 +1,104 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: uservoice-ruby
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Raimo Tuisku
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-21 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rspec
|
16
|
+
requirement: &70340927735480 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '0'
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70340927735480
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: ezcrypto
|
27
|
+
requirement: &70340927734840 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70340927734840
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: json
|
38
|
+
requirement: &70340927732680 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :runtime
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70340927732680
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: oauth
|
49
|
+
requirement: &70340927731780 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :runtime
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70340927731780
|
58
|
+
description: The gem provides Ruby-bindings to UserVoice API and helps generating
|
59
|
+
Single-Sign-On tokens.
|
60
|
+
email:
|
61
|
+
- dev@usevoice.com
|
62
|
+
executables: []
|
63
|
+
extensions: []
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- .gitignore
|
67
|
+
- Gemfile
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- lib/uservoice-ruby.rb
|
71
|
+
- lib/uservoice/user_voice.rb
|
72
|
+
- lib/uservoice/version.rb
|
73
|
+
- spec/config.yml.templ
|
74
|
+
- spec/lib/user_voice_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
- uservoice-ruby.gemspec
|
77
|
+
homepage: http://developer.uservoice.com
|
78
|
+
licenses: []
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
none: false
|
85
|
+
requirements:
|
86
|
+
- - ! '>='
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ! '>='
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project: uservoice-ruby
|
97
|
+
rubygems_version: 1.8.15
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: Client library for UserVoice API
|
101
|
+
test_files:
|
102
|
+
- spec/config.yml.templ
|
103
|
+
- spec/lib/user_voice_spec.rb
|
104
|
+
- spec/spec_helper.rb
|