uservoice-ruby 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|