model_driven_api 3.0.10 → 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ae75e607281baf108e6841f71d4dd5e9f55f8e1fc1658fb83020e28a61338da
4
- data.tar.gz: 9639e68bb94fb4a1562b5078c8832cf2c4fb8b5033b42b2dde1441e9863fc16e
3
+ metadata.gz: 4cce6c28c96890bc490cc6e378fb55915671b29fd66e7b7be9acdbf48b1076cc
4
+ data.tar.gz: 8faf390787d24f102c2dc79c27be9823114e73344522f24b11021dc3ed0d3e06
5
5
  SHA512:
6
- metadata.gz: 145cce106430f76bcae76084684c5926fbd5fbfb774a285778fadf78e5443b1c750a46e71e8775adde75582bfa36548e0b0353bcf7a36fb86bcd524c6acf21d0
7
- data.tar.gz: ad33e323b89e297f16899d99931321ed262dfb07b05b4bd938f19e1250108a0c769d6ca7690c604cfbb2a860f19bedd5d79cce535065c016f042ecb56f49b96a
6
+ metadata.gz: 5688761f9a2707f5518317cfc061ef9e7e9902e241ac4495ef52f9d583e7524bd4948797e788e80f5e070ab9a0d05a2021267e67a7e9df871e18ad220c74ba7a
7
+ data.tar.gz: a3b87deaab6fc9c4b45f8bc0f7f2b304d3adb61d296a58b48791b75f022a24565c3af6856275afe08cf15afeb0ed4974be3a3e368eb46c9d036dd17a16238015
@@ -1,8 +1,8 @@
1
1
  class AuthorizeApiRequest
2
2
  prepend SimpleCommand
3
3
 
4
- def initialize(headers = {})
5
- @headers = headers
4
+ def initialize(request = {})
5
+ @headers = request.headers rescue {}
6
6
  end
7
7
 
8
8
  def call
@@ -1,217 +1,229 @@
1
1
  class Api::V2::ApplicationController < ActionController::API
