eluvia-base 3.37.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +225 -0
- data/lib/eluvia/active_model/amount_attribute.rb +108 -0
- data/lib/eluvia/active_model/array_attribute.rb +134 -0
- data/lib/eluvia/active_model/attributes.rb +417 -0
- data/lib/eluvia/active_model/enum_array_attribute.rb +60 -0
- data/lib/eluvia/active_model/enum_attribute.rb +174 -0
- data/lib/eluvia/active_model/object_attribute.rb +143 -0
- data/lib/eluvia/active_model/range_attribute.rb +74 -0
- data/lib/eluvia/active_record/file_array_attribute.rb +116 -0
- data/lib/eluvia/active_record/file_attribute.rb +158 -0
- data/lib/eluvia/active_record/filtering.rb +680 -0
- data/lib/eluvia/active_record/filters_model.rb +58 -0
- data/lib/eluvia/active_record/ordering.rb +110 -0
- data/lib/eluvia/base/config.rb +38 -0
- data/lib/eluvia/base/version.rb +12 -0
- data/lib/eluvia/errors/bad_request.rb +17 -0
- data/lib/eluvia/errors/forbidden.rb +17 -0
- data/lib/eluvia/errors/not_found.rb +17 -0
- data/lib/eluvia/errors/service_unavailable.rb +17 -0
- data/lib/eluvia/errors/standard_error.rb +33 -0
- data/lib/eluvia/errors/unauthorized.rb +17 -0
- data/lib/eluvia/errors/unprocessable_entity.rb +26 -0
- data/lib/eluvia/fieldset/rest_field.rb +83 -0
- data/lib/eluvia/fieldset/rest_fieldset.rb +158 -0
- data/lib/eluvia/fieldset.rb +12 -0
- data/lib/eluvia/handlers/error_handler.rb +60 -0
- data/lib/eluvia/handlers/pagination_handler.rb +25 -0
- data/lib/eluvia/handlers/params_handler.rb +43 -0
- data/lib/eluvia/helpers/attachment_helper.rb +37 -0
- data/lib/eluvia/integrations/eluvia_integration.rb +281 -0
- data/lib/eluvia/models/file.rb +44 -0
- data/lib/eluvia/models/file_wrapper.rb +38 -0
- data/lib/eluvia/models/image_wrapper.rb +38 -0
- data/lib/eluvia/serializers/error_serializer.rb +21 -0
- data/lib/eluvia/serializers/fieldset_serializer.rb +63 -0
- data/lib/eluvia/services/health_service.rb +56 -0
- data/lib/eluvia/uploads.rb +13 -0
- data/lib/eluvia/utils/hash_and_array.rb +98 -0
- data/lib/eluvia/utils/jwt.rb +36 -0
- data/lib/eluvia/utils/string.rb +31 -0
- data/lib/eluvia/utils/uuid.rb +21 -0
- data/lib/eluvia-base.rb +79 -0
- metadata +186 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module PaginationHandler
|
|
3
|
+
extend ::ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
def set_pagination(default_limit: 20, default_offset: 0, default_order_by: 'created_at:asc', fallback_order_by: 'created_at:asc', force_order_by: false)
|
|
6
|
+
|
|
7
|
+
# Pagination
|
|
8
|
+
@limit = params[:limit] ? params[:limit].to_i : default_limit
|
|
9
|
+
@offset = params[:offset] ? params[:offset].to_i : default_offset
|
|
10
|
+
@page = (@offset / @limit) + 1
|
|
11
|
+
@padding = @offset % @limit
|
|
12
|
+
|
|
13
|
+
# Order by
|
|
14
|
+
@order_by = {}
|
|
15
|
+
order_by_param = Eluvia::Base::Config.api_case == 'camel_case' ? :orderBy : :order_by
|
|
16
|
+
order_by = "#{!force_order_by && params.key?(order_by_param) ? params[order_by_param] : default_order_by},#{fallback_order_by}"
|
|
17
|
+
order_by.to_s.split(',').each do |o|
|
|
18
|
+
s = o.to_s.split(':')
|
|
19
|
+
@order_by[s.first] = s.length >= 2 && s[1].downcase == 'desc' ? :desc : :asc
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module ParamsHandler
|
|
3
|
+
extend ::ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
def parse_json_param(value)
|
|
6
|
+
unless value.blank?
|
|
7
|
+
if value.is_a?(String)
|
|
8
|
+
JSON.parse(value) # This can produce JSON::ParserError which is translated to Eluvia::Errors::StandardError
|
|
9
|
+
elsif value.is_a?(ActionController::Parameters)
|
|
10
|
+
value.to_unsafe_h
|
|
11
|
+
elsif value.is_a?(Array)
|
|
12
|
+
result = []
|
|
13
|
+
value.each do |item|
|
|
14
|
+
if item.is_a?(String)
|
|
15
|
+
result << (JSON.parse(item) rescue item) # This can produce JSON::ParserError which is translated to Eluvia::Errors::StandardError
|
|
16
|
+
elsif item.is_a?(ActionController::Parameters)
|
|
17
|
+
result << item.to_unsafe_h
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
result
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def disassemble_filters(params)
|
|
26
|
+
result = {}
|
|
27
|
+
params.each do |key, value|
|
|
28
|
+
split_key = key.to_s.split('__')
|
|
29
|
+
result_component = result
|
|
30
|
+
split_key.each_with_index do |key_component, index|
|
|
31
|
+
if index + 1 == split_key.length # last component
|
|
32
|
+
result_component[key_component.to_sym] = value
|
|
33
|
+
else
|
|
34
|
+
result_component[key_component.to_sym] = {} if result_component[key_component.to_sym].nil?
|
|
35
|
+
result_component = result_component[key_component.to_sym]
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
result
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module AttachmentHelper
|
|
3
|
+
extend ::ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
def attachment_url(attachment, skip_check: false, expires_in: nil)
|
|
6
|
+
unless skip_check
|
|
7
|
+
return nil unless attachment.attached?
|
|
8
|
+
end
|
|
9
|
+
if Rails.application.config.active_storage.service == :local
|
|
10
|
+
Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
|
|
11
|
+
elsif expires_in
|
|
12
|
+
attachment.url(expires_in: expires_in)
|
|
13
|
+
else
|
|
14
|
+
attachment.url
|
|
15
|
+
end
|
|
16
|
+
# NOTE: class method can't be called from instance method because of controller / view helpers, so we need to
|
|
17
|
+
# duplicate the code here.
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class_methods do
|
|
21
|
+
|
|
22
|
+
def attachment_url(attachment, skip_check: false, expires_in: nil)
|
|
23
|
+
unless skip_check
|
|
24
|
+
return nil unless attachment.attached?
|
|
25
|
+
end
|
|
26
|
+
if Rails.application.config.active_storage.service == :local
|
|
27
|
+
Rails.application.routes.url_helpers.rails_blob_path(attachment, only_path: true)
|
|
28
|
+
elsif expires_in
|
|
29
|
+
attachment.url(expires_in: expires_in)
|
|
30
|
+
else
|
|
31
|
+
attachment.url
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,281 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module EluviaIntegration
|
|
3
|
+
extend ::ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
protected
|
|
6
|
+
|
|
7
|
+
def compose_url(endpoint)
|
|
8
|
+
raise Eluvia::Errors::StandardError.new('External service URL not defined.') if @service_url.blank?
|
|
9
|
+
"#{@service_url.to_s.rtrim('/')}/#{endpoint.trim('/')}"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def compose_headers_private(params = nil)
|
|
13
|
+
if Eluvia::Base::Config.authorization_method == 'jwt'
|
|
14
|
+
{
|
|
15
|
+
params: params,
|
|
16
|
+
'x-authorization' => "Private #{Eluvia::Jwt.encode_private_jwt_token}",
|
|
17
|
+
'content-type' => 'application/json'
|
|
18
|
+
}
|
|
19
|
+
elsif Eluvia::Base::Config.authorization_method == 'session_id'
|
|
20
|
+
{
|
|
21
|
+
params: params,
|
|
22
|
+
'x-private-api-key' => Eluvia::Base::Config.private_api_key,
|
|
23
|
+
'content-type' => 'application/json'
|
|
24
|
+
}
|
|
25
|
+
else
|
|
26
|
+
raise Eluvia::Errors::StandardError.new('Unknown authorization method.')
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def compose_headers_public(session_id_or_user, params = nil)
|
|
31
|
+
if Eluvia::Base::Config.authorization_method == 'jwt'
|
|
32
|
+
{
|
|
33
|
+
params: params,
|
|
34
|
+
'x-authorization' => "Bearer #{Eluvia::Jwt.encode_public_jwt_token(session_id_or_user)}",
|
|
35
|
+
'content-type' => 'application/json'
|
|
36
|
+
}
|
|
37
|
+
elsif Eluvia::Base::Config.authorization_method == 'session_id'
|
|
38
|
+
{
|
|
39
|
+
params: params,
|
|
40
|
+
'x-public-api-key' => Eluvia::Base::Config.public_api_key,
|
|
41
|
+
'session-id' => session_id_or_user,
|
|
42
|
+
'content-type' => 'application/json'
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
raise Eluvia::Errors::StandardError.new('Unknown authorization method.')
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def compose_headers_both(session_id_or_user, params = nil)
|
|
50
|
+
if Eluvia::Base::Config.authorization_method == 'jwt'
|
|
51
|
+
compose_headers_public(session_id_or_user, params)
|
|
52
|
+
elsif Eluvia::Base::Config.authorization_method == 'session_id'
|
|
53
|
+
compose_headers_private(params).merge(compose_headers_public(session_id_or_user, params))
|
|
54
|
+
else
|
|
55
|
+
raise Eluvia::Errors::StandardError.new('Unknown authorization method.')
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def perform_get_request(url, headers)
|
|
60
|
+
RestClient::Request.execute(
|
|
61
|
+
method: :get,
|
|
62
|
+
url: url,
|
|
63
|
+
headers: headers
|
|
64
|
+
)
|
|
65
|
+
rescue RestClient::NotFound => e
|
|
66
|
+
m = parse_error_message(e)
|
|
67
|
+
raise Eluvia::Errors::NotFound.new(m || 'Not found response from external service.')
|
|
68
|
+
rescue RestClient::BadRequest => e
|
|
69
|
+
m = parse_error_message(e)
|
|
70
|
+
raise Eluvia::Errors::BadRequest.new(m || 'Bad response from external service.')
|
|
71
|
+
rescue RestClient::UnprocessableEntity => e
|
|
72
|
+
raise Eluvia::Errors::UnprocessableEntity.new(parse_error_messages(e))
|
|
73
|
+
rescue RestClient::Exception => e
|
|
74
|
+
m = parse_error_message(e)
|
|
75
|
+
raise Eluvia::Errors::StandardError.new(m || 'Standard error from external service.')
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def parse_get_request(url, headers)
|
|
79
|
+
response = perform_get_request(url, headers)
|
|
80
|
+
if response && response.code == 204
|
|
81
|
+
true
|
|
82
|
+
elsif response && response.code < 300
|
|
83
|
+
data = JSON.parse(response.body) rescue nil
|
|
84
|
+
raise Eluvia::Errors::StandardError.new('Unable to parse response from external service.') if data.nil?
|
|
85
|
+
normalize_data(data)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def perform_post_request(url, headers, data = nil)
|
|
90
|
+
RestClient::Request.execute(
|
|
91
|
+
method: :post,
|
|
92
|
+
url: url,
|
|
93
|
+
headers: headers,
|
|
94
|
+
payload: data.to_json
|
|
95
|
+
)
|
|
96
|
+
rescue RestClient::NotFound => e
|
|
97
|
+
m = parse_error_message(e)
|
|
98
|
+
raise Eluvia::Errors::NotFound.new(m || 'Not found response from external service.')
|
|
99
|
+
rescue RestClient::BadRequest => e
|
|
100
|
+
m = parse_error_message(e)
|
|
101
|
+
raise Eluvia::Errors::BadRequest.new(m || 'Bad response from external service.')
|
|
102
|
+
rescue RestClient::UnprocessableEntity => e
|
|
103
|
+
raise Eluvia::Errors::UnprocessableEntity.new(parse_error_messages(e))
|
|
104
|
+
rescue RestClient::Exception => e
|
|
105
|
+
m = parse_error_message(e)
|
|
106
|
+
raise Eluvia::Errors::StandardError.new(m || 'Standard error from external service.')
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def parse_post_request(url, headers, data = nil)
|
|
110
|
+
response = perform_post_request(url, headers, data)
|
|
111
|
+
if response && response.code == 204
|
|
112
|
+
true
|
|
113
|
+
elsif response && response.code < 300
|
|
114
|
+
data = JSON.parse(response.body) rescue nil
|
|
115
|
+
raise Eluvia::Errors::StandardError.new('Unable to parse response from external service.') if data.nil?
|
|
116
|
+
normalize_data(data)
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def perform_put_request(url, headers, data = nil)
|
|
121
|
+
RestClient::Request.execute(
|
|
122
|
+
method: :put,
|
|
123
|
+
url: url,
|
|
124
|
+
headers: headers,
|
|
125
|
+
payload: data.to_json
|
|
126
|
+
)
|
|
127
|
+
rescue RestClient::NotFound => e
|
|
128
|
+
m = parse_error_message(e)
|
|
129
|
+
raise Eluvia::Errors::NotFound.new(m || 'Not found response from external service.')
|
|
130
|
+
rescue RestClient::BadRequest => e
|
|
131
|
+
m = parse_error_message(e)
|
|
132
|
+
raise Eluvia::Errors::BadRequest.new(m || 'Bad response from external service.')
|
|
133
|
+
rescue RestClient::UnprocessableEntity => e
|
|
134
|
+
raise Eluvia::Errors::UnprocessableEntity.new(parse_error_messages(e))
|
|
135
|
+
rescue RestClient::Exception => e
|
|
136
|
+
m = parse_error_message(e)
|
|
137
|
+
raise Eluvia::Errors::StandardError.new(m || 'Standard error from external service.')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def parse_put_request(url, headers, data = nil)
|
|
141
|
+
response = perform_put_request(url, headers, data)
|
|
142
|
+
if response && response.code == 204
|
|
143
|
+
true
|
|
144
|
+
elsif response && response.code < 300
|
|
145
|
+
data = JSON.parse(response.body) rescue nil
|
|
146
|
+
raise Eluvia::Errors::StandardError.new('Unable to parse response from external service.') if data.nil?
|
|
147
|
+
normalize_data(data)
|
|
148
|
+
end
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def perform_patch_request(url, headers, data = nil)
|
|
152
|
+
RestClient::Request.execute(
|
|
153
|
+
method: :patch,
|
|
154
|
+
url: url,
|
|
155
|
+
headers: headers,
|
|
156
|
+
payload: data.to_json
|
|
157
|
+
)
|
|
158
|
+
rescue RestClient::NotFound => e
|
|
159
|
+
m = parse_error_message(e)
|
|
160
|
+
raise Eluvia::Errors::NotFound.new(m || 'Not found response from external service.')
|
|
161
|
+
rescue RestClient::BadRequest => e
|
|
162
|
+
m = parse_error_message(e)
|
|
163
|
+
raise Eluvia::Errors::BadRequest.new(m || 'Bad response from external service.')
|
|
164
|
+
rescue RestClient::UnprocessableEntity => e
|
|
165
|
+
raise Eluvia::Errors::UnprocessableEntity.new(parse_error_messages(e))
|
|
166
|
+
rescue RestClient::Exception => e
|
|
167
|
+
m = parse_error_message(e)
|
|
168
|
+
raise Eluvia::Errors::StandardError.new(m || 'Standard error from external service.')
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def parse_patch_request(url, headers, data = nil)
|
|
172
|
+
response = perform_patch_request(url, headers, data)
|
|
173
|
+
if response && response.code == 204
|
|
174
|
+
true
|
|
175
|
+
elsif response && response.code < 300
|
|
176
|
+
data = JSON.parse(response.body) rescue nil
|
|
177
|
+
raise Eluvia::Errors::StandardError.new('Unable to parse response from external service.') if data.nil?
|
|
178
|
+
normalize_data(data)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def perform_delete_request(url, headers)
|
|
183
|
+
RestClient::Request.execute(
|
|
184
|
+
method: :delete,
|
|
185
|
+
url: url,
|
|
186
|
+
headers: headers
|
|
187
|
+
)
|
|
188
|
+
rescue RestClient::NotFound => e
|
|
189
|
+
m = parse_error_message(e)
|
|
190
|
+
raise Eluvia::Errors::NotFound.new(m || 'Not found response from external service.')
|
|
191
|
+
rescue RestClient::BadRequest => e
|
|
192
|
+
m = parse_error_message(e)
|
|
193
|
+
raise Eluvia::Errors::BadRequest.new(m || 'Bad response from external service.')
|
|
194
|
+
rescue RestClient::UnprocessableEntity => e
|
|
195
|
+
raise Eluvia::Errors::UnprocessableEntity.new(parse_error_messages(e))
|
|
196
|
+
rescue RestClient::Exception => e
|
|
197
|
+
m = parse_error_message(e)
|
|
198
|
+
raise Eluvia::Errors::StandardError.new(m || 'Standard error from external service.')
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
def parse_delete_request(url, headers)
|
|
202
|
+
response = perform_delete_request(url, headers)
|
|
203
|
+
if response && response.code == 204
|
|
204
|
+
true
|
|
205
|
+
elsif response && response.code < 300
|
|
206
|
+
data = JSON.parse(response.body) rescue nil
|
|
207
|
+
raise Eluvia::Errors::StandardError.new('Unable to parse response from external service.') if data.nil?
|
|
208
|
+
normalize_data(data)
|
|
209
|
+
end
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def parse_error_message(e)
|
|
213
|
+
response_data = JSON.parse(e.response.to_s) rescue nil
|
|
214
|
+
if response_data.is_a?(Hash) && response_data['errors']
|
|
215
|
+
response_data['errors'].first&.[]('message')
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def parse_error_messages(e)
|
|
220
|
+
result = {}
|
|
221
|
+
response_data = JSON.parse(e.response.to_s) rescue nil
|
|
222
|
+
if response_data.is_a?(Hash) && response_data['errors']
|
|
223
|
+
response_data['errors'].each do |error|
|
|
224
|
+
result[error['source']] = error['message']
|
|
225
|
+
end
|
|
226
|
+
end
|
|
227
|
+
result
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def paginate_with_offset(limit = 20, &block)
|
|
231
|
+
offset = 0
|
|
232
|
+
not_empty = true
|
|
233
|
+
result = []
|
|
234
|
+
while not_empty
|
|
235
|
+
data = block.call(limit, offset)
|
|
236
|
+
if data.is_a?(Array) && !data.empty?
|
|
237
|
+
result += data
|
|
238
|
+
offset += limit
|
|
239
|
+
else
|
|
240
|
+
not_empty = false
|
|
241
|
+
end
|
|
242
|
+
end
|
|
243
|
+
result
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def paginate_with_page(&block)
|
|
247
|
+
page = 1
|
|
248
|
+
not_empty = true
|
|
249
|
+
result = []
|
|
250
|
+
while not_empty
|
|
251
|
+
data = block.call(page)
|
|
252
|
+
if data.is_a?(Array) && !data.empty?
|
|
253
|
+
result += data
|
|
254
|
+
page += 1
|
|
255
|
+
else
|
|
256
|
+
not_empty = false
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
result
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def calculate_limit_and_offset(page, per)
|
|
263
|
+
page = 1 if page.to_i < 1
|
|
264
|
+
{
|
|
265
|
+
limit: per,
|
|
266
|
+
offset: (page.to_i - 1) * per.to_i
|
|
267
|
+
}
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
def normalize_data(data)
|
|
271
|
+
if data.is_a?(Hash)
|
|
272
|
+
return data.deep_symbolize_keys
|
|
273
|
+
end
|
|
274
|
+
if data.is_a?(Array)
|
|
275
|
+
return data.map { |item| normalize_data(item) }
|
|
276
|
+
end
|
|
277
|
+
data
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
end
|
|
281
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
class File
|
|
3
|
+
include ::ActiveModel::Model
|
|
4
|
+
include Eluvia::ActiveModel::Attributes
|
|
5
|
+
include Eluvia::AttachmentHelper
|
|
6
|
+
|
|
7
|
+
# ***********************************************************************
|
|
8
|
+
# Attributes
|
|
9
|
+
# ***********************************************************************
|
|
10
|
+
|
|
11
|
+
# MIME type of the file
|
|
12
|
+
string_attr :type
|
|
13
|
+
|
|
14
|
+
# URL to access the file
|
|
15
|
+
string_attr :url
|
|
16
|
+
|
|
17
|
+
# File name with extension
|
|
18
|
+
string_attr :filename
|
|
19
|
+
|
|
20
|
+
# Size of the file in bytes
|
|
21
|
+
integer_attr :size
|
|
22
|
+
|
|
23
|
+
# Base64 encoded content of the file, used for uploads
|
|
24
|
+
string_attr :content
|
|
25
|
+
|
|
26
|
+
# Key identifying a previously uploaded (chunked/direct) temp object, used as an alternative to
|
|
27
|
+
# `content` for finalizing large uploads via `Eluvia::Uploads.finalizer`
|
|
28
|
+
string_attr :upload_key
|
|
29
|
+
|
|
30
|
+
# ***********************************************************************
|
|
31
|
+
# Constructor from attachment
|
|
32
|
+
# ***********************************************************************
|
|
33
|
+
|
|
34
|
+
def self.from_active_storage_attachment(attachment, **options)
|
|
35
|
+
new(
|
|
36
|
+
type: attachment.content_type,
|
|
37
|
+
url: attachment_url(attachment, **options),
|
|
38
|
+
filename: attachment.filename,
|
|
39
|
+
size: attachment.byte_size
|
|
40
|
+
)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
class FileWrapper
|
|
3
|
+
include ::ActiveModel::Model
|
|
4
|
+
include Eluvia::ActiveModel::Attributes
|
|
5
|
+
include Eluvia::ActiveRecord::FileAttribute
|
|
6
|
+
|
|
7
|
+
# ***********************************************************************
|
|
8
|
+
# Attributes
|
|
9
|
+
# ***********************************************************************
|
|
10
|
+
|
|
11
|
+
file_attr :file, nature: :file, declare: true
|
|
12
|
+
|
|
13
|
+
string_attr :id
|
|
14
|
+
|
|
15
|
+
def id
|
|
16
|
+
@id ||= (self.url ? Eluvia::Uuid.str_to_uuid("File-#{self.url}") : nil)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
string_attr :type
|
|
20
|
+
|
|
21
|
+
def type
|
|
22
|
+
file&.type
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
string_attr :url
|
|
26
|
+
|
|
27
|
+
def url
|
|
28
|
+
file&.url
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
string_attr :filename
|
|
32
|
+
|
|
33
|
+
def filename
|
|
34
|
+
file&.filename
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
class ImageWrapper
|
|
3
|
+
include ::ActiveModel::Model
|
|
4
|
+
include Eluvia::ActiveModel::Attributes
|
|
5
|
+
include Eluvia::ActiveRecord::FileAttribute
|
|
6
|
+
|
|
7
|
+
# ***********************************************************************
|
|
8
|
+
# Attributes
|
|
9
|
+
# ***********************************************************************
|
|
10
|
+
|
|
11
|
+
file_attr :file, nature: :image, declare: true
|
|
12
|
+
|
|
13
|
+
string_attr :id
|
|
14
|
+
|
|
15
|
+
def id
|
|
16
|
+
@id ||= (self.url ? Eluvia::Uuid.str_to_uuid("Image-#{self.url}") : nil)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
string_attr :type
|
|
20
|
+
|
|
21
|
+
def type
|
|
22
|
+
file&.type
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
string_attr :url
|
|
26
|
+
|
|
27
|
+
def url
|
|
28
|
+
file&.url
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
string_attr :filename
|
|
32
|
+
|
|
33
|
+
def filename
|
|
34
|
+
file&.filename
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
class ErrorSerializer
|
|
3
|
+
|
|
4
|
+
attr_reader :error
|
|
5
|
+
|
|
6
|
+
def initialize(error)
|
|
7
|
+
@error = error
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def to_h
|
|
11
|
+
{
|
|
12
|
+
errors: Array.wrap(error.to_h).flatten
|
|
13
|
+
}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def to_json(_payload)
|
|
17
|
+
to_h.to_json
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module Serializers
|
|
3
|
+
module FieldsetSerializer
|
|
4
|
+
|
|
5
|
+
def serialize(record, fields: nil)
|
|
6
|
+
read_fieldset(fields).to_h do |rest_field|
|
|
7
|
+
[rest_field.name, read_attribute(record, rest_field.name)]
|
|
8
|
+
end
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def read_fieldset(fields = nil)
|
|
12
|
+
@read_fieldsets ||= {}
|
|
13
|
+
@read_fieldsets[fields] ||= resolve_read_fieldset(fields)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def readable_fields
|
|
17
|
+
raise NotImplementedError, "#{self.class} must implement #readable_fields"
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def default_read_fields
|
|
21
|
+
nil
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def read_attribute(record, field_name)
|
|
25
|
+
raise NotImplementedError, "#{self.class} must implement #read_attribute"
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def resolve_read_fieldset(fields)
|
|
31
|
+
readable = readable_fields.map(&:to_s)
|
|
32
|
+
defaults = (default_read_fields || readable_fields).map(&:to_s)
|
|
33
|
+
|
|
34
|
+
requested =
|
|
35
|
+
if fields.present?
|
|
36
|
+
Eluvia::Fieldset::RFS.create_from_string(fields.to_s, default_field_fieldset_string: Eluvia::Fieldset::DEFAULT_FIELDS)
|
|
37
|
+
else
|
|
38
|
+
Eluvia::Fieldset::RFS.create_from_list(defaults, default_field_fieldset_string: Eluvia::Fieldset::DEFAULT_FIELDS)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
if requested.include?(Eluvia::Fieldset::ALL_FIELDS)
|
|
42
|
+
# Wildcard `__all__` replaces the whole request - anything else next to it is discarded
|
|
43
|
+
requested = Eluvia::Fieldset::RFS.create_from_list(readable, default_field_fieldset_string: Eluvia::Fieldset::ALL_FIELDS)
|
|
44
|
+
elsif requested.include?(Eluvia::Fieldset::DEFAULT_FIELDS)
|
|
45
|
+
# Wildcard `__default__` is replaced by the default fields and joined with whatever else was requested
|
|
46
|
+
requested.delete(Eluvia::Fieldset::DEFAULT_FIELDS)
|
|
47
|
+
requested |= Eluvia::Fieldset::RFS.create_from_list(defaults, default_field_fieldset_string: Eluvia::Fieldset::DEFAULT_FIELDS)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
invalid_fields = requested.reject { |rf| readable.include?(rf.name) }.map(&:name)
|
|
51
|
+
if invalid_fields.any?
|
|
52
|
+
messages = invalid_fields.to_h do |field_name|
|
|
53
|
+
[field_name, I18n.t('eluvia.errors.fieldset.unknown_field', default: 'Unknown field.')]
|
|
54
|
+
end
|
|
55
|
+
raise Eluvia::Errors::UnprocessableEntity.new(messages)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
requested & Eluvia::Fieldset::RFS.create_from_list(readable)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
class HealthService
|
|
3
|
+
include Singleton
|
|
4
|
+
|
|
5
|
+
CHECKS = [:databases].freeze
|
|
6
|
+
CHECKED_DATABASES = [:default].freeze
|
|
7
|
+
|
|
8
|
+
def check
|
|
9
|
+
result_content = {}
|
|
10
|
+
result_content[:checks] = {}
|
|
11
|
+
result_ok = true
|
|
12
|
+
CHECKS.each do |check|
|
|
13
|
+
if check == :databases
|
|
14
|
+
check_content, check_ok = check_databases
|
|
15
|
+
else
|
|
16
|
+
raise Eluvia::Errors::StandardError.new("Unknown check #{check}.")
|
|
17
|
+
end
|
|
18
|
+
result_content[:checks][check] = check_content
|
|
19
|
+
result_ok &&= check_ok
|
|
20
|
+
end
|
|
21
|
+
if result_ok
|
|
22
|
+
result_content[:message] = 'I am alive.'
|
|
23
|
+
else
|
|
24
|
+
result_content[:message] = 'One of the checks failed.'
|
|
25
|
+
end
|
|
26
|
+
[result_content, result_ok]
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def check_databases
|
|
32
|
+
result_content = []
|
|
33
|
+
result_ok = true
|
|
34
|
+
CHECKED_DATABASES.each do |database|
|
|
35
|
+
ok = check_database(database)
|
|
36
|
+
result_ok &&= ok
|
|
37
|
+
result_content << {
|
|
38
|
+
database => {
|
|
39
|
+
ok: ok
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
end
|
|
43
|
+
[result_content, result_ok]
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def check_database(_database)
|
|
47
|
+
|
|
48
|
+
::ActiveRecord::Base.connection.execute('SELECT 1')
|
|
49
|
+
true
|
|
50
|
+
rescue StandardError
|
|
51
|
+
false
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Eluvia
|
|
2
|
+
module Uploads
|
|
3
|
+
|
|
4
|
+
# Extension point for finalizing chunked/direct uploads (identified by `Eluvia::File#upload_key`)
|
|
5
|
+
# into the underlying ActiveStorage attachment. Left unset by default so that eluvia-base has no
|
|
6
|
+
# dependency on any specific storage provider (e.g. aws-sdk-s3) or on habarico.
|
|
7
|
+
#
|
|
8
|
+
# Consumers register a callable of the form:
|
|
9
|
+
# ->(attachment_record, upload_key, filename) { ... }
|
|
10
|
+
mattr_accessor :finalizer
|
|
11
|
+
|
|
12
|
+
end
|
|
13
|
+
end
|