/
application_controller.rb
247 lines (212 loc) 路 8.38 KB
/
application_controller.rb
1
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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
require 'jwt'
require 'csv'
module ForestLiana
class ApplicationController < ForestLiana::BaseController
rescue_from ForestLiana::Ability::Exceptions::AccessDenied, with: :render_error
rescue_from ForestLiana::Errors::HTTP403Error, with: :render_error
rescue_from ForestLiana::Errors::HTTP422Error, with: :render_error
def self.papertrail?
Object.const_get('PaperTrail::Version').is_a?(Class) rescue false
end
# NOTICE: Calling the method set_paper_trail_whodunnit loads the PaperTrail
# gem and Forest detects the PaperTrail::Version automatically. This
# method is used to set the whodunnit field automatically to track
# changes made using Forest with PaperTrail.
if Rails::VERSION::MAJOR < 4
before_filter :authenticate_user_from_jwt
before_filter :set_paper_trail_whodunnit if self.papertrail?
else
before_action :authenticate_user_from_jwt
before_action :set_paper_trail_whodunnit if self.papertrail?
end
if self.papertrail?
# NOTICE: The Forest user email is returned to track changes made using
# Forest with Papertrail.
define_method :user_for_paper_trail do
@jwt_decoded_token['email']
end
end
# NOTICE: Helper method for Smart Routes logic based on current user info.
def forest_user
@jwt_decoded_token
end
def serialize_model(record, options = {})
options[:is_collection] = false
json = ForestAdmin::JSONAPI::Serializer.serialize(record, options)
force_utf8_encoding(json)
end
def serialize_models(records, options = {}, fields_searched = [])
options[:is_collection] = true
json = ForestAdmin::JSONAPI::Serializer.serialize(records, options)
if options[:params] && options[:params][:search]
# NOTICE: Add the Smart Fields with a 'String' type.
fields_searched.concat(get_collection.string_smart_fields_names).uniq!
json['meta'] = {
decorators: ForestLiana::DecorationHelper
.decorate_for_search(json, fields_searched, options[:params][:search])
}
end
force_utf8_encoding(json)
end
def authenticate_user_from_jwt
begin
if request.headers
if request.headers['Authorization']
token = request.headers['Authorization'].split.second
# NOTICE: Necessary for downloads authentication.
elsif request.headers['cookie']
match = ForestLiana::Token::REGEX_COOKIE_SESSION_TOKEN.match(request.headers['cookie'])
token = match[1] if match && match[1]
end
@jwt_decoded_token = JWT.decode(token, ForestLiana.auth_secret, true,
{ algorithm: 'HS256' }).try(:first)
# NOTICE: Automatically logs out the users that use tokens having an old data format.
if @jwt_decoded_token['data']
raise ForestLiana::Errors::HTTP401Error.new("Your token format is invalid, please login again.")
end
@rendering_id = @jwt_decoded_token['rendering_id']
else
head :unauthorized
end
rescue JWT::ExpiredSignature, JWT::VerificationError
render json: { error: 'expired_token' }, status: :unauthorized,
serializer: nil
rescue
head :unauthorized
end
end
def internal_server_error
head :internal_server_error
end
def deactivate_count_response
render serializer: nil, json: { meta: { count: 'deactivated'} }
end
private
def render_error(exception)
errors = {
status: exception.error_code,
detail: exception.message,
}
errors['name'] = exception.name if exception.try(:name)
errors['data'] = exception.data if exception.try(:data)
render json: { errors: [errors] }, status: exception.status
end
def force_utf8_encoding(json)
if json['data'].class == Array
# NOTICE: Collection of records case
json['data'].each { |record| force_utf8_attributes_encoding(record) }
else
# NOTICE: Single record case
force_utf8_attributes_encoding(json['data']) if json['data']
end
json['included'].try(:each) do |association|
force_utf8_attributes_encoding(association)
end
json
end
def force_utf8_attributes_encoding(model)
#聽NOTICE: Declare all strings are encoded in utf-8
if model['attributes']
model['attributes'].each do |name, value|
if value.respond_to?(:force_encoding)
begin
model['attributes'][name] = value.force_encoding('utf-8')
rescue
#聽NOTICE: Enums are frozen Strings
end
end
end
end
end
def fields_per_model(params_fields, model)
if params_fields
if Rails::VERSION::MAJOR > 4
params_fields_hash = params_fields.to_unsafe_h
else
params_fields_hash = params_fields.to_hash
end
params_fields_hash.inject({}) do |fields, param_field|
relation_name = param_field[0]
relation_fields = param_field[1]
if relation_name == ForestLiana.name_for(model)
fields[relation_name] = relation_fields
else
model_association = model.reflect_on_association(relation_name.to_sym)
if model_association
model_name = model_association.class_name
# NOTICE: Join fields in case of model with self-references.
if fields[model_name]
fields[model_name] = [
fields[model_name],
relation_fields
].join(',').split(',').uniq.join(',')
else
fields[model_name] = relation_fields
end
end
end
fields
end
else
nil
end
end
def render_csv getter, model
set_headers_file
set_headers_streaming
response.status = 200
csv_header = params[:header].split(',')
collection_name = ForestLiana.name_for(model)
field_names_requested = params[:fields][collection_name].split(',').map { |name| name.to_s }
fields_to_serialize = fields_per_model(params[:fields], model)
self.response_body = Enumerator.new do |content|
content << ::CSV::Row.new(field_names_requested, csv_header, true).to_s
getter.query_for_batch.find_in_batches() do |records|
records.each do |record|
json = serialize_model(record, {
include: getter.includes.map(&:to_s),
fields: fields_to_serialize
})
record_attributes = json['data']['attributes']
record_relationships = json['data']['relationships'] || {}
included = json['included']
values = field_names_requested.map do |field_name|
if record_attributes[field_name]
record_attributes[field_name]
elsif record_relationships[field_name] &&
record_relationships[field_name]['data']
relationship_id = record_relationships[field_name]['data']['id']
relationship_type = record_relationships[field_name]['data']['type']
relationship_object = included.select do |object|
object['id'] == relationship_id && object['type'] == relationship_type
end
relationship_object = relationship_object.first
if relationship_object && relationship_object['attributes']
relationship_object['attributes'][params[:fields][field_name]]
else
nil
end
end
end
content << ::CSV::Row.new(field_names_requested, values).to_s
end
end
end
end
def set_headers_file
csv_filename = "#{params[:filename]}.csv"
headers["Content-Type"] = "text/csv; charset=utf-8"
headers["Content-disposition"] = %{attachment; filename="#{csv_filename}"}
headers['Last-Modified'] = Time.now.ctime.to_s
end
def set_headers_streaming
# NOTICE: From nginx doc: Setting this to "no" will allow unbuffered
# responses suitable for Comet and HTTP streaming applications
headers['X-Accel-Buffering'] = 'no'
headers["Cache-Control"] = "no-cache"
end
def format_stacktrace error
error.backtrace.join("\n\t")
end
end
end