model_driven_api 3.0.11 → 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: 77497a359b4d336baf533725376f1122b376d2a97b05bcf3e8f7e203d4963780
4
- data.tar.gz: 8a799a1009588c764b5dbec7ea1b1cfd4c7f8c1d4ac4554bccb22b35877531a1
3
+ metadata.gz: 4cce6c28c96890bc490cc6e378fb55915671b29fd66e7b7be9acdbf48b1076cc
4
+ data.tar.gz: 8faf390787d24f102c2dc79c27be9823114e73344522f24b11021dc3ed0d3e06
5
5
  SHA512:
6
- metadata.gz: abd1210273d04c393f6a6b46e593f52da8dba14bce52dd3351b3cc3c5575c9c931826538035566a186c9d4af0596cea2f54aa1ce6d685dc7136feb933f82ce1a
7
- data.tar.gz: 6b822a4b6da0f2718a7837124e654fc6c5ba7223798171621d669de2fd54aa4e9a933a99d5c5cf7c5b0b5641fc2eab6f5b729bef7d4d7ae8a4531fda20e3aa87
6
+ metadata.gz: 5688761f9a2707f5518317cfc061ef9e7e9902e241ac4495ef52f9d583e7524bd4948797e788e80f5e070ab9a0d05a2021267e67a7e9df871e18ad220c74ba7a
7
+ data.tar.gz: a3b87deaab6fc9c4b45f8bc0f7f2b304d3adb61d296a58b48791b75f022a24565c3af6856275afe08cf15afeb0ed4974be3a3e368eb46c9d036dd17a16238015
@@ -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 @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
218
229
  end
@@ -1,3 +1,3 @@
1
1
  module ModelDrivenApi
2
- VERSION = "3.0.11".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.11
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-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