stackd 0.0.1 → 0.1.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.
@@ -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: []