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.

Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +49 -48
  3. data/Gemfile.lock +19 -2
  4. data/README.md +33 -54
  5. data/app/controllers/avo/application_controller.rb +34 -1
  6. data/app/controllers/avo/filters_controller.rb +19 -0
  7. data/app/controllers/avo/relations_controller.rb +34 -0
  8. data/app/controllers/avo/resource_overview_controller.rb +14 -7
  9. data/app/controllers/avo/resources_controller.rb +66 -142
  10. data/app/controllers/avo/search_controller.rb +55 -0
  11. data/app/helpers/avo/application_helper.rb +6 -2
  12. data/app/views/layouts/avo/_javascript.html.erb +1 -0
  13. data/app/views/layouts/avo/application.html.erb +34 -17
  14. data/app/views/partials/_footer.html.erb +1 -1
  15. data/app/views/partials/_header.html.erb +1 -1
  16. data/avo.gemspec +18 -4
  17. data/config/credentials.yml.enc +1 -0
  18. data/config/routes.rb +11 -7
  19. data/lib/avo.rb +2 -0
  20. data/lib/avo/app/app.rb +18 -29
  21. data/lib/avo/app/authorization_service.rb +40 -0
  22. data/lib/avo/app/fields/has_and_belongs_to_many.rb +1 -0
  23. data/lib/avo/app/fields/has_many.rb +1 -0
  24. data/lib/avo/app/fields/id_field.rb +4 -4
  25. data/lib/avo/app/licensing/community_license.rb +4 -0
  26. data/lib/avo/app/licensing/hq.rb +85 -0
  27. data/lib/avo/app/licensing/license.rb +48 -0
  28. data/lib/avo/app/licensing/license_manager.rb +25 -0
  29. data/lib/avo/app/licensing/null_license.rb +12 -0
  30. data/lib/avo/app/licensing/pro_license.rb +9 -0
  31. data/lib/avo/app/resource.rb +31 -12
  32. data/lib/avo/configuration.rb +14 -0
  33. data/lib/avo/engine.rb +1 -1
  34. data/lib/avo/version.rb +1 -1
  35. data/lib/generators/avo/templates/initializer.rb +2 -0
  36. data/lib/generators/avo/templates/views/_footer.html.erb +1 -1
  37. data/lib/generators/avo/templates/views/_header.html.erb +1 -1
  38. data/lib/generators/avo/templates/views/_scripts.html.erb +0 -0
  39. data/public/avo-packs/css/application-73e568bc.css +3 -0
  40. data/public/avo-packs/css/application-73e568bc.css.br +0 -0
  41. data/public/avo-packs/css/application-73e568bc.css.gz +0 -0
  42. data/public/avo-packs/js/application-044386b1f6fe7a8dcb9f.js +3 -0
  43. data/public/avo-packs/js/{application-8071f9a0f167bb82b39d.js.LICENSE.txt → application-044386b1f6fe7a8dcb9f.js.LICENSE.txt} +0 -0
  44. data/public/avo-packs/js/application-044386b1f6fe7a8dcb9f.js.br +0 -0
  45. data/public/avo-packs/js/application-044386b1f6fe7a8dcb9f.js.gz +0 -0
  46. data/public/avo-packs/js/application-044386b1f6fe7a8dcb9f.js.map +1 -0
  47. data/public/avo-packs/js/application-044386b1f6fe7a8dcb9f.js.map.br +0 -0
  48. data/public/avo-packs/js/application-044386b1f6fe7a8dcb9f.js.map.gz +0 -0
  49. data/public/avo-packs/manifest.json +8 -6
  50. data/public/avo-packs/manifest.json.br +0 -0
  51. data/public/avo-packs/manifest.json.gz +0 -0
  52. data/public/avo-packs/media/svgs/arrow-circle-right-1ad1e15ec9a7aa54b67d126566a5aa2d.svg +1 -0
  53. data/public/avo-packs/media/svgs/arrow-circle-right-1ad1e15ec9a7aa54b67d126566a5aa2d.svg.br +0 -0
  54. data/public/avo-packs/media/svgs/arrow-circle-right-1ad1e15ec9a7aa54b67d126566a5aa2d.svg.gz +0 -0
  55. data/public/avo-packs/media/svgs/exclamation-8d1c0baa390a8df9bb52176011eb5892.svg +1 -0
  56. data/public/avo-packs/media/svgs/exclamation-8d1c0baa390a8df9bb52176011eb5892.svg.br +0 -0
  57. data/public/avo-packs/media/svgs/exclamation-8d1c0baa390a8df9bb52176011eb5892.svg.gz +0 -0
  58. metadata +70 -25
  59. data/public/avo-packs/css/application-64b0f124.css +0 -3
  60. data/public/avo-packs/css/application-64b0f124.css.br +0 -0
  61. data/public/avo-packs/css/application-64b0f124.css.gz +0 -0
  62. data/public/avo-packs/js/application-8071f9a0f167bb82b39d.js +0 -3
  63. data/public/avo-packs/js/application-8071f9a0f167bb82b39d.js.br +0 -0
  64. data/public/avo-packs/js/application-8071f9a0f167bb82b39d.js.gz +0 -0
  65. data/public/avo-packs/js/application-8071f9a0f167bb82b39d.js.map +0 -1
  66. data/public/avo-packs/js/application-8071f9a0f167bb82b39d.js.map.br +0 -0
  67. 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
- filters = get_filters
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 reated resource (via_resource)
14
- related_resource = App.get_resource_by_name(params[:via_resource_name])
15
- related_model = related_resource.model
16
- # fetch the entries
17
- query = related_model.find(params[:via_resource_id]).public_send(params[:via_relationship])
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
- if filters.present?
37
- filters.each do |filter_class, filter_value|
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 lood the relations
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: 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 fields
152
- resource = resource_model.new
153
-
125
+ def new
154
126
  render json: {
155
- resource: Avo::Resources::Resource.hydrate_resource(resource, avo_resource, :create),
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 get_filters
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 render_header
18
- render partial: 'vendor/avo/partials/header' rescue render partial: 'partials/header'
21
+ def render_scripts
22
+ render partial: 'vendor/avo/partials/scripts' rescue ''
19
23
  end
20
24
  end
21
25
  end
@@ -2,4 +2,5 @@
2
2
  var rootPath = '<%= Avo.configuration.root_path %>';
3
3
  var timezone = '<%= Avo.configuration.timezone %>';
4
4
  var defaultViewType = '<%= Avo.configuration.default_view_type %>';
5
+ var license = <%= Avo::App.license.properties.to_json.html_safe %>;
5
6
  </script>
@@ -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
- <application-sidebar :resources='<%= Avo::App.get_resources_navigation.as_json.html_safe %>'>
17
- <%= render_logo %>
18
- </application-sidebar>
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
- <div class="flex-1 h-full overflow-auto">
21
- <div class="relative bg-white p-2 shadow-md h-16 w-full flex items-center z-40">
22
- <div class="ml-6">
23
- <%= render_header %>
24
- </div>
25
- <div class="flex-1 flex justify-center">
26
- <div class="w-64">
27
- <resources-search :global="true">
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
- <div class="content p-8">
33
- <%= yield %>
34
- <%= render_footer %>
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
- </div>
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> // &copy; <%= Date.today.year %> AvoHQ // v<%= Avo::VERSION %>
2
+ <a href="https://avohq.io/" target="_blank">Avo</a> · &copy; <%= Date.today.year %> AvoHQ · v<%= Avo::VERSION %>
3
3
  </div>