ruby_cms 0.1.0
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/.cursor/dhh.mdc +698 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/README.md +235 -0
- data/Rakefile +30 -0
- data/app/components/ruby_cms/admin/admin_page/admin_table_content.rb +32 -0
- data/app/components/ruby_cms/admin/admin_page.rb +345 -0
- data/app/components/ruby_cms/admin/base_component.rb +78 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table.rb +149 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_actions.rb +127 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_body.rb +15 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_checkbox_cell.rb +41 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_checkbox_head.rb +33 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_delete_modal.rb +174 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header.rb +59 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_header_bar.rb +159 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_pagination.rb +192 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_action_table_row.rb +97 -0
- data/app/components/ruby_cms/admin/bulk_action_table/bulk_actions.rb +137 -0
- data/app/controllers/concerns/ruby_cms/admin_pagination.rb +120 -0
- data/app/controllers/concerns/ruby_cms/admin_turbo_table.rb +68 -0
- data/app/controllers/concerns/ruby_cms/page_tracking.rb +52 -0
- data/app/controllers/concerns/ruby_cms/visitor_error_capture.rb +39 -0
- data/app/controllers/ruby_cms/admin/analytics_controller.rb +191 -0
- data/app/controllers/ruby_cms/admin/base_controller.rb +105 -0
- data/app/controllers/ruby_cms/admin/content_blocks_controller.rb +390 -0
- data/app/controllers/ruby_cms/admin/dashboard_controller.rb +50 -0
- data/app/controllers/ruby_cms/admin/locale_controller.rb +20 -0
- data/app/controllers/ruby_cms/admin/permissions_controller.rb +66 -0
- data/app/controllers/ruby_cms/admin/settings_controller.rb +223 -0
- data/app/controllers/ruby_cms/admin/user_permissions_controller.rb +59 -0
- data/app/controllers/ruby_cms/admin/users_controller.rb +107 -0
- data/app/controllers/ruby_cms/admin/visitor_errors_controller.rb +89 -0
- data/app/controllers/ruby_cms/admin/visual_editor_controller.rb +322 -0
- data/app/controllers/ruby_cms/errors_controller.rb +35 -0
- data/app/helpers/ruby_cms/admin/admin_page_helper.rb +21 -0
- data/app/helpers/ruby_cms/admin/bulk_action_table_helper.rb +159 -0
- data/app/helpers/ruby_cms/application_helper.rb +41 -0
- data/app/helpers/ruby_cms/bulk_action_table_helper.rb +151 -0
- data/app/helpers/ruby_cms/content_blocks_helper.rb +375 -0
- data/app/helpers/ruby_cms/settings_helper.rb +160 -0
- data/app/javascript/controllers/ruby_cms/auto_save_preference_controller.js +73 -0
- data/app/javascript/controllers/ruby_cms/bulk_action_table_controller.js +553 -0
- data/app/javascript/controllers/ruby_cms/clickable_row_controller.js +28 -0
- data/app/javascript/controllers/ruby_cms/flash_messages_controller.js +29 -0
- data/app/javascript/controllers/ruby_cms/index.js +104 -0
- data/app/javascript/controllers/ruby_cms/locale_tabs_controller.js +34 -0
- data/app/javascript/controllers/ruby_cms/mobile_menu_controller.js +55 -0
- data/app/javascript/controllers/ruby_cms/nav_order_sortable_controller.js +192 -0
- data/app/javascript/controllers/ruby_cms/page_preview_controller.js +135 -0
- data/app/javascript/controllers/ruby_cms/toggle_controller.js +39 -0
- data/app/javascript/controllers/ruby_cms/visual_editor_controller.js +321 -0
- data/app/models/concerns/content_block/publishable.rb +54 -0
- data/app/models/concerns/content_block/searchable.rb +22 -0
- data/app/models/content_block.rb +155 -0
- data/app/models/ruby_cms/content_block.rb +8 -0
- data/app/models/ruby_cms/permission.rb +28 -0
- data/app/models/ruby_cms/permittable.rb +39 -0
- data/app/models/ruby_cms/preference.rb +111 -0
- data/app/models/ruby_cms/user_permission.rb +12 -0
- data/app/models/ruby_cms/visitor_error.rb +109 -0
- data/app/services/ruby_cms/analytics/report.rb +362 -0
- data/app/services/ruby_cms/security_tracker.rb +92 -0
- data/app/views/layouts/ruby_cms/_admin_flash_messages.html.erb +37 -0
- data/app/views/layouts/ruby_cms/_admin_sidebar.html.erb +121 -0
- data/app/views/layouts/ruby_cms/admin.html.erb +81 -0
- data/app/views/layouts/ruby_cms/minimal.html.erb +181 -0
- data/app/views/ruby_cms/admin/analytics/index.html.erb +160 -0
- data/app/views/ruby_cms/admin/analytics/page_details.html.erb +84 -0
- data/app/views/ruby_cms/admin/analytics/partials/_back_button.html.erb +3 -0
- data/app/views/ruby_cms/admin/analytics/partials/_browser_device.html.erb +40 -0
- data/app/views/ruby_cms/admin/analytics/partials/_daily_activity_chart.html.erb +58 -0
- data/app/views/ruby_cms/admin/analytics/partials/_hourly_activity_chart.html.erb +51 -0
- data/app/views/ruby_cms/admin/analytics/partials/_recent_activity.html.erb +31 -0
- data/app/views/ruby_cms/admin/analytics/partials/_security_alert.html.erb +4 -0
- data/app/views/ruby_cms/admin/analytics/partials/_top_referrers.html.erb +21 -0
- data/app/views/ruby_cms/admin/analytics/visitor_details.html.erb +125 -0
- data/app/views/ruby_cms/admin/content_blocks/_form.html.erb +161 -0
- data/app/views/ruby_cms/admin/content_blocks/_row.html.erb +25 -0
- data/app/views/ruby_cms/admin/content_blocks/edit.html.erb +17 -0
- data/app/views/ruby_cms/admin/content_blocks/index.html.erb +66 -0
- data/app/views/ruby_cms/admin/content_blocks/new.html.erb +5 -0
- data/app/views/ruby_cms/admin/content_blocks/show.html.erb +110 -0
- data/app/views/ruby_cms/admin/dashboard/index.html.erb +198 -0
- data/app/views/ruby_cms/admin/permissions/_row.html.erb +11 -0
- data/app/views/ruby_cms/admin/permissions/index.html.erb +62 -0
- data/app/views/ruby_cms/admin/settings/index.html.erb +220 -0
- data/app/views/ruby_cms/admin/shared/_bulk_action_table_index.html.erb +56 -0
- data/app/views/ruby_cms/admin/user_permissions/index.html.erb +55 -0
- data/app/views/ruby_cms/admin/users/_row.html.erb +14 -0
- data/app/views/ruby_cms/admin/users/index.html.erb +70 -0
- data/app/views/ruby_cms/admin/visitor_errors/_row.html.erb +35 -0
- data/app/views/ruby_cms/admin/visitor_errors/index.html.erb +57 -0
- data/app/views/ruby_cms/admin/visitor_errors/show.html.erb +147 -0
- data/app/views/ruby_cms/admin/visual_editor/index.html.erb +144 -0
- data/app/views/ruby_cms/errors/not_found.html.erb +92 -0
- data/config/database.yml +6 -0
- data/config/importmap.rb +36 -0
- data/config/locales/en.yml +101 -0
- data/config/routes.rb +65 -0
- data/db/migrate/20260125000001_create_ruby_cms_permissions.rb +14 -0
- data/db/migrate/20260125000002_create_ruby_cms_user_permissions.rb +14 -0
- data/db/migrate/20260125000003_create_ruby_cms_content_blocks.rb +19 -0
- data/db/migrate/20260125000010_add_indexes_to_ruby_cms_tables.rb +9 -0
- data/db/migrate/20260127000001_add_locale_to_ruby_cms_content_blocks.rb +34 -0
- data/db/migrate/20260129000001_create_ruby_cms_visitor_errors.rb +24 -0
- data/db/migrate/20260130000001_add_referer_and_query_to_ruby_cms_visitor_errors.rb +8 -0
- data/db/migrate/20260130000002_create_ruby_cms_preferences.rb +16 -0
- data/db/migrate/20260130000003_add_category_to_ruby_cms_preferences.rb +8 -0
- data/db/migrate/20260211000001_add_ruby_cms_analytics_fields_to_ahoy_events.rb +19 -0
- data/db/migrate/20260212000001_use_unprefixed_cms_tables.rb +146 -0
- data/exe/ruby_cms +25 -0
- data/lib/generators/ruby_cms/install_generator.rb +1062 -0
- data/lib/generators/ruby_cms/templates/admin.html.erb +82 -0
- data/lib/generators/ruby_cms/templates/ruby_cms.rb +86 -0
- data/lib/ruby_cms/app_integration.rb +82 -0
- data/lib/ruby_cms/cli.rb +169 -0
- data/lib/ruby_cms/content_blocks_grouping.rb +41 -0
- data/lib/ruby_cms/content_blocks_sync.rb +329 -0
- data/lib/ruby_cms/css_compiler.rb +35 -0
- data/lib/ruby_cms/engine.rb +498 -0
- data/lib/ruby_cms/settings.rb +145 -0
- data/lib/ruby_cms/settings_registry.rb +289 -0
- data/lib/ruby_cms/version.rb +5 -0
- data/lib/ruby_cms.rb +195 -0
- data/lib/tasks/ruby_cms.rake +27 -0
- data/log/test.log +17875 -0
- data/sig/ruby_cms.rbs +4 -0
- metadata +223 -0
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyCms
|
|
4
|
+
module Admin
|
|
5
|
+
class SettingsController < BaseController
|
|
6
|
+
before_action { require_permission!(:manage_admin) }
|
|
7
|
+
|
|
8
|
+
def index
|
|
9
|
+
RubyCms::Settings.ensure_defaults!
|
|
10
|
+
|
|
11
|
+
@registry_entries = sorted_registry_entries
|
|
12
|
+
@categories = @registry_entries.map {|e| e.category.to_s }.uniq
|
|
13
|
+
@active_tab = resolve_active_tab(params[:tab], @categories)
|
|
14
|
+
@entries_for_tab = @registry_entries.select {|entry| entry.category.to_s == @active_tab }
|
|
15
|
+
|
|
16
|
+
@values = @entries_for_tab.to_h do |entry|
|
|
17
|
+
[entry.key, RubyCms::Settings.get(entry.key, default: entry.default)]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def update
|
|
22
|
+
updated_keys = apply_updates(extract_updates)
|
|
23
|
+
updated_keys = apply_nav_order_update(updated_keys)
|
|
24
|
+
|
|
25
|
+
respond_with_update_success(updated_keys)
|
|
26
|
+
rescue StandardError => e
|
|
27
|
+
respond_with_update_failure(e)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def reset_defaults
|
|
31
|
+
RubyCms::SettingsRegistry.seed_defaults!
|
|
32
|
+
|
|
33
|
+
RubyCms::SettingsRegistry.each do |entry|
|
|
34
|
+
RubyCms::Settings.set(entry.key, entry.default)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
redirect_to ruby_cms_admin_settings_path(redirect_settings_params),
|
|
38
|
+
notice: t("ruby_cms.admin.settings.defaults_reset")
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Dedicated endpoint for saving nav order only. Reads raw JSON body; writes directly to preferences table.
|
|
42
|
+
def update_nav_order
|
|
43
|
+
order = nav_order_from_raw_body
|
|
44
|
+
unless order.kind_of?(Array) && order.any?
|
|
45
|
+
return render json: {
|
|
46
|
+
success: false,
|
|
47
|
+
error: "nav_order_main and nav_order_bottom required"
|
|
48
|
+
},
|
|
49
|
+
status: :unprocessable_content
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
rec = RubyCms::Preference.find_or_initialize_by(key: "nav_order")
|
|
53
|
+
rec.category = "navigation" if rec.new_record?
|
|
54
|
+
rec.value_type = "json"
|
|
55
|
+
rec.value = order.map(&:to_s).to_json
|
|
56
|
+
rec.save!
|
|
57
|
+
render json: { success: true, updated_keys: ["nav_order"], updated_count: 1 }
|
|
58
|
+
rescue StandardError => e
|
|
59
|
+
render json: { success: false, error: e.message }, status: :unprocessable_content
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
def nav_order_from_raw_body
|
|
65
|
+
request.body.rewind if request.body.respond_to?(:rewind)
|
|
66
|
+
body = request.body.read
|
|
67
|
+
return [] if body.blank?
|
|
68
|
+
|
|
69
|
+
data = JSON.parse(body)
|
|
70
|
+
nav_order_from_raw_hash(data)
|
|
71
|
+
rescue JSON::ParserError
|
|
72
|
+
[]
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def redirect_settings_params
|
|
76
|
+
{ tab: params[:tab].presence || default_tab }.tap do |h|
|
|
77
|
+
h[:nav_sub] = params[:nav_sub].presence if params[:tab].to_s == "navigation"
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Persist nav order so it survives reload. Uses Preference directly so we hit the same row Settings.get reads.
|
|
82
|
+
def persist_nav_order(order)
|
|
83
|
+
return unless order.kind_of?(Array) && order.any?
|
|
84
|
+
|
|
85
|
+
RubyCms::Preference.set("nav_order", order)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# For JSON PATCH, Rails may wrap body under :settings (or leave at root). Body stream can be
|
|
89
|
+
# consumed by the parser so we try params first, then rewind and read raw body.
|
|
90
|
+
def nav_order_arrays_from_request
|
|
91
|
+
main = nav_order_param(:nav_order_main)
|
|
92
|
+
bottom = nav_order_param(:nav_order_bottom)
|
|
93
|
+
if main.nil? && bottom.nil? && request.content_mime_type&.symbol == :json
|
|
94
|
+
data = parsed_json_body
|
|
95
|
+
main, bottom = nav_order_arrays_from_json(data) if data
|
|
96
|
+
end
|
|
97
|
+
[
|
|
98
|
+
main.kind_of?(Array) ? main.map(&:to_s) : Array(main).map(&:to_s),
|
|
99
|
+
bottom.kind_of?(Array) ? bottom.map(&:to_s) : Array(bottom).map(&:to_s)
|
|
100
|
+
]
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def nav_order_param(key)
|
|
104
|
+
key_s = key.to_s
|
|
105
|
+
# Root (symbol or string)
|
|
106
|
+
params[key].presence || params[key_s].presence ||
|
|
107
|
+
# Common Rails JSON wrapper
|
|
108
|
+
params.dig(:settings, key).presence || params.dig(:settings, key_s).presence ||
|
|
109
|
+
params.dig("settings", key).presence || params.dig("settings", key_s).presence
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def parsed_json_body
|
|
113
|
+
body = nil
|
|
114
|
+
if request.body.respond_to?(:rewind)
|
|
115
|
+
request.body.rewind
|
|
116
|
+
body = request.body.read
|
|
117
|
+
end
|
|
118
|
+
body = request.raw_post if body.blank? && body != false
|
|
119
|
+
return nil if body.blank?
|
|
120
|
+
|
|
121
|
+
JSON.parse(body)
|
|
122
|
+
rescue JSON::ParserError
|
|
123
|
+
nil
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def sorted_registry_entries
|
|
127
|
+
RubyCms::SettingsRegistry
|
|
128
|
+
.entries
|
|
129
|
+
.values
|
|
130
|
+
.sort_by {|entry| [entry.category.to_s, entry.key.to_s] }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def resolve_active_tab(tab_param, categories)
|
|
134
|
+
requested = tab_param.to_s
|
|
135
|
+
return requested if categories.include?(requested)
|
|
136
|
+
|
|
137
|
+
categories.first || "general"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def default_tab
|
|
141
|
+
sorted_registry_entries.first&.category || "general"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def extract_updates
|
|
145
|
+
if params[:preferences].present?
|
|
146
|
+
params.require(:preferences).to_unsafe_h
|
|
147
|
+
elsif params[:key].present?
|
|
148
|
+
{ params[:key].to_s => params[:value] }
|
|
149
|
+
else
|
|
150
|
+
{}
|
|
151
|
+
end
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def apply_updates(updates)
|
|
155
|
+
updates.filter_map do |key, value|
|
|
156
|
+
entry = RubyCms::SettingsRegistry.fetch(key)
|
|
157
|
+
next unless entry
|
|
158
|
+
|
|
159
|
+
RubyCms::Settings.set(entry.key, value)
|
|
160
|
+
entry.key
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def apply_nav_order_update(updated_keys)
|
|
165
|
+
nav_main, nav_bottom = nav_order_arrays_from_request
|
|
166
|
+
return updated_keys if nav_main.blank? && nav_bottom.blank?
|
|
167
|
+
|
|
168
|
+
order = (nav_main + nav_bottom).map(&:to_s)
|
|
169
|
+
persist_nav_order(order)
|
|
170
|
+
updated_keys + ["nav_order"]
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def respond_with_update_success(updated_keys)
|
|
174
|
+
respond_to do |format|
|
|
175
|
+
format.html do
|
|
176
|
+
redirect_to ruby_cms_admin_settings_path(redirect_settings_params),
|
|
177
|
+
notice: t("ruby_cms.admin.settings.updated_many",
|
|
178
|
+
default: "#{updated_keys.size} setting(s) updated.")
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
format.json do
|
|
182
|
+
render json: {
|
|
183
|
+
success: true,
|
|
184
|
+
updated_keys: updated_keys,
|
|
185
|
+
updated_count: updated_keys.size
|
|
186
|
+
}
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def respond_with_update_failure(error)
|
|
192
|
+
respond_to do |format|
|
|
193
|
+
format.html do
|
|
194
|
+
redirect_to ruby_cms_admin_settings_path(redirect_settings_params),
|
|
195
|
+
alert: error.message
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
format.json do
|
|
199
|
+
render json: { success: false, error: error.message }, status: :unprocessable_content
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
def nav_order_from_raw_hash(data)
|
|
205
|
+
main = data["nav_order_main"]
|
|
206
|
+
bottom = data["nav_order_bottom"]
|
|
207
|
+
main = Array(main).map(&:to_s) if main
|
|
208
|
+
bottom = Array(bottom).map(&:to_s) if bottom
|
|
209
|
+
(main || []) + (bottom || [])
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
def nav_order_arrays_from_json(data)
|
|
213
|
+
main = data.dig("settings", "nav_order_main") ||
|
|
214
|
+
data["nav_order_main"].presence ||
|
|
215
|
+
data[:nav_order_main].presence
|
|
216
|
+
bottom = data.dig("settings", "nav_order_bottom") ||
|
|
217
|
+
data["nav_order_bottom"].presence ||
|
|
218
|
+
data[:nav_order_bottom].presence
|
|
219
|
+
[main, bottom]
|
|
220
|
+
end
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyCms
|
|
4
|
+
module Admin
|
|
5
|
+
class UserPermissionsController < BaseController
|
|
6
|
+
before_action { require_permission!(:manage_permissions) }
|
|
7
|
+
before_action :set_user
|
|
8
|
+
|
|
9
|
+
def index
|
|
10
|
+
@permissions = RubyCms::Permission.order(:key)
|
|
11
|
+
@user_permissions = if @user
|
|
12
|
+
RubyCms::UserPermission.where(user: @user)
|
|
13
|
+
.includes(:permission)
|
|
14
|
+
else
|
|
15
|
+
[]
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create
|
|
20
|
+
permission = RubyCms::Permission.find(params[:permission_id])
|
|
21
|
+
if RubyCms::UserPermission.find_or_create_by!(user: @user, permission: permission)
|
|
22
|
+
redirect_to ruby_cms_admin_user_permissions_path(@user),
|
|
23
|
+
notice: t("ruby_cms.admin.user_permissions.granted")
|
|
24
|
+
end
|
|
25
|
+
rescue ActiveRecord::RecordInvalid
|
|
26
|
+
redirect_to ruby_cms_admin_user_permissions_path(@user),
|
|
27
|
+
alert: t("ruby_cms.admin.user_permissions.could_not_grant")
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def destroy
|
|
31
|
+
up = RubyCms::UserPermission.find_by!(user: @user, id: params[:id])
|
|
32
|
+
up.destroy
|
|
33
|
+
redirect_to ruby_cms_admin_user_permissions_path(@user),
|
|
34
|
+
notice: t("ruby_cms.admin.user_permissions.revoked")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def bulk_delete
|
|
38
|
+
ids = Array(params[:item_ids]).filter_map(&:to_i)
|
|
39
|
+
user_permissions = RubyCms::UserPermission.where(user: @user, id: ids)
|
|
40
|
+
count = user_permissions.count
|
|
41
|
+
user_permissions.destroy_all
|
|
42
|
+
redirect_to ruby_cms_admin_user_permissions_path(@user),
|
|
43
|
+
notice: "#{count} permission(s) #{
|
|
44
|
+
t('ruby_cms.admin.user_permissions.revoked')
|
|
45
|
+
}."
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def set_user
|
|
51
|
+
@user = user_class.find(params[:user_id])
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def user_class
|
|
55
|
+
Object.const_get(Rails.application.config.ruby_cms.user_class_name.presence || "User")
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyCms
|
|
4
|
+
module Admin
|
|
5
|
+
class UsersController < BaseController
|
|
6
|
+
include RubyCms::AdminPagination
|
|
7
|
+
include RubyCms::AdminTurboTable
|
|
8
|
+
|
|
9
|
+
paginates per_page: -> { RubyCms::Preference.get(:users_per_page, default: 50) },
|
|
10
|
+
turbo_frame: "admin_table_content"
|
|
11
|
+
|
|
12
|
+
before_action { require_permission!(:manage_permissions) }
|
|
13
|
+
|
|
14
|
+
def index
|
|
15
|
+
@users = paginated_users
|
|
16
|
+
@index ||= user_class.none
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def create
|
|
20
|
+
user = user_class.new(user_params)
|
|
21
|
+
if user.save
|
|
22
|
+
redirect_to ruby_cms_admin_users_path, notice: t("ruby_cms.admin.users.created")
|
|
23
|
+
else
|
|
24
|
+
handle_create_failure(user)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def destroy
|
|
29
|
+
user = user_class.find(params[:id])
|
|
30
|
+
user.destroy
|
|
31
|
+
redirect_to ruby_cms_admin_users_path, notice: t("ruby_cms.admin.users.deleted")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def bulk_delete
|
|
35
|
+
ids = Array(params[:item_ids]).filter_map(&:to_i)
|
|
36
|
+
users = user_class.where(id: ids)
|
|
37
|
+
count = users.count
|
|
38
|
+
users.destroy_all
|
|
39
|
+
turbo_redirect_to ruby_cms_admin_users_path, notice: "#{count} user(s) #{
|
|
40
|
+
t('ruby_cms.admin.users.deleted')
|
|
41
|
+
}."
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def paginated_users
|
|
47
|
+
collection = user_class.order(:id)
|
|
48
|
+
collection = apply_search_filter(collection)
|
|
49
|
+
paginate_collection(collection)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def apply_search_filter(collection)
|
|
53
|
+
return collection if params[:q].blank?
|
|
54
|
+
|
|
55
|
+
collection.where(build_search_conditions)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def build_search_conditions
|
|
59
|
+
search_term = "%#{params[:q].downcase}%"
|
|
60
|
+
email_attr = user_email_column
|
|
61
|
+
conditions = ["LOWER(#{email_attr}) LIKE ?"]
|
|
62
|
+
values = [search_term]
|
|
63
|
+
|
|
64
|
+
if numeric_query?
|
|
65
|
+
conditions << "id = ?"
|
|
66
|
+
values << params[:q].to_i
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
[conditions.join(" OR "), *values]
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def user_email_column
|
|
73
|
+
user_class.column_names.include?("email_address") ? :email_address : :email
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def numeric_query?
|
|
77
|
+
params[:q] =~ /^\d+$/
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def handle_create_failure(user)
|
|
81
|
+
@users = user_class.order(:id).limit(100)
|
|
82
|
+
flash.now[:alert] = t(
|
|
83
|
+
"ruby_cms.admin.users.could_not_create",
|
|
84
|
+
errors: user.errors.full_messages.to_sentence
|
|
85
|
+
)
|
|
86
|
+
render :index, status: :unprocessable_content
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def user_class
|
|
90
|
+
Object.const_get(Rails.application.config.ruby_cms.user_class_name.presence || "User")
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def user_params
|
|
94
|
+
email_attr = user_class.column_names.include?("email_address") ? :email_address : :email
|
|
95
|
+
password_attrs = if user_class.column_names.include?("password")
|
|
96
|
+
%i[
|
|
97
|
+
password
|
|
98
|
+
password_confirmation
|
|
99
|
+
]
|
|
100
|
+
else
|
|
101
|
+
[]
|
|
102
|
+
end
|
|
103
|
+
params.expect(user: [email_attr, *password_attrs])
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyCms
|
|
4
|
+
module Admin
|
|
5
|
+
class VisitorErrorsController < BaseController
|
|
6
|
+
include RubyCms::AdminPagination
|
|
7
|
+
include RubyCms::AdminTurboTable
|
|
8
|
+
|
|
9
|
+
paginates per_page: -> { RubyCms::Preference.get(:visitor_errors_per_page, default: 25) },
|
|
10
|
+
turbo_frame: "admin_table_content"
|
|
11
|
+
|
|
12
|
+
before_action { require_permission!(:manage_visitor_errors) }
|
|
13
|
+
before_action :set_visitor_error, only: %i[show resolve]
|
|
14
|
+
|
|
15
|
+
def index
|
|
16
|
+
scope = RubyCms::VisitorError.recent
|
|
17
|
+
scope = scope.where(resolved: params[:resolved] == "true") if params[:resolved].present?
|
|
18
|
+
scope = apply_search_filter(scope)
|
|
19
|
+
scope = apply_error_type_filter(scope)
|
|
20
|
+
@visitor_errors = set_pagination_vars(scope)
|
|
21
|
+
render_index
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def show; end
|
|
25
|
+
|
|
26
|
+
def resolve
|
|
27
|
+
@visitor_error.update!(resolved: true)
|
|
28
|
+
redirect_to ruby_cms_admin_visitor_errors_path,
|
|
29
|
+
notice: t("ruby_cms.admin.visitor_errors.resolved")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def bulk_delete
|
|
33
|
+
ids = Array(params[:item_ids]).filter_map(&:to_i)
|
|
34
|
+
count = RubyCms::VisitorError.where(id: ids).destroy_all.size
|
|
35
|
+
redirect_to ruby_cms_admin_visitor_errors_path,
|
|
36
|
+
notice: t("ruby_cms.admin.visitor_errors.bulk_deleted", count:)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def bulk_mark_as_resolved
|
|
40
|
+
ids = Array(params[:item_ids]).filter_map(&:to_i)
|
|
41
|
+
count = mark_visitor_errors_resolved(ids)
|
|
42
|
+
redirect_to ruby_cms_admin_visitor_errors_path,
|
|
43
|
+
notice: t("ruby_cms.admin.visitor_errors.bulk_resolved", count:)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def set_visitor_error
|
|
49
|
+
@visitor_error = RubyCms::VisitorError.find(params[:id])
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def apply_search_filter(scope)
|
|
53
|
+
return scope if params[:search].blank?
|
|
54
|
+
|
|
55
|
+
term = sanitize_search_term(params[:search])
|
|
56
|
+
scope.where("request_path ILIKE ?", "%#{term}%")
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def apply_error_type_filter(scope)
|
|
60
|
+
return scope if params[:error_type].blank?
|
|
61
|
+
|
|
62
|
+
term = sanitize_search_term(params[:error_type])
|
|
63
|
+
scope.where("error_class ILIKE ?", "%#{term}%")
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def sanitize_search_term(term)
|
|
67
|
+
term.to_s.strip.gsub(/[%_\\]/, "").truncate(100)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def mark_visitor_errors_resolved(ids)
|
|
71
|
+
scope = RubyCms::VisitorError.where(id: ids)
|
|
72
|
+
count = 0
|
|
73
|
+
scope.find_each do |record|
|
|
74
|
+
record.update!(resolved: true)
|
|
75
|
+
count += 1
|
|
76
|
+
end
|
|
77
|
+
count
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def render_index
|
|
81
|
+
if turbo_frame_request?
|
|
82
|
+
render :index, layout: false
|
|
83
|
+
else
|
|
84
|
+
render :index
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|