avo 0.2.1 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of avo might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/Gemfile +49 -48
- data/Gemfile.lock +19 -2
- data/README.md +33 -54
- data/app/controllers/avo/application_controller.rb +34 -1
- data/app/controllers/avo/filters_controller.rb +19 -0
- data/app/controllers/avo/relations_controller.rb +34 -0
- data/app/controllers/avo/resource_overview_controller.rb +14 -7
- data/app/controllers/avo/resources_controller.rb +66 -142
- data/app/controllers/avo/search_controller.rb +55 -0
- data/app/helpers/avo/application_helper.rb +6 -2
- data/app/views/layouts/avo/_javascript.html.erb +1 -0
- data/app/views/layouts/avo/application.html.erb +34 -17
- data/app/views/partials/_footer.html.erb +1 -1
- data/app/views/partials/_header.html.erb +1 -1
- data/avo.gemspec +18 -4
- data/config/credentials.yml.enc +1 -0
- data/config/routes.rb +11 -7
- data/lib/avo.rb +2 -0
- data/lib/avo/app/app.rb +18 -29
- data/lib/avo/app/authorization_service.rb +40 -0
- data/lib/avo/app/fields/has_and_belongs_to_many.rb +1 -0
- data/lib/avo/app/fields/has_many.rb +1 -0
- data/lib/avo/app/fields/id_field.rb +4 -4
- data/lib/avo/app/licensing/community_license.rb +4 -0
- data/lib/avo/app/licensing/hq.rb +85 -0
- data/lib/avo/app/licensing/license.rb +48 -0
- data/lib/avo/app/licensing/license_manager.rb +25 -0
- data/lib/avo/app/licensing/null_license.rb +12 -0
- data/lib/avo/app/licensing/pro_license.rb +9 -0
- data/lib/avo/app/resource.rb +31 -12
- data/lib/avo/configuration.rb +14 -0
- data/lib/avo/engine.rb +1 -1
- data/lib/avo/version.rb +1 -1
- data/lib/generators/avo/templates/initializer.rb +2 -0
- data/lib/generators/avo/templates/views/_footer.html.erb +1 -1
- data/lib/generators/avo/templates/views/_header.html.erb +1 -1
- data/lib/generators/avo/templates/views/_scripts.html.erb +0 -0
- data/public/avo-packs/css/application-73e568bc.css +3 -0
- data/public/avo-packs/css/application-73e568bc.css.br +0 -0
- data/public/avo-packs/css/application-73e568bc.css.gz +0 -0
- data/public/avo-packs/js/application-044386b1f6fe7a8dcb9f.js +3 -0
- data/public/avo-packs/js/{application-8071f9a0f167bb82b39d.js.LICENSE.txt → application-044386b1f6fe7a8dcb9f.js.LICENSE.txt} +0 -0
- data/public/avo-packs/js/application-044386b1f6fe7a8dcb9f.js.br +0 -0
- data/public/avo-packs/js/application-044386b1f6fe7a8dcb9f.js.gz +0 -0
- data/public/avo-packs/js/application-044386b1f6fe7a8dcb9f.js.map +1 -0
- data/public/avo-packs/js/application-044386b1f6fe7a8dcb9f.js.map.br +0 -0
- data/public/avo-packs/js/application-044386b1f6fe7a8dcb9f.js.map.gz +0 -0
- data/public/avo-packs/manifest.json +8 -6
- data/public/avo-packs/manifest.json.br +0 -0
- data/public/avo-packs/manifest.json.gz +0 -0
- data/public/avo-packs/media/svgs/arrow-circle-right-1ad1e15ec9a7aa54b67d126566a5aa2d.svg +1 -0
- data/public/avo-packs/media/svgs/arrow-circle-right-1ad1e15ec9a7aa54b67d126566a5aa2d.svg.br +0 -0
- data/public/avo-packs/media/svgs/arrow-circle-right-1ad1e15ec9a7aa54b67d126566a5aa2d.svg.gz +0 -0
- data/public/avo-packs/media/svgs/exclamation-8d1c0baa390a8df9bb52176011eb5892.svg +1 -0
- data/public/avo-packs/media/svgs/exclamation-8d1c0baa390a8df9bb52176011eb5892.svg.br +0 -0
- data/public/avo-packs/media/svgs/exclamation-8d1c0baa390a8df9bb52176011eb5892.svg.gz +0 -0
- metadata +70 -25
- data/public/avo-packs/css/application-64b0f124.css +0 -3
- data/public/avo-packs/css/application-64b0f124.css.br +0 -0
- data/public/avo-packs/css/application-64b0f124.css.gz +0 -0
- data/public/avo-packs/js/application-8071f9a0f167bb82b39d.js +0 -3
- data/public/avo-packs/js/application-8071f9a0f167bb82b39d.js.br +0 -0
- data/public/avo-packs/js/application-8071f9a0f167bb82b39d.js.gz +0 -0
- data/public/avo-packs/js/application-8071f9a0f167bb82b39d.js.map +0 -1
- data/public/avo-packs/js/application-8071f9a0f167bb82b39d.js.map.br +0 -0
- data/public/avo-packs/js/application-8071f9a0f167bb82b39d.js.map.gz +0 -0
@@ -2,21 +2,26 @@ require_dependency 'avo/application_controller'
|
|
2
2
|
|
3
3
|
module Avo
|
4
4
|
class ResourcesController < ApplicationController
|
5
|
+
before_action :authorize_user
|
6
|
+
|
5
7
|
def index
|
6
8
|
params[:page] ||= 1
|
7
9
|
params[:per_page] ||= Avo.configuration.per_page
|
8
10
|
params[:sort_by] = params[:sort_by].present? ? params[:sort_by] : :created_at
|
9
11
|
params[:sort_direction] = params[:sort_direction].present? ? params[:sort_direction] : :desc
|
10
|
-
|
12
|
+
|
13
|
+
query = AuthorizationService.with_policy current_user, resource_model
|
11
14
|
|
12
15
|
if params[:via_resource_name].present? and params[:via_resource_id].present? and params[:via_relationship].present?
|
13
|
-
# get the
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
query =
|
16
|
+
# get the related resource (via_resource)
|
17
|
+
related_model = App.get_resource_by_name(params[:via_resource_name]).model
|
18
|
+
|
19
|
+
relation = related_model.find(params[:via_resource_id]).public_send(params[:via_relationship])
|
20
|
+
query = AuthorizationService.with_policy current_user, relation
|
21
|
+
|
18
22
|
params[:per_page] = Avo.configuration.via_per_page
|
19
23
|
elsif ['has_many', 'has_and_belongs_to_many'].include? params[:for_relation]
|
24
|
+
# has_many searchable query
|
20
25
|
resources = resource_model.all.map do |model|
|
21
26
|
{
|
22
27
|
value: model.id,
|
@@ -27,22 +32,18 @@ module Avo
|
|
27
32
|
return render json: {
|
28
33
|
resources: resources
|
29
34
|
}
|
30
|
-
else
|
31
|
-
query = resource_model
|
32
35
|
end
|
33
36
|
|
34
37
|
query = query.order("#{params[:sort_by]} #{params[:sort_direction]}")
|
35
38
|
|
36
|
-
|
37
|
-
|
38
|
-
query = filter_class.safe_constantize.new.apply_query request, query, filter_value
|
39
|
-
end
|
39
|
+
applied_filters.each do |filter_class, filter_value|
|
40
|
+
query = filter_class.safe_constantize.new.apply_query request, query, filter_value
|
40
41
|
end
|
41
42
|
|
42
43
|
# Eager load the attachments
|
43
44
|
query = eager_load_files(query)
|
44
45
|
|
45
|
-
# Eager
|
46
|
+
# Eager load the relations
|
46
47
|
if avo_resource.includes.present?
|
47
48
|
query = query.includes(*avo_resource.includes)
|
48
49
|
end
|
@@ -51,48 +52,21 @@ module Avo
|
|
51
52
|
|
52
53
|
resources_with_fields = []
|
53
54
|
resources.each do |resource|
|
54
|
-
resources_with_fields << Avo::Resources::Resource.hydrate_resource(resource, avo_resource, :index)
|
55
|
+
resources_with_fields << Avo::Resources::Resource.hydrate_resource(model: resource, resource: avo_resource, view: :index, user: current_user)
|
55
56
|
end
|
56
57
|
|
57
|
-
meta = {
|
58
|
-
per_page_steps: Avo.configuration.per_page_steps,
|
59
|
-
available_view_types: avo_resource.available_view_types,
|
60
|
-
default_view_type: avo_resource.default_view_type || Avo.configuration.default_view_type,
|
61
|
-
}
|
62
|
-
|
63
58
|
render json: {
|
64
59
|
success: true,
|
65
|
-
meta:
|
60
|
+
meta: build_meta,
|
66
61
|
resources: resources_with_fields,
|
67
62
|
per_page: params[:per_page],
|
68
63
|
total_pages: resources.total_pages,
|
69
64
|
}
|
70
65
|
end
|
71
66
|
|
72
|
-
def search
|
73
|
-
if params[:resource_name].present?
|
74
|
-
resources = add_link_to_search_results search_resource(avo_resource)
|
75
|
-
else
|
76
|
-
resources = []
|
77
|
-
|
78
|
-
resources_to_search_through = App.get_resources.select { |r| r.search.present? }
|
79
|
-
resources_to_search_through.each do |resource_model|
|
80
|
-
found_resources = add_link_to_search_results search_resource(resource_model)
|
81
|
-
resources.push({
|
82
|
-
label: resource_model.name,
|
83
|
-
resources: found_resources
|
84
|
-
})
|
85
|
-
end
|
86
|
-
end
|
87
|
-
|
88
|
-
render json: {
|
89
|
-
resources: resources
|
90
|
-
}
|
91
|
-
end
|
92
|
-
|
93
67
|
def show
|
94
68
|
render json: {
|
95
|
-
resource: Avo::Resources::Resource.hydrate_resource(resource, avo_resource, @view || :show),
|
69
|
+
resource: Avo::Resources::Resource.hydrate_resource(model: resource, resource: avo_resource, view: @view || :show, user: current_user),
|
96
70
|
}
|
97
71
|
end
|
98
72
|
|
@@ -120,7 +94,7 @@ module Avo
|
|
120
94
|
|
121
95
|
render json: {
|
122
96
|
success: true,
|
123
|
-
resource: Avo::Resources::Resource.hydrate_resource(resource, avo_resource, :show),
|
97
|
+
resource: Avo::Resources::Resource.hydrate_resource(model: resource, resource: avo_resource, view: :show, user: current_user),
|
124
98
|
message: 'Resource updated',
|
125
99
|
}
|
126
100
|
end
|
@@ -143,16 +117,14 @@ module Avo
|
|
143
117
|
|
144
118
|
render json: {
|
145
119
|
success: true,
|
146
|
-
resource: Avo::Resources::Resource.hydrate_resource(resource, avo_resource, :create),
|
120
|
+
resource: Avo::Resources::Resource.hydrate_resource(model: resource, resource: avo_resource, view: :create, user: current_user),
|
147
121
|
message: 'Resource created',
|
148
122
|
}
|
149
123
|
end
|
150
124
|
|
151
|
-
def
|
152
|
-
resource = resource_model.new
|
153
|
-
|
125
|
+
def new
|
154
126
|
render json: {
|
155
|
-
resource: Avo::Resources::Resource.hydrate_resource(
|
127
|
+
resource: Avo::Resources::Resource.hydrate_resource(model: resource_model.new, resource: avo_resource, view: :create, user: current_user),
|
156
128
|
}
|
157
129
|
end
|
158
130
|
|
@@ -164,64 +136,7 @@ module Avo
|
|
164
136
|
}
|
165
137
|
end
|
166
138
|
|
167
|
-
def filters
|
168
|
-
avo_filters = avo_resource.get_filters
|
169
|
-
filters = []
|
170
|
-
|
171
|
-
avo_filters.each do |filter|
|
172
|
-
filters.push(filter.new.render_response)
|
173
|
-
end
|
174
|
-
|
175
|
-
render json: {
|
176
|
-
filters: filters,
|
177
|
-
}
|
178
|
-
end
|
179
|
-
|
180
|
-
def attach
|
181
|
-
attachment_class = App.get_model_class_by_name params[:attachment_name].pluralize 1
|
182
|
-
attachment_model = attachment_class.safe_constantize.find params[:attachment_id]
|
183
|
-
attached = resource.send(params[:attachment_name]) << attachment_model
|
184
|
-
|
185
|
-
render json: {
|
186
|
-
success: true,
|
187
|
-
message: "#{attachment_class} attached.",
|
188
|
-
}
|
189
|
-
end
|
190
|
-
|
191
|
-
def detach
|
192
|
-
attachment_class = App.get_model_class_by_name params[:attachment_name].pluralize 1
|
193
|
-
attachment_model = attachment_class.safe_constantize.find params[:attachment_id]
|
194
|
-
attached = resource.send(params[:attachment_name]).delete attachment_model
|
195
|
-
|
196
|
-
render json: {
|
197
|
-
success: true,
|
198
|
-
message: "#{attachment_class} attached.",
|
199
|
-
}
|
200
|
-
end
|
201
|
-
|
202
|
-
def cast_nullable(params)
|
203
|
-
fields = avo_resource.get_fields
|
204
|
-
|
205
|
-
nullable_fields = fields.filter { |field| field.nullable }
|
206
|
-
.map { |field| [field.id, field.null_values] }
|
207
|
-
.to_h
|
208
|
-
|
209
|
-
params.each do |key, value|
|
210
|
-
nullable = nullable_fields[key.to_sym]
|
211
|
-
|
212
|
-
if nullable.present? && value.in?(nullable)
|
213
|
-
params[key] = nil
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
params
|
218
|
-
end
|
219
|
-
|
220
139
|
private
|
221
|
-
def resource
|
222
|
-
eager_load_files(resource_model).find params[:id]
|
223
|
-
end
|
224
|
-
|
225
140
|
def permitted_params
|
226
141
|
permitted = avo_resource.get_fields.select(&:updatable).map do |field|
|
227
142
|
# If it's a relation
|
@@ -250,41 +165,6 @@ module Avo
|
|
250
165
|
params.require(:resource).permit(permitted_params)
|
251
166
|
end
|
252
167
|
|
253
|
-
def search_resource(avo_resource)
|
254
|
-
avo_resource.query_search(query: params[:q], via_resource_name: params[:via_resource_name], via_resource_id: params[:via_resource_id])
|
255
|
-
end
|
256
|
-
|
257
|
-
def add_link_to_search_results(resources)
|
258
|
-
resources.map do |model|
|
259
|
-
resource = model.as_json
|
260
|
-
resource[:link] = "/resources/#{model.class.to_s.singularize.underscore}/#{model.id}"
|
261
|
-
|
262
|
-
resource
|
263
|
-
end
|
264
|
-
end
|
265
|
-
|
266
|
-
def eager_load_files(query)
|
267
|
-
if avo_resource.attached_file_fields.present?
|
268
|
-
avo_resource.attached_file_fields.map(&:id).map do |field|
|
269
|
-
query = query.send :"with_attached_#{field}"
|
270
|
-
end
|
271
|
-
end
|
272
|
-
|
273
|
-
query
|
274
|
-
end
|
275
|
-
|
276
|
-
def process_file_field(field, attachment)
|
277
|
-
if attachment.is_a? ActionDispatch::Http::UploadedFile
|
278
|
-
# New file has been attached
|
279
|
-
field.attach attachment
|
280
|
-
elsif attachment.blank?
|
281
|
-
# File has been deleted
|
282
|
-
field.purge
|
283
|
-
elsif attachment.is_a? String
|
284
|
-
# Field is unchanged
|
285
|
-
end
|
286
|
-
end
|
287
|
-
|
288
168
|
def update_file_fields
|
289
169
|
# Pick and attach file fields
|
290
170
|
attached_file_fields = avo_resource.attached_file_fields
|
@@ -322,7 +202,19 @@ module Avo
|
|
322
202
|
end
|
323
203
|
end
|
324
204
|
|
325
|
-
def
|
205
|
+
def process_file_field(field, attachment)
|
206
|
+
if attachment.is_a? ActionDispatch::Http::UploadedFile
|
207
|
+
# New file has been attached
|
208
|
+
field.attach attachment
|
209
|
+
elsif attachment.blank?
|
210
|
+
# File has been deleted
|
211
|
+
field.purge
|
212
|
+
elsif attachment.is_a? String
|
213
|
+
# Field is unchanged
|
214
|
+
end
|
215
|
+
end
|
216
|
+
|
217
|
+
def applied_filters
|
326
218
|
if params[:filters].present?
|
327
219
|
return JSON.parse(Base64.decode64(params[:filters]))
|
328
220
|
end
|
@@ -339,5 +231,37 @@ module Avo
|
|
339
231
|
|
340
232
|
filter_defaults
|
341
233
|
end
|
234
|
+
|
235
|
+
def cast_nullable(params)
|
236
|
+
fields = avo_resource.get_fields
|
237
|
+
|
238
|
+
nullable_fields = fields.filter { |field| field.nullable }
|
239
|
+
.map { |field| [field.id, field.null_values] }
|
240
|
+
.to_h
|
241
|
+
|
242
|
+
params.each do |key, value|
|
243
|
+
nullable = nullable_fields[key.to_sym]
|
244
|
+
|
245
|
+
if nullable.present? && value.in?(nullable)
|
246
|
+
params[key] = nil
|
247
|
+
end
|
248
|
+
end
|
249
|
+
|
250
|
+
params
|
251
|
+
end
|
252
|
+
|
253
|
+
def build_meta
|
254
|
+
{
|
255
|
+
per_page_steps: Avo.configuration.per_page_steps,
|
256
|
+
available_view_types: avo_resource.available_view_types,
|
257
|
+
default_view_type: avo_resource.default_view_type || Avo.configuration.default_view_type,
|
258
|
+
authorization: {
|
259
|
+
create: AuthorizationService::authorize(current_user, avo_resource.model, Avo.configuration.authorization_methods.stringify_keys['create']),
|
260
|
+
update: AuthorizationService::authorize(current_user, avo_resource.model, Avo.configuration.authorization_methods.stringify_keys['update']),
|
261
|
+
show: AuthorizationService::authorize(current_user, avo_resource.model, Avo.configuration.authorization_methods.stringify_keys['show']),
|
262
|
+
destroy: AuthorizationService::authorize(current_user, avo_resource.model, Avo.configuration.authorization_methods.stringify_keys['destroy']),
|
263
|
+
},
|
264
|
+
}
|
265
|
+
end
|
342
266
|
end
|
343
267
|
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require_dependency 'avo/application_controller'
|
2
|
+
|
3
|
+
module Avo
|
4
|
+
class SearchController < ApplicationController
|
5
|
+
before_action :authorize_user
|
6
|
+
|
7
|
+
def index
|
8
|
+
resources = []
|
9
|
+
|
10
|
+
resources_to_search_through = App.get_resources
|
11
|
+
.select { |resource| resource.search.present? }
|
12
|
+
.select { |resource| AuthorizationService.authorize_action current_user, resource.model, 'index' }
|
13
|
+
.each do |resource_model|
|
14
|
+
found_resources = add_link_to_search_results(search_resource(resource_model), resource_model)
|
15
|
+
resources.push({
|
16
|
+
label: resource_model.name,
|
17
|
+
resources: found_resources
|
18
|
+
})
|
19
|
+
end
|
20
|
+
|
21
|
+
render json: {
|
22
|
+
resources: resources
|
23
|
+
}
|
24
|
+
end
|
25
|
+
|
26
|
+
def resource
|
27
|
+
render json: {
|
28
|
+
resources: add_link_to_search_results(search_resource(avo_resource), avo_resource)
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def add_link_to_search_results(resources, avo_resource)
|
34
|
+
resources.map do |model|
|
35
|
+
{
|
36
|
+
id: model.id,
|
37
|
+
search_label: model.send(avo_resource.title),
|
38
|
+
link: "/resources/#{model.class.to_s.singularize.underscore}/#{model.id}",
|
39
|
+
}
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def search_resource(avo_resource)
|
44
|
+
avo_resource.query_search(query: params[:q], via_resource_name: params[:via_resource_name], via_resource_id: params[:via_resource_id], user: current_user)
|
45
|
+
end
|
46
|
+
|
47
|
+
def authorize_user
|
48
|
+
return if params[:action] == 'index'
|
49
|
+
|
50
|
+
action = params[:action] == 'resource' ? :index : params[:action]
|
51
|
+
|
52
|
+
return render_unauthorized unless AuthorizationService::authorize_action current_user, avo_resource.model, action
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -10,12 +10,16 @@ module Avo
|
|
10
10
|
render partial: 'vendor/avo/partials/logo' rescue render partial: 'partials/logo'
|
11
11
|
end
|
12
12
|
|
13
|
+
def render_header
|
14
|
+
render partial: 'vendor/avo/partials/header' rescue render partial: 'partials/header'
|
15
|
+
end
|
16
|
+
|
13
17
|
def render_footer
|
14
18
|
render partial: 'vendor/avo/partials/footer' rescue render partial: 'partials/footer'
|
15
19
|
end
|
16
20
|
|
17
|
-
def
|
18
|
-
render partial: 'vendor/avo/partials/
|
21
|
+
def render_scripts
|
22
|
+
render partial: 'vendor/avo/partials/scripts' rescue ''
|
19
23
|
end
|
20
24
|
end
|
21
25
|
end
|
@@ -13,27 +13,44 @@
|
|
13
13
|
<body class="bg-gray-200">
|
14
14
|
|
15
15
|
<div id="app" class="flex min-h-screen flex-row h-full">
|
16
|
-
<
|
17
|
-
|
18
|
-
|
16
|
+
<app-layout class="flex flex-1 w-full"
|
17
|
+
:router-key="routerKey"
|
18
|
+
:layout="layout"
|
19
|
+
inline-template
|
20
|
+
>
|
21
|
+
<div>
|
22
|
+
<application-sidebar :resources='<%= Avo::App.get_resources_navigation(current_user).as_json.html_safe %>' v-if="layout !== 'blank'">
|
23
|
+
<template #logo>
|
24
|
+
<%= render_logo %>
|
25
|
+
</template>
|
26
|
+
<template #licensing>
|
27
|
+
<license-warnings></license-warnings>
|
28
|
+
</template>
|
29
|
+
</application-sidebar>
|
19
30
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
31
|
+
<div class="flex-1 h-full overflow-auto">
|
32
|
+
<div class="relative bg-white p-2 shadow-md h-16 w-full flex items-center z-40" v-if="layout !== 'blank'">
|
33
|
+
<div class="ml-6">
|
34
|
+
<%= render_header %>
|
35
|
+
</div>
|
36
|
+
<div class="flex-1 flex justify-center">
|
37
|
+
<div class="w-64">
|
38
|
+
<resources-search :global="true">
|
39
|
+
</div>
|
40
|
+
</div>
|
28
41
|
</div>
|
29
|
-
</div>
|
30
|
-
</div>
|
31
42
|
|
32
|
-
|
33
|
-
|
34
|
-
|
43
|
+
<div v-if="layout === 'blank'" class="h-full w-full flex justify-center items-center">
|
44
|
+
<%= yield %>
|
45
|
+
</div>
|
46
|
+
<div class="content p-8" v-else>
|
47
|
+
<%= yield %>
|
48
|
+
<%= render_footer %>
|
49
|
+
</div>
|
50
|
+
</div>
|
35
51
|
</div>
|
36
|
-
</
|
52
|
+
</app-layout>
|
37
53
|
</div>
|
54
|
+
<%= render_scripts %>
|
38
55
|
</body>
|
39
56
|
</html>
|
@@ -1,3 +1,3 @@
|
|
1
1
|
<div class="text-center text-sm text-gray-700">
|
2
|
-
<a href="https://avohq.io/" target="_blank">Avo</a>
|
2
|
+
<a href="https://avohq.io/" target="_blank">Avo</a> · © <%= Date.today.year %> AvoHQ · v<%= Avo::VERSION %>
|
3
3
|
</div>
|