motor-admin-cstham8 0.4.35
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +661 -0
- data/README.md +230 -0
- data/Rakefile +11 -0
- data/app/channels/motor/application_cable/channel.rb +14 -0
- data/app/channels/motor/application_cable/connection.rb +27 -0
- data/app/channels/motor/notes_channel.rb +9 -0
- data/app/channels/motor/notifications_channel.rb +9 -0
- data/app/controllers/concerns/motor/current_ability.rb +21 -0
- data/app/controllers/concerns/motor/current_user_method.rb +18 -0
- data/app/controllers/concerns/motor/load_and_authorize_dynamic_resource.rb +73 -0
- data/app/controllers/concerns/motor/wrap_io_params.rb +25 -0
- data/app/controllers/motor/active_storage_attachments_controller.rb +64 -0
- data/app/controllers/motor/alerts_controller.rb +82 -0
- data/app/controllers/motor/api_base_controller.rb +33 -0
- data/app/controllers/motor/api_configs_controller.rb +54 -0
- data/app/controllers/motor/application_controller.rb +8 -0
- data/app/controllers/motor/assets_controller.rb +43 -0
- data/app/controllers/motor/audits_controller.rb +16 -0
- data/app/controllers/motor/auth_tokens_controller.rb +36 -0
- data/app/controllers/motor/configs_controller.rb +33 -0
- data/app/controllers/motor/dashboards_controller.rb +64 -0
- data/app/controllers/motor/data_controller.rb +88 -0
- data/app/controllers/motor/forms_controller.rb +61 -0
- data/app/controllers/motor/icons_controller.rb +22 -0
- data/app/controllers/motor/note_tags_controller.rb +13 -0
- data/app/controllers/motor/notes_controller.rb +58 -0
- data/app/controllers/motor/notifications_controller.rb +33 -0
- data/app/controllers/motor/queries_controller.rb +64 -0
- data/app/controllers/motor/reminders_controller.rb +38 -0
- data/app/controllers/motor/resource_default_queries_controller.rb +23 -0
- data/app/controllers/motor/resource_methods_controller.rb +23 -0
- data/app/controllers/motor/resources_controller.rb +26 -0
- data/app/controllers/motor/run_api_requests_controller.rb +56 -0
- data/app/controllers/motor/run_graphql_requests_controller.rb +48 -0
- data/app/controllers/motor/run_queries_controller.rb +77 -0
- data/app/controllers/motor/schema_controller.rb +31 -0
- data/app/controllers/motor/send_alerts_controller.rb +26 -0
- data/app/controllers/motor/sessions_controller.rb +23 -0
- data/app/controllers/motor/slack_conversations_controller.rb +11 -0
- data/app/controllers/motor/tags_controller.rb +11 -0
- data/app/controllers/motor/ui_controller.rb +51 -0
- data/app/controllers/motor/users_for_autocomplete_controller.rb +23 -0
- data/app/jobs/motor/alert_sending_job.rb +13 -0
- data/app/jobs/motor/application_job.rb +6 -0
- data/app/jobs/motor/notify_note_mentions_job.rb +9 -0
- data/app/jobs/motor/notify_reminder_job.rb +9 -0
- data/app/mailers/motor/alerts_mailer.rb +39 -0
- data/app/mailers/motor/application_mailer.rb +33 -0
- data/app/mailers/motor/notifications_mailer.rb +33 -0
- data/app/models/motor/alert.rb +30 -0
- data/app/models/motor/alert_lock.rb +7 -0
- data/app/models/motor/api_config.rb +28 -0
- data/app/models/motor/application_record.rb +18 -0
- data/app/models/motor/audit.rb +13 -0
- data/app/models/motor/config.rb +13 -0
- data/app/models/motor/dashboard.rb +26 -0
- data/app/models/motor/form.rb +23 -0
- data/app/models/motor/note.rb +18 -0
- data/app/models/motor/note_tag.rb +7 -0
- data/app/models/motor/note_tag_tag.rb +8 -0
- data/app/models/motor/notification.rb +14 -0
- data/app/models/motor/query.rb +33 -0
- data/app/models/motor/reminder.rb +13 -0
- data/app/models/motor/resource.rb +15 -0
- data/app/models/motor/tag.rb +7 -0
- data/app/models/motor/taggable_tag.rb +8 -0
- data/app/views/layouts/motor/application.html.erb +17 -0
- data/app/views/layouts/motor/mailer.html.erb +72 -0
- data/app/views/motor/alerts_mailer/alert_email.html.erb +54 -0
- data/app/views/motor/notifications_mailer/notify_mention_email.html.erb +28 -0
- data/app/views/motor/notifications_mailer/notify_reminder_email.html.erb +28 -0
- data/app/views/motor/ui/show.html.erb +1 -0
- data/config/locales/el.yml +420 -0
- data/config/locales/en.yml +340 -0
- data/config/locales/es.yml +420 -0
- data/config/locales/ja.yml +340 -0
- data/config/locales/pt.yml +416 -0
- data/config/routes.rb +65 -0
- data/lib/generators/motor/install_generator.rb +24 -0
- data/lib/generators/motor/install_notes_generator.rb +22 -0
- data/lib/generators/motor/migration.rb +17 -0
- data/lib/generators/motor/templates/install.rb +271 -0
- data/lib/generators/motor/templates/install_api_configs.rb +86 -0
- data/lib/generators/motor/templates/install_notes.rb +83 -0
- data/lib/generators/motor/templates/upgrade_motor_api_actions.rb +71 -0
- data/lib/generators/motor/upgrade_generator.rb +43 -0
- data/lib/motor/active_record_utils/action_text_attribute_patch.rb +19 -0
- data/lib/motor/active_record_utils/active_record_connection_column_patch.rb +14 -0
- data/lib/motor/active_record_utils/active_record_filter.rb +405 -0
- data/lib/motor/active_record_utils/active_storage_blob_patch.rb +30 -0
- data/lib/motor/active_record_utils/active_storage_links_extension.rb +11 -0
- data/lib/motor/active_record_utils/defined_scopes_extension.rb +25 -0
- data/lib/motor/active_record_utils/fetch_methods.rb +24 -0
- data/lib/motor/active_record_utils/types.rb +64 -0
- data/lib/motor/active_record_utils.rb +45 -0
- data/lib/motor/admin.rb +141 -0
- data/lib/motor/alerts/persistance.rb +97 -0
- data/lib/motor/alerts/scheduled_alerts_cache.rb +29 -0
- data/lib/motor/alerts/scheduler.rb +30 -0
- data/lib/motor/alerts/slack_sender.rb +74 -0
- data/lib/motor/alerts.rb +52 -0
- data/lib/motor/api_configs.rb +41 -0
- data/lib/motor/api_query/apply_scope.rb +44 -0
- data/lib/motor/api_query/build_json.rb +171 -0
- data/lib/motor/api_query/build_meta.rb +20 -0
- data/lib/motor/api_query/filter.rb +125 -0
- data/lib/motor/api_query/paginate.rb +19 -0
- data/lib/motor/api_query/search.rb +60 -0
- data/lib/motor/api_query/sort.rb +64 -0
- data/lib/motor/api_query.rb +24 -0
- data/lib/motor/assets.rb +62 -0
- data/lib/motor/build_schema/active_storage_attachment_schema.rb +125 -0
- data/lib/motor/build_schema/adjust_devise_model_schema.rb +60 -0
- data/lib/motor/build_schema/apply_permissions.rb +64 -0
- data/lib/motor/build_schema/defaults.rb +66 -0
- data/lib/motor/build_schema/find_display_column.rb +65 -0
- data/lib/motor/build_schema/find_icon.rb +135 -0
- data/lib/motor/build_schema/find_searchable_columns.rb +33 -0
- data/lib/motor/build_schema/load_from_rails.rb +361 -0
- data/lib/motor/build_schema/merge_schema_configs.rb +157 -0
- data/lib/motor/build_schema/reorder_schema.rb +88 -0
- data/lib/motor/build_schema/utils.rb +31 -0
- data/lib/motor/build_schema.rb +125 -0
- data/lib/motor/cancan_utils/ability_patch.rb +31 -0
- data/lib/motor/cancan_utils/can_manage_all.rb +14 -0
- data/lib/motor/cancan_utils.rb +9 -0
- data/lib/motor/configs/build_configs_hash.rb +90 -0
- data/lib/motor/configs/build_ui_app_tag.rb +177 -0
- data/lib/motor/configs/load_from_cache.rb +110 -0
- data/lib/motor/configs/sync_from_file.rb +35 -0
- data/lib/motor/configs/sync_from_hash.rb +159 -0
- data/lib/motor/configs/sync_middleware.rb +72 -0
- data/lib/motor/configs/sync_with_remote.rb +47 -0
- data/lib/motor/configs/write_to_file.rb +36 -0
- data/lib/motor/configs.rb +39 -0
- data/lib/motor/dashboards/persistance.rb +73 -0
- data/lib/motor/dashboards.rb +8 -0
- data/lib/motor/forms/persistance.rb +93 -0
- data/lib/motor/forms.rb +8 -0
- data/lib/motor/hash_serializer.rb +21 -0
- data/lib/motor/net_http_utils.rb +50 -0
- data/lib/motor/notes/notify_mentions.rb +71 -0
- data/lib/motor/notes/notify_reminder.rb +48 -0
- data/lib/motor/notes/persist.rb +36 -0
- data/lib/motor/notes/reminders_scheduler.rb +39 -0
- data/lib/motor/notes/tags.rb +34 -0
- data/lib/motor/notes.rb +12 -0
- data/lib/motor/queries/persistance.rb +90 -0
- data/lib/motor/queries/postgresql_exec_query.rb +28 -0
- data/lib/motor/queries/render_sql_template.rb +61 -0
- data/lib/motor/queries/run_query.rb +289 -0
- data/lib/motor/queries.rb +11 -0
- data/lib/motor/railtie.rb +11 -0
- data/lib/motor/resources/custom_sql_columns_cache.rb +17 -0
- data/lib/motor/resources/fetch_configured_model.rb +269 -0
- data/lib/motor/resources/persist_configs.rb +232 -0
- data/lib/motor/resources.rb +19 -0
- data/lib/motor/slack/client.rb +62 -0
- data/lib/motor/slack.rb +16 -0
- data/lib/motor/tags.rb +32 -0
- data/lib/motor/tasks/motor.rake +54 -0
- data/lib/motor/version.rb +5 -0
- data/lib/motor-admin-cstham8.rb +3 -0
- data/lib/motor.rb +87 -0
- data/ui/dist/manifest.json +1990 -0
- metadata +303 -0
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module ApiQuery
|
5
|
+
module Filter
|
6
|
+
LIKE_FILTER_VALUE_REGEXP = /\A%?(.*?)%?\z/.freeze
|
7
|
+
DISTINCT_RESTRICTED_COLUMN_TYPES = %i[json point].freeze
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def call(rel, params)
|
12
|
+
return rel if params.blank?
|
13
|
+
|
14
|
+
normalized_params = normalize_params(Array.wrap(params))
|
15
|
+
|
16
|
+
rel = apply_filters(rel, normalized_params)
|
17
|
+
rel = rel.distinct if can_apply_distinct?(rel)
|
18
|
+
|
19
|
+
rel
|
20
|
+
end
|
21
|
+
|
22
|
+
def clean_filters(value)
|
23
|
+
if value.class.name == 'ActionController::Parameters'
|
24
|
+
value.to_unsafe_h
|
25
|
+
elsif value.is_a?(Array)
|
26
|
+
value.map { |v| clean_filters(v) }
|
27
|
+
else
|
28
|
+
value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def apply_predicates(rel, filters)
|
33
|
+
joins = ActiveRecord::PredicateBuilder.filter_joins(rel.klass, filters)
|
34
|
+
|
35
|
+
joins.flatten.reduce(rel) do |acc, j|
|
36
|
+
if j.is_a?(String) || j.is_a?(Arel::Nodes::Join)
|
37
|
+
acc.joins(j)
|
38
|
+
elsif j.present?
|
39
|
+
acc.left_outer_joins(j)
|
40
|
+
else
|
41
|
+
acc
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def apply_filters(rel, filters)
|
47
|
+
filters = clean_filters(filters)
|
48
|
+
|
49
|
+
rel = apply_predicates(rel, filters)
|
50
|
+
|
51
|
+
alias_tracker = ActiveRecord::Associations::AliasTracker.create(rel.connection, rel.table.name, [])
|
52
|
+
filter_clause_factory = ActiveRecord::Relation::FilterClauseFactory.new(rel.klass, rel.predicate_builder)
|
53
|
+
|
54
|
+
where_clause = filter_clause_factory.build(filters, alias_tracker)
|
55
|
+
|
56
|
+
rel_values = rel.instance_variable_get(:@values)
|
57
|
+
|
58
|
+
if rel_values[:where]
|
59
|
+
rel_values[:where] += where_clause
|
60
|
+
else
|
61
|
+
rel_values[:where] = where_clause
|
62
|
+
end
|
63
|
+
|
64
|
+
rel
|
65
|
+
end
|
66
|
+
|
67
|
+
def normalize_params(params)
|
68
|
+
params.map do |item|
|
69
|
+
next item if item.is_a?(String)
|
70
|
+
next normalize_params(item) if item.is_a?(Array)
|
71
|
+
|
72
|
+
item = item.to_unsafe_h if item.respond_to?(:to_unsafe_h)
|
73
|
+
|
74
|
+
item.transform_values do |filter|
|
75
|
+
if filter.is_a?(Hash)
|
76
|
+
normalize_filter_hash(filter)
|
77
|
+
else
|
78
|
+
filter
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end.split('OR').product(['OR']).flatten(1)[0...-1]
|
82
|
+
end
|
83
|
+
|
84
|
+
def normalize_filter_hash(hash)
|
85
|
+
hash.each_with_object({}) do |(action, value), acc|
|
86
|
+
new_action, new_value =
|
87
|
+
if value.is_a?(Hash)
|
88
|
+
[action, normalize_filter_hash(value)]
|
89
|
+
else
|
90
|
+
normalize_action(action, value)
|
91
|
+
end
|
92
|
+
|
93
|
+
acc[new_action] = new_value
|
94
|
+
|
95
|
+
acc
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def can_apply_distinct?(rel)
|
100
|
+
rel.columns.none? do |column|
|
101
|
+
DISTINCT_RESTRICTED_COLUMN_TYPES.include?(column.type)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def normalize_action(action, value)
|
106
|
+
case action
|
107
|
+
when 'includes'
|
108
|
+
['contains', value]
|
109
|
+
when 'contains'
|
110
|
+
['ilike', value.sub(LIKE_FILTER_VALUE_REGEXP, '%\1%')]
|
111
|
+
when 'starts_with'
|
112
|
+
['ilike', value.sub(LIKE_FILTER_VALUE_REGEXP, '\1%')]
|
113
|
+
when 'ends_with'
|
114
|
+
['ilike', value.sub(LIKE_FILTER_VALUE_REGEXP, '%\1')]
|
115
|
+
when 'eqnull'
|
116
|
+
['eq', nil]
|
117
|
+
when 'neqnull'
|
118
|
+
['neq', nil]
|
119
|
+
else
|
120
|
+
[action, value]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module ApiQuery
|
5
|
+
module Paginate
|
6
|
+
MAX_PER_PAGE = 500
|
7
|
+
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def call(rel, params)
|
11
|
+
params ||= {}
|
12
|
+
|
13
|
+
rel = rel.limit([MAX_PER_PAGE, (params[:limit] || MAX_PER_PAGE).to_i].min)
|
14
|
+
|
15
|
+
rel.offset(params[:offset].to_i)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module ApiQuery
|
5
|
+
module Search
|
6
|
+
STRING_COLUMN_TYPES = %i[text string citext].freeze
|
7
|
+
NUMBER_COLUMN_TYPES = %i[integer float].freeze
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def call(rel, keyword)
|
12
|
+
return rel if keyword.blank?
|
13
|
+
|
14
|
+
filters = fetch_filters(rel, keyword)
|
15
|
+
|
16
|
+
arel_where = build_arel_or_query(filters)
|
17
|
+
|
18
|
+
rel.where(arel_where)
|
19
|
+
end
|
20
|
+
|
21
|
+
def fetch_filters(rel, keyword)
|
22
|
+
klass = rel.klass
|
23
|
+
|
24
|
+
selected_columns = find_searchable_columns(klass)
|
25
|
+
|
26
|
+
build_arel_filters(klass, selected_columns, keyword)
|
27
|
+
end
|
28
|
+
|
29
|
+
def build_arel_filters(model, column_names, keyword)
|
30
|
+
arel_table = model.arel_table
|
31
|
+
|
32
|
+
column_names.map do |name|
|
33
|
+
column_type = model.columns_hash[name]&.type
|
34
|
+
|
35
|
+
next unless column_type
|
36
|
+
|
37
|
+
if STRING_COLUMN_TYPES.include?(column_type)
|
38
|
+
arel_table[name].matches("%#{keyword}%")
|
39
|
+
elsif NUMBER_COLUMN_TYPES.include?(column_type)
|
40
|
+
arel_table[name].eq(keyword.to_f) if keyword.match?(/\A\d/)
|
41
|
+
else
|
42
|
+
arel_table[name].eq(keyword)
|
43
|
+
end
|
44
|
+
end.compact
|
45
|
+
end
|
46
|
+
|
47
|
+
def build_arel_or_query(filter_array)
|
48
|
+
filter_array.reduce(nil) do |acc, filter|
|
49
|
+
next acc = filter unless acc
|
50
|
+
|
51
|
+
acc.or(filter)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def find_searchable_columns(model)
|
56
|
+
model.try(:motor_searchable_columns) || Motor::BuildSchema::FindSearchableColumns.call(model)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module ApiQuery
|
5
|
+
module Sort
|
6
|
+
FIELD_PARSE_REGEXP = /\A(-)?(.*)\z/.freeze
|
7
|
+
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def call(rel, param)
|
11
|
+
return rel if param.blank?
|
12
|
+
|
13
|
+
arel_order = build_arel_order(rel.klass, param)
|
14
|
+
join_params = build_join_params(rel.klass, param)
|
15
|
+
|
16
|
+
rel = rel.left_joins(join_params) if join_params.present?
|
17
|
+
|
18
|
+
rel.reorder(arel_order)
|
19
|
+
end
|
20
|
+
|
21
|
+
def build_join_params(_model, param)
|
22
|
+
param.split(',').each_with_object({}) do |field, result|
|
23
|
+
key = field[FIELD_PARSE_REGEXP, 2]
|
24
|
+
*path, _ = key.split('.')
|
25
|
+
|
26
|
+
path.reduce(result) do |acc, fragment|
|
27
|
+
hash = {}
|
28
|
+
|
29
|
+
acc[fragment] = hash
|
30
|
+
|
31
|
+
hash
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def build_arel_order(model, param)
|
37
|
+
param.split(',').map do |field|
|
38
|
+
direction, key = field.match(FIELD_PARSE_REGEXP).captures
|
39
|
+
*path, field = key.split('.')
|
40
|
+
|
41
|
+
reflection_model =
|
42
|
+
path.reduce(model) do |acc, fragment|
|
43
|
+
acc.reflections[fragment].klass
|
44
|
+
end
|
45
|
+
|
46
|
+
arel_column = reflection_model.arel_table[field]
|
47
|
+
|
48
|
+
arel_direction = direction.present? ? arel_column.desc : arel_column.asc
|
49
|
+
|
50
|
+
maybe_add_null_last(model, arel_direction)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def maybe_add_null_last(model, arel_direction)
|
55
|
+
if arel_direction.respond_to?(:nulls_last) &&
|
56
|
+
model.connection.class.name == 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
|
57
|
+
arel_direction.nulls_last
|
58
|
+
else
|
59
|
+
arel_direction
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module ApiQuery
|
5
|
+
module_function
|
6
|
+
|
7
|
+
def call(rel, params)
|
8
|
+
rel = ApiQuery::Sort.call(rel, params[:sort] || params[:order])
|
9
|
+
rel = ApiQuery::Paginate.call(rel, params[:page])
|
10
|
+
rel = ApiQuery::Filter.call(rel, params[:filter] || params[:filters])
|
11
|
+
rel = ApiQuery::ApplyScope.call(rel, params[:scope])
|
12
|
+
|
13
|
+
ApiQuery::Search.call(rel, params[:q] || params[:search] || params[:query])
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
require_relative 'api_query/sort'
|
19
|
+
require_relative 'api_query/paginate'
|
20
|
+
require_relative 'api_query/filter'
|
21
|
+
require_relative 'api_query/search'
|
22
|
+
require_relative 'api_query/apply_scope'
|
23
|
+
require_relative 'api_query/build_meta'
|
24
|
+
require_relative 'api_query/build_json'
|
data/lib/motor/assets.rb
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module Assets
|
5
|
+
InvalidPathError = Class.new(StandardError)
|
6
|
+
|
7
|
+
ASSETS_PATH = Pathname.new(__dir__).join('../../ui/dist')
|
8
|
+
MANIFEST_PATH = ASSETS_PATH.join('manifest.json')
|
9
|
+
DEV_SERVER_URL = 'http://localhost:9090/'
|
10
|
+
|
11
|
+
CACHE_STORE =
|
12
|
+
if Rails.env.production?
|
13
|
+
ActiveSupport::Cache::MemoryStore.new(size: 5.megabytes)
|
14
|
+
else
|
15
|
+
ActiveSupport::Cache::NullStore.new
|
16
|
+
end
|
17
|
+
|
18
|
+
module_function
|
19
|
+
|
20
|
+
def manifest
|
21
|
+
CACHE_STORE.fetch('manifest') do
|
22
|
+
JSON.parse(MANIFEST_PATH.read)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def icons
|
27
|
+
manifest.select do |k, v|
|
28
|
+
!k.ends_with?('.gz') && v.starts_with?('icons/') && v.exclude?('DS_Store')
|
29
|
+
end.keys
|
30
|
+
end
|
31
|
+
|
32
|
+
def asset_path(path)
|
33
|
+
Motor::Admin.routes.url_helpers.motor_asset_path(manifest[path])
|
34
|
+
end
|
35
|
+
|
36
|
+
def load_asset(filename, gzip: false)
|
37
|
+
if Motor.development?
|
38
|
+
load_from_dev_server(filename)
|
39
|
+
else
|
40
|
+
load_from_disk(filename, gzip: gzip)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def load_from_disk(filename, gzip:)
|
45
|
+
filename += '.gz' if gzip
|
46
|
+
|
47
|
+
raise InvalidPathError if filename.include?('..')
|
48
|
+
|
49
|
+
path = ASSETS_PATH.join(filename)
|
50
|
+
|
51
|
+
raise InvalidPathError unless path.to_s.starts_with?(ASSETS_PATH.to_s)
|
52
|
+
|
53
|
+
path.read
|
54
|
+
end
|
55
|
+
|
56
|
+
def load_from_dev_server(filename)
|
57
|
+
uri = URI(DEV_SERVER_URL + filename)
|
58
|
+
|
59
|
+
Net::HTTP.get_response(uri).body
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module BuildSchema
|
5
|
+
module ActiveStorageAttachmentSchema
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# rubocop:disable Metrics/MethodLength
|
9
|
+
def call
|
10
|
+
model = ActiveStorage::Attachment
|
11
|
+
|
12
|
+
{
|
13
|
+
name: model.name.underscore,
|
14
|
+
slug: Utils.slugify(model),
|
15
|
+
class_name: model.name,
|
16
|
+
table_name: model.table_name,
|
17
|
+
primary_key: model.primary_key,
|
18
|
+
display_name: model.model_name.human(count: 2, default: 'Attachments'),
|
19
|
+
display_column: 'id',
|
20
|
+
icon: 'paperclip',
|
21
|
+
columns: [
|
22
|
+
{
|
23
|
+
name: 'id',
|
24
|
+
display_name: 'ID',
|
25
|
+
column_type: 'integer',
|
26
|
+
access_type: 'read_only',
|
27
|
+
default_value: nil,
|
28
|
+
validators: [],
|
29
|
+
format: {},
|
30
|
+
is_array: false,
|
31
|
+
reference: nil,
|
32
|
+
virtual: false
|
33
|
+
},
|
34
|
+
{
|
35
|
+
name: 'path',
|
36
|
+
display_name: model.human_attribute_name(:path),
|
37
|
+
column_type: 'string',
|
38
|
+
access_type: 'read_only',
|
39
|
+
default_value: nil,
|
40
|
+
validators: [],
|
41
|
+
format: {},
|
42
|
+
is_array: false,
|
43
|
+
reference: nil,
|
44
|
+
virtual: true
|
45
|
+
},
|
46
|
+
{
|
47
|
+
name: 'name',
|
48
|
+
display_name: model.human_attribute_name(:name),
|
49
|
+
column_type: 'string',
|
50
|
+
access_type: 'read_write',
|
51
|
+
default_value: nil,
|
52
|
+
validators: [],
|
53
|
+
format: {},
|
54
|
+
is_array: false,
|
55
|
+
reference: nil,
|
56
|
+
virtual: false
|
57
|
+
},
|
58
|
+
{
|
59
|
+
name: 'record_type',
|
60
|
+
display_name: model.human_attribute_name(:record_type),
|
61
|
+
column_type: 'string',
|
62
|
+
access_type: 'read_write',
|
63
|
+
default_value: nil,
|
64
|
+
validators: [],
|
65
|
+
format: {},
|
66
|
+
is_array: false,
|
67
|
+
reference: nil,
|
68
|
+
virtual: false
|
69
|
+
},
|
70
|
+
{
|
71
|
+
name: 'record_id',
|
72
|
+
display_name: model.human_attribute_name(:record),
|
73
|
+
column_type: 'integer',
|
74
|
+
access_type: 'read_write',
|
75
|
+
default_value: nil,
|
76
|
+
validators: [],
|
77
|
+
format: {},
|
78
|
+
is_array: false,
|
79
|
+
reference: {
|
80
|
+
name: 'record',
|
81
|
+
display_name: model.human_attribute_name(:record),
|
82
|
+
model_name: nil,
|
83
|
+
reference_type: 'belongs_to',
|
84
|
+
foreign_key: 'record_id',
|
85
|
+
polymorphic: true
|
86
|
+
},
|
87
|
+
virtual: false
|
88
|
+
},
|
89
|
+
{
|
90
|
+
name: 'file',
|
91
|
+
display_name: model.human_attribute_name(:file),
|
92
|
+
column_type: 'file',
|
93
|
+
access_type: 'write_only',
|
94
|
+
default_value: nil,
|
95
|
+
validators: [],
|
96
|
+
format: {},
|
97
|
+
is_array: false,
|
98
|
+
reference: nil,
|
99
|
+
virtual: false
|
100
|
+
},
|
101
|
+
{
|
102
|
+
name: 'created_at',
|
103
|
+
display_name: model.human_attribute_name(:created_at),
|
104
|
+
column_type: 'datetime',
|
105
|
+
access_type: 'read_only',
|
106
|
+
default_value: nil,
|
107
|
+
validators: [],
|
108
|
+
format: {},
|
109
|
+
is_array: false,
|
110
|
+
reference: nil,
|
111
|
+
virtual: false
|
112
|
+
}
|
113
|
+
],
|
114
|
+
associations: [],
|
115
|
+
scopes: [],
|
116
|
+
preferences: {},
|
117
|
+
actions: Motor::BuildSchema::Defaults.actions.reject { |e| e[:name] == 'edit' },
|
118
|
+
tabs: Motor::BuildSchema::Defaults.tabs,
|
119
|
+
visible: false
|
120
|
+
}.with_indifferent_access
|
121
|
+
end
|
122
|
+
# rubocop:enable Metrics/MethodLength
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module BuildSchema
|
5
|
+
module AdjustDeviseModelSchema
|
6
|
+
HIDDEN_COLUMNS = %w[
|
7
|
+
encrypted_password
|
8
|
+
reset_password_token
|
9
|
+
confirmation_token
|
10
|
+
].freeze
|
11
|
+
|
12
|
+
READ_ONLY_COLUMNS = %w[
|
13
|
+
reset_password_sent_at
|
14
|
+
remember_created_at
|
15
|
+
sign_in_count
|
16
|
+
current_sign_in_at
|
17
|
+
last_sign_in_at
|
18
|
+
current_sign_in_ip
|
19
|
+
last_sign_in_ip
|
20
|
+
confirmed_at
|
21
|
+
confirmation_sent_at
|
22
|
+
].freeze
|
23
|
+
|
24
|
+
module_function
|
25
|
+
|
26
|
+
def call(schema, devise_modules)
|
27
|
+
modify_column_access_types!(schema[:columns])
|
28
|
+
add_password_column!(schema[:columns]) if devise_modules.include?(:database_authenticatable)
|
29
|
+
|
30
|
+
schema
|
31
|
+
end
|
32
|
+
|
33
|
+
def modify_column_access_types!(columns)
|
34
|
+
columns.each do |column|
|
35
|
+
column[:access_type] =
|
36
|
+
case column[:name]
|
37
|
+
when *HIDDEN_COLUMNS
|
38
|
+
ColumnAccessTypes::HIDDEN
|
39
|
+
when *READ_ONLY_COLUMNS
|
40
|
+
ColumnAccessTypes::READ_ONLY
|
41
|
+
else
|
42
|
+
column[:access_type]
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def add_password_column!(columns)
|
48
|
+
columns << {
|
49
|
+
name: 'password',
|
50
|
+
display_name: I18n.t('motor.password'),
|
51
|
+
column_type: 'string',
|
52
|
+
access_type: 'write_only',
|
53
|
+
default_value: nil,
|
54
|
+
validators: [],
|
55
|
+
virtual: true
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module BuildSchema
|
5
|
+
module ApplyPermissions
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def call(schema, ability)
|
9
|
+
schema.map do |model|
|
10
|
+
klass = model[:class_name].constantize
|
11
|
+
|
12
|
+
next unless ability.can?(:read, klass)
|
13
|
+
|
14
|
+
model[:associations] = filter_associations(model[:associations], ability)
|
15
|
+
model[:columns] = filter_columns(klass, model[:columns], ability)
|
16
|
+
model[:actions] = filter_actions(klass, model[:actions], ability)
|
17
|
+
model[:tabs] = filter_tabs(klass, model[:tabs], ability)
|
18
|
+
|
19
|
+
model
|
20
|
+
end.compact
|
21
|
+
end
|
22
|
+
|
23
|
+
def filter_associations(associations, ability)
|
24
|
+
associations.select do |assoc|
|
25
|
+
model_class = assoc[:model_name].classify.safe_constantize
|
26
|
+
|
27
|
+
model_class && ability.can?(:read, model_class)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def filter_tabs(_model, tabs, ability)
|
32
|
+
tabs = tabs.reject { |t| t[:name] == 'audits' } unless ability.can?(:read, Motor::Audit)
|
33
|
+
tabs = tabs.reject { |t| t[:name] == 'notes' } unless ability.can?(:read, Motor::Note)
|
34
|
+
|
35
|
+
tabs
|
36
|
+
end
|
37
|
+
|
38
|
+
def filter_columns(model, columns, ability)
|
39
|
+
columns.map do |column|
|
40
|
+
next unless ability.can?(:read, model, column[:name])
|
41
|
+
|
42
|
+
reference_model_name = column.dig(:reference, :model_name)
|
43
|
+
model_class = reference_model_name&.classify&.safe_constantize
|
44
|
+
|
45
|
+
next if reference_model_name &&
|
46
|
+
(model_class.nil? || !ability.can?(:read, model_class))
|
47
|
+
|
48
|
+
if !ability.can?(:update, model, column[:name]) &&
|
49
|
+
column[:access_type] != BuildSchema::ColumnAccessTypes::HIDDEN
|
50
|
+
column = column.merge(access_type: BuildSchema::ColumnAccessTypes::READ_ONLY)
|
51
|
+
end
|
52
|
+
|
53
|
+
column
|
54
|
+
end.compact
|
55
|
+
end
|
56
|
+
|
57
|
+
def filter_actions(model, actions, ability)
|
58
|
+
actions.select do |action|
|
59
|
+
ability.can?(action[:name].to_sym, model)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Motor
|
4
|
+
module BuildSchema
|
5
|
+
module Defaults
|
6
|
+
module_function
|
7
|
+
|
8
|
+
# rubocop:disable Metrics/MethodLength
|
9
|
+
def actions
|
10
|
+
[
|
11
|
+
{
|
12
|
+
name: 'create',
|
13
|
+
display_name: I18n.t('motor.create'),
|
14
|
+
action_type: BuildSchema::DEFAULT_TYPE,
|
15
|
+
preferences: {},
|
16
|
+
apply_on: 'collection',
|
17
|
+
visible: true
|
18
|
+
},
|
19
|
+
{
|
20
|
+
name: 'edit',
|
21
|
+
display_name: I18n.t('motor.edit'),
|
22
|
+
action_type: BuildSchema::DEFAULT_TYPE,
|
23
|
+
preferences: {},
|
24
|
+
apply_on: 'member',
|
25
|
+
visible: true
|
26
|
+
},
|
27
|
+
{
|
28
|
+
name: 'remove',
|
29
|
+
display_name: I18n.t('motor.remove'),
|
30
|
+
action_type: BuildSchema::DEFAULT_TYPE,
|
31
|
+
preferences: {},
|
32
|
+
apply_on: 'member',
|
33
|
+
visible: true
|
34
|
+
}
|
35
|
+
].freeze
|
36
|
+
end
|
37
|
+
|
38
|
+
def tabs
|
39
|
+
[
|
40
|
+
{
|
41
|
+
name: 'details',
|
42
|
+
display_name: I18n.t('motor.details'),
|
43
|
+
tab_type: BuildSchema::DEFAULT_TYPE,
|
44
|
+
preferences: {},
|
45
|
+
visible: true
|
46
|
+
},
|
47
|
+
{
|
48
|
+
name: 'audits',
|
49
|
+
display_name: I18n.t('motor.audits'),
|
50
|
+
tab_type: BuildSchema::DEFAULT_TYPE,
|
51
|
+
preferences: {},
|
52
|
+
visible: true
|
53
|
+
},
|
54
|
+
{
|
55
|
+
name: 'notes',
|
56
|
+
display_name: I18n.t('motor.notes'),
|
57
|
+
tab_type: BuildSchema::DEFAULT_TYPE,
|
58
|
+
preferences: {},
|
59
|
+
visible: true
|
60
|
+
}
|
61
|
+
].freeze
|
62
|
+
end
|
63
|
+
# rubocop:enable Metrics/MethodLength
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|