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.
Files changed (66) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +162 -0
  4. data/Rakefile +33 -0
  5. data/app/assets/javascripts/garage/application.js +16 -0
  6. data/app/assets/javascripts/garage/docs/console.js.coffee +90 -0
  7. data/app/assets/javascripts/garage/docs/jquery.colorbox.js +1026 -0
  8. data/app/assets/stylesheets/garage/application.css +14 -0
  9. data/app/assets/stylesheets/garage/colorbox.scss +62 -0
  10. data/app/assets/stylesheets/garage/style.scss +59 -0
  11. data/app/assets/stylesheets/vendor/bootstrap.min.css +9 -0
  12. data/app/controllers/garage/application_controller.rb +4 -0
  13. data/app/controllers/garage/docs/resources_controller.rb +103 -0
  14. data/app/controllers/garage/meta/docs_controller.rb +20 -0
  15. data/app/controllers/garage/meta/services_controller.rb +20 -0
  16. data/app/helpers/garage/application_helper.rb +4 -0
  17. data/app/helpers/garage/docs/resources_helper.rb +24 -0
  18. data/app/models/garage/hash_representer.rb +11 -0
  19. data/app/views/garage/docs/resources/_layout_navigation.html.haml +5 -0
  20. data/app/views/garage/docs/resources/_navigation.html.haml +6 -0
  21. data/app/views/garage/docs/resources/callback.html.haml +5 -0
  22. data/app/views/garage/docs/resources/console.html.haml +45 -0
  23. data/app/views/garage/docs/resources/index.html.haml +2 -0
  24. data/app/views/garage/docs/resources/show.html.haml +16 -0
  25. data/app/views/layouts/garage/application.html.haml +26 -0
  26. data/config/routes.rb +0 -0
  27. data/lib/garage/app_responder.rb +22 -0
  28. data/lib/garage/authorizable.rb +26 -0
  29. data/lib/garage/config.rb +76 -0
  30. data/lib/garage/controller_helper.rb +110 -0
  31. data/lib/garage/docs/anchor_building.rb +28 -0
  32. data/lib/garage/docs/application.rb +24 -0
  33. data/lib/garage/docs/config.rb +61 -0
  34. data/lib/garage/docs/console_link_building.rb +14 -0
  35. data/lib/garage/docs/document.rb +141 -0
  36. data/lib/garage/docs/engine.rb +35 -0
  37. data/lib/garage/docs/example.rb +26 -0
  38. data/lib/garage/docs/renderer.rb +17 -0
  39. data/lib/garage/docs/toc_renderer.rb +14 -0
  40. data/lib/garage/docs.rb +9 -0
  41. data/lib/garage/exceptions.rb +49 -0
  42. data/lib/garage/hypermedia_filter.rb +44 -0
  43. data/lib/garage/hypermedia_responder.rb +120 -0
  44. data/lib/garage/meta/engine.rb +16 -0
  45. data/lib/garage/meta/remote_service.rb +78 -0
  46. data/lib/garage/meta.rb +6 -0
  47. data/lib/garage/meta_resource.rb +17 -0
  48. data/lib/garage/nested_field_query.rb +183 -0
  49. data/lib/garage/optional_response_body_responder.rb +16 -0
  50. data/lib/garage/paginating_responder.rb +113 -0
  51. data/lib/garage/permission.rb +13 -0
  52. data/lib/garage/permissions.rb +75 -0
  53. data/lib/garage/representer.rb +214 -0
  54. data/lib/garage/resource_casting_responder.rb +13 -0
  55. data/lib/garage/restful_actions.rb +219 -0
  56. data/lib/garage/strategy/access_token.rb +57 -0
  57. data/lib/garage/strategy/auth_server.rb +200 -0
  58. data/lib/garage/strategy/no_authentication.rb +13 -0
  59. data/lib/garage/strategy/test.rb +44 -0
  60. data/lib/garage/strategy.rb +4 -0
  61. data/lib/garage/test/migrator.rb +31 -0
  62. data/lib/garage/token_scope.rb +134 -0
  63. data/lib/garage/utils.rb +28 -0
  64. data/lib/garage/version.rb +3 -0
  65. data/lib/garage.rb +23 -0
  66. 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
@@ -0,0 +1,9 @@
1
+ require 'garage/docs/engine'
2
+
3
+ module Garage
4
+ module Docs
5
+ def self.application
6
+ @application ||= Garage::Docs::Application.new(Rails.application)
7
+ end
8
+ end
9
+ end
@@ -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
@@ -0,0 +1,6 @@
1
+ require 'garage/meta/engine'
2
+
3
+ module Garage
4
+ module Meta
5
+ end
6
+ end
@@ -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