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 +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: 4cce6c28c96890bc490cc6e378fb55915671b29fd66e7b7be9acdbf48b1076cc
|
4
|
+
data.tar.gz: 8faf390787d24f102c2dc79c27be9823114e73344522f24b11021dc3ed0d3e06
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5688761f9a2707f5518317cfc061ef9e7e9902e241ac4495ef52f9d583e7524bd4948797e788e80f5e070ab9a0d05a2021267e67a7e9df871e18ad220c74ba7a
|
7
|
+
data.tar.gz: a3b87deaab6fc9c4b45f8bc0f7f2b304d3adb61d296a58b48791b75f022a24565c3af6856275afe08cf15afeb0ed4974be3a3e368eb46c9d036dd17a16238015
|
@@ -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 @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
|
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
|
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-
|
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
|