the_garage 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +162 -0
- data/Rakefile +33 -0
- data/app/assets/javascripts/garage/application.js +16 -0
- data/app/assets/javascripts/garage/docs/console.js.coffee +90 -0
- data/app/assets/javascripts/garage/docs/jquery.colorbox.js +1026 -0
- data/app/assets/stylesheets/garage/application.css +14 -0
- data/app/assets/stylesheets/garage/colorbox.scss +62 -0
- data/app/assets/stylesheets/garage/style.scss +59 -0
- data/app/assets/stylesheets/vendor/bootstrap.min.css +9 -0
- data/app/controllers/garage/application_controller.rb +4 -0
- data/app/controllers/garage/docs/resources_controller.rb +103 -0
- data/app/controllers/garage/meta/docs_controller.rb +20 -0
- data/app/controllers/garage/meta/services_controller.rb +20 -0
- data/app/helpers/garage/application_helper.rb +4 -0
- data/app/helpers/garage/docs/resources_helper.rb +24 -0
- data/app/models/garage/hash_representer.rb +11 -0
- data/app/views/garage/docs/resources/_layout_navigation.html.haml +5 -0
- data/app/views/garage/docs/resources/_navigation.html.haml +6 -0
- data/app/views/garage/docs/resources/callback.html.haml +5 -0
- data/app/views/garage/docs/resources/console.html.haml +45 -0
- data/app/views/garage/docs/resources/index.html.haml +2 -0
- data/app/views/garage/docs/resources/show.html.haml +16 -0
- data/app/views/layouts/garage/application.html.haml +26 -0
- data/config/routes.rb +0 -0
- data/lib/garage/app_responder.rb +22 -0
- data/lib/garage/authorizable.rb +26 -0
- data/lib/garage/config.rb +76 -0
- data/lib/garage/controller_helper.rb +110 -0
- data/lib/garage/docs/anchor_building.rb +28 -0
- data/lib/garage/docs/application.rb +24 -0
- data/lib/garage/docs/config.rb +61 -0
- data/lib/garage/docs/console_link_building.rb +14 -0
- data/lib/garage/docs/document.rb +141 -0
- data/lib/garage/docs/engine.rb +35 -0
- data/lib/garage/docs/example.rb +26 -0
- data/lib/garage/docs/renderer.rb +17 -0
- data/lib/garage/docs/toc_renderer.rb +14 -0
- data/lib/garage/docs.rb +9 -0
- data/lib/garage/exceptions.rb +49 -0
- data/lib/garage/hypermedia_filter.rb +44 -0
- data/lib/garage/hypermedia_responder.rb +120 -0
- data/lib/garage/meta/engine.rb +16 -0
- data/lib/garage/meta/remote_service.rb +78 -0
- data/lib/garage/meta.rb +6 -0
- data/lib/garage/meta_resource.rb +17 -0
- data/lib/garage/nested_field_query.rb +183 -0
- data/lib/garage/optional_response_body_responder.rb +16 -0
- data/lib/garage/paginating_responder.rb +113 -0
- data/lib/garage/permission.rb +13 -0
- data/lib/garage/permissions.rb +75 -0
- data/lib/garage/representer.rb +214 -0
- data/lib/garage/resource_casting_responder.rb +13 -0
- data/lib/garage/restful_actions.rb +219 -0
- data/lib/garage/strategy/access_token.rb +57 -0
- data/lib/garage/strategy/auth_server.rb +200 -0
- data/lib/garage/strategy/no_authentication.rb +13 -0
- data/lib/garage/strategy/test.rb +44 -0
- data/lib/garage/strategy.rb +4 -0
- data/lib/garage/test/migrator.rb +31 -0
- data/lib/garage/token_scope.rb +134 -0
- data/lib/garage/utils.rb +28 -0
- data/lib/garage/version.rb +3 -0
- data/lib/garage.rb +23 -0
- metadata +275 -0
@@ -0,0 +1,35 @@
|
|
1
|
+
require "rails"
|
2
|
+
require "haml"
|
3
|
+
require "sass-rails"
|
4
|
+
require "coffee-rails"
|
5
|
+
require "redcarpet"
|
6
|
+
|
7
|
+
require "garage/docs/anchor_building"
|
8
|
+
require "garage/docs/console_link_building"
|
9
|
+
require "garage/docs/renderer"
|
10
|
+
require "garage/docs/toc_renderer"
|
11
|
+
require "garage/docs/application"
|
12
|
+
require "garage/docs/document"
|
13
|
+
require "garage/docs/example"
|
14
|
+
|
15
|
+
module Garage
|
16
|
+
module Docs
|
17
|
+
class Engine < ::Rails::Engine
|
18
|
+
isolate_namespace Garage::Docs
|
19
|
+
|
20
|
+
initializer "garage.docs.engine.routes" do
|
21
|
+
Engine.routes.append do
|
22
|
+
root :to => 'resources#index', as: nil
|
23
|
+
resources :resources do
|
24
|
+
collection do
|
25
|
+
get 'console'
|
26
|
+
post 'authenticate'
|
27
|
+
get 'callback'
|
28
|
+
post 'callback'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Garage
|
2
|
+
module Docs
|
3
|
+
class Example
|
4
|
+
def self.build_all(controller, examples)
|
5
|
+
examples.compact.map do |resource|
|
6
|
+
new(resource, controller)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def initialize(resource, controller)
|
11
|
+
@resource, @controller = resource, controller
|
12
|
+
end
|
13
|
+
|
14
|
+
def url
|
15
|
+
if @resource.is_a?(String)
|
16
|
+
@resource
|
17
|
+
elsif @resource.respond_to?(:to_proc)
|
18
|
+
@resource.to_proc.call(@controller.main_app)
|
19
|
+
else
|
20
|
+
@resource.represent!
|
21
|
+
@resource.link_path_for(:self)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Garage
|
2
|
+
module Docs
|
3
|
+
class Renderer < ::Redcarpet::Render::HTML
|
4
|
+
include ConsoleLinkBuilding
|
5
|
+
include AnchorBuilding
|
6
|
+
|
7
|
+
def header(text, header_level)
|
8
|
+
if header_level == 2
|
9
|
+
id = to_anchor(text)
|
10
|
+
%!<a href="##{id}"><h#{header_level} id="#{id}">#{text} #{build_console_link(text)}</h#{header_level}></a>!
|
11
|
+
else
|
12
|
+
%!<h#{header_level}>#{text}</h#{header_level}>!
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Garage
|
2
|
+
module Docs
|
3
|
+
class TocRenderer < ::Redcarpet::Render::HTML_TOC
|
4
|
+
include ConsoleLinkBuilding
|
5
|
+
include AnchorBuilding
|
6
|
+
|
7
|
+
def header(text, header_level)
|
8
|
+
return if header_level > 2
|
9
|
+
|
10
|
+
%'<li><a href="##{to_anchor(text)}">#{text}</a> #{build_console_link(text)}</li>'
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
data/lib/garage/docs.rb
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
module Garage
|
2
|
+
class HTTPError < ::StandardError
|
3
|
+
attr_accessor :status
|
4
|
+
def status_code
|
5
|
+
Rack::Utils.status_code(status)
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class BadRequest < HTTPError
|
10
|
+
def initialize(error)
|
11
|
+
@status = :bad_request
|
12
|
+
super(error)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
class Unauthorized < HTTPError
|
17
|
+
attr_reader :user, :action, :resource_class, :scopes
|
18
|
+
|
19
|
+
def initialize(user, action, resource_class, status = :forbidden, scopes = [])
|
20
|
+
@user, @action, @resource_class, @status, @scopes = user, action, resource_class, status, scopes
|
21
|
+
|
22
|
+
if scopes.empty?
|
23
|
+
super "Authorized user is not allowed to take the requested operation #{action} on #{resource_class}"
|
24
|
+
else
|
25
|
+
super "Insufficient scope to process the requested operation. Missing scope(s): #{scopes.join(", ")}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class PermissionError < Unauthorized; end
|
31
|
+
class MissingScopeError < Unauthorized; end
|
32
|
+
|
33
|
+
class AuthBackendTimeout < HTTPError
|
34
|
+
def initialize(open_timeout, read_timeout)
|
35
|
+
@status = :internal_server_error
|
36
|
+
super("Auth backend timed out: open_timeout=#{open_timeout}, read_timeout=#{read_timeout}")
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
class AuthBackendError < HTTPError
|
41
|
+
attr_reader :response
|
42
|
+
|
43
|
+
def initialize(response)
|
44
|
+
@status = :internal_server_error
|
45
|
+
@response = response
|
46
|
+
super("Auth backend responded error: status=#{response.status_code}")
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Garage
|
2
|
+
class HypermediaFilter
|
3
|
+
MIME_DICT = %r[application/vnd\.cookpad\.dictionary\+(json|x-msgpack)]
|
4
|
+
|
5
|
+
def self.before(controller)
|
6
|
+
helper = new(controller)
|
7
|
+
controller.representation = helper.dictionary_representation if helper.has_dictionary_mime_type?
|
8
|
+
controller.request.format = helper.dictionary_request_format if helper.has_dictionary_mime_type?
|
9
|
+
controller.field_selector = helper.field_selector
|
10
|
+
rescue Garage::NestedFieldQuery::InvalidQuery
|
11
|
+
raise Garage::BadRequest, "Invalid query in ?fields="
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :controller
|
15
|
+
|
16
|
+
def initialize(controller)
|
17
|
+
@controller = controller
|
18
|
+
end
|
19
|
+
|
20
|
+
def field_selector
|
21
|
+
Garage::NestedFieldQuery::Selector.build(fields_param)
|
22
|
+
end
|
23
|
+
|
24
|
+
def fields_param
|
25
|
+
controller.params[:fields]
|
26
|
+
end
|
27
|
+
|
28
|
+
def dictionary_representation
|
29
|
+
:dictionary
|
30
|
+
end
|
31
|
+
|
32
|
+
def dictionary_request_format
|
33
|
+
dictionary_match_data[1].sub(/^x-/, "").to_sym
|
34
|
+
end
|
35
|
+
|
36
|
+
def has_dictionary_mime_type?
|
37
|
+
dictionary_match_data
|
38
|
+
end
|
39
|
+
|
40
|
+
def dictionary_match_data
|
41
|
+
@dictionary_match_data ||= controller.request.format.to_s.match(MIME_DICT)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,120 @@
|
|
1
|
+
require 'oj'
|
2
|
+
|
3
|
+
module Garage
|
4
|
+
module HypermediaResponder
|
5
|
+
def display(resource, given_options={})
|
6
|
+
given_options[:content_type] = representation.content_type if representation.dictionary?
|
7
|
+
super(render(transform(resource)), given_options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def render(data)
|
11
|
+
DataRenderer.render(data, dictionary: representation.dictionary?)
|
12
|
+
end
|
13
|
+
|
14
|
+
def transform(resources)
|
15
|
+
if resources.respond_to?(:map) && resources.respond_to?(:to_a)
|
16
|
+
resources.map {|resource| encode_to_hash(resource, partial: true) }
|
17
|
+
else
|
18
|
+
encode_to_hash(resources)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Cache resource encoding result if the resource can be identified.
|
23
|
+
# Garage try to get resource identifier by acccess the resource
|
24
|
+
# `#resource_identifier` or `#id`.
|
25
|
+
def encode_to_hash(resource, *args)
|
26
|
+
if id = get_resource_identifier(resource)
|
27
|
+
cache_key = "#{resource.class.name}:#{id}"
|
28
|
+
cache[cache_key] ||= _encode_to_hash(resource, *args)
|
29
|
+
else
|
30
|
+
_encode_to_hash(resource, *args)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def _encode_to_hash(resource, options = {})
|
37
|
+
resource.represent!
|
38
|
+
resource.params = controller.params.slice(*resource.class.params)
|
39
|
+
resource.partial = options[:partial]
|
40
|
+
resource.selector = options[:selector] || controller.field_selector
|
41
|
+
resource.render_hash(:responder => self)
|
42
|
+
end
|
43
|
+
|
44
|
+
def cache
|
45
|
+
@cache ||= {}
|
46
|
+
end
|
47
|
+
|
48
|
+
def get_resource_identifier(resource)
|
49
|
+
case
|
50
|
+
when resource.respond_to?(:resource_identifier)
|
51
|
+
resource.resource_identifier
|
52
|
+
when resource.respond_to?(:id)
|
53
|
+
resource.id
|
54
|
+
else
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def representation
|
60
|
+
@representation ||= Representation.new(controller)
|
61
|
+
end
|
62
|
+
|
63
|
+
class Representation
|
64
|
+
attr_reader :controller
|
65
|
+
|
66
|
+
def initialize(controller)
|
67
|
+
@controller = controller
|
68
|
+
end
|
69
|
+
|
70
|
+
def dictionary?
|
71
|
+
controller.representation == :dictionary
|
72
|
+
end
|
73
|
+
|
74
|
+
def content_type
|
75
|
+
mime, payload = controller.request.format.to_s.split("/", 2)
|
76
|
+
"#{mime}/vnd.cookpad.dictionary+#{payload}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
class DataRenderer
|
81
|
+
JSON_ESCAPE_TABLE = { "<" => '\u003C', ">" => '\u003E' }.freeze
|
82
|
+
|
83
|
+
def self.render(*args)
|
84
|
+
new(*args).render
|
85
|
+
end
|
86
|
+
|
87
|
+
attr_reader :data, :options
|
88
|
+
|
89
|
+
def initialize(data, options = {})
|
90
|
+
@data, @options = data, options
|
91
|
+
end
|
92
|
+
|
93
|
+
def render
|
94
|
+
Oj.dump(converted_data, mode: :compat).gsub(/([<>])/, JSON_ESCAPE_TABLE)
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def dictionary?
|
100
|
+
!!options[:dictionary]
|
101
|
+
end
|
102
|
+
|
103
|
+
def convertible_to_dictionary?
|
104
|
+
dictionary? && data.is_a?(Array) && data.all? {|datum| datum.respond_to?(:[]) }
|
105
|
+
end
|
106
|
+
|
107
|
+
def indexed_data
|
108
|
+
data.index_by {|datum| datum["id"] }
|
109
|
+
end
|
110
|
+
|
111
|
+
def converted_data
|
112
|
+
if convertible_to_dictionary?
|
113
|
+
indexed_data
|
114
|
+
else
|
115
|
+
data
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "garage/meta/remote_service"
|
2
|
+
|
3
|
+
module Garage
|
4
|
+
module Meta
|
5
|
+
class Engine < ::Rails::Engine
|
6
|
+
isolate_namespace Garage::Meta
|
7
|
+
|
8
|
+
initializer "garage.meta.engine.routes" do
|
9
|
+
Engine.routes.append do
|
10
|
+
resources :services
|
11
|
+
resources :docs
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Garage
|
2
|
+
module Meta
|
3
|
+
class RemoteService
|
4
|
+
class << self
|
5
|
+
def configure(&block)
|
6
|
+
@config = Config.new
|
7
|
+
@config.instance_eval(&block)
|
8
|
+
end
|
9
|
+
|
10
|
+
def configuration
|
11
|
+
@config or raise "Garage::Meta::RemoteService.configure must be called in initializer"
|
12
|
+
end
|
13
|
+
|
14
|
+
def all
|
15
|
+
configuration.services
|
16
|
+
end
|
17
|
+
|
18
|
+
def build_permissions(perms, other, target)
|
19
|
+
perms.permits! :read
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
include Garage::Representer
|
24
|
+
include Garage::Authorizable
|
25
|
+
|
26
|
+
property :namespace
|
27
|
+
property :name
|
28
|
+
property :endpoint
|
29
|
+
property :alternate_endpoints
|
30
|
+
|
31
|
+
attr_accessor :namespace, :name, :endpoint
|
32
|
+
|
33
|
+
def alternate_endpoints
|
34
|
+
@alternate_endpoints ||= {}
|
35
|
+
end
|
36
|
+
|
37
|
+
class Config
|
38
|
+
attr_reader :services
|
39
|
+
|
40
|
+
def initialize
|
41
|
+
@services = []
|
42
|
+
end
|
43
|
+
|
44
|
+
def service(&block)
|
45
|
+
service = ServiceDSL.new
|
46
|
+
service.instance_eval(&block)
|
47
|
+
@services << service.build
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
class ServiceDSL
|
52
|
+
def initialize
|
53
|
+
@service = RemoteService.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def namespace(arg)
|
57
|
+
@service.namespace = arg.to_s
|
58
|
+
end
|
59
|
+
|
60
|
+
def name(arg)
|
61
|
+
@service.name = arg
|
62
|
+
end
|
63
|
+
|
64
|
+
def endpoint(arg)
|
65
|
+
@service.endpoint = arg
|
66
|
+
end
|
67
|
+
|
68
|
+
def alternate_endpoint(rel, url)
|
69
|
+
@service.alternate_endpoints[rel] = url
|
70
|
+
end
|
71
|
+
|
72
|
+
def build
|
73
|
+
@service
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/garage/meta.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# Public: proxy object to handle model Class as a resource
|
2
|
+
module Garage
|
3
|
+
class MetaResource
|
4
|
+
include Garage::Authorizable
|
5
|
+
|
6
|
+
attr_reader :resource_class
|
7
|
+
|
8
|
+
def initialize(resource_class, args = {})
|
9
|
+
@resource_class = resource_class
|
10
|
+
@args = args
|
11
|
+
end
|
12
|
+
|
13
|
+
def build_permissions(perms, user)
|
14
|
+
resource_class.build_permissions(perms, user, @args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|