model_driven_api 3.0.10 → 3.1.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.
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