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
data/config/routes.rb ADDED
File without changes
@@ -0,0 +1,22 @@
1
+ if Rails::VERSION::MAJOR > 4 ||
2
+ (Rails::VERSION::MAJOR == 4 && Rails::VERSION::MINOR >= 2)
3
+ require 'action_controller/responder'
4
+ else
5
+ require 'action_controller'
6
+ end
7
+
8
+ require "garage/hypermedia_responder"
9
+ require "garage/resource_casting_responder"
10
+ require "garage/paginating_responder"
11
+ require "garage/optional_response_body_responder"
12
+
13
+ class Garage::AppResponder < ActionController::Responder
14
+ # like Rack middleware, responders are applied outside in, bottom to the top
15
+ include Garage::HypermediaResponder
16
+ include Garage::ResourceCastingResponder
17
+ include Garage::PaginatingResponder
18
+ include Garage::OptionalResponseBodyResponder
19
+
20
+ # in case someone tries to do Object#to_msgpack
21
+ undef_method(:to_msgpack) if method_defined?(:to_msgpack)
22
+ end
@@ -0,0 +1,26 @@
1
+ # Public: include this module to make the resource authorizable in Garage::RestfulActions
2
+ #
3
+ # Examples
4
+ #
5
+ # class Post
6
+ # include Garage::Authorizable
7
+ # def build_permissions(perms, other); end
8
+ # def self.build_permissions(perms, other, target); end
9
+ # end
10
+ module Garage
11
+ module Authorizable
12
+ def build_permissions(perms, subject)
13
+ raise NotImplementedError, "#{self.class}#build_permissions must be implemented"
14
+ end
15
+
16
+ def effective_permissions(subject)
17
+ Garage::Permissions.new(subject, resource_class).tap do |perms|
18
+ build_permissions(perms, subject)
19
+ end
20
+ end
21
+
22
+ def authorize!(subject, action)
23
+ effective_permissions(subject).authorize!(action)
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,76 @@
1
+ require 'garage/docs/config'
2
+
3
+ module Garage
4
+ def self.configure(&block)
5
+ @config = Config::Builder.new(&block).build
6
+ end
7
+
8
+ def self.configuration
9
+ @config
10
+ end
11
+
12
+ class Config
13
+ DEFAULT_RESCUE_ERROR = true
14
+
15
+ attr_writer :cast_resource, :docs, :rescue_error, :strategy, :cache_acceess_token_validation
16
+ attr_accessor :auth_server_url, :auth_server_host, :auth_server_timeout, :ttl_for_access_token_cache
17
+
18
+ def initialize
19
+ @cache_acceess_token_validation = false
20
+ @ttl_for_access_token_cache = 5.minutes
21
+ end
22
+
23
+ # Set false if you want to rescue errors by yourself
24
+ # @return [true, false] A flag to rescue Garage::HTTPError in ControllerHelper (default: true)
25
+ # @example
26
+ # Garage.configuration.rescue_error = false
27
+ def rescue_error
28
+ instance_variable_defined?(:@rescue_error) ? @rescue_error : DEFAULT_RESCUE_ERROR
29
+ end
30
+
31
+ # Set authentication strategy module which must satisfy Strategy interface.
32
+ # @return [Module] A auth strategy. default is NoAuthentication strategy.
33
+ # @example
34
+ # Garage.configuration.strategy = Garage::Strategy::AuthServer
35
+ def strategy
36
+ instance_variable_defined?(:@strategy) ? @strategy : Garage::Strategy::NoAuthentication
37
+ end
38
+
39
+ def docs
40
+ @docs ||= Docs::Config.new
41
+ end
42
+
43
+ def cast_resource
44
+ @cast_resource ||= proc { |resource|
45
+ if resource.respond_to?(:map) && resource.respond_to?(:to_a)
46
+ resource.map(&:to_resource)
47
+ else
48
+ resource.to_resource
49
+ end
50
+ }
51
+ end
52
+
53
+ def cache_acceess_token_validation?
54
+ !!@cache_acceess_token_validation
55
+ end
56
+
57
+ class Builder
58
+ def initialize(&block)
59
+ @config = Config.new
60
+ instance_eval(&block)
61
+ end
62
+
63
+ def build
64
+ @config
65
+ end
66
+
67
+ def cast_resource(&block)
68
+ @config.cast_resource = block
69
+ end
70
+
71
+ def docs
72
+ @docs_builder ||= Docs::Config::Builder.new(@config.docs)
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,110 @@
1
+ module Garage
2
+ module ControllerHelper
3
+ extend ActiveSupport::Concern
4
+ include Utils
5
+
6
+ included do
7
+ use Rack::AcceptDefault
8
+
9
+ around_action :notify_request_stats
10
+
11
+ include Garage.configuration.strategy
12
+
13
+ if Garage.configuration.rescue_error
14
+ rescue_from Garage::HTTPError do |exception|
15
+ render json: { status_code: exception.status_code, error: exception.message }, status: exception.status
16
+ end
17
+ end
18
+
19
+ before_action Garage::HypermediaFilter
20
+
21
+ respond_to :json # , :msgpack
22
+ self.responder = Garage::AppResponder
23
+ end
24
+
25
+ # For backword compatiblility.
26
+ def doorkeeper_token
27
+ access_token
28
+ end
29
+
30
+ def resource_owner_id
31
+ access_token.try(:resource_owner_id)
32
+ end
33
+
34
+ # Use this method to render 'unauthorized'.
35
+ # Garage user may overwrite this method to response custom unauthorized response.
36
+ # @return [Hash]
37
+ def unauthorized_render_options(error: nil)
38
+ { json: { status_code: 401, error: "Unauthorized (invalid token)" } }
39
+ end
40
+
41
+ # Implement by using `resource_owner_id` like:
42
+ #
43
+ # def current_resource_owner
44
+ # @current_resource_owner ||= User.find(resource_owner_id) if resource_owner_id
45
+ # end
46
+ #
47
+ def current_resource_owner
48
+ raise "Your ApplicationController needs to implement current_resource_owner!"
49
+ end
50
+
51
+ # Check if the current resource is the same as the requester.
52
+ # The resource must respond to `resource.id` method.
53
+ def requested_by?(resource)
54
+ user = resource.respond_to?(:owner) ? resource.owner : resource
55
+ case
56
+ when current_resource_owner.nil?
57
+ false
58
+ when !user.is_a?(current_resource_owner.class)
59
+ false
60
+ when current_resource_owner.id == user.id
61
+ true
62
+ else
63
+ false
64
+ end
65
+ end
66
+
67
+ # Public: returns if the current request includes the given OAuth scope
68
+ def has_scope?(scope)
69
+ access_token && access_token.scopes.include?(scope)
70
+ end
71
+
72
+ def cache_context
73
+ { t: access_token.try(:id) }
74
+ end
75
+
76
+ attr_accessor :representation, :field_selector
77
+
78
+ def allow_access?(klass, action = :read)
79
+ ability_from_token.allow?(klass, action)
80
+ end
81
+
82
+ private
83
+
84
+ def ability_from_token
85
+ Garage::TokenScope.ability(current_resource_owner, access_token.try(:scopes) || [])
86
+ end
87
+
88
+ def notify_request_stats
89
+ yield
90
+ ensure
91
+ begin
92
+ payload = {
93
+ :controller => self,
94
+ :token => access_token,
95
+ :resource_owner => current_resource_owner,
96
+ }
97
+ ActiveSupport::Notifications.instrument("garage.request", payload)
98
+ rescue Exception
99
+ end
100
+ end
101
+
102
+ def verify_auth
103
+ if !access_token || !access_token.accessible?
104
+ error_status = :unauthorized
105
+ options = unauthorized_render_options
106
+ render options.merge(status: error_status)
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,28 @@
1
+ module Garage
2
+ module Docs
3
+ module AnchorBuilding
4
+ def preprocess(full_document)
5
+ reset
6
+ full_document
7
+ end
8
+
9
+ def postprocess(full_document)
10
+ reset
11
+ full_document
12
+ end
13
+
14
+ private
15
+
16
+ def reset
17
+ @anchors = Hash.new(0)
18
+ end
19
+
20
+ def to_anchor(text)
21
+ unique_text = text + @anchors[text].to_s
22
+ @anchors[text] += 1
23
+
24
+ unique_text.gsub(/\s+/, '-').gsub(/<\/?[^>]*>/, '').downcase
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,24 @@
1
+ module Garage
2
+ module Docs
3
+ class Application
4
+ def initialize(application)
5
+ @application = application
6
+ end
7
+
8
+ def name
9
+ @application.class.name.split("::")[0]
10
+ end
11
+
12
+ def documents
13
+ cached = Garage.configuration.docs.docs_cache_enabled
14
+ @documents ||= pathnames.map {|pathname| Garage::Docs::Document.new(pathname, cached) }
15
+ end
16
+
17
+ private
18
+
19
+ def pathnames
20
+ Pathname.glob("#{Garage.configuration.docs.document_root}/resources/**/*.md").sort
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,61 @@
1
+ module Garage
2
+ module Docs
3
+ class Config
4
+ attr_accessor :document_root, :current_user_method, :authenticate,
5
+ :console_app_uid, :console_app_secret, :remote_server,
6
+ :docs_authorization_method, :docs_cache_enabled
7
+
8
+ def initialize
9
+ reset
10
+ end
11
+
12
+ def reset
13
+ @document_root = Rails.root.join('doc/garage')
14
+ @current_user_method = Proc.new { current_user }
15
+ @authenticate = Proc.new {}
16
+ @console_app_uid = ''
17
+ @remote_server = Proc.new {|request| "#{request.protocol}#{request.host_with_port}" }
18
+ @docs_authorization_method = nil
19
+ @docs_cache_enabled = true
20
+ end
21
+
22
+ class Builder
23
+ def initialize(config)
24
+ @config = config
25
+ end
26
+
27
+ def document_root=(value)
28
+ @config.document_root = value
29
+ end
30
+
31
+ def current_user_method(&block)
32
+ @config.current_user_method = block
33
+ end
34
+
35
+ def authenticate(&block)
36
+ @config.authenticate = block
37
+ end
38
+
39
+ def console_app_uid=(value)
40
+ @config.console_app_uid = value
41
+ end
42
+
43
+ def console_app_secret=(value)
44
+ @config.console_app_secret = value
45
+ end
46
+
47
+ def remote_server=(value)
48
+ @config.remote_server = value
49
+ end
50
+
51
+ def docs_cache_enabled=(value)
52
+ @config.docs_cache_enabled = value
53
+ end
54
+
55
+ def docs_authorization_method(&block)
56
+ @config.docs_authorization_method = block
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,14 @@
1
+ module Garage
2
+ module Docs
3
+ module ConsoleLinkBuilding
4
+ def build_console_link(text)
5
+ if text.match(/^(POST|GET|PUT|DELETE)\s+(\/.*)$/)
6
+ query = Rack::Utils.build_query(method: $1, location: $2)
7
+ "<a href='console?#{query}'><small>(console)</small></a>"
8
+ else
9
+ ''
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,141 @@
1
+ module Garage
2
+ module Docs
3
+ class Document
4
+ class << self
5
+ def all
6
+ application.documents
7
+ end
8
+
9
+ def find_by_name(name)
10
+ all.find {|document| document.name == name }
11
+ end
12
+
13
+ def renderer
14
+ @renderer ||= Redcarpet::Markdown.new(
15
+ Garage::Docs::Renderer.new(with_toc_data: true),
16
+ fenced_code_blocks: true,
17
+ no_intra_emphasis: true,
18
+ tables: true,
19
+ )
20
+ end
21
+
22
+ def toc_renderer
23
+ @toc_renderer ||= Redcarpet::Markdown.new(
24
+ Garage::Docs::TocRenderer.new,
25
+ no_intra_emphasis: true
26
+ )
27
+ end
28
+
29
+ def build_permissions(perms, other, target)
30
+ perms.permits! :read
31
+ end
32
+
33
+ private
34
+
35
+ def application
36
+ Garage::Docs.application
37
+ end
38
+ end
39
+
40
+ include Garage::Authorizable
41
+ include Garage::Representer
42
+
43
+ property :name
44
+ property :toc
45
+ property :rendered_body
46
+
47
+ attr_reader :pathname
48
+ attr_accessor :cached
49
+
50
+ def initialize(pathname, cached = false)
51
+ @pathname = pathname
52
+ @cached = cached
53
+ end
54
+
55
+ def name
56
+ relative_base_name.to_s.gsub('/', '-')
57
+ end
58
+
59
+ def humanized_name
60
+ name.split('-').map(&:humanize).join(' / ')
61
+ end
62
+
63
+ def cache_key(type)
64
+ "garage-doc-#{type}-#{pathname}"
65
+ end
66
+
67
+ def toc
68
+ if cached
69
+ Rails.cache.fetch(cache_key(:toc)) do
70
+ self.class.toc_renderer.render(body).html_safe
71
+ end
72
+ else
73
+ self.class.toc_renderer.render(body).html_safe
74
+ end
75
+ end
76
+
77
+ def render
78
+ if cached
79
+ Rails.cache.fetch(cache_key(:render)) do
80
+ self.class.renderer.render(body).html_safe
81
+ end
82
+ else
83
+ self.class.renderer.render(body).html_safe
84
+ end
85
+ end
86
+
87
+ alias :rendered_body :render
88
+
89
+ def body
90
+ pathname.read
91
+ end
92
+
93
+ def resource_class
94
+ @resource_class ||= extract_resource_class || relative_base_name.camelize.singularize.constantize
95
+ rescue NameError
96
+ nil
97
+ end
98
+
99
+ def examples(*args)
100
+ if resource_class && resource_class.respond_to?(:garage_examples)
101
+ resource_class.garage_examples(*args)
102
+ else
103
+ []
104
+ end
105
+ end
106
+
107
+ # If you need authentication logic,
108
+ # assign a Proc to Garage.docs.configuration.docs_authorization_method.
109
+ #
110
+ # Example:
111
+ #
112
+ # Garage.docs.configuration.docs_authorization_method do |args|
113
+ # if name.start_with?("admin_")
114
+ # args[:user].admin?
115
+ # else
116
+ # true
117
+ # end
118
+ # end
119
+ #
120
+ def visible_to?(user)
121
+ if method = Garage.configuration.docs.docs_authorization_method
122
+ method.call(document: self, user: user)
123
+ else
124
+ true
125
+ end
126
+ end
127
+
128
+ private
129
+
130
+ def extract_resource_class
131
+ if /<!-- resource_class: (\S+)/ === body
132
+ $1.constantize
133
+ end
134
+ end
135
+
136
+ def relative_base_name
137
+ pathname.relative_path_from(Pathname.new("#{Garage.configuration.docs.document_root}/resources")).to_s.sub('.md', '')
138
+ end
139
+ end
140
+ end
141
+ end