model_driven_api 3.0.11 → 3.1.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77497a359b4d336baf533725376f1122b376d2a97b05bcf3e8f7e203d4963780
4
- data.tar.gz: 8a799a1009588c764b5dbec7ea1b1cfd4c7f8c1d4ac4554bccb22b35877531a1
3
+ metadata.gz: 5b2e60735be9ed9050c42d522a30c5e441bd649cf8447f0313b717e04c0186ce
4
+ data.tar.gz: 29607e7fc184665bcab771c1889745e21f87973aff976c8865bbe0f714f249c2
5
5
  SHA512:
6
- metadata.gz: abd1210273d04c393f6a6b46e593f52da8dba14bce52dd3351b3cc3c5575c9c931826538035566a186c9d4af0596cea2f54aa1ce6d685dc7136feb933f82ce1a
7
- data.tar.gz: 6b822a4b6da0f2718a7837124e654fc6c5ba7223798171621d669de2fd54aa4e9a933a99d5c5cf7c5b0b5641fc2eab6f5b729bef7d4d7ae8a4531fda20e3aa87
6
+ metadata.gz: 84e9365bf07f643ef2bf78055902f538f77d06301c7ba1e888cfd6773bfa381791d861d13ffa46f3448f58426f954522e9187f1d70aec48f3622d801f93c6615
7
+ data.tar.gz: 1db187f6b765a5cb0bd0c1b2f0ee0f33883c9f4c3a5dabad32eaeda8488d57d386ddd71094b3a632c2b1222d04e421e95ba25d3be4eba3c5d13a25746e7b2ff0
@@ -1,218 +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
- custom_action, token = params[:do].split("-")
133
- resource = "custom_action_#{custom_action}"
134
- raise NoMethodError unless @model.respond_to?(resource)
135
- # puts json_attrs
136
- params[:request_url] = request.url
137
- params[:token] = token.presence || bearer_token
138
- body, status = @model.send(resource, params)
139
- return true, body.to_json(json_attrs), status
140
- end
141
- # if it's here there is no custom action in the request querystring
142
- return false
143
- end
144
-
145
- def bearer_token
146
- pattern = /^Bearer /
147
- header = request.headers['Authorization']
148
- header.gsub(pattern, '') if header && header.match(pattern)
149
- end
150
-
151
- def class_exists?(class_name)
152
- klass = Module.const_get(class_name)
153
- return klass.is_a?(Class)
154
- rescue NameError
155
- return false
156
- end
157
-
158
- def authenticate_request
159
- @current_user = nil
160
- Settings.ns(:security).allowed_authorization_headers.split(",").each do |header|
161
- # puts "Found header #{header}: #{request.headers[header]}"
162
- check_authorization("Authorize#{header}".constantize.call(request))
163
- end
164
-
165
- check_authorization AuthorizeApiRequest.call(request) unless @current_user
166
- return unauthenticated!(OpenStruct.new({message: @auth_errors})) unless @current_user
167
-
168
- current_user = @current_user
169
- params[:current_user_id] = @current_user.id
170
- # Now every time the user fires off a successful GET request,
171
- # a new token is generated and passed to them, and the clock resets.
172
- response.set_header('Token', JsonWebToken.encode(user_id: current_user.id))
173
- end
174
-
175
- def find_record
176
- record_id ||= (params[:path].split("/").second.to_i rescue nil)
177
- # Keeping this automation can be too dangerous and lead to unpredicted results
178
- # TODO: Remove it
179
- # @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]))
180
- @record = @model.find((@record_id.presence || params[:id]))
181
- return not_found! if @record.blank?
182
- end
183
-
184
- def json_attrs
185
- # In order of importance: if you send the configuration via querystring you are ok
186
- # has precedence over if you have setup the json_attrs in the model concern
187
- from_params = params[:a].deep_symbolize_keys unless params[:a].blank?
188
- from_params = params[:json_attrs].deep_symbolize_keys unless params[:json_attrs].blank?
189
- from_params.presence || @json_attrs.presence || @model.json_attrs.presence || {} rescue {}
190
- end
191
-
192
- def extract_model
193
- # This method is only valid for ActiveRecords
194
- # For any other model-less controller, the actions must be
195
- # defined in the route, and must exist in the controller definition.
196
- # So, if it's not an activerecord, the find model makes no sense at all
197
- # thus must return 404.
198
- @model = (params[:ctrl].classify.constantize rescue params[:path].split("/").first.classify.constantize rescue controller_path.classify.constantize rescue controller_name.classify.constantize rescue nil)
199
- # Getting the body of the request if it exists, it's ok the singular or
200
- # plural form, this helps with automatic tests with Insomnia.
201
- @body = params[@model.model_name.singular].presence || params[@model.model_name.route_key]
202
- # Only ActiveRecords can have this model caputed
203
- return not_found! if (!@model.new.is_a? ActiveRecord::Base rescue false)
204
- end
205
-
206
- def check_authorization cmd
207
- if cmd.success?
208
- @current_user = cmd.result
209
- else
210
- @auth_errors = cmd.errors
211
- end
212
- end
213
-
214
- # Nullifying strong params for API
215
- def params
216
- request.parameters
217
- 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 defined?("Endpoints::#{@model}.#{custom_action}")
141
+ # Custom endpoint exists and can be called in the sub-modules form
142
+ body, status = "Endpoints::#{@model}".constantize.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
218
229
  end
@@ -1,3 +1,3 @@
1
1
  module ModelDrivenApi
2
- VERSION = "3.0.11".freeze
2
+ VERSION = "3.1.1".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.11
4
+ version: 3.1.1
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-09-21 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