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 +4 -4
- data/app/controllers/api/v2/application_controller.rb +227 -216
- data/lib/model_driven_api/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5b2e60735be9ed9050c42d522a30c5e441bd649cf8447f0313b717e04c0186ce
|
4
|
+
data.tar.gz: 29607e7fc184665bcab771c1889745e21f87973aff976c8865bbe0f714f249c2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 84e9365bf07f643ef2bf78055902f538f77d06301c7ba1e888cfd6773bfa381791d861d13ffa46f3448f58426f954522e9187f1d70aec48f3622d801f93c6615
|
7
|
+
data.tar.gz: 1db187f6b765a5cb0bd0c1b2f0ee0f33883c9f4c3a5dabad32eaeda8488d57d386ddd71094b3a632c2b1222d04e421e95ba25d3be4eba3c5d13a25746e7b2ff0
|
@@ -1,218 +1,229 @@
|
|
1
1
|
class Api::V2::ApplicationController < ActionController::API
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
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
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
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
|
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.
|
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-
|
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
|