apicasso 0.4.5 → 0.4.6
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 +5 -5
- data/README.md +226 -223
- data/Rakefile +23 -23
- data/app/controllers/apicasso/apidocs_controller.rb +309 -299
- data/app/controllers/apicasso/application_controller.rb +170 -147
- data/app/controllers/apicasso/crud_controller.rb +246 -245
- data/app/controllers/concerns/orderable.rb +45 -45
- data/app/models/apicasso/ability.rb +38 -38
- data/app/models/apicasso/application_record.rb +6 -6
- data/app/models/apicasso/key.rb +25 -25
- data/app/models/apicasso/request.rb +8 -8
- data/config/routes.rb +14 -14
- data/lib/apicasso/active_record_extension.rb +0 -44
- data/lib/apicasso/engine.rb +13 -13
- data/lib/apicasso/version.rb +3 -3
- data/lib/apicasso.rb +15 -13
- data/lib/generators/apicasso/install/install_generator.rb +25 -25
- data/lib/generators/apicasso/install/templates/create_apicasso_tables.rb +20 -20
- data/spec/dummy/Gemfile +56 -0
- data/spec/dummy/Gemfile.lock +237 -0
- data/spec/dummy/app/controllers/application_controller.rb +1 -1
- data/spec/dummy/app/models/used_model.rb +42 -0
- data/spec/dummy/app/serializers/used_model_serializer.rb +3 -0
- data/spec/dummy/bin/rails +5 -0
- data/spec/dummy/bin/rake +5 -0
- data/spec/dummy/bin/setup +0 -3
- data/spec/dummy/bin/spring +17 -0
- data/spec/dummy/bin/update +0 -3
- data/spec/dummy/config/application.rb +14 -10
- data/spec/dummy/config/cable.yml +1 -1
- data/spec/dummy/config/credentials.yml.enc +1 -0
- data/spec/dummy/config/database.yml +5 -14
- data/spec/dummy/config/environments/development.rb +6 -10
- data/spec/dummy/config/environments/production.rb +1 -10
- data/spec/dummy/config/initializers/cors.rb +16 -0
- data/spec/dummy/config/locales/en.yml +7 -32
- data/spec/dummy/config/routes.rb +1 -1
- data/{db/migrate/20180826141433_create_apicasso_tables.rb → spec/dummy/db/migrate/20180918134607_create_apicasso_tables.rb} +1 -0
- data/spec/dummy/db/migrate/20180918141254_create_used_models.rb +44 -0
- data/spec/dummy/db/migrate/20180919130152_create_active_storage_tables.active_storage.rb +26 -0
- data/spec/dummy/db/migrate/20180920133933_change_used_model_to_validates.rb +7 -0
- data/spec/dummy/db/schema.rb +98 -0
- data/spec/dummy/db/seeds.rb +56 -0
- data/spec/factories/used_model.rb +28 -0
- data/spec/models/used_model_spec.rb +35 -0
- data/spec/rails_helper.rb +66 -0
- data/spec/requests/requests_spec.rb +227 -0
- data/spec/spec_helper.rb +7 -9
- data/spec/support/factory_bot.rb +3 -0
- metadata +83 -64
- data/spec/controllers/apicasso/aplication_controller_spec.rb +0 -18
- data/spec/controllers/apicasso/crud_controller_spec.rb +0 -107
- data/spec/dummy/app/assets/config/manifest.js +0 -3
- data/spec/dummy/app/assets/javascripts/application.js +0 -15
- data/spec/dummy/app/assets/javascripts/cable.js +0 -13
- data/spec/dummy/app/assets/stylesheets/application.css +0 -15
- data/spec/dummy/app/channels/application_cable/channel.rb +0 -4
- data/spec/dummy/app/channels/application_cable/connection.rb +0 -4
- data/spec/dummy/app/helpers/application_helper.rb +0 -2
- data/spec/dummy/app/jobs/application_job.rb +0 -2
- data/spec/dummy/app/mailers/application_mailer.rb +0 -4
- data/spec/dummy/app/views/layouts/application.html.erb +0 -15
- data/spec/dummy/app/views/layouts/mailer.html.erb +0 -13
- data/spec/dummy/app/views/layouts/mailer.text.erb +0 -1
- data/spec/dummy/bin/yarn +0 -11
- data/spec/dummy/config/initializers/assets.rb +0 -14
- data/spec/dummy/config/initializers/content_security_policy.rb +0 -25
- data/spec/dummy/config/initializers/cookies_serializer.rb +0 -5
- data/spec/dummy/log/development.log +0 -0
- data/spec/dummy/public/404.html +0 -67
- data/spec/dummy/public/422.html +0 -67
- data/spec/dummy/public/500.html +0 -66
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/factories/apicasso_key.rb +0 -9
- data/spec/factories/object.rb +0 -5
- data/spec/models/apicasso/key.rb +0 -5
- data/spec/routing/appointments_routing_spec.rb +0 -38
@@ -1,147 +1,170 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Apicasso
|
4
|
-
# Controller to extract common API features, such as authentication and
|
5
|
-
# authorization. Used to be inherited by non-CRUD controllers when your
|
6
|
-
# application needs to create custom actions.
|
7
|
-
class ApplicationController < ActionController::API
|
8
|
-
include ActionController::HttpAuthentication::Token::ControllerMethods
|
9
|
-
prepend_before_action :restrict_access, unless: -> { preflight? }
|
10
|
-
before_action :set_access_control_headers
|
11
|
-
after_action :register_api_request
|
12
|
-
|
13
|
-
# Sets the authorization scope for the current API key, it's a getter
|
14
|
-
# to make scoping easier
|
15
|
-
def current_ability
|
16
|
-
@current_ability ||= Apicasso::Ability.new(@api_key)
|
17
|
-
end
|
18
|
-
|
19
|
-
private
|
20
|
-
|
21
|
-
# Identifies API key used in the request, avoiding unauthenticated access.
|
22
|
-
# Responds with status 401 when token is not present or not found.
|
23
|
-
# Access restriction happens on the `Authentication` HTTP header.
|
24
|
-
# Example:
|
25
|
-
# curl -X GET http://example.com/objects -H 'authorization: Token token=f1e048a0b0ef4071a9a64ceecd48c64b'
|
26
|
-
def restrict_access
|
27
|
-
authenticate_or_request_with_http_token do |token, _options|
|
28
|
-
@api_key = Apicasso::Key.find_by(token: token)
|
29
|
-
end
|
30
|
-
end
|
31
|
-
|
32
|
-
# Creates a request object in databse, registering the API key and
|
33
|
-
# a hash of the request and the response. It's an auditing proccess,
|
34
|
-
# all relevant information about the requests and it's reponses get
|
35
|
-
# recorded within the `Apicasso::Request`. This method assumes that
|
36
|
-
# your project is using some kind of ActiveRecord extension with a
|
37
|
-
# `.delay` method, which when not present makes your API very slow.
|
38
|
-
def register_api_request
|
39
|
-
Apicasso::Request.delay.create(api_key_id: @api_key.try(:id),
|
40
|
-
object: { request: request_metadata,
|
41
|
-
response: response_metadata })
|
42
|
-
rescue NoMethodError
|
43
|
-
Apicasso::Request.create(api_key_id: @api_key.try(:id),
|
44
|
-
object: { request: request_metadata,
|
45
|
-
response: response_metadata })
|
46
|
-
end
|
47
|
-
|
48
|
-
# Information that gets inserted on `register_api_request` as auditing data
|
49
|
-
# about the request. Returns a Hash with UUID, URL, HTTP Headers and IP
|
50
|
-
def request_metadata
|
51
|
-
{
|
52
|
-
uuid: request.uuid,
|
53
|
-
url: request.original_url,
|
54
|
-
headers: request.env.select { |key, _v| key =~ /^HTTP_/ },
|
55
|
-
ip: request.remote_ip
|
56
|
-
}
|
57
|
-
end
|
58
|
-
|
59
|
-
# Information that gets inserted on `register_api_request` as auditing data
|
60
|
-
# about the response sent back to the client. Returns HTTP Status and request body
|
61
|
-
def response_metadata
|
62
|
-
{
|
63
|
-
status: response.status,
|
64
|
-
body: (response.body.present? ? JSON.parse(response.body) : '')
|
65
|
-
}
|
66
|
-
end
|
67
|
-
|
68
|
-
#
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apicasso
|
4
|
+
# Controller to extract common API features, such as authentication and
|
5
|
+
# authorization. Used to be inherited by non-CRUD controllers when your
|
6
|
+
# application needs to create custom actions.
|
7
|
+
class ApplicationController < ActionController::API
|
8
|
+
include ActionController::HttpAuthentication::Token::ControllerMethods
|
9
|
+
prepend_before_action :restrict_access, unless: -> { preflight? }
|
10
|
+
before_action :set_access_control_headers
|
11
|
+
after_action :register_api_request
|
12
|
+
|
13
|
+
# Sets the authorization scope for the current API key, it's a getter
|
14
|
+
# to make scoping easier
|
15
|
+
def current_ability
|
16
|
+
@current_ability ||= Apicasso::Ability.new(@api_key)
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
# Identifies API key used in the request, avoiding unauthenticated access.
|
22
|
+
# Responds with status 401 when token is not present or not found.
|
23
|
+
# Access restriction happens on the `Authentication` HTTP header.
|
24
|
+
# Example:
|
25
|
+
# curl -X GET http://example.com/objects -H 'authorization: Token token=f1e048a0b0ef4071a9a64ceecd48c64b'
|
26
|
+
def restrict_access
|
27
|
+
authenticate_or_request_with_http_token do |token, _options|
|
28
|
+
@api_key = Apicasso::Key.find_by(token: token)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Creates a request object in databse, registering the API key and
|
33
|
+
# a hash of the request and the response. It's an auditing proccess,
|
34
|
+
# all relevant information about the requests and it's reponses get
|
35
|
+
# recorded within the `Apicasso::Request`. This method assumes that
|
36
|
+
# your project is using some kind of ActiveRecord extension with a
|
37
|
+
# `.delay` method, which when not present makes your API very slow.
|
38
|
+
def register_api_request
|
39
|
+
Apicasso::Request.delay.create(api_key_id: @api_key.try(:id),
|
40
|
+
object: { request: request_metadata,
|
41
|
+
response: response_metadata })
|
42
|
+
rescue NoMethodError
|
43
|
+
Apicasso::Request.create(api_key_id: @api_key.try(:id),
|
44
|
+
object: { request: request_metadata,
|
45
|
+
response: response_metadata })
|
46
|
+
end
|
47
|
+
|
48
|
+
# Information that gets inserted on `register_api_request` as auditing data
|
49
|
+
# about the request. Returns a Hash with UUID, URL, HTTP Headers and IP
|
50
|
+
def request_metadata
|
51
|
+
{
|
52
|
+
uuid: request.uuid,
|
53
|
+
url: request.original_url,
|
54
|
+
headers: request.env.select { |key, _v| key =~ /^HTTP_/ },
|
55
|
+
ip: request.remote_ip
|
56
|
+
}
|
57
|
+
end
|
58
|
+
|
59
|
+
# Information that gets inserted on `register_api_request` as auditing data
|
60
|
+
# about the response sent back to the client. Returns HTTP Status and request body
|
61
|
+
def response_metadata
|
62
|
+
{
|
63
|
+
status: response.status,
|
64
|
+
body: (response.body.present? ? JSON.parse(response.body) : '')
|
65
|
+
}
|
66
|
+
end
|
67
|
+
|
68
|
+
# A method to extract all assosciations available
|
69
|
+
def associations_array
|
70
|
+
resource.reflect_on_all_associations.map { |association| association.name.to_s }
|
71
|
+
end
|
72
|
+
|
73
|
+
# Used to avoid errors parsing the search query, which can be passed as
|
74
|
+
# a JSON or as a key-value param. JSON is preferred because it generates
|
75
|
+
# shorter URLs on GET parameters.
|
76
|
+
def parsed_query
|
77
|
+
JSON.parse(params[:q])
|
78
|
+
rescue JSON::ParserError, TypeError
|
79
|
+
params[:q]
|
80
|
+
end
|
81
|
+
|
82
|
+
# Used to avoid errors in included associations parsing and to enable a
|
83
|
+
# insertion point for a change on splitting method.
|
84
|
+
def parsed_associations
|
85
|
+
params[:include].split(',').map do |param|
|
86
|
+
if @object.respond_to?(param)
|
87
|
+
param if associations_array.include?(param)
|
88
|
+
end
|
89
|
+
end.compact
|
90
|
+
rescue NoMethodError
|
91
|
+
[]
|
92
|
+
end
|
93
|
+
|
94
|
+
# Used to avoid errors in included associations parsing and to enable a
|
95
|
+
# insertion point for a change on splitting method.
|
96
|
+
def parsed_methods
|
97
|
+
params[:include].split(',').map do |param|
|
98
|
+
if @object.respond_to?(param)
|
99
|
+
param unless associations_array.include?(param)
|
100
|
+
end
|
101
|
+
end.compact
|
102
|
+
rescue NoMethodError
|
103
|
+
[]
|
104
|
+
end
|
105
|
+
|
106
|
+
# Used to avoid errors in fieldset selection parsing and to enable a
|
107
|
+
# insertion point for a change on splitting method.
|
108
|
+
def parsed_select
|
109
|
+
params[:select].split(',').map do |field|
|
110
|
+
field if @records.column_names.include?(field)
|
111
|
+
end
|
112
|
+
rescue NoMethodError
|
113
|
+
[]
|
114
|
+
end
|
115
|
+
|
116
|
+
# Receives a `.paginate`d collection and returns the pagination
|
117
|
+
# metadata to be merged into response
|
118
|
+
def pagination_metadata_for(records)
|
119
|
+
{ total: records.total_entries,
|
120
|
+
total_pages: records.total_pages,
|
121
|
+
last_page: records.next_page.blank?,
|
122
|
+
previous_page: previous_link_for(records),
|
123
|
+
next_page: next_link_for(records),
|
124
|
+
out_of_bounds: records.out_of_bounds?,
|
125
|
+
offset: records.offset }
|
126
|
+
end
|
127
|
+
|
128
|
+
# Generates a contextualized URL of the next page for the request
|
129
|
+
def next_link_for(records)
|
130
|
+
uri = URI.parse(request.original_url)
|
131
|
+
query = Rack::Utils.parse_query(uri.query)
|
132
|
+
query['page'] = records.next_page
|
133
|
+
uri.query = Rack::Utils.build_query(query)
|
134
|
+
uri.to_s
|
135
|
+
end
|
136
|
+
|
137
|
+
# Generates a contextualized URL of the previous page for the request
|
138
|
+
def previous_link_for(records)
|
139
|
+
uri = URI.parse(request.original_url)
|
140
|
+
query = Rack::Utils.parse_query(uri.query)
|
141
|
+
query['page'] = records.previous_page
|
142
|
+
uri.query = Rack::Utils.build_query(query)
|
143
|
+
uri.to_s
|
144
|
+
end
|
145
|
+
|
146
|
+
# Receives a `:action, :resource, :object` hash to validate authorization
|
147
|
+
# Example:
|
148
|
+
# > authorize_for action: :read, resource: :object_class, object: :object
|
149
|
+
def authorize_for(opts = {})
|
150
|
+
authorize! opts[:action], opts[:resource] if opts[:resource].present?
|
151
|
+
authorize! opts[:action], opts[:object] if opts[:object].present?
|
152
|
+
end
|
153
|
+
|
154
|
+
# @TODO
|
155
|
+
# Remove this in favor of a more controllable aproach of CORS
|
156
|
+
def set_access_control_headers
|
157
|
+
response.headers['Access-Control-Allow-Origin'] = request.headers["Origin"]
|
158
|
+
response.headers['Access-Control-Allow-Methods'] = 'POST, GET, PUT, PATCH, DELETE, OPTIONS'
|
159
|
+
response.headers['Access-Control-Allow-Credentials'] = 'true'
|
160
|
+
response.headers['Access-Control-Allow-Headers'] = 'Origin, Content-Type, Accept, Authorization, Token, Auth-Token, Email, X-User-Token, X-User-Email'
|
161
|
+
response.headers['Access-Control-Max-Age'] = '1728000'
|
162
|
+
end
|
163
|
+
|
164
|
+
# Checks if current request is a CORS preflight check
|
165
|
+
def preflight?
|
166
|
+
request.request_method == 'OPTIONS' &&
|
167
|
+
!request.headers['Authorization'].present?
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|