stackd 0.0.1 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 712a5a41ba1e4378a6f294d4ab7f4bd42dfa60df
4
+ data.tar.gz: 262301250ceb765546dd1120f259540e064ed25b
5
+ SHA512:
6
+ metadata.gz: 0c670d327edea7dc4d9fd30337b370094abe354e5f571f0b9f201d49bfeefef479508bfbd09c305f24646a597bef4e3b96fb0440abd21b99f97a1aee072c2ad4
7
+ data.tar.gz: 22e64533ead572f8c16a2c2c190492cf4772eab612b47fdc6dafd8fe6defde8f0b881fa74960b7d50b602a1b3bc5ca320c2184cf0565066f00be21ccb50ea304
data/.gitignore CHANGED
@@ -1,4 +1,9 @@
1
- *.gem
2
- .bundle
3
- Gemfile.lock
4
- pkg/*
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
data/Gemfile CHANGED
@@ -1,4 +1,4 @@
1
- source "http://rubygems.org"
1
+ source 'https://rubygems.org'
2
2
 
3
3
  # Specify your gem's dependencies in stackd.gemspec
4
4
  gemspec
data/LICENSE ADDED
@@ -0,0 +1,24 @@
1
+ This is free and unencumbered software released into the public domain.
2
+
3
+ Anyone is free to copy, modify, publish, use, compile, sell, or
4
+ distribute this software, either in source code form or as a compiled
5
+ binary, for any purpose, commercial or non-commercial, and by any
6
+ means.
7
+
8
+ In jurisdictions that recognize copyright laws, the author or authors
9
+ of this software dedicate any and all copyright interest in the
10
+ software to the public domain. We make this dedication for the benefit
11
+ of the public at large and to the detriment of our heirs and
12
+ successors. We intend this dedication to be an overt act of
13
+ relinquishment in perpetuity of all present and future rights to this
14
+ software under copyright law.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
+ IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
+ OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
+ ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
24
+ For more information, please refer to <http://unlicense.org>
@@ -0,0 +1,184 @@
1
+ # Stackd Ruby Client
2
+
3
+ [![Codeship Status for stackd/stackd-ruby](https://codeship.com/projects/ffa792f0-d110-0132-a1ec-2af27bf90e4e/status?branch=master)](https://codeship.com/projects/77196)
4
+
5
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/stackd`. To experiment with that code, run `bin/console` for an interactive prompt.
6
+
7
+ TODO: Delete this and the text above, and describe your gem
8
+
9
+ ### Installation
10
+
11
+ Add this line to your application's Gemfile:
12
+
13
+ ```ruby
14
+ gem 'stackd'
15
+ ```
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install stackd
24
+
25
+ ### Usage
26
+
27
+ ###### Creating a client
28
+
29
+ ```ruby
30
+ $stackd = Stackd::Client.new id: 'YOUR_CLIENT_ID',
31
+ secret: 'YOUR_CLIENT_SECRET'
32
+ ```
33
+
34
+ ###### Getting an app token
35
+
36
+ ```ruby
37
+ app_token = $stackd.auth_requests.client scope: 'a,b,c'
38
+ # => <Stackd::Token app_id: "YOUR_CLIENT_ID",
39
+ # scope: "a,b,c",
40
+ # access_token: "e1979b29529ac",
41
+ # expires_in: 3600>
42
+ ```
43
+
44
+ ###### Getting a user token
45
+
46
+ ```ruby
47
+ class MyController
48
+ def authorize
49
+ auth_request = $stackd.auth_requests.new redirect_uri: 'YOUR_CALLBACK_URL',
50
+ scope: 'a,b,c'
51
+
52
+ session[:auth_state] = auth_request.state
53
+
54
+ redirect_to auth_request.url
55
+ # stackd.com/authorize?client_id=...&state=...&redirect_uri=...&scope=...
56
+ end
57
+
58
+ def callback
59
+ auth_request = $stackd.auth_requests.new state: session[:state]
60
+
61
+ user_token = auth_request.callback code: params[:code],
62
+ state: params[:state]
63
+ # => <Stackd::Token user_id: "09545bce-3c31-4593-99bd-1dfe8b98df93",
64
+ # scope: "a,b",
65
+ # access_token: "e1979b29529ac",
66
+ # refresh_token: "ca92592b9791e",
67
+ # expires_in: 3600>
68
+ end
69
+ end
70
+ ```
71
+
72
+ ###### Refreshing a user token
73
+
74
+ ```ruby
75
+ refreshed_user_token = $stackd.auth_requests.refresh user_token
76
+ ```
77
+
78
+ ###### Persisting tokens (example)
79
+
80
+ ```
81
+ rails generate model TokenData \
82
+ user_id:string \
83
+ scope:string \
84
+ access_token:string \
85
+ refresh_token:string \
86
+ expires_in:integer
87
+ ```
88
+
89
+ ```ruby
90
+ Stackd::Token.on_grant do |token|
91
+ TokenData.create! user_id: token.user_id,
92
+ scope: token.scope,
93
+ access_token: token.access_token,
94
+ refresh_token: token.refresh_token,
95
+ expires_in: token.expires_in
96
+ end
97
+ ```
98
+
99
+ ```ruby
100
+ class TokenData
101
+ def self.fresh_token_by criteria
102
+ # ...
103
+ token = $stackd.tokens.new user_id: user_id,
104
+ scope: scope,
105
+ access_token: access_token,
106
+ refresh_token: refresh_token,
107
+ expires_in: expires_in,
108
+ token_type: 'bearer'
109
+
110
+ if expired?
111
+ $stackd.auth_requests.refresh token
112
+ else
113
+ token
114
+ end
115
+ end
116
+
117
+ def expired?
118
+ Time.now > created_at + expires_in.seconds
119
+ end
120
+ end
121
+ ```
122
+
123
+ ###### Making requests
124
+
125
+ https://stackd.com/docs
126
+
127
+ ```ruby
128
+ token.post :rappers, name: 'Marshall Mathers'
129
+ # POST /rappers
130
+ # {"name": "Marshall Mathers"}
131
+ # => { ... }
132
+ ```
133
+
134
+ ```ruby
135
+ token.patch :rappers, ':id', name: 'EMIN3M'
136
+ # PATCH /rappers/:id
137
+ # {"name": "EMIN3M"}
138
+ # => { ... }
139
+ ```
140
+
141
+ ```ruby
142
+ token.get :rapper, ':id'
143
+ # GET /rappers/:id
144
+ # => { ... }
145
+ ```
146
+
147
+ ```ruby
148
+ token.get :rappers, q: 'Slim Shady'
149
+ # GET /rappers?q=Slim%20Shady
150
+ # => { rappers: [ ... ], ... }
151
+ ```
152
+
153
+ ```ruby
154
+ token.get '/any/url'
155
+ # GET /any/url
156
+ ```
157
+
158
+ **Application errors (4xx)**
159
+
160
+ Will throw a `Stackd::Error`:
161
+
162
+ ```ruby
163
+ # <Stackd::Error error="error_code"
164
+ # error_description="Something went wrong."
165
+ # error_uri="https://stackd.com/docs/errors#error_code">
166
+ ```
167
+
168
+ **Server errors (5xx)**
169
+
170
+ Will throw a `Stackd::ServerError`.
171
+
172
+ ### Development
173
+
174
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment. Run `bundle exec stackd` to use the code located in this directory, ignoring other installed copies of this gem.
175
+
176
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
177
+
178
+ ### Contributing
179
+
180
+ 1. Fork it ( https://github.com/stackd/stackd-ruby/fork )
181
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
182
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
183
+ 4. Push to the branch (`git push origin my-new-feature`)
184
+ 5. Create a new Pull Request
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'bundler' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('bundler', 'bundler')
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "stackd"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'htmldiff' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('diff-lcs', 'htmldiff')
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'ldiff' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('diff-lcs', 'ldiff')
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rake' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rake', 'rake')
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rdoc' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rdoc', 'rdoc')
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'restclient' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rest-client', 'restclient')
data/bin/ri ADDED
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'ri' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rdoc', 'ri')
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'rspec' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('rspec-core', 'rspec')
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'safe_yaml' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('safe_yaml', 'safe_yaml')
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # This file was generated by Bundler.
4
+ #
5
+ # The application 'stackd' is installed as part of a gem, and
6
+ # this file is here to facilitate running it.
7
+ #
8
+
9
+ require 'pathname'
10
+ ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
11
+ Pathname.new(__FILE__).realpath)
12
+
13
+ require 'rubygems'
14
+ require 'bundler/setup'
15
+
16
+ load Gem.bin_path('stackd', 'stackd')
@@ -1,4 +1,29 @@
1
- require "stackd/version"
2
-
3
1
  module Stackd
2
+ autoload :Version, 'stackd/version'
3
+ autoload :Config, 'stackd/config'
4
+ autoload :Client, 'stackd/client'
5
+ autoload :AuthRequest, 'stackd/auth_request'
6
+ autoload :Token, 'stackd/token'
7
+ autoload :Error, 'stackd/error'
8
+ autoload :ServerError, 'stackd/server_error'
9
+
10
+ module Util
11
+ autoload :Portal, 'stackd/util/portal'
12
+ autoload :HTTP, 'stackd/util/http'
13
+ end
14
+
15
+ module Concerns
16
+ autoload :TattrAccessor, 'stackd/concerns/tattr_accessor'
17
+ autoload :RequireAttr, 'stackd/concerns/require_attr'
18
+ end
19
+
20
+ class << self
21
+ def config
22
+ @config ||= Stackd::Config.new
23
+ end
24
+
25
+ def configure
26
+ yield config
27
+ end
28
+ end
4
29
  end
@@ -0,0 +1,138 @@
1
+ require 'securerandom'
2
+ require 'active_support/core_ext/hash/keys'
3
+ require 'unirest'
4
+
5
+ module Stackd
6
+ class AuthRequest
7
+ include Concerns::TattrAccessor
8
+ include Concerns::RequireAttr
9
+
10
+ tattr_accessor client: Client,
11
+ state: String,
12
+ redirect_uri: String
13
+
14
+ class << self
15
+ def password client, params
16
+ if params[:username].nil?
17
+ raise ArgumentError.new "username is required"
18
+ end
19
+
20
+ if params[:password].nil?
21
+ raise ArgumentError.new "password is required"
22
+ end
23
+
24
+ request_token client, grant_type: 'password',
25
+ username: params[:username],
26
+ password: params[:password],
27
+ scope: params[:scope]
28
+ end
29
+
30
+ def client client, params = {}
31
+ request_token client, grant_type: 'client_credentials',
32
+ scope: params[:scope]
33
+ end
34
+
35
+ def refresh client, token, params = {}
36
+ if token.nil?
37
+ raise ArgumentError.new "Token is required"
38
+ end
39
+
40
+ if token.refresh_token.nil?
41
+ raise ArgumentError.new "Token must have refresh_token"
42
+ end
43
+
44
+ request_token client, grant_type: 'refresh_token',
45
+ refresh_token: token.refresh_token,
46
+ scope: params[:scope]
47
+ end
48
+
49
+ private
50
+
51
+ def request_token client, params = {}
52
+ if params[:grant_type].nil?
53
+ raise ArgumentError.new "grant_type not provided"
54
+ end
55
+
56
+ res = Unirest.post "#{Stackd.config.api_url}/token", {
57
+ auth: {
58
+ user: client.id,
59
+ password: client.secret
60
+ },
61
+ headers: {'Content-Type' => 'application/x-www-form-urlencoded'},
62
+ parameters: params.reject { |k,v| v.nil? }
63
+ }
64
+
65
+ if res.code == 200
66
+ token = client.tokens.new res.body.symbolize_keys
67
+
68
+ on_grant_callbacks = \
69
+ Stackd::Token.instance_variable_get :@_on_grant_callbacks
70
+ on_grant_callbacks.each {|c| c.(token) }
71
+
72
+ token
73
+ else
74
+ raise Error.new res.body.symbolize_keys
75
+ end
76
+ end
77
+ end
78
+
79
+ def initialize client, attrs = {}
80
+ if client.nil?
81
+ raise ArgumentError.new "client not provided"
82
+ else
83
+ self.client = client
84
+ end
85
+
86
+ attrs.each do |key, val|
87
+ public_send :"#{key}=", val
88
+ end
89
+
90
+ if state.nil?
91
+ self.state = SecureRandom.urlsafe_base64 9
92
+ end
93
+ end
94
+
95
+ def url params = {}
96
+ uri = auth_url
97
+
98
+ uri.query_values = {
99
+ response_type: 'code',
100
+ client_id: client.id,
101
+ state: state,
102
+ redirect_uri: params[:redirect_uri],
103
+ scope: params[:scope]
104
+ }.reject { |k,v| v.nil? }
105
+
106
+ uri.to_s
107
+ end
108
+
109
+ def callback params
110
+ unless params[:code] || params[:error]
111
+ raise ArgumentError.new 'no code or error'
112
+ end
113
+
114
+ if params[:state] != state
115
+ raise StateMismatchError.new "#{params[:state]} != #{state}"
116
+ end
117
+
118
+ if params[:error]
119
+ raise Error.new params
120
+ else
121
+ self.class.send :request_token, client, {
122
+ grant_type: 'authorization_code',
123
+ code: params[:code],
124
+ redirect_uri: redirect_uri
125
+ }
126
+ end
127
+ end
128
+
129
+ private
130
+
131
+ def auth_url
132
+ Addressable::URI.parse Stackd.config.auth_url
133
+ end
134
+
135
+ class StateMismatchError < StandardError
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,29 @@
1
+ module Stackd
2
+ class Client
3
+ include Concerns::TattrAccessor
4
+ include Concerns::RequireAttr
5
+
6
+ tattr_accessor id: String,
7
+ secret: String
8
+
9
+ def initialize attrs
10
+ attrs.each do |attr, val|
11
+ public_send :"#{attr}=", val
12
+ end
13
+
14
+ begin
15
+ require_attr! :id, :secret
16
+ rescue Concerns::RequireAttr::AttrNotSetError => e
17
+ raise ArgumentError.new e.message
18
+ end
19
+ end
20
+
21
+ def auth_requests
22
+ @auth_requests ||= Util::Portal.new self, AuthRequest
23
+ end
24
+
25
+ def tokens
26
+ @tokens ||= Util::Portal.new self, Token
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,20 @@
1
+ require 'active_support/concern'
2
+
3
+ module Stackd
4
+ module Concerns
5
+ module RequireAttr
6
+ extend ActiveSupport::Concern
7
+
8
+ def require_attr! *attrs
9
+ attrs.each do |key|
10
+ if public_send(key).nil?
11
+ raise AttrNotSetError.new "#{key} not set"
12
+ end
13
+ end
14
+ end
15
+
16
+ class AttrNotSetError < StandardError
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,51 @@
1
+ require 'active_support/concern'
2
+
3
+ module Stackd
4
+ module Concerns
5
+ module TattrAccessor
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ @_tattrs = {}
10
+ end
11
+
12
+ class_methods do
13
+ def tattr_accessor tattrs
14
+ tattrs.each do |tattr, type|
15
+ attr_reader tattr
16
+
17
+ @_tattrs[tattr] = type
18
+
19
+ define_method :"#{tattr}=" do |value|
20
+ unless value.nil? || self.class.tattr_type_match?(tattr, value)
21
+ tattr_type = self.class.tattr_type tattr
22
+ raise TypeMismatchError.new [
23
+ "tattr: #{tattr}",
24
+ "tattr_type: #{tattr_type}",
25
+ "value: #{value.inspect}"
26
+ ].join ', '
27
+ end
28
+
29
+ instance_variable_set :"@#{tattr}", value
30
+ end
31
+ end
32
+ end
33
+
34
+ def tattr_type_match? tattr, value
35
+ value.is_a? tattr_type(tattr)
36
+ end
37
+
38
+ def tattr_type tattr
39
+ @_tattrs[tattr]
40
+ end
41
+ end
42
+
43
+ def tattr? key
44
+ self.class.instance_variable_get(:@_tattrs).has_key? key
45
+ end
46
+
47
+ class TypeMismatchError < StandardError
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,23 @@
1
+ module Stackd
2
+ class Config
3
+ include Concerns::TattrAccessor
4
+
5
+ tattr_accessor auth_url: String,
6
+ token_url: String,
7
+ api_url: String
8
+
9
+ def initialize attrs = {}
10
+ attrs.each do |key, val|
11
+ public_send :"#{key}=", val
12
+ end
13
+ end
14
+
15
+ def auth_url
16
+ @auth_url || "https://stackd.com/authorize"
17
+ end
18
+
19
+ def api_url
20
+ @api_url || "https://api.stackd.com"
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ module Stackd
2
+ class Error < StandardError
3
+ include Concerns::TattrAccessor
4
+ include Concerns::RequireAttr
5
+
6
+ tattr_accessor error: String,
7
+ error_description: String,
8
+ error_uri: String,
9
+ error_params: Hash
10
+
11
+ def initialize attrs
12
+ attrs.each do |key, val|
13
+ public_send :"#{key}=", val if tattr? key
14
+ end
15
+
16
+ begin
17
+ require_attr! :error
18
+ rescue Concerns::RequireAttr::AttrNotSetError => e
19
+ raise ArgumentError.new e.message
20
+ end
21
+ end
22
+
23
+ def message
24
+ error_description
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,4 @@
1
+ module Stackd
2
+ class ServerError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,103 @@
1
+ require 'active_support/core_ext/object/to_query'
2
+
3
+ module Stackd
4
+ class Token
5
+ include Concerns::TattrAccessor
6
+ include Concerns::RequireAttr
7
+
8
+ @_on_grant_callbacks = []
9
+
10
+ tattr_accessor client: Client,
11
+ access_token: String,
12
+ token_type: String,
13
+ expires_in: Integer,
14
+ refresh_token: String,
15
+ scope: String,
16
+ service_id: String,
17
+ user_id: String
18
+
19
+ def self.on_grant &block
20
+ @_on_grant_callbacks.push block
21
+ end
22
+
23
+ def initialize client, attrs = {}
24
+ self.client = client
25
+
26
+ attrs.each do |key, val|
27
+ public_send :"#{key}=", val if tattr? key
28
+ end
29
+
30
+ begin
31
+ require_attr! :client, :access_token, :token_type
32
+ rescue Concerns::RequireAttr::AttrNotSetError => e
33
+ raise ArgumentError.new e.message
34
+ end
35
+ end
36
+
37
+ def get *args
38
+ Util::HTTP.get self, get_url(*args)
39
+ end
40
+
41
+ def post *args
42
+ url_args = reject_body_arg *args
43
+ body_arg = get_body_arg *args
44
+ content_type = content_type_for body_arg
45
+
46
+ Util::HTTP.post self, get_url(*url_args), body_arg, content_type
47
+ end
48
+
49
+ def put *args
50
+ url_args = reject_body_arg *args
51
+ body_arg = get_body_arg *args
52
+ content_type = content_type_for body_arg
53
+
54
+ Util::HTTP.put self, get_url(*url_args), body_arg, content_type
55
+ end
56
+
57
+ def patch *args
58
+ url_args = reject_body_arg *args
59
+ body_arg = get_body_arg *args
60
+ content_type = content_type_for body_arg
61
+
62
+ Util::HTTP.patch self, get_url(*url_args), body_arg, content_type
63
+ end
64
+
65
+ def delete *args
66
+ url_args = reject_body_arg *args
67
+ body_arg = get_body_arg *args
68
+ content_type = content_type_for body_arg
69
+
70
+ Util::HTTP.delete self, get_url(*url_args), body_arg, content_type
71
+ end
72
+
73
+ private
74
+
75
+ def get_url *args
76
+ args.inject Stackd.config.api_url do |url, part|
77
+ if part.is_a? Hash
78
+ "#{url}?#{part.to_query}"
79
+ else part = part.to_s
80
+ if part.index(Stackd.config.api_url) == 0
81
+ part
82
+ elsif part.index('/') == 0
83
+ "#{url}#{part}"
84
+ else
85
+ "#{url}/#{part}"
86
+ end
87
+ end
88
+ end
89
+ end
90
+
91
+ def reject_body_arg *args
92
+ args.reject {|a| a.is_a? Hash }
93
+ end
94
+
95
+ def get_body_arg *args
96
+ args.last if args.last.is_a? Hash
97
+ end
98
+
99
+ def content_type_for body_arg
100
+ :json unless body_arg.nil?
101
+ end
102
+ end
103
+ end
@@ -0,0 +1,135 @@
1
+ require 'json'
2
+ require 'uri'
3
+ require 'unirest'
4
+ require 'active_support/core_ext/hash/keys'
5
+
6
+ module Stackd
7
+ module Util
8
+ module HTTP
9
+ def self.get credentials, url
10
+ request_without_body :get, credentials, url
11
+ end
12
+
13
+ def self.post credentials, url, body, content_type
14
+ request_with_body :post, credentials, url, body, content_type
15
+ end
16
+
17
+ def self.put credentials, url, body, content_type
18
+ request_with_body :put, credentials, url, body, content_type
19
+ end
20
+
21
+ def self.patch credentials, url, body, content_type
22
+ request_with_body :patch, credentials, url, body, content_type
23
+ end
24
+
25
+ def self.delete credentials, url
26
+ request_without_body :delete, credentials, url
27
+ end
28
+
29
+ private
30
+
31
+ def self.request_without_body method, credentials, url
32
+ validate_params! credentials, url
33
+
34
+ basic_auth = basic_auth_for credentials
35
+ headers = headers_for credentials
36
+
37
+ res = Unirest.public_send method, url, headers: headers,
38
+ auth: basic_auth
39
+
40
+ process_response res
41
+ end
42
+
43
+ def self.request_with_body method, credentials, url, body, content_type
44
+ validate_params! credentials, url, body, content_type
45
+
46
+ basic_auth = basic_auth_for credentials
47
+ headers = headers_for credentials, content_type
48
+ parameters = parameters_for body, content_type
49
+
50
+ res = Unirest.public_send method, url, headers: headers,
51
+ auth: basic_auth,
52
+ parameters: parameters
53
+
54
+ process_response res
55
+ end
56
+
57
+ def self.validate_params! credentials, url, body = nil, content_type = nil
58
+ unless creds_are_valid? credentials
59
+ raise ArgumentError.new \
60
+ "credentials must be a Stackd::Token, Hash with :user + " + \
61
+ ":password, credentials: #{credentials.inspect}"
62
+ end
63
+
64
+ unless url.is_a? String
65
+ raise ArgumentError.new "url must be String. url: #{url.inspect}"
66
+ end
67
+
68
+ unless body.nil? || body.is_a?(Hash)
69
+ raise ArgumentError.new \
70
+ "body must be a Hash or nil. body: #{body.inspect}"
71
+ end
72
+
73
+ unless content_type.nil? || [:json, :form].include?(content_type)
74
+ raise ArgumentError.new \
75
+ "content_type must be :json, :form, or nil. " + \
76
+ "content_type: #{content_type.inspect}"
77
+ end
78
+ end
79
+
80
+ def self.creds_are_valid? credentials
81
+ credentials.nil? || \
82
+ credentials.is_a?(Client) || \
83
+ credentials.is_a?(Token) || \
84
+ (credentials.is_a?(Hash) && \
85
+ credentials[:user].is_a?(String) && \
86
+ credentials[:password].is_a?(String))
87
+ end
88
+
89
+ def self.basic_auth_for credentials
90
+ if credentials.is_a? Hash
91
+ credentials
92
+ elsif credentials.is_a? Client
93
+ { user: credentials.id, password: credentials.secret }
94
+ end
95
+ end
96
+
97
+ def self.headers_for credentials, content_type = nil
98
+ headers = {}
99
+
100
+ if credentials.is_a? Token
101
+ headers.merge!(
102
+ { 'Authorization' => "Bearer #{credentials.access_token}" })
103
+ end
104
+
105
+ unless content_type.nil?
106
+ content_type_value = content_type == :json \
107
+ ? 'application/json' \
108
+ : 'application/x-www-form-urlencoded'
109
+
110
+ headers.merge!({ 'Content-Type' => content_type_value })
111
+ end
112
+
113
+ headers
114
+ end
115
+
116
+ def self.parameters_for body, content_type
117
+ if content_type == :json
118
+ JSON.generate body
119
+ else
120
+ body
121
+ end
122
+ end
123
+
124
+ def self.process_response res
125
+ if res.code >= 500
126
+ raise ServerError.new res.body.deep_symbolize_keys
127
+ elsif res.code >= 400
128
+ raise Error.new res.body.deep_symbolize_keys
129
+ else
130
+ res.body.deep_symbolize_keys
131
+ end
132
+ end
133
+ end
134
+ end
135
+ end
@@ -0,0 +1,14 @@
1
+ module Stackd
2
+ module Util
3
+ class Portal
4
+ def initialize parent, klass
5
+ @parent = parent
6
+ @klass = klass
7
+ end
8
+
9
+ def method_missing method, *args, &block
10
+ @klass.public_send method, @parent, *args, &block
11
+ end
12
+ end
13
+ end
14
+ end
@@ -1,3 +1,3 @@
1
1
  module Stackd
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.1"
3
3
  end
@@ -1,18 +1,31 @@
1
- # -*- encoding: utf-8 -*-
2
- $:.push File.expand_path("../lib", __FILE__)
3
- require "stackd/version"
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'stackd/version'
4
5
 
5
- Gem::Specification.new do |s|
6
- s.name = "stackd"
7
- s.version = Stackd::VERSION
8
- s.author = "Stackd"
9
- s.email = "support@stackd.com"
10
- s.homepage = "https://stackd.com/"
11
- s.summary = %q{Stackd gem}
12
- s.description = %q{Stackd gem}
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "stackd"
8
+ spec.version = Stackd::VERSION
9
+ spec.authors = ["Stephen Ausman"]
10
+ spec.email = ["stephen@stackd.com"]
13
11
 
14
- s.files = `git ls-files`.split("\n")
15
- s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
- s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
- s.require_paths = ["lib"]
12
+ spec.summary = "Stackd Ruby client"
13
+ spec.description = "https://stackd.com"
14
+ spec.homepage = "https://github.com/stackd/stackd-ruby"
15
+ spec.license = "Unlicense"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject { |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ }
20
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
21
+ spec.require_paths = ["lib"]
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.8"
24
+ spec.add_development_dependency "rake", "~> 10.0"
25
+ spec.add_development_dependency "rspec", "~> 3.2"
26
+ spec.add_development_dependency "webmock", "~> 1.21"
27
+
28
+ spec.add_dependency "unirest", "~> 1.1"
29
+ spec.add_dependency "addressable", "~> 2.3"
30
+ spec.add_dependency "activesupport", "~> 4.2"
18
31
  end
metadata CHANGED
@@ -1,50 +1,174 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: stackd
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
5
- prerelease:
4
+ version: 0.1.1
6
5
  platform: ruby
7
6
  authors:
8
- - Stackd
7
+ - Stephen Ausman
9
8
  autorequire:
10
9
  bindir: bin
11
10
  cert_chain: []
12
- date: 2012-02-09 00:00:00.000000000 Z
13
- dependencies: []
14
- description: Stackd gem
15
- email: support@stackd.com
11
+ date: 2015-12-21 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.8'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.8'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: webmock
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.21'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.21'
69
+ - !ruby/object:Gem::Dependency
70
+ name: unirest
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '1.1'
76
+ type: :runtime
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '1.1'
83
+ - !ruby/object:Gem::Dependency
84
+ name: addressable
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '2.3'
90
+ type: :runtime
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '2.3'
97
+ - !ruby/object:Gem::Dependency
98
+ name: activesupport
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: '4.2'
104
+ type: :runtime
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: '4.2'
111
+ description: https://stackd.com
112
+ email:
113
+ - stephen@stackd.com
16
114
  executables: []
17
115
  extensions: []
18
116
  extra_rdoc_files: []
19
117
  files:
20
- - .gitignore
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".travis.yml"
21
121
  - Gemfile
122
+ - LICENSE
123
+ - README.md
22
124
  - Rakefile
125
+ - bin/bundler
126
+ - bin/console
127
+ - bin/htmldiff
128
+ - bin/ldiff
129
+ - bin/rake
130
+ - bin/rdoc
131
+ - bin/restclient
132
+ - bin/ri
133
+ - bin/rspec
134
+ - bin/safe_yaml
135
+ - bin/setup
136
+ - bin/stackd
23
137
  - lib/stackd.rb
138
+ - lib/stackd/auth_request.rb
139
+ - lib/stackd/client.rb
140
+ - lib/stackd/concerns/require_attr.rb
141
+ - lib/stackd/concerns/tattr_accessor.rb
142
+ - lib/stackd/config.rb
143
+ - lib/stackd/error.rb
144
+ - lib/stackd/server_error.rb
145
+ - lib/stackd/token.rb
146
+ - lib/stackd/util/http.rb
147
+ - lib/stackd/util/portal.rb
24
148
  - lib/stackd/version.rb
25
149
  - stackd.gemspec
26
- homepage: https://stackd.com/
27
- licenses: []
150
+ homepage: https://github.com/stackd/stackd-ruby
151
+ licenses:
152
+ - Unlicense
153
+ metadata: {}
28
154
  post_install_message:
29
155
  rdoc_options: []
30
156
  require_paths:
31
157
  - lib
32
158
  required_ruby_version: !ruby/object:Gem::Requirement
33
- none: false
34
159
  requirements:
35
- - - ! '>='
160
+ - - ">="
36
161
  - !ruby/object:Gem::Version
37
162
  version: '0'
38
163
  required_rubygems_version: !ruby/object:Gem::Requirement
39
- none: false
40
164
  requirements:
41
- - - ! '>='
165
+ - - ">="
42
166
  - !ruby/object:Gem::Version
43
167
  version: '0'
44
168
  requirements: []
45
169
  rubyforge_project:
46
- rubygems_version: 1.8.15
170
+ rubygems_version: 2.4.5.1
47
171
  signing_key:
48
- specification_version: 3
49
- summary: Stackd gem
172
+ specification_version: 4
173
+ summary: Stackd Ruby client
50
174
  test_files: []