habarico 7.2.0.pre.beta.5
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 +82 -0
- data/Rakefile +15 -0
- data/VERSION +1 -0
- data/app/controllers/habarico/action_schema_controller.rb +41 -0
- data/app/controllers/habarico/application_controller.rb +15 -0
- data/app/controllers/habarico/contrib/preferences/user_preferences_controller.rb +105 -0
- data/app/controllers/habarico/global_schema_controller.rb +11 -0
- data/app/controllers/habarico/local_schema_controller.rb +35 -0
- data/app/helpers/habarico/application_helper.rb +4 -0
- data/app/models/habarico/application_record.rb +5 -0
- data/app/views/layouts/habarico/application.html.erb +17 -0
- data/config/locales/cs.yml +39 -0
- data/config/locales/de.yml +39 -0
- data/config/locales/en.yml +39 -0
- data/config/locales/es.yml +39 -0
- data/config/locales/fr.yml +39 -0
- data/config/locales/it.yml +39 -0
- data/config/locales/ja.yml +39 -0
- data/config/locales/nl.yml +39 -0
- data/config/locales/pl.yml +39 -0
- data/config/locales/pt.yml +39 -0
- data/config/locales/ro.yml +39 -0
- data/config/locales/sk.yml +39 -0
- data/config/locales/uk.yml +39 -0
- data/config/routes.rb +1 -0
- data/lib/generators/habarico/preferences/install_generator.rb +27 -0
- data/lib/generators/habarico/preferences/templates/create_habarico_user_preferences.rb.tt +15 -0
- data/lib/habarico/active_model/references.rb +68 -0
- data/lib/habarico/authenticatable.rb +31 -0
- data/lib/habarico/config.rb +71 -0
- data/lib/habarico/context.rb +126 -0
- data/lib/habarico/contrib/preferences/base_preferences.rb +40 -0
- data/lib/habarico/contrib/preferences/config.rb +31 -0
- data/lib/habarico/contrib/preferences/errors.rb +79 -0
- data/lib/habarico/contrib/preferences/locales/cs.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/de.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/en.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/es.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/fr.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/it.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/ja.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/nl.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/pl.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/pt.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/ro.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/sk.yml +8 -0
- data/lib/habarico/contrib/preferences/locales/uk.yml +8 -0
- data/lib/habarico/contrib/preferences/registry.rb +293 -0
- data/lib/habarico/contrib/preferences/routes.rb +17 -0
- data/lib/habarico/contrib/preferences/schemas.rb +129 -0
- data/lib/habarico/contrib/preferences/serializer.rb +19 -0
- data/lib/habarico/contrib/preferences/user_preference.rb +16 -0
- data/lib/habarico/contrib/preferences.rb +32 -0
- data/lib/habarico/controller.rb +584 -0
- data/lib/habarico/definition/action_reference.rb +18 -0
- data/lib/habarico/definition/actions/action.rb +14 -0
- data/lib/habarico/definition/actions/create_action.rb +18 -0
- data/lib/habarico/definition/actions/delete_action.rb +20 -0
- data/lib/habarico/definition/actions/edit_action.rb +18 -0
- data/lib/habarico/definition/actions/link_action.rb +11 -0
- data/lib/habarico/definition/actions/resource_action.rb +12 -0
- data/lib/habarico/definition/actions/save_action.rb +14 -0
- data/lib/habarico/definition/actions/show_action.rb +18 -0
- data/lib/habarico/definition/actions/upload_images_action.rb +11 -0
- data/lib/habarico/definition/actions/view_action.rb +11 -0
- data/lib/habarico/definition/boxes/array_box.rb +10 -0
- data/lib/habarico/definition/boxes/box.rb +32 -0
- data/lib/habarico/definition/boxes/gallery_box.rb +10 -0
- data/lib/habarico/definition/boxes/grid_box.rb +10 -0
- data/lib/habarico/definition/boxes/grid_item_box.rb +10 -0
- data/lib/habarico/definition/boxes/list_box.rb +10 -0
- data/lib/habarico/definition/boxes/object_box.rb +10 -0
- data/lib/habarico/definition/boxes/tab_box.rb +10 -0
- data/lib/habarico/definition/boxes/tab_item_box.rb +10 -0
- data/lib/habarico/definition/boxes/wizard_box.rb +10 -0
- data/lib/habarico/definition/concerns/definition_model.rb +38 -0
- data/lib/habarico/definition/definition.rb +206 -0
- data/lib/habarico/definition/field_reference.rb +9 -0
- data/lib/habarico/definition/filter_reference.rb +7 -0
- data/lib/habarico/definition/menu/link_menu_item.rb +12 -0
- data/lib/habarico/definition/menu/menu_item.rb +9 -0
- data/lib/habarico/definition/menu/text_menu_item.rb +12 -0
- data/lib/habarico/definition/menu/view_menu_item.rb +13 -0
- data/lib/habarico/definition/response.rb +13 -0
- data/lib/habarico/definition/views/action_view.rb +11 -0
- data/lib/habarico/definition/views/create_view.rb +15 -0
- data/lib/habarico/definition/views/edit_view.rb +15 -0
- data/lib/habarico/definition/views/list_view.rb +17 -0
- data/lib/habarico/definition/views/show_view.rb +15 -0
- data/lib/habarico/definition/views/view.rb +10 -0
- data/lib/habarico/engine.rb +6 -0
- data/lib/habarico/file_validations.rb +119 -0
- data/lib/habarico/generators/abstract_generator.rb +21 -0
- data/lib/habarico/generators/action/action_schema_generator.rb +111 -0
- data/lib/habarico/generators/action/confirm_dialog_properties_generator.rb +30 -0
- data/lib/habarico/generators/action/global_resource_action_schema_generator.rb +46 -0
- data/lib/habarico/generators/action/link_action_parameters_generator.rb +31 -0
- data/lib/habarico/generators/action/resource_action_endpoint_parameters_generator.rb +70 -0
- data/lib/habarico/generators/action/resource_action_parameters_generator.rb +40 -0
- data/lib/habarico/generators/action/resource_action_schema_generator.rb +51 -0
- data/lib/habarico/generators/action/save_action_parameters_generator.rb +26 -0
- data/lib/habarico/generators/action/upload_images_action_parameters_generator.rb +41 -0
- data/lib/habarico/generators/action/view_action_endpoint_parameters_generator.rb +37 -0
- data/lib/habarico/generators/action/view_action_parameters_generator.rb +56 -0
- data/lib/habarico/generators/endpoint/dynamic_render_options_generator.rb +26 -0
- data/lib/habarico/generators/endpoint/endpoints_schema_generator.rb +61 -0
- data/lib/habarico/generators/endpoint/enum_type_generator.rb +45 -0
- data/lib/habarico/generators/endpoint/field_options_generator.rb +179 -0
- data/lib/habarico/generators/endpoint/field_schema_generator.rb +381 -0
- data/lib/habarico/generators/endpoint/fields_schema_generator.rb +35 -0
- data/lib/habarico/generators/endpoint/filter_schema_generator.rb +201 -0
- data/lib/habarico/generators/endpoint/filters_schema_generator.rb +25 -0
- data/lib/habarico/generators/endpoint/method_schema_generator.rb +34 -0
- data/lib/habarico/generators/endpoint/number_field_options_generator.rb +45 -0
- data/lib/habarico/generators/endpoint/phone_field_options_generator.rb +29 -0
- data/lib/habarico/generators/endpoint/relation_field_options_generator.rb +90 -0
- data/lib/habarico/generators/endpoint/search_filter_schema_generator.rb +44 -0
- data/lib/habarico/generators/menu/menu_group_schema_generator.rb +63 -0
- data/lib/habarico/generators/menu/menu_link_parameters_generator.rb +31 -0
- data/lib/habarico/generators/menu/menu_schema_generator.rb +105 -0
- data/lib/habarico/generators/menu/menu_view_endpoint_parameters_generator.rb +44 -0
- data/lib/habarico/generators/menu/menu_view_parameters_generator.rb +50 -0
- data/lib/habarico/generators/resource/action_resource_schema_generator.rb +84 -0
- data/lib/habarico/generators/resource/global_resource_schema_generator.rb +70 -0
- data/lib/habarico/generators/resource/local_resource_schema_generator.rb +111 -0
- data/lib/habarico/generators/resource/object_resource_schema_generator.rb +49 -0
- data/lib/habarico/generators/resource/resource_schema_generator.rb +31 -0
- data/lib/habarico/generators/view/action_descriptor_generator.rb +88 -0
- data/lib/habarico/generators/view/array_box_parameters_generator.rb +34 -0
- data/lib/habarico/generators/view/box_options_generator.rb +23 -0
- data/lib/habarico/generators/view/box_schema_generator.rb +194 -0
- data/lib/habarico/generators/view/detail_view_parameters_generator.rb +57 -0
- data/lib/habarico/generators/view/field_descriptor_generator.rb +107 -0
- data/lib/habarico/generators/view/filter_descriptor_generator.rb +75 -0
- data/lib/habarico/generators/view/gallery_box_parameters_generator.rb +71 -0
- data/lib/habarico/generators/view/global_view_schema_generator.rb +59 -0
- data/lib/habarico/generators/view/grid_item_box_parameters_generator.rb +44 -0
- data/lib/habarico/generators/view/list_box_parameters_generator.rb +124 -0
- data/lib/habarico/generators/view/list_view_parameters_generator.rb +120 -0
- data/lib/habarico/generators/view/object_box_parameters_generator.rb +26 -0
- data/lib/habarico/generators/view/sort_descriptor_generator.rb +30 -0
- data/lib/habarico/generators/view/tab_box_parameters_generator.rb +26 -0
- data/lib/habarico/generators/view/view_endpoint_parameters_generator.rb +71 -0
- data/lib/habarico/generators/view/view_schema_generator.rb +87 -0
- data/lib/habarico/generators/view/wizard_box_parameters_generator.rb +32 -0
- data/lib/habarico/helpers/action_helper.rb +9 -0
- data/lib/habarico/helpers/attributes_helper.rb +67 -0
- data/lib/habarico/helpers/box_helper.rb +19 -0
- data/lib/habarico/helpers/callable_helper.rb +15 -0
- data/lib/habarico/helpers/filter_params_helper.rb +49 -0
- data/lib/habarico/helpers/order_by_helper.rb +56 -0
- data/lib/habarico/helpers/reflection_helper.rb +61 -0
- data/lib/habarico/helpers/sorting_helper.rb +136 -0
- data/lib/habarico/schema/action/action_schema.rb +46 -0
- data/lib/habarico/schema/action/confirm_dialog_properties.rb +13 -0
- data/lib/habarico/schema/action/global_resource_action_schema.rb +29 -0
- data/lib/habarico/schema/action/link_action_parameters.rb +15 -0
- data/lib/habarico/schema/action/resource_action_endpoint_parameters.rb +24 -0
- data/lib/habarico/schema/action/resource_action_parameters.rb +17 -0
- data/lib/habarico/schema/action/resource_action_schema.rb +14 -0
- data/lib/habarico/schema/action/save_action_parameters.rb +10 -0
- data/lib/habarico/schema/action/upload_images_action_parameters.rb +23 -0
- data/lib/habarico/schema/action/view_action_endpoint_parameters.rb +16 -0
- data/lib/habarico/schema/action/view_action_parameters.rb +27 -0
- data/lib/habarico/schema/endpoint/ai_field_options.rb +10 -0
- data/lib/habarico/schema/endpoint/box_options.rb +10 -0
- data/lib/habarico/schema/endpoint/choices_field_options.rb +10 -0
- data/lib/habarico/schema/endpoint/dynamic_render_options.rb +14 -0
- data/lib/habarico/schema/endpoint/endpoints_schema.rb +22 -0
- data/lib/habarico/schema/endpoint/enum_type.rb +21 -0
- data/lib/habarico/schema/endpoint/field_options.rb +56 -0
- data/lib/habarico/schema/endpoint/field_schema.rb +65 -0
- data/lib/habarico/schema/endpoint/filter_schema.rb +44 -0
- data/lib/habarico/schema/endpoint/image_field_options.rb +13 -0
- data/lib/habarico/schema/endpoint/key_value.rb +13 -0
- data/lib/habarico/schema/endpoint/method_schema.rb +13 -0
- data/lib/habarico/schema/endpoint/number_field_options.rb +24 -0
- data/lib/habarico/schema/endpoint/phone_field_options.rb +13 -0
- data/lib/habarico/schema/endpoint/relation_field_options.rb +39 -0
- data/lib/habarico/schema/extensible_enum.rb +20 -0
- data/lib/habarico/schema/menu/action_view_choice.rb +13 -0
- data/lib/habarico/schema/menu/menu_group_schema.rb +26 -0
- data/lib/habarico/schema/menu/menu_schema.rb +36 -0
- data/lib/habarico/schema/resource/global_resource_schema.rb +14 -0
- data/lib/habarico/schema/resource/local_resource_schema.rb +14 -0
- data/lib/habarico/schema/resource/object_resource_schema.rb +8 -0
- data/lib/habarico/schema/resource/resource_schema.rb +16 -0
- data/lib/habarico/schema/types.rb +102 -0
- data/lib/habarico/schema/view/action_descriptor.rb +30 -0
- data/lib/habarico/schema/view/array_box_parameters.rb +13 -0
- data/lib/habarico/schema/view/box_schema.rb +67 -0
- data/lib/habarico/schema/view/detail_view_parameters.rb +16 -0
- data/lib/habarico/schema/view/field_descriptor.rb +50 -0
- data/lib/habarico/schema/view/field_wrapper_properties.rb +13 -0
- data/lib/habarico/schema/view/filter_descriptor.rb +24 -0
- data/lib/habarico/schema/view/gallery_box_parameters.rb +31 -0
- data/lib/habarico/schema/view/global_view_schema.rb +25 -0
- data/lib/habarico/schema/view/grid_item_box_parameters.rb +13 -0
- data/lib/habarico/schema/view/list_box_parameters.rb +44 -0
- data/lib/habarico/schema/view/list_view_parameters.rb +31 -0
- data/lib/habarico/schema/view/object_box_parameters.rb +10 -0
- data/lib/habarico/schema/view/sort_descriptor.rb +15 -0
- data/lib/habarico/schema/view/sortable_parameters.rb +13 -0
- data/lib/habarico/schema/view/tab_box_parameters.rb +10 -0
- data/lib/habarico/schema/view/view_action_descriptor_parameters.rb +21 -0
- data/lib/habarico/schema/view/view_endpoint_parameters.rb +22 -0
- data/lib/habarico/schema/view/view_schema.rb +33 -0
- data/lib/habarico/schema/view/wizard_box_parameters.rb +13 -0
- data/lib/habarico/search/field_resolver.rb +138 -0
- data/lib/habarico/search/search_matcher.rb +152 -0
- data/lib/habarico/serializers/meta_serializer.rb +42 -0
- data/lib/habarico/serializers/record_serializer.rb +33 -0
- data/lib/habarico/serializers/serializer.rb +61 -0
- data/lib/habarico/services/resource_service.rb +69 -0
- data/lib/habarico/services/routing_service.rb +160 -0
- data/lib/habarico/services/schema_service.rb +72 -0
- data/lib/habarico/version.rb +5 -0
- data/lib/habarico.rb +262 -0
- metadata +324 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
require "active_record"
|
|
2
|
+
|
|
3
|
+
module Habarico
|
|
4
|
+
module Contrib
|
|
5
|
+
module Preferences
|
|
6
|
+
# Per-user preference row. Keyed on the scalar `user_id` (the auth token subject
|
|
7
|
+
# `current_user.id`), NOT a DB foreign key: in the token-based, multi-service
|
|
8
|
+
# setup the user is often not a local ActiveRecord row, so there is no local table
|
|
9
|
+
# to reference. Hosts that do have a local users table can add a FK in their own
|
|
10
|
+
# (generator-copied) migration.
|
|
11
|
+
class UserPreference < ::ActiveRecord::Base
|
|
12
|
+
self.table_name = "habarico_user_preferences"
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# Optional Habarico module providing a per-user, JSON-Schema-validated preference store
|
|
2
|
+
# (theme, locale, dashboard layout, saved queries, column selection, ...). It is NOT loaded
|
|
3
|
+
# by `require "habarico"`; the host app opts in with `require "habarico/contrib/preferences"`
|
|
4
|
+
# and adds the `json_schemer` gem to its Gemfile.
|
|
5
|
+
begin
|
|
6
|
+
require "json_schemer"
|
|
7
|
+
rescue LoadError => e
|
|
8
|
+
raise LoadError, "habarico/contrib/preferences requires the `json_schemer` gem. " \
|
|
9
|
+
"Add it to your Gemfile.\n#{e.message}"
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
require "habarico"
|
|
13
|
+
|
|
14
|
+
require "habarico/contrib/preferences/errors"
|
|
15
|
+
require "habarico/contrib/preferences/config"
|
|
16
|
+
require "habarico/contrib/preferences/registry"
|
|
17
|
+
require "habarico/contrib/preferences/user_preference"
|
|
18
|
+
require "habarico/contrib/preferences/schemas"
|
|
19
|
+
require "habarico/contrib/preferences/base_preferences"
|
|
20
|
+
require "habarico/contrib/preferences/serializer"
|
|
21
|
+
require "habarico/contrib/preferences/routes"
|
|
22
|
+
|
|
23
|
+
# NOTE: UserPreferencesController lives in app/controllers so the Rails engine autoloads it
|
|
24
|
+
# (its superclass Habarico::ApplicationController is only available under a booted app). It is
|
|
25
|
+
# not required here; the routes above reference it by path and it resolves on the first request.
|
|
26
|
+
|
|
27
|
+
# Register the contrib's translations (opt-in module, not picked up by the engine's
|
|
28
|
+
# config/locales). Host apps can override any key in their own files.
|
|
29
|
+
I18n.load_path += Dir[File.expand_path("preferences/locales/*.yml", __dir__)]
|
|
30
|
+
|
|
31
|
+
# Register built-in preference keys on load (mirrors the Python AppConfig.ready hook).
|
|
32
|
+
Habarico::Contrib::Preferences.register_base_preferences
|
|
@@ -0,0 +1,584 @@
|
|
|
1
|
+
module Habarico
|
|
2
|
+
module Controller
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
|
|
5
|
+
included do
|
|
6
|
+
include Eluvia::ErrorHandler
|
|
7
|
+
include Eluvia::PaginationHandler
|
|
8
|
+
include Habarico::Authenticatable
|
|
9
|
+
|
|
10
|
+
# Order matters: `set_list_pagination` hands the `order_by` parameter to `set_pagination`,
|
|
11
|
+
# which refuses anything that is not `field:direction`, so the validator below only ever
|
|
12
|
+
# sees well-formed entries and is left with the field names (HAB-915).
|
|
13
|
+
before_action :set_list_pagination, only: [:index]
|
|
14
|
+
before_action :validate_order_by_param, only: [:index]
|
|
15
|
+
before_action :set_record, only: %i[show update destroy]
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
# *************************************************************************
|
|
19
|
+
# Actions
|
|
20
|
+
# *************************************************************************
|
|
21
|
+
|
|
22
|
+
def index
|
|
23
|
+
read_allowed = context.can?(:read)
|
|
24
|
+
# Without read permission listing is forbidden outright (same as the Python backend), unless
|
|
25
|
+
# the resource explicitly opts into ID-based reads via `allow_read_by_id`.
|
|
26
|
+
raise Eluvia::Errors::Forbidden.new('List not allowed.') unless read_allowed || definition.allow_read_by_id
|
|
27
|
+
|
|
28
|
+
filter_params = sanitize_filter_params.to_h.symbolize_keys
|
|
29
|
+
if read_allowed
|
|
30
|
+
# Habarico owns the typed fulltext search over `search_fields` (see SearchMatcher), so the
|
|
31
|
+
# search term is pulled out and applied separately - it must not reach the base library
|
|
32
|
+
# `filter_2!`, whose `apply_search` uses a different field list and always unaccents.
|
|
33
|
+
filter_params = extract_search!(filter_params)
|
|
34
|
+
else
|
|
35
|
+
# `allow_read_by_id` without read permission: only allow filtering by IDs, and set empty
|
|
36
|
+
# result if no IDs provided. This supports references across multiple services with limited
|
|
37
|
+
# permissions - a caller holding an ID(s) of the object may see its data, but not list all
|
|
38
|
+
# objects (this is what `ReferenceField` on the frontend uses).
|
|
39
|
+
filter_params = definition.model.parse_filter_term(filter_params[:filter].to_s, raise_on_invalid: false).symbolize_keys if filter_params.key?(:filter)
|
|
40
|
+
filter_params = { id__in: [] }.merge(filter_params.slice(:id__in))
|
|
41
|
+
end
|
|
42
|
+
@records = definition.model.all
|
|
43
|
+
|
|
44
|
+
# Policy scope
|
|
45
|
+
if definition.policy_scope.present?
|
|
46
|
+
@records = definition.policy_scope.call(@records, context)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Scopes
|
|
50
|
+
if definition.scopes.present? && !definition.scopes.empty?
|
|
51
|
+
definition.scopes.each do |scope|
|
|
52
|
+
@records = @records.public_send(scope) if @records.respond_to?(scope)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Filtering (filter_2!, typed fulltext search, default filter)
|
|
57
|
+
@records = apply_filtering(@records, filter_params)
|
|
58
|
+
|
|
59
|
+
# Includes
|
|
60
|
+
if definition.includes.present? && !definition.includes.empty?
|
|
61
|
+
@records = @records.includes(*definition.includes)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Ordering
|
|
65
|
+
@records = if @records.respond_to?(:order_2)
|
|
66
|
+
@records.order_2(@order_by)
|
|
67
|
+
else
|
|
68
|
+
@records.order(@order_by)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Pagination
|
|
72
|
+
@records = @records.page(@page).per(@limit).padding(@padding)
|
|
73
|
+
|
|
74
|
+
# Custom prefetch
|
|
75
|
+
if definition.prefetch.respond_to?(:call)
|
|
76
|
+
definition.prefetch.call(@records)
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
render_list
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def show
|
|
83
|
+
# `show` requires can_read, same as index. `allow_read_by_id` opts into ID-based access
|
|
84
|
+
# without read permission (e.g. cross-service references) - possession of the ID is treated
|
|
85
|
+
# as the credential, while `policy_scope` in `set_record` still bounds what is reachable.
|
|
86
|
+
raise Eluvia::Errors::Forbidden.new('Show not allowed.') unless context.can?(:read) || definition.allow_read_by_id
|
|
87
|
+
|
|
88
|
+
render_detail
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def create
|
|
92
|
+
raise Eluvia::Errors::Forbidden.new('Create not allowed.') unless context.can?(:create)
|
|
93
|
+
raise Eluvia::Errors::BadRequest.new('Create not possible.') unless is_create_possible?
|
|
94
|
+
@record = definition.model.new(sanitize_record_params.merge(complement_record_params))
|
|
95
|
+
if @record.save
|
|
96
|
+
render_detail
|
|
97
|
+
else
|
|
98
|
+
throw_validation_error
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def update
|
|
103
|
+
raise Eluvia::Errors::Forbidden.new('Update not allowed.') unless context.can?(:update)
|
|
104
|
+
raise Eluvia::Errors::BadRequest.new('Update not possible.') unless is_update_possible?(@record)
|
|
105
|
+
if @record.update(sanitize_record_params.merge(complement_record_params))
|
|
106
|
+
render_detail
|
|
107
|
+
else
|
|
108
|
+
throw_validation_error
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def destroy
|
|
113
|
+
raise Eluvia::Errors::Forbidden.new('Delete not allowed.') unless context.can?(:delete)
|
|
114
|
+
raise Eluvia::Errors::BadRequest.new('Delete not possible.') unless is_delete_possible?(@record)
|
|
115
|
+
@record.destroy
|
|
116
|
+
render_detail
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
protected
|
|
120
|
+
|
|
121
|
+
# *************************************************************************
|
|
122
|
+
# Renderers
|
|
123
|
+
# *************************************************************************
|
|
124
|
+
|
|
125
|
+
# `fields` defaults straight from the query string — kept out of
|
|
126
|
+
# `sanitize_filter_params`/`filter_2!` entirely, the same way `search` is
|
|
127
|
+
# held apart from filter term validation, since it controls serialization
|
|
128
|
+
# rather than which records are returned.
|
|
129
|
+
def serialize_record(view_type, record, fields: params[:fields])
|
|
130
|
+
context = _context(record)
|
|
131
|
+
|
|
132
|
+
# Tags
|
|
133
|
+
tags = definition.tags.respond_to?(:call) ? definition.tags.call(context) : []
|
|
134
|
+
|
|
135
|
+
# Representation
|
|
136
|
+
representation = record.respond_to?(:representation) ? record.representation : record.to_s
|
|
137
|
+
|
|
138
|
+
# Schema
|
|
139
|
+
schema = Habarico::SchemaService.instance.get_object_schema(controller_name, view_type, context).as_json
|
|
140
|
+
|
|
141
|
+
# Trigger action
|
|
142
|
+
trigger_action_schema = get_trigger_action_schema(record)
|
|
143
|
+
trigger_action = trigger_action_schema ? trigger_action_schema.as_json.deep_compact(compactable: [nil, {}].freeze) : nil
|
|
144
|
+
|
|
145
|
+
meta_data = {
|
|
146
|
+
'repr' => representation,
|
|
147
|
+
'schema' => schema,
|
|
148
|
+
'tags' => tags,
|
|
149
|
+
'trigger_action' => trigger_action
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
finalize_meta(record_serializer.serialize(record, meta_data, fields: fields), view_type, record)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def finalize_meta(result, view_type, record)
|
|
156
|
+
return result unless result.key?('meta')
|
|
157
|
+
|
|
158
|
+
meta = result['meta']
|
|
159
|
+
meta['schema'] = meta['schema'].deep_compact(compactable: [nil, {}].freeze) if meta['schema']
|
|
160
|
+
|
|
161
|
+
# Search highlights sidecar: present only on list responses when a search is active and at
|
|
162
|
+
# least one `search_fields` path matched. Full (untrimmed) field value, mirroring the Python
|
|
163
|
+
# `meta.highlights` (MR !198). Bypasses `fields=meta(...)` pruning, same as before HAB-614.
|
|
164
|
+
if view_type == :list
|
|
165
|
+
highlights = list_search_highlights(record)
|
|
166
|
+
meta['highlights'] = highlights if highlights
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
result['meta'] = meta.compact
|
|
170
|
+
result
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
# Applies filter_2!, the typed fulltext search and the resource default filter to `scope`.
|
|
174
|
+
def apply_filtering(scope, filter_params)
|
|
175
|
+
scope = scope.filter_2!(filter_params, current_user: current_user) if scope.respond_to?(:filter_2!)
|
|
176
|
+
|
|
177
|
+
# Typed fulltext search (owned by Habarico, see SearchMatcher) - applied after the other
|
|
178
|
+
# filters so it further narrows the already-filtered set (AND semantics).
|
|
179
|
+
scope = apply_search_scope(scope)
|
|
180
|
+
|
|
181
|
+
# Default filter - applied after filter_2! so it is never dropped when a :filter string param
|
|
182
|
+
# is present (filter_2! discards non-:filter keys in that branch, which would silently bypass
|
|
183
|
+
# default_filter).
|
|
184
|
+
if definition.default_filter.present? && scope.respond_to?(:filter_2)
|
|
185
|
+
scope = scope.filter_2(definition.default_filter, current_user: current_user)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
scope
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Narrows `scope` with the typed fulltext SearchMatcher when a search is active; otherwise
|
|
192
|
+
# returns it unchanged.
|
|
193
|
+
def apply_search_scope(scope)
|
|
194
|
+
return scope if @search_query.blank? || definition.search_fields.blank?
|
|
195
|
+
|
|
196
|
+
Habarico::Search::SearchMatcher.new(
|
|
197
|
+
model: definition.model,
|
|
198
|
+
search_fields: definition.search_fields
|
|
199
|
+
).apply(scope, @search_query)
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Full-value highlights for a list record, or nil when there is no active search query, no
|
|
203
|
+
# `search_fields` path matched, or no highlighter is registered.
|
|
204
|
+
#
|
|
205
|
+
# Match highlighting is a paid feature: the free gem leaves `Config.search_highlighter_class`
|
|
206
|
+
# at nil and the `highlights` key is simply absent, while per-resource search keeps working
|
|
207
|
+
# (HAB-812). `habarico-enterprise` registers its `SearchHighlighter` there.
|
|
208
|
+
def list_search_highlights(record)
|
|
209
|
+
return nil if @search_query.blank? || definition.search_fields.blank?
|
|
210
|
+
|
|
211
|
+
highlighter_class = Habarico::Config.search_highlighter_class!
|
|
212
|
+
return nil if highlighter_class.nil?
|
|
213
|
+
|
|
214
|
+
entries = highlighter_class.new(
|
|
215
|
+
model: definition.model,
|
|
216
|
+
search_fields: definition.search_fields
|
|
217
|
+
).highlight(record, @search_query, full: true)
|
|
218
|
+
|
|
219
|
+
entries.presence
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def render_detail
|
|
223
|
+
render json: serialize_record(:detail, @record)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def render_list
|
|
227
|
+
render json: {
|
|
228
|
+
meta: {
|
|
229
|
+
limit: @limit,
|
|
230
|
+
offset: @offset,
|
|
231
|
+
total_count: @records.total_count,
|
|
232
|
+
},
|
|
233
|
+
objects: @records.map { |record| serialize_record(:list, record) }
|
|
234
|
+
}
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def render_response(response)
|
|
238
|
+
if response.action
|
|
239
|
+
action = definition.normalized_actions.find { |a| a.name.to_s == response.action.to_s }
|
|
240
|
+
if action
|
|
241
|
+
resource_path = Habarico::ResourceService.instance.get_resource_path(controller_name)
|
|
242
|
+
action_schema_generator = definition.action_schema_generator.new(definition, resource_path)
|
|
243
|
+
trigger_action = action_schema_generator.generate(action, context).as_json.deep_compact(compactable: [nil, {}].freeze)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
render json: {
|
|
247
|
+
state: response.state,
|
|
248
|
+
message: response.message,
|
|
249
|
+
trigger_action: trigger_action
|
|
250
|
+
}.deep_compact
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
def get_trigger_action_schema(_record)
|
|
254
|
+
nil # override in subclasses to return an ActionSchema instance
|
|
255
|
+
end
|
|
256
|
+
|
|
257
|
+
# Checks the `order_by` query parameter against what this resource offers for sorting.
|
|
258
|
+
#
|
|
259
|
+
# The notation itself - `field:direction`, exactly one `:`, a lower-case `asc`/`desc` - and the
|
|
260
|
+
# name of the parameter in camel-case mode belong to `set_pagination`, which runs first and
|
|
261
|
+
# answers its own 422 (HAB-915). What is left here is the field whitelist, the part only
|
|
262
|
+
# habarico knows: the `sort_by` values its schema advertises.
|
|
263
|
+
def validate_order_by_param
|
|
264
|
+
# Forced sorting is not read off the request at all, so refusing the parameter here would
|
|
265
|
+
# refuse a request the backend is about to serve.
|
|
266
|
+
return if definition.force_sorting
|
|
267
|
+
|
|
268
|
+
order_by_param = params[Eluvia::PaginationHandler.order_by_param_name]
|
|
269
|
+
return if order_by_param.blank?
|
|
270
|
+
|
|
271
|
+
# Every unknown field at once, the way the Python backend answers - a client correcting one
|
|
272
|
+
# column per request is the alternative.
|
|
273
|
+
errors = {}
|
|
274
|
+
Habarico::OrderByHelper.parse(order_by_param).map(&:first).each do |field_name|
|
|
275
|
+
next if valid_order_by_field?(field_name)
|
|
276
|
+
|
|
277
|
+
errors["order_by__#{field_name}"] = I18n.t('habarico.errors.order_by.unknown_field', default: 'Unknown field name.')
|
|
278
|
+
end
|
|
279
|
+
|
|
280
|
+
raise Eluvia::Errors::UnprocessableEntity.new(errors) if errors.present?
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def valid_order_by_field?(field_name)
|
|
284
|
+
order_by_whitelist.nil? || order_by_whitelist.include?(field_name)
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
# Values this resource accepts in `order_by`, or nil when it exposes no whitelist at all - see
|
|
288
|
+
# `Habarico::SortingHelper`. Resolved once per request, the definition does not change under it.
|
|
289
|
+
#
|
|
290
|
+
# The tiebreaker is added here rather than in the helper so that a controller overriding
|
|
291
|
+
# `list_order_by_tiebreaker` does not have to repeat itself: what the list is ordered by anyway
|
|
292
|
+
# is what a client may ask for.
|
|
293
|
+
def order_by_whitelist
|
|
294
|
+
return @order_by_whitelist if defined?(@order_by_whitelist)
|
|
295
|
+
|
|
296
|
+
allowed_fields = Habarico::SortingHelper.allowed_order_by_fields(definition)
|
|
297
|
+
@order_by_whitelist = allowed_fields && (allowed_fields + list_order_by_tiebreaker.map(&:to_s)).uniq
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def throw_validation_error
|
|
301
|
+
errors = @record.errors.to_hash
|
|
302
|
+
errors_with_variants = {}
|
|
303
|
+
errors.each do |attr_name, error|
|
|
304
|
+
Habarico::AttributesHelper.generate_attr_variants(definition.model, attr_name).each do |attr_name_variant|
|
|
305
|
+
errors_with_variants[attr_name_variant] = error
|
|
306
|
+
end
|
|
307
|
+
end
|
|
308
|
+
raise Eluvia::Errors::UnprocessableEntity.new(errors_with_variants)
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# *************************************************************************
|
|
312
|
+
# Pagination
|
|
313
|
+
# *************************************************************************
|
|
314
|
+
|
|
315
|
+
# Resolves the page size of `index` and hands it to the base library (HAB-946).
|
|
316
|
+
#
|
|
317
|
+
# Each limit is passed as a keyword only when habarico has an opinion about it. What is left
|
|
318
|
+
# out falls through to `set_pagination` itself - its `default_limit: 20` and its
|
|
319
|
+
# `pagination_max_limit`, which an application controller may have overridden and which is
|
|
320
|
+
# also the only place the ceiling can be removed altogether
|
|
321
|
+
# (`Eluvia::Base::Config.max_page_limit = nil`).
|
|
322
|
+
#
|
|
323
|
+
# Note the method names: `Eluvia::PaginationHandler` is included into the controller *after*
|
|
324
|
+
# this concern, so its `pagination_max_limit` shadows anything of that name declared here -
|
|
325
|
+
# which is why habarico resolves its own limits under distinct names and passes them in
|
|
326
|
+
# explicitly instead of overriding the handler's method.
|
|
327
|
+
def set_list_pagination
|
|
328
|
+
options = {
|
|
329
|
+
# No fallback for `set_pagination` to concatenate - habarico appends its own behind the
|
|
330
|
+
# resolved ordering instead, see `append_order_by_tiebreaker`.
|
|
331
|
+
default_order_by: definition.default_sorting,
|
|
332
|
+
fallback_order_by: '',
|
|
333
|
+
force_order_by: definition.force_sorting
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
default_limit = list_pagination_default_limit
|
|
337
|
+
options[:default_limit] = default_limit unless default_limit.nil?
|
|
338
|
+
|
|
339
|
+
max_limit = list_pagination_max_limit
|
|
340
|
+
options[:max_limit] = max_limit unless max_limit.nil?
|
|
341
|
+
|
|
342
|
+
set_pagination(**options)
|
|
343
|
+
append_order_by_tiebreaker
|
|
344
|
+
end
|
|
345
|
+
|
|
346
|
+
# Orders by the primary key behind whatever else the list is ordered by, so that two records
|
|
347
|
+
# comparing equal keep a stable position and a page neither repeats nor skips one (HAB-915).
|
|
348
|
+
#
|
|
349
|
+
# This is what `fallback_order_by` is for, but handing the key to `set_pagination` cannot do
|
|
350
|
+
# the job: that string is concatenated onto the ordering before it is parsed into a hash keyed
|
|
351
|
+
# by field name, so a fallback of `id:asc` would overwrite the direction of a request already
|
|
352
|
+
# sorting by `id` - `?order_by=id:desc` would answer ascending. Appending afterwards leaves a
|
|
353
|
+
# field the ordering already names alone.
|
|
354
|
+
def append_order_by_tiebreaker
|
|
355
|
+
list_order_by_tiebreaker.each { |field_name| @order_by[field_name.to_s] ||= :asc }
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
# Field names appended to the ordering of `index` to break ties. The primary key by default -
|
|
359
|
+
# it is unique and every model has one, `Array()` covering a composite key. A model with no
|
|
360
|
+
# primary key at all gets nothing, and so does a model without ActiveRecord reflection.
|
|
361
|
+
#
|
|
362
|
+
# Until HAB-915 this was `created_at:asc`, hardcoded: a model without that column answered 422
|
|
363
|
+
# (`order_by__created_at` - "Does not exist.") or a database error on *every* list request,
|
|
364
|
+
# and records sharing a timestamp could still swap places between two pages. Override to sort
|
|
365
|
+
# ties differently; to keep `created_at` in the ordering, name it in `default_sorting`.
|
|
366
|
+
def list_order_by_tiebreaker
|
|
367
|
+
Array(definition.model.try(:primary_key))
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
# Page size for a request without a `limit`, or nil to leave it to the base library.
|
|
371
|
+
def list_pagination_default_limit
|
|
372
|
+
definition.default_page_limit || Habarico::Config.default_page_limit
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
# Ceiling for `limit`, or nil to leave it to the base library.
|
|
376
|
+
def list_pagination_max_limit
|
|
377
|
+
definition.max_page_limit || Habarico::Config.max_page_limit
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
# *************************************************************************
|
|
381
|
+
# Model setters
|
|
382
|
+
# *************************************************************************
|
|
383
|
+
|
|
384
|
+
def set_record
|
|
385
|
+
@pk = params[:id]
|
|
386
|
+
scope = definition.model.all
|
|
387
|
+
|
|
388
|
+
# Policy scope - applied unconditionally, same as index, so a record outside the Pundit
|
|
389
|
+
# scope (other tenant, soft-deleted, ...) 404s. Built from a fresh, non-memoized context
|
|
390
|
+
# since `context` is memoized on first call and must reflect the actual @record once found.
|
|
391
|
+
scope = definition.policy_scope.call(scope, _context(nil)) if definition.policy_scope.present?
|
|
392
|
+
|
|
393
|
+
@record = if scope.respond_to?(:filter_2)
|
|
394
|
+
scope.filter_2(definition.default_filter).find(@pk)
|
|
395
|
+
else
|
|
396
|
+
scope.find(@pk)
|
|
397
|
+
end
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# *************************************************************************
|
|
401
|
+
# Sanitizers
|
|
402
|
+
# *************************************************************************
|
|
403
|
+
|
|
404
|
+
def sanitize_record_params
|
|
405
|
+
write_fields = (definition.fields + definition.write_only_fields).uniq
|
|
406
|
+
# `ActionController::Parameters#permit` silently drops a Hash value for a bare field name
|
|
407
|
+
# (e.g. a `file_attr`'s `{filename:, content:}` or `{upload_key:}` payload) unless its
|
|
408
|
+
# nested keys are explicitly permitted - expand those fields into a nested permit spec.
|
|
409
|
+
permit_spec = write_fields.map { |field| record_param_permit_spec(field) }
|
|
410
|
+
params.permit(*permit_spec)
|
|
411
|
+
end
|
|
412
|
+
|
|
413
|
+
def record_param_permit_spec(field)
|
|
414
|
+
# Hash entries (`{ name => subfields }`) are already a nested permit spec - keep intact.
|
|
415
|
+
return field if field.is_a?(::Hash)
|
|
416
|
+
|
|
417
|
+
# Normalize Symbol/String/FieldReference entries so `permit` never receives a FieldReference.
|
|
418
|
+
name = Habarico::AttributesHelper.field_name(field)
|
|
419
|
+
# `ActionController::Parameters#permit` only allows scalar values for a bare field name -
|
|
420
|
+
# a Hash value (e.g. a `file_attr` field's base64 `{filename:, content:}` or chunked-upload
|
|
421
|
+
# `{upload_key:}` payload) is silently dropped unless its nested keys are explicitly
|
|
422
|
+
# permitted. Expand file_attr fields into a nested permit spec; leave every other field as-is.
|
|
423
|
+
if definition.model.respond_to?(:has_file_attr?) && definition.model.has_file_attr?(name)
|
|
424
|
+
{ name => %i[filename content upload_key] }
|
|
425
|
+
else
|
|
426
|
+
name
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
|
|
430
|
+
def complement_record_params
|
|
431
|
+
{} # override in subclasses if needed
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def sanitize_filter_params
|
|
435
|
+
pk = definition.model.primary_key.to_sym
|
|
436
|
+
filters = definition.filters + [pk, :"#{pk}__in"]
|
|
437
|
+
|
|
438
|
+
permitted_params = definition.model.permitted_filter_params
|
|
439
|
+
selected_params = permitted_params.select { |f| filters.include?(filter_param_to_sym(f)) }
|
|
440
|
+
selected_keys = selected_params.map { |p| filter_param_to_sym(p) }
|
|
441
|
+
|
|
442
|
+
disallowed = (params.keys.map(&:to_sym) & permitted_params.map { |p| filter_param_to_sym(p) }) - selected_keys - %i[filter search]
|
|
443
|
+
raise_filter_errors(disallowed) if disallowed.any?
|
|
444
|
+
|
|
445
|
+
if params.key?(:filter)
|
|
446
|
+
filter_term_keys = definition.model.parse_filter_term(params[:filter].to_s, raise_on_invalid: false).symbolize_keys.keys
|
|
447
|
+
disallowed_terms = filter_term_keys.map(&:to_sym) - selected_keys - [:search]
|
|
448
|
+
raise_filter_errors(disallowed_terms) if disallowed_terms.any?
|
|
449
|
+
end
|
|
450
|
+
|
|
451
|
+
params.permit(selected_params + %i[filter search])
|
|
452
|
+
end
|
|
453
|
+
|
|
454
|
+
def filter_param_to_sym(param)
|
|
455
|
+
param.is_a?(Hash) ? param.keys.first : param.to_sym
|
|
456
|
+
end
|
|
457
|
+
|
|
458
|
+
# Pulls the search out of the filter params: stashes the effective query in `@search_query` and
|
|
459
|
+
# returns the params with the search removed (so it never reaches the base library's `filter_2!`). Both
|
|
460
|
+
# transport forms (`:search` param and a `search="..."` term in the `:filter` string) are handled
|
|
461
|
+
# by `Habarico::FilterParamsHelper`.
|
|
462
|
+
def extract_search!(filter_params)
|
|
463
|
+
@search_query = Habarico::FilterParamsHelper.extract(filter_params, :search)
|
|
464
|
+
Habarico::FilterParamsHelper.strip(filter_params, :search)
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
def raise_filter_errors(keys)
|
|
468
|
+
errors = keys.to_h { |k| [k.to_s, I18n.t('habarico.errors.filter.unknown_term', default: 'Unknown filter term.')] }
|
|
469
|
+
raise Eluvia::Errors::UnprocessableEntity.new(errors)
|
|
470
|
+
end
|
|
471
|
+
|
|
472
|
+
# *************************************************************************
|
|
473
|
+
# Context
|
|
474
|
+
# *************************************************************************
|
|
475
|
+
|
|
476
|
+
def _context(record)
|
|
477
|
+
Habarico::Context.new(definition: definition, current_user: current_user,
|
|
478
|
+
record_pk: record&.id, record: record)
|
|
479
|
+
end
|
|
480
|
+
|
|
481
|
+
def context
|
|
482
|
+
@context ||= _context(@record)
|
|
483
|
+
end
|
|
484
|
+
|
|
485
|
+
# *************************************************************************
|
|
486
|
+
# Restrictions
|
|
487
|
+
# *************************************************************************
|
|
488
|
+
|
|
489
|
+
def is_create_possible?
|
|
490
|
+
true # override in subclasses if needed
|
|
491
|
+
end
|
|
492
|
+
|
|
493
|
+
def is_update_possible?(_record)
|
|
494
|
+
true # override in subclasses if needed
|
|
495
|
+
end
|
|
496
|
+
|
|
497
|
+
def is_delete_possible?(_record)
|
|
498
|
+
true # override in subclasses if needed
|
|
499
|
+
end
|
|
500
|
+
|
|
501
|
+
# *************************************************************************
|
|
502
|
+
# Class objects
|
|
503
|
+
# *************************************************************************
|
|
504
|
+
|
|
505
|
+
def definition
|
|
506
|
+
self.class.definition
|
|
507
|
+
end
|
|
508
|
+
|
|
509
|
+
def serializer
|
|
510
|
+
self.class.serializer
|
|
511
|
+
end
|
|
512
|
+
|
|
513
|
+
def meta_serializer
|
|
514
|
+
self.class.meta_serializer
|
|
515
|
+
end
|
|
516
|
+
|
|
517
|
+
def record_serializer
|
|
518
|
+
self.class.record_serializer
|
|
519
|
+
end
|
|
520
|
+
|
|
521
|
+
class_methods do
|
|
522
|
+
|
|
523
|
+
# ***********************************************************************
|
|
524
|
+
# Definition
|
|
525
|
+
# ***********************************************************************
|
|
526
|
+
|
|
527
|
+
def definition
|
|
528
|
+
@definition ||= Habarico::Definition.new
|
|
529
|
+
end
|
|
530
|
+
|
|
531
|
+
Habarico::Definition.attrs.each do |attr|
|
|
532
|
+
define_method(attr) do |value = nil, &block|
|
|
533
|
+
definition.send("#{attr}=", value) unless value.nil?
|
|
534
|
+
definition.send("#{attr}=", block) unless block.nil?
|
|
535
|
+
end
|
|
536
|
+
end
|
|
537
|
+
|
|
538
|
+
# ***********************************************************************
|
|
539
|
+
# Serializer
|
|
540
|
+
# ***********************************************************************
|
|
541
|
+
|
|
542
|
+
def serializer
|
|
543
|
+
@serializer ||= Habarico::Serializers::Serializer.new(definition)
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
def meta_serializer
|
|
547
|
+
@meta_serializer ||= Habarico::Serializers::MetaSerializer.new
|
|
548
|
+
end
|
|
549
|
+
|
|
550
|
+
def record_serializer
|
|
551
|
+
@record_serializer ||= Habarico::Serializers::RecordSerializer.new(serializer: serializer, meta_serializer: meta_serializer)
|
|
552
|
+
end
|
|
553
|
+
|
|
554
|
+
# ***********************************************************************
|
|
555
|
+
# Registered actions
|
|
556
|
+
# ***********************************************************************
|
|
557
|
+
|
|
558
|
+
def registered_actions
|
|
559
|
+
@registered_actions ||= []
|
|
560
|
+
end
|
|
561
|
+
|
|
562
|
+
def register_action(name, is_detail: true, method: :put, model: nil, permission: :read, &proc)
|
|
563
|
+
self.registered_actions << {
|
|
564
|
+
name: name,
|
|
565
|
+
is_detail: is_detail,
|
|
566
|
+
model: model,
|
|
567
|
+
permission: permission,
|
|
568
|
+
method: method
|
|
569
|
+
}
|
|
570
|
+
define_method(name) do
|
|
571
|
+
set_record if is_detail
|
|
572
|
+
raise Eluvia::Errors::StandardError.new("Action `#{name}` is not correctly implemented.") unless proc.respond_to?(:call)
|
|
573
|
+
raise Eluvia::Errors::Forbidden.new("Action `#{name}` not allowed.") unless context.can?(permission)
|
|
574
|
+
response = self.instance_exec(&proc)
|
|
575
|
+
response = Habarico::Response.new(message: response) if response.is_a?(::String)
|
|
576
|
+
response = Habarico::Response.new unless response.is_a?(Habarico::Response)
|
|
577
|
+
render_response(response)
|
|
578
|
+
end
|
|
579
|
+
end
|
|
580
|
+
|
|
581
|
+
end
|
|
582
|
+
|
|
583
|
+
end
|
|
584
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Habarico
|
|
2
|
+
class ActionReference
|
|
3
|
+
include DefinitionModel
|
|
4
|
+
|
|
5
|
+
attr_accessor :name, :label, :icon, :color, :action, :if, :permission, :menu_items, :view_mode, :dialog_width, :view_choices, :view_parameters, :view_keep_linked_parent
|
|
6
|
+
|
|
7
|
+
def initialize(params = {})
|
|
8
|
+
@permission = case params[:name].to_s
|
|
9
|
+
when 'create' then :create
|
|
10
|
+
when 'edit' then :update
|
|
11
|
+
when 'delete' then :delete
|
|
12
|
+
else :read
|
|
13
|
+
end
|
|
14
|
+
super(params)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module Habarico
|
|
2
|
+
class Action
|
|
3
|
+
include DefinitionModel
|
|
4
|
+
|
|
5
|
+
attr_accessor :type, :label, :icon, :color, :is_detail, :name, :show_in_detail, :show_in_list,
|
|
6
|
+
:confirm_dialog_title, :confirm_dialog_message, :permission
|
|
7
|
+
attr_accessor :view_name, :view_mode, :dialog_width, :view_parameters, :view_keep_linked_parent # attributes for type = :view
|
|
8
|
+
attr_accessor :endpoint_action_name, :endpoint_method, :endpoint_reference # attributes for type = :view or :resource
|
|
9
|
+
attr_accessor :is_background, :data # attributes for type = :resource
|
|
10
|
+
attr_accessor :link_url, :link_target, :if # attributes for type = :link
|
|
11
|
+
attr_accessor :upload_images_reference, :upload_images_target, :upload_images_accept, :upload_images_image_field # attributes for type = :"upload-images"
|
|
12
|
+
|
|
13
|
+
end
|
|
14
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Habarico
|
|
2
|
+
class CreateAction < ViewAction
|
|
3
|
+
include DefinitionModel
|
|
4
|
+
|
|
5
|
+
def initialize(params = {})
|
|
6
|
+
@name = 'create'
|
|
7
|
+
@icon = 'Add'
|
|
8
|
+
@is_detail = false # -> not tied to a specific resource instance
|
|
9
|
+
@show_in_detail = false # -> do not show in detail views
|
|
10
|
+
@show_in_list = true # -> show in list views (on top of the list)
|
|
11
|
+
@view_name = 'create' # -> connect it to the Habarico::CreateView or any other view named 'edit'
|
|
12
|
+
@view_mode = :redirect # -> open the view on the new page
|
|
13
|
+
@permission = :create
|
|
14
|
+
super(params)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
end
|
|
18
|
+
end
|