2
- # Detect Locale from Accept-Language headers
3
- include HttpAcceptLanguage::AutoLocale
4
- # Actions will be authorized directly in the action
5
- include CanCan::ControllerAdditions
6
- include ::ApiExceptionManagement
7
-
8
- attr_accessor :current_user
9
-
10
- before_action :authenticate_request
11
- before_action :extract_model
12
- before_action :find_record, only: [ :show, :destroy, :update ]
13
-
14
- # GET :controller/
15
- def index
16
- authorize! :index, @model
17
-
18
- # Custom Action
19
- status, result, status_number = check_for_custom_action
20
- return render json: result, status: (status_number.presence || 200) if status == true
21
-
22
- # Normal Index Action with Ransack querying
23
- # Keeping this automation can be too dangerous and lead to unpredicted results
24
- # TODO: Remove it
25
- # @q = (@model.column_names.include?("user_id") ? @model.where(user_id: current_user.id) : @model).ransack(@query.presence|| params[:q])
26
- @q = @model.ransack(@query.presence|| params[:q])
27
- @records_all = @q.result # (distinct: true) Removing, but I'm not sure, with it I cannot sort in postgres for associated records (throws an exception on misuse of sort with distinct)
28
- page = (@page.presence || params[:page])
29
- per = (@per.presence || params[:per])
30
- # pages_info = (@pages_info.presence || params[:pages_info])
31
- count = (@count.presence || params[:count])
32
- # Pagination
33
- @records = @records_all.page(page).per(per)
34
- # Content-Range: posts 0-4/27
35
- range_start = [(page.to_i - 1) * per.to_i, 0].max;
36
- range_end = [0, page.to_i * per.to_i - 1].max;
37
- response.set_header('Content-Range', "#{@model.table_name} #{range_start}-#{range_end}/#{@records.total_count}")
38
-
39
- # If there's the keyword pagination_info, then return a pagination info object
40
- # return render json: {count: @records_all.count,current_page_count: @records.count,next_page: @records.next_page,prev_page: @records.prev_page,is_first_page: @records.first_page?,is_last_page: @records.last_page?,is_out_of_range: @records.out_of_range?,pages_count: @records.total_pages,current_page_number: @records.current_page } if !pages_info.blank?
41
-
42
- # puts "ALL RECORDS FOUND: #{@records_all.inspect}"
43
- status = @records_all.blank? ? 404 : 200
44
- # puts "If it's asked for page number, then paginate"
45
- return render json: @records.as_json(json_attrs), status: status if !page.blank? # (@json_attrs || {})
46
- #puts "if you ask for count, then return a json object with just the number of objects"
47
- return render json: {count: @records_all.count}if !count.blank?
48
- #puts "Default"
49
- json_out = @records_all.as_json(json_attrs)
50
- #puts "JSON ATTRS: #{json_attrs}"
51
- #puts "JSON OUT: #{json_out}"
52
- render json: json_out, status: status #(@json_attrs || {})
53
- end
54
-
55
- def show
56
- authorize! :show, @record_id
57
-
58
- # Custom Show Action
59
- status, result, status_number = check_for_custom_action
60
- return render json: result, status: (status_number.presence || 200) if status == true
61
-
62
- # Normal Show
63
- result = @record.to_json(json_attrs)
64
- render json: result, status: 200
65
- end
66
-
67
- def create
68
- # Normal Create Action
69
- @record = @model.new(@body)
70
- authorize! :create, @record
71
- # Custom Action
72
- status, result, status_number = check_for_custom_action
73
- return render json: result, status: (status_number.presence || 200) if status == true
74
- # Keeping this automation can be too dangerous and lead to unpredicted results
75
- # TODO: Remove it
76
- # @record.user_id = current_user.id if @model.column_names.include? "user_id"
77
- @record.save!
78
- render json: @record.to_json(json_attrs), status: 201
79
- end
80
-
81
- def update
82
- authorize! :update, @record
83
-
84
- # Custom Action
85
- status, result, status_number = check_for_custom_action
86
- return render json: result, status: (status_number.presence || 200) if status == true
87
-
88
- # Normal Update Action
89
- # Rails 6 vs Rails 6.1
90
- @record.respond_to?('update_attributes!') ? @record.update_attributes!(@body) : @record.update!(@body)
91
- render json: @record.to_json(json_attrs), status: 200
92
- end
93
-
94
- def update_multi
95
- authorize! :update, @model
96
- ids = params[:ids].split(",")
97
- @model.where(id: ids).update!(@body)
98
- render json: ids.to_json, status: 200
99
- end
100
-
101
- def destroy
102
- authorize! :destroy, @record
103
-
104
- # Custom Action
105
- status, result, status_number = check_for_custom_action
106
- return render json: result, status: (status_number.presence || 200) if status == true
107
-
108
- # Normal Destroy Action
109
- return api_error(status: 500) unless @record.destroy
110
- head :ok
111
- end
112
-
113
- def destroy_multi
114
- authorize! :destroy, @model
115
-
116
- # Normal Destroy Action
117
- ids = params[:ids].split(",")
118
- @model.where(id: ids).destroy!(@body)
119
- render json: ids.to_json, status: 200
120
- end
121
-
122
- private
123
-
124
- def check_for_custom_action
125
- ## CUSTOM ACTION
126
- # [GET|PUT|POST|DELETE] :controller?do=:custom_action
127
- # or
128
- # [GET|PUT|POST|DELETE] :controller/:id?do=:custom_action
129
- unless params[:do].blank?
130
- # Poor man's solution to avoid the possibility to
131
- # call an unwanted method in the AR Model.
132
- resource = "custom_action_#{params[:do]}"
133
- raise NoMethodError unless @model.respond_to?(resource)
134
- # puts json_attrs
135
- params[:request_url] = request.url
136
- params[:token] = bearer_token
137
- body, status = @model.send(resource, params)
138
- return true, body.to_json(json_attrs), status
139
- end
140
- # if it's here there is no custom action in the request querystring
141
- return false
142
- end
143
-
144
- def bearer_token
145
- pattern = /^Bearer /
146
- header = request.headers['Authorization']
147
- header.gsub(pattern, '') if header && header.match(pattern)
148
- end
149
-
150
- def class_exists?(class_name)
151
- klass = Module.const_get(class_name)
152
- return klass.is_a?(Class)
153
- rescue NameError
154
- return false
155
- end
156
-
157
- def authenticate_request
158
- @current_user = nil
159
- Settings.ns(:security).allowed_authorization_headers.split(",").each do |header|
160
- # puts "Found header #{header}: #{request.headers[header]}"
161
- check_authorization("Authorize#{header}".constantize.call(request.headers)) # if request.headers[header]
162
- end
163
-
164
- check_authorization AuthorizeApiRequest.call(request.headers) unless @current_user
165
- return unauthenticated!(OpenStruct.new({message: @auth_errors})) unless @current_user
166
-
167
- current_user = @current_user
168
- params[:current_user_id] = @current_user.id
169
- # Now every time the user fires off a successful GET request,
170
- # a new token is generated and passed to them, and the clock resets.
171
- response.set_header('Token', JsonWebToken.encode(user_id: current_user.id))
172
- end
173
-
174
- def find_record
175
- record_id ||= (params[:path].split("/").second.to_i rescue nil)
176
- # Keeping this automation can be too dangerous and lead to unpredicted results
177
- # TODO: Remove it
178
- # @record = @model.column_names.include?("user_id") ? @model.where(id: (record_id.presence || @record_id.presence || params[:id]), user_id: current_user.id).first : @model.find((@record_id.presence || params[:id]))
179
- @record = @model.find((@record_id.presence || params[:id]))
180
- return not_found! if @record.blank?
181
- end
182
-
183
- def json_attrs
184
- # In order of importance: if you send the configuration via querystring you are ok
185
- # has precedence over if you have setup the json_attrs in the model concern
186
- from_params = params[:a].deep_symbolize_keys unless params[:a].blank?
187
- from_params = params[:json_attrs].deep_symbolize_keys unless params[:json_attrs].blank?
188
- from_params.presence || @json_attrs.presence || @model.json_attrs.presence || {} rescue {}
189
- end
190
-
191
- def extract_model
192
- # This method is only valid for ActiveRecords
193
- # For any other model-less controller, the actions must be
194
- # defined in the route, and must exist in the controller definition.
195
- # So, if it's not an activerecord, the find model makes no sense at all
196
- # thus must return 404.
197
- @model = (params[:ctrl].classify.constantize rescue params[:path].split("/").first.classify.constantize rescue controller_path.classify.constantize rescue controller_name.classify.constantize rescue nil)
198
- # Getting the body of the request if it exists, it's ok the singular or
199
- # plural form, this helps with automatic tests with Insomnia.
200
- @body = params[@model.model_name.singular].presence || params[@model.model_name.route_key]
201
- # Only ActiveRecords can have this model caputed
202
- return not_found! if (!@model.new.is_a? ActiveRecord::Base rescue false)
203
- end
204
-
205
- def check_authorization cmd
206
- if cmd.success?
207
- @current_user = cmd.result
208
- else
209
- @auth_errors = cmd.errors
210
- end
211
- end
212
-
213
- # Nullifying strong params for API
214
- def params
215
- request.parameters
216
- end
2
+ # Detect Locale from Accept-Language headers
3
+ include HttpAcceptLanguage::AutoLocale
4
+ # Actions will be authorized directly in the action
5
+ include CanCan::ControllerAdditions
6
+ include ::ApiExceptionManagement
7
+
8
+ attr_accessor :current_user
9
+
10
+ before_action :authenticate_request
11
+ before_action :extract_model
12
+ before_action :find_record, only: [:show, :destroy, :update]
13
+
14
+ # GET :controller/
15
+ def index
16
+ authorize! :index, @model
17
+
18
+ # Custom Action
19
+ status, result, status_number = check_for_custom_action
20
+ return render json: result, status: (status_number.presence || 200) if status == true
21
+
22
+ # Normal Index Action with Ransack querying
23
+ # Keeping this automation can be too dangerous and lead to unpredicted results
24
+ # TODO: Remove it
25
+ # @q = (@model.column_names.include?("user_id") ? @model.where(user_id: current_user.id) : @model).ransack(@query.presence|| params[:q])
26
+ @q = @model.ransack(@query.presence || params[:q])
27
+ @records_all = @q.result # (distinct: true) Removing, but I'm not sure, with it I cannot sort in postgres for associated records (throws an exception on misuse of sort with distinct)
28
+ page = (@page.presence || params[:page])
29
+ per = (@per.presence || params[:per])
30
+ # pages_info = (@pages_info.presence || params[:pages_info])
31
+ count = (@count.presence || params[:count])
32
+ # Pagination
33
+ @records = @records_all.page(page).per(per)
34
+ # Content-Range: posts 0-4/27
35
+ range_start = [(page.to_i - 1) * per.to_i, 0].max
36
+ range_end = [0, page.to_i * per.to_i - 1].max
37
+ response.set_header("Content-Range", "#{@model.table_name} #{range_start}-#{range_end}/#{@records.total_count}")
38
+
39
+ # If there's the keyword pagination_info, then return a pagination info object
40
+ # return render json: {count: @records_all.count,current_page_count: @records.count,next_page: @records.next_page,prev_page: @records.prev_page,is_first_page: @records.first_page?,is_last_page: @records.last_page?,is_out_of_range: @records.out_of_range?,pages_count: @records.total_pages,current_page_number: @records.current_page } if !pages_info.blank?
41
+
42
+ # puts "ALL RECORDS FOUND: #{@records_all.inspect}"
43
+ status = @records_all.blank? ? 404 : 200
44
+ # puts "If it's asked for page number, then paginate"
45
+ return render json: @records.as_json(json_attrs), status: status if !page.blank? # (@json_attrs || {})
46
+ #puts "if you ask for count, then return a json object with just the number of objects"
47
+ return render json: { count: @records_all.count } if !count.blank?
48
+ #puts "Default"
49
+ json_out = @records_all.as_json(json_attrs)
50
+ #puts "JSON ATTRS: #{json_attrs}"
51
+ #puts "JSON OUT: #{json_out}"
52
+ render json: json_out, status: status #(@json_attrs || {})
53
+ end
54
+
55
+ def show
56
+ authorize! :show, @record_id
57
+
58
+ # Custom Show Action
59
+ status, result, status_number = check_for_custom_action
60
+ return render json: result, status: (status_number.presence || 200) if status == true
61
+
62
+ # Normal Show
63
+ result = @record.to_json(json_attrs)
64
+ render json: result, status: 200
65
+ end
66
+
67
+ def create
68
+ # Normal Create Action
69
+ @record = @model.new(@body)
70
+ authorize! :create, @record
71
+ # Custom Action
72
+ status, result, status_number = check_for_custom_action
73
+ return render json: result, status: (status_number.presence || 200) if status == true
74
+ # Keeping this automation can be too dangerous and lead to unpredicted results
75
+ # TODO: Remove it
76
+ # @record.user_id = current_user.id if @model.column_names.include? "user_id"
77
+ @record.save!
78
+ render json: @record.to_json(json_attrs), status: 201
79
+ end
80
+
81
+ def update
82
+ authorize! :update, @record
83
+
84
+ # Custom Action
85
+ status, result, status_number = check_for_custom_action
86
+ return render json: result, status: (status_number.presence || 200) if status == true
87
+
88
+ # Normal Update Action
89
+ # Rails 6 vs Rails 6.1
90
+ @record.respond_to?("update_attributes!") ? @record.update_attributes!(@body) : @record.update!(@body)
91
+ render json: @record.to_json(json_attrs), status: 200
92
+ end
93
+
94
+ def update_multi
95
+ authorize! :update, @model
96
+ ids = params[:ids].split(",")
97
+ @model.where(id: ids).update!(@body)
98
+ render json: ids.to_json, status: 200
99
+ end
100
+
101
+ def destroy
102
+ authorize! :destroy, @record
103
+
104
+ # Custom Action
105
+ status, result, status_number = check_for_custom_action
106
+ return render json: result, status: (status_number.presence || 200) if status == true
107
+
108
+ # Normal Destroy Action
109
+ return api_error(status: 500) unless @record.destroy
110
+ head :ok
111
+ end
112
+
113
+ def destroy_multi
114
+ authorize! :destroy, @model
115
+
116
+ # Normal Destroy Action
117
+ ids = params[:ids].split(",")
118
+ @model.where(id: ids).destroy!(@body)
119
+ render json: ids.to_json, status: 200
120
+ end
121
+
122
+ private
123
+
124
+ def check_for_custom_action
125
+ ## CUSTOM ACTION
126
+ # [GET|PUT|POST|DELETE] :controller?do=:custom_action
127
+ # or
128
+ # [GET|PUT|POST|DELETE] :controller/:id?do=:custom_action
129
+ unless params[:do].blank?
130
+ # Poor man's solution to avoid the possibility to
131
+ # call an unwanted method in the AR Model.
132
+ custom_action, token = params[:do].split("-")
133
+
134
+ params[:request_url] = request.url
135
+ params[:remote_ip] = request.remote_ip
136
+ params[:token] = token.presence || bearer_token
137
+ # The endpoint can be expressed in two wayy:
138
+ # 1. As a method in the model, with suffix custom_action_<custom_action>
139
+ # 2. As a module instance method in the model, like Track::Endpoints.inventory
140
+ if @model.const_defined?(:Endpoints) && defined?("#{@model}::Endpoints.#{custom_action}")
141
+ # Custom endpoint exists and can be called in the sub-modules form
142
+ body, status = @model::Endpoints.send(custom_action, params)
143
+ elsif @model.respond_to?("custom_action_#{custom_action}")
144
+ body, status = @model.send("custom_action_#{custom_action}", params)
145
+ else
146
+ # Custom endpoint does not exist or cannot be called
147
+ raise NoMethodError
148
+ end
149
+
150
+ return true, body.to_json(json_attrs), status
151
+ end
152
+ # if it's here there is no custom action in the request querystring
153
+ return false
154
+ end
155
+
156
+ def bearer_token
157
+ pattern = /^Bearer /
158
+ header = request.headers["Authorization"]
159
+ header.gsub(pattern, "") if header && header.match(pattern)
160
+ end
161
+
162
+ def class_exists?(class_name)
163
+ klass = Module.const_get(class_name)
164
+ return klass.is_a?(Class)
165
+ rescue NameError
166
+ return false
167
+ end
168
+
169
+ def authenticate_request
170
+ @current_user = nil
171
+ Settings.ns(:security).allowed_authorization_headers.split(",").each do |header|
172
+ # puts "Found header #{header}: #{request.headers[header]}"
173
+ check_authorization("Authorize#{header}".constantize.call(request))
174
+ end
175
+
176
+ check_authorization AuthorizeApiRequest.call(request) unless @current_user
177
+ return unauthenticated!(OpenStruct.new({ message: @auth_errors })) unless @current_user
178
+
179
+ current_user = @current_user
180
+ params[:current_user_id] = @current_user.id
181
+ # Now every time the user fires off a successful GET request,
182
+ # a new token is generated and passed to them, and the clock resets.
183
+ response.set_header("Token", JsonWebToken.encode(user_id: current_user.id))
184
+ end
185
+
186
+ def find_record
187
+ record_id ||= (params[:path].split("/").second.to_i rescue nil)
188
+ # Keeping this automation can be too dangerous and lead to unpredicted results
189
+ # TODO: Remove it
190
+ # @record = @model.column_names.include?("user_id") ? @model.where(id: (record_id.presence || @record_id.presence || params[:id]), user_id: current_user.id).first : @model.find((@record_id.presence || params[:id]))
191
+ @record = @model.find((@record_id.presence || params[:id]))
192
+ return not_found! if @record.blank?
193
+ end
194
+
195
+ def json_attrs
196
+ # In order of importance: if you send the configuration via querystring you are ok
197
+ # has precedence over if you have setup the json_attrs in the model concern
198
+ from_params = params[:a].deep_symbolize_keys unless params[:a].blank?
199
+ from_params = params[:json_attrs].deep_symbolize_keys unless params[:json_attrs].blank?
200
+ from_params.presence || @json_attrs.presence || @model.json_attrs.presence || {} rescue {}
201
+ end
202
+
203
+ def extract_model
204
+ # This method is only valid for ActiveRecords
205
+ # For any other model-less controller, the actions must be
206
+ # defined in the route, and must exist in the controller definition.
207
+ # So, if it's not an activerecord, the find model makes no sense at all
208
+ # thus must return 404.
209
+ @model = (params[:ctrl].classify.constantize rescue params[:path].split("/").first.classify.constantize rescue controller_path.classify.constantize rescue controller_name.classify.constantize rescue nil)
210
+ # Getting the body of the request if it exists, it's ok the singular or
211
+ # plural form, this helps with automatic tests with Insomnia.
212
+ @body = params[@model.model_name.singular].presence || params[@model.model_name.route_key]
213
+ # Only ActiveRecords can have this model caputed
214
+ return not_found! if (!@model.new.is_a? ActiveRecord::Base rescue false)
215
+ end
216
+
217
+ def check_authorization(cmd)
218
+ if cmd.success?
219
+ @current_user = cmd.result
220
+ else
221
+ @auth_errors = cmd.errors
222
+ end
223
+ end
224
+
225
+ # Nullifying strong params for API
226
+ def params
227
+ request.parameters
228
+ end
217
229
  end
@@ -1,3 +1,3 @@
1
1
  module ModelDrivenApi
2
- VERSION = "3.0.10".freeze
2
+ VERSION = "3.1.0".freeze
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: model_driven_api
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.0.10
4
+ version: 3.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Gabriele Tassoni
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-06-28 00:00:00.000000000 Z
11
+ date: 2023-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: thecore_backend_commons